Merge "veyron/security/audit: Implementation of an "auditing" Principal that logs all private key operations."
diff --git a/security/audit/id.go b/security/audit/id.go
index 0c0cdc4..b251ecc 100644
--- a/security/audit/id.go
+++ b/security/audit/id.go
@@ -12,8 +12,6 @@
auditor Auditor
}
-type args []interface{}
-
// NewPrivateID returns a security.PrivateID implementation that wraps over 'wrapped' but
// logs all operations that use the private key of wrapped to the auditor.
func NewPrivateID(wrapped security.PrivateID, auditor Auditor) security.PrivateID {
diff --git a/security/audit/id_test.go b/security/audit/id_test.go
index 8d01746..e655b01 100644
--- a/security/audit/id_test.go
+++ b/security/audit/id_test.go
@@ -99,7 +99,7 @@
if want := (audit.Entry{
Method: test.Method,
Arguments: []interface{}(test.Args),
- Results: sliceOrNil(test.AuditedResult),
+ Results: sliceOrNil(true, test.AuditedResult),
Timestamp: audited.Timestamp, // Hard to come up with the expected timestamp, relying on sanity check above.
}); !reflect.DeepEqual(audited, want) {
t.Errorf("id.%v(%#v) resulted in [%#v] being audited, wanted [%#v]", test.Method, test.Args, audited, want)
@@ -107,14 +107,7 @@
}
}
-func sliceOrNil(item interface{}) []interface{} {
- if item == nil {
- return nil
- }
- return []interface{}{item}
-}
-
-func TestUnauditedMethods(t *testing.T) {
+func TestUnauditedMethodsOnPrivateID(t *testing.T) {
var (
mockID = new(mockID)
mockAuditor = new(mockAuditor)
@@ -213,29 +206,6 @@
func (id *mockID) PublicKey() security.PublicKey { return id.NextResult.(security.PublicKey) }
-type mockAuditor struct {
- LastEntry audit.Entry
- NextError error
-}
-
-func (a *mockAuditor) Audit(entry audit.Entry) error {
- if a.NextError != nil {
- err := a.NextError
- a.NextError = nil
- return err
- }
- a.LastEntry = entry
- return nil
-}
-
-func (a *mockAuditor) Release() audit.Entry {
- entry := a.LastEntry
- a.LastEntry = audit.Entry{}
- return entry
-}
-
-type V []interface{}
-
// thirdPartyCaveat implements security.ThirdPartyCaveat
type thirdPartyCaveat struct{}
@@ -247,6 +217,12 @@
}
func (thirdPartyCaveat) Dischargeable(security.Context) error { return nil }
+// discharge implements the security.Discharge interface
+type discharge struct{}
+
+func (*discharge) ID() string { return "thirdPartyCaveatID" }
+func (*discharge) ThirdPartyCaveats() []security.ThirdPartyCaveat { return nil }
+
// context implements security.Context
type context struct{}
@@ -255,32 +231,10 @@
func (context) Suffix() string { return "suffix" }
func (context) Label() security.Label { return security.ReadLabel }
func (context) Discharges() map[string]security.Discharge { return nil }
-func (context) LocalID() security.PublicID { return nil }
-func (context) RemoteID() security.PublicID { return nil }
func (context) LocalPrincipal() security.Principal { return nil }
func (context) LocalBlessings() security.Blessings { return nil }
func (context) RemoteBlessings() security.Blessings { return nil }
+func (context) LocalID() security.PublicID { return nil }
+func (context) RemoteID() security.PublicID { return nil }
func (context) LocalEndpoint() naming.Endpoint { return nil }
func (context) RemoteEndpoint() naming.Endpoint { return nil }
-
-// discharge implements the security.Discharge interface
-type discharge struct{}
-
-func (*discharge) ID() string { return "thirdPartyCaveatID" }
-func (*discharge) ThirdPartyCaveats() []security.ThirdPartyCaveat { return nil }
-
-func call(receiver interface{}, method string, args V) (results []interface{}, err interface{}) {
- defer func() {
- err = recover()
- }()
- callargs := make([]reflect.Value, len(args))
- for idx, arg := range args {
- callargs[idx] = reflect.ValueOf(arg)
- }
- callresults := reflect.ValueOf(receiver).MethodByName(method).Call(callargs)
- results = make([]interface{}, len(callresults))
- for idx, res := range callresults {
- results[idx] = res.Interface()
- }
- return
-}
diff --git a/security/audit/principal.go b/security/audit/principal.go
new file mode 100644
index 0000000..c7c2e50
--- /dev/null
+++ b/security/audit/principal.go
@@ -0,0 +1,85 @@
+package audit
+
+import (
+ "fmt"
+ "time"
+
+ "veyron.io/veyron/veyron2/security"
+)
+
+// NewPrincipal returns a security.Principal implementation that logs
+// all private key operations of 'wrapped' to 'auditor' (i.e., all calls to
+// BlessSelf, Bless, MintDischarge and Sign).
+func NewPrincipal(wrapped security.Principal, auditor Auditor) security.Principal {
+ return &auditingPrincipal{wrapped, auditor}
+}
+
+type auditingPrincipal struct {
+ principal security.Principal
+ auditor Auditor
+}
+
+type args []interface{}
+
+func (p *auditingPrincipal) Bless(key security.PublicKey, with security.Blessings, extension string, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Blessings, error) {
+ blessings, err := p.principal.Bless(key, with, extension, caveat, additionalCaveats...)
+ if err = p.audit(err, "Bless", addCaveats(args{key, with, extension, caveat}, additionalCaveats...), blessings); err != nil {
+ return nil, err
+ }
+ return blessings, nil
+}
+
+func (p *auditingPrincipal) BlessSelf(name string, caveats ...security.Caveat) (security.Blessings, error) {
+ blessings, err := p.principal.BlessSelf(name, caveats...)
+ if err = p.audit(err, "BlessSelf", addCaveats(args{name}, caveats...), blessings); err != nil {
+ return nil, err
+ }
+ return blessings, nil
+}
+
+func (p *auditingPrincipal) Sign(message []byte) (security.Signature, error) {
+ // Do not save the signature itself.
+ sig, err := p.principal.Sign(message)
+ if err = p.audit(err, "Sign", args{message}, nil); err != nil {
+ return security.Signature{}, err
+ }
+ return sig, nil
+}
+
+func (p *auditingPrincipal) MintDischarge(tp security.ThirdPartyCaveat, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Discharge, error) {
+ d, err := p.principal.MintDischarge(tp, caveat, additionalCaveats...)
+ // No need to log the discharge
+ if err = p.audit(err, "MintDischarge", addCaveats(args{tp, caveat}, additionalCaveats...), nil); err != nil {
+ return nil, err
+ }
+ return d, nil
+}
+
+func (p *auditingPrincipal) PublicKey() security.PublicKey { return p.principal.PublicKey() }
+func (p *auditingPrincipal) Roots() security.BlessingRoots { return p.principal.Roots() }
+func (p *auditingPrincipal) BlessingStore() security.BlessingStore { return p.principal.BlessingStore() }
+func (p *auditingPrincipal) AddToRoots(b security.Blessings) error { return p.principal.AddToRoots(b) }
+
+func (p *auditingPrincipal) audit(err error, method string, args args, result interface{}) error {
+ if err != nil {
+ return err
+ }
+ entry := Entry{Method: method, Timestamp: time.Now()}
+ if len(args) > 0 {
+ entry.Arguments = []interface{}(args)
+ }
+ if result != nil {
+ entry.Results = []interface{}{result}
+ }
+ if err := p.auditor.Audit(entry); err != nil {
+ return fmt.Errorf("failed to audit call to %q: %v", method, err)
+ }
+ return nil
+}
+
+func addCaveats(args args, caveats ...security.Caveat) args {
+ for _, c := range caveats {
+ args = append(args, c)
+ }
+ return args
+}
diff --git a/security/audit/principal_test.go b/security/audit/principal_test.go
new file mode 100644
index 0000000..ba3d356
--- /dev/null
+++ b/security/audit/principal_test.go
@@ -0,0 +1,272 @@
+package audit_test
+
+import (
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "errors"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "veyron.io/veyron/veyron/security/audit"
+ "veyron.io/veyron/veyron2/security"
+)
+
+func TestAuditingPrincipal(t *testing.T) {
+ var (
+ thirdPartyCaveat, discharge = newThirdPartyCaveatAndDischarge(t)
+ wantErr = errors.New("call failed") // The error returned by call calls to mockID operations
+
+ mockP = new(mockPrincipal)
+ auditor = new(mockAuditor)
+ p = audit.NewPrincipal(mockP, auditor)
+ )
+ tests := []struct {
+ Method string
+ Args V
+ Result interface{} // Result returned by the Method call.
+ AuditResult bool // If true, Result should appear in the audit log. If false, it should not.
+ }{
+ {"BlessSelf", V{"self"}, newBlessing(t, "blessing"), true},
+ {"Bless", V{newPrincipal(t).PublicKey(), newBlessing(t, "root"), "extension", security.UnconstrainedUse()}, newBlessing(t, "root/extension"), true},
+ {"MintDischarge", V{thirdPartyCaveat, security.UnconstrainedUse()}, discharge, false},
+ {"Sign", V{make([]byte, 10)}, security.Signature{R: []byte{1}, S: []byte{1}}, false},
+ }
+ for _, test := range tests {
+ // Test1: If the underlying operation fails, the error should be returned and nothing should be audited.
+ mockP.NextError = wantErr
+ results, err := call(p, test.Method, test.Args)
+ if err != nil {
+ t.Errorf("failed to invoke p.%v(%#v): %v", test.Method, test.Args, err)
+ continue
+ }
+ if got, ok := results[len(results)-1].(error); !ok || got != wantErr {
+ t.Errorf("p.%v(%#v) returned (..., %v), want (..., %v)", test.Method, test.Args, got, wantErr)
+ }
+ if audited := auditor.Release(); !reflect.DeepEqual(audited, audit.Entry{}) {
+ t.Errorf("p.%v(%#v) resulted in [%+v] being written to the audit log, nothing should have been", test.Method, test.Args, audited)
+ }
+
+ // Test2: If the auditor fails, then the operation should fail too.
+ auditor.NextError = errors.New("auditor failed")
+ results, err = call(p, test.Method, test.Args)
+ if err != nil {
+ t.Errorf("failed to invoke p.%v(%#v): %v", test.Method, test.Args, err)
+ continue
+ }
+ if got, ok := results[len(results)-1].(error); !ok || !strings.HasSuffix(got.Error(), "auditor failed") {
+ t.Errorf("p.%v(%#v) returned %v when auditor failed, wanted (..., %v)", test.Method, test.Args, results, "... auditor failed")
+ }
+
+ // Test3: If the underlying operation succeeds, should return the same value and write to the audit log.
+ now := time.Now()
+ mockP.NextResult = test.Result
+ results, err = call(p, test.Method, test.Args)
+ audited := auditor.Release()
+ if err != nil {
+ t.Errorf("failed to invoke p.%v(%#v): %v", test.Method, test.Args, err)
+ continue
+ }
+ if got := results[len(results)-1]; got != nil {
+ t.Errorf("p.%v(%#v) returned an error: %v", test.Method, test.Args, got)
+ }
+ if got := results[0]; !reflect.DeepEqual(got, test.Result) {
+ t.Errorf("p.%v(%#v) returned %v(%T) want %v(%T)", test.Method, test.Args, got, got, test.Result, test.Result)
+ }
+ if audited.Timestamp.Before(now) || audited.Timestamp.IsZero() {
+ t.Errorf("p.%v(%#v) audited the time as %v, should have been a time after %v", test.Method, test.Args, audited.Timestamp, now)
+ }
+ if want := (audit.Entry{
+ Method: test.Method,
+ Arguments: []interface{}(test.Args),
+ Results: sliceOrNil(test.AuditResult, test.Result),
+ Timestamp: audited.Timestamp, // Hard to come up with the expected timestamp, relying on sanity check above.
+ }); !reflect.DeepEqual(audited, want) {
+ t.Errorf("p.%v(%#v) resulted in [%#v] being audited, wanted [%#v]", test.Method, test.Args, audited, want)
+ }
+ }
+}
+
+func TestUnauditedMethodsOnPrincipal(t *testing.T) {
+ var (
+ auditor = new(mockAuditor)
+ p = newPrincipal(t)
+ auditedP = audit.NewPrincipal(p, auditor)
+ )
+ blessing, err := p.BlessSelf("self")
+ if err != nil {
+ t.Fatal(err)
+ }
+ tests := []struct {
+ Method string
+ Args V
+ }{
+ {"PublicKey", V{}},
+ {"Roots", V{}},
+ {"AddToRoots", V{blessing}},
+ {"BlessingStore", V{}},
+ }
+
+ for _, test := range tests {
+ want, err := call(p, test.Method, test.Args)
+ if err != nil {
+ t.Fatalf("%v: %v", test.Method, err)
+ }
+ got, err := call(auditedP, test.Method, test.Args)
+ if err != nil {
+ t.Fatalf("%v: %v", test.Method, err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Got %v, want %v", got, want)
+ }
+ if gotEntry := auditor.Release(); !reflect.DeepEqual(gotEntry, audit.Entry{}) {
+ t.Errorf("Unexpected entry in audit log: %v", gotEntry)
+ }
+ }
+}
+
+type mockPrincipal struct {
+ NextResult interface{}
+ NextError error
+}
+
+func (p *mockPrincipal) reset() {
+ p.NextError = nil
+ p.NextResult = nil
+}
+
+func (p *mockPrincipal) Bless(security.PublicKey, security.Blessings, string, security.Caveat, ...security.Caveat) (security.Blessings, error) {
+ defer p.reset()
+ b, _ := p.NextResult.(security.Blessings)
+ return b, p.NextError
+}
+
+func (p *mockPrincipal) BlessSelf(string, ...security.Caveat) (security.Blessings, error) {
+ defer p.reset()
+ b, _ := p.NextResult.(security.Blessings)
+ return b, p.NextError
+}
+
+func (p *mockPrincipal) Sign([]byte) (sig security.Signature, err error) {
+ defer p.reset()
+ sig, _ = p.NextResult.(security.Signature)
+ err = p.NextError
+ return
+}
+
+func (p *mockPrincipal) MintDischarge(security.ThirdPartyCaveat, security.Caveat, ...security.Caveat) (security.Discharge, error) {
+ defer p.reset()
+ d, _ := p.NextResult.(security.Discharge)
+ return d, p.NextError
+}
+
+func (p *mockPrincipal) PublicKey() security.PublicKey { return p.NextResult.(security.PublicKey) }
+func (p *mockPrincipal) Roots() security.BlessingRoots { return nil }
+func (p *mockPrincipal) BlessingStore() security.BlessingStore { return nil }
+func (p *mockPrincipal) AddToRoots(b security.Blessings) error { return nil }
+
+type mockAuditor struct {
+ LastEntry audit.Entry
+ NextError error
+}
+
+func (a *mockAuditor) Audit(entry audit.Entry) error {
+ if a.NextError != nil {
+ err := a.NextError
+ a.NextError = nil
+ return err
+ }
+ a.LastEntry = entry
+ return nil
+}
+
+func (a *mockAuditor) Release() audit.Entry {
+ entry := a.LastEntry
+ a.LastEntry = audit.Entry{}
+ return entry
+}
+
+type V []interface{}
+
+func call(receiver interface{}, method string, args V) (results []interface{}, err interface{}) {
+ defer func() {
+ err = recover()
+ }()
+ callargs := make([]reflect.Value, len(args))
+ for idx, arg := range args {
+ callargs[idx] = reflect.ValueOf(arg)
+ }
+ callresults := reflect.ValueOf(receiver).MethodByName(method).Call(callargs)
+ results = make([]interface{}, len(callresults))
+ for idx, res := range callresults {
+ results[idx] = res.Interface()
+ }
+ return
+}
+
+func sliceOrNil(include bool, item interface{}) []interface{} {
+ if item != nil && include {
+ return []interface{}{item}
+ }
+ return nil
+}
+
+func newPrincipal(t *testing.T) security.Principal {
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ signer := security.NewInMemoryECDSASigner(key)
+ p, err := security.CreatePrincipal(signer, &store{signer.PublicKey()}, &roots{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ return p
+}
+
+func newCaveat(c security.Caveat, err error) security.Caveat {
+ if err != nil {
+ panic(err)
+ }
+ return c
+}
+
+func newBlessing(t *testing.T, name string) security.Blessings {
+ b, err := newPrincipal(t).BlessSelf(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return b
+}
+
+func newThirdPartyCaveatAndDischarge(t *testing.T) (security.ThirdPartyCaveat, security.Discharge) {
+ p := newPrincipal(t)
+ c, err := security.NewPublicKeyCaveat(p.PublicKey(), "location", security.ThirdPartyRequirements{}, newCaveat(security.MethodCaveat("method")))
+ if err != nil {
+ t.Fatal(err)
+ }
+ d, err := p.MintDischarge(c, security.UnconstrainedUse())
+ if err != nil {
+ t.Fatal(err)
+ }
+ return c, d
+}
+
+// TODO(ashankar,ataly): Consider moving these implementations to veyron2/security/test so that various
+// test libraries do not need to implement this.
+type store struct {
+ key security.PublicKey
+}
+
+func (*store) Add(blessings security.Blessings, forPeers security.BlessingPattern) error { return nil }
+func (*store) ForPeer(peerBlesssings ...string) security.Blessings { return nil }
+func (*store) SetDefault(blessings security.Blessings) error { return nil }
+func (*store) Default() security.Blessings { return nil }
+func (s *store) PublicKey() security.PublicKey { return s.key }
+
+type roots struct{}
+
+func (*roots) Add(security.PublicKey, security.BlessingPattern) error { return nil }
+func (*roots) Recognized(security.PublicKey, string) error { return nil }