1
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
118
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
131
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 }
141
View as plain text