audit-go/pkg/audit/api/builder_test.go
Mathias Koehrer (EXT) 84c49f2690 Merged PR 889470: feat: Add more validation to proto schema
Made the initiator.email optional and added a new validation.
Added a new regex pattern to string fields to prevent them from consisting only of whitespace.

Security-concept-update-needed: false

JIRA Work Item: [STACKITRMA-677](https://jira.schwarz/browse/STACKITRMA-677)
2025-11-27 14:52:27 +00:00

1381 lines
57 KiB
Go

package api
import (
"context"
"fmt"
"testing"
"time"
"buf.build/go/protovalidate"
"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"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgAuditUtils "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/utils"
)
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: pkgAuditCommon.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: pkgAuditCommon.ObjectTypeProject,
},
)
assert.NoError(t, err)
assert.Equal(t, "value", objectId)
assert.Equal(t, pkgAuditCommon.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(pkgAuditCommon.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(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.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(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id").
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(pkgAuditCommon.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("with service account token", func(t *testing.T) {
builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeadersSa,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.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, "my-service-yifc9e1@sa.stackit.cloud", *authenticationInfo.PrincipalEmail)
assert.Equal(t, "10f38b01_534b_47bb_a03a_e294ca2be4de", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Equal(t, "projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/10f38b01-534b-47bb-a03a-e294ca2be4de", *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, "10f38b01_534b_47bb_a03a_e294ca2be4de/stackit%2Fserviceaccount", requestAttributesAuth.Principal)
assert.Equal(t, []string{"stackit", "api"}, 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("system event", func(t *testing.T) {
builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.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.Nil(t, 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(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id").
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(pkgAuditCommon.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(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.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(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.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)
})
t.Run("get api request", func(t *testing.T) {
requestBody := map[string]interface{}{"key": "response"}
requestBodyBytes, err := ResponseBodyToBytes(requestBody)
assert.NoError(t, err)
builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: requestBodyBytes,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id")
// get the request before building the auditlog entry
apiRequest := builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
assert.NotNil(t, logEntry)
// get the request after building the auditlog entry
apiRequest = builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
})
t.Run("get invalid api request", func(t *testing.T) {
requestBodyBytes := []byte("invalid")
builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: requestBodyBytes,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id")
// get the request before building the auditlog entry
apiRequest := builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
assert.EqualError(t, err, "invalid character 'i' looking for beginning of value\ninvalid request body")
assert.Nil(t, logEntry)
// get the request after building the auditlog entry
apiRequest = builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
})
t.Run("modify request body", func(t *testing.T) {
requestBodyBytes := []byte("{\"key\":\"value\"}")
builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: requestBodyBytes,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id")
// get the request before building the auditlog entry
apiRequest := builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
// update the request body
updatedBodyBytes := []byte("{\"key\":\"updated\"}")
apiRequest.Body = updatedBodyBytes
// build the audit log entry
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
assert.NotNil(t, logEntry)
// check the request body from the serialized event
requestBodyJson, err := logEntry.ProtoPayload.Request.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, updatedBodyBytes, requestBodyJson)
// check the request after building the auditlog entry
apiRequest = builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, updatedBodyBytes, apiRequest.Body)
})
}
func Test_AuditEventBuilder(t *testing.T) {
t.Run("nothing set", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.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 := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId("objectId").
WithRequiredObjectType(pkgAuditCommon.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 := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId).
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation).
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredRequestClientIp("127.0.0.1")
routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.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 := pkgAuditUtils.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(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation).
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredRequestClientIp("127.0.0.1").
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(pkgAuditCommon.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 := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.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 := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId).
WithRequiredObjectType(pkgAuditCommon.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, pkgAuditCommon.SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, pkgAuditCommon.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, pkgAuditCommon.SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, pkgAuditCommon.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.Nil(t, 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 := pkgAuditUtils.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, pkgAuditCommon.SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, pkgAuditCommon.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, pkgAuditCommon.SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, pkgAuditCommon.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.Nil(t, 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 := pkgAuditUtils.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(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation).
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredRequestClientIp("127.0.0.1").
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(pkgAuditCommon.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 := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.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("mark as built", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
builder.MarkAsBuilt()
assert.True(t, builder.IsBuilt())
})
t.Run("no entry builder", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.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 := pkgAuditUtils.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 := pkgAuditUtils.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())
})
}