Rename module to dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git

This commit is contained in:
Christian Schaible 2024-07-15 07:51:41 +02:00
parent de5d0a8948
commit 4437c7b510
16 changed files with 352 additions and 115 deletions

View file

@ -1,8 +1,10 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
)
@ -41,14 +43,29 @@ type AuditApi interface {
- protovalidate.ValidationError - if schema validation errors have been detected
- protobuf serialization errors - if the event couldn't be serialized
*/
Log(ctx context.Context, event *auditV1.AuditEvent, visibility auditV1.Visibility, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier) error
Log(
ctx context.Context,
event *auditV1.AuditEvent,
visibility auditV1.Visibility,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
) error
// ValidateAndSerialize validates and serializes the event into a byte representation.
// The result has to be sent explicitly by calling the Send method.
ValidateAndSerialize(event *auditV1.AuditEvent, visibility auditV1.Visibility, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier) (SerializedPayload, error)
ValidateAndSerialize(
event *auditV1.AuditEvent,
visibility auditV1.Visibility,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
) (SerializedPayload, error)
// Send the serialized content as byte array to the audit log system.
Send(ctx context.Context, routingIdentifier *RoutingIdentifier, serializedPayload *SerializedPayload) error
Send(
ctx context.Context,
routingIdentifier *RoutingIdentifier,
serializedPayload *SerializedPayload,
) error
}
// ProtobufValidator is an abstraction for validators.
@ -81,3 +98,11 @@ type RoutingIdentifier struct {
Identifier uuid.UUID
Type RoutingIdentifierType
}
// TopicNameResolver is an abstraction for dynamic topic name resolution
// based on event data or api parameters.
type TopicNameResolver interface {
// Resolve returns a topic name for the given routing identifier
Resolve(routingIdentifier *RoutingIdentifier) (string, error)
}

View file

@ -1,10 +1,13 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
"errors"
"fmt"
"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"
)
@ -45,7 +48,13 @@ var ErrMessagingApiNil = errors.New("messaging api nil")
// ErrSerializedPayloadNil states that the give serialized payload is nil
var ErrSerializedPayloadNil = errors.New("serialized payload nil")
func validateAndSerializePartially(validator *ProtobufValidator, event *auditV1.AuditEvent, visibility auditV1.Visibility, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier) (*auditV1.RoutableAuditEvent, error) {
func validateAndSerializePartially(
validator *ProtobufValidator,
event *auditV1.AuditEvent,
visibility auditV1.Visibility,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
) (*auditV1.RoutableAuditEvent, error) {
// Return error if the given event is nil
if event == nil {
@ -107,9 +116,11 @@ func validateAndSerializePartially(validator *ProtobufValidator, event *auditV1.
// Set oneof protobuf fields after creation of the object
if objectIdentifier == nil {
routableEvent.ResourceReference = &auditV1.RoutableAuditEvent_ObjectName{ObjectName: auditV1.ObjectName_OBJECT_NAME_SYSTEM}
routableEvent.ResourceReference = &auditV1.RoutableAuditEvent_ObjectName{
ObjectName: auditV1.ObjectName_OBJECT_NAME_SYSTEM}
} else {
routableEvent.ResourceReference = &auditV1.RoutableAuditEvent_ObjectIdentifier{ObjectIdentifier: objectIdentifier}
routableEvent.ResourceReference = &auditV1.RoutableAuditEvent_ObjectIdentifier{
ObjectIdentifier: objectIdentifier}
}
err = (*validator).Validate(&routableEvent)
@ -146,7 +157,13 @@ func serializeToProtobufMessage(routableEvent *auditV1.RoutableAuditEvent) (Seri
}
// Send implements AuditApi.Send
func send(topicNameResolver *TopicNameResolver, messagingApi *MessagingApi, ctx context.Context, routingIdentifier *RoutingIdentifier, serializedPayload *SerializedPayload) error {
func send(
topicNameResolver *TopicNameResolver,
messagingApi *messaging.MessagingApi,
ctx context.Context,
routingIdentifier *RoutingIdentifier,
serializedPayload *SerializedPayload,
) error {
if topicNameResolver == nil {
return ErrTopicNameResolverNil

View file

@ -1,18 +1,30 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
"errors"
"fmt"
"testing"
"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"
"github.com/bufbuild/protovalidate-go"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"google.golang.org/protobuf/proto"
"testing"
)
type MessagingApiMock struct {
mock.Mock
}
func (m *MessagingApiMock) Send(ctx context.Context, topic string, data []byte, contentType string) error {
args := m.Called(ctx, topic, data, contentType)
return args.Error(0)
}
type ProtobufValidatorMock struct {
mock.Mock
}
@ -42,7 +54,9 @@ func NewValidator(t *testing.T) ProtobufValidator {
func Test_ValidateAndSerializePartially_EventNil(t *testing.T) {
validator := NewValidator(t)
_, err := validateAndSerializePartially(&validator, nil, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil)
_, err := validateAndSerializePartially(
&validator, nil, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil)
assert.ErrorIs(t, err, ErrEventNil)
}
@ -52,7 +66,9 @@ func Test_ValidateAndSerializePartially_AuditEventValidationFailed(t *testing.T)
event, routingIdentifier, objectIdentifier := NewOrganizationAuditEvent(nil)
event.EventName = ""
_, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier)
_, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier)
assert.EqualError(t, err, "validation error:\n - event_name: value is required [required]")
}
@ -60,8 +76,8 @@ func Test_ValidateAndSerializePartially_RoutableEventValidationFailed(t *testing
validator := NewValidator(t)
event, routingIdentifier, objectIdentifier := NewOrganizationAuditEvent(nil)
_, err := validateAndSerializePartially(&validator, event, 3, routingIdentifier, objectIdentifier)
assert.EqualError(t, err, "validation error:\n - visibility: value must be one of the defined enum values [enum.defined_only]")
}
@ -71,46 +87,62 @@ func Test_ValidateAndSerializePartially_CheckVisibility(t *testing.T) {
event, routingIdentifier, objectIdentifier := NewOrganizationAuditEvent(nil)
t.Run("Visibility public - object identifier nil - routing identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil)
_, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierVisibilityMismatch)
})
t.Run("Visibility public - object identifier nil - routing identifier set", func(t *testing.T) {
_, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, nil)
_, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierVisibilityMismatch)
})
t.Run("Visibility public - object identifier set - routing identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil, objectIdentifier)
_, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil, objectIdentifier)
assert.ErrorIs(t, err, ErrRoutableIdentifierMissing)
})
t.Run("Visibility public - object identifier set - routing identifier set", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier)
routableEvent, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier)
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
t.Run("Visibility private - object identifier nil - routing identifier nil", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil, nil)
routableEvent, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil, nil)
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
t.Run("Visibility private - object identifier nil - routing identifier set", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routingIdentifier, nil)
routableEvent, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routingIdentifier, nil)
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
t.Run("Visibility private - object identifier set - routing identifier nil", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil, objectIdentifier)
routableEvent, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil, objectIdentifier)
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
t.Run("Visibility private - object identifier set - routing identifier set", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routingIdentifier, objectIdentifier)
routableEvent, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routingIdentifier, objectIdentifier)
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
@ -122,7 +154,9 @@ func Test_ValidateAndSerializePartially_IdentifierTypeMismatch(t *testing.T) {
event, routingIdentifier, objectIdentifier := NewFolderAuditEvent(nil)
routingIdentifier.Type = RoutingIdentifierTypeProject
_, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier)
_, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier)
assert.ErrorIs(t, err, ErrRoutableIdentifierTypeMismatch)
}
@ -132,7 +166,9 @@ func Test_ValidateAndSerializePartially_IdentifierMismatch(t *testing.T) {
event, routingIdentifier, objectIdentifier := NewProjectAuditEvent(nil)
routingIdentifier.Identifier = uuid.New()
_, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier)
_, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier)
assert.ErrorIs(t, err, ErrRoutableIdentifierMismatch)
}
@ -141,7 +177,9 @@ func Test_ValidateAndSerializePartially_SystemEvent(t *testing.T) {
event := NewSystemAuditEvent(nil)
routableEvent, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil, nil)
routableEvent, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil, nil)
assert.NoError(t, err)
switch reference := routableEvent.ResourceReference.(type) {
@ -159,7 +197,8 @@ func Test_SerializeToProtobufMessage(t *testing.T) {
event, identifier, objectIdentifier := NewOrganizationAuditEventWithDetails()
// Serialize to routable event
routableEvent, err := validateAndSerializePartially(&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, identifier, objectIdentifier)
routableEvent, err := validateAndSerializePartially(
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, identifier, objectIdentifier)
assert.NoError(t, err)
// Serialize to protobuf message
@ -193,7 +232,7 @@ func Test_Send_TopicNameResolutionError(t *testing.T) {
var serializedPayload SerializedPayload = &routablePayload{}
var messagingApi MessagingApi = &AmqpMessagingApi{}
var messagingApi messaging.MessagingApi = &messaging.AmqpMessagingApi{}
err := send(&topicNameResolver, &messagingApi, context.Background(), nil, &serializedPayload)
assert.ErrorIs(t, err, expectedError)
}
@ -206,7 +245,7 @@ func Test_Send_MessagingApiNil(t *testing.T) {
func Test_Send_SerializedPayloadNil(t *testing.T) {
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"}
var messagingApi MessagingApi = &AmqpMessagingApi{}
var messagingApi messaging.MessagingApi = &messaging.AmqpMessagingApi{}
err := send(&topicNameResolver, &messagingApi, context.Background(), nil, nil)
assert.ErrorIs(t, err, ErrSerializedPayloadNil)
}
@ -220,7 +259,7 @@ func Test_Send(t *testing.T) {
messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi MessagingApi = &messagingApiMock
var messagingApi messaging.MessagingApi = &messagingApiMock
assert.NoError(t, send(&topicNameResolver, &messagingApi, context.Background(), nil, &serializedPayload))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))

View file

@ -1,12 +1,15 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
"encoding/json"
"errors"
"google.golang.org/protobuf/proto"
"time"
"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"
)
// legacyPayload implements SerializedPayload.
@ -44,7 +47,7 @@ type LegacyTopicNameConfig struct {
//
// Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented
type LegacyAuditApi struct {
messagingApi *MessagingApi
messagingApi *messaging.MessagingApi
topicNameResolver *TopicNameResolver
validator *ProtobufValidator
}
@ -52,7 +55,11 @@ type LegacyAuditApi struct {
// NewLegacyAuditApi can be used to initialize the audit log api with LegacyAuditLogConnectionDetails.
//
// Note: The NewLegacyAuditApi method will be deprecated and replaced with "newRoutableAuditApi" once the new audit log routing is implemented
func NewLegacyAuditApi(messagingApi *MessagingApi, topicNameConfig LegacyTopicNameConfig, validator ProtobufValidator) (*AuditApi, error) {
func NewLegacyAuditApi(
messagingApi *messaging.MessagingApi,
topicNameConfig LegacyTopicNameConfig,
validator ProtobufValidator,
) (*AuditApi, error) {
if messagingApi == nil {
return nil, errors.New("messaging api nil")
@ -72,7 +79,13 @@ func NewLegacyAuditApi(messagingApi *MessagingApi, topicNameConfig LegacyTopicNa
}
// Log implements AuditApi.Log
func (a *LegacyAuditApi) Log(ctx context.Context, event *auditV1.AuditEvent, visibility auditV1.Visibility, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier) error {
func (a *LegacyAuditApi) Log(
ctx context.Context,
event *auditV1.AuditEvent,
visibility auditV1.Visibility,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
) error {
serializedPayload, err := a.ValidateAndSerialize(event, visibility, routingIdentifier, objectIdentifier)
if err != nil {
@ -84,7 +97,13 @@ func (a *LegacyAuditApi) Log(ctx context.Context, event *auditV1.AuditEvent, vis
// ValidateAndSerialize implements AuditApi.ValidateAndSerialize.
// It serializes the event into the byte representation of the legacy audit log system.
func (a *LegacyAuditApi) ValidateAndSerialize(event *auditV1.AuditEvent, visibility auditV1.Visibility, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier) (SerializedPayload, error) {
func (a *LegacyAuditApi) ValidateAndSerialize(
event *auditV1.AuditEvent,
visibility auditV1.Visibility,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
) (SerializedPayload, error) {
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routingIdentifier, objectIdentifier)
if err != nil {
return nil, err
@ -106,7 +125,12 @@ func (a *LegacyAuditApi) ValidateAndSerialize(event *auditV1.AuditEvent, visibil
}
// Send implements AuditApi.Send
func (a *LegacyAuditApi) Send(ctx context.Context, routingIdentifier *RoutingIdentifier, serializedPayload *SerializedPayload) error {
func (a *LegacyAuditApi) Send(
ctx context.Context,
routingIdentifier *RoutingIdentifier,
serializedPayload *SerializedPayload,
) error {
return send(a.topicNameResolver, a.messagingApi, ctx, routingIdentifier, serializedPayload)
}

View file

@ -1,18 +1,21 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
"encoding/json"
"errors"
"github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"log/slog"
"os"
"testing"
"time"
"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"
"github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestLegacyAuditApi(t *testing.T) {
@ -23,12 +26,12 @@ func TestLegacyAuditApi(t *testing.T) {
defer cancelFn()
// Start solace docker container
solaceContainer, err := NewSolaceContainer(context.Background())
solaceContainer, err := messaging.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
messagingApi, err := NewAmqpMessagingApi(AmqpConfig{URL: solaceContainer.AmqpConnectionString})
messagingApi, err := messaging.NewAmqpMessagingApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString})
assert.NoError(t, err)
// Validator
@ -408,7 +411,7 @@ func validateSentMessageWithDetails(t *testing.T, topicName string, message *amq
assert.Equal(t, header.Value, (*auditEvent.Request.Headers)[header.Key])
}
for idx, _ := range event.Principals {
for idx := range event.Principals {
assert.Equal(t, event.Principals[idx].Id, auditEvent.ServiceAccountDelegationInfo.Principals[idx].Id)
assert.Equal(t, event.Principals[idx].Email, auditEvent.ServiceAccountDelegationInfo.Principals[idx].Email)
}

View file

@ -1,8 +1,10 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1"
"github.com/bufbuild/protovalidate-go"
)
@ -36,7 +38,13 @@ func (a *MockAuditApi) Log(
}
// ValidateAndSerialize implements AuditApi.ValidateAndSerialize
func (a *MockAuditApi) ValidateAndSerialize(event *auditV1.AuditEvent, visibility auditV1.Visibility, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier) (SerializedPayload, error) {
func (a *MockAuditApi) ValidateAndSerialize(
event *auditV1.AuditEvent,
visibility auditV1.Visibility,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
) (SerializedPayload, error) {
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routingIdentifier, objectIdentifier)
if err != nil {
return nil, err

View file

@ -1,10 +1,12 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
"github.com/stretchr/testify/assert"
"testing"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1"
"github.com/stretchr/testify/assert"
)
func TestMockAuditApi_Log(t *testing.T) {
@ -17,25 +19,31 @@ func TestMockAuditApi_Log(t *testing.T) {
// Test
t.Run("Log", func(t *testing.T) {
assert.Nil(t, (*auditApi).Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier))
assert.Nil(t, (*auditApi).Log(
context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routingIdentifier, objectIdentifier))
})
t.Run("ValidateAndSerialize", func(t *testing.T) {
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
serializedPayload, err := (*auditApi).ValidateAndSerialize(event, visibility, routingIdentifier, objectIdentifier)
serializedPayload, err := (*auditApi).ValidateAndSerialize(
event, visibility, routingIdentifier, objectIdentifier)
assert.NoError(t, err)
validateProtobufMessagePayload(t, serializedPayload.GetPayload(), routingIdentifier, objectIdentifier, event, event.EventName, visibility)
validateProtobufMessagePayload(
t, serializedPayload.GetPayload(), routingIdentifier, objectIdentifier, event, event.EventName, visibility)
})
t.Run("ValidateAndSerialize event nil", func(t *testing.T) {
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
_, err := (*auditApi).ValidateAndSerialize(nil, visibility, routingIdentifier, objectIdentifier)
assert.ErrorIs(t, err, ErrEventNil)
})
t.Run("Send", func(t *testing.T) {
var payload SerializedPayload = &routablePayload{}
assert.Nil(t, (*auditApi).Send(context.Background(), routingIdentifier, &payload))
})
}

View file

@ -1,10 +1,12 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
"errors"
"fmt"
"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"
)
// routablePayload implements SerializedPayload.
@ -34,9 +36,11 @@ type routableTopicNameResolver struct {
// Resolve implements TopicNameResolver.Resolve.
func (r *routableTopicNameResolver) Resolve(routingIdentifier *RoutingIdentifier) (string, error) {
if routingIdentifier == nil {
return r.systemTopicName, nil
}
switch routingIdentifier.Type {
case RoutingIdentifierTypeOrganization:
return fmt.Sprintf("topic://%s/%s", r.organizationTopicPrefix, routingIdentifier.Identifier), nil
@ -58,13 +62,17 @@ type topicNameConfig struct {
// Warning: It is only there for local (compatibility) testing.
// DO NOT USE IT!
type routableAuditApi struct {
messagingApi *MessagingApi
messagingApi *messaging.MessagingApi
topicNameResolver *TopicNameResolver
validator *ProtobufValidator
}
// NewRoutableAuditApi can be used to initialize the audit log api.
func newRoutableAuditApi(messagingApi *MessagingApi, topicNameConfig topicNameConfig, validator ProtobufValidator) (*AuditApi, error) {
func newRoutableAuditApi(
messagingApi *messaging.MessagingApi,
topicNameConfig topicNameConfig,
validator ProtobufValidator,
) (*AuditApi, error) {
if messagingApi == nil {
return nil, errors.New("messaging api nil")
@ -88,7 +96,13 @@ func newRoutableAuditApi(messagingApi *MessagingApi, topicNameConfig topicNameCo
}
// Log implements AuditApi.Log
func (a *routableAuditApi) Log(ctx context.Context, event *auditV1.AuditEvent, visibility auditV1.Visibility, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier) error {
func (a *routableAuditApi) Log(
ctx context.Context,
event *auditV1.AuditEvent,
visibility auditV1.Visibility,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
) error {
serializedPayload, err := a.ValidateAndSerialize(event, visibility, routingIdentifier, objectIdentifier)
if err != nil {
@ -99,8 +113,20 @@ func (a *routableAuditApi) Log(ctx context.Context, event *auditV1.AuditEvent, v
}
// ValidateAndSerialize implements AuditApi.ValidateAndSerialize
func (a *routableAuditApi) ValidateAndSerialize(event *auditV1.AuditEvent, visibility auditV1.Visibility, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier) (SerializedPayload, error) {
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routingIdentifier, objectIdentifier)
func (a *routableAuditApi) ValidateAndSerialize(
event *auditV1.AuditEvent,
visibility auditV1.Visibility,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
) (SerializedPayload, error) {
routableEvent, err := validateAndSerializePartially(
a.validator,
event,
visibility,
routingIdentifier,
objectIdentifier)
if err != nil {
return nil, err
}
@ -109,6 +135,11 @@ func (a *routableAuditApi) ValidateAndSerialize(event *auditV1.AuditEvent, visib
}
// Send implements AuditApi.Send
func (a *routableAuditApi) Send(ctx context.Context, routingIdentifier *RoutingIdentifier, serializedPayload *SerializedPayload) error {
func (a *routableAuditApi) Send(
ctx context.Context,
routingIdentifier *RoutingIdentifier,
serializedPayload *SerializedPayload,
) error {
return send(a.topicNameResolver, a.messagingApi, ctx, routingIdentifier, serializedPayload)
}

View file

@ -1,17 +1,20 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
"errors"
"fmt"
"testing"
"time"
"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"
"github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"google.golang.org/protobuf/proto"
"testing"
"time"
)
func TestRoutableAuditApi(t *testing.T) {
@ -21,12 +24,12 @@ func TestRoutableAuditApi(t *testing.T) {
defer cancelFn()
// Start solace docker container
solaceContainer, err := NewSolaceContainer(context.Background())
solaceContainer, err := messaging.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
messagingApi, err := NewAmqpMessagingApi(AmqpConfig{URL: solaceContainer.AmqpConnectionString})
messagingApi, err := messaging.NewAmqpMessagingApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString})
assert.NoError(t, err)
// Validator
@ -39,7 +42,10 @@ func TestRoutableAuditApi(t *testing.T) {
systemTopicName := "topic://system/admin-events"
auditApi, err := newRoutableAuditApi(
messagingApi,
topicNameConfig{OrganizationTopicPrefix: organizationTopicPrefix, ProjectTopicPrefix: projectTopicPrefix, SystemTopicName: systemTopicName},
topicNameConfig{
OrganizationTopicPrefix: organizationTopicPrefix,
ProjectTopicPrefix: projectTopicPrefix,
SystemTopicName: systemTopicName},
validator,
)
assert.NoError(t, err)
@ -69,7 +75,15 @@ func TestRoutableAuditApi(t *testing.T) {
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "ORGANIZATION_CREATED", visibility)
validateSentEvent(
t,
organizationTopicPrefix,
message,
routingIdentifier,
objectIdentifier,
event,
"ORGANIZATION_CREATED",
visibility)
})
t.Run("Log private organization event", func(t *testing.T) {
@ -81,7 +95,10 @@ func TestRoutableAuditApi(t *testing.T) {
// Instantiate test data
event, routingIdentifier, objectIdentifier := NewOrganizationAuditEvent(nil)
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, fmt.Sprintf("org/%s", routingIdentifier.Identifier)))
topicName := fmt.Sprintf("org/%s", routingIdentifier.Identifier)
assert.NoError(
t,
solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicName))
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -98,7 +115,15 @@ func TestRoutableAuditApi(t *testing.T) {
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "ORGANIZATION_CREATED", visibility)
validateSentEvent(
t,
organizationTopicPrefix,
message,
routingIdentifier,
objectIdentifier,
event,
"ORGANIZATION_CREATED",
visibility)
})
// Check logging of folder events
@ -126,7 +151,15 @@ func TestRoutableAuditApi(t *testing.T) {
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "FOLDER_CREATED", visibility)
validateSentEvent(
t,
organizationTopicPrefix,
message,
routingIdentifier,
objectIdentifier,
event,
"FOLDER_CREATED",
visibility)
})
t.Run("Log private folder event", func(t *testing.T) {
@ -138,7 +171,8 @@ func TestRoutableAuditApi(t *testing.T) {
// Instantiate test data
event, routingIdentifier, objectIdentifier := NewFolderAuditEvent(nil)
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, fmt.Sprintf("org/%s", routingIdentifier.Identifier)))
topicName := fmt.Sprintf("org/%s", routingIdentifier.Identifier)
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicName))
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -155,7 +189,15 @@ func TestRoutableAuditApi(t *testing.T) {
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "FOLDER_CREATED", visibility)
validateSentEvent(
t,
organizationTopicPrefix,
message,
routingIdentifier,
objectIdentifier,
event,
"FOLDER_CREATED",
visibility)
})
// Check logging of project events
@ -184,7 +226,15 @@ func TestRoutableAuditApi(t *testing.T) {
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, projectTopicPrefix, message, routingIdentifier, objectIdentifier, event, "PROJECT_CREATED", visibility)
validateSentEvent(
t,
projectTopicPrefix,
message,
routingIdentifier,
objectIdentifier,
event,
"PROJECT_CREATED",
visibility)
})
t.Run("Log private project event", func(t *testing.T) {
@ -212,7 +262,15 @@ func TestRoutableAuditApi(t *testing.T) {
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, projectTopicPrefix, message, routingIdentifier, objectIdentifier, event, "PROJECT_CREATED", visibility)
validateSentEvent(
t,
projectTopicPrefix,
message,
routingIdentifier,
objectIdentifier,
event,
"PROJECT_CREATED",
visibility)
})
// Check logging of system events
@ -249,7 +307,14 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, proto.Unmarshal(message.Data[0], &protobufMessage))
assert.Equal(t, "audit.v1.RoutableAuditEvent", protobufMessage.ProtobufType)
validateProtobufMessagePayload(t, message.Data[0], nil, nil, event, "SYSTEM_CHANGED", visibility)
validateProtobufMessagePayload(
t,
message.Data[0],
nil,
nil,
event,
"SYSTEM_CHANGED",
visibility)
})
// Check logging of organization events
@ -277,11 +342,28 @@ func TestRoutableAuditApi(t *testing.T) {
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "ORGANIZATION_CREATED", visibility)
validateSentEvent(
t,
organizationTopicPrefix,
message,
routingIdentifier,
objectIdentifier,
event,
"ORGANIZATION_CREATED",
visibility)
})
}
func validateSentEvent(t *testing.T, topicPrefix string, message *amqp.Message, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier, event *auditV1.AuditEvent, eventName string, visibility auditV1.Visibility) {
func validateSentEvent(
t *testing.T,
topicPrefix string,
message *amqp.Message,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
event *auditV1.AuditEvent,
eventName string,
visibility auditV1.Visibility,
) {
// Check topic name
assert.Equal(t,
@ -293,10 +375,19 @@ func validateSentEvent(t *testing.T, topicPrefix string, message *amqp.Message,
assert.NoError(t, proto.Unmarshal(message.Data[0], &protobufMessage))
assert.Equal(t, "audit.v1.RoutableAuditEvent", protobufMessage.ProtobufType)
validateProtobufMessagePayload(t, message.Data[0], routingIdentifier, objectIdentifier, event, eventName, visibility)
validateProtobufMessagePayload(
t, message.Data[0], routingIdentifier, objectIdentifier, event, eventName, visibility)
}
func validateProtobufMessagePayload(t *testing.T, payload []byte, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier, event *auditV1.AuditEvent, eventName string, visibility auditV1.Visibility) {
func validateProtobufMessagePayload(
t *testing.T,
payload []byte,
routingIdentifier *RoutingIdentifier,
objectIdentifier *auditV1.ObjectIdentifier,
event *auditV1.AuditEvent,
eventName string,
visibility auditV1.Visibility,
) {
// Check deserialized message
var protobufMessage auditV1.ProtobufMessage

View file

@ -1,11 +1,13 @@
package main
package api
import (
auditV1 "audit-schema/gen/go/audit/v1"
"time"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1"
"github.com/google/uuid"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"time"
)
func NewOrganizationAuditEvent(

View file

@ -1,4 +1,4 @@
package main
package messaging
// terraform-provider-solacebroker
//
@ -23,12 +23,13 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/hashicorp/go-retryablehttp"
"io"
"log/slog"
"net/http"
"net/http/cookiejar"
"time"
"github.com/hashicorp/go-retryablehttp"
)
var (
@ -172,7 +173,7 @@ func (c *Client) doRequest(request *http.Request) ([]byte, error) {
return rawBody, nil
}
func parseResponseAsObject(ctx context.Context, request *http.Request, dataResponse []byte) (map[string]any, error) {
func parseResponseAsObject(_ context.Context, request *http.Request, dataResponse []byte) (map[string]any, error) {
data := map[string]any{}
err := json.Unmarshal(dataResponse, &data)
if err != nil {

View file

@ -1,4 +1,4 @@
package main
package messaging
import (
"context"
@ -7,19 +7,12 @@ import (
"log/slog"
"strings"
"time"
//"dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/audit/api"
)
// Default connection timeout for the AMQP connection
const connectionTimeoutSeconds = 10
// TopicNameResolver is an abstraction for dynamic topic name resolution
// based on event data or api parameters.
type TopicNameResolver interface {
// Resolve returns a topic name for the given routing identifier
Resolve(routingIdentifier *RoutingIdentifier) (string, error)
}
// MessagingApi is an abstraction for a messaging system that can be used to send
// audit logs to the audit log system.
type MessagingApi interface {

View file

@ -1,4 +1,4 @@
package main
package messaging
import (
"context"
@ -11,15 +11,6 @@ import (
"time"
)
type MessagingApiMock struct {
mock.Mock
}
func (m *MessagingApiMock) Send(ctx context.Context, topic string, data []byte, contentType string) error {
args := m.Called(ctx, topic, data, contentType)
return args.Error(0)
}
type AmqpSessionMock struct {
mock.Mock
}

View file

@ -1,4 +1,4 @@
package main
package messaging
import (
"context"
@ -229,10 +229,12 @@ func (c SolaceContainer) ValidateTopicName(topicSubscriptionTopicPattern string,
}
// Check topic subscription topic pattern
allowedTopicSubscriptionCharacters, err := regexp.Compile("(?:(?:[0-9A-Za-z-.]+|[0-9A-Za-z-.]*\\*)(?:/(?:[0-9A-Za-z-.]+|[0-9A-Za-z-.]*\\*))+|(?:[0-9A-Za-z-.]+|[0-9A-Za-z-.]*\\*)|/>)|>")
allowedTopicSubscriptionCharacters, err := regexp.Compile(
"(?:(?:[0-9A-Za-z-.]+|[0-9A-Za-z-.]*\\*)(?:/(?:[0-9A-Za-z-.]+|[0-9A-Za-z-.]*\\*))+|(?:[0-9A-Za-z-.]+|[0-9A-Za-z-.]*\\*)|/>)|>")
if err != nil {
return err
}
if !allowedTopicSubscriptionCharacters.MatchString(topicSubscriptionTopicPattern) {
return errors.New("invalid topic subscription name")
}

2
go.mod
View file

@ -1,4 +1,4 @@
module audit-schema
module dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git
go 1.22

View file

@ -1,8 +1,10 @@
package main
import (
auditV1 "audit-schema/gen/go/audit/v1"
"fmt"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1"
"github.com/bufbuild/protovalidate-go"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"