audit-go/audit/api/api_legacy_dynamic_test.go
Christian Schaible 5742604629 Merged PR 716929: feat: Replace AMQP connection management
So far the SDK provided a messaging API that was not thread-safe (i.e. goroutine-safe). Additionally the SDK provided a MutexAPI which made it thread-safe at the cost of removed concurrency possibilities. The changes implemented in this commit replace both implementations with a thread-safe connection pool based solution.

The api gateway is a SDK user that requires reliable high performance send capabilities with a limit amount of amqp connections. These changes in the PR try address their requirements by moving the responsibility of connection management into the SDK. From this change other SDK users will benefit as well.

Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-62
2025-01-27 13:23:54 +00:00

520 lines
17 KiB
Go

package api
import (
"context"
"encoding/json"
"errors"
"go.opentelemetry.io/otel"
"net/url"
"strings"
"testing"
"time"
"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"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestDynamicLegacyAuditApi(t *testing.T) {
// Specify test timeout
ctx, cancelFn := context.WithTimeout(context.Background(), 120*time.Second)
defer cancelFn()
// Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(
messaging.AmqpConnectionPoolConfig{
Parameters: messaging.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
assert.NoError(t, err)
// Validator
validator, err := protovalidate.New()
assert.NoError(t, err)
topicSubscriptionTopicPattern := "audit-log/>"
// Check that event-type data-access is rejected as it is currently
// not supported by downstream services
t.Run("reject data access event", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Create the queue and topic subscription in solace
queueName := "reject-data-access-legacy"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-rejected"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.ErrorIs(t, auditApi.Log(
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
), ErrUnsupportedEventTypeDataAccess)
})
// Check logging of organization events
t.Run("Log public organization event", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Create the queue and topic subscription in solace
queueName := "org-event-public-legacy"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
})
t.Run("Log private organization event", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Create the queue and topic subscription in solace
queueName := "org-event-private-legacy"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
})
// Check logging of folder events
t.Run("Log public folder event", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Create the queue and topic subscription in solace
queueName := "folder-event-public-legacy"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
})
t.Run("Log private folder event", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Create the queue and topic subscription in solace
queueName := "folder-event-private-legacy"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
})
// Check logging of project events
t.Run("Log public project event", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Create the queue and topic subscription in solace
queueName := "project-event-public-legacy"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
})
t.Run("Log private project event", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Create the queue and topic subscription in solace
queueName := "project-event-private-legacy"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
})
// Check logging of system events with identifier
t.Run("Log private project system event", func(t *testing.T) {
defer solaceContainer.StopOnError()
queueName := "project-system-event-private"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := newProjectSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t,
auditApi.Log(
ctx,
event,
visibility,
RoutableSystemIdentifier,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
// Check topic name
assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), auditEvent.EventTimeStamp)
assert.Equal(t, event.ProtoPayload.AuthenticationInfo.PrincipalId, auditEvent.Initiator.Id)
assert.Equal(t, "SYSTEM_EVENT", auditEvent.EventType)
assert.Equal(t, "INFO", auditEvent.Severity)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Path, auditEvent.Request.Endpoint)
assert.Equal(t, event.ProtoPayload.RequestMetadata.CallerIp, auditEvent.SourceIpAddress)
assert.Equal(t, event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent, auditEvent.UserAgent)
})
// Check logging of system events
t.Run("Log private system event", func(t *testing.T) {
defer solaceContainer.StopOnError()
queueName := "system-event-private"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := newSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t,
auditApi.Log(
ctx,
event,
visibility,
RoutableSystemIdentifier,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
// Check topic name
assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), auditEvent.EventTimeStamp)
assert.Equal(t, event.ProtoPayload.AuthenticationInfo.PrincipalId, auditEvent.Initiator.Id)
assert.Equal(t, "SYSTEM_EVENT", auditEvent.EventType)
assert.Equal(t, "INFO", auditEvent.Severity)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Path, auditEvent.Request.Endpoint)
assert.Equal(t, event.ProtoPayload.RequestMetadata.CallerIp, auditEvent.SourceIpAddress)
assert.Equal(t, event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent, auditEvent.UserAgent)
})
t.Run("Log event with details", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Create the queue and topic subscription in solace
queueName := "org-event-with-details-legacy"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
escapedQuery := url.QueryEscape("param=value")
event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessageWithDetails(t, topicName, message, event)
})
}
func TestDynamicLegacyAuditApi_NewLegacyAuditApi_MessagingApiNil(t *testing.T) {
auditApi, err := NewDynamicLegacyAuditApi(nil, nil)
assert.Nil(t, auditApi)
assert.EqualError(t, err, "messaging api nil")
}
func TestDynamicLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
expectedError := errors.New("expected error")
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
event := newSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
func TestDynamicLegacyAuditApi_Log_ValidationFailed(t *testing.T) {
expectedError := errors.New("expected error")
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
event := newSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
func TestDynamicLegacyAuditApi_Log_NilEvent(t *testing.T) {
auditApi := DynamicLegacyAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil)
}
func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) {
customization := func(event *auditV1.AuditLogEntry,
objectIdentifier *auditV1.ObjectIdentifier) {
objectIdentifier.Type = "invalid"
}
event, objectIdentifier := newProjectAuditEvent(&customization)
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(nil)
var protobufValidator ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
}