google.golang.org/grpc@v1.62.1/authz/audit/stdout/stdout_logger_test.go (about) 1 /* 2 * 3 * Copyright 2023 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package stdout 20 21 import ( 22 "bytes" 23 "encoding/json" 24 "log" 25 "os" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 "google.golang.org/grpc/authz/audit" 31 "google.golang.org/grpc/internal/grpctest" 32 ) 33 34 type s struct { 35 grpctest.Tester 36 } 37 38 func Test(t *testing.T) { 39 grpctest.RunSubTests(t, s{}) 40 } 41 42 func (s) TestStdoutLogger_Log(t *testing.T) { 43 tests := map[string]struct { 44 event *audit.Event 45 wantMessage string 46 wantErr string 47 }{ 48 "few fields": { 49 event: &audit.Event{PolicyName: "test policy", Principal: "test principal"}, 50 wantMessage: `{"fullMethodName":"","principal":"test principal","policyName":"test policy","matchedRule":"","authorized":false`, 51 }, 52 "all fields": { 53 event: &audit.Event{ 54 FullMethodName: "/helloworld.Greeter/SayHello", 55 Principal: "spiffe://example.org/ns/default/sa/default/backend", 56 PolicyName: "example-policy", 57 MatchedRule: "dev-access", 58 Authorized: true, 59 }, 60 wantMessage: `{"fullMethodName":"/helloworld.Greeter/SayHello",` + 61 `"principal":"spiffe://example.org/ns/default/sa/default/backend","policyName":"example-policy",` + 62 `"matchedRule":"dev-access","authorized":true`, 63 }, 64 } 65 66 for name, test := range tests { 67 t.Run(name, func(t *testing.T) { 68 before := time.Now().Unix() 69 var buf bytes.Buffer 70 builder := &loggerBuilder{goLogger: log.New(&buf, "", 0)} 71 auditLogger := builder.Build(nil) 72 73 auditLogger.Log(test.event) 74 75 var container map[string]any 76 if err := json.Unmarshal(buf.Bytes(), &container); err != nil { 77 t.Fatalf("Failed to unmarshal audit log event: %v", err) 78 } 79 innerEvent := extractEvent(container["grpc_audit_log"].(map[string]any)) 80 if innerEvent.Timestamp == "" { 81 t.Fatalf("Resulted event has no timestamp: %v", innerEvent) 82 } 83 after := time.Now().Unix() 84 innerEventUnixTime, err := time.Parse(time.RFC3339Nano, innerEvent.Timestamp) 85 if err != nil { 86 t.Fatalf("Failed to convert event timestamp into Unix time format: %v", err) 87 } 88 if before > innerEventUnixTime.Unix() || after < innerEventUnixTime.Unix() { 89 t.Errorf("The audit event timestamp is outside of the test interval: test start %v, event timestamp %v, test end %v", before, innerEventUnixTime.Unix(), after) 90 } 91 if diff := cmp.Diff(trimEvent(innerEvent), test.event); diff != "" { 92 t.Fatalf("Unexpected message\ndiff (-got +want):\n%s", diff) 93 } 94 }) 95 } 96 } 97 98 func (s) TestStdoutLoggerBuilder_NilConfig(t *testing.T) { 99 builder := &loggerBuilder{ 100 goLogger: log.New(os.Stdout, "", log.LstdFlags), 101 } 102 config, err := builder.ParseLoggerConfig(nil) 103 if err != nil { 104 t.Fatalf("Failed to parse stdout logger configuration: %v", err) 105 } 106 if l := builder.Build(config); l == nil { 107 t.Fatal("Failed to build stdout audit logger") 108 } 109 } 110 111 func (s) TestStdoutLoggerBuilder_Registration(t *testing.T) { 112 if audit.GetLoggerBuilder("stdout_logger") == nil { 113 t.Fatal("stdout logger is not registered") 114 } 115 } 116 117 // extractEvent extracts an stdout.event from a map 118 // unmarshalled from a logged json message. 119 func extractEvent(container map[string]any) event { 120 return event{ 121 FullMethodName: container["rpc_method"].(string), 122 Principal: container["principal"].(string), 123 PolicyName: container["policy_name"].(string), 124 MatchedRule: container["matched_rule"].(string), 125 Authorized: container["authorized"].(bool), 126 Timestamp: container["timestamp"].(string), 127 } 128 } 129 130 // trimEvent converts a logged stdout.event into an audit.Event 131 // by removing Timestamp field. It is used for comparing events during testing. 132 func trimEvent(testEvent event) *audit.Event { 133 return &audit.Event{ 134 FullMethodName: testEvent.FullMethodName, 135 Principal: testEvent.Principal, 136 PolicyName: testEvent.PolicyName, 137 MatchedRule: testEvent.MatchedRule, 138 Authorized: testEvent.Authorized, 139 } 140 }