audit-go/internal/audit/api/api_common.go
Christian Schaible (EXT) 85aae1c2e7 Merged PR 779949: feat: Refactor module structure to reflect best practices
Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-259
2025-05-19 11:54:00 +00:00

233 lines
7.5 KiB
Go

package api
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalTelemetry "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/telemetry"
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"
)
var TopicNamePattern = regexp.MustCompile(`^topic://stackit-platform/t/swz/audit-log/(?:conway|eu01|eu02|sx-stoi01)/[Vv][1-9](?:\.\d)?/[A-Za-z0-9-]+/[A-Za-z0-9-/]+`)
func ValidateAndSerializePartially(
validator pkgAuditCommon.ProtobufValidator,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*auditV1.RoutableAuditEvent, error) {
// Check preconditions
err := validateAuditLogEntry(validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
// Serialize the AuditLogEntry and wrap it into a RoutableAuditEvent
routableEvent, err := newValidatedRoutableAuditEvent(validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
return routableEvent, nil
}
func newValidatedRoutableAuditEvent(
validator pkgAuditCommon.ProtobufValidator,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier) (*auditV1.RoutableAuditEvent, error) {
// 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
}
payload := auditV1.UnencryptedData{
Data: auditEventBytes,
ProtobufType: fmt.Sprintf("%v", event.ProtoReflect().Descriptor().FullName()),
}
routableEvent := auditV1.RoutableAuditEvent{
OperationName: event.ProtoPayload.OperationName,
ObjectIdentifier: routableIdentifier.ToObjectIdentifier(),
Visibility: visibility,
Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &payload},
}
err = validator.Validate(&routableEvent)
if err != nil {
return nil, err
}
return &routableEvent, nil
}
func validateAuditLogEntry(
validator pkgAuditCommon.ProtobufValidator,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error {
// Return error if the given event or object identifier is nil
if event == nil {
return pkgAuditCommon.ErrEventNil
}
if routableIdentifier == nil {
return pkgAuditCommon.ErrObjectIdentifierNil
}
// Validate the actual event
err := validator.Validate(event)
if err != nil {
return err
}
// Ensure that a valid object identifier is set if the event is public
if isSystemIdentifier(routableIdentifier) && visibility == auditV1.Visibility_VISIBILITY_PUBLIC {
return pkgAuditCommon.ErrObjectIdentifierVisibilityMismatch
}
// Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, pkgAuditCommon.ErrUnknownObjectType) {
return pkgAuditCommon.ErrUnsupportedRoutableType
}
return err
}
// Check identifier consistency across event attributes
if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeSystemEvent)) {
if routableIdentifier.Identifier != pkgAuditCommon.SystemIdentifier.Identifier || routableIdentifier.Type != pkgAuditCommon.ObjectTypeSystem {
return pkgAuditCommon.ErrInvalidRoutableIdentifierForSystemEvent
}
// The resource name can either contain the system identifier or another resource identifier
} else {
if err := areIdentifiersIdentical(routableIdentifier, event.LogName); err != nil {
return err
}
if err := areIdentifiersIdentical(routableIdentifier, event.ProtoPayload.ResourceName); err != nil {
return err
}
}
return nil
}
// Send implements AuditApi.Send
func Send(
topicNameResolver pkgAuditCommon.TopicNameResolver,
messagingApi pkgMessagingApi.Api,
ctx context.Context,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *pkgAuditCommon.CloudEvent,
) error {
// Check that given objects are not nil
if topicNameResolver == nil {
return pkgAuditCommon.ErrTopicNameResolverNil
}
if messagingApi == nil {
return pkgAuditCommon.ErrMessagingApiNil
}
if cloudEvent == nil {
return pkgAuditCommon.ErrCloudEventNil
}
if routableIdentifier == nil {
return pkgAuditCommon.ErrObjectIdentifierNil
}
// Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, pkgAuditCommon.ErrUnknownObjectType) {
return pkgAuditCommon.ErrUnsupportedRoutableType
}
return err
}
topic, err := topicNameResolver.Resolve(routableIdentifier)
if err != nil {
return err
}
// Naming according to AMQP protocol binding spec
// https://github.com/cloudevents/spec/blob/main/cloudevents/bindings/amqp-protocol-binding.md
applicationAttributes := make(map[string]any)
applicationAttributes["cloudEvents:specversion"] = cloudEvent.SpecVersion
applicationAttributes["cloudEvents:source"] = cloudEvent.Source
applicationAttributes["cloudEvents:id"] = cloudEvent.Id
applicationAttributes["cloudEvents:time"] = cloudEvent.Time.UnixMilli()
applicationAttributes["cloudEvents:datacontenttype"] = cloudEvent.DataContentType
applicationAttributes["cloudEvents:type"] = cloudEvent.DataType
applicationAttributes["cloudEvents:subject"] = cloudEvent.Subject
if cloudEvent.TraceParent != nil {
applicationAttributes["cloudEvents:traceparent"] = *cloudEvent.TraceParent
}
if cloudEvent.TraceState != nil {
applicationAttributes["cloudEvents:tracestate"] = *cloudEvent.TraceState
}
// Telemetry
applicationAttributes["cloudEvents:sdklanguage"] = "go"
auditGoVersion := internalTelemetry.AuditGoVersion
if auditGoVersion != "" {
applicationAttributes["cloudEvents:sdkversion"] = auditGoVersion
}
auditGoGrpcVersion := internalTelemetry.AuditGoGrpcVersion
if auditGoGrpcVersion != "" {
applicationAttributes["cloudEvents:sdkgrpcversion"] = auditGoGrpcVersion
}
auditGoHttpVersion := internalTelemetry.AuditGoHttpVersion
if auditGoHttpVersion != "" {
applicationAttributes["cloudEvents:sdkhttpversion"] = auditGoHttpVersion
}
return messagingApi.Send(
ctx,
topic,
cloudEvent.Data,
cloudEvent.DataContentType,
applicationAttributes)
}
func isSystemIdentifier(identifier *pkgAuditCommon.RoutableIdentifier) bool {
if identifier.Identifier == uuid.Nil.String() && identifier.Type == pkgAuditCommon.ObjectTypeSystem {
return true
}
return false
}
func areIdentifiersIdentical(routableIdentifier *pkgAuditCommon.RoutableIdentifier, logName string) error {
dataType, identifier := getTypeAndIdentifierFromString(logName)
objectType := pkgAuditCommon.ObjectTypeFromPluralString(dataType)
err := objectType.IsSupportedType()
if err != nil {
return err
}
return areTypeAndIdentifierIdentical(routableIdentifier, objectType, identifier)
}
func areTypeAndIdentifierIdentical(routableIdentifier *pkgAuditCommon.RoutableIdentifier, dataType pkgAuditCommon.ObjectType, identifier string) error {
if routableIdentifier.Identifier != identifier {
return pkgAuditCommon.ErrAttributeIdentifierInvalid
}
if routableIdentifier.Type != dataType {
return pkgAuditCommon.ErrAttributeTypeInvalid
}
return nil
}
func getTypeAndIdentifierFromString(input string) (string, string) {
parts := strings.Split(input, "/")
dataType := parts[0]
identifier := parts[1]
return dataType, identifier
}