Fix event type mapping in legacy converter and system event handling

This commit is contained in:
Christian Schaible 2024-10-15 11:30:03 +02:00
parent 66773aad3d
commit 03d4ae5d1b
9 changed files with 471 additions and 65 deletions

View file

@ -18,18 +18,25 @@ import (
const ContentTypeCloudEventsProtobuf = "application/cloudevents+protobuf"
const ContentTypeCloudEventsJson = "application/cloudevents+json; charset=UTF-8"
// ErrUnknownPluralType indicates that the given input is an unknown plural type
var ErrUnknownPluralType = errors.New("unknown plural type")
// ErrAttributeIdentifierInvalid indicates that the object identifier
// and the identifier in the checked attribute do not match
var ErrAttributeIdentifierInvalid = errors.New("attribute identifier invalid")
// ErrUnknownSingularType indicates that the given input is an unknown singular type
var ErrUnknownSingularType = errors.New("unknown singular type")
// ErrAttributeTypeInvalid indicates that an invalid type has been provided.
var ErrAttributeTypeInvalid = errors.New("attribute type invalid")
// ErrUnsupportedRoutableType indicates that the given input is an unsupported routable type
var ErrUnsupportedRoutableType = errors.New("unsupported routable type")
// ErrCloudEventNil states that the given cloud event is nil
var ErrCloudEventNil = errors.New("cloud event nil")
// ErrEventNil indicates that the event was nil
var ErrEventNil = errors.New("event is nil")
// ErrInvalidRoutableIdentifierForSystemEvent states that the routable identifier is not valid for a system event
var ErrInvalidRoutableIdentifierForSystemEvent = errors.New("invalid identifier for system event")
// ErrMessagingApiNil states that the messaging api is nil
var ErrMessagingApiNil = errors.New("messaging api nil")
// ErrObjectIdentifierNil indicates that the object identifier was nil
var ErrObjectIdentifierNil = errors.New("object identifier is nil")
@ -40,28 +47,24 @@ var ErrObjectIdentifierNil = errors.New("object identifier is nil")
// * Visibility: Private, ObjectIdentifier: <type | system>
var ErrObjectIdentifierVisibilityMismatch = errors.New("object reference visibility mismatch")
// ErrUnsupportedObjectIdentifierType indicates that an unsupported object identifier type has been provided.
var ErrUnsupportedObjectIdentifierType = errors.New("unsupported object identifier type")
// ErrAttributeTypeInvalid indicates that an invalid type has been provided.
var ErrAttributeTypeInvalid = errors.New("attribute type invalid")
// ErrAttributeIdentifierInvalid indicates that the object identifier
// and the identifier in the checked attribute do not match
var ErrAttributeIdentifierInvalid = errors.New("attribute identifier invalid")
// ErrTopicNameResolverNil states that the topic name resolve is nil
var ErrTopicNameResolverNil = errors.New("topic name resolver nil")
// ErrMessagingApiNil states that the messaging api is nil
var ErrMessagingApiNil = errors.New("messaging api nil")
// ErrUnknownPluralType indicates that the given input is an unknown plural type
var ErrUnknownPluralType = errors.New("unknown plural type")
// ErrCloudEventNil states that the given cloud event is nil
var ErrCloudEventNil = errors.New("cloud event nil")
// ErrUnknownSingularType indicates that the given input is an unknown singular type
var ErrUnknownSingularType = errors.New("unknown singular type")
// ErrUnsupportedEventTypeDataAccess states that the event type "data-access" is currently not supported
var ErrUnsupportedEventTypeDataAccess = errors.New("unsupported event type data access")
// ErrUnsupportedObjectIdentifierType indicates that an unsupported object identifier type has been provided.
var ErrUnsupportedObjectIdentifierType = errors.New("unsupported object identifier type")
// ErrUnsupportedRoutableType indicates that the given input is an unsupported routable type
var ErrUnsupportedRoutableType = errors.New("unsupported routable type")
func validateAndSerializePartially(
validator *ProtobufValidator,
event *auditV1.AuditLogEntry,
@ -97,11 +100,18 @@ func validateAndSerializePartially(
}
// Check identifier consistency across event attributes
if err := areIdentifiersIdentical(routableIdentifier, event.LogName); err != nil {
return nil, err
}
if err := areIdentifiersIdentical(routableIdentifier, event.ProtoPayload.ResourceName); err != nil {
return nil, err
if strings.HasSuffix(event.LogName, string(EventTypeSystemEvent)) {
if !(routableIdentifier.Identifier == SystemIdentifier.Identifier && routableIdentifier.Type == SingularTypeSystem) {
return nil, ErrInvalidRoutableIdentifierForSystemEvent
}
// The resource name can either contain the system identifier or another resource identifier
} else {
if err := areIdentifiersIdentical(routableIdentifier, event.LogName); err != nil {
return nil, err
}
if err := areIdentifiersIdentical(routableIdentifier, event.ProtoPayload.ResourceName); err != nil {
return nil, err
}
}
// Test serialization even if the data is dropped later when logging to the legacy solution

View file

@ -171,7 +171,7 @@ func Test_ValidateAndSerializePartially_CheckVisibility_SystemEvent(t *testing.T
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(SingularTypeOrganization)}))
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
assert.ErrorIs(t, err, ErrInvalidRoutableIdentifierForSystemEvent)
})
t.Run("Visibility private - object identifier system", func(t *testing.T) {
@ -188,7 +188,7 @@ func Test_ValidateAndSerializePartially_CheckVisibility_SystemEvent(t *testing.T
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, NewRoutableIdentifier(
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(SingularTypeOrganization)}))
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
assert.ErrorIs(t, err, ErrInvalidRoutableIdentifierForSystemEvent)
})
}

View file

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/url"
"strings"
"time"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/gen/go/audit/v1"
@ -19,6 +20,20 @@ func convertAndSerializeIntoLegacyFormat(
routableEvent *auditV1.RoutableAuditEvent,
) ([]byte, error) {
// Event type
var eventType string
if strings.HasSuffix(event.LogName, string(EventTypeAdminActivity)) {
eventType = "ADMIN_ACTIVITY"
} else if strings.HasSuffix(event.LogName, string(EventTypeSystemEvent)) {
eventType = "SYSTEM_EVENT"
} else if strings.HasSuffix(event.LogName, string(EventTypePolicyDenied)) {
eventType = "POLICY_DENIED"
} else if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess
} else {
return nil, errors.New("unsupported event type")
}
// Source IP & User agent
var sourceIpAddress string
var userAgent string
@ -106,31 +121,26 @@ func convertAndSerializeIntoLegacyFormat(
// Context and event type
var messageContext *LegacyAuditEventContext
var eventType string
switch routableEvent.ObjectIdentifier.Type {
case string(SingularTypeProject):
eventType = "ADMIN_ACTIVITY"
messageContext = &LegacyAuditEventContext{
OrganizationId: nil,
FolderId: nil,
ProjectId: &routableEvent.ObjectIdentifier.Identifier,
}
case string(SingularTypeFolder):
eventType = "ADMIN_ACTIVITY"
messageContext = &LegacyAuditEventContext{
OrganizationId: nil,
FolderId: &routableEvent.ObjectIdentifier.Identifier,
ProjectId: nil,
}
case string(SingularTypeOrganization):
eventType = "ADMIN_ACTIVITY"
messageContext = &LegacyAuditEventContext{
OrganizationId: &routableEvent.ObjectIdentifier.Identifier,
FolderId: nil,
ProjectId: nil,
}
case string(SingularTypeSystem):
eventType = "SYSTEM_EVENT"
messageContext = nil
default:
return nil, ErrUnsupportedObjectIdentifierType

View file

@ -314,6 +314,64 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
// Check logging of system events with identifier
t.Run("Log private project system event", func(t *testing.T) {
defer solaceContainer.StopOnError()
queueName := "project-system-event-private"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := newProjectSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t,
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
RoutableSystemIdentifier,
nil,
nil,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
// Check topic name
assert.Equal(t, topicName, *message.Properties.To)
assert.Nil(t, message.ApplicationProperties["cloudEvents:traceparent"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), auditEvent.EventTimeStamp)
assert.Equal(t, event.ProtoPayload.AuthenticationInfo.PrincipalId, auditEvent.Initiator.Id)
assert.Equal(t, "SYSTEM_EVENT", auditEvent.EventType)
assert.Equal(t, "INFO", auditEvent.Severity)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Path, auditEvent.Request.Endpoint)
assert.Equal(t, event.ProtoPayload.RequestMetadata.CallerIp, auditEvent.SourceIpAddress)
assert.Equal(t, event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent, auditEvent.UserAgent)
})
// Check logging of system events
t.Run("Log private system event", func(t *testing.T) {
defer solaceContainer.StopOnError()

View file

@ -315,6 +315,64 @@ func TestLegacyAuditApi(t *testing.T) {
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
// Check logging of system events with identifier
t.Run("Log private project system event", func(t *testing.T) {
defer solaceContainer.StopOnError()
queueName := "project-system-event-private"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := newProjectSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
RoutableSystemIdentifier,
nil,
nil,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
// Check topic name
assert.Equal(t, topicName, *message.Properties.To)
assert.Nil(t, message.ApplicationProperties["cloudEvents:traceparent"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), auditEvent.EventTimeStamp)
assert.Equal(t, event.ProtoPayload.AuthenticationInfo.PrincipalId, auditEvent.Initiator.Id)
assert.Equal(t, "SYSTEM_EVENT", auditEvent.EventType)
assert.Equal(t, "INFO", auditEvent.Severity)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Path, auditEvent.Request.Endpoint)
assert.Equal(t, event.ProtoPayload.RequestMetadata.CallerIp, auditEvent.SourceIpAddress)
assert.Equal(t, event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent, auditEvent.UserAgent)
})
// Check logging of system events
t.Run("Log private system event", func(t *testing.T) {
defer solaceContainer.StopOnError()

View file

@ -320,6 +320,58 @@ func TestRoutableAuditApi(t *testing.T) {
&traceState)
})
// Check logging of system events with identifier
t.Run("Log private project system event", func(t *testing.T) {
defer solaceContainer.StopOnError()
queueName := "project-system-event-private"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*"))
// Instantiate test data
event := newProjectSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
RoutableSystemIdentifier,
nil,
nil,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
// Check topic name
assert.Equal(t, systemTopicName, *message.Properties.To)
// Check cloud event properties
applicationProperties := message.ApplicationProperties
assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"])
assert.Equal(t, "resource-manager", applicationProperties["cloudEvents:source"])
_, isUuid := uuid.Parse(fmt.Sprintf("%s", applicationProperties["cloudEvents:id"]))
assert.True(t, true, isUuid)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, "application/cloudevents+protobuf", applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "audit.v1.RoutableAuditEvent", applicationProperties["cloudEvents:type"])
assert.Nil(t, applicationProperties["cloudEvents:traceparent"])
assert.Nil(t, applicationProperties["cloudEvents:tracestate"])
// Check deserialized message
validateRoutableEventPayload(
t,
message.Data[0],
RoutableSystemIdentifier.ToObjectIdentifier(),
event,
"stackit.resourcemanager.v2.system.changed",
visibility)
})
// Check logging of system events
t.Run("Log private system event", func(t *testing.T) {
defer solaceContainer.StopOnError()

View file

@ -7,6 +7,7 @@ import (
"dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/log"
"errors"
"fmt"
"github.com/google/uuid"
"go.opentelemetry.io/otel/trace"
"time"
)
@ -38,13 +39,12 @@ type AuditParameters struct {
}
func getObjectIdAndTypeFromAuditParams(
ctx context.Context,
auditParams *AuditParameters,
) (string, *SingularType, *PluralType, error) {
) (string, *PluralType, error) {
objectId := auditParams.ObjectId
if objectId == "" {
return "", nil, nil, errors.New("object id missing")
return "", nil, errors.New("object id missing")
}
var objectType *SingularType
@ -53,16 +53,16 @@ func getObjectIdAndTypeFromAuditParams(
}
if objectType == nil {
return "", nil, nil, errors.New("singular type missing")
return "", nil, errors.New("singular type missing")
}
// Convert to plural type
plural, err := objectType.AsPluralType()
if err != nil {
log.AuditLogger.Error("failed to convert singular type to plural type", err)
return "", nil, nil, err
return "", nil, err
}
return objectId, objectType, &plural, nil
return objectId, &plural, nil
}
// AuditLogEntryBuilder collects audit params to construct auditV1.AuditLogEntry
@ -324,7 +324,7 @@ func (builder *AuditLogEntryBuilder) Build(ctx context.Context, sequenceNumber S
auditTime := time.Now()
builder.auditMetadata.AuditTime = &auditTime
objectId, _, pluralType, err := getObjectIdAndTypeFromAuditParams(ctx, &builder.auditParams)
objectId, pluralType, err := getObjectIdAndTypeFromAuditParams(&builder.auditParams)
if err != nil {
return nil, err
}
@ -340,8 +340,18 @@ func (builder *AuditLogEntryBuilder) Build(ctx context.Context, sequenceNumber S
}
resourceName := fmt.Sprintf("%s/%s", *pluralType, objectId)
var logIdentifier string
var logType PluralType
if builder.auditParams.EventType == EventTypeSystemEvent {
logIdentifier = SystemIdentifier.Identifier
logType = PluralTypeSystem
} else {
logIdentifier = objectId
logType = *pluralType
}
builder.auditMetadata.AuditInsertId = NewInsertId(time.Now().UTC(), builder.location, builder.workerId, uint64(sequenceNumber))
builder.auditMetadata.AuditLogName = fmt.Sprintf("%s/%s/logs/%s", *pluralType, objectId, builder.auditParams.EventType)
builder.auditMetadata.AuditLogName = fmt.Sprintf("%s/%s/logs/%s", logType, logIdentifier, builder.auditParams.EventType)
builder.auditMetadata.AuditResourceName = resourceName
var details *map[string]interface{} = nil
@ -608,14 +618,29 @@ func (builder *AuditEventBuilder) Build(ctx context.Context, sequenceNumber Sequ
if builder.auditLogEntryBuilder == nil {
return nil, nil, "", fmt.Errorf("audit log entry builder not set")
}
objectId := builder.auditLogEntryBuilder.auditParams.ObjectId
objectType := builder.auditLogEntryBuilder.auditParams.ObjectType
var routingIdentifier *RoutableIdentifier
if builder.auditLogEntryBuilder.auditParams.EventType == EventTypeSystemEvent {
routingIdentifier = NewAuditRoutingIdentifier(uuid.Nil.String(), SingularTypeSystem)
if objectId == "" {
objectId = uuid.Nil.String()
builder.WithRequiredObjectId(objectId)
}
if objectType == "" {
objectType = SingularTypeSystem
builder.WithRequiredObjectType(objectType)
}
} else {
routingIdentifier = NewAuditRoutingIdentifier(objectId, objectType)
}
auditLogEntry, err := builder.auditLogEntryBuilder.Build(ctx, sequenceNumber)
if err != nil {
return nil, nil, "", err
}
objectId := builder.auditLogEntryBuilder.auditParams.ObjectId
objectType := builder.auditLogEntryBuilder.auditParams.ObjectType
ctx, span := builder.tracer.Start(ctx, "create-audit-event")
defer span.End()
@ -624,7 +649,6 @@ func (builder *AuditEventBuilder) Build(ctx context.Context, sequenceNumber Sequ
var traceState *string = nil
visibility := builder.visibility
operation := auditLogEntry.ProtoPayload.OperationName
routingIdentifier := NewAuditRoutingIdentifier(objectId, objectType)
// Validate and serialize the protobuf event into a cloud event
_, validateSerializeSpan := builder.tracer.Start(ctx, "validate-and-serialize-audit-event")

View file

@ -20,50 +20,46 @@ func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
t.Run(
"object id empty", func(t *testing.T) {
objectId, objectType, objectTypePlural, err := getObjectIdAndTypeFromAuditParams(context.Background(), &AuditParameters{})
objectId, objectTypePlural, err := getObjectIdAndTypeFromAuditParams(&AuditParameters{})
assert.EqualError(t, err, "object id missing")
assert.Equal(t, "", objectId)
assert.Nil(t, objectType)
assert.Nil(t, objectTypePlural)
},
)
t.Run(
"object type empty", func(t *testing.T) {
objectId, objectType, objectTypePlural, err := getObjectIdAndTypeFromAuditParams(context.Background(), &AuditParameters{ObjectId: "value"})
objectId, objectTypePlural, err := getObjectIdAndTypeFromAuditParams(&AuditParameters{ObjectId: "value"})
assert.EqualError(t, err, "singular type missing")
assert.Equal(t, "", objectId)
assert.Nil(t, objectType)
assert.Nil(t, objectTypePlural)
},
)
t.Run(
"object id and invalid type set", func(t *testing.T) {
objectId, objectType, objectTypePlural, err := getObjectIdAndTypeFromAuditParams(
context.Background(), &AuditParameters{
objectId, objectTypePlural, err := getObjectIdAndTypeFromAuditParams(
&AuditParameters{
ObjectId: "value",
ObjectType: AsSingularType("invalid"),
},
)
assert.EqualError(t, err, "unknown singular type")
assert.Equal(t, "", objectId)
assert.Nil(t, objectType)
assert.Nil(t, objectTypePlural)
},
)
t.Run(
"object id and type set", func(t *testing.T) {
objectId, objectType, objectTypePlural, err := getObjectIdAndTypeFromAuditParams(
context.Background(), &AuditParameters{
objectId, objectTypePlural, err := getObjectIdAndTypeFromAuditParams(
&AuditParameters{
ObjectId: "value",
ObjectType: SingularTypeProject,
},
)
assert.NoError(t, err)
assert.Equal(t, "value", objectId)
assert.Equal(t, SingularTypeProject, *objectType)
assert.Equal(t, PluralTypeProject, *objectTypePlural)
},
)
@ -329,7 +325,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, fmt.Sprintf("system/%s/logs/system-event", uuid.Nil.String()), logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
@ -601,7 +597,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NotNil(t, cloudEvent.Data)
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
assert.Equal(t, routableIdentifier.ToObjectIdentifier(), routableAuditEvent.ObjectIdentifier)
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)
@ -714,7 +711,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(EventTypeSystemEvent).
WithEventType(EventTypeAdminActivity).
WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId").
@ -751,7 +748,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NotNil(t, cloudEvent.Data)
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
assert.Equal(t, routableIdentifier.ToObjectIdentifier(), routableAuditEvent.ObjectIdentifier)
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)
@ -759,7 +757,7 @@ func Test_AuditEventBuilder(t *testing.T) {
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.Equal(t, fmt.Sprintf("projects/%s/logs/admin-activity", objectId), logEntry.LogName)
assert.Equal(t, map[string]string{"key": "label"}, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
@ -836,14 +834,13 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err)
})
t.Run("system event", func(t *testing.T) {
t.Run("system event with object reference", 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).
@ -854,7 +851,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err)
assert.True(t, builder.IsBuilt())
assert.Equal(t, &routableIdentifier, routingIdentifier)
assert.Equal(t, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
assert.Equal(t, operation, op)
assert.NotNil(t, cloudEvent)
@ -872,7 +870,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NotNil(t, cloudEvent.Data)
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
assert.Equal(t, routableIdentifier.ToObjectIdentifier(), routableAuditEvent.ObjectIdentifier)
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)
@ -880,7 +879,7 @@ func Test_AuditEventBuilder(t *testing.T) {
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.Equal(t, fmt.Sprintf("system/%s/logs/system-event", uuid.Nil.String()), logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
@ -951,6 +950,119 @@ 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")
operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
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, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
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("system/%s", uuid.Nil.String()), 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, 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.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("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 responsebody unserialized", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
@ -984,7 +1096,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(EventTypeSystemEvent).
WithEventType(EventTypeAdminActivity).
WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId").

View file

@ -289,6 +289,88 @@ func newProjectAuditEvent(
return auditEvent, objectIdentifier
}
func newProjectSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {
identifier := uuid.New()
requestId := fmt.Sprintf("%s/1", identifier)
claims, _ := structpb.NewStruct(map[string]interface{}{})
correlationId := "9b5a8e9b-32a0-435f-b97b-a9a42b9e016b"
headers := make(map[string]string)
headers["Content-Type"] = "application/json"
labels := make(map[string]string)
labels["label1"] = "value1"
serviceAccountId := uuid.NewString()
serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId)
delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}}
auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", SystemIdentifier.Type, SystemIdentifier.Identifier, EventTypeSystemEvent),
ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.system.changed",
ResourceName: fmt.Sprintf("%s/%s", PluralTypeProject, identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: serviceAccountId,
PrincipalEmail: "service-account@sa.stackit.cloud",
ServiceAccountName: &serviceAccountName,
ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal},
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", PluralTypeProject, identifier),
Permission: nil,
Granted: nil,
}},
RequestMetadata: &auditV1.RequestMetadata{
CallerIp: "127.0.0.1",
CallerSuppliedUserAgent: "OpenAPI-Generator/ 1.0.0/ go",
RequestAttributes: &auditV1.AttributeContext_Request{
Id: &requestId,
Method: auditV1.AttributeContext_HTTP_METHOD_POST,
Headers: headers,
Path: "/v2/projects",
Host: "stackit-resource-manager-dev.apps.01.cf.eu01.stackit.cloud",
Scheme: "https",
Query: nil,
Time: timestamppb.New(time.Now().UTC()),
Protocol: "http/1.1",
Auth: &auditV1.AttributeContext_Auth{
Principal: "https%3A%2F%2Faccounts.dev.stackit.cloud/stackit-resource-manager-dev",
Audiences: []string{"https:// stackit-resource-manager-dev.apps.01.cf.eu01.stackit.cloud", "stackit", "api"},
Claims: claims,
},
},
},
Request: nil,
ResponseMetadata: &auditV1.ResponseMetadata{
StatusCode: wrapperspb.Int32(200),
ErrorMessage: nil,
ErrorDetails: nil,
ResponseAttributes: &auditV1.AttributeContext_Response{
NumResponseItems: nil,
Size: nil,
Headers: nil,
Time: timestamppb.New(time.Now().UTC()),
},
},
Response: nil,
Metadata: nil,
},
InsertId: fmt.Sprintf("%d/eu01/e72182e8-0bb9-4be2-a19f-87fc0dd6e738/00000000001", time.Now().UnixNano()),
Labels: labels,
CorrelationId: &correlationId,
Timestamp: timestamppb.New(time.Now()),
Severity: auditV1.LogSeverity_LOG_SEVERITY_DEFAULT,
TraceParent: nil,
TraceState: nil,
}
if customization != nil {
(*customization)(auditEvent)
}
return auditEvent
}
func newSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {