From e9bd1575aadd6698cc24b7a0bca390a7e0c88827 Mon Sep 17 00:00:00 2001 From: Christian Schaible Date: Tue, 1 Oct 2024 14:58:52 +0200 Subject: [PATCH] Support logging of cloud events in legacy format --- audit/api/api_legacy.go | 4 +- audit/api/api_legacy_dynamic.go | 2 +- audit/api/api_legacy_test.go | 2 +- audit/api/log.go | 5 +- audit/api/log_test.go | 105 +++++++++++++++++++++++++------- 5 files changed, 93 insertions(+), 25 deletions(-) diff --git a/audit/api/api_legacy.go b/audit/api/api_legacy.go index e14c614..00ab816 100644 --- a/audit/api/api_legacy.go +++ b/audit/api/api_legacy.go @@ -9,6 +9,8 @@ import ( "google.golang.org/protobuf/proto" ) +const DataTypeLegacyAuditEventV1 = "audit.v1.LegacyAuditEvent" + // LegacyTopicNameResolver implements TopicNameResolver. // A hard-coded topic name is used, routing identifiers are ignored. type LegacyTopicNameResolver struct { @@ -135,7 +137,7 @@ func (a *LegacyAuditApi) ValidateAndSerializeWithTrace( Id: event.InsertId, Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), DataContentType: ContentTypeCloudEventsJson, - DataType: "audit.v1.LegacyAuditEvent", + DataType: DataTypeLegacyAuditEventV1, Subject: event.ProtoPayload.ResourceName, Data: legacyBytes, TraceParent: traceParent, diff --git a/audit/api/api_legacy_dynamic.go b/audit/api/api_legacy_dynamic.go index 9959a70..d687420 100644 --- a/audit/api/api_legacy_dynamic.go +++ b/audit/api/api_legacy_dynamic.go @@ -120,7 +120,7 @@ func (a *DynamicLegacyAuditApi) ValidateAndSerializeWithTrace( Id: event.InsertId, Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), DataContentType: ContentTypeCloudEventsJson, - DataType: "audit.v1.LegacyAuditEvent", + DataType: DataTypeLegacyAuditEventV1, Subject: event.ProtoPayload.ResourceName, Data: legacyBytes, TraceParent: traceParent, diff --git a/audit/api/api_legacy_test.go b/audit/api/api_legacy_test.go index 7b34cf2..dfa4283 100644 --- a/audit/api/api_legacy_test.go +++ b/audit/api/api_legacy_test.go @@ -442,7 +442,7 @@ func validateSentMessageWithDetails( // Check topic name assert.Equal(t, topicName, *message.Properties.To) assert.Equal(t, ContentTypeCloudEventsJson, message.ApplicationProperties["cloudEvents:datacontenttype"]) - assert.Equal(t, "audit.v1.LegacyAuditEvent", message.ApplicationProperties["cloudEvents:type"]) + assert.Equal(t, DataTypeLegacyAuditEventV1, message.ApplicationProperties["cloudEvents:type"]) assert.Equal(t, *traceParent, message.ApplicationProperties["cloudEvents:traceparent"]) assert.Equal(t, *traceState, message.ApplicationProperties["cloudEvents:tracestate"]) diff --git a/audit/api/log.go b/audit/api/log.go index 58c787a..d25fc83 100644 --- a/audit/api/log.go +++ b/audit/api/log.go @@ -13,7 +13,10 @@ import ( // LogEvent logs an event to the terminal func LogEvent(event *CloudEvent) error { - if event.DataType != "audit.v1.RoutableAuditEvent" { + if event.DataType == DataTypeLegacyAuditEventV1 { + slog.Info(string(event.Data)) + return nil + } else if event.DataType != "audit.v1.RoutableAuditEvent" { return errors.New("Unsupported data type " + event.DataType) } diff --git a/audit/api/log_test.go b/audit/api/log_test.go index f75a649..502dd0e 100644 --- a/audit/api/log_test.go +++ b/audit/api/log_test.go @@ -3,6 +3,8 @@ package api import ( "context" "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/audit/utils" + auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/gen/go/audit/v1" + "github.com/bufbuild/protovalidate-go" "github.com/google/uuid" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" @@ -14,25 +16,86 @@ func Test_LogEvent(t *testing.T) { api, _ := NewMockAuditApi() sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() tracer := otel.Tracer("test-tracer") - eventBuilder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", uuid.NewString(), "eu01") - cloudEvent, _, _, err := eventBuilder. - WithRequiredObjectId(uuid.NewString()). - WithRequiredOperation("stackit.demo-service.v1.project.update"). - WithRequiredRequestClientIp("0.0.0.0"). - WithRequiredObjectType(SingularTypeProject). - 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: "GET", - Scheme: "https", - Proto: "HTTP/1.1", - URL: RequestUrl{ - Path: "/", - RawQuery: nil, - }, - }). - Build(context.Background(), eventBuilder.NextSequenceNumber()) - assert.NoError(t, err) - assert.NoError(t, LogEvent(cloudEvent)) + + t.Run("new format", func(t *testing.T) { + eventBuilder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", uuid.NewString(), "eu01") + + cloudEvent, _, _, err := eventBuilder. + 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: "GET", + Scheme: "https", + Proto: "HTTP/1.1", + URL: RequestUrl{ + Path: "/", + RawQuery: nil, + }, + }). + WithRequiredObjectId(uuid.NewString()). + WithRequiredObjectType(SingularTypeProject). + WithRequiredOperation("stackit.demo-service.v1.project.update"). + WithRequiredRequestClientIp("0.0.0.0"). + Build(context.Background(), eventBuilder.NextSequenceNumber()) + + assert.NoError(t, err) + assert.NoError(t, LogEvent(cloudEvent)) + }) + + t.Run("legacy format", func(t *testing.T) { + object_id := uuid.NewString() + entry, err := 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: "GET", + Scheme: "https", + Proto: "HTTP/1.1", + URL: RequestUrl{ + Path: "/", + RawQuery: nil, + }, + }). + WithRequiredLocation("eu01"). + WithRequiredObjectId(object_id). + WithRequiredObjectType(SingularTypeProject). + WithRequiredOperation("stackit.demo-service.v1.project.update"). + WithRequiredRequestClientIp("0.0.0.0"). + WithRequiredServiceName("demo-service"). + WithRequiredWorkerId(uuid.NewString()). + Build(context.Background(), SequenceNumber(1)) + assert.NoError(t, err) + + validator, err := protovalidate.New() + assert.NoError(t, err) + var protoValidator ProtobufValidator = validator + + routableIdentifier := RoutableIdentifier{ + Identifier: object_id, + Type: SingularTypeProject, + } + + routableEvent, err := validateAndSerializePartially(&protoValidator, entry, auditV1.Visibility_VISIBILITY_PUBLIC, &routableIdentifier) + assert.NoError(t, err) + + legacyBytes, err := convertAndSerializeIntoLegacyFormat(entry, routableEvent) + assert.NoError(t, err) + + cloudEvent := CloudEvent{ + SpecVersion: "1.0", + Source: entry.ProtoPayload.ServiceName, + Id: entry.InsertId, + Time: entry.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), + DataContentType: ContentTypeCloudEventsJson, + DataType: DataTypeLegacyAuditEventV1, + Subject: entry.ProtoPayload.ResourceName, + Data: legacyBytes, + TraceParent: nil, + TraceState: nil, + } + + assert.NoError(t, LogEvent(&cloudEvent)) + }) }