diff --git a/functions/functionsv2/helloauditlog/go.mod b/functions/functionsv2/helloauditlog/go.mod new file mode 100644 index 0000000000..db13dff9de --- /dev/null +++ b/functions/functionsv2/helloauditlog/go.mod @@ -0,0 +1,9 @@ +module github.com/GoogleCloudPlatform/golang-samples/functions/functionsv2/helloauditlog + +go 1.13 + +require ( + github.com/cloudevents/sdk-go/v2 v2.5.0 + github.com/google/go-cmp v0.5.5 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect +) diff --git a/functions/functionsv2/helloauditlog/go.sum b/functions/functionsv2/helloauditlog/go.sum new file mode 100644 index 0000000000..8e2f07b526 --- /dev/null +++ b/functions/functionsv2/helloauditlog/go.sum @@ -0,0 +1,44 @@ +github.com/cloudevents/sdk-go/v2 v2.5.0 h1:Ts6aLHbBUJfcNcZ4ouAfJ4+Np7SE1Yf2w4ADKRCd7Fo= +github.com/cloudevents/sdk-go/v2 v2.5.0/go.mod h1:nlXhgFkf0uTopxmRXalyMwS2LG70cRGPrxzmjJgSG0U= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/functions/functionsv2/helloauditlog/hello_auditlog.go b/functions/functionsv2/helloauditlog/hello_auditlog.go new file mode 100644 index 0000000000..fdc249ae26 --- /dev/null +++ b/functions/functionsv2/helloauditlog/hello_auditlog.go @@ -0,0 +1,68 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// [START functions_log_cloudevent] + +// Package helloworld provides a set of Cloud Functions samples. +package helloworld + +import ( + "context" + "fmt" + "log" + + "github.com/cloudevents/sdk-go/v2/event" +) + +// AuditLogEntry represents a LogEntry as described at +// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry +type AuditLogEntry struct { + ProtoPayload *AuditLogProtoPayload `json:"protoPayload"` +} + +// AuditLogProtoPayload represents AuditLog within the LogEntry.protoPayload +// See https://cloud.google.com/logging/docs/reference/audit/auditlog/rest/Shared.Types/AuditLog +type AuditLogProtoPayload struct { + MethodName string `json:"methodName"` + ResourceName string `json:"resourceName"` + AuthenticationInfo map[string]interface{} `json:"authenticationInfo"` +} + +// HelloAuditLog receives a CloudEvent containing an AuditLogEntry, and logs a few fields. +func HelloAuditLog(ctx context.Context, e event.Event) error { + // Print out details from the CloudEvent itself + // See https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#subject + // for details on the Subject field + log.Printf("Event Type: %s", e.Type()) + log.Printf("Subject: %s", e.Subject()) + + // Decode the Cloud Audit Logging message embedded in the CloudEvent + logentry := &AuditLogEntry{} + if err := e.DataAs(logentry); err != nil { + ferr := fmt.Errorf("event.DataAs: %w", err) + log.Print(ferr) + return ferr + } + // Print out some of the information contained in the Cloud Audit Logging event + // See https://cloud.google.com/logging/docs/audit#audit_log_entry_structure + // for a full description of available fields. + log.Printf("API Method: %s", logentry.ProtoPayload.MethodName) + log.Printf("Resource Name: %s", logentry.ProtoPayload.ResourceName) + if v, ok := logentry.ProtoPayload.AuthenticationInfo["principalEmail"]; ok { + log.Printf("Principal: %s", v) + } + return nil +} + +// [END functions_log_cloudevent] diff --git a/functions/functionsv2/helloauditlog/hello_auditlog_test.go b/functions/functionsv2/helloauditlog/hello_auditlog_test.go new file mode 100644 index 0000000000..0ac35cd097 --- /dev/null +++ b/functions/functionsv2/helloauditlog/hello_auditlog_test.go @@ -0,0 +1,107 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package helloworld provides a set of Cloud Functions samples. +package helloworld + +import ( + "context" + "encoding/json" + "io/ioutil" + "log" + "os" + "strings" + "testing" + + "github.com/cloudevents/sdk-go/v2/event" +) + +func makeAuditLog(subject string, payload AuditLogProtoPayload) (event.Event, error) { + logevent := AuditLogEntry{ + ProtoPayload: &payload, + } + e := event.New() + e.SetSubject(subject) + e.SetType("google.cloud.audit.log.v1.written") + eventdata, err := json.Marshal(logevent) + if err != nil { + return event.New(), err + } + e.SetDataContentType("application/json") + e.SetData("application/json", eventdata) + return e, nil +} + +func TestHelloAuditLog(t *testing.T) { + + tests := []struct { + name string + subject string + payload AuditLogProtoPayload + expectedLogs []string + }{ + {name: "sample-output", + subject: "storage.googleapis.com/projects/_/buckets/my-bucket/objects/test.txt", + payload: AuditLogProtoPayload{ + MethodName: "storage.objects.create", + ResourceName: "my-resource", + AuthenticationInfo: map[string]interface{}{ + "principalEmail": "example@serviceaccounts.googleapis.com", + }, + }, + expectedLogs: []string{ + "Event Type: google.cloud.audit.log.v1.written", + "Subject: storage.googleapis.com/projects/_/buckets/my-bucket/objects/test.txt", + "API Method: storage.objects.create", + "Resource Name: my-resource", + "Principal: example@serviceaccounts.googleapis.com", + }, + }, + } + for _, tt := range tests { + + // Capture log output + r, w, _ := os.Pipe() + log.SetOutput(w) + originalFlags := log.Flags() + log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) + + t.Run(tt.name, func(t *testing.T) { + event, err := makeAuditLog(tt.subject, tt.payload) + if err != nil { + t.Errorf("HelloAuditLog() failed to create audit.LogEntryData: %v", err) + } + if err := HelloAuditLog(context.Background(), event); err != nil { + t.Errorf("HelloAuditLog() unexpected error: %v", err) + } + + w.Close() + log.SetOutput(os.Stderr) + log.SetFlags(originalFlags) + + // check output sent to the logging pipe. + output, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("Failed reading output from HelloAuditLog(): %v", err) + } + for _, l := range tt.expectedLogs { + if !strings.Contains(string(output), l) { + t.Errorf("HelloAuditlog() expected log not found: expected '%s', got '%s'", l, output) + } + } + + }) + + } +}