Skip to content
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 23 commits into from Jun 1, 2023
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
72 changes: 56 additions & 16 deletions authz/grpc_audit_end2end_test.go
Expand Up @@ -8,19 +8,30 @@ import (
"io"
"net"
"os"
"strconv"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"google.golang.org/grpc"
"google.golang.org/grpc/authz/audit"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/internal/grpctest"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
"google.golang.org/grpc/testdata"

_ "google.golang.org/grpc/authz/audit/stdout"
)

type s struct {
rockspore marked this conversation as resolved.
Show resolved Hide resolved
grpctest.Tester
}

func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}

type testServer struct {
testgrpc.UnimplementedTestServiceServer
}
Expand All @@ -30,33 +41,38 @@ func (s *testServer) UnaryCall(ctx context.Context, req *testpb.SimpleRequest) (
}

type statAuditLogger struct {
Stat map[bool]int
stdoutLogger audit.Logger
SpiffeIds []string
AuthzDescisionStat map[bool]int //Map to hold the counts of authorization decisions
dfawley marked this conversation as resolved.
Show resolved Hide resolved
EventContent map[string]string //Map to hold event fields in key:value fashion
SpiffeIds []string //Slice to hold collected SPIFFE IDs
}

func (s *statAuditLogger) Log(event *audit.Event) {
s.stdoutLogger.Log(event)
if event.Authorized {
s.Stat[true]++
s.AuthzDescisionStat[true]++
rockspore marked this conversation as resolved.
Show resolved Hide resolved
} else {
s.Stat[false]++
s.AuthzDescisionStat[false]++
}
s.SpiffeIds = append(s.SpiffeIds, event.Principal)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have EventContent, do we still need this field singled out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm checking EventContent only once but I'd still prefer to check SPIFFE every time

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any specific reason behind that? I thought if Spiffe ID is populated in one case, we can safely assume it's already there.

s.EventContent["rpc_method"] = event.FullMethodName
s.EventContent["principal"] = event.Principal
s.EventContent["policy_name"] = event.PolicyName
s.EventContent["matched_rule"] = event.MatchedRule
s.EventContent["authorized"] = strconv.FormatBool(event.Authorized)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not s.EventContent = event and make this field type *audit.Event? (Also consider a rename to lastEvent?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here EventContent is a map created at loggerBuilder but manipulated by statAuditLogger. At the end I'm using loggerBuilder to access it's content. I'm not sure how to achieve the same using *audit.Event - maybe only if I use a slice or map wrapping it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I DON"T UNDERSTAND THIS> Why can't you just change the type of this field (now lastEventContent) to *audit.Event and rename the field to lastEvent? Are you saying the tricky bit is communicating between the test and the constructed logger? If so, 2 options:

  1. Use *audit.Event and *s.lastEvent = *event
  2. Use **audit.Event and *s.lastEvent = event

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow what happened with my last comment here?? Sometimes the shift key sticks on my keyboard, but I can't believe I didn't see it. Sorry if that came off as rude, I didn't mean to type in all-caps.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries!

}

type loggerBuilder struct {
Stat map[bool]int
SpiffeIds []string
AuthzDescisionStat map[bool]int
EventContent map[string]string
SpiffeIds []string
}

func (loggerBuilder) Name() string {
return "stat_logger"
}
func (lb *loggerBuilder) Build(audit.LoggerConfig) audit.Logger {
dfawley marked this conversation as resolved.
Show resolved Hide resolved
return &statAuditLogger{
Stat: lb.Stat,
stdoutLogger: audit.GetLoggerBuilder("stdout_logger").Build(nil),
AuthzDescisionStat: lb.AuthzDescisionStat,
EventContent: lb.EventContent,
}
}

Expand All @@ -66,7 +82,7 @@ func (*loggerBuilder) ParseLoggerConfig(config json.RawMessage) (audit.LoggerCon

const spiffeId = "spiffe://foo.bar.com/client/workload/1"

func TestAuditLogger(t *testing.T) {
func (s) TestAuditLogger(t *testing.T) {
gtcooke94 marked this conversation as resolved.
Show resolved Hide resolved
dfawley marked this conversation as resolved.
Show resolved Hide resolved
tests := map[string]struct {
dfawley marked this conversation as resolved.
Show resolved Hide resolved
authzPolicy string
wantAllows int
Expand Down Expand Up @@ -136,6 +152,10 @@ func TestAuditLogger(t *testing.T) {
"name": "stat_logger",
"config": {},
"is_optional": false
},
{
"name": "stdout_logger",
"is_optional": false
}
]
}
Expand Down Expand Up @@ -209,7 +229,8 @@ func TestAuditLogger(t *testing.T) {
t.Run(name, func(t *testing.T) {
// Setup test statAuditLogger, gRPC test server with authzPolicy, unary and stream interceptors
lb := &loggerBuilder{
Stat: make(map[bool]int),
AuthzDescisionStat: make(map[bool]int),
EventContent: make(map[string]string),
}
audit.RegisterLoggerBuilder(lb)
i, _ := NewStatic(test.authzPolicy)
Expand Down Expand Up @@ -287,18 +308,37 @@ func TestAuditLogger(t *testing.T) {
stream.CloseAndRecv()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to check the error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning towards ignoring the errors here - #6304 (comment)


//Compare expected number of allows/denies with content of internal map of statAuditLogger
rockspore marked this conversation as resolved.
Show resolved Hide resolved
if lb.Stat[true] != test.wantAllows {
t.Errorf("Allow case failed, want %v got %v", test.wantAllows, lb.Stat[true])
if lb.AuthzDescisionStat[true] != test.wantAllows {
t.Errorf("Allow case failed, want %v got %v", test.wantAllows, lb.AuthzDescisionStat[true])
}
if lb.Stat[false] != test.wantDenies {
t.Errorf("Deny case failed, want %v got %v", test.wantDenies, lb.Stat[false])
if lb.AuthzDescisionStat[false] != test.wantDenies {
t.Errorf("Deny case failed, want %v got %v", test.wantDenies, lb.AuthzDescisionStat[false])
}
//Compare recorded SPIFFE Ids with the value from cert
for _, id := range lb.SpiffeIds {
if id != spiffeId {
t.Errorf("Unexpected SPIFFE Id, want %v got %v", spiffeId, id)
}
}
//Special case - compare event fields with expected values from authz policy
if name == `Allow All Deny Streaming - Audit All` {
if diff := cmp.Diff(lb.EventContent, generateEventAsMap()); diff != "" {
t.Fatalf("Unexpected message\ndiff (-got +want):\n%s", diff)
}
}
})
}
}

// generateEvent produces an map contaning audit.Event fields.
// It's used to compare captured audit.Event with the matched rule during
// `Allow All Deny Streaming - Audit All` scenario (authz_deny_all rule)
func generateEventAsMap() map[string]string {
dfawley marked this conversation as resolved.
Show resolved Hide resolved
return map[string]string{
"rpc_method": "/grpc.testing.TestService/StreamingInputCall",
"principal": "spiffe://foo.bar.com/client/workload/1",
"policy_name": "authz",
"matched_rule": "authz_deny_all",
"authorized": "false",
}
}