mirror of
https://dev.azure.com/schwarzit/schwarzit.stackit-public/_git/audit-go
synced 2026-02-08 00:57:24 +00:00
Add optional response body serialization to event builder
This commit is contained in:
parent
0caeabedbd
commit
a364d42c7d
2 changed files with 218 additions and 2 deletions
|
|
@ -31,6 +31,8 @@ type AuditParameters struct {
|
|||
// Type of the object, the audit event refers to
|
||||
ObjectType SingularType
|
||||
|
||||
ResponseBody any
|
||||
|
||||
// Log severity
|
||||
Severity auditV1.LogSeverity
|
||||
}
|
||||
|
|
@ -256,7 +258,13 @@ func (builder *AuditLogEntryBuilder) WithStatusCode(statusCode int) *AuditLogEnt
|
|||
return builder
|
||||
}
|
||||
|
||||
// WithResponseBodyBytes adds the response body as bytes (json or protobuf expected)
|
||||
// WithResponseBody adds the response body to the builder and transforms it in the Build method (json serializable or protobuf message expected)
|
||||
func (builder *AuditLogEntryBuilder) WithResponseBody(responseBody any) *AuditLogEntryBuilder {
|
||||
builder.auditParams.ResponseBody = responseBody
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithResponseBodyBytes adds the response body as bytes (serialized json or protobuf message expected)
|
||||
func (builder *AuditLogEntryBuilder) WithResponseBodyBytes(responseBody *[]byte) *AuditLogEntryBuilder {
|
||||
builder.auditResponse.ResponseBodyBytes = responseBody
|
||||
return builder
|
||||
|
|
@ -292,6 +300,16 @@ func (builder *AuditLogEntryBuilder) Build(ctx context.Context, sequenceNumber S
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if builder.auditResponse.ResponseBodyBytes != nil && builder.auditParams.ResponseBody != nil {
|
||||
return nil, errors.New("responseBodyBytes and responseBody set")
|
||||
} else if builder.auditParams.ResponseBody != nil {
|
||||
responseBytes, err := ResponseBodyToBytes(builder.auditParams.ResponseBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builder.auditResponse.ResponseBodyBytes = responseBytes
|
||||
}
|
||||
|
||||
resourceName := fmt.Sprintf("%s/%s", *pluralType, objectId)
|
||||
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)
|
||||
|
|
@ -505,7 +523,13 @@ func (builder *AuditEventBuilder) WithStatusCode(statusCode int) *AuditEventBuil
|
|||
return builder
|
||||
}
|
||||
|
||||
// WithResponseBodyBytes adds the response body as bytes (json or protobuf expected)
|
||||
// WithResponseBody adds the response body to the builder and transforms it in the Build method (json serializable or protobuf message expected)
|
||||
func (builder *AuditEventBuilder) WithResponseBody(responseBody any) *AuditEventBuilder {
|
||||
builder.auditLogEntryBuilder.WithResponseBody(responseBody)
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithResponseBodyBytes adds the response body as bytes (serialized json or protobuf message expected)
|
||||
func (builder *AuditEventBuilder) WithResponseBodyBytes(responseBody *[]byte) *AuditEventBuilder {
|
||||
builder.auditLogEntryBuilder.WithResponseBodyBytes(responseBody)
|
||||
return builder
|
||||
|
|
|
|||
|
|
@ -291,6 +291,123 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
|
|||
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(SingularTypeProject).
|
||||
WithRequiredOperation("stackit.demo-service.v1.operation").
|
||||
WithRequiredApiRequest(ApiRequest{
|
||||
Body: nil,
|
||||
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
|
||||
Host: "localhost",
|
||||
Method: "POST",
|
||||
Scheme: "https",
|
||||
Proto: "HTTP/1.1",
|
||||
URL: RequestUrl{
|
||||
Path: "/",
|
||||
RawQuery: nil,
|
||||
},
|
||||
}).
|
||||
WithRequiredRequestClientIp("127.0.0.1").
|
||||
WithRequiredServiceName("demo-service").
|
||||
WithRequiredWorkerId("worker-id").
|
||||
WithAuditPermission(permission).
|
||||
WithAuditPermissionCheckResult(permissionCheckResult).
|
||||
WithDetails(details).
|
||||
WithEventType(EventTypeSystemEvent).
|
||||
WithLabels(map[string]string{"key": "label"}).
|
||||
WithNumResponseItems(int64(10)).
|
||||
WithRequestCorrelationId("correlationId").
|
||||
WithRequestId("requestId").
|
||||
WithRequestTime(requestTime).
|
||||
WithResponseBody(responseBody).
|
||||
WithResponseHeaders(map[string][]string{"key": {"header"}}).
|
||||
WithResponseTime(responseTime).
|
||||
WithSeverity(auditV1.LogSeverity_LOG_SEVERITY_ERROR).
|
||||
WithStatusCode(400)
|
||||
|
||||
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, logEntry)
|
||||
assert.NotNil(t, logEntry.ProtoPayload)
|
||||
|
||||
expectedResponse, _ := structpb.NewStruct(responseBody)
|
||||
assert.Equal(t, "projects/1", logEntry.ProtoPayload.ResourceName)
|
||||
assert.Equal(t, expectedResponse, logEntry.ProtoPayload.Response)
|
||||
|
||||
validator, err := protovalidate.New()
|
||||
assert.NoError(t, err)
|
||||
err = validator.Validate(logEntry)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("with response body and response body bytes set", func(t *testing.T) {
|
||||
responseBody := map[string]interface{}{"key": "response"}
|
||||
responseBodyBytes, err := ResponseBodyToBytes(responseBody)
|
||||
assert.NoError(t, err)
|
||||
builder := NewAuditLogEntryBuilder().
|
||||
WithRequiredApiRequest(ApiRequest{
|
||||
Body: nil,
|
||||
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
|
||||
Host: "localhost",
|
||||
Method: "POST",
|
||||
Scheme: "https",
|
||||
Proto: "HTTP/1.1",
|
||||
URL: RequestUrl{
|
||||
Path: "/",
|
||||
RawQuery: nil,
|
||||
},
|
||||
}).
|
||||
WithRequiredLocation("eu01").
|
||||
WithRequiredObjectId("1").
|
||||
WithRequiredObjectType(SingularTypeProject).
|
||||
WithRequiredOperation("stackit.demo-service.v1.operation").
|
||||
WithRequiredRequestClientIp("127.0.0.1").
|
||||
WithRequiredServiceName("demo-service").
|
||||
WithRequiredWorkerId("worker-id").
|
||||
WithResponseBody(responseBody).
|
||||
WithResponseBodyBytes(responseBodyBytes)
|
||||
|
||||
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
|
||||
assert.EqualError(t, err, "responseBodyBytes and responseBody set")
|
||||
assert.Nil(t, logEntry)
|
||||
})
|
||||
|
||||
t.Run("with invalid response body", func(t *testing.T) {
|
||||
builder := NewAuditLogEntryBuilder().
|
||||
WithRequiredApiRequest(ApiRequest{
|
||||
Body: nil,
|
||||
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
|
||||
Host: "localhost",
|
||||
Method: "POST",
|
||||
Scheme: "https",
|
||||
Proto: "HTTP/1.1",
|
||||
URL: RequestUrl{
|
||||
Path: "/",
|
||||
RawQuery: nil,
|
||||
},
|
||||
}).
|
||||
WithRequiredLocation("eu01").
|
||||
WithRequiredObjectId("1").
|
||||
WithRequiredObjectType(SingularTypeProject).
|
||||
WithRequiredOperation("stackit.demo-service.v1.operation").
|
||||
WithRequiredRequestClientIp("127.0.0.1").
|
||||
WithRequiredServiceName("demo-service").
|
||||
WithRequiredWorkerId("worker-id").
|
||||
WithResponseBody("invalid")
|
||||
|
||||
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
|
||||
assert.EqualError(t, err, "json: cannot unmarshal string into Go value of type map[string]interface {}\ninvalid response")
|
||||
assert.Nil(t, logEntry)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AuditEventBuilder(t *testing.T) {
|
||||
|
|
@ -579,6 +696,81 @@ func Test_AuditEventBuilder(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("with responsebody unserialized", func(t *testing.T) {
|
||||
api, _ := NewMockAuditApi()
|
||||
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
|
||||
tracer := otel.Tracer("test")
|
||||
|
||||
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, tracer, "demo-service", "worker-id", "eu01").
|
||||
WithRequiredObjectId(objectId).
|
||||
WithRequiredObjectType(SingularTypeProject).
|
||||
WithRequiredOperation(operation).
|
||||
WithRequiredApiRequest(ApiRequest{
|
||||
Body: nil,
|
||||
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
|
||||
Host: "localhost",
|
||||
Method: "POST",
|
||||
Scheme: "https",
|
||||
Proto: "HTTP/1.1",
|
||||
URL: RequestUrl{
|
||||
Path: "/",
|
||||
RawQuery: nil,
|
||||
},
|
||||
}).
|
||||
WithRequiredRequestClientIp("127.0.0.1").
|
||||
WithAuditPermission(permission).
|
||||
WithAuditPermissionCheckResult(permissionCheckResult).
|
||||
WithDetails(details).
|
||||
WithEventType(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).
|
||||
WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE)
|
||||
|
||||
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: SingularTypeProject}
|
||||
|
||||
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)
|
||||
|
||||
var routableAuditEvent auditV1.RoutableAuditEvent
|
||||
assert.NotNil(t, cloudEvent.Data)
|
||||
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
|
||||
|
||||
var logEntry auditV1.AuditLogEntry
|
||||
assert.NotNil(t, routableAuditEvent.GetUnencryptedData().Data)
|
||||
assert.NoError(t, proto.Unmarshal(routableAuditEvent.GetUnencryptedData().Data, &logEntry))
|
||||
assert.NotNil(t, logEntry.ProtoPayload)
|
||||
|
||||
expectedResponse, _ := structpb.NewStruct(responseBody)
|
||||
assert.Equal(t, fmt.Sprintf("projects/%s", objectId), logEntry.ProtoPayload.ResourceName)
|
||||
assert.Equal(t, expectedResponse, logEntry.ProtoPayload.Response)
|
||||
|
||||
validator, err := protovalidate.New()
|
||||
assert.NoError(t, err)
|
||||
err = validator.Validate(&logEntry)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("no entry builder", func(t *testing.T) {
|
||||
api, _ := NewMockAuditApi()
|
||||
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
|
||||
|
|
|
|||
Loading…
Reference in a new issue