...

Source file src/google.golang.org/grpc/authz/audit/stdout/stdout_logger_test.go

Documentation: google.golang.org/grpc/authz/audit/stdout

     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  }
   141  

View as plain text