Add AsSystemEvent method to event builders

This commit is contained in:
Christian Schaible 2024-10-09 08:23:23 +02:00
parent 51cf882c93
commit 63ac2962e9
4 changed files with 298 additions and 7 deletions

View file

@ -104,7 +104,7 @@ func validateAndSerializePartially(
return nil, err
}
// Test serialization even if the data is dropped later while logging to the legacy solution
// Test serialization even if the data is dropped later when logging to the legacy solution
auditEventBytes, err := proto.Marshal(event)
if err != nil {
return nil, err

View file

@ -90,7 +90,7 @@ func NewAuditLogEntryBuilder() *AuditLogEntryBuilder {
EventType: EventTypeAdminActivity,
},
auditRequest: AuditRequest{
Request: nil,
Request: &ApiRequest{},
RequestClientIP: "0.0.0.0",
RequestCorrelationId: nil,
RequestId: nil,
@ -120,6 +120,35 @@ func NewAuditLogEntryBuilder() *AuditLogEntryBuilder {
}
}
func (builder *AuditLogEntryBuilder) AsSystemEvent() *AuditLogEntryBuilder {
if builder.auditRequest.Request == nil {
builder.auditRequest.Request = &ApiRequest{}
}
if builder.auditRequest.Request.Header == nil {
builder.auditRequest.Request.Header = map[string][]string{"user-agent": {"none"}}
}
if builder.auditRequest.Request.Host == "" {
builder.auditRequest.Request.Host = "0.0.0.0"
}
if builder.auditRequest.Request.Method == "" {
builder.auditRequest.Request.Method = "OTHER"
}
if builder.auditRequest.Request.Scheme == "" {
builder.auditRequest.Request.Scheme = "none"
}
if builder.auditRequest.Request.Proto == "" {
builder.auditRequest.Request.Proto = "none"
}
if builder.auditRequest.Request.URL.Path == "" {
builder.auditRequest.Request.URL.Path = "none"
}
if builder.auditRequest.RequestClientIP == "" {
builder.auditRequest.RequestClientIP = "0.0.0.0"
}
builder.WithEventType(EventTypeSystemEvent)
return builder
}
// WithRequiredApiRequest adds api request details
func (builder *AuditLogEntryBuilder) WithRequiredApiRequest(request ApiRequest) *AuditLogEntryBuilder {
builder.auditRequest.Request = &request
@ -398,6 +427,12 @@ func (builder *AuditEventBuilder) RevertSequenceNumber() {
(*builder.sequenceNumberGenerator).Revert()
}
func (builder *AuditEventBuilder) AsSystemEvent() *AuditEventBuilder {
builder.auditLogEntryBuilder.AsSystemEvent()
builder.WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE)
return builder
}
// WithAuditLogEntryBuilder overwrites the preconfigured AuditLogEntryBuilder
func (builder *AuditEventBuilder) WithAuditLogEntryBuilder(auditLogEntryBuilder *AuditLogEntryBuilder) *AuditEventBuilder {
builder.auditLogEntryBuilder = auditLogEntryBuilder

View file

@ -71,6 +71,29 @@ func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
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(SingularTypeProject).
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").
@ -199,7 +222,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(EventTypeSystemEvent).
WithEventType(EventTypePolicyDenied).
WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId").
@ -215,7 +238,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, logEntry)
assert.Equal(t, "projects/1/logs/system-event", logEntry.LogName)
assert.Equal(t, "projects/1/logs/policy-denied", logEntry.LogName)
assert.Equal(t, map[string]string{"key": "label"}, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
@ -292,6 +315,91 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
assert.NoError(t, err)
})
t.Run("system event", func(t *testing.T) {
builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(SingularTypeProject).
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, "projects/1/logs/system-event", logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
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, "do-not-reply@stackit.cloud", 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"
@ -412,6 +520,38 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
func Test_AuditEventBuilder(t *testing.T) {
t.Run("nothing set", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
cloudEvent, routingIdentifier, op, err := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "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)
assert.Equal(t, "", op)
})
t.Run("details missing", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
cloudEvent, routingIdentifier, op, err := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
WithRequiredObjectId("objectId").
WithRequiredObjectType(SingularTypeProject).
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)
assert.Equal(t, "", op)
})
t.Run("required only", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
@ -696,6 +836,121 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err)
})
t.Run("system event", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation"
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: SingularTypeProject}
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId).
WithRequiredObjectType(SingularTypeProject).
WithRequiredOperation(operation).
AsSystemEvent()
cloudEvent, routingIdentifier, op, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
assert.True(t, builder.IsBuilt())
assert.Equal(t, &routableIdentifier, routingIdentifier)
assert.Equal(t, operation, op)
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, "00-00000000000000000000000000000000-0000000000000000-00", *cloudEvent.TraceParent)
assert.Nil(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(), routableAuditEvent.ObjectIdentifier)
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/system-event", objectId), logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
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, "do-not-reply@stackit.cloud", 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("with responsebody unserialized", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()

View file

@ -657,9 +657,10 @@ func AuditAttributesFromAuthorizationHeader(request *ApiRequest) (
error,
) {
var principalId string
var principalEmail string
var auditClaims *structpb.Struct = nil
var principalId = "none"
var principalEmail = "do-not-reply@stackit.cloud"
emptyClaims, _ := structpb.NewStruct(make(map[string]interface{}))
var auditClaims = emptyClaims
var authenticationPrincipal = "none/none"
var serviceAccountName *string = nil
audiences := make([]string, 0)