Skip to content

Commit

Permalink
xds: E2E Test for Audit Logging (grpc#6377)
Browse files Browse the repository at this point in the history
Add E2E Test for Audit Logging through the XDS path
  • Loading branch information
gtcooke94 committed Jun 29, 2023
1 parent 07718ef commit 67e881c
Showing 1 changed file with 96 additions and 0 deletions.
96 changes: 96 additions & 0 deletions test/xds/xds_server_rbac_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ package xds_test

import (
"context"
"encoding/json"
"fmt"
"net"
"strconv"
"strings"
"testing"

v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3"
v3routerpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc"
"google.golang.org/grpc/authz/audit"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/internal"
Expand All @@ -36,6 +40,7 @@ import (
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/structpb"

v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
Expand Down Expand Up @@ -415,6 +420,8 @@ func (s) TestRBACHTTPFilter(t *testing.T) {
rbacCfg *rpb.RBAC
wantStatusEmptyCall codes.Code
wantStatusUnaryCall codes.Code
wantAuthzOutcomes map[bool]int
eventContent *audit.Event
}{
// This test tests an RBAC HTTP Filter which is configured to allow any RPC.
// Any RPC passing through this RBAC HTTP Filter should proceed as normal.
Expand All @@ -433,10 +440,30 @@ func (s) TestRBACHTTPFilter(t *testing.T) {
},
},
},
AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{
AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW,
LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{
{
AuditLogger: &v3corepb.TypedExtensionConfig{
Name: "stat_logger",
TypedConfig: createXDSTypedStruct(t, map[string]interface{}{}, "stat_logger"),
},
IsOptional: false,
},
},
},
},
},
wantStatusEmptyCall: codes.OK,
wantStatusUnaryCall: codes.OK,
wantAuthzOutcomes: map[bool]int{true: 2, false: 0},
// TODO(gtcooke94) add policy name (RBAC filter name) once
// https://github.com/grpc/grpc-go/pull/6327 is merged.
eventContent: &audit.Event{
FullMethodName: "/grpc.testing.TestService/UnaryCall",
MatchedRule: "anyone",
Authorized: true,
},
},
// This test tests an RBAC HTTP Filter which is configured to allow only
// RPC's with certain paths ("UnaryCall"). Only unary calls passing
Expand Down Expand Up @@ -605,6 +632,12 @@ func (s) TestRBACHTTPFilter(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
func() {
lb := &loggerBuilder{
authzDecisionStat: map[bool]int{true: 0, false: 0},
lastEvent: &audit.Event{},
}
audit.RegisterLoggerBuilder(lb)

managementServer, nodeID, bootstrapContents, resolver, cleanup1 := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
defer cleanup1()

Expand Down Expand Up @@ -660,6 +693,17 @@ func (s) TestRBACHTTPFilter(t *testing.T) {
}
// Toggle RBAC back on for next iterations.
envconfig.XDSRBAC = true

if test.wantAuthzOutcomes != nil {
if diff := cmp.Diff(lb.authzDecisionStat, test.wantAuthzOutcomes); diff != "" {
t.Fatalf("authorization decision do not match\ndiff (-got +want):\n%s", diff)
}
}
if test.eventContent != nil {
if diff := cmp.Diff(lb.lastEvent, test.eventContent); diff != "" {
t.Fatalf("unexpected event\ndiff (-got +want):\n%s", diff)
}
}
}()
})
}
Expand Down Expand Up @@ -895,3 +939,55 @@ func (s) TestRBACToggledOff_WithBadRouteConfiguration(t *testing.T) {
t.Fatalf("UnaryCall() returned err with status: %v, if RBAC is disabled all RPC's should proceed as normal", status.Code(err))
}
}

type statAuditLogger struct {
authzDecisionStat map[bool]int // Map to hold counts of authorization decisions
lastEvent *audit.Event // Field to store last received event
}

func (s *statAuditLogger) Log(event *audit.Event) {
s.authzDecisionStat[event.Authorized]++
*s.lastEvent = *event
}

type loggerBuilder struct {
authzDecisionStat map[bool]int
lastEvent *audit.Event
}

func (loggerBuilder) Name() string {
return "stat_logger"
}

func (lb *loggerBuilder) Build(audit.LoggerConfig) audit.Logger {
return &statAuditLogger{
authzDecisionStat: lb.authzDecisionStat,
lastEvent: lb.lastEvent,
}
}

func (*loggerBuilder) ParseLoggerConfig(config json.RawMessage) (audit.LoggerConfig, error) {
return nil, nil
}

// This is used when converting a custom config from raw JSON to a TypedStruct.
// The TypeURL of the TypeStruct will be "grpc.authz.audit_logging/<name>".
const typeURLPrefix = "grpc.authz.audit_logging/"

// Builds custom configs for audit logger RBAC protos.
func createXDSTypedStruct(t *testing.T, in map[string]interface{}, name string) *anypb.Any {
t.Helper()
pb, err := structpb.NewStruct(in)
if err != nil {
t.Fatalf("createXDSTypedStruct failed during structpb.NewStruct: %v", err)
}
typedStruct := &v3xdsxdstypepb.TypedStruct{
TypeUrl: typeURLPrefix + name,
Value: pb,
}
customConfig, err := anypb.New(typedStruct)
if err != nil {
t.Fatalf("createXDSTypedStruct failed during anypb.New: %v", err)
}
return customConfig
}

0 comments on commit 67e881c

Please sign in to comment.