package api import ( "context" "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/utils" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1" "fmt" "github.com/bufbuild/protovalidate-go" "github.com/google/uuid" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/wrapperspb" "testing" "time" ) func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) { t.Run( "object id empty", func(t *testing.T) { objectId, objectType, err := getObjectIdAndTypeFromAuditParams(&AuditParameters{}) assert.EqualError(t, err, "object id missing") assert.Equal(t, "", objectId) assert.Nil(t, objectType) }, ) t.Run( "object type empty", func(t *testing.T) { objectId, objectType, err := getObjectIdAndTypeFromAuditParams(&AuditParameters{ObjectId: "value"}) assert.EqualError(t, err, "object type missing") assert.Equal(t, "", objectId) assert.Nil(t, objectType) }, ) t.Run( "object id and invalid type set", func(t *testing.T) { objectId, objectType, err := getObjectIdAndTypeFromAuditParams( &AuditParameters{ ObjectId: "value", ObjectType: ObjectTypeFromPluralString("invalid"), }, ) assert.EqualError(t, err, "unknown object type") assert.Equal(t, "", objectId) assert.Nil(t, objectType) }, ) t.Run( "object id and type set", func(t *testing.T) { objectId, objectType, err := getObjectIdAndTypeFromAuditParams( &AuditParameters{ ObjectId: "value", ObjectType: ObjectTypeProject, }, ) assert.NoError(t, err) assert.Equal(t, "value", objectId) assert.Equal(t, ObjectTypeProject, *objectType) }, ) } func Test_AuditLogEntryBuilder(t *testing.T) { t.Run("nothing set", func(t *testing.T) { logEntry, err := NewAuditLogEntryBuilder().Build(context.Background(), SequenceNumber(1)) assert.Error(t, err) assert.Equal(t, "object id missing", err.Error()) assert.Nil(t, logEntry) }) t.Run("details missing", func(t *testing.T) { logEntry, err := NewAuditLogEntryBuilder().WithRequiredLocation("eu01"). WithRequiredObjectId("1"). WithRequiredObjectType(ObjectTypeProject). Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.NotNil(t, logEntry) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(logEntry) assert.Error(t, err) assert.Equal(t, "validation error:\n - proto_payload.service_name: value is required [required]\n - proto_payload.operation_name: value is required [required]\n - proto_payload.request_metadata.caller_supplied_user_agent: value is required [required]\n - proto_payload.request_metadata.request_attributes.method: value is required [required]\n - proto_payload.request_metadata.request_attributes.headers: value is required [required]\n - proto_payload.request_metadata.request_attributes.path: value is required [required]\n - proto_payload.request_metadata.request_attributes.host: value is required [required]\n - proto_payload.request_metadata.request_attributes.scheme: value is required [required]\n - proto_payload.request_metadata.request_attributes.protocol: value is required [required]\n - insert_id: value does not match regex pattern `^[0-9]+/[a-z0-9-]+/[a-z0-9-]+/[0-9]+$` [string.pattern]", err.Error()) }) t.Run("required only", func(t *testing.T) { builder := NewAuditLogEntryBuilder(). WithRequiredLocation("eu01"). WithRequiredObjectId("1"). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredApiRequest(ApiRequest{ Body: nil, Header: TestHeaders, Host: "localhost", Method: "POST", Scheme: "https", Proto: "HTTP/1.1", URL: RequestUrl{ Path: "/", RawQuery: nil, }, }). WithRequiredRequestClientIp("127.0.0.1"). WithRequiredServiceName("demo-service"). WithRequiredWorkerId("worker-id") logEntry, err := builder.Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.NotNil(t, logEntry) assert.Equal(t, "projects/1/logs/admin-activity", logEntry.LogName) assert.Nil(t, logEntry.Labels) assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity) assert.NotNil(t, logEntry.Timestamp) assert.Nil(t, logEntry.CorrelationId) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", logEntry.InsertId) assert.NotNil(t, logEntry.ProtoPayload) authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo assert.NotNil(t, authenticationInfo) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, logEntry.ProtoPayload.AuthorizationInfo) assert.Nil(t, logEntry.ProtoPayload.Metadata) assert.Equal(t, "stackit.demo-service.v1.operation", logEntry.ProtoPayload.OperationName) assert.Nil(t, logEntry.ProtoPayload.Request) requestMetadata := logEntry.ProtoPayload.RequestMetadata assert.NotNil(t, requestMetadata) assert.Equal(t, "127.0.0.1", requestMetadata.CallerIp) assert.Equal(t, "custom", requestMetadata.CallerSuppliedUserAgent) requestAttributes := requestMetadata.RequestAttributes assert.NotNil(t, requestAttributes) assert.Equal(t, "/", requestAttributes.Path) assert.NotNil(t, requestAttributes.Time) assert.Equal(t, "localhost", requestAttributes.Host) assert.Equal(t, auditV1.AttributeContext_HTTP_METHOD_POST, requestAttributes.Method) assert.Nil(t, requestAttributes.Id) assert.Equal(t, "https", requestAttributes.Scheme) assert.Equal(t, map[string]string{"user-agent": "custom"}, requestAttributes.Headers) assert.Nil(t, requestAttributes.Query) assert.Equal(t, "HTTP/1.1", requestAttributes.Protocol) requestAttributesAuth := requestAttributes.Auth assert.NotNil(t, requestAttributesAuth) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63/https%3A%2F%2Faccounts.dev.stackit.cloud", requestAttributesAuth.Principal) assert.Equal(t, []string{"stackit-portal-login-dev-client-id"}, requestAttributesAuth.Audiences) assert.NotNil(t, requestAttributesAuth.Claims) assert.Equal(t, "projects/1", logEntry.ProtoPayload.ResourceName) assert.Nil(t, logEntry.ProtoPayload.Response) responseMetadata := logEntry.ProtoPayload.ResponseMetadata assert.NotNil(t, responseMetadata) assert.Nil(t, responseMetadata.ErrorDetails) assert.Nil(t, responseMetadata.ErrorMessage) assert.Equal(t, wrapperspb.Int32(200), responseMetadata.StatusCode) responseAttributes := responseMetadata.ResponseAttributes assert.NotNil(t, responseAttributes) assert.Nil(t, responseAttributes.Headers) assert.Nil(t, responseAttributes.NumResponseItems) assert.Nil(t, responseAttributes.Size) assert.NotNil(t, responseAttributes.Time) assert.Equal(t, "demo-service", logEntry.ProtoPayload.ServiceName) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(logEntry) assert.NoError(t, err) }) t.Run("with details", func(t *testing.T) { details := map[string]interface{}{"key": "detail"} permission := "project.edit" permissionCheckResult := true requestTime := time.Now().AddDate(0, 0, -2).UTC() responseTime := time.Now().AddDate(0, 0, -1).UTC() responseBody := map[string]interface{}{"key": "response"} responseBodyBytes, err := ResponseBodyToBytes(responseBody) assert.NoError(t, err) builder := NewAuditLogEntryBuilder(). WithRequiredLocation("eu01"). WithRequiredObjectId("1"). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredApiRequest(ApiRequest{ Body: nil, Header: TestHeaders, Host: "localhost", Method: "POST", Scheme: "https", Proto: "HTTP/1.1", URL: RequestUrl{ Path: "/", RawQuery: nil, }, }). WithRequiredRequestClientIp("127.0.0.1"). WithRequiredServiceName("demo-service"). WithRequiredWorkerId("worker-id"). WithAuditPermission(permission). WithAuditPermissionCheckResult(permissionCheckResult). WithDetails(details). WithEventType(EventTypePolicyDenied). WithLabels(map[string]string{"key": "label"}). WithNumResponseItems(int64(10)). WithRequestCorrelationId("correlationId"). WithRequestId("requestId"). WithRequestTime(requestTime). WithResponseBodyBytes(responseBodyBytes). WithResponseHeaders(map[string][]string{"key": {"header"}}). WithResponseTime(responseTime). WithSeverity(auditV1.LogSeverity_LOG_SEVERITY_ERROR). WithStatusCode(400) logEntry, err := builder.Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.NotNil(t, logEntry) assert.Equal(t, "projects/1/logs/policy-denied", logEntry.LogName) assert.Equal(t, map[string]string{"key": "label"}, logEntry.Labels) assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_ERROR, logEntry.Severity) assert.NotNil(t, logEntry.Timestamp) assert.Equal(t, "correlationId", *logEntry.CorrelationId) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", logEntry.InsertId) assert.NotNil(t, logEntry.ProtoPayload) authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo assert.NotNil(t, authenticationInfo) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Equal(t, []*auditV1.AuthorizationInfo{{ Resource: "projects/1", Permission: &permission, Granted: &permissionCheckResult, }}, logEntry.ProtoPayload.AuthorizationInfo) expectedMetadata, _ := structpb.NewStruct(details) assert.Equal(t, expectedMetadata, logEntry.ProtoPayload.Metadata) assert.Equal(t, "stackit.demo-service.v1.operation", logEntry.ProtoPayload.OperationName) assert.Nil(t, logEntry.ProtoPayload.Request) requestMetadata := logEntry.ProtoPayload.RequestMetadata assert.NotNil(t, requestMetadata) assert.Equal(t, "127.0.0.1", requestMetadata.CallerIp) assert.Equal(t, "custom", requestMetadata.CallerSuppliedUserAgent) requestAttributes := requestMetadata.RequestAttributes assert.NotNil(t, requestAttributes) assert.Equal(t, "/", requestAttributes.Path) assert.NotNil(t, requestAttributes.Time) assert.Equal(t, "localhost", requestAttributes.Host) assert.Equal(t, auditV1.AttributeContext_HTTP_METHOD_POST, requestAttributes.Method) assert.Equal(t, "requestId", *requestAttributes.Id) assert.Equal(t, "https", requestAttributes.Scheme) assert.Equal(t, map[string]string{"user-agent": "custom"}, requestAttributes.Headers) assert.Nil(t, requestAttributes.Query) assert.Equal(t, "HTTP/1.1", requestAttributes.Protocol) requestAttributesAuth := requestAttributes.Auth assert.NotNil(t, requestAttributesAuth) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63/https%3A%2F%2Faccounts.dev.stackit.cloud", requestAttributesAuth.Principal) assert.Equal(t, []string{"stackit-portal-login-dev-client-id"}, requestAttributesAuth.Audiences) assert.NotNil(t, requestAttributesAuth.Claims) expectedResponse, _ := structpb.NewStruct(responseBody) assert.Equal(t, "projects/1", logEntry.ProtoPayload.ResourceName) assert.Equal(t, expectedResponse, logEntry.ProtoPayload.Response) responseMetadata := logEntry.ProtoPayload.ResponseMetadata assert.NotNil(t, responseMetadata) assert.Nil(t, responseMetadata.ErrorDetails) assert.Equal(t, "Client error", *responseMetadata.ErrorMessage) assert.Equal(t, wrapperspb.Int32(400), responseMetadata.StatusCode) responseAttributes := responseMetadata.ResponseAttributes assert.NotNil(t, responseAttributes) assert.Equal(t, map[string]string{"key": "header"}, responseAttributes.Headers) assert.Equal(t, wrapperspb.Int64(10), responseAttributes.NumResponseItems) assert.Equal(t, wrapperspb.Int64(18), responseAttributes.Size) assert.NotNil(t, responseAttributes.Time) assert.Equal(t, "demo-service", logEntry.ProtoPayload.ServiceName) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(logEntry) assert.NoError(t, err) }) t.Run("system event", func(t *testing.T) { builder := NewAuditLogEntryBuilder(). WithRequiredLocation("eu01"). WithRequiredObjectId("1"). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredServiceName("demo-service"). WithRequiredWorkerId("worker-id"). AsSystemEvent() logEntry, err := builder.Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.NotNil(t, logEntry) assert.Equal(t, fmt.Sprintf("system/%s/logs/system-event", uuid.Nil.String()), logEntry.LogName) assert.Nil(t, logEntry.Labels) assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity) assert.NotNil(t, logEntry.Timestamp) assert.Nil(t, logEntry.CorrelationId) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", logEntry.InsertId) assert.NotNil(t, logEntry.ProtoPayload) authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo assert.NotNil(t, authenticationInfo) assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail) assert.Equal(t, "none", authenticationInfo.PrincipalId) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, logEntry.ProtoPayload.AuthorizationInfo) assert.Nil(t, logEntry.ProtoPayload.Metadata) assert.Equal(t, "stackit.demo-service.v1.operation", logEntry.ProtoPayload.OperationName) assert.Nil(t, logEntry.ProtoPayload.Request) requestMetadata := logEntry.ProtoPayload.RequestMetadata assert.NotNil(t, requestMetadata) assert.Equal(t, "0.0.0.0", requestMetadata.CallerIp) assert.Equal(t, "none", requestMetadata.CallerSuppliedUserAgent) requestAttributes := requestMetadata.RequestAttributes assert.NotNil(t, requestAttributes) assert.Equal(t, "none", requestAttributes.Path) assert.NotNil(t, requestAttributes.Time) assert.Equal(t, "0.0.0.0", requestAttributes.Host) assert.Equal(t, auditV1.AttributeContext_HTTP_METHOD_OTHER, requestAttributes.Method) assert.Nil(t, requestAttributes.Id) assert.Equal(t, "none", requestAttributes.Scheme) assert.Equal(t, map[string]string{"user-agent": "none"}, requestAttributes.Headers) assert.Nil(t, requestAttributes.Query) assert.Equal(t, "none", requestAttributes.Protocol) requestAttributesAuth := requestAttributes.Auth assert.NotNil(t, requestAttributesAuth) assert.Equal(t, "none/none", requestAttributesAuth.Principal) assert.Equal(t, []string{}, requestAttributesAuth.Audiences) assert.NotNil(t, requestAttributesAuth.Claims) assert.Equal(t, map[string]any{}, requestAttributesAuth.Claims.AsMap()) assert.Equal(t, "projects/1", logEntry.ProtoPayload.ResourceName) assert.Nil(t, logEntry.ProtoPayload.Response) responseMetadata := logEntry.ProtoPayload.ResponseMetadata assert.NotNil(t, responseMetadata) assert.Nil(t, responseMetadata.ErrorDetails) assert.Nil(t, responseMetadata.ErrorMessage) assert.Equal(t, wrapperspb.Int32(200), responseMetadata.StatusCode) responseAttributes := responseMetadata.ResponseAttributes assert.NotNil(t, responseAttributes) assert.Nil(t, responseAttributes.Headers) assert.Nil(t, responseAttributes.NumResponseItems) assert.Nil(t, responseAttributes.Size) assert.NotNil(t, responseAttributes.Time) assert.Equal(t, "demo-service", logEntry.ProtoPayload.ServiceName) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(logEntry) assert.NoError(t, err) }) t.Run("with response body unserialized", func(t *testing.T) { details := map[string]interface{}{"key": "detail"} permission := "project.edit" permissionCheckResult := true requestTime := time.Now().AddDate(0, 0, -2).UTC() responseTime := time.Now().AddDate(0, 0, -1).UTC() responseBody := map[string]interface{}{"key": "response"} builder := NewAuditLogEntryBuilder(). WithRequiredLocation("eu01"). WithRequiredObjectId("1"). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredApiRequest(ApiRequest{ Body: nil, Header: TestHeaders, Host: "localhost", Method: "POST", Scheme: "https", Proto: "HTTP/1.1", URL: RequestUrl{ Path: "/", RawQuery: nil, }, }). WithRequiredRequestClientIp("127.0.0.1"). WithRequiredServiceName("demo-service"). WithRequiredWorkerId("worker-id"). WithAuditPermission(permission). WithAuditPermissionCheckResult(permissionCheckResult). WithDetails(details). WithEventType(EventTypeSystemEvent). WithLabels(map[string]string{"key": "label"}). WithNumResponseItems(int64(10)). WithRequestCorrelationId("correlationId"). WithRequestId("requestId"). WithRequestTime(requestTime). WithResponseBody(responseBody). WithResponseHeaders(map[string][]string{"key": {"header"}}). WithResponseTime(responseTime). WithSeverity(auditV1.LogSeverity_LOG_SEVERITY_ERROR). WithStatusCode(400) logEntry, err := builder.Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.NotNil(t, logEntry) assert.NotNil(t, logEntry.ProtoPayload) expectedResponse, _ := structpb.NewStruct(responseBody) assert.Equal(t, "projects/1", logEntry.ProtoPayload.ResourceName) assert.Equal(t, expectedResponse, logEntry.ProtoPayload.Response) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(logEntry) assert.NoError(t, err) }) t.Run("with response body and response body bytes set", func(t *testing.T) { responseBody := map[string]interface{}{"key": "response"} responseBodyBytes, err := ResponseBodyToBytes(responseBody) assert.NoError(t, err) builder := NewAuditLogEntryBuilder(). WithRequiredApiRequest(ApiRequest{ Body: nil, Header: TestHeaders, Host: "localhost", Method: "POST", Scheme: "https", Proto: "HTTP/1.1", URL: RequestUrl{ Path: "/", RawQuery: nil, }, }). WithRequiredLocation("eu01"). WithRequiredObjectId("1"). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredRequestClientIp("127.0.0.1"). WithRequiredServiceName("demo-service"). WithRequiredWorkerId("worker-id"). WithResponseBody(responseBody). WithResponseBodyBytes(responseBodyBytes) logEntry, err := builder.Build(context.Background(), SequenceNumber(1)) assert.EqualError(t, err, "responseBodyBytes and responseBody set") assert.Nil(t, logEntry) }) t.Run("with invalid response body", func(t *testing.T) { builder := NewAuditLogEntryBuilder(). WithRequiredApiRequest(ApiRequest{ Body: nil, Header: TestHeaders, Host: "localhost", Method: "POST", Scheme: "https", Proto: "HTTP/1.1", URL: RequestUrl{ Path: "/", RawQuery: nil, }, }). WithRequiredLocation("eu01"). WithRequiredObjectId("1"). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredRequestClientIp("127.0.0.1"). WithRequiredServiceName("demo-service"). WithRequiredWorkerId("worker-id"). WithResponseBody("invalid") logEntry, err := builder.Build(context.Background(), SequenceNumber(1)) assert.EqualError(t, err, "json: cannot unmarshal string into Go value of type map[string]interface {}\ninvalid response") assert.Nil(t, logEntry) }) } func Test_AuditEventBuilder(t *testing.T) { t.Run("nothing set", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). Build(context.Background(), SequenceNumber(1)) assert.Error(t, err) assert.Equal(t, "object id missing", err.Error()) assert.Nil(t, cloudEvent) assert.Nil(t, routingIdentifier) }) t.Run("details missing", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). WithRequiredObjectId("objectId"). WithRequiredObjectType(ObjectTypeProject). Build(context.Background(), SequenceNumber(1)) assert.Error(t, err) assert.Equal(t, "validation error:\n - log_name: value does not match regex pattern `^[a-z-]+/[a-z0-9-]+/logs/(?:admin-activity|system-event|policy-denied|data-access)$` [string.pattern]\n - proto_payload.operation_name: value is required [required]\n - proto_payload.resource_name: value does not match regex pattern `^[a-z]+/[a-z0-9-]+(?:/[a-z0-9-]+/[a-z0-9-_]+)*$` [string.pattern]\n - proto_payload.request_metadata.caller_supplied_user_agent: value is required [required]\n - proto_payload.request_metadata.request_attributes.method: value is required [required]\n - proto_payload.request_metadata.request_attributes.headers: value is required [required]\n - proto_payload.request_metadata.request_attributes.path: value is required [required]\n - proto_payload.request_metadata.request_attributes.host: value is required [required]\n - proto_payload.request_metadata.request_attributes.scheme: value is required [required]\n - proto_payload.request_metadata.request_attributes.protocol: value is required [required]", err.Error()) assert.Nil(t, cloudEvent) assert.Nil(t, routingIdentifier) }) t.Run("required only", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() objectId := uuid.NewString() operation := "stackit.demo-service.v1.operation" builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). WithRequiredObjectId(objectId). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation(operation). WithRequiredApiRequest(ApiRequest{ Body: nil, Header: TestHeaders, Host: "localhost", Method: "POST", Scheme: "https", Proto: "HTTP/1.1", URL: RequestUrl{ Path: "/", RawQuery: nil, }, }). WithRequiredRequestClientIp("127.0.0.1") routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject} cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.True(t, builder.IsBuilt()) assert.Equal(t, &routableIdentifier, routingIdentifier) assert.NotNil(t, cloudEvent) assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType) assert.Equal(t, "audit.v1.RoutableAuditEvent", cloudEvent.DataType) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", cloudEvent.Id) assert.Equal(t, "demo-service", cloudEvent.Source) assert.Equal(t, "1.0", cloudEvent.SpecVersion) assert.Equal(t, fmt.Sprintf("projects/%s", objectId), cloudEvent.Subject) assert.NotNil(t, cloudEvent.Time) assert.Equal(t, "", *cloudEvent.TraceParent) assert.Equal(t, "", *cloudEvent.TraceState) var routableAuditEvent auditV1.RoutableAuditEvent assert.NotNil(t, cloudEvent.Data) assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent)) assert.Equal(t, routableIdentifier.ToObjectIdentifier().Identifier, routableAuditEvent.ObjectIdentifier.Identifier) assert.Equal(t, routableIdentifier.ToObjectIdentifier().Type, routableAuditEvent.ObjectIdentifier.Type) assert.Equal(t, auditV1.Visibility_VISIBILITY_PUBLIC, routableAuditEvent.Visibility) assert.Equal(t, operation, routableAuditEvent.OperationName) var logEntry auditV1.AuditLogEntry assert.NotNil(t, routableAuditEvent.GetUnencryptedData().Data) assert.NoError(t, proto.Unmarshal(routableAuditEvent.GetUnencryptedData().Data, &logEntry)) assert.Equal(t, fmt.Sprintf("projects/%s/logs/admin-activity", objectId), logEntry.LogName) assert.Nil(t, logEntry.Labels) assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity) assert.NotNil(t, logEntry.Timestamp) assert.Nil(t, logEntry.CorrelationId) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", logEntry.InsertId) assert.NotNil(t, logEntry.ProtoPayload) authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo assert.NotNil(t, authenticationInfo) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, logEntry.ProtoPayload.AuthorizationInfo) assert.Nil(t, logEntry.ProtoPayload.Metadata) assert.Equal(t, operation, logEntry.ProtoPayload.OperationName) assert.Nil(t, logEntry.ProtoPayload.Request) requestMetadata := logEntry.ProtoPayload.RequestMetadata assert.NotNil(t, requestMetadata) assert.Equal(t, "127.0.0.1", requestMetadata.CallerIp) assert.Equal(t, "custom", requestMetadata.CallerSuppliedUserAgent) requestAttributes := requestMetadata.RequestAttributes assert.NotNil(t, requestAttributes) assert.Equal(t, "/", requestAttributes.Path) assert.NotNil(t, requestAttributes.Time) assert.Equal(t, "localhost", requestAttributes.Host) assert.Equal(t, auditV1.AttributeContext_HTTP_METHOD_POST, requestAttributes.Method) assert.Nil(t, requestAttributes.Id) assert.Equal(t, "https", requestAttributes.Scheme) assert.Equal(t, map[string]string{"user-agent": "custom"}, requestAttributes.Headers) assert.Nil(t, requestAttributes.Query) assert.Equal(t, "HTTP/1.1", requestAttributes.Protocol) requestAttributesAuth := requestAttributes.Auth assert.NotNil(t, requestAttributesAuth) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63/https%3A%2F%2Faccounts.dev.stackit.cloud", requestAttributesAuth.Principal) assert.Equal(t, []string{"stackit-portal-login-dev-client-id"}, requestAttributesAuth.Audiences) assert.NotNil(t, requestAttributesAuth.Claims) assert.Equal(t, fmt.Sprintf("projects/%s", objectId), logEntry.ProtoPayload.ResourceName) assert.Nil(t, logEntry.ProtoPayload.Response) responseMetadata := logEntry.ProtoPayload.ResponseMetadata assert.NotNil(t, responseMetadata) assert.Nil(t, responseMetadata.ErrorDetails) assert.Nil(t, responseMetadata.ErrorMessage) assert.Equal(t, wrapperspb.Int32(200), responseMetadata.StatusCode) responseAttributes := responseMetadata.ResponseAttributes assert.NotNil(t, responseAttributes) assert.Nil(t, responseAttributes.Headers) assert.Nil(t, responseAttributes.NumResponseItems) assert.Nil(t, responseAttributes.Size) assert.NotNil(t, responseAttributes.Time) assert.Equal(t, "demo-service", logEntry.ProtoPayload.ServiceName) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(&logEntry) assert.NoError(t, err) }) t.Run("with details", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() objectId := uuid.NewString() operation := "stackit.demo-service.v1.operation" details := map[string]interface{}{"key": "detail"} permission := "project.edit" permissionCheckResult := true requestTime := time.Now().AddDate(0, 0, -2).UTC() responseTime := time.Now().AddDate(0, 0, -1).UTC() responseBody := map[string]interface{}{"key": "response"} responseBodyBytes, err := ResponseBodyToBytes(responseBody) assert.NoError(t, err) builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). WithRequiredObjectId(objectId). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation(operation). WithRequiredApiRequest(ApiRequest{ Body: nil, Header: TestHeaders, Host: "localhost", Method: "POST", Scheme: "https", Proto: "HTTP/1.1", URL: RequestUrl{ Path: "/", RawQuery: nil, }, }). WithRequiredRequestClientIp("127.0.0.1"). WithAuditPermission(permission). WithAuditPermissionCheckResult(permissionCheckResult). WithDetails(details). WithEventType(EventTypeAdminActivity). WithLabels(map[string]string{"key": "label"}). WithNumResponseItems(int64(10)). WithRequestCorrelationId("correlationId"). WithRequestId("requestId"). WithRequestTime(requestTime). WithResponseBodyBytes(responseBodyBytes). WithResponseHeaders(map[string][]string{"key": {"header"}}). WithResponseTime(responseTime). WithSeverity(auditV1.LogSeverity_LOG_SEVERITY_ERROR). WithStatusCode(400). WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE) routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject} cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.True(t, builder.IsBuilt()) assert.Equal(t, &routableIdentifier, routingIdentifier) assert.NotNil(t, cloudEvent) assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType) assert.Equal(t, "audit.v1.RoutableAuditEvent", cloudEvent.DataType) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", cloudEvent.Id) assert.Equal(t, "demo-service", cloudEvent.Source) assert.Equal(t, "1.0", cloudEvent.SpecVersion) assert.Equal(t, fmt.Sprintf("projects/%s", objectId), cloudEvent.Subject) assert.NotNil(t, cloudEvent.Time) assert.Equal(t, "", *cloudEvent.TraceParent) assert.Equal(t, "", *cloudEvent.TraceState) var routableAuditEvent auditV1.RoutableAuditEvent assert.NotNil(t, cloudEvent.Data) assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent)) assert.Equal(t, routableIdentifier.ToObjectIdentifier().Identifier, routableAuditEvent.ObjectIdentifier.Identifier) assert.Equal(t, routableIdentifier.ToObjectIdentifier().Type, routableAuditEvent.ObjectIdentifier.Type) assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility) assert.Equal(t, operation, routableAuditEvent.OperationName) var logEntry auditV1.AuditLogEntry assert.NotNil(t, routableAuditEvent.GetUnencryptedData().Data) assert.NoError(t, proto.Unmarshal(routableAuditEvent.GetUnencryptedData().Data, &logEntry)) assert.Equal(t, fmt.Sprintf("projects/%s/logs/admin-activity", objectId), logEntry.LogName) assert.Equal(t, map[string]string{"key": "label"}, logEntry.Labels) assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_ERROR, logEntry.Severity) assert.NotNil(t, logEntry.Timestamp) assert.Equal(t, "correlationId", *logEntry.CorrelationId) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", logEntry.InsertId) assert.NotNil(t, logEntry.ProtoPayload) authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo assert.NotNil(t, authenticationInfo) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Equal(t, []*auditV1.AuthorizationInfo{{ Resource: fmt.Sprintf("projects/%s", objectId), Permission: &permission, Granted: &permissionCheckResult, }}, logEntry.ProtoPayload.AuthorizationInfo) expectedMetadata, _ := structpb.NewStruct(details) assert.Equal(t, expectedMetadata, logEntry.ProtoPayload.Metadata) assert.Equal(t, "stackit.demo-service.v1.operation", logEntry.ProtoPayload.OperationName) assert.Nil(t, logEntry.ProtoPayload.Request) requestMetadata := logEntry.ProtoPayload.RequestMetadata assert.NotNil(t, requestMetadata) assert.Equal(t, "127.0.0.1", requestMetadata.CallerIp) assert.Equal(t, "custom", requestMetadata.CallerSuppliedUserAgent) requestAttributes := requestMetadata.RequestAttributes assert.NotNil(t, requestAttributes) assert.Equal(t, "/", requestAttributes.Path) assert.NotNil(t, requestAttributes.Time) assert.Equal(t, "localhost", requestAttributes.Host) assert.Equal(t, auditV1.AttributeContext_HTTP_METHOD_POST, requestAttributes.Method) assert.Equal(t, "requestId", *requestAttributes.Id) assert.Equal(t, "https", requestAttributes.Scheme) assert.Equal(t, map[string]string{"user-agent": "custom"}, requestAttributes.Headers) assert.Nil(t, requestAttributes.Query) assert.Equal(t, "HTTP/1.1", requestAttributes.Protocol) requestAttributesAuth := requestAttributes.Auth assert.NotNil(t, requestAttributesAuth) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63/https%3A%2F%2Faccounts.dev.stackit.cloud", requestAttributesAuth.Principal) assert.Equal(t, []string{"stackit-portal-login-dev-client-id"}, requestAttributesAuth.Audiences) assert.NotNil(t, requestAttributesAuth.Claims) expectedResponse, _ := structpb.NewStruct(responseBody) assert.Equal(t, fmt.Sprintf("projects/%s", objectId), logEntry.ProtoPayload.ResourceName) assert.Equal(t, expectedResponse, logEntry.ProtoPayload.Response) responseMetadata := logEntry.ProtoPayload.ResponseMetadata assert.NotNil(t, responseMetadata) assert.Nil(t, responseMetadata.ErrorDetails) assert.Equal(t, "Client error", *responseMetadata.ErrorMessage) assert.Equal(t, wrapperspb.Int32(400), responseMetadata.StatusCode) responseAttributes := responseMetadata.ResponseAttributes assert.NotNil(t, responseAttributes) assert.Equal(t, map[string]string{"key": "header"}, responseAttributes.Headers) assert.Equal(t, wrapperspb.Int64(10), responseAttributes.NumResponseItems) assert.Equal(t, wrapperspb.Int64(18), responseAttributes.Size) assert.NotNil(t, responseAttributes.Time) assert.Equal(t, "demo-service", logEntry.ProtoPayload.ServiceName) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(&logEntry) assert.NoError(t, err) }) t.Run("system event with object reference", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() objectId := uuid.NewString() operation := "stackit.demo-service.v1.operation" builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). WithRequiredObjectId(objectId). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation(operation). AsSystemEvent() cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.True(t, builder.IsBuilt()) assert.Equal(t, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier) assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type) assert.NotNil(t, cloudEvent) assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType) assert.Equal(t, "audit.v1.RoutableAuditEvent", cloudEvent.DataType) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", cloudEvent.Id) assert.Equal(t, "demo-service", cloudEvent.Source) assert.Equal(t, "1.0", cloudEvent.SpecVersion) assert.Equal(t, fmt.Sprintf("projects/%s", objectId), cloudEvent.Subject) assert.NotNil(t, cloudEvent.Time) assert.Equal(t, "", *cloudEvent.TraceParent) assert.Equal(t, "", *cloudEvent.TraceState) var routableAuditEvent auditV1.RoutableAuditEvent assert.NotNil(t, cloudEvent.Data) assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent)) assert.Equal(t, SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier) assert.Equal(t, SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type) assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility) assert.Equal(t, operation, routableAuditEvent.OperationName) var logEntry auditV1.AuditLogEntry assert.NotNil(t, routableAuditEvent.GetUnencryptedData().Data) assert.NoError(t, proto.Unmarshal(routableAuditEvent.GetUnencryptedData().Data, &logEntry)) assert.Equal(t, fmt.Sprintf("system/%s/logs/system-event", uuid.Nil.String()), logEntry.LogName) assert.Nil(t, logEntry.Labels) assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity) assert.NotNil(t, logEntry.Timestamp) assert.Nil(t, logEntry.CorrelationId) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", logEntry.InsertId) assert.NotNil(t, logEntry.ProtoPayload) authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo assert.NotNil(t, authenticationInfo) assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail) assert.Equal(t, "none", authenticationInfo.PrincipalId) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, logEntry.ProtoPayload.AuthorizationInfo) assert.Nil(t, logEntry.ProtoPayload.Metadata) assert.Equal(t, operation, logEntry.ProtoPayload.OperationName) assert.Nil(t, logEntry.ProtoPayload.Request) requestMetadata := logEntry.ProtoPayload.RequestMetadata assert.NotNil(t, requestMetadata) assert.Equal(t, "0.0.0.0", requestMetadata.CallerIp) assert.Equal(t, "none", requestMetadata.CallerSuppliedUserAgent) requestAttributes := requestMetadata.RequestAttributes assert.NotNil(t, requestAttributes) assert.Equal(t, "none", requestAttributes.Path) assert.NotNil(t, requestAttributes.Time) assert.Equal(t, "0.0.0.0", requestAttributes.Host) assert.Equal(t, auditV1.AttributeContext_HTTP_METHOD_OTHER, requestAttributes.Method) assert.Nil(t, requestAttributes.Id) assert.Equal(t, "none", requestAttributes.Scheme) assert.Equal(t, map[string]string{"user-agent": "none"}, requestAttributes.Headers) assert.Nil(t, requestAttributes.Query) assert.Equal(t, "none", requestAttributes.Protocol) requestAttributesAuth := requestAttributes.Auth assert.NotNil(t, requestAttributesAuth) assert.Equal(t, "none/none", requestAttributesAuth.Principal) assert.Nil(t, requestAttributesAuth.Audiences) assert.NotNil(t, requestAttributesAuth.Claims) assert.Equal(t, map[string]any{}, requestAttributesAuth.Claims.AsMap()) assert.Equal(t, fmt.Sprintf("projects/%s", objectId), logEntry.ProtoPayload.ResourceName) assert.Nil(t, logEntry.ProtoPayload.Response) responseMetadata := logEntry.ProtoPayload.ResponseMetadata assert.NotNil(t, responseMetadata) assert.Nil(t, responseMetadata.ErrorDetails) assert.Nil(t, responseMetadata.ErrorMessage) assert.Equal(t, wrapperspb.Int32(200), responseMetadata.StatusCode) responseAttributes := responseMetadata.ResponseAttributes assert.NotNil(t, responseAttributes) assert.Nil(t, responseAttributes.Headers) assert.Nil(t, responseAttributes.NumResponseItems) assert.Nil(t, responseAttributes.Size) assert.NotNil(t, responseAttributes.Time) assert.Equal(t, "demo-service", logEntry.ProtoPayload.ServiceName) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(&logEntry) assert.NoError(t, err) }) t.Run("system event", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() operation := "stackit.demo-service.v1.operation" builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). WithRequiredOperation(operation). AsSystemEvent() cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.True(t, builder.IsBuilt()) assert.Equal(t, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier) assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type) assert.NotNil(t, cloudEvent) assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType) assert.Equal(t, "audit.v1.RoutableAuditEvent", cloudEvent.DataType) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", cloudEvent.Id) assert.Equal(t, "demo-service", cloudEvent.Source) assert.Equal(t, "1.0", cloudEvent.SpecVersion) assert.Equal(t, fmt.Sprintf("system/%s", uuid.Nil.String()), cloudEvent.Subject) assert.NotNil(t, cloudEvent.Time) assert.Equal(t, "", *cloudEvent.TraceParent) assert.Equal(t, "", *cloudEvent.TraceState) var routableAuditEvent auditV1.RoutableAuditEvent assert.NotNil(t, cloudEvent.Data) assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent)) assert.Equal(t, SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier) assert.Equal(t, SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type) assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility) assert.Equal(t, operation, routableAuditEvent.OperationName) var logEntry auditV1.AuditLogEntry assert.NotNil(t, routableAuditEvent.GetUnencryptedData().Data) assert.NoError(t, proto.Unmarshal(routableAuditEvent.GetUnencryptedData().Data, &logEntry)) assert.Equal(t, fmt.Sprintf("system/%s/logs/system-event", uuid.Nil.String()), logEntry.LogName) assert.Nil(t, logEntry.Labels) assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity) assert.NotNil(t, logEntry.Timestamp) assert.Nil(t, logEntry.CorrelationId) assert.Regexp(t, "[0-9]+/eu01/worker-id/1", logEntry.InsertId) assert.NotNil(t, logEntry.ProtoPayload) authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo assert.NotNil(t, authenticationInfo) assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail) assert.Equal(t, "none", authenticationInfo.PrincipalId) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, logEntry.ProtoPayload.AuthorizationInfo) assert.Nil(t, logEntry.ProtoPayload.Metadata) assert.Equal(t, operation, logEntry.ProtoPayload.OperationName) assert.Nil(t, logEntry.ProtoPayload.Request) requestMetadata := logEntry.ProtoPayload.RequestMetadata assert.NotNil(t, requestMetadata) assert.Equal(t, "0.0.0.0", requestMetadata.CallerIp) assert.Equal(t, "none", requestMetadata.CallerSuppliedUserAgent) requestAttributes := requestMetadata.RequestAttributes assert.NotNil(t, requestAttributes) assert.Equal(t, "none", requestAttributes.Path) assert.NotNil(t, requestAttributes.Time) assert.Equal(t, "0.0.0.0", requestAttributes.Host) assert.Equal(t, auditV1.AttributeContext_HTTP_METHOD_OTHER, requestAttributes.Method) assert.Nil(t, requestAttributes.Id) assert.Equal(t, "none", requestAttributes.Scheme) assert.Equal(t, map[string]string{"user-agent": "none"}, requestAttributes.Headers) assert.Nil(t, requestAttributes.Query) assert.Equal(t, "none", requestAttributes.Protocol) requestAttributesAuth := requestAttributes.Auth assert.NotNil(t, requestAttributesAuth) assert.Equal(t, "none/none", requestAttributesAuth.Principal) assert.Nil(t, requestAttributesAuth.Audiences) assert.NotNil(t, requestAttributesAuth.Claims) assert.Equal(t, map[string]any{}, requestAttributesAuth.Claims.AsMap()) assert.Equal(t, fmt.Sprintf("system/%s", uuid.Nil.String()), logEntry.ProtoPayload.ResourceName) assert.Nil(t, logEntry.ProtoPayload.Response) responseMetadata := logEntry.ProtoPayload.ResponseMetadata assert.NotNil(t, responseMetadata) assert.Nil(t, responseMetadata.ErrorDetails) assert.Nil(t, responseMetadata.ErrorMessage) assert.Equal(t, wrapperspb.Int32(200), responseMetadata.StatusCode) responseAttributes := responseMetadata.ResponseAttributes assert.NotNil(t, responseAttributes) assert.Nil(t, responseAttributes.Headers) assert.Nil(t, responseAttributes.NumResponseItems) assert.Nil(t, responseAttributes.Size) assert.NotNil(t, responseAttributes.Time) assert.Equal(t, "demo-service", logEntry.ProtoPayload.ServiceName) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(&logEntry) assert.NoError(t, err) }) t.Run("with response body unserialized", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() objectId := uuid.NewString() operation := "stackit.demo-service.v1.operation" details := map[string]interface{}{"key": "detail"} permission := "project.edit" permissionCheckResult := true requestTime := time.Now().AddDate(0, 0, -2).UTC() responseTime := time.Now().AddDate(0, 0, -1).UTC() responseBody := map[string]interface{}{"key": "response"} builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). WithRequiredObjectId(objectId). WithRequiredObjectType(ObjectTypeProject). WithRequiredOperation(operation). WithRequiredApiRequest(ApiRequest{ Body: nil, Header: TestHeaders, Host: "localhost", Method: "POST", Scheme: "https", Proto: "HTTP/1.1", URL: RequestUrl{ Path: "/", RawQuery: nil, }, }). WithRequiredRequestClientIp("127.0.0.1"). WithAuditPermission(permission). WithAuditPermissionCheckResult(permissionCheckResult). WithDetails(details). WithEventType(EventTypeAdminActivity). WithLabels(map[string]string{"key": "label"}). WithNumResponseItems(int64(10)). WithRequestCorrelationId("correlationId"). WithRequestId("requestId"). WithRequestTime(requestTime). WithResponseBody(responseBody). WithResponseHeaders(map[string][]string{"key": {"header"}}). WithResponseTime(responseTime). WithSeverity(auditV1.LogSeverity_LOG_SEVERITY_ERROR). WithStatusCode(400). WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE) routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject} cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) assert.NoError(t, err) assert.True(t, builder.IsBuilt()) assert.Equal(t, &routableIdentifier, routingIdentifier) assert.NotNil(t, cloudEvent) var routableAuditEvent auditV1.RoutableAuditEvent assert.NotNil(t, cloudEvent.Data) assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent)) var logEntry auditV1.AuditLogEntry assert.NotNil(t, routableAuditEvent.GetUnencryptedData().Data) assert.NoError(t, proto.Unmarshal(routableAuditEvent.GetUnencryptedData().Data, &logEntry)) assert.NotNil(t, logEntry.ProtoPayload) expectedResponse, _ := structpb.NewStruct(responseBody) assert.Equal(t, fmt.Sprintf("projects/%s", objectId), logEntry.ProtoPayload.ResourceName) assert.Equal(t, expectedResponse, logEntry.ProtoPayload.Response) validator, err := protovalidate.New() assert.NoError(t, err) err = validator.Validate(&logEntry) assert.NoError(t, err) }) t.Run("no entry builder", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). WithAuditLogEntryBuilder(nil).Build(context.Background(), SequenceNumber(1)) assert.EqualError(t, err, "audit log entry builder not set") assert.Nil(t, cloudEvent) assert.Nil(t, routingIdentifier) }) t.Run("next sequence number", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01") assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber()) assert.Equal(t, SequenceNumber(1), builder.NextSequenceNumber()) }) t.Run("revert sequence number", func(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01") assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber()) assert.Equal(t, SequenceNumber(1), builder.NextSequenceNumber()) assert.Equal(t, SequenceNumber(2), builder.NextSequenceNumber()) builder.RevertSequenceNumber(SequenceNumber(1)) assert.Equal(t, SequenceNumber(1), builder.NextSequenceNumber()) assert.Equal(t, SequenceNumber(3), builder.NextSequenceNumber()) }) }