package api import ( "context" "errors" "fmt" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" "google.golang.org/protobuf/proto" 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" pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api" ) const DataTypeLegacyAuditEventV1 = "audit.v1.LegacyAuditEvent" // StaticTopicNameConfig provides topic name information required for the topic name resolution. type StaticTopicNameConfig struct { TopicName string } // LegacyAuditApi is an implementation of AuditApi to send events to the legacy audit log system. // // Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented type LegacyAuditApi struct { messagingApi pkgMessagingApi.Api topicNameResolver pkgAuditCommon.TopicNameResolver tracer trace.Tracer validator pkgAuditCommon.ProtobufValidator } // NewLegacyAuditApi can be used to initialize the audit log api. // // Note: The NewLegacyAuditApi method will be deprecated and replaced with "newRoutableAuditApi" once the new audit log routing is implemented func NewLegacyAuditApi( messagingApi pkgMessagingApi.Api, topicNameConfig StaticTopicNameConfig, validator pkgAuditCommon.ProtobufValidator, ) (pkgAuditCommon.AuditApi, error) { if messagingApi == nil { return nil, pkgAuditCommon.ErrMessagingApiNil } // Topic resolver if topicNameConfig.TopicName == "" { return nil, errors.New("topic name is required") } if !pkgAuditCommon.TopicNamePattern.MatchString(topicNameConfig.TopicName) { return nil, fmt.Errorf("invalid topic name: %s - "+ "expected stackit-platform/t/swz/audit-log/{region}/{version}/{eventSource}/{additionalParts} "+ "where region is one of [conway, eu01, eu02, sx-stoi01], version is vX.Y, eventSource is the service name "+ "and additionalParts is a describing string the audit log relates to or 'events'", topicNameConfig.TopicName) } var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: topicNameConfig.TopicName} // Audit api var auditApi pkgAuditCommon.AuditApi = &LegacyAuditApi{ messagingApi: messagingApi, topicNameResolver: topicNameResolver, tracer: otel.Tracer("legacy-audit-api"), validator: validator, } return auditApi, nil } // Log implements AuditApi.Log func (a *LegacyAuditApi) Log( ctx context.Context, event *auditV1.AuditLogEntry, visibility auditV1.Visibility, routableIdentifier *pkgAuditCommon.RoutableIdentifier, ) error { cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier) if err != nil { return err } return a.Send(ctx, routableIdentifier, cloudEvent) } // ValidateAndSerialize implements AuditApi.ValidateAndSerialize. // It serializes the event into the byte representation of the legacy audit log system. func (a *LegacyAuditApi) ValidateAndSerialize( ctx context.Context, event *auditV1.AuditLogEntry, visibility auditV1.Visibility, routableIdentifier *pkgAuditCommon.RoutableIdentifier, ) (*pkgAuditCommon.CloudEvent, error) { ctx, span := a.tracer.Start(ctx, "validate-and-serialize") defer span.End() routableEvent, err := internalAuditApi.ValidateAndSerializePartially(a.validator, event, visibility, routableIdentifier) if err != nil { return nil, err } // Reject event type data-access as the downstream services // cannot handle it at the moment if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) { return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess } // Do nothing with the serialized data in the legacy solution _, err = proto.Marshal(routableEvent) if err != nil { return nil, err } // Convert attributes legacyBytes, err := internalAuditApi.ConvertAndSerializeIntoLegacyFormat(event, routableEvent) if err != nil { return nil, err } traceParent, traceState := internalAuditApi.TraceParentAndStateFromContext(ctx) message := pkgAuditCommon.CloudEvent{ SpecVersion: "1.0", Source: event.ProtoPayload.ServiceName, Id: event.InsertId, Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), DataContentType: pkgAuditCommon.ContentTypeCloudEventsJson, DataType: DataTypeLegacyAuditEventV1, Subject: event.ProtoPayload.ResourceName, Data: legacyBytes, TraceParent: &traceParent, TraceState: &traceState, } return &message, nil } // Send implements AuditApi.Send func (a *LegacyAuditApi) Send( ctx context.Context, routableIdentifier *pkgAuditCommon.RoutableIdentifier, cloudEvent *pkgAuditCommon.CloudEvent, ) error { if cloudEvent != nil && cloudEvent.TraceParent != nil && cloudEvent.TraceState != nil { ctx = internalAuditApi.AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState) } ctx, span := a.tracer.Start(ctx, "send") defer span.End() return internalAuditApi.Send(a.topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent) }