lib/auditlogsapi/itest/main.go (186 lines of code) (raw):

// Copyright 2020 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 // // http://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. // Binary itest is an integration test for the API with the Stackdriver. // To run the test: // go run lib/auditlogsapi/itest/main.go --enable=true --alsologtostderr --project=your-project --user="subject" package main import ( "context" "flag" "fmt" "net/http" "os" "strings" "time" "cloud.google.com/go/logging" /* copybara-comment */ "github.com/google/go-cmp/cmp" /* copybara-comment */ "google.golang.org/api/option" /* copybara-comment: option */ "google.golang.org/grpc" /* copybara-comment */ "google.golang.org/protobuf/testing/protocmp" /* copybara-comment */ "github.com/pborman/uuid" /* copybara-comment */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/auditlog" /* copybara-comment: auditlog */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/auditlogsapi" /* copybara-comment: auditlogsapi */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/grpcutil" /* copybara-comment: grpcutil */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/httputils" /* copybara-comment: httputils */ glog "github.com/golang/glog" /* copybara-comment */ lgrpcpb "google.golang.org/genproto/googleapis/logging/v2" /* copybara-comment: logging_go_grpc */ lpb "google.golang.org/genproto/googleapis/logging/v2" /* copybara-comment: logging_go_proto */ dpb "github.com/golang/protobuf/ptypes/duration" /* copybara-comment */ apb "github.com/GoogleCloudPlatform/healthcare-federated-access-services/proto/auditlogs/v0" /* copybara-comment: auditlogs_go_proto */ ) var ( enable = flag.Bool("enable", false, "Enable test") projectID = flag.String("project", "", "GCP project ID") serviceName = flag.String("service", "", "service name") userID = flag.String("user", "", "user id (cirrently the subject of tokens)") sdlAddr = flag.String("sdl_addr", "logging.googleapis.com:443", "The address for Stackdriver Logging API") ) func main() { ctx := context.Background() flag.Parse() if !*enable { os.Exit(0) } conn := grpcutil.NewGRPCClient(ctx, *sdlAddr) defer conn.Close() projectName := "projects/" + *projectID sdlc := lgrpcpb.NewLoggingServiceV2Client(conn) logger := NewLogger(ctx, conn, *projectID) s := auditlogsapi.NewAuditLogs(sdlc, *projectID, *serviceName) TestListLogEntriesRequest(ctx, sdlc, projectName, *userID) TestListAuditLogFromProject(ctx, s, projectName, *userID) TestAuditLog(ctx, s, sdlc, logger, projectName, *userID) glog.Exit() } // TestListLogEntriesRequest reads logs audit logs from Stackdriver Logging and displays them. // Checks that the way we call the Stackdriver Logging actually works. func TestListLogEntriesRequest(ctx context.Context, c lgrpcpb.LoggingServiceV2Client, projectName string, userID string) { glog.Infof("## TestListLogEntriesRequest started. ##") defer glog.Infof("## TestListLogEntriesRequest finished. ##\n\n") since := time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC) filters := []string{ fmt.Sprintf("timestamp>=%q", since.Format(time.RFC3339)), `logName="` + projectName + `/logs/federated-access-audit"`, `labels.token_subject="` + userID + `"`, } req := &lpb.ListLogEntriesRequest{ ResourceNames: []string{projectName}, PageSize: 1000, OrderBy: "timestamp desc", Filter: strings.Join(filters, " AND "), } glog.Infof("Request: %v\n\n", req) resp, err := c.ListLogEntries(ctx, req) if err != nil { glog.Errorf("ListLogEntries() failed: %v", err) return } glog.Infof("Response: %v\n\n", resp) } // TestListAuditLogFromProject checks the intergration of both auditlogsapi packages with Stackdriver logging. // Gets auditlogs for the specified user with default options for listing and logs them. func TestListAuditLogFromProject(ctx context.Context, s *auditlogsapi.AuditLogs, projectName string, userID string) { glog.Infof("## TestListAuditLogFromProject started. ##") defer glog.Infof("## TestListAuditLogFromProject finished. ##\n\n") resp, err := s.ListAuditLogs(ctx, &apb.ListAuditLogsRequest{UserId: userID}) if err != nil { glog.Errorf("ListAuditLogs() failed: %v", err) return } for _, l := range resp.GetAuditLogs() { glog.Infof("AuditLog: %v\n\n", l) } } // TestAuditLog checks the intergration of both auditlog and auditlogsapi packages with Stackdriver logging. // It creates audit logs for a user (a generated uuid) and lists and logs them. func TestAuditLog(ctx context.Context, s *auditlogsapi.AuditLogs, c lgrpcpb.LoggingServiceV2Client, logger *logging.Client, projectName string, userID string) { glog.Infof("## TestAuditLog started. ##") defer glog.Infof("## TestAuditLog finished. ##\n\n") // Generate random user id if no user is specified. randomUser := false if userID == "" { userID = "fake-user-id-" + uuid.New() randomUser = true } // Write an access and a policy log. al := &auditlog.RequestLog{ TokenID: "fake-token-id", TokenSubject: userID, TokenIssuer: "fake-issuer-id", TracingID: "fake-tracing-id", RequestMethod: "fake-method", RequestEndpoint: "fake-endpoint", RequestPath: "fake-path", RequestIP: "fake-requester-ip", ErrorType: "fake-error-type", ResponseCode: 1234, Request: httputils.MustNewReq(http.MethodGet, "http://fake.org/fake-path", nil), PassAuthCheck: true, Payload: "fake-reason", } auditlog.WriteRequestLog(ctx, logger, al) pl := &auditlog.PolicyDecisionLog{ TokenID: "fake-token-id", TokenSubject: userID, TokenIssuer: "fake-issuer-id", Resource: "fake-resource", TTL: time.Hour.String(), PassAuthCheck: true, ErrorType: "fake-error-type", Message: "fake-reason", } auditlog.WritePolicyDecisionLog(logger, pl) // It takes a while before written logs are visible on Stackdriver. var got *apb.ListAuditLogsResponse end := time.Now().Add(time.Minute) for len(got.GetAuditLogs()) < 2 && time.Now().Before(end) { var err error got, err = s.ListAuditLogs(ctx, &apb.ListAuditLogsRequest{UserId: userID}) if err != nil { glog.Errorf("ListAuditLogs() failed: %v", err) return } time.Sleep(time.Second) } for _, l := range got.GetAuditLogs() { glog.Infof("AuditLog: %v\n\n", l) } if !randomUser { return } // If it was a randomly generated user, we can check if the logs are correct. got.AuditLogs[0].Name = "" got.AuditLogs[0].Time = nil got.AuditLogs[1].Name = "" got.AuditLogs[1].Time = nil want := &apb.ListAuditLogsResponse{ AuditLogs: []*apb.AuditLog{ { Name: "", ServiceName: "unset-serviceinfo-Name", ServiceType: "unset-serviceinfo-Type", TokenId: "fake-token-id", TokenSubject: userID, TokenIssuer: "fake-issuer-id", Decision: apb.Decision_PASS, ErrorType: "fake-error-type", Reason: "fake-reason", MethodName: http.MethodGet, ResourceName: "fake-endpoint", TracingId: "fake-tracing-id", CallerIp: "fake-requester-ip", HttpResponseCode: 1234, HttpRequest: nil, }, { Name: "", ServiceName: "unset-serviceinfo-Name", ServiceType: "unset-serviceinfo-Type", TokenId: "fake-token-id", TokenSubject: userID, TokenIssuer: "fake-issuer-id", Decision: apb.Decision_PASS, ErrorType: "fake-error-type", Reason: "fake-reason", ResourceName: "fake-resource", Ttl: &dpb.Duration{Seconds: int64(time.Hour / time.Second)}, }, }, } if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { glog.Errorf("ListAuditLogs() returned diff (-want +got):\n%s", diff) } } // NewLogger creates a new logger. func NewLogger(ctx context.Context, conn *grpc.ClientConn, projectName string) *logging.Client { auditlog.LogSync = true logger, err := logging.NewClient(ctx, projectName, option.WithGRPCConn(conn)) if err != nil { glog.Exitf("logging.NewClient(ctx,%v) failed: %v", projectName, err) } return logger }