mirror of
https://dev.azure.com/schwarzit/schwarzit.stackit-public/_git/audit-go
synced 2026-02-08 09:07:26 +00:00
278 lines
9.1 KiB
Go
278 lines
9.1 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/telemetry"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging"
|
|
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.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"
|
|
const ContentTypeCloudEventsJson = "application/cloudevents+json; charset=UTF-8"
|
|
|
|
// ErrAttributeIdentifierInvalid indicates that the object identifier
|
|
// and the identifier in the checked attribute do not match
|
|
var ErrAttributeIdentifierInvalid = errors.New("attribute identifier invalid")
|
|
|
|
// ErrAttributeTypeInvalid indicates that an invalid type has been provided.
|
|
var ErrAttributeTypeInvalid = errors.New("attribute type invalid")
|
|
|
|
// ErrCloudEventNil states that the given cloud event is nil
|
|
var ErrCloudEventNil = errors.New("cloud event nil")
|
|
|
|
// ErrEventNil indicates that the event was nil
|
|
var ErrEventNil = errors.New("event is nil")
|
|
|
|
// ErrInvalidRoutableIdentifierForSystemEvent states that the routable identifier is not valid for a system event
|
|
var ErrInvalidRoutableIdentifierForSystemEvent = errors.New("invalid identifier for system event")
|
|
|
|
// ErrMessagingApiNil states that the messaging api is nil
|
|
var ErrMessagingApiNil = errors.New("messaging api 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")
|
|
|
|
// ErrTopicNameResolverNil states that the topic name resolve is nil
|
|
var ErrTopicNameResolverNil = errors.New("topic name resolver nil")
|
|
|
|
// ErrUnknownObjectType indicates that the given input is an unknown object type
|
|
var ErrUnknownObjectType = errors.New("unknown object type")
|
|
|
|
// ErrUnsupportedEventTypeDataAccess states that the event type "data-access" is currently not supported
|
|
var ErrUnsupportedEventTypeDataAccess = errors.New("unsupported event type data access")
|
|
|
|
// ErrUnsupportedObjectIdentifierType indicates that an unsupported object identifier type has been provided
|
|
var ErrUnsupportedObjectIdentifierType = errors.New("unsupported object identifier type")
|
|
|
|
// ErrUnsupportedRoutableType indicates that the given input is an unsupported routable type
|
|
var ErrUnsupportedRoutableType = errors.New("unsupported routable type")
|
|
|
|
func validateAndSerializePartially(
|
|
validator ProtobufValidator,
|
|
event *auditV1.AuditLogEntry,
|
|
visibility auditV1.Visibility,
|
|
routableIdentifier *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 ProtobufValidator,
|
|
event *auditV1.AuditLogEntry,
|
|
visibility auditV1.Visibility,
|
|
routableIdentifier *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 ProtobufValidator,
|
|
event *auditV1.AuditLogEntry,
|
|
visibility auditV1.Visibility,
|
|
routableIdentifier *RoutableIdentifier,
|
|
) error {
|
|
|
|
// Return error if the given event or object identifier is nil
|
|
if event == nil {
|
|
return ErrEventNil
|
|
}
|
|
if routableIdentifier == nil {
|
|
return 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 ErrObjectIdentifierVisibilityMismatch
|
|
}
|
|
|
|
// Check that provided identifier type is supported
|
|
if err := routableIdentifier.Type.IsSupportedType(); err != nil {
|
|
if errors.Is(err, ErrUnknownObjectType) {
|
|
return ErrUnsupportedRoutableType
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Check identifier consistency across event attributes
|
|
if strings.HasSuffix(event.LogName, string(EventTypeSystemEvent)) {
|
|
if !(routableIdentifier.Identifier == SystemIdentifier.Identifier && routableIdentifier.Type == ObjectTypeSystem) {
|
|
return 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 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, ErrUnknownObjectType) {
|
|
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
|
|
}
|
|
|
|
// Telemetry
|
|
applicationAttributes["cloudEvents:sdklanguage"] = "go"
|
|
auditGoVersion := telemetry.AuditGoVersion
|
|
if auditGoVersion != "" {
|
|
applicationAttributes["cloudEvents:sdkversion"] = auditGoVersion
|
|
}
|
|
auditGoGrpcVersion := telemetry.AuditGoGrpcVersion
|
|
if auditGoGrpcVersion != "" {
|
|
applicationAttributes["cloudEvents:sdkgrpcversion"] = auditGoGrpcVersion
|
|
}
|
|
auditGoHttpVersion := telemetry.AuditGoHttpVersion
|
|
if auditGoHttpVersion != "" {
|
|
applicationAttributes["cloudEvents:sdkhttpversion"] = auditGoHttpVersion
|
|
}
|
|
|
|
return messagingApi.Send(
|
|
ctx,
|
|
topic,
|
|
cloudEvent.Data,
|
|
cloudEvent.DataContentType,
|
|
applicationAttributes)
|
|
}
|
|
|
|
func isSystemIdentifier(identifier *RoutableIdentifier) bool {
|
|
if identifier.Identifier == uuid.Nil.String() && identifier.Type == ObjectTypeSystem {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func areIdentifiersIdentical(routableIdentifier *RoutableIdentifier, logName string) error {
|
|
dataType, identifier := getTypeAndIdentifierFromString(logName)
|
|
objectType := ObjectTypeFromPluralString(dataType)
|
|
err := objectType.IsSupportedType()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return areTypeAndIdentifierIdentical(routableIdentifier, objectType, identifier)
|
|
}
|
|
|
|
func areTypeAndIdentifierIdentical(routableIdentifier *RoutableIdentifier, dataType ObjectType, 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
|
|
}
|