mirror of
https://dev.azure.com/schwarzit/schwarzit.stackit-public/_git/audit-go
synced 2026-02-08 00:57:24 +00:00
Introduce additional legacy api to dynamically set the topic name per message via context
This commit is contained in:
parent
e759ae397a
commit
a353d4fa0c
8 changed files with 897 additions and 302 deletions
|
|
@ -201,7 +201,7 @@ type CloudEvent struct {
|
||||||
type TopicNameResolver interface {
|
type TopicNameResolver interface {
|
||||||
|
|
||||||
// Resolve returns a topic name for the given object identifier
|
// Resolve returns a topic name for the given object identifier
|
||||||
Resolve(objectIdentifier *RoutableIdentifier) (string, error)
|
Resolve(routableIdentifier *RoutableIdentifier) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoutableIdentifier struct {
|
type RoutableIdentifier struct {
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,14 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/audit/messaging"
|
"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"
|
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnsupportedSeverity = errors.New("unsupported severity level")
|
|
||||||
|
|
||||||
// LegacyTopicNameResolver implements TopicNameResolver.
|
// LegacyTopicNameResolver implements TopicNameResolver.
|
||||||
// A hard-coded topic name is used, routing identifiers are ignored.
|
// A hard-coded topic name is used, routing identifiers are ignored.
|
||||||
type LegacyTopicNameResolver struct {
|
type LegacyTopicNameResolver struct {
|
||||||
|
|
@ -131,7 +125,7 @@ func (a *LegacyAuditApi) ValidateAndSerializeWithTrace(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert attributes
|
// Convert attributes
|
||||||
legacyBytes, err := a.convertAndSerializeIntoLegacyFormat(event, routableEvent)
|
legacyBytes, err := convertAndSerializeIntoLegacyFormat(event, routableEvent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -160,279 +154,3 @@ func (a *LegacyAuditApi) Send(
|
||||||
|
|
||||||
return send(a.topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
|
return send(a.topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertAndSerializeIntoLegacyFormat converts the protobuf events into the json serialized legacy audit log format
|
|
||||||
func (a *LegacyAuditApi) convertAndSerializeIntoLegacyFormat(
|
|
||||||
event *auditV1.AuditLogEntry,
|
|
||||||
routableEvent *auditV1.RoutableAuditEvent,
|
|
||||||
) ([]byte, error) {
|
|
||||||
|
|
||||||
// Source IP & User agent
|
|
||||||
var sourceIpAddress string
|
|
||||||
var userAgent string
|
|
||||||
if event.ProtoPayload == nil || event.ProtoPayload.RequestMetadata == nil {
|
|
||||||
sourceIpAddress = "0.0.0.0"
|
|
||||||
userAgent = "none"
|
|
||||||
} else {
|
|
||||||
sourceIpAddress = event.ProtoPayload.RequestMetadata.CallerIp
|
|
||||||
userAgent = event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Principals
|
|
||||||
var serviceAccountDelegationInfo *LegacyAuditEventServiceAccountDelegationInfo = nil
|
|
||||||
if len(event.ProtoPayload.AuthenticationInfo.ServiceAccountDelegationInfo) > 0 {
|
|
||||||
var principals []LegacyAuditEventPrincipal
|
|
||||||
for _, principal := range event.ProtoPayload.AuthenticationInfo.ServiceAccountDelegationInfo {
|
|
||||||
switch principalValue := principal.Authority.(type) {
|
|
||||||
case *auditV1.ServiceAccountDelegationInfo_IdpPrincipal_:
|
|
||||||
principals = append(principals, LegacyAuditEventPrincipal{
|
|
||||||
Id: principalValue.IdpPrincipal.PrincipalId,
|
|
||||||
Email: &principalValue.IdpPrincipal.PrincipalEmail,
|
|
||||||
})
|
|
||||||
case *auditV1.ServiceAccountDelegationInfo_SystemPrincipal_:
|
|
||||||
principals = append(principals, LegacyAuditEventPrincipal{
|
|
||||||
Id: "system",
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unsupported principal type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serviceAccountDelegationInfo = &LegacyAuditEventServiceAccountDelegationInfo{Principals: principals}
|
|
||||||
}
|
|
||||||
|
|
||||||
var request LegacyAuditEventRequest
|
|
||||||
if event.ProtoPayload.RequestMetadata.RequestAttributes == nil {
|
|
||||||
request = LegacyAuditEventRequest{
|
|
||||||
Endpoint: "none",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var parameters map[string]interface{} = nil
|
|
||||||
if event.ProtoPayload.RequestMetadata.RequestAttributes.Path != "" &&
|
|
||||||
event.ProtoPayload.RequestMetadata.RequestAttributes.Query != nil &&
|
|
||||||
*event.ProtoPayload.RequestMetadata.RequestAttributes.Query != "" {
|
|
||||||
parameters = map[string]interface{}{}
|
|
||||||
|
|
||||||
parsedUrl, err := url.Parse(fmt.Sprintf("%s?%s",
|
|
||||||
event.ProtoPayload.RequestMetadata.RequestAttributes.Path,
|
|
||||||
*event.ProtoPayload.RequestMetadata.RequestAttributes.Query))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range parsedUrl.Query() {
|
|
||||||
parameters[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body map[string]interface{} = nil
|
|
||||||
if event.ProtoPayload.Request != nil {
|
|
||||||
body = event.ProtoPayload.Request.AsMap()
|
|
||||||
}
|
|
||||||
var headers map[string]interface{} = nil
|
|
||||||
if event.ProtoPayload.RequestMetadata.RequestAttributes.Headers != nil {
|
|
||||||
headers = map[string]interface{}{}
|
|
||||||
for key, value := range event.ProtoPayload.RequestMetadata.RequestAttributes.Headers {
|
|
||||||
headers[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request = LegacyAuditEventRequest{
|
|
||||||
Endpoint: event.ProtoPayload.RequestMetadata.RequestAttributes.Path,
|
|
||||||
Parameters: ¶meters,
|
|
||||||
Body: &body,
|
|
||||||
Headers: &headers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if routableEvent.ObjectIdentifier == nil {
|
|
||||||
return nil, ErrObjectIdentifierNil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context and event type
|
|
||||||
var messageContext *LegacyAuditEventContext
|
|
||||||
var eventType string
|
|
||||||
switch routableEvent.ObjectIdentifier.Type {
|
|
||||||
case string(SingularTypeProject):
|
|
||||||
eventType = "ADMIN_ACTIVITY"
|
|
||||||
messageContext = &LegacyAuditEventContext{
|
|
||||||
OrganizationId: nil,
|
|
||||||
FolderId: nil,
|
|
||||||
ProjectId: &routableEvent.ObjectIdentifier.Identifier,
|
|
||||||
}
|
|
||||||
case string(SingularTypeFolder):
|
|
||||||
eventType = "ADMIN_ACTIVITY"
|
|
||||||
messageContext = &LegacyAuditEventContext{
|
|
||||||
OrganizationId: nil,
|
|
||||||
FolderId: &routableEvent.ObjectIdentifier.Identifier,
|
|
||||||
ProjectId: nil,
|
|
||||||
}
|
|
||||||
case string(SingularTypeOrganization):
|
|
||||||
eventType = "ADMIN_ACTIVITY"
|
|
||||||
messageContext = &LegacyAuditEventContext{
|
|
||||||
OrganizationId: &routableEvent.ObjectIdentifier.Identifier,
|
|
||||||
FolderId: nil,
|
|
||||||
ProjectId: nil,
|
|
||||||
}
|
|
||||||
case string(SingularTypeSystem):
|
|
||||||
eventType = "SYSTEM_EVENT"
|
|
||||||
messageContext = nil
|
|
||||||
default:
|
|
||||||
return nil, ErrUnsupportedObjectIdentifierType
|
|
||||||
}
|
|
||||||
|
|
||||||
var visibility string
|
|
||||||
switch routableEvent.Visibility {
|
|
||||||
case auditV1.Visibility_VISIBILITY_PUBLIC:
|
|
||||||
visibility = "PUBLIC"
|
|
||||||
case auditV1.Visibility_VISIBILITY_PRIVATE:
|
|
||||||
visibility = "PRIVATE"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Details
|
|
||||||
var details = event.ProtoPayload.Request.AsMap()
|
|
||||||
|
|
||||||
// Result
|
|
||||||
var result = event.ProtoPayload.Response.AsMap()
|
|
||||||
|
|
||||||
// Severity
|
|
||||||
var severity string
|
|
||||||
switch event.Severity {
|
|
||||||
case auditV1.LogSeverity_LOG_SEVERITY_DEFAULT:
|
|
||||||
fallthrough
|
|
||||||
case auditV1.LogSeverity_LOG_SEVERITY_DEBUG:
|
|
||||||
fallthrough
|
|
||||||
case auditV1.LogSeverity_LOG_SEVERITY_INFO:
|
|
||||||
fallthrough
|
|
||||||
case auditV1.LogSeverity_LOG_SEVERITY_NOTICE:
|
|
||||||
fallthrough
|
|
||||||
case auditV1.LogSeverity_LOG_SEVERITY_WARNING:
|
|
||||||
severity = "INFO"
|
|
||||||
case auditV1.LogSeverity_LOG_SEVERITY_ERROR:
|
|
||||||
fallthrough
|
|
||||||
case auditV1.LogSeverity_LOG_SEVERITY_CRITICAL:
|
|
||||||
fallthrough
|
|
||||||
case auditV1.LogSeverity_LOG_SEVERITY_ALERT:
|
|
||||||
fallthrough
|
|
||||||
case auditV1.LogSeverity_LOG_SEVERITY_EMERGENCY:
|
|
||||||
severity = "ERROR"
|
|
||||||
default:
|
|
||||||
return nil, ErrUnsupportedSeverity
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate the legacy event - missing values are filled with defaults
|
|
||||||
legacyAuditEvent := LegacyAuditEvent{
|
|
||||||
Severity: severity,
|
|
||||||
Visibility: visibility,
|
|
||||||
EventType: eventType,
|
|
||||||
EventTimeStamp: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
|
|
||||||
EventName: event.ProtoPayload.OperationName,
|
|
||||||
SourceIpAddress: sourceIpAddress,
|
|
||||||
UserAgent: userAgent,
|
|
||||||
Initiator: LegacyAuditEventPrincipal{
|
|
||||||
Id: event.ProtoPayload.AuthenticationInfo.PrincipalId,
|
|
||||||
Email: &event.ProtoPayload.AuthenticationInfo.PrincipalEmail,
|
|
||||||
},
|
|
||||||
ServiceAccountDelegationInfo: serviceAccountDelegationInfo,
|
|
||||||
Request: request,
|
|
||||||
Context: messageContext,
|
|
||||||
ResourceName: &event.ProtoPayload.ResourceName,
|
|
||||||
CorrelationId: event.CorrelationId,
|
|
||||||
Result: &result,
|
|
||||||
Details: &details,
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := json.Marshal(legacyAuditEvent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LegacyAuditEvent has the format as follows:
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"severity": "INFO",
|
|
||||||
"visibility": "PUBLIC",
|
|
||||||
"eventType": "ADMIN_ACTIVITY",
|
|
||||||
"eventTimeStamp": "2019-08-24T14:15:22Z",
|
|
||||||
"eventName": "Create organization",
|
|
||||||
"sourceIpAddress": "127.0.0.1",
|
|
||||||
"userAgent": "CLI",
|
|
||||||
"initiator": {
|
|
||||||
"id": "string",
|
|
||||||
"email": "user@example.com"
|
|
||||||
},
|
|
||||||
"serviceAccountDelegationInfo": {
|
|
||||||
"principals": [
|
|
||||||
{
|
|
||||||
"id": "string",
|
|
||||||
"email": "user@example.com"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"request": {
|
|
||||||
"endpoint": "string",
|
|
||||||
"parameters": {},
|
|
||||||
"body": {},
|
|
||||||
"headers": {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"context": {
|
|
||||||
"organizationId": "string",
|
|
||||||
"folderId": "string",
|
|
||||||
"projectId": "string"
|
|
||||||
},
|
|
||||||
"resourceId": "string",
|
|
||||||
"resourceName": "string",
|
|
||||||
"correlationId": "string",
|
|
||||||
"result": {},
|
|
||||||
"details": {}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
type LegacyAuditEvent struct {
|
|
||||||
Severity string `json:"severity"`
|
|
||||||
Visibility string `json:"visibility"`
|
|
||||||
EventType string `json:"eventType"`
|
|
||||||
EventTimeStamp time.Time `json:"eventTimeStamp"`
|
|
||||||
EventName string `json:"eventName"`
|
|
||||||
SourceIpAddress string `json:"sourceIpAddress"`
|
|
||||||
UserAgent string `json:"userAgent"`
|
|
||||||
Initiator LegacyAuditEventPrincipal `json:"initiator"`
|
|
||||||
ServiceAccountDelegationInfo *LegacyAuditEventServiceAccountDelegationInfo `json:"serviceAccountDelegationInfo"`
|
|
||||||
Request LegacyAuditEventRequest `json:"request"`
|
|
||||||
Context *LegacyAuditEventContext `json:"context"`
|
|
||||||
ResourceId *string `json:"resourceId"`
|
|
||||||
ResourceName *string `json:"resourceName"`
|
|
||||||
CorrelationId *string `json:"correlationId"`
|
|
||||||
Result *map[string]interface{} `json:"result"`
|
|
||||||
Details *map[string]interface{} `json:"details"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LegacyAuditEventPrincipal is a representation for a principal's id (+optional email) information.
|
|
||||||
type LegacyAuditEventPrincipal struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Email *string `json:"email"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LegacyAuditEventServiceAccountDelegationInfo contains information about service account delegation.
|
|
||||||
type LegacyAuditEventServiceAccountDelegationInfo struct {
|
|
||||||
Principals []LegacyAuditEventPrincipal `json:"principals"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LegacyAuditEventRequest contains request information, which mirrors the action of the user and
|
|
||||||
// the resulting changes within the system.
|
|
||||||
type LegacyAuditEventRequest struct {
|
|
||||||
Endpoint string `json:"endpoint"`
|
|
||||||
Parameters *map[string]interface{} `json:"parameters"`
|
|
||||||
Body *map[string]interface{} `json:"body"`
|
|
||||||
Headers *map[string]interface{} `json:"headers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LegacyAuditEventContext contains optional context information.
|
|
||||||
type LegacyAuditEventContext struct {
|
|
||||||
OrganizationId *string `json:"organizationId"`
|
|
||||||
FolderId *string `json:"folderId"`
|
|
||||||
ProjectId *string `json:"projectId"`
|
|
||||||
}
|
|
||||||
|
|
|
||||||
288
audit/api/api_legacy_converter.go
Normal file
288
audit/api/api_legacy_converter.go
Normal file
|
|
@ -0,0 +1,288 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrUnsupportedSeverity = errors.New("unsupported severity level")
|
||||||
|
|
||||||
|
// convertAndSerializeIntoLegacyFormat converts the protobuf events into the json serialized legacy audit log format
|
||||||
|
func convertAndSerializeIntoLegacyFormat(
|
||||||
|
event *auditV1.AuditLogEntry,
|
||||||
|
routableEvent *auditV1.RoutableAuditEvent,
|
||||||
|
) ([]byte, error) {
|
||||||
|
|
||||||
|
// Source IP & User agent
|
||||||
|
var sourceIpAddress string
|
||||||
|
var userAgent string
|
||||||
|
if event.ProtoPayload == nil || event.ProtoPayload.RequestMetadata == nil {
|
||||||
|
sourceIpAddress = "0.0.0.0"
|
||||||
|
userAgent = "none"
|
||||||
|
} else {
|
||||||
|
sourceIpAddress = event.ProtoPayload.RequestMetadata.CallerIp
|
||||||
|
userAgent = event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Principals
|
||||||
|
var serviceAccountDelegationInfo *LegacyAuditEventServiceAccountDelegationInfo = nil
|
||||||
|
if len(event.ProtoPayload.AuthenticationInfo.ServiceAccountDelegationInfo) > 0 {
|
||||||
|
var principals []LegacyAuditEventPrincipal
|
||||||
|
for _, principal := range event.ProtoPayload.AuthenticationInfo.ServiceAccountDelegationInfo {
|
||||||
|
switch principalValue := principal.Authority.(type) {
|
||||||
|
case *auditV1.ServiceAccountDelegationInfo_IdpPrincipal_:
|
||||||
|
principals = append(principals, LegacyAuditEventPrincipal{
|
||||||
|
Id: principalValue.IdpPrincipal.PrincipalId,
|
||||||
|
Email: &principalValue.IdpPrincipal.PrincipalEmail,
|
||||||
|
})
|
||||||
|
case *auditV1.ServiceAccountDelegationInfo_SystemPrincipal_:
|
||||||
|
principals = append(principals, LegacyAuditEventPrincipal{
|
||||||
|
Id: "system",
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported principal type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serviceAccountDelegationInfo = &LegacyAuditEventServiceAccountDelegationInfo{Principals: principals}
|
||||||
|
}
|
||||||
|
|
||||||
|
var request LegacyAuditEventRequest
|
||||||
|
if event.ProtoPayload.RequestMetadata.RequestAttributes == nil {
|
||||||
|
request = LegacyAuditEventRequest{
|
||||||
|
Endpoint: "none",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var parameters map[string]interface{} = nil
|
||||||
|
if event.ProtoPayload.RequestMetadata.RequestAttributes.Path != "" &&
|
||||||
|
event.ProtoPayload.RequestMetadata.RequestAttributes.Query != nil &&
|
||||||
|
*event.ProtoPayload.RequestMetadata.RequestAttributes.Query != "" {
|
||||||
|
parameters = map[string]interface{}{}
|
||||||
|
|
||||||
|
parsedUrl, err := url.Parse(fmt.Sprintf("%s?%s",
|
||||||
|
event.ProtoPayload.RequestMetadata.RequestAttributes.Path,
|
||||||
|
*event.ProtoPayload.RequestMetadata.RequestAttributes.Query))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range parsedUrl.Query() {
|
||||||
|
parameters[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body map[string]interface{} = nil
|
||||||
|
if event.ProtoPayload.Request != nil {
|
||||||
|
body = event.ProtoPayload.Request.AsMap()
|
||||||
|
}
|
||||||
|
var headers map[string]interface{} = nil
|
||||||
|
if event.ProtoPayload.RequestMetadata.RequestAttributes.Headers != nil {
|
||||||
|
headers = map[string]interface{}{}
|
||||||
|
for key, value := range event.ProtoPayload.RequestMetadata.RequestAttributes.Headers {
|
||||||
|
headers[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request = LegacyAuditEventRequest{
|
||||||
|
Endpoint: event.ProtoPayload.RequestMetadata.RequestAttributes.Path,
|
||||||
|
Parameters: ¶meters,
|
||||||
|
Body: &body,
|
||||||
|
Headers: &headers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if routableEvent.ObjectIdentifier == nil {
|
||||||
|
return nil, ErrObjectIdentifierNil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context and event type
|
||||||
|
var messageContext *LegacyAuditEventContext
|
||||||
|
var eventType string
|
||||||
|
switch routableEvent.ObjectIdentifier.Type {
|
||||||
|
case string(SingularTypeProject):
|
||||||
|
eventType = "ADMIN_ACTIVITY"
|
||||||
|
messageContext = &LegacyAuditEventContext{
|
||||||
|
OrganizationId: nil,
|
||||||
|
FolderId: nil,
|
||||||
|
ProjectId: &routableEvent.ObjectIdentifier.Identifier,
|
||||||
|
}
|
||||||
|
case string(SingularTypeFolder):
|
||||||
|
eventType = "ADMIN_ACTIVITY"
|
||||||
|
messageContext = &LegacyAuditEventContext{
|
||||||
|
OrganizationId: nil,
|
||||||
|
FolderId: &routableEvent.ObjectIdentifier.Identifier,
|
||||||
|
ProjectId: nil,
|
||||||
|
}
|
||||||
|
case string(SingularTypeOrganization):
|
||||||
|
eventType = "ADMIN_ACTIVITY"
|
||||||
|
messageContext = &LegacyAuditEventContext{
|
||||||
|
OrganizationId: &routableEvent.ObjectIdentifier.Identifier,
|
||||||
|
FolderId: nil,
|
||||||
|
ProjectId: nil,
|
||||||
|
}
|
||||||
|
case string(SingularTypeSystem):
|
||||||
|
eventType = "SYSTEM_EVENT"
|
||||||
|
messageContext = nil
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedObjectIdentifierType
|
||||||
|
}
|
||||||
|
|
||||||
|
var visibility string
|
||||||
|
switch routableEvent.Visibility {
|
||||||
|
case auditV1.Visibility_VISIBILITY_PUBLIC:
|
||||||
|
visibility = "PUBLIC"
|
||||||
|
case auditV1.Visibility_VISIBILITY_PRIVATE:
|
||||||
|
visibility = "PRIVATE"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Details
|
||||||
|
var details = event.ProtoPayload.Request.AsMap()
|
||||||
|
|
||||||
|
// Result
|
||||||
|
var result = event.ProtoPayload.Response.AsMap()
|
||||||
|
|
||||||
|
// Severity
|
||||||
|
var severity string
|
||||||
|
switch event.Severity {
|
||||||
|
case auditV1.LogSeverity_LOG_SEVERITY_DEFAULT:
|
||||||
|
fallthrough
|
||||||
|
case auditV1.LogSeverity_LOG_SEVERITY_DEBUG:
|
||||||
|
fallthrough
|
||||||
|
case auditV1.LogSeverity_LOG_SEVERITY_INFO:
|
||||||
|
fallthrough
|
||||||
|
case auditV1.LogSeverity_LOG_SEVERITY_NOTICE:
|
||||||
|
fallthrough
|
||||||
|
case auditV1.LogSeverity_LOG_SEVERITY_WARNING:
|
||||||
|
severity = "INFO"
|
||||||
|
case auditV1.LogSeverity_LOG_SEVERITY_ERROR:
|
||||||
|
fallthrough
|
||||||
|
case auditV1.LogSeverity_LOG_SEVERITY_CRITICAL:
|
||||||
|
fallthrough
|
||||||
|
case auditV1.LogSeverity_LOG_SEVERITY_ALERT:
|
||||||
|
fallthrough
|
||||||
|
case auditV1.LogSeverity_LOG_SEVERITY_EMERGENCY:
|
||||||
|
severity = "ERROR"
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedSeverity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate the legacy event - missing values are filled with defaults
|
||||||
|
legacyAuditEvent := LegacyAuditEvent{
|
||||||
|
Severity: severity,
|
||||||
|
Visibility: visibility,
|
||||||
|
EventType: eventType,
|
||||||
|
EventTimeStamp: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
|
||||||
|
EventName: event.ProtoPayload.OperationName,
|
||||||
|
SourceIpAddress: sourceIpAddress,
|
||||||
|
UserAgent: userAgent,
|
||||||
|
Initiator: LegacyAuditEventPrincipal{
|
||||||
|
Id: event.ProtoPayload.AuthenticationInfo.PrincipalId,
|
||||||
|
Email: &event.ProtoPayload.AuthenticationInfo.PrincipalEmail,
|
||||||
|
},
|
||||||
|
ServiceAccountDelegationInfo: serviceAccountDelegationInfo,
|
||||||
|
Request: request,
|
||||||
|
Context: messageContext,
|
||||||
|
ResourceName: &event.ProtoPayload.ResourceName,
|
||||||
|
CorrelationId: event.CorrelationId,
|
||||||
|
Result: &result,
|
||||||
|
Details: &details,
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(legacyAuditEvent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyAuditEvent has the format as follows:
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"severity": "INFO",
|
||||||
|
"visibility": "PUBLIC",
|
||||||
|
"eventType": "ADMIN_ACTIVITY",
|
||||||
|
"eventTimeStamp": "2019-08-24T14:15:22Z",
|
||||||
|
"eventName": "Create organization",
|
||||||
|
"sourceIpAddress": "127.0.0.1",
|
||||||
|
"userAgent": "CLI",
|
||||||
|
"initiator": {
|
||||||
|
"id": "string",
|
||||||
|
"email": "user@example.com"
|
||||||
|
},
|
||||||
|
"serviceAccountDelegationInfo": {
|
||||||
|
"principals": [
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"email": "user@example.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"endpoint": "string",
|
||||||
|
"parameters": {},
|
||||||
|
"body": {},
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"organizationId": "string",
|
||||||
|
"folderId": "string",
|
||||||
|
"projectId": "string"
|
||||||
|
},
|
||||||
|
"resourceId": "string",
|
||||||
|
"resourceName": "string",
|
||||||
|
"correlationId": "string",
|
||||||
|
"result": {},
|
||||||
|
"details": {}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
type LegacyAuditEvent struct {
|
||||||
|
Severity string `json:"severity"`
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
EventType string `json:"eventType"`
|
||||||
|
EventTimeStamp time.Time `json:"eventTimeStamp"`
|
||||||
|
EventName string `json:"eventName"`
|
||||||
|
SourceIpAddress string `json:"sourceIpAddress"`
|
||||||
|
UserAgent string `json:"userAgent"`
|
||||||
|
Initiator LegacyAuditEventPrincipal `json:"initiator"`
|
||||||
|
ServiceAccountDelegationInfo *LegacyAuditEventServiceAccountDelegationInfo `json:"serviceAccountDelegationInfo"`
|
||||||
|
Request LegacyAuditEventRequest `json:"request"`
|
||||||
|
Context *LegacyAuditEventContext `json:"context"`
|
||||||
|
ResourceId *string `json:"resourceId"`
|
||||||
|
ResourceName *string `json:"resourceName"`
|
||||||
|
CorrelationId *string `json:"correlationId"`
|
||||||
|
Result *map[string]interface{} `json:"result"`
|
||||||
|
Details *map[string]interface{} `json:"details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyAuditEventPrincipal is a representation for a principal's id (+optional email) information.
|
||||||
|
type LegacyAuditEventPrincipal struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyAuditEventServiceAccountDelegationInfo contains information about service account delegation.
|
||||||
|
type LegacyAuditEventServiceAccountDelegationInfo struct {
|
||||||
|
Principals []LegacyAuditEventPrincipal `json:"principals"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyAuditEventRequest contains request information, which mirrors the action of the user and
|
||||||
|
// the resulting changes within the system.
|
||||||
|
type LegacyAuditEventRequest struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
Parameters *map[string]interface{} `json:"parameters"`
|
||||||
|
Body *map[string]interface{} `json:"body"`
|
||||||
|
Headers *map[string]interface{} `json:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyAuditEventContext contains optional context information.
|
||||||
|
type LegacyAuditEventContext struct {
|
||||||
|
OrganizationId *string `json:"organizationId"`
|
||||||
|
FolderId *string `json:"folderId"`
|
||||||
|
ProjectId *string `json:"projectId"`
|
||||||
|
}
|
||||||
20
audit/api/api_legacy_converter_test.go
Normal file
20
audit/api/api_legacy_converter_test.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ConvertAndSerializeIntoLegacyFormat_NoObjectIdentifier(t *testing.T) {
|
||||||
|
event, _ := NewProjectAuditEvent(nil)
|
||||||
|
routableEvent := auditV1.RoutableAuditEvent{
|
||||||
|
OperationName: event.ProtoPayload.OperationName,
|
||||||
|
Visibility: auditV1.Visibility_VISIBILITY_PUBLIC,
|
||||||
|
ObjectIdentifier: nil,
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := convertAndSerializeIntoLegacyFormat(event, &routableEvent)
|
||||||
|
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
|
||||||
|
}
|
||||||
152
audit/api/api_legacy_dynamic.go
Normal file
152
audit/api/api_legacy_dynamic.go
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 *messaging.Api
|
||||||
|
validator *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 *messaging.Api,
|
||||||
|
validator ProtobufValidator,
|
||||||
|
) (*AuditApi, error) {
|
||||||
|
|
||||||
|
if messagingApi == nil {
|
||||||
|
return nil, ErrMessagingApiNil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audit api
|
||||||
|
var auditApi AuditApi = &DynamicLegacyAuditApi{
|
||||||
|
messagingApi: messagingApi,
|
||||||
|
validator: &validator,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &auditApi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log implements AuditApi.Log
|
||||||
|
func (a *DynamicLegacyAuditApi) Log(
|
||||||
|
ctx context.Context,
|
||||||
|
event *auditV1.AuditLogEntry,
|
||||||
|
visibility auditV1.Visibility,
|
||||||
|
routableIdentifier *RoutableIdentifier,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
return a.LogWithTrace(ctx, event, visibility, routableIdentifier, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogWithTrace implements AuditApi.LogWithTrace
|
||||||
|
func (a *DynamicLegacyAuditApi) LogWithTrace(
|
||||||
|
ctx context.Context,
|
||||||
|
event *auditV1.AuditLogEntry,
|
||||||
|
visibility auditV1.Visibility,
|
||||||
|
routableIdentifier *RoutableIdentifier,
|
||||||
|
traceParent *string,
|
||||||
|
traceState *string,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
cloudEvent, err := a.ValidateAndSerializeWithTrace(event, visibility, routableIdentifier, traceParent, traceState)
|
||||||
|
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(
|
||||||
|
event *auditV1.AuditLogEntry,
|
||||||
|
visibility auditV1.Visibility,
|
||||||
|
routableIdentifier *RoutableIdentifier,
|
||||||
|
) (*CloudEvent, error) {
|
||||||
|
return a.ValidateAndSerializeWithTrace(event, visibility, routableIdentifier, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAndSerializeWithTrace implements AuditApi.ValidateAndSerializeWithTrace.
|
||||||
|
// It serializes the event into the byte representation of the legacy audit log system.
|
||||||
|
func (a *DynamicLegacyAuditApi) ValidateAndSerializeWithTrace(
|
||||||
|
event *auditV1.AuditLogEntry,
|
||||||
|
visibility auditV1.Visibility,
|
||||||
|
routableIdentifier *RoutableIdentifier,
|
||||||
|
traceParent *string,
|
||||||
|
traceState *string,
|
||||||
|
) (*CloudEvent, error) {
|
||||||
|
|
||||||
|
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do nothing with the serialized data in the legacy solution
|
||||||
|
_, err = proto.Marshal(routableEvent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert attributes
|
||||||
|
legacyBytes, err := convertAndSerializeIntoLegacyFormat(event, routableEvent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
message := CloudEvent{
|
||||||
|
SpecVersion: "1.0",
|
||||||
|
Source: event.ProtoPayload.ServiceName,
|
||||||
|
Id: event.InsertId,
|
||||||
|
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
|
||||||
|
DataContentType: ContentTypeCloudEventsProtobuf,
|
||||||
|
DataType: fmt.Sprintf("%v", routableEvent.ProtoReflect().Descriptor().FullName()),
|
||||||
|
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 *RoutableIdentifier,
|
||||||
|
cloudEvent *CloudEvent,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
rawTopicName := ctx.Value(ContextKeyTopic)
|
||||||
|
if rawTopicName == nil {
|
||||||
|
return ErrNoTopicNameProvided
|
||||||
|
}
|
||||||
|
topicName := fmt.Sprintf("%s", rawTopicName)
|
||||||
|
if len(topicName) == 0 {
|
||||||
|
return ErrTopicNameEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: topicName}
|
||||||
|
|
||||||
|
return send(&topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
|
||||||
|
}
|
||||||
432
audit/api/api_legacy_dynamic_test.go
Normal file
432
audit/api/api_legacy_dynamic_test.go
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"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/bufbuild/protovalidate-go"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDynamicLegacyAuditApi(t *testing.T) {
|
||||||
|
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
||||||
|
|
||||||
|
// 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.AmqpConfig{URL: solaceContainer.AmqpConnectionString})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Validator
|
||||||
|
validator, err := protovalidate.New()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
topicSubscriptionTopicPattern := "audit-log/>"
|
||||||
|
traceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
|
||||||
|
traceState := "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
|
||||||
|
|
||||||
|
// 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).LogWithTrace(
|
||||||
|
ctx,
|
||||||
|
event,
|
||||||
|
visibility,
|
||||||
|
NewRoutableIdentifier(objectIdentifier),
|
||||||
|
&traceParent,
|
||||||
|
&traceState,
|
||||||
|
))
|
||||||
|
|
||||||
|
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
|
||||||
|
})
|
||||||
|
|
||||||
|
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).LogWithTrace(
|
||||||
|
ctx,
|
||||||
|
event,
|
||||||
|
visibility,
|
||||||
|
NewRoutableIdentifier(objectIdentifier),
|
||||||
|
&traceParent,
|
||||||
|
&traceState,
|
||||||
|
))
|
||||||
|
|
||||||
|
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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).LogWithTrace(
|
||||||
|
ctx,
|
||||||
|
event,
|
||||||
|
visibility,
|
||||||
|
NewRoutableIdentifier(objectIdentifier),
|
||||||
|
&traceParent,
|
||||||
|
&traceState,
|
||||||
|
))
|
||||||
|
|
||||||
|
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
|
||||||
|
})
|
||||||
|
|
||||||
|
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).LogWithTrace(
|
||||||
|
ctx,
|
||||||
|
event,
|
||||||
|
visibility,
|
||||||
|
NewRoutableIdentifier(objectIdentifier),
|
||||||
|
&traceParent,
|
||||||
|
&traceState,
|
||||||
|
))
|
||||||
|
|
||||||
|
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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).LogWithTrace(
|
||||||
|
ctx,
|
||||||
|
event,
|
||||||
|
visibility,
|
||||||
|
NewRoutableIdentifier(objectIdentifier),
|
||||||
|
&traceParent,
|
||||||
|
&traceState,
|
||||||
|
))
|
||||||
|
|
||||||
|
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
|
||||||
|
})
|
||||||
|
|
||||||
|
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).LogWithTrace(
|
||||||
|
ctx,
|
||||||
|
event,
|
||||||
|
visibility,
|
||||||
|
NewRoutableIdentifier(objectIdentifier),
|
||||||
|
&traceParent,
|
||||||
|
&traceState,
|
||||||
|
))
|
||||||
|
|
||||||
|
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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).LogWithTrace(
|
||||||
|
ctx,
|
||||||
|
event,
|
||||||
|
visibility,
|
||||||
|
RoutableSystemIdentifier,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
))
|
||||||
|
|
||||||
|
// 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.Nil(t, message.ApplicationProperties["cloudEvents:traceparent"])
|
||||||
|
assert.Nil(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)
|
||||||
|
|
||||||
|
// Log the event to solace
|
||||||
|
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
|
||||||
|
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
|
||||||
|
assert.NoError(t, (*auditApi).LogWithTrace(
|
||||||
|
ctx,
|
||||||
|
event,
|
||||||
|
visibility,
|
||||||
|
NewRoutableIdentifier(objectIdentifier),
|
||||||
|
&traceParent,
|
||||||
|
&traceState,
|
||||||
|
))
|
||||||
|
|
||||||
|
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
validateSentMessageWithDetails(t, topicName, message, event, &traceParent, &traceState)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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{validator: &protobufValidator}
|
||||||
|
|
||||||
|
event := NewSystemAuditEvent(nil)
|
||||||
|
_, err := auditApi.ValidateAndSerialize(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{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{}
|
||||||
|
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{validator: &protobufValidator}
|
||||||
|
_, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
||||||
|
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
|
||||||
|
}
|
||||||
|
|
@ -48,8 +48,6 @@ func TestLegacyAuditApi(t *testing.T) {
|
||||||
t.Run("Log public organization event", func(t *testing.T) {
|
t.Run("Log public organization event", func(t *testing.T) {
|
||||||
defer solaceContainer.StopOnError()
|
defer solaceContainer.StopOnError()
|
||||||
|
|
||||||
slog.Info("test abc")
|
|
||||||
|
|
||||||
// Create the queue and topic subscription in solace
|
// Create the queue and topic subscription in solace
|
||||||
queueName := "org-event-public-legacy"
|
queueName := "org-event-public-legacy"
|
||||||
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
|
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
|
||||||
|
|
@ -548,17 +546,3 @@ func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifi
|
||||||
_, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
_, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
||||||
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
|
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormat_NoObjectIdentifier(t *testing.T) {
|
|
||||||
event, _ := NewProjectAuditEvent(nil)
|
|
||||||
routableEvent := auditV1.RoutableAuditEvent{
|
|
||||||
OperationName: event.ProtoPayload.OperationName,
|
|
||||||
Visibility: auditV1.Visibility_VISIBILITY_PUBLIC,
|
|
||||||
ObjectIdentifier: nil,
|
|
||||||
Data: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
auditApi := LegacyAuditApi{}
|
|
||||||
_, err := auditApi.convertAndSerializeIntoLegacyFormat(event, &routableEvent)
|
|
||||||
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -408,6 +408,7 @@ message AttributeContext {
|
||||||
// "email": "max@mail.schwarz",
|
// "email": "max@mail.schwarz",
|
||||||
// "iss": "https://api.dev.stackit.cloud",
|
// "iss": "https://api.dev.stackit.cloud",
|
||||||
// "jti": "45a196e0-480f-4c34-a592-dc5db81c8c3a"
|
// "jti": "45a196e0-480f-4c34-a592-dc5db81c8c3a"
|
||||||
|
// "sub": "cd94f01a-df2e-4456-902f-48f5e57f0b63"
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Required: true
|
// Required: true
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue