Skip to content

Commit

Permalink
enhancement: Allow specification of gRPC request metadata keys to be …
Browse files Browse the repository at this point in the history
…logged (#1202)
  • Loading branch information
oguzhand95 committed Sep 12, 2022
1 parent 6df6dd7 commit b4ba131
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 15 deletions.
2 changes: 2 additions & 0 deletions docs/modules/configuration/partials/fullconfiguration.adoc
Expand Up @@ -3,6 +3,8 @@ audit:
backend: local # Backend states which backend to use for Audits.
decisionLogsEnabled: false # DecisionLogsEnabled defines whether logging of policy decisions is enabled.
enabled: false # Enabled defines whether audit logging is enabled.
excludeMetadataKeys: ['authorization'] # ExcludeMetadataKeys defines which gRPC request metadata keys should be excluded from access logs. Takes precedence over includeMetadataKeys.
includeMetadataKeys: ['content-type'] # IncludeMetadataKeys defines which gRPC request metadata keys should be included in access logs.
file:
path: /path/to/file.log # Path to the log file to use as output. The special values stdout and stderr can be used to write to stdout or stderr respectively.
local:
Expand Down
4 changes: 4 additions & 0 deletions internal/audit/conf.go
Expand Up @@ -22,6 +22,10 @@ type Conf struct {
type confHolder struct {
// Backend states which backend to use for Audits.
Backend string `yaml:"backend" conf:",example=local"`
// IncludeMetadataKeys defines which gRPC request metadata keys should be included in access logs.
IncludeMetadataKeys []string `yaml:"includeMetadataKeys" conf:",example=['content-type']"`
// ExcludeMetadataKeys defines which gRPC request metadata keys should be excluded from access logs. Takes precedence over includeMetadataKeys.
ExcludeMetadataKeys []string `yaml:"excludeMetadataKeys" conf:",example=['authorization']"`
// Enabled defines whether audit logging is enabled.
Enabled bool `yaml:"enabled" conf:",example=false"`
// AccessLogsEnabled defines whether access logging is enabled.
Expand Down
59 changes: 48 additions & 11 deletions internal/audit/interceptor.go
Expand Up @@ -5,6 +5,7 @@ package audit

import (
"context"
"fmt"
"time"

"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
Expand All @@ -17,13 +18,23 @@ import (
auditv1 "github.com/cerbos/cerbos/api/genpb/cerbos/audit/v1"
)

var excludeMetadataKeys = map[string]struct{}{
"grpc-trace-bin": {},
}
type (
ExcludeMethod func(string) bool
IncludeKeysMethod func(string) bool
)

type ExcludeMethod func(string) bool
func NewUnaryInterceptor(log Log, exclude ExcludeMethod) (grpc.UnaryServerInterceptor, error) {
conf, err := GetConf()
if err != nil {
return nil, fmt.Errorf("failed to read audit configuration: %w", err)
}

var keysPresent bool
var includeKeys IncludeKeysMethod
if keysPresent = !(len(conf.ExcludeMetadataKeys) == 0 && len(conf.IncludeMetadataKeys) == 0); keysPresent {
includeKeys = mkIncludeKeysMethod(conf.ExcludeMetadataKeys, conf.IncludeMetadataKeys)
}

func NewUnaryInterceptor(log Log, exclude ExcludeMethod) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
if exclude(info.FullMethod) {
return handler(ctx, req)
Expand All @@ -47,12 +58,14 @@ func NewUnaryInterceptor(log Log, exclude ExcludeMethod) grpc.UnaryServerInterce
StatusCode: uint32(status.Code(err)),
}

md, ok := metadata.FromIncomingContext(ctx)
if ok {
entry.Metadata = make(map[string]*auditv1.MetaValues, len(md))
for key, values := range md {
if _, ok := excludeMetadataKeys[key]; !ok {
entry.Metadata[key] = &auditv1.MetaValues{Values: values}
if keysPresent {
md, ok := metadata.FromIncomingContext(ctx)
if ok {
entry.Metadata = make(map[string]*auditv1.MetaValues, len(md))
for key, values := range md {
if includeKeys(key) {
entry.Metadata[key] = &auditv1.MetaValues{Values: values}
}
}
}
}
Expand All @@ -63,5 +76,29 @@ func NewUnaryInterceptor(log Log, exclude ExcludeMethod) grpc.UnaryServerInterce
}

return resp, err
}, nil
}

func mkIncludeKeysMethod(excludedMetadataKeys, includedMetadataKeys []string) IncludeKeysMethod {
exclude := sliceToLookupMap(excludedMetadataKeys)
include := sliceToLookupMap(includedMetadataKeys)
return func(key string) bool {
_, existsInExcludedKeys := exclude[key]
_, existsInIncludedKeys := include[key]

if !existsInExcludedKeys && existsInIncludedKeys {
return true
}

return false
}
}

func sliceToLookupMap(slice []string) map[string]struct{} {
m := make(map[string]struct{})
for _, k := range slice {
m[k] = struct{}{}
}

return m
}
44 changes: 44 additions & 0 deletions internal/audit/interceptor_test.go
@@ -0,0 +1,44 @@
// Copyright 2021-2022 Zenauth Ltd.
// SPDX-License-Identifier: Apache-2.0

//go:build tests
// +build tests

package audit

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestMkIncludeKeysMethod(t *testing.T) {
testCases := []struct {
key string
expected bool
}{
{
key: "a",
expected: false,
},
{
key: "b",
expected: false,
},
{
key: "c",
expected: true,
},
}

excludedMetadataKeys := []string{"a", "b"}
includedMetadataKeys := []string{"a", "c"}
includeKeys := mkIncludeKeysMethod(excludedMetadataKeys, includedMetadataKeys)

for _, tc := range testCases {
t.Run(tc.key, func(t *testing.T) {
actual := includeKeys(tc.key)
require.Equal(t, tc.expected, actual)
})
}
}
16 changes: 12 additions & 4 deletions internal/server/server.go
Expand Up @@ -335,7 +335,10 @@ func (s *Server) getTLSConfig() (*tls.Config, error) {

func (s *Server) startGRPCServer(l net.Listener, param Param) (*grpc.Server, error) {
log := zap.L().Named("grpc")
server := s.mkGRPCServer(log, param.AuditLog)
server, err := s.mkGRPCServer(log, param.AuditLog)
if err != nil {
return nil, fmt.Errorf("failed to create gRPC server: %w", err)
}

healthpb.RegisterHealthServer(server, s.health)
reflection.Register(server)
Expand Down Expand Up @@ -402,10 +405,15 @@ func checkForUnsafeAdminCredentials(log *zap.Logger, passwordHash []byte) {
}
}

func (s *Server) mkGRPCServer(log *zap.Logger, auditLog audit.Log) *grpc.Server {
func (s *Server) mkGRPCServer(log *zap.Logger, auditLog audit.Log) (*grpc.Server, error) {
payloadLog := zap.L().Named("payload")
telemetryInt := telemetry.Intercept()

auditInterceptor, err := audit.NewUnaryInterceptor(auditLog, accessLogExclude)
if err != nil {
return nil, fmt.Errorf("failed to create audit unary interceptor: %w", err)
}

opts := []grpc.ServerOption{
grpc.ChainStreamInterceptor(
grpc_recovery.StreamServerInterceptor(),
Expand All @@ -429,14 +437,14 @@ func (s *Server) mkGRPCServer(log *zap.Logger, auditLog audit.Log) *grpc.Server
grpc_zap.WithMessageProducer(messageProducer),
),
grpc_zap.PayloadUnaryServerInterceptor(payloadLog, payloadLoggingDecider(s.conf)),
audit.NewUnaryInterceptor(auditLog, accessLogExclude),
auditInterceptor,
),
grpc.StatsHandler(&ocgrpc.ServerHandler{}),
grpc.KeepaliveParams(keepalive.ServerParameters{MaxConnectionAge: maxConnectionAge}),
grpc.UnknownServiceHandler(handleUnknownServices),
}

return grpc.NewServer(opts...)
return grpc.NewServer(opts...), nil
}

func (s *Server) startHTTPServer(ctx context.Context, l net.Listener, grpcSrv *grpc.Server, zpagesEnabled bool) (*http.Server, error) {
Expand Down

0 comments on commit b4ba131

Please sign in to comment.