| package audit_test |
| |
| import ( |
| "crypto/ecdsa" |
| "crypto/elliptic" |
| "crypto/rand" |
| "errors" |
| "reflect" |
| "strings" |
| "testing" |
| "time" |
| |
| "veyron/security/audit" |
| "veyron2/naming" |
| "veyron2/security" |
| ) |
| |
| func TestAuditingID(t *testing.T) { |
| var ( |
| // A bunch of values that will be used as arguments to method calls. |
| byteSlice []byte |
| publicID = security.FakePublicID("publicid") |
| str string |
| duration time.Duration |
| caveats []security.Caveat |
| thirdPartyCaveat thirdPartyCaveat |
| context context |
| |
| // A bunch of values that will be returned as results |
| rSignature = security.Signature{R: []byte{1}, S: []byte{1}} |
| rBlessing = security.FakePublicID("blessed") |
| rDischarge = new(discharge) |
| |
| // The error returned by call calls to mockID operations |
| wantErr = errors.New("call failed") |
| |
| // The PrivateID to wrap over |
| mockID = new(mockID) |
| mockAuditor = new(mockAuditor) |
| ) |
| id := audit.NewPrivateID(mockID, mockAuditor) |
| tests := []struct { |
| Method string |
| Args V |
| Result interface{} // Result returned by the Method call. |
| AuditedResult interface{} // Result written to the audit entry. |
| }{ |
| {"Sign", V{byteSlice}, rSignature, nil}, |
| {"Bless", V{publicID, str, duration, caveats}, rBlessing, rBlessing}, |
| {"MintDischarge", V{thirdPartyCaveat, context, duration, caveats}, rDischarge, nil}, |
| } |
| for _, test := range tests { |
| // Test1: Nothing is written to the audit log if the underlying operation fails. |
| mockID.NextError = wantErr |
| results, err := call(id, test.Method, test.Args) |
| if err != nil { |
| t.Errorf("failed to invoke id.%v(%#v): %v", test.Method, test.Args, err) |
| continue |
| } |
| got, ok := results[len(results)-1].(error) |
| if !ok || got != wantErr { |
| t.Errorf("id.%v(%#v) returned (..., %v), want (..., %v)", test.Method, test.Args, got, wantErr) |
| } |
| // Nothing should be audited |
| if audited := mockAuditor.Release(); !reflect.DeepEqual(audited, audit.Entry{}) { |
| t.Errorf("id.%v(%#v) resulted in [%+v] being written to the audit log, nothing should have been", test.Method, test.Args, audited) |
| } |
| |
| // Test2: If the auditing fails then the operation should fail too. |
| mockAuditor.NextError = errors.New("auditor failed") |
| results, err = call(id, test.Method, test.Args) |
| if err != nil { |
| t.Errorf("failed to invoke id.%v(%#v): %v", test.Method, test.Args, err) |
| continue |
| } |
| got, ok = results[len(results)-1].(error) |
| if !ok || !strings.HasSuffix(got.Error(), "auditor failed") { |
| t.Errorf("id.%v(%#v) returned (..., %v) when auditor failed, wanted (..., %v)", test.Method, test.Args, got, "... auditor failed") |
| } |
| |
| // Test3: Should audit the call and return the same value as the underlying operation. |
| now := time.Now() |
| mockID.NextResult = test.Result |
| results, err = call(id, test.Method, test.Args) |
| audited := mockAuditor.Release() |
| if err != nil { |
| t.Errorf("failed to invoke id.%v(%#v): %v", test.Method, test.Args, err) |
| continue |
| } |
| if got := results[len(results)-1]; got != nil { |
| t.Errorf("id.%v(%#v) returned an error: %v", test.Method, test.Args, got) |
| } |
| if got := results[0]; !reflect.DeepEqual(got, test.Result) { |
| t.Errorf("id.%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("id.%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.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) |
| } |
| } |
| } |
| |
| func sliceOrNil(item interface{}) []interface{} { |
| if item == nil { |
| return nil |
| } |
| return []interface{}{item} |
| } |
| |
| func TestUnauditedMethods(t *testing.T) { |
| var ( |
| mockID = new(mockID) |
| mockAuditor = new(mockAuditor) |
| id = audit.NewPrivateID(mockID, mockAuditor) |
| key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| pubkey = security.NewECDSAPublicKey(&key.PublicKey) |
| emptyEntry audit.Entry |
| ) |
| if err != nil { |
| t.Fatal(err) |
| } |
| mockID.NextResult = pubkey |
| if got, want := id.PublicKey(), pubkey; got != want { |
| t.Errorf("Got %v want %v", got, want) |
| } |
| if entry := mockAuditor.Release(); !reflect.DeepEqual(entry, emptyEntry) { |
| t.Errorf("Unexpected audit: %v", entry) |
| } |
| |
| var pubID security.PublicID |
| mockID.NextResult = pubID |
| if got, want := id.PublicID(), pubID; got != want { |
| t.Errorf("Got %v want %v", got, want) |
| } |
| if entry := mockAuditor.Release(); !reflect.DeepEqual(entry, emptyEntry) { |
| t.Errorf("Unexpected audit: %v", entry) |
| } |
| } |
| |
| func TestDerive(t *testing.T) { |
| var ( |
| mockID = new(mockID) |
| mockAuditor = new(mockAuditor) |
| id = audit.NewPrivateID(mockID, mockAuditor) |
| publicID = security.FakePublicID("publicid") |
| rSignature = security.Signature{R: []byte{2}, S: []byte{2}} |
| msg = []byte{1, 2, 3} |
| ) |
| mockID.NextResult = mockID |
| derived, err := id.Derive(publicID) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| mockID.NextResult = rSignature |
| if got, err := derived.Sign(msg); err != nil || !reflect.DeepEqual(got, rSignature) { |
| t.Fatalf("Got (%v, %v) want (%v, nil)", got, err, rSignature) |
| } |
| got := mockAuditor.Release() |
| if want := (audit.Entry{Method: "Sign", Arguments: []interface{}{msg}, Timestamp: got.Timestamp}); !reflect.DeepEqual(got, want) { |
| t.Fatalf("Audit log shows [%v] want [%v]", got, want) |
| } |
| } |
| |
| type mockID struct { |
| NextError error |
| NextResult interface{} |
| } |
| |
| func (id *mockID) Sign(message []byte) (security.Signature, error) { |
| defer id.reset() |
| ret, ok := id.NextResult.(security.Signature) |
| if ok { |
| return ret, id.NextError |
| } |
| return security.Signature{}, id.NextError |
| } |
| |
| func (id *mockID) PublicID() security.PublicID { |
| defer id.reset() |
| if id.NextResult == nil { |
| return nil |
| } |
| return id.NextResult.(security.PublicID) |
| } |
| func (id *mockID) Bless(blessee security.PublicID, blessingName string, duration time.Duration, caveats []security.Caveat) (security.PublicID, error) { |
| defer id.reset() |
| result, _ := id.NextResult.(security.PublicID) |
| return result, id.NextError |
| } |
| func (id *mockID) Derive(publicID security.PublicID) (security.PrivateID, error) { |
| defer id.reset() |
| result, _ := id.NextResult.(security.PrivateID) |
| return result, id.NextError |
| } |
| func (id *mockID) MintDischarge(caveat security.ThirdPartyCaveat, context security.Context, duration time.Duration, caveats []security.Caveat) (security.Discharge, error) { |
| defer id.reset() |
| result, _ := id.NextResult.(security.Discharge) |
| return result, id.NextError |
| } |
| |
| func (id *mockID) reset() { |
| id.NextError = nil |
| id.NextResult = nil |
| } |
| |
| 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{} |
| |
| func (thirdPartyCaveat) Validate(security.Context) error { return nil } |
| func (thirdPartyCaveat) ID() string { return "thirdPartyCaveatID" } |
| func (thirdPartyCaveat) Location() string { return "thirdPartyCaveatLocation" } |
| func (thirdPartyCaveat) Requirements() security.ThirdPartyRequirements { |
| return security.ThirdPartyRequirements{} |
| } |
| |
| // context implements security.Context |
| type context struct{} |
| |
| func (context) Method() string { return "method" } |
| func (context) Name() string { return "name" } |
| 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) 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 |
| } |