New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
authz: End2End test for AuditLogger #6304
Merged
Merged
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
5778913
Draft of e2e test
erm-g cebf932
No Audit, Audit on Allow and Deny
erm-g d405ab2
Audit on Allow, Audit on Deny
erm-g 0baf0e6
fix typo
erm-g b569c67
SPIFFE related testing
erm-g 135565a
SPIFFE Id validation and certs creation script
erm-g c19e304
Address PR comments
erm-g 1e7fcc4
Wrap tests using grpctest.Tester
erm-g e5991f2
Address PR comments
erm-g 620d990
Change package name to authz_test to fit other end2end tests
erm-g cba398c
Add licence header, remove SPIFFE slice
erm-g de59ce5
Licence year change
erm-g 9a8ee49
Address PR comments part 1
erm-g 36bc552
Address PR comments part 2
erm-g b255385
Address PR comments part 3
erm-g 7777c5b
Address PR comments final part
erm-g 191fdf6
Drop newline for a brace
erm-g 80d38b4
Address PR comments, fix outdated function comment
erm-g 46fda00
Address PR comments
erm-g a02960d
Fix typo
erm-g 56eba6e
Remove unused var
erm-g cb01a30
Address PR comment, change most test error handling to Errorf
erm-g 4f30f15
Address PR comments
erm-g File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,16 +33,20 @@ import ( | |
"google.golang.org/grpc" | ||
"google.golang.org/grpc/authz" | ||
"google.golang.org/grpc/authz/audit" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/credentials" | ||
"google.golang.org/grpc/internal/grpctest" | ||
"google.golang.org/grpc/internal/stubserver" | ||
testgrpc "google.golang.org/grpc/interop/grpc_testing" | ||
testpb "google.golang.org/grpc/interop/grpc_testing" | ||
"google.golang.org/grpc/status" | ||
"google.golang.org/grpc/testdata" | ||
|
||
_ "google.golang.org/grpc/authz/audit/stdout" | ||
) | ||
|
||
var permissionDeniedStatus = status.New(codes.PermissionDenied, "unauthorized RPC request rejected") | ||
|
||
type s struct { | ||
grpctest.Tester | ||
} | ||
|
@@ -53,7 +57,7 @@ func Test(t *testing.T) { | |
|
||
type statAuditLogger struct { | ||
authzDecisionStat map[bool]int // Map to hold the counts of authorization decisions | ||
lastEvent *audit.Event // Map to hold event fields in key:value fashion | ||
lastEvent *audit.Event // Field to store last received event | ||
} | ||
|
||
func (s *statAuditLogger) Log(event *audit.Event) { | ||
|
@@ -227,7 +231,7 @@ func (s) TestAuditLogger(t *testing.T) { | |
wantAuthzOutcomes: map[bool]int{true: 0, false: 3}, | ||
}, | ||
} | ||
//Construct the credentials for the tests and the stub server | ||
// Construct the credentials for the tests and the stub server | ||
serverCreds := loadServerCreds(t) | ||
clientCreds := loadClientCreds(t) | ||
ss := &stubserver.StubServer{ | ||
|
@@ -275,23 +279,26 @@ func (s) TestAuditLogger(t *testing.T) { | |
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||
defer cancel() | ||
|
||
client.UnaryCall(ctx, &testpb.SimpleRequest{}) | ||
client.UnaryCall(ctx, &testpb.SimpleRequest{}) | ||
_, err = client.UnaryCall(ctx, &testpb.SimpleRequest{}) | ||
validateCallResult(t, err) | ||
_, err = client.UnaryCall(ctx, &testpb.SimpleRequest{}) | ||
validateCallResult(t, err) | ||
stream, _ := client.StreamingInputCall(ctx) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check the error. |
||
req := &testpb.StreamingInputCallRequest{ | ||
Payload: &testpb.Payload{ | ||
Body: []byte("hi"), | ||
}, | ||
} | ||
stream.Send(req) | ||
stream.CloseAndRecv() | ||
validateCallResult(t, stream.Send(req)) | ||
_, err = stream.CloseAndRecv() | ||
validateCallResult(t, err) | ||
|
||
// Compare expected number of allows/denies with content of the internal | ||
// map of statAuditLogger. | ||
if diff := cmp.Diff(lb.authzDecisionStat, test.wantAuthzOutcomes); diff != "" { | ||
t.Fatalf("Authorization decisions do not match\ndiff (-got +want):\n%s", diff) | ||
} | ||
// Compare event fields with expected values from authz policy. | ||
// Compare last event received by statAuditLogger with expected event. | ||
if test.eventContent != nil { | ||
if diff := cmp.Diff(lb.lastEvent, test.eventContent); diff != "" { | ||
t.Fatalf("Unexpected message\ndiff (-got +want):\n%s", diff) | ||
|
@@ -305,7 +312,7 @@ func (s) TestAuditLogger(t *testing.T) { | |
func loadServerCreds(t *testing.T) credentials.TransportCredentials { | ||
t.Helper() | ||
cert := loadKeys(t, "x509/server1_cert.pem", "x509/server1_key.pem") | ||
certPool := loadCaCerts(t, "x509/client_ca_cert.pem") | ||
certPool := loadCACerts(t, "x509/client_ca_cert.pem") | ||
return credentials.NewTLS(&tls.Config{ | ||
ClientAuth: tls.RequireAndVerifyClientCert, | ||
Certificates: []tls.Certificate{cert}, | ||
|
@@ -317,7 +324,7 @@ func loadServerCreds(t *testing.T) credentials.TransportCredentials { | |
func loadClientCreds(t *testing.T) credentials.TransportCredentials { | ||
t.Helper() | ||
cert := loadKeys(t, "x509/client_with_spiffe_cert.pem", "x509/client_with_spiffe_key.pem") | ||
roots := loadCaCerts(t, "x509/server_ca_cert.pem") | ||
roots := loadCACerts(t, "x509/server_ca_cert.pem") | ||
return credentials.NewTLS(&tls.Config{ | ||
Certificates: []tls.Certificate{cert}, | ||
RootCAs: roots, | ||
|
@@ -326,7 +333,7 @@ func loadClientCreds(t *testing.T) credentials.TransportCredentials { | |
|
||
} | ||
|
||
// loadCaCerts loads X509 key pair from the provided file paths. | ||
// loadKeys loads X509 key pair from the provided file paths. | ||
// It is used for loading both client and server certificates for the test | ||
func loadKeys(t *testing.T, certPath, key string) tls.Certificate { | ||
t.Helper() | ||
|
@@ -337,9 +344,9 @@ func loadKeys(t *testing.T, certPath, key string) tls.Certificate { | |
return cert | ||
} | ||
|
||
// loadCaCerts loads CA certificates and constructs x509.CertPool | ||
// loadCACerts loads CA certificates and constructs x509.CertPool | ||
// It is used for loading both client and server CAs for the test | ||
func loadCaCerts(t *testing.T, certPath string) *x509.CertPool { | ||
func loadCACerts(t *testing.T, certPath string) *x509.CertPool { | ||
t.Helper() | ||
ca, err := os.ReadFile(testdata.Path(certPath)) | ||
if err != nil { | ||
|
@@ -351,3 +358,15 @@ func loadCaCerts(t *testing.T, certPath string) *x509.CertPool { | |
} | ||
return roots | ||
} | ||
|
||
// validateCallResult checks if the error resulting from making a call can be | ||
// ignored. It is used for both unary and streaming calls in this test. | ||
func validateCallResult(t *testing.T, err error) { | ||
t.Helper() | ||
if err == nil || err == io.EOF { | ||
return | ||
} | ||
if errStatus := status.Convert(err); errStatus.Code() != permissionDeniedStatus.Code() || errStatus.Message() != permissionDeniedStatus.Message() { | ||
t.Errorf("Call failed:%v", err) | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inline this please, since it's so simple:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also need to check if the err is a
permission denied
one - it's a valid scenario for the test (we countdeny
as well).I think I can rely on
code
only but still it'll be 2 LOCsOr I can create a helper function which takes
SimpleResponse, error
and returns back error code - smth likeWDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is more of a problem of your test design.. You're using a table driven test but things are sufficiently different between iterations that it leads to these sorts of problems.
If you want to not care which one should happen:
Or if you want to require no error when unary should not be denied and vice-versa:
Realistically, the latter is how your code should behave, but it's ugly. Maybe the test should be reworked a bit or split into multiple tests, as this seems wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea is to have a set of authz policies with various
audit_condition
values. In order to test eachaudit_condition
I need to make a few calls and then check if audit logger was invoked as expected. That's why I think that iterations are common enough to use a table driven approach. From my perspectivecovers all the cases (we want to error test if a call returns an err but it's not a permission denied one).
What'd be your suggestion for refactoring? I think that splitting it will result in very similar tests with different err handling for permission denied cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a
codes.Code
for the expected result from the Unary and Streaming RPC calls to the table?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I think it makes the test more readable. PTAL