audit-go/audit/api/api_common.go
2024-07-31 13:44:52 +02:00

221 lines
7.2 KiB
Go

package api
import (
"context"
"errors"
"fmt"
"github.com/google/uuid"
"strings"
"dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/audit/messaging"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1"
"google.golang.org/protobuf/proto"
)
// ContentTypeCloudEventsProtobuf the cloudevents protobuf content-type sent in metadata of messages
const ContentTypeCloudEventsProtobuf = "application/cloudevents+protobuf"
// ErrUnknownPluralType indicates that the given input is an unknown plural type
var ErrUnknownPluralType = errors.New("unknown plural type")
// ErrUnknownSingularType indicates that the given input is an unknown singular type
var ErrUnknownSingularType = errors.New("unknown singular type")
// ErrUnsupportedRoutableType indicates that the given input is an unsupported routable type
var ErrUnsupportedRoutableType = errors.New("unsupported routable type")
// ErrEventNil indicates that the event was nil
var ErrEventNil = errors.New("event is nil")
// ErrObjectIdentifierNil indicates that the object identifier was nil
var ErrObjectIdentifierNil = errors.New("object identifier is nil")
// ErrObjectIdentifierVisibilityMismatch indicates that a reference mismatch was detected.
//
// Valid combinations are:
// * Visibility: Public, ObjectIdentifier: <type>
// * 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")
// ErrCloudEventNil states that the given cloud event is nil
var ErrCloudEventNil = errors.New("cloud event nil")
func validateAndSerializePartially(
validator *ProtobufValidator,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) (*auditV1.RoutableAuditEvent, error) {
// Return error if the given event or object identifier is nil
if event == nil {
return nil, ErrEventNil
}
if routableIdentifier == nil {
return nil, ErrObjectIdentifierNil
}
// Validate the actual event
err := (*validator).Validate(event)
if err != nil {
return nil, err
}
// Ensure that a valid object identifier is set if the event is public
if isSystemIdentifier(routableIdentifier) && visibility == auditV1.Visibility_VISIBILITY_PUBLIC {
return nil, ErrObjectIdentifierVisibilityMismatch
}
// Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, ErrUnknownSingularType) {
return nil, ErrUnsupportedRoutableType
}
return nil, err
}
// 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
}
// Test serialization even if the data is dropped later while 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{
EventName: event.ProtoPayload.MethodName,
ObjectIdentifier: routableIdentifier.ToObjectIdentifier(),
Visibility: visibility,
Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &payload},
}
err = (*validator).Validate(&routableEvent)
if err != nil {
return nil, err
}
return &routableEvent, nil
}
// Send implements AuditApi.Send
func send(
topicNameResolver *TopicNameResolver,
messagingApi *messaging.Api,
ctx context.Context,
routableIdentifier *RoutableIdentifier,
cloudEvent *CloudEvent,
) error {
// Check that given objects are not nil
if topicNameResolver == nil {
return ErrTopicNameResolverNil
}
if messagingApi == nil {
return ErrMessagingApiNil
}
if cloudEvent == nil {
return ErrCloudEventNil
}
if routableIdentifier == nil {
return ErrObjectIdentifierNil
}
// Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, ErrUnknownSingularType) {
return 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
}
return (*messagingApi).Send(
ctx,
topic,
(*cloudEvent).data,
(*cloudEvent).dataContentType,
applicationAttributes)
}
func isSystemIdentifier(identifier *RoutableIdentifier) bool {
if identifier.Identifier == uuid.Nil.String() && identifier.Type == SingularTypeSystem {
return true
}
return false
}
func areIdentifiersIdentical(routableIdentifier *RoutableIdentifier, logName string) error {
dataType, identifier := getTypeAndIdentifierFromString(logName)
pluralType := AsPluralType(dataType)
singularType, err := pluralType.AsSingularType()
if err != nil {
return err
}
return areTypeAndIdentifierIdentical(routableIdentifier, singularType, identifier)
}
func areTypeAndIdentifierIdentical(routableIdentifier *RoutableIdentifier, dataType SingularType, identifier string) error {
if routableIdentifier.Identifier != identifier {
return ErrAttributeIdentifierInvalid
}
if routableIdentifier.Type != dataType {
return ErrAttributeTypeInvalid
}
return nil
}
func getTypeAndIdentifierFromString(input string) (string, string) {
parts := strings.Split(input, "/")
dataType := parts[0]
identifier := parts[1]
return dataType, identifier
}