mirror of
https://dev.azure.com/schwarzit/schwarzit.stackit-public/_git/audit-go
synced 2026-02-08 00:57:24 +00:00
159 lines
5.3 KiB
Go
159 lines
5.3 KiB
Go
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"
|
|
)
|
|
|
|
type ContextKey string
|
|
|
|
const ContextKeyTopic ContextKey = "topic"
|
|
|
|
var ErrNoTopicNameProvided = errors.New("no topic name provided")
|
|
var ErrTopicNameEmpty = errors.New("empty topic name provided")
|
|
|
|
// DynamicLegacyAuditApi is an implementation of AuditApi to send events to the legacy audit log system
|
|
// by setting the topic name explicitly in the context with the key "topic".
|
|
//
|
|
// Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented
|
|
type DynamicLegacyAuditApi struct {
|
|
messagingApi pkgMessagingApi.Api
|
|
tracer trace.Tracer
|
|
validator pkgAuditCommon.ProtobufValidator
|
|
}
|
|
|
|
// NewDynamicLegacyAuditApi 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 NewDynamicLegacyAuditApi(
|
|
messagingApi pkgMessagingApi.Api,
|
|
validator pkgAuditCommon.ProtobufValidator,
|
|
) (pkgAuditCommon.AuditApi, error) {
|
|
|
|
if messagingApi == nil {
|
|
return nil, pkgAuditCommon.ErrMessagingApiNil
|
|
}
|
|
|
|
// Audit api
|
|
var auditApi pkgAuditCommon.AuditApi = &DynamicLegacyAuditApi{
|
|
messagingApi: messagingApi,
|
|
tracer: otel.Tracer("dynamic-legacy-audit-api"),
|
|
validator: validator,
|
|
}
|
|
|
|
return auditApi, nil
|
|
}
|
|
|
|
// Log implements AuditApi.Log
|
|
func (a *DynamicLegacyAuditApi) 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 *DynamicLegacyAuditApi) 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
|
|
//
|
|
// Requires to have the topic name set as key "topic" in the context.
|
|
func (a *DynamicLegacyAuditApi) Send(
|
|
ctx context.Context,
|
|
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
|
|
cloudEvent *pkgAuditCommon.CloudEvent,
|
|
) error {
|
|
|
|
rawTopicName := ctx.Value(ContextKeyTopic)
|
|
if rawTopicName == nil {
|
|
return ErrNoTopicNameProvided
|
|
}
|
|
topicName := fmt.Sprintf("%s", rawTopicName)
|
|
if topicName == "" {
|
|
return ErrTopicNameEmpty
|
|
}
|
|
if !pkgAuditCommon.TopicNamePattern.MatchString(topicName) {
|
|
return 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'", topicName)
|
|
}
|
|
|
|
var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: topicName}
|
|
|
|
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(topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
|
|
}
|