Compare commits

...

20 commits
v0.1.7 ... main

Author SHA1 Message Date
Christian Schaible (EXT)
80326d88a6 Merged PR 908570: feat: Update dependencies and add sonarqube configuration
- Update dependencies
- Update related code adjustments
- Add sonarqube configuration and pipeline adjustments

Security-concept-update-needed: false.

JIRA Work Item: [STACKITRMA-822](https://jira.schwarz/browse/STACKITRMA-822)
2026-01-13 09:00:27 +00:00
Christian Schaible (EXT)
b10f6797ff Merged PR 890629: feat: Update dependencies
Security-concept-update-needed: false.

JIRA Work Item: [STACKITRMA-697](https://jira.schwarz/browse/STACKITRMA-697)
2025-12-02 08:30:55 +00:00
Mathias Koehrer (EXT)
84c49f2690 Merged PR 889470: feat: Add more validation to proto schema
Made the initiator.email optional and added a new validation.
Added a new regex pattern to string fields to prevent them from consisting only of whitespace.

Security-concept-update-needed: false

JIRA Work Item: [STACKITRMA-677](https://jira.schwarz/browse/STACKITRMA-677)
2025-11-27 14:52:27 +00:00
Christian Schaible (EXT)
4c7c36c8f1 Merged PR 880350: feat: Add auditlog deprecation notice
Security-concept-update-needed: false.

JIRA Work Item: [STACKITRMA-647](https://jira.schwarz/browse/STACKITRMA-647)
2025-11-27 14:17:40 +00:00
Patrick Schmitz (EXT)
a706af62a4 Merged PR 861752: feat: Adjust azure pr template to fit with the current jira board setup
Adjust azure pr template to fit with the current jira board setup

Security-concept-update-needed: false.

JIRA Work Item: [STACKITRMA-553](https://jira.schwarz/browse/STACKITRMA-553)
2025-10-14 13:24:13 +00:00
Christian Schaible (EXT)
f2715624e9 Merged PR 843653: feat: Allow underscores in subject-claims and update dependencies
The subject claim may contain underscore characters which are not allowed by the schema yet. Therefore, an adjustment of the regex in the schema has been made to allow it.

Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-437
2025-09-11 07:51:34 +00:00
Christian Schaible (EXT)
40eacfe4ad Merged PR 804853: fix: Update dependencies
Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-284
2025-06-30 09:15:01 +00:00
Christian Schaible (EXT)
cdea0ac81a Merged PR 801769: feat: Make the request body accessible and modifiable in the audit event builder
Makes the request body accessible and modifiable in the audit event builder to enable SDK users to hide secrets in request bodies captured in the request body (by middlewares). Also updates dependencies.

Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-284
2025-06-30 06:34:51 +00:00
Christian Schaible (EXT)
85aae1c2e7 Merged PR 779949: feat: Refactor module structure to reflect best practices
Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-259
2025-05-19 11:54:00 +00:00
Christian Schaible (EXT)
56b04b94cb Merged PR 770819: feat: Allow the term "conway" as environment name in topic names and update dependencies
Allows the term "conway" as environment name in topic names.
Updates dependencies.

github.com/bufbuild/protovalidate-go@v0.9.3 requires go >= 1.23.4

Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-249
2025-04-23 08:59:20 +00:00
Muhammad Umar Shabbir
6af0e83a95 Merged PR 758953: feat: update vmImage to ubuntu-24.04
JIRA Work Item: [STACKITAGA-340](https://jira.schwarz/browse/STACKITAGA-340)
2025-04-01 08:39:17 +00:00
Christian Schaible (EXT)
6b5bc6dfe2 Merged PR 757276: feat: Validate topic names and update dependencies
Validates solace topic names to prevent issues when processing messages in the auditlog-appender.
Updates dependencies and build tools.

Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-220
2025-03-31 12:32:16 +00:00
Christian Schaible (EXT)
e8567c19ff Merged PR 756491: feat: Download images from STACKIT container registry
Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-211
2025-03-26 09:16:19 +00:00
Christian Schaible (EXT)
618be58a26 Merged PR 752362: feat: Apply stricter linter rules
Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-184
2025-03-25 08:40:27 +00:00
Patrick Schmitz (EXT)
68cec628e0 Merged PR 749395: chore: Update dependencies and go version to 1.23.0
Updates library versions to fix a reported vulnerability in a test dependency and to use latest versions.

Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-152
2025-03-17 14:27:01 +00:00
Christian Schaible
3eb803ae1c Merged PR 724980: fix: Set default connection pool size to 2
Updating the default connection pool size after clarifying with the Integration platform team.

Security-concept-update-needed: false.

JIRA Work Item: STACKITRMA-68
2025-01-29 15:07:27 +00:00
Christian Schaible
ddee3db2fe Merged PR 724873: feat: Make connection pool size optional
Security-concept-update-needed: false.

JIRA Work Item: STACKITRMA-68
2025-01-29 14:31:09 +00:00
Christian Schaible
720a1a6d72 Merged PR 723917: fix: Filter grpcgateway-authorization headers
Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-98
2025-01-28 13:39:06 +00:00
Christian Schaible
3472ce1585 Merged PR 723282: chore: Update dependencies
Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-97
2025-01-27 15:55:38 +00:00
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
68 changed files with 5240 additions and 2812 deletions

View file

@ -1,17 +1,22 @@
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-24.04'
variables: variables:
- name: bufVersion - name: bufVersion
value: v1.47.2 # go install github.com/bufbuild/buf/cmd/buf@
value: v1.63.0
- name: golangCiLintVersion - name: golangCiLintVersion
value: v1.62.2 # github.com/golangci/golangci-lint
value: v2.8.0
- name: goVersion - name: goVersion
value: 1.22.7 # github.com/golang/go
value: 1.24.0
- name: protobufValidateVersion - name: protobufValidateVersion
value: v1.1.0 # go install github.com/envoyproxy/protoc-gen-validate@
value: v1.3.0
- name: protobufVersion - name: protobufVersion
value: v1.36.0 # go install google.golang.org/protobuf/cmd/protoc-gen-go@
value: v1.36.11
- name: GOPATH - name: GOPATH
value: '$(system.defaultWorkingDirectory)/gopath' value: '$(system.defaultWorkingDirectory)/gopath'
@ -21,6 +26,7 @@ stages:
- job: GoBuildTest - job: GoBuildTest
displayName: Run build and tests displayName: Run build and tests
variables: variables:
- group: artifactory-xx-sit-odj-sec-ident
- name: isCiBuild - name: isCiBuild
value: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')] value: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]
steps: steps:
@ -80,6 +86,10 @@ stages:
condition: succeeded() condition: succeeded()
displayName: Check local changes after code generation and formatting displayName: Check local changes after code generation and formatting
- script: echo "$(ARTIFACTORY_PASSWORD)" | docker login schwarzit-docker.jfrog.io --username $(ARTIFACTORY_USER) --password-stdin
displayName: 'Docker login'
condition: succeeded()
- bash: go build ./... - bash: go build ./...
condition: succeeded() condition: succeeded()
displayName: Build displayName: Build

View file

@ -0,0 +1,46 @@
---
name: audit_go_main_code_analyze_$(Date:yyyy-MM-dd)_$(SourceBranchName)_$(Rev:r)
trigger:
- main
resources:
repositories:
- repository: tools
type: git
name: schwarzit.stackit-core-platform/core-platform-tools
ref: refs/tags/v1.15.0
pool:
vmImage: ubuntu-24.04
variables:
- name: reportDir
value: '$(System.DefaultWorkingDirectory)/out'
- name: goVersion
value: 1.25.5
stages:
- stage: CodeQualityScans
displayName: "Code Quality Scans"
jobs:
- template: ./.azuredevops/templates/jobs/code/code-format.yml@tools
parameters:
lintReports: true
lintReportDir: $(reportDir)
- template: ./.azuredevops/templates/jobs/code/code-test.yml@tools
parameters:
testReports: true
testReportDir: $(reportDir)
- template: ./.azuredevops/templates/jobs/code/code-quality-scans.yml@tools
parameters:
dependsOn:
- Tests
- Linter
organization: 'xx-sit-odj-stackit-public'
serviceConnection: 'xx-sit-odj-stackit-public-snyk'
sonar: true
sonarReportSourceDir: $(reportDir)
sonarServiceConnection: sonarqube-audit-go

View file

@ -6,6 +6,6 @@
[Describe how the change was tested if it needs explanation] [Describe how the change was tested if it needs explanation]
Security-concept-update-needed: true/false. Security-concept-update-needed: false.
JIRA Work Item: STACKITALO-xxx JIRA Work Item: [STACKITRMA-XXX](https://jira.schwarz/browse/STACKITRMA-XXX)

View file

@ -1,7 +1,7 @@
trigger: none trigger: none
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-24.04'
parameters: parameters:
- name: releaseType - name: releaseType

287
.golangci.yml Normal file
View file

@ -0,0 +1,287 @@
version: "2"
run:
issues-exit-code: 1
tests: true
linters:
default: none
enable:
- asciicheck
- bodyclose
- copyloopvar
- cyclop
- dogsled
- dupl
- durationcheck
- errcheck
- errorlint
- exhaustive
- forbidigo
- forcetypeassert
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- govet
- importas
- ineffassign
- lll
- makezero
- misspell
- nakedret
- nestif
- nilerr
- noctx
- nolintlint
- prealloc
- predeclared
- promlinter
- revive
- rowserrcheck
- sqlclosecheck
- staticcheck
- tparallel
- unconvert
- unparam
- unused
- wastedassign
settings:
cyclop:
max-complexity: 45
package-average: 0
dogsled:
max-blank-identifiers: 2
dupl:
threshold: 150
errcheck:
check-type-assertions: true
check-blank: true
exhaustive:
default-signifies-exhaustive: true
funlen:
lines: 100
statements: 50
gocognit:
min-complexity: 45
goconst:
min-len: 3
min-occurrences: 5
gocritic:
disabled-checks:
- dupImport
- octalLiteral
- unnamedResult
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
settings:
hugeParam:
sizeThreshold: 121
gocyclo:
min-complexity: 45
govet:
disable:
- fieldalignment
enable-all: true
lll:
line-length: 180
tab-width: 1
nakedret:
max-func-lines: 5
nestif:
min-complexity: 10
nlreturn:
block-size: 5
nolintlint:
require-explanation: true
require-specific: true
allow-unused: false
prealloc:
simple: true
range-loops: true
for-loops: true
revive:
rules:
- name: context-keys-type
disabled: false
- name: time-naming
disabled: false
- name: var-declaration
disabled: false
- name: unexported-return
disabled: false
- name: errorf
disabled: false
- name: blank-imports
disabled: false
- name: context-as-argument
disabled: false
- name: dot-imports
disabled: false
- name: error-return
disabled: false
- name: error-strings
disabled: false
- name: error-naming
disabled: false
- name: exported
disabled: false
- name: increment-decrement
disabled: false
- name: var-naming
disabled: true
- name: package-comments
disabled: false
- name: range
disabled: false
- name: receiver-naming
disabled: false
- name: indent-error-flow
disabled: false
staticcheck:
initialisms:
- ACL
- API
- ASCII
- CPU
- CSS
- DNS
- EOF
- GUID
- HTML
- HTTP
- HTTPS
- ID
- IP
- JSON
- QPS
- RAM
- RPC
- SLA
- SMTP
- SQL
- SSH
- TCP
- TLS
- TTL
- UDP
- UI
- GID
- UID
- UUID
- URI
- URL
- UTF8
- VM
- XML
- XMPP
- XSRF
- XSS
- SIP
- RTP
- AMQP
- DB
- TS
unparam:
check-exported: false
unused:
exported-fields-are-used: false
whitespace:
multi-if: false
multi-func: false
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- path: internal/audit/api/api_common.go
text: context-as-argument
- linters:
- gochecknoglobals
path: pkg/audit/common/api.go|pkg/log/log.go|internal/telemetry/telemetry.go
- linters:
- dupl
path: pkg/audit/api/api_.*.go
- path: internal/audit/api/model_test.go|internal/audit/api/model.go
text: G115
- linters:
- gosec
path: internal/audit/api/test_data.go
- linters:
- dogsled
- dupl
- errcheck
- forbidigo
- forcetypeassert
- gochecknoglobals
- gocognit
- goconst
- gocritic
- ineffassign
- lll
- nakedret
- nestif
- nlreturn
- noctx
- revive
- staticcheck
- unconvert
- unparam
- wastedassign
- wsl
path: _test\.go
- linters:
- govet
text: declaration of "err" shadows declaration
- linters:
- dogsled
- dupl
- errcheck
- forbidigo
- forcetypeassert
- gochecknoglobals
- gocognit
- goconst
- gocritic
- ineffassign
- lll
- nakedret
- nestif
- nlreturn
- noctx
- revive
- staticcheck
- unconvert
- unparam
- wastedassign
- wsl
path: test_.*\.go|pkg/messaging/test/solace.go
- linters:
- prealloc
path: internal/messaging/amqp_connection_pool_test.go
text: Consider preallocating connections with capacity 5
paths:
- third_party$
- builtin$
- examples$
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

82
Makefile Normal file
View file

@ -0,0 +1,82 @@
SHELL = /bin/bash -euo pipefail
PWD = $(shell pwd)
export PATH := $(PWD)/bin:$(PATH)
# constants
GOLANGCI_VERSION = 2.8.0
all: download build ## Initializes all tools and files
all/ci: ado-git-setup all
out:
@mkdir -pv "$(@)"
build: out ## do nothing
.PHONY: build/%
build/%: out ## do nothing
download:
@go mod download
fmt:
@go fmt ./...
GOLANGCI_LINT = bin/golangci-lint-$(GOLANGCI_VERSION)
$(GOLANGCI_LINT):
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b bin v$(GOLANGCI_VERSION)
@mv bin/golangci-lint "$(@)"
lint: fmt $(GOLANGCI_LINT) download ## Lints all code with golangci-lint
@$(GOLANGCI_LINT) run
lint/fix: fmt $(GOLANGCI_LINT) download ## Fixes automatically fixable things like imports for the defined lint rules
@$(GOLANGCI_LINT) run --fix
lint/reports: fmt $(GOLANGCI_LINT) download ## Fixes automatically fixable things like imports for the defined lint rules
@$(GOLANGCI_LINT) run ./... --output.checkstyle.path stdout | awk '!/0 issues./' > out/lint.xml
test-clean:
@go clean -testcache
tidy:
@go mod tidy
test:
@go test ./...
coverage: out/report.json ## Displays coverage per func on cli
go tool cover -func=out/cover.out
html-coverage: out/report.json ## Displays the coverage results in the browser
go tool cover -html=out/cover.out
test-reports: out/report.json
.PHONY: out/report.json
out/report.json: out
go test -v $$(go list ./... | grep -v '/tests') -tags=unit -coverprofile=out/cover.out -json | tee "$(@)"
clean:
@rm -rf bin out
.PHONY: ado-git-setup
ado-git-setup:
# Add "dev.azure.com/schwarzit" to GOPRIVATE if not present
@priv="$$(go env GOPRIVATE)"; \
[[ "$$priv" =~ '(^|,)dev\.azure\.com(/|,|$)' ]] || go env -w "GOPRIVATE=$${priv:+$$priv,}dev.azure.com/schwarzit"
# Configure HTTPS (with PAT) or SSH access to Go import paths
@if [[ -n "$${ADO_PAT:+x}" ]]; then \
git config --global "url.https://schwarzit:$${ADO_PAT}@dev.azure.com/schwarzit/.insteadof" 'https://dev.azure.com/schwarzit/'; \
else \
git config --global 'url.git@ssh.dev.azure.com:v3/schwarzit.insteadOf' 'https://dev.azure.com/schwarzit'; \
fi
help:
@echo 'Usage: make <OPTIONS> ... <TARGETS>'
@echo ''
@echo 'Available targets are:'
@echo ''
@grep -E '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@echo ''

View file

@ -1,3 +1,35 @@
> ## DEPRECATION NOTICE
>
> ### Discontinuation of the current audit log system
>
> The audit log system provided to date will be discontinued in its current form.
> This decision was made to pave the way for a new, more powerful audit log system that
> will be provided in the future. The new system offers extended functionalities and
> improved integration options, particularly with regard to the use and analysis of
> audit data by our customers.
>
> ### What does it mean?
> The existing audit log system will be supported until the new system is generally
> available to customers on Mai 1, 2026.
> **Services that are already sending audit log events to the existing audit log
> system must continue to do so until the new system is GA** and further information
> about the shutdown process is provided.
> **Large volumes of new audit event types must not be sent to the existing audit log
> system.**
>
> STACKIT services should start migrating to the new system now by sending data to
> the new system (**in parallel**).
> **The new audit log system may drop and does not guarantee to store events until
> it will be GA**.
> Further information on the changeover and how to use the new system can be found in the
> [developer docs](https://developers.stackit.schwarz/domains/central-services/telemetry-router/integration/).
>
> We are confident that the new audit log system will make an important contribution to
> improving the transparency, traceability, and integration for our customers.
> If you have any questions or need assistance, the
> [STACKIT Telemetry Hub](https://chat.google.com/room/AAQAf9NsX6M?cls=7) team will be
> happy to help.
## audit-go ## audit-go
The audit-go library is the core library for validation and sending of audit events. The audit-go library is the core library for validation and sending of audit events.
@ -32,13 +64,7 @@ The code can be found in the [api_routable.go](./api_routable.go) and
### Development ### Development
#### Go #### Go
The current minimum toolchain version is **go1.22**. The current minimum Go version is **go1.24.0**.
The toolchain version can be set as environment variable (either manually in the terminal
or in the ~/.basrc or ~/.zshrc):
```shell
export GOTOOLCHAIN=go1.22.7
```
#### Linter #### Linter
@ -46,7 +72,7 @@ The linter *golangci-lint* can either be installed via package manager (e.g. bre
by running the following command in the terminal: by running the following command in the terminal:
```shell ```shell
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.62.2 curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.8.0
``` ```
#### Schema Generation #### Schema Generation
@ -61,9 +87,9 @@ Buf and the required plugins can either be installed via package manager (e.g. b
or manually by running: or manually by running:
```shell ```shell
go install github.com/bufbuild/buf/cmd/buf@v1.47.2 #Pipeline: bufVersion go install github.com/bufbuild/buf/cmd/buf@v1.63.0 #Pipeline: bufVersion
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.0 #Pipeline: protobufVersion, go.mod: buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11 #Pipeline: protobufVersion, go.mod: buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go
go install github.com/envoyproxy/protoc-gen-validate@v1.1.0 #Pipeline: protobufValidateVersion, go.mod: google.golang.org/protobuf go install github.com/envoyproxy/protoc-gen-validate@v1.3.0 #Pipeline: protobufValidateVersion, go.mod: google.golang.org/protobuf
``` ```
Please check that the versions above match the versions in the *go.mod* file Please check that the versions above match the versions in the *go.mod* file

View file

@ -1,423 +0,0 @@
package api
import (
"context"
"errors"
"fmt"
"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/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"google.golang.org/protobuf/proto"
)
type MessagingApiMock struct {
mock.Mock
}
func (m *MessagingApiMock) Send(
ctx context.Context,
topic string,
data []byte,
contentType string,
applicationProperties map[string]any,
) error {
args := m.Called(ctx, topic, data, contentType, applicationProperties)
return args.Error(0)
}
func (m *MessagingApiMock) Close(_ context.Context) error {
return nil
}
type ProtobufValidatorMock struct {
mock.Mock
}
func (m *ProtobufValidatorMock) Validate(msg proto.Message) error {
args := m.Called(msg)
return args.Error(0)
}
type TopicNameResolverMock struct {
mock.Mock
}
func (m *TopicNameResolverMock) Resolve(routableIdentifier *RoutableIdentifier) (string, error) {
args := m.Called(routableIdentifier)
return args.String(0), args.Error(1)
}
func NewValidator(t *testing.T) ProtobufValidator {
validator, err := protovalidate.New()
var protoValidator ProtobufValidator = validator
assert.NoError(t, err)
return protoValidator
}
func Test_ValidateAndSerializePartially_EventNil(t *testing.T) {
validator := NewValidator(t)
_, err := validateAndSerializePartially(
validator, nil, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
assert.ErrorIs(t, err, ErrEventNil)
}
func Test_ValidateAndSerializePartially_AuditEventValidationFailed(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := newOrganizationAuditEvent(nil)
event.LogName = ""
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.EqualError(t, err, "validation error:\n - log_name: value is required [required]")
}
func Test_ValidateAndSerializePartially_RoutableEventValidationFailed(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := newOrganizationAuditEvent(nil)
_, err := validateAndSerializePartially(validator, event, 3, NewRoutableIdentifier(objectIdentifier))
assert.EqualError(t, err, "validation error:\n - visibility: value must be one of the defined enum values [enum.defined_only]")
}
func Test_ValidateAndSerializePartially_CheckVisibility_Event(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := newOrganizationAuditEvent(nil)
t.Run("Visibility public - object identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
})
t.Run("Visibility private - object identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
})
t.Run("Visibility public - object identifier system", func(t *testing.T) {
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrObjectIdentifierVisibilityMismatch)
})
t.Run("Visibility public - object identifier set", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
t.Run("Visibility private - object identifier system", func(t *testing.T) {
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
})
t.Run("Visibility private - object identifier set", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, NewRoutableIdentifier(objectIdentifier))
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
}
func Test_ValidateAndSerializePartially_CheckVisibility_SystemEvent(t *testing.T) {
validator := NewValidator(t)
event := newSystemAuditEvent(nil)
t.Run("Visibility public - object identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
})
t.Run("Visibility private - object identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
})
t.Run("Visibility public - object identifier system", func(t *testing.T) {
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrObjectIdentifierVisibilityMismatch)
})
t.Run("Visibility public - object identifier set", func(t *testing.T) {
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(ObjectTypeOrganization)}))
assert.ErrorIs(t, err, ErrInvalidRoutableIdentifierForSystemEvent)
})
t.Run("Visibility private - object identifier system", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, RoutableSystemIdentifier)
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
t.Run("Visibility private - object identifier set", func(t *testing.T) {
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, NewRoutableIdentifier(
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(ObjectTypeOrganization)}))
assert.ErrorIs(t, err, ErrInvalidRoutableIdentifierForSystemEvent)
})
}
func Test_ValidateAndSerializePartially_UnsupportedIdentifierType(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := newFolderAuditEvent(nil)
objectIdentifier.Type = "invalid"
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
}
func Test_ValidateAndSerializePartially_LogNameIdentifierMismatch(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := newFolderAuditEvent(nil)
parts := strings.Split(event.LogName, "/")
identifier := parts[1]
t.Run("LogName type mismatch", func(t *testing.T) {
event.LogName = fmt.Sprintf("projects/%s/logs/admin-activity", identifier)
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrAttributeTypeInvalid)
})
t.Run("LogName identifier mismatch", func(t *testing.T) {
event.LogName = fmt.Sprintf("folders/%s/logs/admin-activity", uuid.NewString())
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
})
}
func Test_ValidateAndSerializePartially_ResourceNameIdentifierMismatch(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := newFolderAuditEvent(nil)
parts := strings.Split(event.ProtoPayload.ResourceName, "/")
identifier := parts[1]
t.Run("ResourceName type mismatch", func(t *testing.T) {
event.ProtoPayload.ResourceName = fmt.Sprintf("projects/%s", identifier)
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrAttributeTypeInvalid)
})
t.Run("ResourceName identifier mismatch", func(t *testing.T) {
event.ProtoPayload.ResourceName = fmt.Sprintf("folders/%s", uuid.NewString())
_, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
})
}
func Test_ValidateAndSerializePartially_SystemEvent(t *testing.T) {
validator := NewValidator(t)
event := newSystemAuditEvent(nil)
routableEvent, err := validateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, RoutableSystemIdentifier)
assert.NoError(t, err)
assert.Equal(t, event.LogName, fmt.Sprintf("system/%s/logs/%s", SystemIdentifier.Identifier, EventTypeSystemEvent))
assert.True(t, proto.Equal(routableEvent.ObjectIdentifier, SystemIdentifier))
}
func Test_Send_TopicNameResolverNil(t *testing.T) {
err := send(nil, nil, context.Background(), nil, nil)
assert.ErrorIs(t, err, ErrTopicNameResolverNil)
}
func Test_Send_TopicNameResolutionError(t *testing.T) {
expectedError := errors.New("expected error")
topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", expectedError)
var topicNameResolver TopicNameResolver = &topicNameResolverMock
var cloudEvent = CloudEvent{}
var messagingApi messaging.Api = &messaging.AmqpApi{}
err := send(topicNameResolver, messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent)
assert.ErrorIs(t, err, expectedError)
}
func Test_Send_MessagingApiNil(t *testing.T) {
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"}
err := send(topicNameResolver, nil, context.Background(), nil, nil)
assert.ErrorIs(t, err, ErrMessagingApiNil)
}
func Test_Send_CloudEventNil(t *testing.T) {
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"}
var messagingApi messaging.Api = &messaging.AmqpApi{}
err := send(topicNameResolver, messagingApi, context.Background(), nil, nil)
assert.ErrorIs(t, err, ErrCloudEventNil)
}
func Test_Send_ObjectIdentifierNil(t *testing.T) {
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"}
var messagingApi messaging.Api = &messaging.AmqpApi{}
var cloudEvent = CloudEvent{}
err := send(topicNameResolver, messagingApi, context.Background(), nil, &cloudEvent)
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
}
func Test_Send_UnsupportedObjectIdentifierType(t *testing.T) {
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"}
var messagingApi messaging.Api = &messaging.AmqpApi{}
var cloudEvent = CloudEvent{}
var objectIdentifier = auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: "unsupported"}
err := send(topicNameResolver, messagingApi, context.Background(), NewRoutableIdentifier(&objectIdentifier), &cloudEvent)
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
}
func Test_Send(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
var topicNameResolver TopicNameResolver = &topicNameResolverMock
messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi messaging.Api = &messagingApiMock
var cloudEvent = CloudEvent{}
assert.NoError(t, send(topicNameResolver, messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
}
func Test_SendAllHeadersSet(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
var topicNameResolver TopicNameResolver = &topicNameResolverMock
messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi messaging.Api = &messagingApiMock
traceState := "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
traceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
expectedTime := time.Now()
var cloudEvent = CloudEvent{
SpecVersion: "1.0",
Source: "resourcemanager",
Id: "id",
Time: expectedTime,
DataContentType: ContentTypeCloudEventsProtobuf,
DataType: "type",
Subject: "subject",
TraceParent: &traceParent,
TraceState: &traceState,
}
assert.NoError(t, send(topicNameResolver, messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
arguments := messagingApiMock.Calls[0].Arguments
topic := arguments.Get(1).(string)
assert.Equal(t, "topic", topic)
contentType := arguments.Get(3).(string)
assert.Equal(t, ContentTypeCloudEventsProtobuf, contentType)
applicationProperties := arguments.Get(4).(map[string]any)
assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"])
assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"])
assert.Equal(t, "id", applicationProperties["cloudEvents:id"])
assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "type", applicationProperties["cloudEvents:type"])
assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"])
assert.Equal(t, traceParent, applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, traceState, applicationProperties["cloudEvents:tracestate"])
messagingApiMock.AssertExpectations(t)
}
func Test_SendWithoutOptionalHeadersSet(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
var topicNameResolver TopicNameResolver = &topicNameResolverMock
messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi messaging.Api = &messagingApiMock
expectedTime := time.Now()
var cloudEvent = CloudEvent{
SpecVersion: "1.0",
Source: "resourcemanager",
Id: "id",
Time: expectedTime,
DataContentType: ContentTypeCloudEventsProtobuf,
DataType: "type",
Subject: "subject",
}
assert.NoError(t, send(topicNameResolver, messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
arguments := messagingApiMock.Calls[0].Arguments
topic := arguments.Get(1).(string)
assert.Equal(t, "topic", topic)
contentType := arguments.Get(3).(string)
assert.Equal(t, ContentTypeCloudEventsProtobuf, contentType)
applicationProperties := arguments.Get(4).(map[string]any)
assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"])
assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"])
assert.Equal(t, "id", applicationProperties["cloudEvents:id"])
assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "type", applicationProperties["cloudEvents:type"])
assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"])
assert.Equal(t, nil, applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, nil, applicationProperties["cloudEvents:tracestate"])
messagingApiMock.AssertExpectations(t)
}

View file

@ -1,240 +0,0 @@
package messaging
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/log"
"github.com/Azure/go-amqp"
)
// Default connection timeout for the AMQP connection
const connectionTimeoutSeconds = 10
// Api is an abstraction for a messaging system that can be used to send
// audit logs to the audit log system.
type Api interface {
// Send method will send the given data to the specified topic synchronously.
// Parameters:
// * ctx - the context object
// * topic - the messaging topic where to send the data to
// * data - the serialized data as byte array
// * contentType - the contentType of the serialized data
// * applicationProperties - properties to send with the message (i.e. cloud event headers)
//
// It returns technical errors for connection issues or sending problems.
Send(ctx context.Context, topic string, data []byte, contentType string, applicationProperties map[string]any) error
// Close the underlying connection to the messaging system.
// Parameters:
// * ctx - the context object
//
// It returns an error if the connection cannot be closed successfully
Close(ctx context.Context) error
}
// MutexApi is wrapper around an API implementation that controls mutual exclusive access to the api.
type MutexApi struct {
mutex sync.Mutex
api Api
}
var _ Api = &MutexApi{}
func NewMutexApi(api Api) (Api, error) {
if api == nil {
return nil, errors.New("api is nil")
}
mutexApi := MutexApi{
mutex: sync.Mutex{},
api: api,
}
var genericApi Api = &mutexApi
return genericApi, nil
}
// Send implements Api.Send
func (m *MutexApi) Send(ctx context.Context, topic string, data []byte, contentType string, applicationProperties map[string]any) error {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.api.Send(ctx, topic, data, contentType, applicationProperties)
}
func (m *MutexApi) Close(ctx context.Context) error {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.api.Close(ctx)
}
// AmqpConfig provides AMQP connection related parameters.
type AmqpConfig struct {
URL string
User string
Password string
}
// AmqpSession is an abstraction providing a subset of the methods of amqp.Session
type AmqpSession interface {
NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (AmqpSender, error)
Close(ctx context.Context) error
}
type AmqpSessionWrapper struct {
session *amqp.Session
}
func (w AmqpSessionWrapper) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (AmqpSender, error) {
return w.session.NewSender(ctx, target, opts)
}
func (w AmqpSessionWrapper) Close(ctx context.Context) error {
return w.session.Close(ctx)
}
// AmqpSender is an abstraction providing a subset of the methods of amqp.Sender
type AmqpSender interface {
Send(ctx context.Context, msg *amqp.Message, opts *amqp.SendOptions) error
Close(ctx context.Context) error
}
// AmqpApi implements Api.
type AmqpApi struct {
config AmqpConfig
connection *amqp.Conn
session AmqpSession
}
var _ Api = &AmqpApi{}
func NewAmqpApi(amqpConfig AmqpConfig) (Api, error) {
amqpApi := &AmqpApi{config: amqpConfig}
if err := amqpApi.connect(); err != nil {
return nil, fmt.Errorf("connect to broker: %w", err)
}
return amqpApi, nil
}
// connect opens a new connection and session to the AMQP messaging system.
// The connection attempt will be cancelled after connectionTimeoutSeconds.
func (a *AmqpApi) connect() error {
log.AuditLogger.Info("connecting to audit messaging system")
// Set credentials if specified
auth := amqp.SASLTypeAnonymous()
if a.config.User != "" && a.config.Password != "" {
auth = amqp.SASLTypePlain(a.config.User, a.config.Password)
log.AuditLogger.Info("using username and password for messaging")
} else {
log.AuditLogger.Warn("using anonymous messaging!")
}
options := &amqp.ConnOptions{
SASLType: auth,
}
// Create new context with timeout for the connection initialization
subCtx, cancel := context.WithTimeout(context.Background(), connectionTimeoutSeconds*time.Second)
defer cancel()
// Initialize connection
conn, err := amqp.Dial(subCtx, a.config.URL, options)
if err != nil {
return fmt.Errorf("dial connection to broker: %w", err)
}
a.connection = conn
// Initialize session
session, err := conn.NewSession(context.Background(), nil)
if err != nil {
return fmt.Errorf("create session: %w", err)
}
var amqpSession AmqpSession = &AmqpSessionWrapper{session: session}
a.session = amqpSession
return nil
}
// Send implements Api.Send.
// If errors occur the connection to the messaging system will be closed and re-established.
func (a *AmqpApi) Send(ctx context.Context, topic string, data []byte, contentType string, applicationProperties map[string]any) error {
err := a.trySend(ctx, topic, data, contentType, applicationProperties)
if err == nil {
return nil
}
// Drop the current sender, as it cannot connect to the broker anymore
log.AuditLogger.Error("message sender error, recreating", err)
err = a.resetConnection(ctx)
if err != nil {
return fmt.Errorf("reset connection: %w", err)
}
return a.trySend(ctx, topic, data, contentType, applicationProperties)
}
// trySend actually sends the given data as amqp.Message to the messaging system.
func (a *AmqpApi) trySend(ctx context.Context, topic string, data []byte, contentType string, applicationProperties map[string]any) error {
if !strings.HasPrefix(topic, AmqpTopicPrefix) {
return fmt.Errorf(
"topic %q name lacks mandatory prefix %q",
topic,
AmqpTopicPrefix,
)
}
sender, err := a.session.NewSender(ctx, topic, nil)
if err != nil {
return fmt.Errorf("new sender: %w", err)
}
defer func() {
if err := sender.Close(ctx); err != nil {
log.AuditLogger.Error("failed to close session sender", err)
}
}()
bytes := [][]byte{data}
message := amqp.Message{
Header: &amqp.MessageHeader{
Durable: true,
},
Properties: &amqp.MessageProperties{
To: &topic,
ContentType: &contentType,
},
ApplicationProperties: applicationProperties,
Data: bytes,
}
err = sender.Send(ctx, &message, nil)
if err != nil {
return fmt.Errorf("send message: %w", err)
}
return nil
}
// resetConnection closes the current session and connection and reconnects to the messaging system.
func (a *AmqpApi) resetConnection(ctx context.Context) error {
if err := a.Close(ctx); err != nil {
log.AuditLogger.Error("failed to close audit messaging connection", err)
}
return a.connect()
}
// Close implements Api.Close
func (a *AmqpApi) Close(ctx context.Context) error {
log.AuditLogger.Info("close audit messaging connection")
return errors.Join(a.session.Close(ctx), a.connection.Close())
}

View file

@ -1,183 +0,0 @@
package messaging
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/Azure/go-amqp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type AmqpSessionMock struct {
mock.Mock
}
func (m *AmqpSessionMock) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (AmqpSender, error) {
args := m.Called(ctx, target, opts)
var sender AmqpSender = nil
if args.Get(0) != nil {
sender = args.Get(0).(AmqpSender)
}
err := args.Error(1)
return sender, err
}
func (m *AmqpSessionMock) Close(ctx context.Context) error {
args := m.Called(ctx)
return args.Error(0)
}
type AmqpSenderMock struct {
mock.Mock
}
func (m *AmqpSenderMock) Send(ctx context.Context, msg *amqp.Message, opts *amqp.SendOptions) error {
args := m.Called(ctx, msg, opts)
return args.Error(0)
}
func (m *AmqpSenderMock) Close(ctx context.Context) error {
args := m.Called(ctx)
return args.Error(0)
}
func Test_NewAmqpMessagingApi(t *testing.T) {
_, err := NewAmqpApi(AmqpConfig{URL: "not-handled-protocol://localhost:5672"})
assert.EqualError(t, err, "connect to broker: dial connection to broker: unsupported scheme \"not-handled-protocol\"")
}
func Test_AmqpMessagingApi_Send(t *testing.T) {
// Specify test timeout
ctx, cancelFn := context.WithTimeout(context.Background(), 120*time.Second)
defer cancelFn()
// Start solace docker container
solaceContainer, err := NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
t.Run("Missing topic prefix", func(t *testing.T) {
defer solaceContainer.StopOnError()
api, err := NewAmqpApi(AmqpConfig{URL: solaceContainer.AmqpConnectionString})
assert.NoError(t, err)
err = api.Send(ctx, "topic-name", []byte{}, "application/json", make(map[string]any))
assert.EqualError(t, err, "topic \"topic-name\" name lacks mandatory prefix \"topic://\"")
})
t.Run("Close connection without errors", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Initialize the solace queue
topicSubscriptionTopicPattern := "auditlog/>"
queueName := "close-connection-without-error"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := fmt.Sprintf("topic://auditlog/%s", "amqp-close-connection")
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
api := &AmqpApi{config: AmqpConfig{URL: solaceContainer.AmqpConnectionString}}
err := api.connect()
assert.NoError(t, err)
err = api.Close(ctx)
assert.NoError(t, err)
})
t.Run("New sender call returns error", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Initialize the solace queue
topicSubscriptionTopicPattern := "auditlog/>"
queueName := "messaging-new-sender"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := fmt.Sprintf("topic://auditlog/%s", "amqp-no-new-sender")
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
api := &AmqpApi{config: AmqpConfig{URL: solaceContainer.AmqpConnectionString}}
err := api.connect()
assert.NoError(t, err)
expectedError := errors.New("expected error")
// Set mock session
sessionMock := AmqpSessionMock{}
sessionMock.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(nil, expectedError)
sessionMock.On("Close", mock.Anything).Return(nil)
var amqpSession AmqpSession = &sessionMock
api.session = amqpSession
// It's expected that the test succeeds.
// First the session is closed as it returns the expected error
// Then the retry mechanism restarts the connection and successfully sends the data
value := "test"
err = api.Send(ctx, topicName, []byte(value), "application/json", make(map[string]any))
assert.NoError(t, err)
// Check that the mock was called
assert.True(t, sessionMock.AssertNumberOfCalls(t, "NewSender", 1))
assert.True(t, sessionMock.AssertNumberOfCalls(t, "Close", 1))
message, err := solaceContainer.NextMessage(ctx, fmt.Sprintf("queue://%s", queueName), true)
assert.NoError(t, err)
assert.Equal(t, value, string(message.Data[0]))
assert.Equal(t, topicName, *message.Properties.To)
})
t.Run("Send call on sender returns error", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Initialize the solace queue
topicSubscriptionTopicPattern := "auditlog/>"
queueName := "messaging-sender-error"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := fmt.Sprintf("topic://auditlog/%s", "amqp-sender-error")
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
api := &AmqpApi{config: AmqpConfig{URL: solaceContainer.AmqpConnectionString}}
err := api.connect()
assert.NoError(t, err)
expectedError := errors.New("expected error")
// Instantiate mock sender
senderMock := AmqpSenderMock{}
senderMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(expectedError)
senderMock.On("Close", mock.Anything).Return(nil)
var amqpSender AmqpSender = &senderMock
// Set mock session
sessionMock := AmqpSessionMock{}
sessionMock.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(&amqpSender, nil)
sessionMock.On("Close", mock.Anything).Return(nil)
var amqpSession AmqpSession = &sessionMock
api.session = amqpSession
// It's expected that the test succeeds.
// First the sender and session are closed as the sender returns the expected error
// Then the retry mechanism restarts the connection and successfully sends the data
value := "test"
err = api.Send(ctx, topicName, []byte(value), "application/json", make(map[string]any))
assert.NoError(t, err)
// Check that the mocks were called
assert.True(t, sessionMock.AssertNumberOfCalls(t, "NewSender", 1))
assert.True(t, sessionMock.AssertNumberOfCalls(t, "Close", 1))
assert.True(t, senderMock.AssertNumberOfCalls(t, "Send", 1))
assert.True(t, senderMock.AssertNumberOfCalls(t, "Close", 1))
message, err := solaceContainer.NextMessage(ctx, fmt.Sprintf("queue://%s", queueName), true)
assert.NoError(t, err)
assert.Equal(t, value, string(message.Data[0]))
assert.Equal(t, topicName, *message.Properties.To)
})
}

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.0 // protoc-gen-go v1.36.11
// protoc (unknown) // protoc (unknown)
// source: audit/v1/audit_event.proto // source: audit/v1/audit_event.proto
@ -15,6 +15,7 @@ import (
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
unsafe "unsafe"
) )
const ( const (
@ -520,8 +521,8 @@ type AuthenticationInfo struct {
// The email address of the authenticated user. // The email address of the authenticated user.
// Service accounts have email addresses that can be used. // Service accounts have email addresses that can be used.
// //
// Required: true // Required: false
PrincipalEmail string `protobuf:"bytes,2,opt,name=principal_email,json=principalEmail,proto3" json:"principal_email,omitempty"` PrincipalEmail *string `protobuf:"bytes,2,opt,name=principal_email,json=principalEmail,proto3,oneof" json:"principal_email,omitempty"`
// The name of the service account used to create or exchange // The name of the service account used to create or exchange
// credentials for authenticating the service account making the request. // credentials for authenticating the service account making the request.
// //
@ -583,8 +584,8 @@ func (x *AuthenticationInfo) GetPrincipalId() string {
} }
func (x *AuthenticationInfo) GetPrincipalEmail() string { func (x *AuthenticationInfo) GetPrincipalEmail() string {
if x != nil { if x != nil && x.PrincipalEmail != nil {
return x.PrincipalEmail return *x.PrincipalEmail
} }
return "" return ""
} }
@ -1464,335 +1465,152 @@ func (x *ServiceAccountDelegationInfo_IdpPrincipal) GetServiceMetadata() *struct
var File_audit_v1_audit_event_proto protoreflect.FileDescriptor var File_audit_v1_audit_event_proto protoreflect.FileDescriptor
var file_audit_v1_audit_event_proto_rawDesc = []byte{ const file_audit_v1_audit_event_proto_rawDesc = "" +
0x0a, 0x1a, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x64, 0x69, 0x74, "\n" +
0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x61, 0x75, "\x1aaudit/v1/audit_event.proto\x12\baudit.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xe2\x04\n" +
0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, "\rAuditLogEntry\x12x\n" +
0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, "\blog_name\x18\x01 \x01(\tB]\xbaHZ\xc8\x01\x01rU2S^[a-z-]+/[a-z0-9-]+/logs/(?:admin-activity|system-event|policy-denied|data-access)$R\alogName\x12?\n" +
0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, "\rproto_payload\x18\x02 \x01(\v2\x12.audit.v1.AuditLogB\x06\xbaH\x03\xc8\x01\x01R\fprotoPayload\x12L\n" +
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, "\tinsert_id\x18\x03 \x01(\tB/\xbaH,\xc8\x01\x01r'2%^[0-9]+/[a-z0-9-]+/[a-z0-9-]+/[0-9]+$R\binsertId\x12;\n" +
0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, "\x06labels\x18\x04 \x03(\v2#.audit.v1.AuditLogEntry.LabelsEntryR\x06labels\x126\n" +
0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, "\x0ecorrelation_id\x18\x05 \x01(\tB\n" +
0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, "\xbaH\ar\x05\x10\x01\x18\xff\x01H\x00R\rcorrelationId\x88\x01\x01\x12E\n" +
0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, "\ttimestamp\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampB\v\xbaH\b\xc8\x01\x01\xb2\x01\x028\x01R\ttimestamp\x12>\n" +
0x74, 0x6f, 0x22, 0xe2, 0x04, 0x0a, 0x0d, 0x41, 0x75, 0x64, 0x69, 0x74, 0x4c, 0x6f, 0x67, 0x45, "\bseverity\x18\a \x01(\x0e2\x15.audit.v1.LogSeverityB\v\xbaH\b\xc8\x01\x01\x82\x01\x02\x10\x01R\bseverity\x1a9\n" +
0x6e, 0x74, 0x72, 0x79, 0x12, 0x78, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, "\vLabelsEntry\x12\x10\n" +
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x5d, 0xba, 0x48, 0x5a, 0xc8, 0x01, 0x01, 0x72, 0x55, "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
0x32, 0x53, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x2d, 0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x11\n" +
0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x28, 0x3f, 0x3a, 0x61, 0x64, "\x0f_correlation_id\"\xb3\x06\n" +
0x6d, 0x69, 0x6e, 0x2d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x7c, 0x73, 0x79, 0x73, "\bAuditLog\x125\n" +
0x74, 0x65, 0x6d, 0x2d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x7c, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, "\fservice_name\x18\x01 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
0x2d, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x7c, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x61, 0x63, 0x63, "\x10\x012\x06.*\\S.*R\vserviceName\x12w\n" +
0x65, 0x73, 0x73, 0x29, 0x24, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3f, "\x0eoperation_name\x18\x02 \x01(\tBP\xbaHM\xc8\x01\x01rH\x10\x01\x18\xff\x012A^stackit\\.[a-z0-9-]+\\.(?:v[0-9]+\\.)?(?:[a-z0-9-.]+\\.)?[a-z0-9-]+$R\roperationName\x12c\n" +
0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, "\rresource_name\x18\x03 \x01(\tB>\xbaH;\xc8\x01\x01r6\x10\x01\x18\xff\x012/^[a-z]+/[a-z0-9-]+(?:/[a-z0-9-]+/[a-z0-9-_]+)*$R\fresourceName\x12U\n" +
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, "\x13authentication_info\x18\x04 \x01(\v2\x1c.audit.v1.AuthenticationInfoB\x06\xbaH\x03\xc8\x01\x01R\x12authenticationInfo\x12J\n" +
0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x4c, 0x6f, 0x67, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, "\x12authorization_info\x18\x05 \x03(\v2\x1b.audit.v1.AuthorizationInfoR\x11authorizationInfo\x12L\n" +
0x01, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, "\x10request_metadata\x18\x06 \x01(\v2\x19.audit.v1.RequestMetadataB\x06\xbaH\x03\xc8\x01\x01R\x0frequestMetadata\x126\n" +
0x4c, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, "\arequest\x18\a \x01(\v2\x17.google.protobuf.StructH\x00R\arequest\x88\x01\x01\x12O\n" +
0x28, 0x09, 0x42, 0x2f, 0xba, 0x48, 0x2c, 0xc8, 0x01, 0x01, 0x72, 0x27, 0x32, 0x25, 0x5e, 0x5b, "\x11response_metadata\x18\b \x01(\v2\x1a.audit.v1.ResponseMetadataB\x06\xbaH\x03\xc8\x01\x01R\x10responseMetadata\x128\n" +
0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, "\bresponse\x18\t \x01(\v2\x17.google.protobuf.StructH\x01R\bresponse\x88\x01\x01\x128\n" +
0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x2f, 0x5b, 0x30, 0x2d, 0x39, "\bmetadata\x18\n" +
0x5d, 0x2b, 0x24, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x49, 0x64, 0x12, 0x3b, 0x0a, " \x01(\v2\x17.google.protobuf.StructH\x02R\bmetadata\x88\x01\x01B\n" +
0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, "\n" +
0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x4c, 0x6f, "\b_requestB\v\n" +
0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, "\t_responseB\v\n" +
0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x36, 0x0a, 0x0e, 0x63, 0x6f, "\t_metadata\"\x93\x03\n" +
0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, "\x12AuthenticationInfo\x125\n" +
0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x48, 0x00, "\fprincipal_id\x18\x01 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x88, "\x10\x012\x06.*\\S.*R\vprincipalId\x12:\n" +
0x01, 0x01, 0x12, 0x45, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, "\x0fprincipal_email\x18\x02 \x01(\tB\f\xbaH\tr\a\x10\x05\x18\xff\x01`\x01H\x00R\x0eprincipalEmail\x88\x01\x01\x12n\n" +
0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, "\x14service_account_name\x18\x03 \x01(\tB7\xbaH4r220^[a-z-]+/[a-z0-9-]+/service-accounts/[a-z0-9-]+$H\x01R\x12serviceAccountName\x88\x01\x01\x12m\n" +
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, "\x1fservice_account_delegation_info\x18\x04 \x03(\v2&.audit.v1.ServiceAccountDelegationInfoR\x1cserviceAccountDelegationInfoB\x12\n" +
0x70, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0xb2, 0x01, 0x02, 0x38, 0x01, 0x52, 0x09, "\x10_principal_emailB\x17\n" +
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3e, 0x0a, 0x08, 0x73, 0x65, 0x76, "\x15_service_account_name\"\xf2\x01\n" +
0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x61, 0x75, "\x11AuthorizationInfo\x12U\n" +
0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, "\bresource\x18\x01 \x01(\tB9\xbaH6\xc8\x01\x01r12/^[a-z]+/[a-z0-9-]+(?:/[a-z0-9-]+/[a-z0-9-_]+)*$R\bresource\x12L\n" +
0x74, 0x79, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, "\n" +
0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, "permission\x18\x02 \x01(\tB'\xbaH$r\"2 ^[a-z-]+(?:\\.[a-z-]+)*\\.[a-z-]+$H\x00R\n" +
0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, "permission\x88\x01\x01\x12\x1d\n" +
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, "\agranted\x18\x03 \x01(\bH\x01R\agranted\x88\x01\x01B\r\n" +
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, "\v_permissionB\n" +
0x3a, 0x02, 0x38, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, "\n" +
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xab, 0x06, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, "\b_granted\"\xaa\v\n" +
0x74, 0x4c, 0x6f, 0x67, 0x12, 0x2d, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, "\x10AttributeContext\x1a\xa9\x01\n" +
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, "\x04Auth\x12J\n" +
0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, "\tprincipal\x18\x01 \x01(\tB,\xbaH)\xc8\x01\x01r$2\"^[a-zA-Z0-9-%._]+/[a-zA-Z0-9-%.]+$R\tprincipal\x12\x1c\n" +
0x61, 0x6d, 0x65, 0x12, 0x77, 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, "\taudiences\x18\x02 \x03(\tR\taudiences\x127\n" +
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x50, 0xba, 0x48, 0x4d, "\x06claims\x18\x03 \x01(\v2\x17.google.protobuf.StructB\x06\xbaH\x03\xc8\x01\x01R\x06claims\x1a\xce\x04\n" +
0xc8, 0x01, 0x01, 0x72, 0x48, 0x10, 0x01, 0x18, 0xff, 0x01, 0x32, 0x41, 0x5e, 0x73, 0x74, 0x61, "\aRequest\x12\x13\n" +
0x63, 0x6b, 0x69, 0x74, 0x5c, 0x2e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, "\x02id\x18\x01 \x01(\tH\x00R\x02id\x88\x01\x01\x12J\n" +
0x5c, 0x2e, 0x28, 0x3f, 0x3a, 0x76, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x5c, 0x2e, 0x29, 0x3f, "\x06method\x18\x02 \x01(\x0e2%.audit.v1.AttributeContext.HttpMethodB\v\xbaH\b\xc8\x01\x01\x82\x01\x02\x10\x01R\x06method\x12Q\n" +
0x28, 0x3f, 0x3a, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x2e, 0x5d, 0x2b, 0x5c, 0x2e, "\aheaders\x18\x03 \x03(\v2/.audit.v1.AttributeContext.Request.HeadersEntryB\x06\xbaH\x03\xc8\x01\x01R\aheaders\x12)\n" +
0x29, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x0d, 0x6f, "\x04path\x18\x04 \x01(\tB\x15\xbaH\x12\xc8\x01\x01r\r\x10\x01\x18\xff\x012\x06.*\\S.*R\x04path\x12&\n" +
0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x63, 0x0a, 0x0d, "\x04host\x18\x05 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, "\x10\x012\x06.*\\S.*R\x04host\x12*\n" +
0x01, 0x28, 0x09, 0x42, 0x3e, 0xba, 0x48, 0x3b, 0xc8, 0x01, 0x01, 0x72, 0x36, 0x10, 0x01, 0x18, "\x06scheme\x18\x06 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
0xff, 0x01, 0x32, 0x2f, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, "\x10\x012\x06.*\\S.*R\x06scheme\x12\x19\n" +
0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x28, 0x3f, 0x3a, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, "\x05query\x18\a \x01(\tH\x01R\x05query\x88\x01\x01\x12;\n" +
0x39, 0x2d, 0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, "\x04time\x18\b \x01(\v2\x1a.google.protobuf.TimestampB\v\xbaH\b\xc8\x01\x01\xb2\x01\x028\x01R\x04time\x12.\n" +
0x29, 0x2a, 0x24, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, "\bprotocol\x18\t \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
0x65, 0x12, 0x55, 0x0a, 0x13, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, "\x10\x012\x06.*\\S.*R\bprotocol\x12;\n" +
0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, "\x04auth\x18\n" +
0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, " \x01(\v2\x1f.audit.v1.AttributeContext.AuthB\x06\xbaH\x03\xc8\x01\x01R\x04auth\x1a:\n" +
0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x06, 0xba, 0x48, "\fHeadersEntry\x12\x10\n" +
0x03, 0xc8, 0x01, 0x01, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x05\n" +
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x05, "\x03_idB\b\n" +
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, "\x06_query\x1a\x87\x03\n" +
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, "\bResponse\x12W\n" +
0x6f, 0x52, 0x11, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, "\x12num_response_items\x18\x01 \x01(\v2\x1b.google.protobuf.Int64ValueB\a\xbaH\x04\"\x02(\x00H\x00R\x10numResponseItems\x88\x01\x01\x12=\n" +
0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4c, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, "\x04size\x18\x02 \x01(\v2\x1b.google.protobuf.Int64ValueB\a\xbaH\x04\"\x02(\x00H\x01R\x04size\x88\x01\x01\x12J\n" +
0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, "\aheaders\x18\x03 \x03(\v20.audit.v1.AttributeContext.Response.HeadersEntryR\aheaders\x12;\n" +
0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, "\x04time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\v\xbaH\b\xc8\x01\x01\xb2\x01\x028\x01R\x04time\x1a:\n" +
0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, "\fHeadersEntry\x12\x10\n" +
0x01, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
0x74, 0x61, 0x12, 0x36, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x07, 0x20, "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x15\n" +
0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, "\x13_num_response_itemsB\a\n" +
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x48, 0x00, 0x52, 0x07, "\x05_size\"\x8e\x02\n" +
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x4f, 0x0a, 0x11, 0x72, 0x65, "\n" +
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, "HttpMethod\x12\x1b\n" +
0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, "\x17HTTP_METHOD_UNSPECIFIED\x10\x00\x12\x15\n" +
0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, "\x11HTTP_METHOD_OTHER\x10\x01\x12\x13\n" +
0x61, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x10, 0x72, 0x65, 0x73, 0x70, 0x6f, "\x0fHTTP_METHOD_GET\x10\x02\x12\x14\n" +
0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x08, 0x72, "\x10HTTP_METHOD_HEAD\x10\x03\x12\x14\n" +
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, "\x10HTTP_METHOD_POST\x10\x04\x12\x13\n" +
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, "\x0fHTTP_METHOD_PUT\x10\x05\x12\x16\n" +
0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x48, 0x01, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, "\x12HTTP_METHOD_DELETE\x10\x06\x12\x17\n" +
0x73, 0x65, 0x88, 0x01, 0x01, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, "\x13HTTP_METHOD_CONNECT\x10\a\x12\x17\n" +
0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, "\x13HTTP_METHOD_OPTIONS\x10\b\x12\x15\n" +
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, "\x11HTTP_METHOD_TRACE\x10\t\x12\x15\n" +
0x48, 0x02, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, "\x11HTTP_METHOD_PATCH\x10\n" +
0x0a, 0x0a, 0x08, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x0b, 0x0a, 0x09, 0x5f, "\"\xe9\x01\n" +
0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, "\x0fRequestMetadata\x12'\n" +
0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf3, 0x02, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, "\tcaller_ip\x18\x01 \x01(\tB\n" +
0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, 0x0c, "\xbaH\a\xc8\x01\x01r\x02p\x01R\bcallerIp\x12R\n" +
0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, "\x1acaller_supplied_user_agent\x18\x02 \x01(\tB\x15\xbaH\x12\xc8\x01\x01r\r\x10\x01\x18\xff\x012\x06.*\\S.*R\x17callerSuppliedUserAgent\x12Y\n" +
0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0b, "\x12request_attributes\x18\x03 \x01(\v2\".audit.v1.AttributeContext.RequestB\x06\xbaH\x03\xc8\x01\x01R\x11requestAttributes\"\xb4\x02\n" +
0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x0f, 0x70, "\x10ResponseMetadata\x12H\n" +
0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, "\vstatus_code\x18\x01 \x01(\v2\x1b.google.protobuf.Int32ValueB\n" +
0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x0a, 0xc8, 0x01, 0x01, 0x72, 0x05, 0x10, 0x01, "\xbaH\a\xc8\x01\x01\x1a\x02(\x00R\n" +
0x18, 0xff, 0x01, 0x52, 0x0e, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x45, 0x6d, "statusCode\x12(\n" +
0x61, 0x69, 0x6c, 0x12, 0x6e, 0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, "\rerror_message\x18\x02 \x01(\tH\x00R\ferrorMessage\x88\x01\x01\x12<\n" +
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, "\rerror_details\x18\x03 \x03(\v2\x17.google.protobuf.StructR\ferrorDetails\x12\\\n" +
0x09, 0x42, 0x37, 0xba, 0x48, 0x34, 0x72, 0x32, 0x32, 0x30, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x2d, "\x13response_attributes\x18\x04 \x01(\v2#.audit.v1.AttributeContext.ResponseB\x06\xbaH\x03\xc8\x01\x01R\x12responseAttributesB\x10\n" +
0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x2f, 0x73, 0x65, "\x0e_error_message\"\xca\x04\n" +
0x72, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x5b, "\x1cServiceAccountDelegationInfo\x12c\n" +
0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x24, 0x48, 0x00, 0x52, 0x12, 0x73, 0x65, "\x10system_principal\x18\x01 \x01(\v26.audit.v1.ServiceAccountDelegationInfo.SystemPrincipalH\x00R\x0fsystemPrincipal\x12Z\n" +
0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, "\ridp_principal\x18\x02 \x01(\v23.audit.v1.ServiceAccountDelegationInfo.IdpPrincipalH\x00R\fidpPrincipal\x1ao\n" +
0x88, 0x01, 0x01, 0x12, 0x6d, 0x0a, 0x1f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, "\x0fSystemPrincipal\x12G\n" +
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, "\x10service_metadata\x18\x01 \x01(\v2\x17.google.protobuf.StructH\x00R\x0fserviceMetadata\x88\x01\x01B\x13\n" +
0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, "\x11_service_metadata\x1a\xe3\x01\n" +
0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, "\fIdpPrincipal\x125\n" +
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, "\fprincipal_id\x18\x01 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x1c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, "\x10\x012\x06.*\\S.*R\vprincipalId\x12>\n" +
0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, "\x0fprincipal_email\x18\x02 \x01(\tB\x15\xbaH\x12\xc8\x01\x01r\r\x10\x01\x18\xff\x012\x06.*\\S.*R\x0eprincipalEmail\x12G\n" +
0x66, 0x6f, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, "\x10service_metadata\x18\x03 \x01(\v2\x17.google.protobuf.StructH\x00R\x0fserviceMetadata\x88\x01\x01B\x13\n" +
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xf2, 0x01, 0x0a, 0x11, "\x11_service_metadataB\x12\n" +
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, "\tauthority\x12\x05\xbaH\x02\b\x01*\x96\x02\n" +
0x6f, 0x12, 0x55, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, "\vLogSeverity\x12\x1c\n" +
0x01, 0x28, 0x09, 0x42, 0x39, 0xba, 0x48, 0x36, 0xc8, 0x01, 0x01, 0x72, 0x31, 0x32, 0x2f, 0x5e, "\x18LOG_SEVERITY_UNSPECIFIED\x10\x00\x12\x18\n" +
0x5b, 0x61, 0x2d, 0x7a, 0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, "\x14LOG_SEVERITY_DEFAULT\x10d\x12\x17\n" +
0x2b, 0x28, 0x3f, 0x3a, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x2f, "\x12LOG_SEVERITY_DEBUG\x10\xc8\x01\x12\x16\n" +
0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x29, 0x2a, 0x24, 0x52, 0x08, "\x11LOG_SEVERITY_INFO\x10\xac\x02\x12\x18\n" +
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, "\x13LOG_SEVERITY_NOTICE\x10\x90\x03\x12\x19\n" +
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x27, 0xba, 0x48, "\x14LOG_SEVERITY_WARNING\x10\xf4\x03\x12\x17\n" +
0x24, 0x72, 0x22, 0x32, 0x20, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x2d, 0x5d, 0x2b, 0x28, 0x3f, 0x3a, "\x12LOG_SEVERITY_ERROR\x10\xd8\x04\x12\x1a\n" +
0x5c, 0x2e, 0x5b, 0x61, 0x2d, 0x7a, 0x2d, 0x5d, 0x2b, 0x29, 0x2a, 0x5c, 0x2e, 0x5b, 0x61, 0x2d, "\x15LOG_SEVERITY_CRITICAL\x10\xbc\x05\x12\x17\n" +
0x7a, 0x2d, 0x5d, 0x2b, 0x24, 0x48, 0x00, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, "\x12LOG_SEVERITY_ALERT\x10\xa0\x06\x12\x1b\n" +
0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, "\x16LOG_SEVERITY_EMERGENCY\x10\x84\aB1\n" +
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, "\x1ccom.schwarz.stackit.audit.v1P\x01Z\x0f./audit;auditV1b\x06proto3"
0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64,
0x22, 0x89, 0x0b, 0x0a, 0x10, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f,
0x6e, 0x74, 0x65, 0x78, 0x74, 0x1a, 0xa8, 0x01, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x49,
0x0a, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x42, 0x2b, 0xba, 0x48, 0x28, 0xc8, 0x01, 0x01, 0x72, 0x23, 0x32, 0x21, 0x5e, 0x5b, 0x61,
0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x25, 0x2e, 0x5d, 0x2b, 0x2f, 0x5b, 0x61,
0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x25, 0x2e, 0x5d, 0x2b, 0x24, 0x52, 0x09,
0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x75, 0x64,
0x69, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x75,
0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d,
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74,
0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73,
0x1a, 0xae, 0x04, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x13, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 0x01,
0x01, 0x12, 0x4a, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x25, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x48, 0x74,
0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01,
0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x51, 0x0a,
0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f,
0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62,
0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42,
0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73,
0x12, 0x21, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d,
0xba, 0x48, 0x0a, 0xc8, 0x01, 0x01, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x04, 0x70,
0x61, 0x74, 0x68, 0x12, 0x1e, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x68,
0x6f, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x06, 0x20,
0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52,
0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79,
0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x88,
0x01, 0x01, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x0b, 0xba, 0x48,
0x08, 0xc8, 0x01, 0x01, 0xb2, 0x01, 0x02, 0x38, 0x01, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12,
0x26, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28,
0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3b, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78,
0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04,
0x61, 0x75, 0x74, 0x68, 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x71, 0x75, 0x65, 0x72,
0x79, 0x1a, 0x87, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57,
0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x69,
0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74,
0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x28, 0x00,
0x48, 0x00, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x49,
0x74, 0x65, 0x6d, 0x73, 0x88, 0x01, 0x01, 0x12, 0x3d, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c,
0x75, 0x65, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x28, 0x00, 0x48, 0x01, 0x52, 0x04, 0x73,
0x69, 0x7a, 0x65, 0x88, 0x01, 0x01, 0x12, 0x4a, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72,
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e,
0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74,
0x65, 0x78, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61,
0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65,
0x72, 0x73, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x0b, 0xba, 0x48,
0x08, 0xc8, 0x01, 0x01, 0xb2, 0x01, 0x02, 0x38, 0x01, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x1a,
0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x15, 0x0a, 0x13, 0x5f,
0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x69, 0x74, 0x65,
0x6d, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x8e, 0x02, 0x0a, 0x0a,
0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1b, 0x0a, 0x17, 0x48, 0x54,
0x54, 0x50, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43,
0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x48, 0x54, 0x54, 0x50, 0x5f,
0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x10, 0x01, 0x12, 0x13,
0x0a, 0x0f, 0x48, 0x54, 0x54, 0x50, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x47, 0x45,
0x54, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x48, 0x54, 0x54, 0x50, 0x5f, 0x4d, 0x45, 0x54, 0x48,
0x4f, 0x44, 0x5f, 0x48, 0x45, 0x41, 0x44, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x48, 0x54, 0x54,
0x50, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x10, 0x04, 0x12,
0x13, 0x0a, 0x0f, 0x48, 0x54, 0x54, 0x50, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x50,
0x55, 0x54, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x5f, 0x4d, 0x45, 0x54,
0x48, 0x4f, 0x44, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x06, 0x12, 0x17, 0x0a, 0x13,
0x48, 0x54, 0x54, 0x50, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x4e,
0x45, 0x43, 0x54, 0x10, 0x07, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x54, 0x50, 0x5f, 0x4d, 0x45,
0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x10, 0x08, 0x12, 0x15,
0x0a, 0x11, 0x48, 0x54, 0x54, 0x50, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x52,
0x41, 0x43, 0x45, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, 0x48, 0x54, 0x54, 0x50, 0x5f, 0x4d, 0x45,
0x54, 0x48, 0x4f, 0x44, 0x5f, 0x50, 0x41, 0x54, 0x43, 0x48, 0x10, 0x0a, 0x22, 0xe1, 0x01, 0x0a,
0x0f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x12, 0x27, 0x0a, 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x70, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x70, 0x01, 0x52,
0x08, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x70, 0x12, 0x4a, 0x0a, 0x1a, 0x63, 0x61, 0x6c,
0x6c, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65,
0x72, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba,
0x48, 0x0a, 0xc8, 0x01, 0x01, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x17, 0x63, 0x61,
0x6c, 0x6c, 0x65, 0x72, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72,
0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x59, 0x0a, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x22, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x11, 0x72,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x22, 0xb4, 0x02, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x48, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f,
0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74,
0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x1a,
0x02, 0x28, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12,
0x28, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x3c, 0x0a, 0x0d, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72,
0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x5c, 0x0a, 0x13, 0x72, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01,
0x01, 0x52, 0x12, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69,
0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xba, 0x04, 0x0a, 0x1c, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x63, 0x0a, 0x10, 0x73, 0x79, 0x73, 0x74,
0x65, 0x6d, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x36, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x65,
0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65,
0x6d, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x48, 0x00, 0x52, 0x0f, 0x73, 0x79,
0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x5a, 0x0a,
0x0d, 0x69, 0x64, 0x70, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65,
0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x49, 0x64, 0x70,
0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x48, 0x00, 0x52, 0x0c, 0x69, 0x64, 0x70,
0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x1a, 0x6f, 0x0a, 0x0f, 0x53, 0x79, 0x73,
0x74, 0x65, 0x6d, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x47, 0x0a, 0x10,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x48,
0x00, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xd3, 0x01, 0x0a, 0x0c, 0x49,
0x64, 0x70, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x0c, 0x70,
0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0b, 0x70,
0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x0f, 0x70, 0x72,
0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x0a, 0xc8, 0x01, 0x01, 0x72, 0x05, 0x10, 0x01, 0x18,
0xff, 0x01, 0x52, 0x0e, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x45, 0x6d, 0x61,
0x69, 0x6c, 0x12, 0x47, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53,
0x74, 0x72, 0x75, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x13, 0x0a, 0x11, 0x5f,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x42, 0x12, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x05, 0xba,
0x48, 0x02, 0x08, 0x01, 0x2a, 0x96, 0x02, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x53, 0x65, 0x76, 0x65,
0x72, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x0a, 0x18, 0x4c, 0x4f, 0x47, 0x5f, 0x53, 0x45, 0x56, 0x45,
0x52, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x4c, 0x4f, 0x47, 0x5f, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49,
0x54, 0x59, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x64, 0x12, 0x17, 0x0a, 0x12,
0x4c, 0x4f, 0x47, 0x5f, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x44, 0x45, 0x42,
0x55, 0x47, 0x10, 0xc8, 0x01, 0x12, 0x16, 0x0a, 0x11, 0x4c, 0x4f, 0x47, 0x5f, 0x53, 0x45, 0x56,
0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0xac, 0x02, 0x12, 0x18, 0x0a,
0x13, 0x4c, 0x4f, 0x47, 0x5f, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x4f,
0x54, 0x49, 0x43, 0x45, 0x10, 0x90, 0x03, 0x12, 0x19, 0x0a, 0x14, 0x4c, 0x4f, 0x47, 0x5f, 0x53,
0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10,
0xf4, 0x03, 0x12, 0x17, 0x0a, 0x12, 0x4c, 0x4f, 0x47, 0x5f, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49,
0x54, 0x59, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0xd8, 0x04, 0x12, 0x1a, 0x0a, 0x15, 0x4c,
0x4f, 0x47, 0x5f, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x43, 0x52, 0x49, 0x54,
0x49, 0x43, 0x41, 0x4c, 0x10, 0xbc, 0x05, 0x12, 0x17, 0x0a, 0x12, 0x4c, 0x4f, 0x47, 0x5f, 0x53,
0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x41, 0x4c, 0x45, 0x52, 0x54, 0x10, 0xa0, 0x06,
0x12, 0x1b, 0x0a, 0x16, 0x4c, 0x4f, 0x47, 0x5f, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59,
0x5f, 0x45, 0x4d, 0x45, 0x52, 0x47, 0x45, 0x4e, 0x43, 0x59, 0x10, 0x84, 0x07, 0x42, 0x31, 0x0a,
0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x63, 0x68, 0x77, 0x61, 0x72, 0x7a, 0x2e, 0x73, 0x74, 0x61,
0x63, 0x6b, 0x69, 0x74, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x50, 0x01, 0x5a,
0x0f, 0x2e, 0x2f, 0x61, 0x75, 0x64, 0x69, 0x74, 0x3b, 0x61, 0x75, 0x64, 0x69, 0x74, 0x56, 0x31,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var ( var (
file_audit_v1_audit_event_proto_rawDescOnce sync.Once file_audit_v1_audit_event_proto_rawDescOnce sync.Once
file_audit_v1_audit_event_proto_rawDescData = file_audit_v1_audit_event_proto_rawDesc file_audit_v1_audit_event_proto_rawDescData []byte
) )
func file_audit_v1_audit_event_proto_rawDescGZIP() []byte { func file_audit_v1_audit_event_proto_rawDescGZIP() []byte {
file_audit_v1_audit_event_proto_rawDescOnce.Do(func() { file_audit_v1_audit_event_proto_rawDescOnce.Do(func() {
file_audit_v1_audit_event_proto_rawDescData = protoimpl.X.CompressGZIP(file_audit_v1_audit_event_proto_rawDescData) file_audit_v1_audit_event_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_audit_v1_audit_event_proto_rawDesc), len(file_audit_v1_audit_event_proto_rawDesc)))
}) })
return file_audit_v1_audit_event_proto_rawDescData return file_audit_v1_audit_event_proto_rawDescData
} }
@ -1882,7 +1700,7 @@ func file_audit_v1_audit_event_proto_init() {
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_audit_v1_audit_event_proto_rawDesc, RawDescriptor: unsafe.Slice(unsafe.StringData(file_audit_v1_audit_event_proto_rawDesc), len(file_audit_v1_audit_event_proto_rawDesc)),
NumEnums: 2, NumEnums: 2,
NumMessages: 16, NumMessages: 16,
NumExtensions: 0, NumExtensions: 0,
@ -1894,7 +1712,6 @@ func file_audit_v1_audit_event_proto_init() {
MessageInfos: file_audit_v1_audit_event_proto_msgTypes, MessageInfos: file_audit_v1_audit_event_proto_msgTypes,
}.Build() }.Build()
File_audit_v1_audit_event_proto = out.File File_audit_v1_audit_event_proto = out.File
file_audit_v1_audit_event_proto_rawDesc = nil
file_audit_v1_audit_event_proto_goTypes = nil file_audit_v1_audit_event_proto_goTypes = nil
file_audit_v1_audit_event_proto_depIdxs = nil file_audit_v1_audit_event_proto_depIdxs = nil
} }

View file

@ -141,7 +141,7 @@ type AuditLogEntryMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m AuditLogEntryMultiError) Error() string { func (m AuditLogEntryMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -466,7 +466,7 @@ type AuditLogMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m AuditLogMultiError) Error() string { func (m AuditLogMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -554,8 +554,6 @@ func (m *AuthenticationInfo) validate(all bool) error {
// no validation rules for PrincipalId // no validation rules for PrincipalId
// no validation rules for PrincipalEmail
for idx, item := range m.GetServiceAccountDelegationInfo() { for idx, item := range m.GetServiceAccountDelegationInfo() {
_, _ = idx, item _, _ = idx, item
@ -590,6 +588,10 @@ func (m *AuthenticationInfo) validate(all bool) error {
} }
if m.PrincipalEmail != nil {
// no validation rules for PrincipalEmail
}
if m.ServiceAccountName != nil { if m.ServiceAccountName != nil {
// no validation rules for ServiceAccountName // no validation rules for ServiceAccountName
} }
@ -608,7 +610,7 @@ type AuthenticationInfoMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m AuthenticationInfoMultiError) Error() string { func (m AuthenticationInfoMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -720,7 +722,7 @@ type AuthorizationInfoMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m AuthorizationInfoMultiError) Error() string { func (m AuthorizationInfoMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -822,7 +824,7 @@ type AttributeContextMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m AttributeContextMultiError) Error() string { func (m AttributeContextMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -955,7 +957,7 @@ type RequestMetadataMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m RequestMetadataMultiError) Error() string { func (m RequestMetadataMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -1151,7 +1153,7 @@ type ResponseMetadataMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m ResponseMetadataMultiError) Error() string { func (m ResponseMetadataMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -1338,7 +1340,7 @@ type ServiceAccountDelegationInfoMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m ServiceAccountDelegationInfoMultiError) Error() string { func (m ServiceAccountDelegationInfoMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -1472,7 +1474,7 @@ type AttributeContext_AuthMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m AttributeContext_AuthMultiError) Error() string { func (m AttributeContext_AuthMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -1652,7 +1654,7 @@ type AttributeContext_RequestMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m AttributeContext_RequestMultiError) Error() string { func (m AttributeContext_RequestMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -1851,7 +1853,7 @@ type AttributeContext_ResponseMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m AttributeContext_ResponseMultiError) Error() string { func (m AttributeContext_ResponseMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -1989,7 +1991,7 @@ type ServiceAccountDelegationInfo_SystemPrincipalMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m ServiceAccountDelegationInfo_SystemPrincipalMultiError) Error() string { func (m ServiceAccountDelegationInfo_SystemPrincipalMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -2133,7 +2135,7 @@ type ServiceAccountDelegationInfo_IdpPrincipalMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m ServiceAccountDelegationInfo_IdpPrincipalMultiError) Error() string { func (m ServiceAccountDelegationInfo_IdpPrincipalMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.0 // protoc-gen-go v1.36.11
// protoc (unknown) // protoc (unknown)
// source: audit/v1/routable_event.proto // source: audit/v1/routable_event.proto
@ -12,6 +12,7 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
unsafe "unsafe"
) )
const ( const (
@ -410,83 +411,53 @@ func (*RoutableAuditEvent_EncryptedData) isRoutableAuditEvent_Data() {}
var File_audit_v1_routable_event_proto protoreflect.FileDescriptor var File_audit_v1_routable_event_proto protoreflect.FileDescriptor
var file_audit_v1_routable_event_proto_rawDesc = []byte{ const file_audit_v1_routable_event_proto_rawDesc = "" +
0x0a, 0x1d, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x61, "\n" +
0x62, 0x6c, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, "\x1daudit/v1/routable_event.proto\x12\baudit.v1\x1a\x1bbuf/validate/validate.proto\"_\n" +
0x08, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, "\x10ObjectIdentifier\x12+\n" +
0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, "\n" +
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5f, 0x0a, 0x10, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, "identifier\x18\x01 \x01(\tB\v\xbaH\b\xc8\x01\x01r\x03\xb0\x01\x01R\n" +
0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x0a, 0x69, 0x64, "identifier\x12\x1e\n" +
0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, "\x04type\x18\x02 \x01(\tB\n" +
0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x0a, 0x69, 0x64, 0x65, "\xbaH\a\xc8\x01\x01r\x02\x10\x01R\x04type\"\xc5\x01\n" +
0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, "\rEncryptedData\x12\x1e\n" +
0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, "\x04data\x18\x01 \x01(\fB\n" +
0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xc5, 0x01, 0x0a, 0x0d, 0x45, 0x6e, 0x63, 0x72, "\xbaH\a\xc8\x01\x01z\x02\x10\x01R\x04data\x12/\n" +
0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x04, 0x64, 0x61, 0x74, "\rprotobuf_type\x18\x02 \x01(\tB\n" +
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x7a, "\xbaH\a\xc8\x01\x01r\x02\x10\x01R\fprotobufType\x129\n" +
0x02, 0x10, 0x01, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2f, 0x0a, 0x0d, 0x70, 0x72, 0x6f, "\x12encrypted_password\x18\x03 \x01(\tB\n" +
0x74, 0x6f, 0x62, 0x75, 0x66, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, "\xbaH\a\xc8\x01\x01r\x02\x10\x01R\x11encryptedPassword\x12(\n" +
0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x70, 0x72, "\vkey_version\x18\x04 \x01(\x05B\a\xbaH\x04\x1a\x02(\x01R\n" +
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x54, 0x79, 0x70, 0x65, 0x12, 0x39, 0x0a, 0x12, 0x65, 0x6e, "keyVersion\"b\n" +
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, "\x0fUnencryptedData\x12\x1e\n" +
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, "\x04data\x18\x01 \x01(\fB\n" +
0x10, 0x01, 0x52, 0x11, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x50, 0x61, 0x73, "\xbaH\a\xc8\x01\x01z\x02\x10\x01R\x04data\x12/\n" +
0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x28, 0x0a, 0x0b, 0x6b, 0x65, 0x79, 0x5f, 0x76, 0x65, 0x72, "\rprotobuf_type\x18\x02 \x01(\tB\n" +
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, 0x04, 0x1a, "\xbaH\a\xc8\x01\x01r\x02\x10\x01R\fprotobufType\"\xb5\x03\n" +
0x02, 0x28, 0x01, 0x52, 0x0a, 0x6b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, "\x12RoutableAuditEvent\x12r\n" +
0x62, 0x0a, 0x0f, 0x55, 0x6e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, "\x0eoperation_name\x18\x01 \x01(\tBK\xbaHH\xc8\x01\x01rC2A^stackit\\.[a-z0-9-]+\\.(?:v[0-9]+\\.)?(?:[a-z0-9-.]+\\.)?[a-z0-9-]+$R\roperationName\x12A\n" +
0x74, 0x61, 0x12, 0x1e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, "\n" +
0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x7a, 0x02, 0x10, 0x01, 0x52, 0x04, 0x64, 0x61, "visibility\x18\x02 \x01(\x0e2\x14.audit.v1.VisibilityB\v\xbaH\b\xc8\x01\x01\x82\x01\x02\x10\x01R\n" +
0x74, 0x61, 0x12, 0x2f, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x5f, 0x74, "visibility\x12O\n" +
0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, "\x11object_identifier\x18\x03 \x01(\v2\x1a.audit.v1.ObjectIdentifierB\x06\xbaH\x03\xc8\x01\x01R\x10objectIdentifier\x12F\n" +
0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x54, "\x10unencrypted_data\x18\x04 \x01(\v2\x19.audit.v1.UnencryptedDataH\x00R\x0funencryptedData\x12@\n" +
0x79, 0x70, 0x65, 0x22, 0xb5, 0x03, 0x0a, 0x12, 0x52, 0x6f, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, "\x0eencrypted_data\x18\x05 \x01(\v2\x17.audit.v1.EncryptedDataH\x00R\rencryptedDataB\r\n" +
0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x72, 0x0a, 0x0e, 0x6f, 0x70, "\x04data\x12\x05\xbaH\x02\b\x01*W\n" +
0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, "\n" +
0x28, 0x09, 0x42, 0x4b, 0xba, 0x48, 0x48, 0xc8, 0x01, 0x01, 0x72, 0x43, 0x32, 0x41, 0x5e, 0x73, "Visibility\x12\x1a\n" +
0x74, 0x61, 0x63, 0x6b, 0x69, 0x74, 0x5c, 0x2e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, "\x16VISIBILITY_UNSPECIFIED\x10\x00\x12\x15\n" +
0x5d, 0x2b, 0x5c, 0x2e, 0x28, 0x3f, 0x3a, 0x76, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x5c, 0x2e, "\x11VISIBILITY_PUBLIC\x10\x01\x12\x16\n" +
0x29, 0x3f, 0x28, 0x3f, 0x3a, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x2e, 0x5d, 0x2b, "\x12VISIBILITY_PRIVATE\x10\x02B1\n" +
0x5c, 0x2e, 0x29, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x24, 0x52, "\x1ccom.schwarz.stackit.audit.v1P\x01Z\x0f./audit;auditV1b\x06proto3"
0x0d, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x41,
0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x14, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x69,
0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01,
0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74,
0x79, 0x12, 0x4f, 0x0a, 0x11, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61,
0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01,
0x52, 0x10, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69,
0x65, 0x72, 0x12, 0x46, 0x0a, 0x10, 0x75, 0x6e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61,
0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70,
0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x65, 0x6e, 0x63,
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x0e, 0x65, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x17, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x0d, 0x65,
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x42, 0x0d, 0x0a, 0x04,
0x64, 0x61, 0x74, 0x61, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x2a, 0x57, 0x0a, 0x0a, 0x56,
0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x49, 0x53,
0x49, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x49, 0x53, 0x49, 0x42, 0x49, 0x4c,
0x49, 0x54, 0x59, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12,
0x56, 0x49, 0x53, 0x49, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x50, 0x52, 0x49, 0x56, 0x41,
0x54, 0x45, 0x10, 0x02, 0x42, 0x31, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x63, 0x68, 0x77,
0x61, 0x72, 0x7a, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x69, 0x74, 0x2e, 0x61, 0x75, 0x64, 0x69,
0x74, 0x2e, 0x76, 0x31, 0x50, 0x01, 0x5a, 0x0f, 0x2e, 0x2f, 0x61, 0x75, 0x64, 0x69, 0x74, 0x3b,
0x61, 0x75, 0x64, 0x69, 0x74, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var ( var (
file_audit_v1_routable_event_proto_rawDescOnce sync.Once file_audit_v1_routable_event_proto_rawDescOnce sync.Once
file_audit_v1_routable_event_proto_rawDescData = file_audit_v1_routable_event_proto_rawDesc file_audit_v1_routable_event_proto_rawDescData []byte
) )
func file_audit_v1_routable_event_proto_rawDescGZIP() []byte { func file_audit_v1_routable_event_proto_rawDescGZIP() []byte {
file_audit_v1_routable_event_proto_rawDescOnce.Do(func() { file_audit_v1_routable_event_proto_rawDescOnce.Do(func() {
file_audit_v1_routable_event_proto_rawDescData = protoimpl.X.CompressGZIP(file_audit_v1_routable_event_proto_rawDescData) file_audit_v1_routable_event_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_audit_v1_routable_event_proto_rawDesc), len(file_audit_v1_routable_event_proto_rawDesc)))
}) })
return file_audit_v1_routable_event_proto_rawDescData return file_audit_v1_routable_event_proto_rawDescData
} }
@ -525,7 +496,7 @@ func file_audit_v1_routable_event_proto_init() {
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_audit_v1_routable_event_proto_rawDesc, RawDescriptor: unsafe.Slice(unsafe.StringData(file_audit_v1_routable_event_proto_rawDesc), len(file_audit_v1_routable_event_proto_rawDesc)),
NumEnums: 1, NumEnums: 1,
NumMessages: 4, NumMessages: 4,
NumExtensions: 0, NumExtensions: 0,
@ -537,7 +508,6 @@ func file_audit_v1_routable_event_proto_init() {
MessageInfos: file_audit_v1_routable_event_proto_msgTypes, MessageInfos: file_audit_v1_routable_event_proto_msgTypes,
}.Build() }.Build()
File_audit_v1_routable_event_proto = out.File File_audit_v1_routable_event_proto = out.File
file_audit_v1_routable_event_proto_rawDesc = nil
file_audit_v1_routable_event_proto_goTypes = nil file_audit_v1_routable_event_proto_goTypes = nil
file_audit_v1_routable_event_proto_depIdxs = nil file_audit_v1_routable_event_proto_depIdxs = nil
} }

View file

@ -75,7 +75,7 @@ type ObjectIdentifierMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m ObjectIdentifierMultiError) Error() string { func (m ObjectIdentifierMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -183,7 +183,7 @@ type EncryptedDataMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m EncryptedDataMultiError) Error() string { func (m EncryptedDataMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -287,7 +287,7 @@ type UnencryptedDataMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m UnencryptedDataMultiError) Error() string { func (m UnencryptedDataMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }
@ -507,7 +507,7 @@ type RoutableAuditEventMultiError []error
// Error returns a concatenation of all the error messages it wraps. // Error returns a concatenation of all the error messages it wraps.
func (m RoutableAuditEventMultiError) Error() string { func (m RoutableAuditEventMultiError) Error() string {
var msgs []string msgs := make([]string, 0, len(m))
for _, err := range m { for _, err := range m {
msgs = append(msgs, err.Error()) msgs = append(msgs, err.Error())
} }

113
go.mod
View file

@ -1,83 +1,88 @@
module dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git module dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git
go 1.22.7 go 1.24.0
require ( require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.0-20241127180247-a33202765966.1 buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1
github.com/Azure/go-amqp v1.3.0 buf.build/go/protovalidate v1.1.0
github.com/bufbuild/protovalidate-go v0.8.0 github.com/Azure/go-amqp v1.5.1
github.com/docker/docker v28.5.2+incompatible
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lestrrat-go/jwx/v2 v2.1.3 github.com/lestrrat-go/jwx/v2 v2.1.6
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
github.com/testcontainers/testcontainers-go v0.34.0 github.com/testcontainers/testcontainers-go v0.40.0
go.opentelemetry.io/otel v1.33.0 go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/trace v1.33.0 go.opentelemetry.io/otel/trace v1.39.0
google.golang.org/protobuf v1.36.0 google.golang.org/protobuf v1.36.11
) )
require ( require (
cel.dev/expr v0.18.0 // indirect cel.dev/expr v0.25.1 // indirect
dario.cat/mergo v1.0.0 // indirect dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/containerd v1.7.18 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/google/cel-go v0.26.1 // indirect
github.com/google/cel-go v0.22.1 // indirect github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect github.com/lestrrat-go/httprc v1.0.6 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.10 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.1.0 // indirect github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/term v0.5.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/moby/term v0.5.2 // indirect
github.com/morikuni/aec v1.1.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.1 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shirou/gopsutil/v4 v4.25.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stoewer/go-strcase v1.3.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.3 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
go.opentelemetry.io/otel/metric v1.33.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
golang.org/x/crypto v0.31.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect golang.org/x/crypto v0.46.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/sys v0.40.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect golang.org/x/text v0.33.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

319
go.sum
View file

@ -1,25 +1,33 @@
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.0-20241127180247-a33202765966.1 h1:ntAj16eF7AtUyzOOAFk5gvbAO52QmUKPKk7GmsIEORo= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 h1:j9yeqTWEFrtimt8Nng2MIeRrpoCvQzM9/g25XTvqUGg=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.0-20241127180247-a33202765966.1/go.mod h1:AxRT+qTj5PJCz2nyQzsR/qxAcveW5USRhJTt/edTO5w= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= buf.build/go/protovalidate v1.1.0 h1:pQqEQRpOo4SqS60qkvmhLTTQU9JwzEvdyiqAtXa5SeY=
cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= buf.build/go/protovalidate v1.1.0/go.mod h1:bGZcPiAQDC3ErCHK3t74jSoJDFOs2JH3d7LWuTEIdss=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/Azure/go-amqp v1.3.0 h1://1rikYhoIQNXJFXyoO/Rlb4+4EkHYfJceNtLlys2/4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/Azure/go-amqp v1.3.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-amqp v1.5.1 h1:WyiPTz2C3zVvDL7RLAqwWdeoYhMtX62MZzQoP09fzsU=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-amqp v1.5.1/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/bufbuild/protovalidate-go v0.8.0 h1:Xs3kCLCJ4tQiogJ0iOXm+ClKw/KviW3nLAryCGW2I3Y= github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
github.com/bufbuild/protovalidate-go v0.8.0/go.mod h1:JPWZInGm2y2NBg3vKDKdDIkvDjyLv31J3hLH5GIFc/Q= github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
@ -32,215 +40,188 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/cel-go v0.22.1 h1:AfVXx3chM2qwoSbM7Da8g8hX8OVSkBFwX+rz2+PcK40= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/cel-go v0.22.1/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5/Jo= github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA=
github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo= github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo= github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw=
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=

View file

@ -2,72 +2,27 @@ package api
import ( import (
"context" "context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/telemetry"
"errors" "errors"
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/google/uuid" "github.com/google/uuid"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalTelemetry "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/telemetry"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
) )
// ContentTypeCloudEventsProtobuf the cloudevents protobuf content-type sent in metadata of messages var TopicNamePattern = regexp.MustCompile(`^topic://stackit-platform/t/swz/audit-log/(?:conway|eu01|eu02|sx-stoi01)/[Vv][1-9](?:\.\d)?/[A-Za-z0-9-]+/[A-Za-z0-9-/]+`)
const ContentTypeCloudEventsProtobuf = "application/cloudevents+protobuf"
const ContentTypeCloudEventsJson = "application/cloudevents+json; charset=UTF-8"
// ErrAttributeIdentifierInvalid indicates that the object identifier func ValidateAndSerializePartially(
// and the identifier in the checked attribute do not match validator pkgAuditCommon.ProtobufValidator,
var ErrAttributeIdentifierInvalid = errors.New("attribute identifier invalid")
// ErrAttributeTypeInvalid indicates that an invalid type has been provided.
var ErrAttributeTypeInvalid = errors.New("attribute type invalid")
// ErrCloudEventNil states that the given cloud event is nil
var ErrCloudEventNil = errors.New("cloud event nil")
// ErrEventNil indicates that the event was nil
var ErrEventNil = errors.New("event is nil")
// ErrInvalidRoutableIdentifierForSystemEvent states that the routable identifier is not valid for a system event
var ErrInvalidRoutableIdentifierForSystemEvent = errors.New("invalid identifier for system event")
// ErrMessagingApiNil states that the messaging api is nil
var ErrMessagingApiNil = errors.New("messaging api nil")
// ErrObjectIdentifierNil indicates that the object identifier was nil
var ErrObjectIdentifierNil = errors.New("object identifier is nil")
// ErrObjectIdentifierVisibilityMismatch indicates that a reference mismatch was detected.
//
// Valid combinations are:
// * Visibility: Public, ObjectIdentifier: <type>
// * Visibility: Private, ObjectIdentifier: <type | system>
var ErrObjectIdentifierVisibilityMismatch = errors.New("object reference visibility mismatch")
// ErrTopicNameResolverNil states that the topic name resolve is nil
var ErrTopicNameResolverNil = errors.New("topic name resolver nil")
// ErrUnknownObjectType indicates that the given input is an unknown object type
var ErrUnknownObjectType = errors.New("unknown object type")
// ErrUnsupportedEventTypeDataAccess states that the event type "data-access" is currently not supported
var ErrUnsupportedEventTypeDataAccess = errors.New("unsupported event type data access")
// ErrUnsupportedObjectIdentifierType indicates that an unsupported object identifier type has been provided
var ErrUnsupportedObjectIdentifierType = errors.New("unsupported object identifier type")
// ErrUnsupportedRoutableType indicates that the given input is an unsupported routable type
var ErrUnsupportedRoutableType = errors.New("unsupported routable type")
func validateAndSerializePartially(
validator ProtobufValidator,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*auditV1.RoutableAuditEvent, error) { ) (*auditV1.RoutableAuditEvent, error) {
// Check preconditions // Check preconditions
@ -86,10 +41,10 @@ func validateAndSerializePartially(
} }
func newValidatedRoutableAuditEvent( func newValidatedRoutableAuditEvent(
validator ProtobufValidator, validator pkgAuditCommon.ProtobufValidator,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier) (*auditV1.RoutableAuditEvent, error) { routableIdentifier *pkgAuditCommon.RoutableIdentifier) (*auditV1.RoutableAuditEvent, error) {
// Test serialization even if the data is dropped later when logging to the legacy solution // Test serialization even if the data is dropped later when logging to the legacy solution
auditEventBytes, err := proto.Marshal(event) auditEventBytes, err := proto.Marshal(event)
@ -117,18 +72,18 @@ func newValidatedRoutableAuditEvent(
} }
func validateAuditLogEntry( func validateAuditLogEntry(
validator ProtobufValidator, validator pkgAuditCommon.ProtobufValidator,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error { ) error {
// Return error if the given event or object identifier is nil // Return error if the given event or object identifier is nil
if event == nil { if event == nil {
return ErrEventNil return pkgAuditCommon.ErrEventNil
} }
if routableIdentifier == nil { if routableIdentifier == nil {
return ErrObjectIdentifierNil return pkgAuditCommon.ErrObjectIdentifierNil
} }
// Validate the actual event // Validate the actual event
@ -139,21 +94,21 @@ func validateAuditLogEntry(
// Ensure that a valid object identifier is set if the event is public // Ensure that a valid object identifier is set if the event is public
if isSystemIdentifier(routableIdentifier) && visibility == auditV1.Visibility_VISIBILITY_PUBLIC { if isSystemIdentifier(routableIdentifier) && visibility == auditV1.Visibility_VISIBILITY_PUBLIC {
return ErrObjectIdentifierVisibilityMismatch return pkgAuditCommon.ErrObjectIdentifierVisibilityMismatch
} }
// Check that provided identifier type is supported // Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil { if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, ErrUnknownObjectType) { if errors.Is(err, pkgAuditCommon.ErrUnknownObjectType) {
return ErrUnsupportedRoutableType return pkgAuditCommon.ErrUnsupportedRoutableType
} }
return err return err
} }
// Check identifier consistency across event attributes // Check identifier consistency across event attributes
if strings.HasSuffix(event.LogName, string(EventTypeSystemEvent)) { if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeSystemEvent)) {
if !(routableIdentifier.Identifier == SystemIdentifier.Identifier && routableIdentifier.Type == ObjectTypeSystem) { if routableIdentifier.Identifier != pkgAuditCommon.SystemIdentifier.Identifier || routableIdentifier.Type != pkgAuditCommon.ObjectTypeSystem {
return ErrInvalidRoutableIdentifierForSystemEvent return pkgAuditCommon.ErrInvalidRoutableIdentifierForSystemEvent
} }
// The resource name can either contain the system identifier or another resource identifier // The resource name can either contain the system identifier or another resource identifier
} else { } else {
@ -168,32 +123,32 @@ func validateAuditLogEntry(
} }
// Send implements AuditApi.Send // Send implements AuditApi.Send
func send( func Send(
topicNameResolver TopicNameResolver, topicNameResolver pkgAuditCommon.TopicNameResolver,
messagingApi messaging.Api, messagingApi pkgMessagingApi.Api,
ctx context.Context, ctx context.Context,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *CloudEvent, cloudEvent *pkgAuditCommon.CloudEvent,
) error { ) error {
// Check that given objects are not nil // Check that given objects are not nil
if topicNameResolver == nil { if topicNameResolver == nil {
return ErrTopicNameResolverNil return pkgAuditCommon.ErrTopicNameResolverNil
} }
if messagingApi == nil { if messagingApi == nil {
return ErrMessagingApiNil return pkgAuditCommon.ErrMessagingApiNil
} }
if cloudEvent == nil { if cloudEvent == nil {
return ErrCloudEventNil return pkgAuditCommon.ErrCloudEventNil
} }
if routableIdentifier == nil { if routableIdentifier == nil {
return ErrObjectIdentifierNil return pkgAuditCommon.ErrObjectIdentifierNil
} }
// Check that provided identifier type is supported // Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil { if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, ErrUnknownObjectType) { if errors.Is(err, pkgAuditCommon.ErrUnknownObjectType) {
return ErrUnsupportedRoutableType return pkgAuditCommon.ErrUnsupportedRoutableType
} }
return err return err
} }
@ -222,15 +177,15 @@ func send(
// Telemetry // Telemetry
applicationAttributes["cloudEvents:sdklanguage"] = "go" applicationAttributes["cloudEvents:sdklanguage"] = "go"
auditGoVersion := telemetry.AuditGoVersion auditGoVersion := internalTelemetry.AuditGoVersion
if auditGoVersion != "" { if auditGoVersion != "" {
applicationAttributes["cloudEvents:sdkversion"] = auditGoVersion applicationAttributes["cloudEvents:sdkversion"] = auditGoVersion
} }
auditGoGrpcVersion := telemetry.AuditGoGrpcVersion auditGoGrpcVersion := internalTelemetry.AuditGoGrpcVersion
if auditGoGrpcVersion != "" { if auditGoGrpcVersion != "" {
applicationAttributes["cloudEvents:sdkgrpcversion"] = auditGoGrpcVersion applicationAttributes["cloudEvents:sdkgrpcversion"] = auditGoGrpcVersion
} }
auditGoHttpVersion := telemetry.AuditGoHttpVersion auditGoHttpVersion := internalTelemetry.AuditGoHttpVersion
if auditGoHttpVersion != "" { if auditGoHttpVersion != "" {
applicationAttributes["cloudEvents:sdkhttpversion"] = auditGoHttpVersion applicationAttributes["cloudEvents:sdkhttpversion"] = auditGoHttpVersion
} }
@ -243,16 +198,16 @@ func send(
applicationAttributes) applicationAttributes)
} }
func isSystemIdentifier(identifier *RoutableIdentifier) bool { func isSystemIdentifier(identifier *pkgAuditCommon.RoutableIdentifier) bool {
if identifier.Identifier == uuid.Nil.String() && identifier.Type == ObjectTypeSystem { if identifier.Identifier == uuid.Nil.String() && identifier.Type == pkgAuditCommon.ObjectTypeSystem {
return true return true
} }
return false return false
} }
func areIdentifiersIdentical(routableIdentifier *RoutableIdentifier, logName string) error { func areIdentifiersIdentical(routableIdentifier *pkgAuditCommon.RoutableIdentifier, logName string) error {
dataType, identifier := getTypeAndIdentifierFromString(logName) dataType, identifier := getTypeAndIdentifierFromString(logName)
objectType := ObjectTypeFromPluralString(dataType) objectType := pkgAuditCommon.ObjectTypeFromPluralString(dataType)
err := objectType.IsSupportedType() err := objectType.IsSupportedType()
if err != nil { if err != nil {
return err return err
@ -260,12 +215,12 @@ func areIdentifiersIdentical(routableIdentifier *RoutableIdentifier, logName str
return areTypeAndIdentifierIdentical(routableIdentifier, objectType, identifier) return areTypeAndIdentifierIdentical(routableIdentifier, objectType, identifier)
} }
func areTypeAndIdentifierIdentical(routableIdentifier *RoutableIdentifier, dataType ObjectType, identifier string) error { func areTypeAndIdentifierIdentical(routableIdentifier *pkgAuditCommon.RoutableIdentifier, dataType pkgAuditCommon.ObjectType, identifier string) error {
if routableIdentifier.Identifier != identifier { if routableIdentifier.Identifier != identifier {
return ErrAttributeIdentifierInvalid return pkgAuditCommon.ErrAttributeIdentifierInvalid
} }
if routableIdentifier.Type != dataType { if routableIdentifier.Type != dataType {
return ErrAttributeTypeInvalid return pkgAuditCommon.ErrAttributeTypeInvalid
} }
return nil return nil
} }

View file

@ -0,0 +1,477 @@
package api
import (
"context"
"errors"
"fmt"
"strings"
"testing"
"time"
"buf.build/go/protovalidate"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
)
type MessagingApiMock struct {
mock.Mock
}
func (m *MessagingApiMock) Send(
ctx context.Context,
topic string,
data []byte,
contentType string,
applicationProperties map[string]any,
) error {
args := m.Called(ctx, topic, data, contentType, applicationProperties)
return args.Error(0)
}
func (m *MessagingApiMock) Close(_ context.Context) error {
return nil
}
type TopicNameResolverMock struct {
mock.Mock
}
func (m *TopicNameResolverMock) Resolve(routableIdentifier *pkgAuditCommon.RoutableIdentifier) (string, error) {
args := m.Called(routableIdentifier)
return args.String(0), args.Error(1)
}
func NewValidator(t *testing.T) pkgAuditCommon.ProtobufValidator {
validator, err := protovalidate.New()
var protoValidator pkgAuditCommon.ProtobufValidator = validator
assert.NoError(t, err)
return protoValidator
}
func Test_ValidateAndSerializePartially_EventNil(t *testing.T) {
validator := NewValidator(t)
_, err := ValidateAndSerializePartially(
validator, nil, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
}
func Test_ValidateAndSerializePartially_AuditEventValidationFailed(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := NewOrganizationAuditEvent(nil)
event.LogName = ""
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.EqualError(t, err, "validation error: log_name: value is required")
}
func Test_ValidateAndSerializePartially_RoutableEventValidationFailed(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := NewOrganizationAuditEvent(nil)
_, err := ValidateAndSerializePartially(validator, event, 3, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.EqualError(t, err, "validation error: visibility: value must be one of the defined enum values")
}
func Test_ValidateAndSerializePartially_CheckVisibility_Event(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := NewOrganizationAuditEvent(nil)
t.Run("Visibility public - object identifier nil", func(t *testing.T) {
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
})
t.Run("Visibility private - object identifier nil", func(t *testing.T) {
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil)
assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
})
t.Run("Visibility public - object identifier system", func(t *testing.T) {
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierVisibilityMismatch)
})
t.Run("Visibility public - object identifier set", func(t *testing.T) {
routableEvent, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
t.Run("Visibility private - object identifier system", func(t *testing.T) {
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeIdentifierInvalid)
})
t.Run("Visibility private - object identifier set", func(t *testing.T) {
routableEvent, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
}
func Test_ValidateAndSerializePartially_CheckVisibility_SystemEvent(t *testing.T) {
validator := NewValidator(t)
event := NewSystemAuditEvent(nil)
t.Run("Visibility public - object identifier nil", func(t *testing.T) {
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
})
t.Run("Visibility private - object identifier nil", func(t *testing.T) {
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil)
assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
})
t.Run("Visibility public - object identifier system", func(t *testing.T) {
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierVisibilityMismatch)
})
t.Run("Visibility public - object identifier set", func(t *testing.T) {
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(pkgAuditCommon.ObjectTypeOrganization)}))
assert.ErrorIs(t, err, pkgAuditCommon.ErrInvalidRoutableIdentifierForSystemEvent)
})
t.Run("Visibility private - object identifier system", func(t *testing.T) {
routableEvent, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.RoutableSystemIdentifier)
assert.NoError(t, err)
assert.NotNil(t, routableEvent)
})
t.Run("Visibility private - object identifier set", func(t *testing.T) {
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.NewRoutableIdentifier(
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(pkgAuditCommon.ObjectTypeOrganization)}))
assert.ErrorIs(t, err, pkgAuditCommon.ErrInvalidRoutableIdentifierForSystemEvent)
})
}
func Test_ValidateAndSerializePartially_UnsupportedIdentifierType(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := NewFolderAuditEvent(nil)
objectIdentifier.Type = "invalid"
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
}
func Test_ValidateAndSerializePartially_LogNameIdentifierMismatch(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := NewFolderAuditEvent(nil)
parts := strings.Split(event.LogName, "/")
identifier := parts[1]
t.Run("LogName type mismatch", func(t *testing.T) {
event.LogName = fmt.Sprintf("projects/%s/logs/admin-activity", identifier)
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeTypeInvalid)
})
t.Run("LogName identifier mismatch", func(t *testing.T) {
event.LogName = fmt.Sprintf("folders/%s/logs/admin-activity", uuid.NewString())
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeIdentifierInvalid)
})
}
func Test_ValidateAndSerializePartially_ResourceNameIdentifierMismatch(t *testing.T) {
validator := NewValidator(t)
event, objectIdentifier := NewFolderAuditEvent(nil)
parts := strings.Split(event.ProtoPayload.ResourceName, "/")
identifier := parts[1]
t.Run("ResourceName type mismatch", func(t *testing.T) {
event.ProtoPayload.ResourceName = fmt.Sprintf("projects/%s", identifier)
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeTypeInvalid)
})
t.Run("ResourceName identifier mismatch", func(t *testing.T) {
event.ProtoPayload.ResourceName = fmt.Sprintf("folders/%s", uuid.NewString())
_, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeIdentifierInvalid)
})
}
func Test_ValidateAndSerializePartially_SystemEvent(t *testing.T) {
validator := NewValidator(t)
event := NewSystemAuditEvent(nil)
routableEvent, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.RoutableSystemIdentifier)
assert.NoError(t, err)
assert.Equal(t, event.LogName, fmt.Sprintf("system/%s/logs/%s", pkgAuditCommon.SystemIdentifier.Identifier, pkgAuditCommon.EventTypeSystemEvent))
assert.True(t, proto.Equal(routableEvent.ObjectIdentifier, pkgAuditCommon.SystemIdentifier))
}
func Test_Send_TopicNameResolverNil(t *testing.T) {
err := Send(nil, nil, context.Background(), nil, nil)
assert.ErrorIs(t, err, pkgAuditCommon.ErrTopicNameResolverNil)
}
func Test_Send_TopicNameResolutionError(t *testing.T) {
expectedError := errors.New("expected error")
topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", expectedError)
var topicNameResolver pkgAuditCommon.TopicNameResolver = &topicNameResolverMock
var cloudEvent = pkgAuditCommon.CloudEvent{}
var messagingApi pkgMessagingApi.Api = &pkgMessagingApi.AmqpApi{}
err := Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.RoutableSystemIdentifier, &cloudEvent)
assert.ErrorIs(t, err, expectedError)
}
func Test_Send_MessagingApiNil(t *testing.T) {
var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: "test"}
err := Send(topicNameResolver, nil, context.Background(), nil, nil)
assert.ErrorIs(t, err, pkgAuditCommon.ErrMessagingApiNil)
}
func Test_Send_CloudEventNil(t *testing.T) {
var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: "test"}
var messagingApi pkgMessagingApi.Api = &pkgMessagingApi.AmqpApi{}
err := Send(topicNameResolver, messagingApi, context.Background(), nil, nil)
assert.ErrorIs(t, err, pkgAuditCommon.ErrCloudEventNil)
}
func Test_Send_ObjectIdentifierNil(t *testing.T) {
var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: "test"}
var messagingApi pkgMessagingApi.Api = &pkgMessagingApi.AmqpApi{}
var cloudEvent = pkgAuditCommon.CloudEvent{}
err := Send(topicNameResolver, messagingApi, context.Background(), nil, &cloudEvent)
assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
}
func Test_Send_UnsupportedObjectIdentifierType(t *testing.T) {
var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: "test"}
var messagingApi pkgMessagingApi.Api = &pkgMessagingApi.AmqpApi{}
var cloudEvent = pkgAuditCommon.CloudEvent{}
var objectIdentifier = auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: "unsupported"}
err := Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.NewRoutableIdentifier(&objectIdentifier), &cloudEvent)
assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
}
func Test_Send(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
var topicNameResolver pkgAuditCommon.TopicNameResolver = &topicNameResolverMock
messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi pkgMessagingApi.Api = &messagingApiMock
var cloudEvent = pkgAuditCommon.CloudEvent{}
assert.NoError(t, Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.RoutableSystemIdentifier, &cloudEvent))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
}
func Test_SendAllHeadersSet(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
var topicNameResolver pkgAuditCommon.TopicNameResolver = &topicNameResolverMock
messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi pkgMessagingApi.Api = &messagingApiMock
traceState := "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
traceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
expectedTime := time.Now()
var cloudEvent = pkgAuditCommon.CloudEvent{
SpecVersion: "1.0",
Source: "resourcemanager",
Id: "id",
Time: expectedTime,
DataContentType: pkgAuditCommon.ContentTypeCloudEventsProtobuf,
DataType: "type",
Subject: "subject",
TraceParent: &traceParent,
TraceState: &traceState,
}
assert.NoError(t, Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.RoutableSystemIdentifier, &cloudEvent))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
arguments := messagingApiMock.Calls[0].Arguments
topic := arguments.Get(1).(string)
assert.Equal(t, "topic", topic)
contentType := arguments.Get(3).(string)
assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, contentType)
applicationProperties := arguments.Get(4).(map[string]any)
assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"])
assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"])
assert.Equal(t, "id", applicationProperties["cloudEvents:id"])
assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "type", applicationProperties["cloudEvents:type"])
assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"])
assert.Equal(t, traceParent, applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, traceState, applicationProperties["cloudEvents:tracestate"])
messagingApiMock.AssertExpectations(t)
}
func Test_SendWithoutOptionalHeadersSet(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
var topicNameResolver pkgAuditCommon.TopicNameResolver = &topicNameResolverMock
messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi pkgMessagingApi.Api = &messagingApiMock
expectedTime := time.Now()
var cloudEvent = pkgAuditCommon.CloudEvent{
SpecVersion: "1.0",
Source: "resourcemanager",
Id: "id",
Time: expectedTime,
DataContentType: pkgAuditCommon.ContentTypeCloudEventsProtobuf,
DataType: "type",
Subject: "subject",
}
assert.NoError(t, Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.RoutableSystemIdentifier, &cloudEvent))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
arguments := messagingApiMock.Calls[0].Arguments
topic := arguments.Get(1).(string)
assert.Equal(t, "topic", topic)
contentType := arguments.Get(3).(string)
assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, contentType)
applicationProperties := arguments.Get(4).(map[string]any)
assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"])
assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"])
assert.Equal(t, "id", applicationProperties["cloudEvents:id"])
assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "type", applicationProperties["cloudEvents:type"])
assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"])
assert.Equal(t, nil, applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, nil, applicationProperties["cloudEvents:tracestate"])
messagingApiMock.AssertExpectations(t)
}
func Test_ValidateTopicNames(t *testing.T) {
t.Run("conway", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/conway/v1.0/service-name/events"
assert.True(t, TopicNamePattern.MatchString(topicName))
})
t.Run("eu01", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1.0/service-name/events"
assert.True(t, TopicNamePattern.MatchString(topicName))
})
t.Run("eu02", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/eu02/v1.0/service-name/events"
assert.True(t, TopicNamePattern.MatchString(topicName))
})
t.Run("sx-stoi01", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/sx-stoi01/v1.0/service-name/events"
assert.True(t, TopicNamePattern.MatchString(topicName))
})
t.Run("version without decimals", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/service-name/events"
assert.True(t, TopicNamePattern.MatchString(topicName))
})
t.Run("version as uppercase", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/V1.0/service-name/events"
assert.True(t, TopicNamePattern.MatchString(topicName))
})
t.Run("service name without dash", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1.0/service/events"
assert.True(t, TopicNamePattern.MatchString(topicName))
})
t.Run("multiple additional parts", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1.0/service-name/multiple/additional/parts"
assert.True(t, TopicNamePattern.MatchString(topicName))
})
t.Run("additional parts with dash", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1.0/service-name/multiple-additional/parts"
assert.True(t, TopicNamePattern.MatchString(topicName))
})
t.Run("topic prefix missing", func(t *testing.T) {
topicName := "stackit-platform/t/swz/audit-log/eu01/v1.0/service-name/events"
assert.False(t, TopicNamePattern.MatchString(topicName))
})
t.Run("invalid region", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/invalid/v1.0/service-name/events"
assert.False(t, TopicNamePattern.MatchString(topicName))
})
t.Run("additional parts missing", func(t *testing.T) {
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1.0/service-name"
assert.False(t, TopicNamePattern.MatchString(topicName))
})
}

View file

@ -8,29 +8,32 @@ import (
"strings" "strings"
"time" "time"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
var ErrUnsupportedSeverity = errors.New("unsupported severity level") var ErrUnsupportedSeverity = errors.New("unsupported severity level")
// convertAndSerializeIntoLegacyFormat converts the protobuf events into the json serialized legacy audit log format // ConvertAndSerializeIntoLegacyFormat converts the protobuf events into the json serialized legacy audit log format
func convertAndSerializeIntoLegacyFormat( func ConvertAndSerializeIntoLegacyFormat(
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
routableEvent *auditV1.RoutableAuditEvent, routableEvent *auditV1.RoutableAuditEvent,
) ([]byte, error) { ) ([]byte, error) {
// Event type // Event type
var eventType string var eventType string
if strings.HasSuffix(event.LogName, string(EventTypeAdminActivity)) { switch {
case strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity)):
eventType = "ADMIN_ACTIVITY" eventType = "ADMIN_ACTIVITY"
} else if strings.HasSuffix(event.LogName, string(EventTypeSystemEvent)) { case strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeSystemEvent)):
eventType = "SYSTEM_EVENT" eventType = "SYSTEM_EVENT"
} else if strings.HasSuffix(event.LogName, string(EventTypePolicyDenied)) { case strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypePolicyDenied)):
eventType = "POLICY_DENIED" eventType = "POLICY_DENIED"
} else if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) { case strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)):
return nil, ErrUnsupportedEventTypeDataAccess return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
} else { default:
return nil, errors.New("unsupported event type") return nil, errors.New("unsupported event type")
} }
@ -46,7 +49,7 @@ func convertAndSerializeIntoLegacyFormat(
} }
// Principals // Principals
var serviceAccountDelegationInfo *LegacyAuditEventServiceAccountDelegationInfo = nil var serviceAccountDelegationInfo *LegacyAuditEventServiceAccountDelegationInfo
if len(event.ProtoPayload.AuthenticationInfo.ServiceAccountDelegationInfo) > 0 { if len(event.ProtoPayload.AuthenticationInfo.ServiceAccountDelegationInfo) > 0 {
var principals []LegacyAuditEventPrincipal var principals []LegacyAuditEventPrincipal
for _, principal := range event.ProtoPayload.AuthenticationInfo.ServiceAccountDelegationInfo { for _, principal := range event.ProtoPayload.AuthenticationInfo.ServiceAccountDelegationInfo {
@ -73,7 +76,7 @@ func convertAndSerializeIntoLegacyFormat(
Endpoint: "none", Endpoint: "none",
} }
} else { } else {
var parameters map[string]interface{} = nil var parameters map[string]interface{}
if event.ProtoPayload.RequestMetadata.RequestAttributes.Path != "" && if event.ProtoPayload.RequestMetadata.RequestAttributes.Path != "" &&
event.ProtoPayload.RequestMetadata.RequestAttributes.Query != nil && event.ProtoPayload.RequestMetadata.RequestAttributes.Query != nil &&
*event.ProtoPayload.RequestMetadata.RequestAttributes.Query != "" { *event.ProtoPayload.RequestMetadata.RequestAttributes.Query != "" {
@ -95,11 +98,11 @@ func convertAndSerializeIntoLegacyFormat(
} }
} }
var body map[string]interface{} = nil var body map[string]interface{}
if event.ProtoPayload.Request != nil { if event.ProtoPayload.Request != nil {
body = event.ProtoPayload.Request.AsMap() body = event.ProtoPayload.Request.AsMap()
} }
var headers map[string]interface{} = nil var headers map[string]interface{}
if event.ProtoPayload.RequestMetadata.RequestAttributes.Headers != nil { if event.ProtoPayload.RequestMetadata.RequestAttributes.Headers != nil {
headers = map[string]interface{}{} headers = map[string]interface{}{}
for key, value := range event.ProtoPayload.RequestMetadata.RequestAttributes.Headers { for key, value := range event.ProtoPayload.RequestMetadata.RequestAttributes.Headers {
@ -116,34 +119,34 @@ func convertAndSerializeIntoLegacyFormat(
} }
if routableEvent.ObjectIdentifier == nil { if routableEvent.ObjectIdentifier == nil {
return nil, ErrObjectIdentifierNil return nil, pkgAuditCommon.ErrObjectIdentifierNil
} }
// Context and event type // Context and event type
var messageContext *LegacyAuditEventContext var messageContext *LegacyAuditEventContext
switch routableEvent.ObjectIdentifier.Type { switch routableEvent.ObjectIdentifier.Type {
case string(ObjectTypeProject): case string(pkgAuditCommon.ObjectTypeProject):
messageContext = &LegacyAuditEventContext{ messageContext = &LegacyAuditEventContext{
OrganizationId: nil, OrganizationId: nil,
FolderId: nil, FolderId: nil,
ProjectId: &routableEvent.ObjectIdentifier.Identifier, ProjectId: &routableEvent.ObjectIdentifier.Identifier,
} }
case string(ObjectTypeFolder): case string(pkgAuditCommon.ObjectTypeFolder):
messageContext = &LegacyAuditEventContext{ messageContext = &LegacyAuditEventContext{
OrganizationId: nil, OrganizationId: nil,
FolderId: &routableEvent.ObjectIdentifier.Identifier, FolderId: &routableEvent.ObjectIdentifier.Identifier,
ProjectId: nil, ProjectId: nil,
} }
case string(ObjectTypeOrganization): case string(pkgAuditCommon.ObjectTypeOrganization):
messageContext = &LegacyAuditEventContext{ messageContext = &LegacyAuditEventContext{
OrganizationId: &routableEvent.ObjectIdentifier.Identifier, OrganizationId: &routableEvent.ObjectIdentifier.Identifier,
FolderId: nil, FolderId: nil,
ProjectId: nil, ProjectId: nil,
} }
case string(ObjectTypeSystem): case string(pkgAuditCommon.ObjectTypeSystem):
messageContext = nil messageContext = nil
default: default:
return nil, ErrUnsupportedObjectIdentifierType return nil, pkgAuditCommon.ErrUnsupportedObjectIdentifierType
} }
var visibility string var visibility string
@ -152,6 +155,8 @@ func convertAndSerializeIntoLegacyFormat(
visibility = "PUBLIC" visibility = "PUBLIC"
case auditV1.Visibility_VISIBILITY_PRIVATE: case auditV1.Visibility_VISIBILITY_PRIVATE:
visibility = "PRIVATE" visibility = "PRIVATE"
case auditV1.Visibility_VISIBILITY_UNSPECIFIED:
visibility = ""
} }
// Details // Details
@ -171,23 +176,16 @@ func convertAndSerializeIntoLegacyFormat(
// Severity // Severity
var severity string var severity string
switch event.Severity { switch event.Severity {
case auditV1.LogSeverity_LOG_SEVERITY_DEFAULT: case auditV1.LogSeverity_LOG_SEVERITY_DEFAULT,
fallthrough auditV1.LogSeverity_LOG_SEVERITY_DEBUG,
case auditV1.LogSeverity_LOG_SEVERITY_DEBUG: auditV1.LogSeverity_LOG_SEVERITY_INFO,
fallthrough auditV1.LogSeverity_LOG_SEVERITY_NOTICE,
case auditV1.LogSeverity_LOG_SEVERITY_INFO: auditV1.LogSeverity_LOG_SEVERITY_WARNING:
fallthrough
case auditV1.LogSeverity_LOG_SEVERITY_NOTICE:
fallthrough
case auditV1.LogSeverity_LOG_SEVERITY_WARNING:
severity = "INFO" severity = "INFO"
case auditV1.LogSeverity_LOG_SEVERITY_ERROR: case auditV1.LogSeverity_LOG_SEVERITY_ERROR,
fallthrough auditV1.LogSeverity_LOG_SEVERITY_CRITICAL,
case auditV1.LogSeverity_LOG_SEVERITY_CRITICAL: auditV1.LogSeverity_LOG_SEVERITY_ALERT,
fallthrough auditV1.LogSeverity_LOG_SEVERITY_EMERGENCY:
case auditV1.LogSeverity_LOG_SEVERITY_ALERT:
fallthrough
case auditV1.LogSeverity_LOG_SEVERITY_EMERGENCY:
severity = "ERROR" severity = "ERROR"
default: default:
return nil, ErrUnsupportedSeverity return nil, ErrUnsupportedSeverity
@ -204,7 +202,7 @@ func convertAndSerializeIntoLegacyFormat(
UserAgent: userAgent, UserAgent: userAgent,
Initiator: LegacyAuditEventPrincipal{ Initiator: LegacyAuditEventPrincipal{
Id: event.ProtoPayload.AuthenticationInfo.PrincipalId, Id: event.ProtoPayload.AuthenticationInfo.PrincipalId,
Email: &event.ProtoPayload.AuthenticationInfo.PrincipalEmail, Email: event.ProtoPayload.AuthenticationInfo.PrincipalEmail,
}, },
ServiceAccountDelegationInfo: serviceAccountDelegationInfo, ServiceAccountDelegationInfo: serviceAccountDelegationInfo,
Request: request, Request: request,

View file

@ -3,12 +3,14 @@ package api
import ( import (
"testing" "testing"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
func Test_ConvertAndSerializeIntoLegacyFormat_NoObjectIdentifier(t *testing.T) { func Test_ConvertAndSerializeIntoLegacyFormat_NoObjectIdentifier(t *testing.T) {
event, _ := newProjectAuditEvent(nil) event, _ := NewProjectAuditEvent(nil)
routableEvent := auditV1.RoutableAuditEvent{ routableEvent := auditV1.RoutableAuditEvent{
OperationName: event.ProtoPayload.OperationName, OperationName: event.ProtoPayload.OperationName,
Visibility: auditV1.Visibility_VISIBILITY_PUBLIC, Visibility: auditV1.Visibility_VISIBILITY_PUBLIC,
@ -16,6 +18,6 @@ func Test_ConvertAndSerializeIntoLegacyFormat_NoObjectIdentifier(t *testing.T) {
Data: nil, Data: nil,
} }
_, err := convertAndSerializeIntoLegacyFormat(event, &routableEvent) _, err := ConvertAndSerializeIntoLegacyFormat(event, &routableEvent)
assert.ErrorIs(t, err, ErrObjectIdentifierNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
} }

View file

@ -2,23 +2,21 @@ package api
import ( import (
"context" "context"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/google/uuid"
"github.com/lestrrat-go/jwx/v2/jwt"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
"net"
"net/url" "net/url"
"regexp"
"slices" "slices"
"strings" "strings"
"time" "time"
"github.com/lestrrat-go/jwx/v2/jwt"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
const EmailAddressDoNotReplyAtStackItDotCloud = "do-not-reply@stackit.cloud" const EmailAddressDoNotReplyAtStackItDotCloud = "do-not-reply@stackit.cloud"
@ -31,66 +29,6 @@ var ErrInvalidAuthorizationHeaderValue = errors.New("invalid authorization heade
var ErrInvalidBearerToken = errors.New("invalid bearer token") var ErrInvalidBearerToken = errors.New("invalid bearer token")
var ErrTokenIsNotBearerToken = errors.New("token is not a bearer token") var ErrTokenIsNotBearerToken = errors.New("token is not a bearer token")
var objectTypeIdPattern, _ = regexp.Compile(".*/(projects|folders|organizations)/([0-9a-fA-F-]{36})(?:/.*)?")
type ApiRequest struct {
// Body
//
// Required: false
Body *[]byte
// The (HTTP) request headers / gRPC metadata.
//
// Internal IP-Addresses have to be removed (e.g. in x-forwarded-xxx headers).
//
// Required: true
Header map[string][]string
// The HTTP request `Host` header value.
//
// Required: true
Host string
// Method
//
// Required: true
Method string
// The URL scheme, such as `http`, `https` or `gRPC`.
//
// Required: true
Scheme string
// The network protocol used with the request, such as "http/1.1",
// "spdy/3", "h2", "h2c", "webrtc", "tcp", "udp", "quic". See
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
// for details.
//
// Required: true
Proto string
// The url
//
// Required: true
URL RequestUrl
}
type RequestUrl struct {
// The gRPC / HTTP URL path.
//
// Required: true
Path string
// The HTTP URL query in the format of "name1=value1&name2=value2", as it
// appears in the first line of the HTTP request.
// The input should be escaped to not contain any special characters.
//
// Required: false
RawQuery *string
}
// AuditRequest bundles request related parameters // AuditRequest bundles request related parameters
type AuditRequest struct { type AuditRequest struct {
@ -100,7 +38,7 @@ type AuditRequest struct {
// It should never include user-generated data, such as file contents. // It should never include user-generated data, such as file contents.
// //
// Required: true // Required: true
Request *ApiRequest Request *pkgAuditCommon.ApiRequest
// The IP address of the caller. // The IP address of the caller.
// For caller from internet, this will be public IPv4 or IPv6 address. // For caller from internet, this will be public IPv4 or IPv6 address.
@ -145,7 +83,7 @@ type AuditResponse struct {
// elsewhere in the log record. // elsewhere in the log record.
// //
// Required: false // Required: false
ResponseBodyBytes *[]byte ResponseBodyBytes []byte
// The http or gRPC status code. // The http or gRPC status code.
// //
@ -294,7 +232,7 @@ func NewAuditLogEntry(
auditResponse AuditResponse, auditResponse AuditResponse,
// Optional map that is added as "details" to the message // Optional map that is added as "details" to the message
eventMetadata *map[string]interface{}, eventMetadata map[string]interface{},
// Required metadata // Required metadata
auditMetadata AuditMetadata, auditMetadata AuditMetadata,
@ -309,9 +247,9 @@ func NewAuditLogEntry(
if err != nil { if err != nil {
return nil, errors.Join(err, ErrInvalidResponse) return nil, errors.Join(err, ErrInvalidResponse)
} }
var responseLength *int64 = nil var responseLength *int64
if responseBody != nil { if responseBody != nil {
length := int64(len(*auditResponse.ResponseBodyBytes)) length := int64(len(auditResponse.ResponseBodyBytes))
responseLength = &length responseLength = &length
} }
@ -332,7 +270,7 @@ func NewAuditLogEntry(
scheme := auditRequest.Request.Scheme scheme := auditRequest.Request.Scheme
// Initialize authorization info if available // Initialize authorization info if available
var authorizationInfo []*auditV1.AuthorizationInfo = nil var authorizationInfo []*auditV1.AuthorizationInfo
if auditMetadata.AuditPermission != nil && auditMetadata.AuditPermissionGranted != nil { if auditMetadata.AuditPermission != nil && auditMetadata.AuditPermissionGranted != nil {
authorizationInfo = []*auditV1.AuthorizationInfo{ authorizationInfo = []*auditV1.AuthorizationInfo{
NewAuthorizationInfo( NewAuthorizationInfo(
@ -342,15 +280,15 @@ func NewAuditLogEntry(
} }
// Initialize labels if available // Initialize labels if available
var labels map[string]string = nil var labels map[string]string
if auditMetadata.AuditLabels != nil { if auditMetadata.AuditLabels != nil {
labels = *auditMetadata.AuditLabels labels = *auditMetadata.AuditLabels
} }
// Initialize metadata/details // Initialize metadata/details
var metadata *structpb.Struct = nil var metadata *structpb.Struct
if eventMetadata != nil { if eventMetadata != nil {
metadataStruct, err := structpb.NewStruct(*eventMetadata) metadataStruct, err := structpb.NewStruct(eventMetadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -409,24 +347,6 @@ func NewAuditLogEntry(
return &event, nil return &event, nil
} }
// GetCalledServiceNameFromRequest extracts the called service name from subdomain name
func GetCalledServiceNameFromRequest(request *ApiRequest, fallbackName string) string {
if request == nil {
return fallbackName
}
var calledServiceName = fallbackName
host := request.Host
ip := net.ParseIP(host)
if ip == nil && !strings.Contains(host, "localhost") {
dotIdx := strings.Index(host, ".")
if dotIdx != -1 {
calledServiceName = host[0:dotIdx]
}
}
return calledServiceName
}
// NewPbInt64Value returns protobuf int64 wrapper if value is not nil. // NewPbInt64Value returns protobuf int64 wrapper if value is not nil.
func NewPbInt64Value(value *int64) *wrapperspb.Int64Value { func NewPbInt64Value(value *int64) *wrapperspb.Int64Value {
if value != nil { if value != nil {
@ -437,7 +357,7 @@ func NewPbInt64Value(value *int64) *wrapperspb.Int64Value {
// NewRequestMetadata returns initialized protobuf RequestMetadata object. // NewRequestMetadata returns initialized protobuf RequestMetadata object.
func NewRequestMetadata( func NewRequestMetadata(
request *ApiRequest, request *pkgAuditCommon.ApiRequest,
requestHeaders map[string]string, requestHeaders map[string]string,
requestId *string, requestId *string,
requestScheme string, requestScheme string,
@ -469,7 +389,7 @@ func NewRequestMetadata(
// NewRequestAttributes returns initialized protobuf AttributeContext_Request object. // NewRequestAttributes returns initialized protobuf AttributeContext_Request object.
func NewRequestAttributes( func NewRequestAttributes(
request *ApiRequest, request *pkgAuditCommon.ApiRequest,
requestHeaders map[string]string, requestHeaders map[string]string,
requestId *string, requestId *string,
requestScheme string, requestScheme string,
@ -480,7 +400,7 @@ func NewRequestAttributes(
) *auditV1.AttributeContext_Request { ) *auditV1.AttributeContext_Request {
rawQuery := request.URL.RawQuery rawQuery := request.URL.RawQuery
var query *string = nil var query *string
if rawQuery != nil && *rawQuery != "" { if rawQuery != nil && *rawQuery != "" {
escapedQuery := url.QueryEscape(*rawQuery) escapedQuery := url.QueryEscape(*rawQuery)
query = &escapedQuery query = &escapedQuery
@ -488,7 +408,7 @@ func NewRequestAttributes(
return &auditV1.AttributeContext_Request{ return &auditV1.AttributeContext_Request{
Id: requestId, Id: requestId,
Method: StringToHttpMethod(request.Method), Method: pkgAuditCommon.StringToHttpMethod(request.Method),
Headers: requestHeaders, Headers: requestHeaders,
Path: request.URL.Path, Path: request.URL.Path,
Host: request.Host, Host: request.Host,
@ -505,7 +425,7 @@ func NewRequestAttributes(
} }
// NewAuthorizationInfo returns protobuf AuthorizationInfo for the given parameters. // NewAuthorizationInfo returns protobuf AuthorizationInfo for the given parameters.
func NewAuthorizationInfo(resourceName string, permission string, granted bool) *auditV1.AuthorizationInfo { func NewAuthorizationInfo(resourceName, permission string, granted bool) *auditV1.AuthorizationInfo {
return &auditV1.AuthorizationInfo{ return &auditV1.AuthorizationInfo{
Resource: resourceName, Resource: resourceName,
Permission: &permission, Permission: &permission,
@ -514,14 +434,14 @@ func NewAuthorizationInfo(resourceName string, permission string, granted bool)
} }
// NewInsertId returns a correctly formatted insert id. // NewInsertId returns a correctly formatted insert id.
func NewInsertId(insertTime time.Time, location string, workerId string, eventSequenceNumber uint64) string { func NewInsertId(insertTime time.Time, location, workerId string, eventSequenceNumber uint64) string {
return fmt.Sprintf("%d/%s/%s/%d", insertTime.UnixNano(), location, workerId, eventSequenceNumber) return fmt.Sprintf("%d/%s/%s/%d", insertTime.UnixNano(), location, workerId, eventSequenceNumber)
} }
// NewResponseMetadata returns protobuf response status with status code and short message. // NewResponseMetadata returns protobuf response status with status code and short message.
func NewResponseMetadata(statusCode int, numResponseItems *int64, responseSize *int64, headers map[string]string, responseTime time.Time) *auditV1.ResponseMetadata { func NewResponseMetadata(statusCode int, numResponseItems, responseSize *int64, headers map[string]string, responseTime time.Time) *auditV1.ResponseMetadata {
var message *string = nil var message *string
if statusCode >= 400 && statusCode < 500 { if statusCode >= 400 && statusCode < 500 {
text := "Client error" text := "Client error"
message = &text message = &text
@ -530,7 +450,7 @@ func NewResponseMetadata(statusCode int, numResponseItems *int64, responseSize *
message = &text message = &text
} }
var size *wrapperspb.Int64Value = nil var size *wrapperspb.Int64Value
if responseSize != nil { if responseSize != nil {
size = wrapperspb.Int64(*responseSize) size = wrapperspb.Int64(*responseSize)
} }
@ -548,26 +468,26 @@ func NewResponseMetadata(statusCode int, numResponseItems *int64, responseSize *
} }
// NewResponseBody converts the JSON byte response into a protobuf struct. // NewResponseBody converts the JSON byte response into a protobuf struct.
func NewResponseBody(response *[]byte) (*structpb.Struct, error) { func NewResponseBody(response []byte) (*structpb.Struct, error) {
// Return if nil // Return if nil
if response == nil || len(*response) == 0 { if len(response) == 0 {
return nil, nil return nil, nil
} }
// Convert to protobuf struct // Convert to protobuf struct
return byteArrayToPbStruct(*response) return byteArrayToPbStruct(response)
} }
// NewRequestBody converts the request body into a protobuf struct. // NewRequestBody converts the request body into a protobuf struct.
func NewRequestBody(request *ApiRequest) (*structpb.Struct, error) { func NewRequestBody(request *pkgAuditCommon.ApiRequest) (*structpb.Struct, error) {
if request.Body == nil || len(*request.Body) == 0 { if len(request.Body) == 0 {
return nil, nil return nil, nil
} }
// Convert to protobuf struct // Convert to protobuf struct
return byteArrayToPbStruct(*request.Body) return byteArrayToPbStruct(request.Body)
} }
// byteArrayToPbStruct converts a given json byte array into a protobuf struct. // byteArrayToPbStruct converts a given json byte array into a protobuf struct.
@ -582,12 +502,12 @@ func byteArrayToPbStruct(bytes []byte) (*structpb.Struct, error) {
} }
// FilterAndMergeHeaders filters ":authority", "Authorization", "B3" and "Host" headers as well as // FilterAndMergeHeaders filters ":authority", "Authorization", "B3" and "Host" headers as well as
// all headers starting with the prefixes "X-" and "STACKIT-". // all headers starting with the prefixes "X-", "STACKIT-" and "grpcgateway-".
// Headers are merged if there is more than one value for a given name. // Headers are merged if there is more than one value for a given name.
func FilterAndMergeHeaders(headers map[string][]string) map[string]string { func FilterAndMergeHeaders(headers map[string][]string) map[string]string {
var resultMap = make(map[string]string) var resultMap = make(map[string]string)
skipHeaders := []string{":authority", "authorization", "b3", "host"} skipHeaders := []string{":authority", "authorization", "b3", "host"}
skipPrefixHeaders := []string{"x-", "stackit-"} skipPrefixHeaders := []string{"x-", "stackit-", "grpcgateway-"}
if len(headers) == 0 { if len(headers) == 0 {
return nil return nil
@ -616,8 +536,8 @@ func FilterAndMergeHeaders(headers map[string][]string) map[string]string {
// NewAuditRoutingIdentifier instantiates a new auditApi.RoutableIdentifier for // NewAuditRoutingIdentifier instantiates a new auditApi.RoutableIdentifier for
// the given object ID and object type. // the given object ID and object type.
func NewAuditRoutingIdentifier(objectId string, objectType ObjectType) *RoutableIdentifier { func NewAuditRoutingIdentifier(objectId string, objectType pkgAuditCommon.ObjectType) *pkgAuditCommon.RoutableIdentifier {
return &RoutableIdentifier{ return &pkgAuditCommon.RoutableIdentifier{
Identifier: objectId, Identifier: objectId,
Type: objectType, Type: objectType,
} }
@ -628,7 +548,7 @@ func NewAuditRoutingIdentifier(objectId string, objectType ObjectType) *Routable
// - authenticationPrincipal - principal identifier // - authenticationPrincipal - principal identifier
// - audiences - list of audience claims // - audiences - list of audience claims
// - authenticationInfo - information about the user or service-account authentication // - authenticationInfo - information about the user or service-account authentication
func AuditAttributesFromAuthorizationHeader(request *ApiRequest) ( func AuditAttributesFromAuthorizationHeader(request *pkgAuditCommon.ApiRequest) (
*structpb.Struct, *structpb.Struct,
string, string,
[]string, []string,
@ -636,14 +556,17 @@ func AuditAttributesFromAuthorizationHeader(request *ApiRequest) (
error, error,
) { ) {
var principalId = "none"
var principalEmail = EmailAddressDoNotReplyAtStackItDotCloud
emptyClaims, _ := structpb.NewStruct(make(map[string]interface{}))
var auditClaims = emptyClaims
var authenticationPrincipal = "none/none" var authenticationPrincipal = "none/none"
var serviceAccountName *string = nil var principalId = "none"
var principalEmail *string
emptyClaims, err := structpb.NewStruct(make(map[string]interface{}))
if err != nil {
return nil, authenticationPrincipal, nil, nil, err
}
var auditClaims = emptyClaims
var serviceAccountName *string
audiences := make([]string, 0) audiences := make([]string, 0)
var delegationInfo []*auditV1.ServiceAccountDelegationInfo = nil var delegationInfo []*auditV1.ServiceAccountDelegationInfo
authorizationHeaders := request.Header["Authorization"] authorizationHeaders := request.Header["Authorization"]
if len(authorizationHeaders) == 0 { if len(authorizationHeaders) == 0 {
@ -652,7 +575,7 @@ func AuditAttributesFromAuthorizationHeader(request *ApiRequest) (
} }
authorizationHeader := strings.Join(authorizationHeaders, ",") authorizationHeader := strings.Join(authorizationHeaders, ",")
trimmedAuthorizationHeader := strings.TrimSpace(authorizationHeader) trimmedAuthorizationHeader := strings.TrimSpace(authorizationHeader)
if len(trimmedAuthorizationHeader) > 0 { if trimmedAuthorizationHeader != "" {
// Parse claims // Parse claims
token, err := parseToken(trimmedAuthorizationHeader) token, err := parseToken(trimmedAuthorizationHeader)
@ -712,9 +635,8 @@ func getTokenClaim(token jwt.Token, claimName string) *string {
if claimExists { if claimExists {
claimString := fmt.Sprintf("%s", claim) claimString := fmt.Sprintf("%s", claim)
return &claimString return &claimString
} else {
return nil
} }
return nil
} }
func extractAuthenticationPrincipal(token jwt.Token) string { func extractAuthenticationPrincipal(token jwt.Token) string {
@ -792,9 +714,8 @@ func extractServiceAccountDelegationInfoDetails(actClaims map[string]interface{}
nestedDelegations := extractServiceAccountDelegationInfo(actClaims) nestedDelegations := extractServiceAccountDelegationInfo(actClaims)
if len(nestedDelegations) > 0 { if len(nestedDelegations) > 0 {
return append(delegations, nestedDelegations...) return append(delegations, nestedDelegations...)
} else {
return delegations
} }
return delegations
} }
func extractServiceAccountDelegationInfo(claims map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo { func extractServiceAccountDelegationInfo(claims map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo {
@ -820,144 +741,15 @@ func extractSubjectAndEmailFromActClaims(actClaim map[string]interface{}) (strin
return principalId, principalEmail return principalId, principalEmail
} }
func extractSubjectAndEmail(token jwt.Token) (string, string) { func extractSubjectAndEmail(token jwt.Token) (string, *string) {
var principalEmail string var principalEmail *string
principalId := token.Subject() principalId := token.Subject()
emailClaim, hasEmail := token.Get("email") emailClaim, hasEmail := token.Get("email")
if !hasEmail { if hasEmail {
principalEmail = EmailAddressDoNotReplyAtStackItDotCloud trimmedEmail := strings.TrimSpace(fmt.Sprintf("%s", emailClaim))
} else { if trimmedEmail != "" {
principalEmail = fmt.Sprintf("%s", emailClaim) principalEmail = &trimmedEmail
}
} }
return principalId, principalEmail return principalId, principalEmail
} }
// OperationNameFromUrlPath converts the request url path into an operation name.
// UUIDs and query parameters are filtered out, slashes replaced by dots.
// HTTP methods are added as suffix as follows:
// - POST - create
// - PUT - update
// - PATCH - update
// - DELETE - delete
// - others - read
func OperationNameFromUrlPath(path string, requestMethod string) string {
queryIdx := strings.Index(path, "?")
if queryIdx != -1 {
path = path[:queryIdx]
}
path = strings.TrimPrefix(path, "/")
path = strings.TrimSuffix(path, "/")
split := strings.Split(path, "/")
operation := ""
for _, part := range split {
// skip uuids in path
_, err := uuid.Parse(part)
if err == nil {
continue
}
operation = fmt.Sprintf("%s/%s", operation, part)
}
operation = strings.ReplaceAll(operation, "/", ".")
operation = strings.TrimPrefix(operation, ".")
operation = strings.ToLower(operation)
if len(operation) > 0 {
method := StringToHttpMethod(requestMethod)
var action string
switch method {
case auditV1.AttributeContext_HTTP_METHOD_PUT:
fallthrough
case auditV1.AttributeContext_HTTP_METHOD_PATCH:
action = "update"
case auditV1.AttributeContext_HTTP_METHOD_POST:
action = "create"
case auditV1.AttributeContext_HTTP_METHOD_DELETE:
action = "delete"
default:
action = "read"
}
operation = fmt.Sprintf("%s.%s", operation, action)
}
return operation
}
// OperationNameFromGrpcMethod converts the grpc path into an operation name.
func OperationNameFromGrpcMethod(path string) string {
operation := strings.TrimPrefix(path, "/")
operation = strings.TrimSuffix(operation, "/")
operation = strings.ReplaceAll(operation, "/", ".")
operation = strings.TrimPrefix(operation, ".")
operation = strings.ToLower(operation)
return operation
}
func GetObjectIdAndTypeFromUrlPath(path string) (
string,
*ObjectType,
error,
) {
// Extract object id and type from request url
objectTypeIdMatches := objectTypeIdPattern.FindStringSubmatch(path)
if len(objectTypeIdMatches) > 0 {
objectType := ObjectTypeFromPluralString(objectTypeIdMatches[1])
err := objectType.IsSupportedType()
if err != nil {
return "", nil, err
}
objectId := objectTypeIdMatches[2]
return objectId, &objectType, nil
}
return "", nil, nil
}
func ToArrayMap(input map[string]string) map[string][]string {
output := map[string][]string{}
for key, value := range input {
output[key] = []string{value}
}
return output
}
func StringAttributeFromMetadata(metadata map[string][]string, name string) string {
var value = ""
rawValue, hasAttribute := metadata[name]
if hasAttribute && len(rawValue) > 0 {
value = rawValue[0]
}
return value
}
// ResponseBodyToBytes converts a JSON or Protobuf response into a byte array
func ResponseBodyToBytes(response any) (*[]byte, error) {
if response == nil {
return nil, nil
}
responseBytes, isBytes := response.([]byte)
if isBytes {
return &responseBytes, nil
}
responseProtoMessage, isProtoMessage := response.(proto.Message)
if isProtoMessage {
responseJson, err := protojson.Marshal(responseProtoMessage)
if err != nil {
return nil, err
}
return &responseJson, nil
} else {
responseJson, err := json.Marshal(response)
if err != nil {
return nil, err
}
return &responseJson, nil
}
}

View file

@ -1,61 +1,23 @@
package api package api
import ( import (
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
"time" "time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
func Test_GetCalledServiceNameFromRequest(t *testing.T) {
t.Run("request is nil", func(t *testing.T) {
serviceName := GetCalledServiceNameFromRequest(nil, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
})
t.Run("localhost", func(t *testing.T) {
request := ApiRequest{Host: "localhost:8080"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
})
t.Run("cf", func(t *testing.T) {
request := ApiRequest{Host: "stackit-resource-manager-go-dev.apps.01.cf.eu01.stackit.cloud"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "stackit-resource-manager-go-dev", serviceName)
})
t.Run("cf invalid host", func(t *testing.T) {
request := ApiRequest{Host: ""}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
})
t.Run("ip", func(t *testing.T) {
request := ApiRequest{Host: "127.0.0.1"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
},
)
t.Run("ip short", func(t *testing.T) {
request := ApiRequest{Host: "::1"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
},
)
}
func Test_NewPbInt64Value(t *testing.T) { func Test_NewPbInt64Value(t *testing.T) {
t.Run("nil", func(t *testing.T) { t.Run("nil", func(t *testing.T) {
@ -123,9 +85,9 @@ func Test_NewRequestMetadata(t *testing.T) {
requestHeaders["Custom"] = []string{"customHeader"} requestHeaders["Custom"] = []string{"customHeader"}
queryString := "topic=project" queryString := "topic=project"
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Scheme: "http", Scheme: "http",
@ -187,9 +149,9 @@ func Test_NewRequestMetadata(t *testing.T) {
}) })
t.Run("without query parameters", func(t *testing.T) { t.Run("without query parameters", func(t *testing.T) {
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new"}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new"},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -213,9 +175,9 @@ func Test_NewRequestMetadata(t *testing.T) {
t.Run("with empty query parameters", func(t *testing.T) { t.Run("with empty query parameters", func(t *testing.T) {
emptyQuery := "" emptyQuery := ""
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -238,9 +200,9 @@ func Test_NewRequestMetadata(t *testing.T) {
}) })
t.Run("without request id", func(t *testing.T) { t.Run("without request id", func(t *testing.T) {
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -262,9 +224,9 @@ func Test_NewRequestMetadata(t *testing.T) {
t.Run("various default http methods", func(t *testing.T) { t.Run("various default http methods", func(t *testing.T) {
httpMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"} httpMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"}
for _, httpMethod := range httpMethods { for _, httpMethod := range httpMethods {
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: httpMethod, Method: httpMethod,
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -286,9 +248,9 @@ func Test_NewRequestMetadata(t *testing.T) {
}) })
t.Run("unknown http method", func(t *testing.T) { t.Run("unknown http method", func(t *testing.T) {
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "", Method: "",
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -331,6 +293,7 @@ func Test_FilterAndMergeRequestHeaders(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["X-Forwarded-Proto"] = []string{"https"} headers["X-Forwarded-Proto"] = []string{"https"}
headers["Stackit-test"] = []string{"test"} headers["Stackit-test"] = []string{"test"}
headers["grpcgateway-authorization"] = []string{userToken}
filteredHeaders := FilterAndMergeHeaders(headers) filteredHeaders := FilterAndMergeHeaders(headers)
assert.Equal(t, 0, len(filteredHeaders)) assert.Equal(t, 0, len(filteredHeaders))
@ -376,7 +339,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Basic username:password" headerValue := "Basic username:password"
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue} headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrTokenIsNotBearerToken) assert.ErrorIs(t, err, ErrTokenIsNotBearerToken)
@ -386,7 +349,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "a b c" headerValue := "a b c"
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue} headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidAuthorizationHeaderValue) assert.ErrorIs(t, err, ErrInvalidAuthorizationHeaderValue)
@ -396,7 +359,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Bearer a.b.c.d" headerValue := "Bearer a.b.c.d"
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue} headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidBearerToken) assert.ErrorIs(t, err, ErrInvalidBearerToken)
@ -406,7 +369,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Bearer a.b.c" headerValue := "Bearer a.b.c"
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue} headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidBearerToken) assert.ErrorIs(t, err, ErrInvalidBearerToken)
@ -415,7 +378,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("client credentials token", func(t *testing.T) { t.Run("client credentials token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{clientCredentialsToken} headers["Authorization"] = []string{clientCredentialsToken}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -441,7 +404,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"stackit-resource-manager-dev"}, audiences) assert.Equal(t, []string{"stackit-resource-manager-dev"}, audiences)
assert.Equal(t, "stackit-resource-manager-dev", authenticationInfo.PrincipalId) assert.Equal(t, "stackit-resource-manager-dev", authenticationInfo.PrincipalId)
assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail) assert.Nil(t, authenticationInfo.PrincipalEmail)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
@ -450,7 +413,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("service account access token", func(t *testing.T) { t.Run("service account access token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountToken} headers["Authorization"] = []string{serviceAccountToken}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -479,7 +442,47 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"stackit", "api"}, audiences) assert.Equal(t, []string{"stackit", "api"}, audiences)
assert.Equal(t, "10f38b01-534b-47bb-a03a-e294ca2be4de", authenticationInfo.PrincipalId) assert.Equal(t, "10f38b01-534b-47bb-a03a-e294ca2be4de", authenticationInfo.PrincipalId)
assert.Equal(t, "my-service-yifc9e1@sa.stackit.cloud", authenticationInfo.PrincipalEmail) assert.Equal(t, "my-service-yifc9e1@sa.stackit.cloud", *authenticationInfo.PrincipalEmail)
assert.Equal(t,
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/10f38b01-534b-47bb-a03a-e294ca2be4de",
*authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
})
t.Run("service account access token with underscore in subject", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountTokenUnderscoreSubject}
request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Len(t, auditClaimsMap, 12)
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"])
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", auditClaimsMap["azp"])
assert.Equal(t, "my-service-yifc9e1@sa.stackit.cloud", auditClaimsMap["email"])
assert.Equal(t, "2024-08-03 07:15:43 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-02 07:15:43 +0000 UTC", auditClaimsMap["iat"])
assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"])
assert.Equal(t, "84c30a46-1001-436f-859f-89c0ba19be1e", auditClaimsMap["jti"])
assert.Equal(t, "api", auditClaimsMap["stackit/serviceaccount/namespace"])
assert.Equal(t, "10f38b01-534b-47bb-a03a-e294ca2be4de", auditClaimsMap[TokenClaimStackitServiceAccountId])
assert.Equal(t, "legacy", auditClaimsMap["stackit/serviceaccount/token.source"])
assert.Equal(t, "dacc7830-843e-4c5e-86ff-aa0fb51d636f", auditClaimsMap[TokenClaimStackitProjectId])
assert.Equal(t, "10f38b01_534b_47bb_a03a_e294ca2be4de", auditClaimsMap["sub"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("10f38b01_534b_47bb_a03a_e294ca2be4de"),
url.QueryEscape("stackit/serviceaccount"))
assert.Equal(t, principal, authenticationPrincipal)
assert.Equal(t, []string{"stackit", "api"}, audiences)
assert.Equal(t, "10f38b01_534b_47bb_a03a_e294ca2be4de", authenticationInfo.PrincipalId)
assert.Equal(t, "my-service-yifc9e1@sa.stackit.cloud", *authenticationInfo.PrincipalEmail)
assert.Equal(t, assert.Equal(t,
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/10f38b01-534b-47bb-a03a-e294ca2be4de", "projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/10f38b01-534b-47bb-a03a-e294ca2be4de",
@ -490,7 +493,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("impersonated token of access token", func(t *testing.T) { t.Run("impersonated token of access token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountTokenImpersonated} headers["Authorization"] = []string{serviceAccountTokenImpersonated}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -523,7 +526,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"stackit", "api"}, audiences) assert.Equal(t, []string{"stackit", "api"}, audiences)
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", authenticationInfo.PrincipalId) assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", authenticationInfo.PrincipalId)
assert.Equal(t, "service-account-2-tj9srt1@sa.stackit.cloud", authenticationInfo.PrincipalEmail) assert.Equal(t, "service-account-2-tj9srt1@sa.stackit.cloud", *authenticationInfo.PrincipalEmail)
assert.Equal(t, assert.Equal(t,
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/f45009b2-6433-43c1-b6c7-618c44359e71", "projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/f45009b2-6433-43c1-b6c7-618c44359e71",
@ -538,7 +541,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("impersonated token of impersonated access token", func(t *testing.T) { t.Run("impersonated token of impersonated access token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountTokenRepeatedlyImpersonated} headers["Authorization"] = []string{serviceAccountTokenRepeatedlyImpersonated}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -574,7 +577,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"stackit", "api"}, audiences) assert.Equal(t, []string{"stackit", "api"}, audiences)
assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", authenticationInfo.PrincipalId) assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", authenticationInfo.PrincipalId)
assert.Equal(t, "service-account-3-fghsxw1@sa.stackit.cloud", authenticationInfo.PrincipalEmail) assert.Equal(t, "service-account-3-fghsxw1@sa.stackit.cloud", *authenticationInfo.PrincipalEmail)
assert.Equal(t, assert.Equal(t,
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/1734b4b6-1d5e-4819-9b50-29917a1b9ad5", "projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/1734b4b6-1d5e-4819-9b50-29917a1b9ad5",
@ -591,7 +594,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("user token", func(t *testing.T) { t.Run("user token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{userToken} headers["Authorization"] = []string{userToken}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -619,7 +622,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"stackit-portal-login-dev-client-id"}, audiences) assert.Equal(t, []string{"stackit-portal-login-dev-client-id"}, audiences)
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", *authenticationInfo.PrincipalEmail)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
@ -628,7 +631,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("user token with simple aud claim", func(t *testing.T) { t.Run("user token with simple aud claim", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{userTokenWithSimpleAudience} headers["Authorization"] = []string{userTokenWithSimpleAudience}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -654,7 +657,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"https://stackit-service-account-dev.apps.01.cf.eu01.stackit.cloud"}, audiences) assert.Equal(t, []string{"https://stackit-service-account-dev.apps.01.cf.eu01.stackit.cloud"}, audiences)
assert.Equal(t, "5e426aed-c487-4c48-af25-87f69cf9cdd4", authenticationInfo.PrincipalId) assert.Equal(t, "5e426aed-c487-4c48-af25-87f69cf9cdd4", authenticationInfo.PrincipalId)
assert.Equal(t, "Lukas.Schmitt@stackit.cloud", authenticationInfo.PrincipalEmail) assert.Equal(t, "Lukas.Schmitt@stackit.cloud", *authenticationInfo.PrincipalEmail)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
@ -670,9 +673,9 @@ func Test_NewAuditLogEntry(t *testing.T) {
requestHeaders["User-Agent"] = []string{userAgent} requestHeaders["User-Agent"] = []string{userAgent}
requestHeaders["Custom"] = []string{"customHeader"} requestHeaders["Custom"] = []string{"customHeader"}
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new"}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new"},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Scheme: "http", Scheme: "http",
@ -699,7 +702,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
} }
objectId := uuid.NewString() objectId := uuid.NewString()
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity) logName := fmt.Sprintf("projects/%s/logs/%s", objectId, pkgAuditCommon.EventTypeAdminActivity)
serviceName := "resource-manager" serviceName := "resource-manager"
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName) operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
resourceName := fmt.Sprintf("projects/%s", objectId) resourceName := fmt.Sprintf("projects/%s", objectId)
@ -753,7 +756,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
authenticationInfo := payload.AuthenticationInfo authenticationInfo := payload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", *authenticationInfo.PrincipalEmail)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
@ -779,14 +782,14 @@ func Test_NewAuditLogEntry(t *testing.T) {
requestBody["key"] = "request" requestBody["key"] = "request"
requestBodyBytes, _ := json.Marshal(requestBody) requestBodyBytes, _ := json.Marshal(requestBody)
query := "topic=project" query := "topic=project"
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &query}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &query},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Scheme: "http", Scheme: "http",
Header: requestHeaders, Header: requestHeaders,
Body: &requestBodyBytes, Body: requestBodyBytes,
} }
clientIp := "127.0.0.1" clientIp := "127.0.0.1"
@ -813,7 +816,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
responseTime := time.Now().UTC() responseTime := time.Now().UTC()
auditResponse := AuditResponse{ auditResponse := AuditResponse{
ResponseBodyBytes: &responseBody, ResponseBodyBytes: responseBody,
ResponseStatusCode: responseStatusCode, ResponseStatusCode: responseStatusCode,
ResponseHeaders: responseHeader, ResponseHeaders: responseHeader,
ResponseNumItems: &responseNumItems, ResponseNumItems: &responseNumItems,
@ -823,7 +826,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
auditTime := time.Now().UTC() auditTime := time.Now().UTC()
objectId := uuid.NewString() objectId := uuid.NewString()
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity) logName := fmt.Sprintf("projects/%s/logs/%s", objectId, pkgAuditCommon.EventTypeAdminActivity)
serviceName := "resource-manager" serviceName := "resource-manager"
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName) operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
resourceName := fmt.Sprintf("projects/%s", objectId) resourceName := fmt.Sprintf("projects/%s", objectId)
@ -852,7 +855,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
logEntry, _ := NewAuditLogEntry( logEntry, _ := NewAuditLogEntry(
auditRequest, auditRequest,
auditResponse, auditResponse,
&eventMetadata, eventMetadata,
auditMetadata) auditMetadata)
assert.Equal(t, logName, logEntry.LogName) assert.Equal(t, logName, logEntry.LogName)
@ -886,7 +889,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
authenticationInfo := payload.AuthenticationInfo authenticationInfo := payload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", *authenticationInfo.PrincipalEmail)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
@ -920,231 +923,9 @@ func Test_NewInsertId(t *testing.T) {
func Test_NewNewAuditRoutingIdentifier(t *testing.T) { func Test_NewNewAuditRoutingIdentifier(t *testing.T) {
objectId := uuid.NewString() objectId := uuid.NewString()
objectType := ObjectTypeProject objectType := pkgAuditCommon.ObjectTypeProject
routingIdentifier := NewAuditRoutingIdentifier(objectId, objectType) routingIdentifier := NewAuditRoutingIdentifier(objectId, objectType)
assert.Equal(t, objectId, routingIdentifier.Identifier) assert.Equal(t, objectId, routingIdentifier.Identifier)
assert.Equal(t, objectType, routingIdentifier.Type) assert.Equal(t, objectType, routingIdentifier.Type)
} }
func Test_OperationNameFromUrlPath(t *testing.T) {
t.Run("empty path", func(t *testing.T) {
operationName := OperationNameFromUrlPath("", "GET")
assert.Equal(t, "", operationName)
})
t.Run("root path", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/", "GET")
assert.Equal(t, "", operationName)
})
t.Run("path without version", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("path with uuid without version", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("path with uuid and version", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/v2/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET")
assert.Equal(t, "v2.projects.read", operationName)
})
t.Run("concatenated path", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/folders/167fc176-9d8e-477b-a56c-b50d7b26adcf/projects/0a2a4f9b-4e67-4562-ad02-c2d200e05aa6/audit/policy", "GET")
assert.Equal(t, "v2.organizations.folders.projects.audit.policy.read", operationName)
})
t.Run("path with query params", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/audit/policy?since=2024-08-27", "GET")
assert.Equal(t, "v2.organizations.audit.policy.read", operationName)
})
t.Run("path trailing slash", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("path trailing slash and query params", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/?changeDate=2024-10-13", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("http method post", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "POST")
assert.Equal(t, "projects.create", operationName)
})
t.Run("http method put", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "PUT")
assert.Equal(t, "projects.update", operationName)
})
t.Run("http method patch", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "PATCH")
assert.Equal(t, "projects.update", operationName)
})
t.Run("http method delete", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "DELETE")
assert.Equal(t, "projects.delete", operationName)
})
t.Run("operation name fallback on options", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "OPTIONS")
assert.Equal(t, "projects.read", operationName)
})
t.Run("operation name fallback on unknown", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "UNKNOWN")
assert.Equal(t, "projects.read", operationName)
})
}
func Test_OperationNameFromGrpcMethod(t *testing.T) {
t.Run("empty path", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("")
assert.Equal(t, "", operationName)
})
t.Run("root path", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("/")
assert.Equal(t, "", operationName)
})
t.Run("path without version", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("/example.ExampleService/ManualAuditEvent")
assert.Equal(t, "example.exampleservice.manualauditevent", operationName)
})
t.Run("path with version", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("/example.v1.ExampleService/ManualAuditEvent")
assert.Equal(t, "example.v1.exampleservice.manualauditevent", operationName)
})
t.Run("path trailing slash", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("/example.v1.ExampleService/ManualAuditEvent/")
assert.Equal(t, "example.v1.exampleservice.manualauditevent", operationName)
})
}
func Test_GetObjectIdAndTypeFromUrlPath(t *testing.T) {
t.Run("object id and type not in url", func(t *testing.T) {
objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/projects/audit")
assert.NoError(t, err)
assert.Equal(t, "", objectId)
assert.Nil(t, objectType)
})
t.Run("object id and type in url", func(t *testing.T) {
objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/projects/f17d4064-9b65-4334-b6a7-8fed96340124")
assert.NoError(t, err)
assert.Equal(t, "f17d4064-9b65-4334-b6a7-8fed96340124", objectId)
assert.Equal(t, ObjectTypeProject, *objectType)
})
t.Run("multiple object ids and types in url", func(t *testing.T) {
objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/organization/8ee58bec-d496-4bb9-af8d-72fda4d78b6b/projects/f17d4064-9b65-4334-b6a7-8fed96340124")
assert.NoError(t, err)
assert.Equal(t, "f17d4064-9b65-4334-b6a7-8fed96340124", objectId)
assert.Equal(t, ObjectTypeProject, *objectType)
})
}
func Test_ToArrayMap(t *testing.T) {
t.Run("empty map", func(t *testing.T) {
result := ToArrayMap(map[string]string{})
assert.Equal(t, map[string][]string{}, result)
})
t.Run("empty map", func(t *testing.T) {
result := ToArrayMap(map[string]string{"key1": "value1", "key2": "value2"})
assert.Equal(t, map[string][]string{
"key1": {"value1"},
"key2": {"value2"},
}, result)
})
}
func Test_StringAttributeFromMetadata(t *testing.T) {
metadata := map[string][]string{"key1": {"value1"}, "key2": {"value2"}}
t.Run("not found", func(t *testing.T) {
attribute := StringAttributeFromMetadata(metadata, "key3")
assert.Equal(t, "", attribute)
})
t.Run("found", func(t *testing.T) {
attribute := StringAttributeFromMetadata(metadata, "key2")
assert.Equal(t, "value2", attribute)
})
}
func Test_ResponseBodyToBytes(t *testing.T) {
t.Run(
"nil response body", func(t *testing.T) {
bytes, err := ResponseBodyToBytes(nil)
assert.Nil(t, bytes)
assert.Nil(t, err)
},
)
t.Run(
"bytes", func(t *testing.T) {
responseBody := []byte("data")
bytes, err := ResponseBodyToBytes(responseBody)
assert.Nil(t, err)
assert.Equal(t, &responseBody, bytes)
},
)
t.Run(
"Protobuf message", func(t *testing.T) {
protobufMessage := auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(ObjectTypeProject)}
bytes, err := ResponseBodyToBytes(&protobufMessage)
assert.Nil(t, err)
expected, err := protojson.Marshal(&protobufMessage)
assert.Nil(t, err)
assert.Equal(t, &expected, bytes)
},
)
t.Run(
"struct", func(t *testing.T) {
type CustomObject struct {
Value string
}
responseBody := CustomObject{Value: "data"}
bytes, err := ResponseBodyToBytes(responseBody)
assert.Nil(t, err)
expected, err := json.Marshal(responseBody)
assert.Nil(t, err)
assert.Equal(t, &expected, bytes)
},
)
t.Run(
"map", func(t *testing.T) {
responseBody := map[string]interface{}{"value": "data"}
bytes, err := ResponseBodyToBytes(responseBody)
assert.Nil(t, err)
expected, err := json.Marshal(responseBody)
assert.Nil(t, err)
assert.Equal(t, &expected, bytes)
},
)
}

View file

@ -1,10 +1,13 @@
package api package api
import ( import (
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"
"testing" "testing"
"buf.build/go/protovalidate"
"github.com/stretchr/testify/assert"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
func Test_RoutableAuditEvent(t *testing.T) { func Test_RoutableAuditEvent(t *testing.T) {
@ -18,7 +21,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
Visibility: auditV1.Visibility_VISIBILITY_PUBLIC, Visibility: auditV1.Visibility_VISIBILITY_PUBLIC,
ObjectIdentifier: &auditV1.ObjectIdentifier{ ObjectIdentifier: &auditV1.ObjectIdentifier{
Identifier: "14f7aa86-77ba-4d77-a091-a2cf3395a221", Identifier: "14f7aa86-77ba-4d77-a091-a2cf3395a221",
Type: string(ObjectTypeProject), Type: string(pkgAuditCommon.ObjectTypeProject),
}, },
Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &auditV1.UnencryptedData{ Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &auditV1.UnencryptedData{
Data: []byte("data"), Data: []byte("data"),
@ -37,7 +40,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.OperationName = "" event.OperationName = ""
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - operation_name: value is required [required]") assert.EqualError(t, err, "validation error: operation_name: value is required")
}) })
t.Run("invalid operation name", func(t *testing.T) { t.Run("invalid operation name", func(t *testing.T) {
@ -45,7 +48,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.OperationName = "stackit.resource-manager.v1.INVALID.organizations.create" event.OperationName = "stackit.resource-manager.v1.INVALID.organizations.create"
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - operation_name: value does not match regex pattern `^stackit\\.[a-z0-9-]+\\.(?:v[0-9]+\\.)?(?:[a-z0-9-.]+\\.)?[a-z0-9-]+$` [string.pattern]") assert.EqualError(t, err, "validation error: operation_name: value does not match regex pattern `^stackit\\.[a-z0-9-]+\\.(?:v[0-9]+\\.)?(?:[a-z0-9-.]+\\.)?[a-z0-9-]+$`")
}) })
t.Run("visibility invalid", func(t *testing.T) { t.Run("visibility invalid", func(t *testing.T) {
@ -53,7 +56,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.Visibility = -1 event.Visibility = -1
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - visibility: value must be one of the defined enum values [enum.defined_only]") assert.EqualError(t, err, "validation error: visibility: value must be one of the defined enum values")
}) })
t.Run("visibility unspecified", func(t *testing.T) { t.Run("visibility unspecified", func(t *testing.T) {
@ -61,7 +64,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.Visibility = auditV1.Visibility_VISIBILITY_UNSPECIFIED event.Visibility = auditV1.Visibility_VISIBILITY_UNSPECIFIED
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - visibility: value is required [required]") assert.EqualError(t, err, "validation error: visibility: value is required")
}) })
t.Run("object identifier nil", func(t *testing.T) { t.Run("object identifier nil", func(t *testing.T) {
@ -69,7 +72,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.ObjectIdentifier = nil event.ObjectIdentifier = nil
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - object_identifier: value is required [required]") assert.EqualError(t, err, "validation error: object_identifier: value is required")
}) })
t.Run("object identifier id empty", func(t *testing.T) { t.Run("object identifier id empty", func(t *testing.T) {
@ -77,7 +80,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.ObjectIdentifier.Identifier = "" event.ObjectIdentifier.Identifier = ""
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - object_identifier.identifier: value is required [required]") assert.EqualError(t, err, "validation error: object_identifier.identifier: value is required")
}) })
t.Run("object identifier id not uuid", func(t *testing.T) { t.Run("object identifier id not uuid", func(t *testing.T) {
@ -85,7 +88,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.ObjectIdentifier.Identifier = "invalid" event.ObjectIdentifier.Identifier = "invalid"
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - object_identifier.identifier: value must be a valid UUID [string.uuid]") assert.EqualError(t, err, "validation error: object_identifier.identifier: value must be a valid UUID")
}) })
t.Run("object identifier type empty", func(t *testing.T) { t.Run("object identifier type empty", func(t *testing.T) {
@ -93,7 +96,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.ObjectIdentifier.Type = "" event.ObjectIdentifier.Type = ""
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - object_identifier.type: value is required [required]") assert.EqualError(t, err, "validation error: object_identifier.type: value is required")
}) })
t.Run("data nil", func(t *testing.T) { t.Run("data nil", func(t *testing.T) {
@ -101,7 +104,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.Data = nil event.Data = nil
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - data: exactly one field is required in oneof [required]") assert.EqualError(t, err, "validation error: data: exactly one field is required in oneof")
}) })
t.Run("data empty", func(t *testing.T) { t.Run("data empty", func(t *testing.T) {
@ -112,7 +115,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
}} }}
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - unencrypted_data.data: value is required [required]") assert.EqualError(t, err, "validation error: unencrypted_data.data: value is required")
}) })
t.Run("data protobuf type empty", func(t *testing.T) { t.Run("data protobuf type empty", func(t *testing.T) {
@ -123,6 +126,59 @@ func Test_RoutableAuditEvent(t *testing.T) {
}} }}
err := validator.Validate(&event) err := validator.Validate(&event)
assert.EqualError(t, err, "validation error:\n - unencrypted_data.protobuf_type: value is required [required]") assert.EqualError(t, err, "validation error: unencrypted_data.protobuf_type: value is required")
}) })
} }
func Test_AuthenticationInfo(t *testing.T) {
validator, err := protovalidate.New()
assert.NoError(t, err)
email := "x@x.x"
newEvent := func() auditV1.AuthenticationInfo {
return auditV1.AuthenticationInfo{
PrincipalId: "1234567890",
PrincipalEmail: &email,
ServiceAccountName: nil,
ServiceAccountDelegationInfo: nil,
}
}
t.Run("valid event", func(t *testing.T) {
event := newEvent()
err := validator.Validate(&event)
assert.NoError(t, err)
})
t.Run("valid event without email", func(t *testing.T) {
event := newEvent()
event.PrincipalEmail = nil
err := validator.Validate(&event)
assert.NoError(t, err)
})
t.Run("principal id contains only whitespace", func(t *testing.T) {
event := newEvent()
event.PrincipalId = " "
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: principal_id: value does not match regex pattern `.*\\S.*`")
})
t.Run("principal email contains only whitespace", func(t *testing.T) {
event := newEvent()
whitespaceEmail := " "
event.PrincipalEmail = &whitespaceEmail
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: principal_email: value must be a valid email address")
})
t.Run("missing host in email", func(t *testing.T) {
event := newEvent()
invalidEmail := "@test.com"
event.PrincipalEmail = &invalidEmail
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: principal_email: value must be a valid email address")
})
}

View file

@ -4,16 +4,17 @@ import (
"fmt" "fmt"
"time" "time"
"google.golang.org/protobuf/types/known/wrapperspb"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/google/uuid" "github.com/google/uuid"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
const clientCredentialsToken = "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1yZXNvdXJjZS1tYW5hZ2VyLWRldiJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2IiwiZXhwIjoxNzI0NDA1MzI2LCJpYXQiOjE3MjQ0MDQ0MjYsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZGV2LnN0YWNraXQuY2xvdWQiLCJqdGkiOiJlNDZlYmEzOC1kZWRiLTQ1NDEtOTRmMy00OWY5N2E5MzRkNTgiLCJuYmYiOjE3MjQ0MDQ0MjYsInNjb3BlIjoidWFhLm5vbmUiLCJzdWIiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2In0.JP5Uy7AMdK4ukzQ6aOYzbVwEmq0Tp2ppQGRqGOhuVQgbqs6yJ33GKXo7RPsJVLw3FR7XAxENIVqNvzGotbDXr0NjBGdzyxIHzrOaUqM4w1iLzD1KF51dXFwkoigqDdD7Ze9eI_Uo3tSn8FwGLTSoO-ONQYpnceCiGut2Gc6VIL8HOLdh8dzlRENGQtgYd-3Y5zqpoLrsR2Bd-0sv15sF-5aI0CqcC8gE70JPImKf2u_IYI-TYMDNk86YSCtaYO5-alOrHXXWwgzSoH-r2s5qoOhPbei9myV_P4fdcKXxMqfap9hImXPUooVhpdUr1AabZw3MtW7rION8tJAiauhMQA" const clientCredentialsToken = "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1yZXNvdXJjZS1tYW5hZ2VyLWRldiJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2IiwiZXhwIjoxNzI0NDA1MzI2LCJpYXQiOjE3MjQ0MDQ0MjYsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZGV2LnN0YWNraXQuY2xvdWQiLCJqdGkiOiJlNDZlYmEzOC1kZWRiLTQ1NDEtOTRmMy00OWY5N2E5MzRkNTgiLCJuYmYiOjE3MjQ0MDQ0MjYsInNjb3BlIjoidWFhLm5vbmUiLCJzdWIiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2In0.JP5Uy7AMdK4ukzQ6aOYzbVwEmq0Tp2ppQGRqGOhuVQgbqs6yJ33GKXo7RPsJVLw3FR7XAxENIVqNvzGotbDXr0NjBGdzyxIHzrOaUqM4w1iLzD1KF51dXFwkoigqDdD7Ze9eI_Uo3tSn8FwGLTSoO-ONQYpnceCiGut2Gc6VIL8HOLdh8dzlRENGQtgYd-3Y5zqpoLrsR2Bd-0sv15sF-5aI0CqcC8gE70JPImKf2u_IYI-TYMDNk86YSCtaYO5-alOrHXXWwgzSoH-r2s5qoOhPbei9myV_P4fdcKXxMqfap9hImXPUooVhpdUr1AabZw3MtW7rION8tJAiauhMQA"
const serviceAccountTokenUnderscoreSubject = "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxMGYzOGIwMV81MzRiXzQ3YmJfYTAzYV9lMjk0Y2EyYmU0ZGUiLCJhdWQiOlsic3RhY2tpdCIsImFwaSJdLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3Rva2VuLnNvdXJjZSI6ImxlZ2FjeSIsInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiJjZDk0ZjAxYS1kZjJlLTQ0NTYtOTAyZS00OGY1ZTU3ZjBiNjMiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50Iiwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMTBmMzhiMDEtNTM0Yi00N2JiLWEwM2EtZTI5NGNhMmJlNGRlIiwiZXhwIjoxNzIyNjY5MzQzLCJpYXQiOjE3MjI1ODI5NDMsImVtYWlsIjoibXktc2VydmljZS15aWZjOWUxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiI4NGMzMGE0Ni0xMDAxLTQzNmYtODU5Zi04OWMwYmExOWJlMWUifQ.bfD2TxfioqaKbqFJvnV_gq5zY_aoKVD2qzySMQjubaLQ5Vx_Tj95HU0q7gdNczNgcT0tBRyUp0pE4g4bwaPpB2MtYtUUunzpwG8sOX_OBchkorhcC4N50cdF5TR2pg0SMp3L6QBo3coHVbjHvaipshCj1NvyXYzARb4dSR0adrsIGnqy3IaScty1A2XQ7PN6SX_OVmxO5swpL0I-afKvCOffnChI3qmFAL5t6sFxm8PoaCWLIrkoxdtqxw5ZqsPPOJ0qDhssTuc3nE4JrQnzX8fZH5FiBVVHGT76KUNgPFd26UsVzbGqBXK20pn3pbIQHwbRiVOh6qanjr9kvHBXpQ"
const serviceAccountTokenRepeatedlyImpersonated = "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxNzM0YjRiNi0xZDVlLTQ4MTktOWI1MC0yOTkxN2ExYjlhZDUiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiZjQ1MDA5YjItNjQzMy00M2MxLWI2YzctNjE4YzQ0MzU5ZTcxIiwiYWN0Ijp7InN1YiI6IjAyYWVmNTE2LTMxN2YtNGVjMS1hMWRmLTFhY2JkNGQ0OWZlMyJ9fSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhcGkiLCJzdGFja2l0L3Byb2plY3QvcHJvamVjdC5pZCI6ImRhY2M3ODMwLTg0M2UtNGM1ZS04NmZmLWFhMGZiNTFkNjM2ZiIsImF6cCI6ImY0NTAwOWIyLTY0MzMtNDNjMS1iNmM3LTYxOGM0NDM1OWU3MSIsInN0YWNraXQvc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjE3MzRiNGI2LTFkNWUtNDgxOS05YjUwLTI5OTE3YTFiOWFkNSIsImV4cCI6MTcyNDA2Mjk2MywiaWF0IjoxNzI0MDU5MzYzLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC0zLWZnaHN4dzFAc2Euc3RhY2tpdC5jbG91ZCIsImp0aSI6IjFmN2YxZWZjLTMzNDktNDExYS1hNWQ3LTIyNTVlMGE1YThhZSJ9.c1ae17bAtyOdmwXQbK37W-NTyOxo7iER5aHS_C0fU1qKl2BjOz708GLjH-_vxx9eKPeYznfI21_xlTaAvuG4Aco9f5YDK7fooTVHnDaOSSggqcEaDzDPrNXhhKEDxotJeq9zRMVCEStcbirjTounnLbuULRbO5GSY5jo-8n2UKxSZ2j5G_SjFHajdJwmzwvOttp08tdL8ck1uDdgVNBfcm0VIdb6WmgrCIUq5rmoa-cRPkdEurNtIEgEB_9U0Xh-SpmmsvFsWWeNIKz0e_5RCIyJonm_wMkGmblGegemkYL76ypeMNXTQsly1RozDIePfzHuZOWbySHSCd-vKQa2kw" const serviceAccountTokenRepeatedlyImpersonated = "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxNzM0YjRiNi0xZDVlLTQ4MTktOWI1MC0yOTkxN2ExYjlhZDUiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiZjQ1MDA5YjItNjQzMy00M2MxLWI2YzctNjE4YzQ0MzU5ZTcxIiwiYWN0Ijp7InN1YiI6IjAyYWVmNTE2LTMxN2YtNGVjMS1hMWRmLTFhY2JkNGQ0OWZlMyJ9fSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhcGkiLCJzdGFja2l0L3Byb2plY3QvcHJvamVjdC5pZCI6ImRhY2M3ODMwLTg0M2UtNGM1ZS04NmZmLWFhMGZiNTFkNjM2ZiIsImF6cCI6ImY0NTAwOWIyLTY0MzMtNDNjMS1iNmM3LTYxOGM0NDM1OWU3MSIsInN0YWNraXQvc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjE3MzRiNGI2LTFkNWUtNDgxOS05YjUwLTI5OTE3YTFiOWFkNSIsImV4cCI6MTcyNDA2Mjk2MywiaWF0IjoxNzI0MDU5MzYzLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC0zLWZnaHN4dzFAc2Euc3RhY2tpdC5jbG91ZCIsImp0aSI6IjFmN2YxZWZjLTMzNDktNDExYS1hNWQ3LTIyNTVlMGE1YThhZSJ9.c1ae17bAtyOdmwXQbK37W-NTyOxo7iER5aHS_C0fU1qKl2BjOz708GLjH-_vxx9eKPeYznfI21_xlTaAvuG4Aco9f5YDK7fooTVHnDaOSSggqcEaDzDPrNXhhKEDxotJeq9zRMVCEStcbirjTounnLbuULRbO5GSY5jo-8n2UKxSZ2j5G_SjFHajdJwmzwvOttp08tdL8ck1uDdgVNBfcm0VIdb6WmgrCIUq5rmoa-cRPkdEurNtIEgEB_9U0Xh-SpmmsvFsWWeNIKz0e_5RCIyJonm_wMkGmblGegemkYL76ypeMNXTQsly1RozDIePfzHuZOWbySHSCd-vKQa2kw"
const serviceAccountTokenImpersonated = "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiMDJhZWY1MTYtMzE3Zi00ZWMxLWExZGYtMWFjYmQ0ZDQ5ZmUzIn0sInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiIwMmFlZjUxNi0zMTdmLTRlYzEtYTFkZi0xYWNiZDRkNDlmZTMiLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJleHAiOjE3MjQwNjI5MDcsImlhdCI6MTcyNDA1OTMwNywiZW1haWwiOiJzZXJ2aWNlLWFjY291bnQtMi10ajlzcnQxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiIzNzU1NTE4My0wMWI5LTQyNzAtYmRjMS02OWI0ZmNmZDVlZTkifQ.auBvvsIesFMAlWOCPCPC77DrrHF7gSKZwKs_Zry5KFvu2bpZZC1BcSXOc8b9eh0SzANI9M9aGJBhOzOm39-ZZ5XOQ-6_y1aWuEenYQ6kT5D3GzCUTMDzSi1lcZ4IG5nFMa_AAlVEN_7AMv7LHGtz49bWLJnAgeTo1cvof-OgP4mCQ5O6E0iyAq-5u8V8NJL7HIZy7BDe4J1mjfYhwKagrN7QFWu4fhN4TNS7d922X_6V489BhjRFRYjLW_qDnv912JorbGRz_XwNy_dPA81EkdMyKE0BJUezguJUEKEG2_JEi9O64Flcoi6x8cFHYhaDuMMSLipzePaHdyk2lQtH7Q" const serviceAccountTokenImpersonated = "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiMDJhZWY1MTYtMzE3Zi00ZWMxLWExZGYtMWFjYmQ0ZDQ5ZmUzIn0sInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiIwMmFlZjUxNi0zMTdmLTRlYzEtYTFkZi0xYWNiZDRkNDlmZTMiLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJleHAiOjE3MjQwNjI5MDcsImlhdCI6MTcyNDA1OTMwNywiZW1haWwiOiJzZXJ2aWNlLWFjY291bnQtMi10ajlzcnQxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiIzNzU1NTE4My0wMWI5LTQyNzAtYmRjMS02OWI0ZmNmZDVlZTkifQ.auBvvsIesFMAlWOCPCPC77DrrHF7gSKZwKs_Zry5KFvu2bpZZC1BcSXOc8b9eh0SzANI9M9aGJBhOzOm39-ZZ5XOQ-6_y1aWuEenYQ6kT5D3GzCUTMDzSi1lcZ4IG5nFMa_AAlVEN_7AMv7LHGtz49bWLJnAgeTo1cvof-OgP4mCQ5O6E0iyAq-5u8V8NJL7HIZy7BDe4J1mjfYhwKagrN7QFWu4fhN4TNS7d922X_6V489BhjRFRYjLW_qDnv912JorbGRz_XwNy_dPA81EkdMyKE0BJUezguJUEKEG2_JEi9O64Flcoi6x8cFHYhaDuMMSLipzePaHdyk2lQtH7Q"
const serviceAccountToken = "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxMGYzOGIwMS01MzRiLTQ3YmItYTAzYS1lMjk0Y2EyYmU0ZGUiLCJhdWQiOlsic3RhY2tpdCIsImFwaSJdLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3Rva2VuLnNvdXJjZSI6ImxlZ2FjeSIsInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiJjZDk0ZjAxYS1kZjJlLTQ0NTYtOTAyZS00OGY1ZTU3ZjBiNjMiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50Iiwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMTBmMzhiMDEtNTM0Yi00N2JiLWEwM2EtZTI5NGNhMmJlNGRlIiwiZXhwIjoxNzIyNjY5MzQzLCJpYXQiOjE3MjI1ODI5NDMsImVtYWlsIjoibXktc2VydmljZS15aWZjOWUxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiI4NGMzMGE0Ni0xMDAxLTQzNmYtODU5Zi04OWMwYmExOWJlMWUifQ.hb8X9VKc9xViHgNMyFHT9ePj_lyEwTV1D2es8E278WtoCJ9-4GPPQGjhcLGGrigjnvpRYV2LKzNqpQslerT5lFT_pHACsryaAE0ImYjmoe-nutA7BBpYuM_JN6pk5VIjVFLTqRKeIvFexPacqS2Vo3YoK1GvxPB8WPWBbGIsBtMl-PTm8OTwwzooBOoCRhhMR-E1lFbAymLsc1JI4yDQKLLomvhEopgmocCnQ-P1QkiKMqdkNxiD_YYLLYTOApg6d62BhqpH66ziqx493AStdZ8d5Kjvf3e1knDhaxVwNCghQj7lSo2kNAqZe__g2tiXpiZNTXBFJ_5HgQMLh67wng" const serviceAccountToken = "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxMGYzOGIwMS01MzRiLTQ3YmItYTAzYS1lMjk0Y2EyYmU0ZGUiLCJhdWQiOlsic3RhY2tpdCIsImFwaSJdLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3Rva2VuLnNvdXJjZSI6ImxlZ2FjeSIsInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiJjZDk0ZjAxYS1kZjJlLTQ0NTYtOTAyZS00OGY1ZTU3ZjBiNjMiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50Iiwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMTBmMzhiMDEtNTM0Yi00N2JiLWEwM2EtZTI5NGNhMmJlNGRlIiwiZXhwIjoxNzIyNjY5MzQzLCJpYXQiOjE3MjI1ODI5NDMsImVtYWlsIjoibXktc2VydmljZS15aWZjOWUxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiI4NGMzMGE0Ni0xMDAxLTQzNmYtODU5Zi04OWMwYmExOWJlMWUifQ.hb8X9VKc9xViHgNMyFHT9ePj_lyEwTV1D2es8E278WtoCJ9-4GPPQGjhcLGGrigjnvpRYV2LKzNqpQslerT5lFT_pHACsryaAE0ImYjmoe-nutA7BBpYuM_JN6pk5VIjVFLTqRKeIvFexPacqS2Vo3YoK1GvxPB8WPWBbGIsBtMl-PTm8OTwwzooBOoCRhhMR-E1lFbAymLsc1JI4yDQKLLomvhEopgmocCnQ-P1QkiKMqdkNxiD_YYLLYTOApg6d62BhqpH66ziqx493AStdZ8d5Kjvf3e1knDhaxVwNCghQj7lSo2kNAqZe__g2tiXpiZNTXBFJ_5HgQMLh67wng"
@ -21,8 +22,9 @@ const userToken = "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My
const userTokenWithSimpleAudience = "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6InNlcnZpY2UtYWNjb3VudC1mMDdiZjZhOC02MjA3LTRmOGItYjNlOS03M2VkMGJlYjg4ZjUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3N0YWNraXQtc2VydmljZS1hY2NvdW50LWRldi5hcHBzLjAxLmNmLmV1MDEuc3RhY2tpdC5jbG91ZCIsImVtYWlsIjoiTHVrYXMuU2NobWl0dEBzdGFja2l0LmNsb3VkIiwiZXhwIjoxNzMyMTgyMDM1LCJpYXQiOjE3MzIxNzg0MzUsImlzcyI6Imh0dHBzOi8vYXBpLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiYzJiZTE2NTEtMWU1NC00ZTZlLWJhYzMtZWYwNzJiM2YwMTQ5IiwibmJmIjoxNzMyMTc4NDE4LCJyb2xlcyI6bnVsbCwic2NvcGUiOiJvcGVuaWQgZW1haWwgcG9ydGFsLWJmZiIsInN1YiI6IjVlNDI2YWVkLWM0ODctNGM0OC1hZjI1LTg3ZjY5Y2Y5Y2RkNCIsInVzZXJfaWQiOiIiLCJ4X2NsaWVudF9pZCI6IiIsInppZCI6IiJ9.notavailable" const userTokenWithSimpleAudience = "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6InNlcnZpY2UtYWNjb3VudC1mMDdiZjZhOC02MjA3LTRmOGItYjNlOS03M2VkMGJlYjg4ZjUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3N0YWNraXQtc2VydmljZS1hY2NvdW50LWRldi5hcHBzLjAxLmNmLmV1MDEuc3RhY2tpdC5jbG91ZCIsImVtYWlsIjoiTHVrYXMuU2NobWl0dEBzdGFja2l0LmNsb3VkIiwiZXhwIjoxNzMyMTgyMDM1LCJpYXQiOjE3MzIxNzg0MzUsImlzcyI6Imh0dHBzOi8vYXBpLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiYzJiZTE2NTEtMWU1NC00ZTZlLWJhYzMtZWYwNzJiM2YwMTQ5IiwibmJmIjoxNzMyMTc4NDE4LCJyb2xlcyI6bnVsbCwic2NvcGUiOiJvcGVuaWQgZW1haWwgcG9ydGFsLWJmZiIsInN1YiI6IjVlNDI2YWVkLWM0ODctNGM0OC1hZjI1LTg3ZjY5Y2Y5Y2RkNCIsInVzZXJfaWQiOiIiLCJ4X2NsaWVudF9pZCI6IiIsInppZCI6IiJ9.notavailable"
var TestHeaders = map[string][]string{"user-agent": {"custom"}, "authorization": {userToken}} var TestHeaders = map[string][]string{"user-agent": {"custom"}, "authorization": {userToken}}
var TestHeadersSa = map[string][]string{"user-agent": {"custom"}, "authorization": {serviceAccountTokenUnderscoreSubject}}
func newOrganizationAuditEvent( func NewOrganizationAuditEvent(
customization *func( customization *func(
*auditV1.AuditLogEntry, *auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier, *auditV1.ObjectIdentifier,
@ -41,20 +43,21 @@ func newOrganizationAuditEvent(
headers["Content-Type"] = "application/json" headers["Content-Type"] = "application/json"
labels := make(map[string]string) labels := make(map[string]string)
labels["label1"] = "value1" labels["label1"] = "value1"
email := "user@example.com"
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeOrganization.Plural(), identifier, EventTypeAdminActivity), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier, pkgAuditCommon.EventTypeAdminActivity),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.organization.created", OperationName: "stackit.resourcemanager.v2.organization.created",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeOrganization.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: uuid.NewString(), PrincipalId: uuid.NewString(),
PrincipalEmail: "user@example.com", PrincipalEmail: &email,
ServiceAccountName: nil, ServiceAccountName: nil,
ServiceAccountDelegationInfo: nil, ServiceAccountDelegationInfo: nil,
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeOrganization.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier),
Permission: &permission, Permission: &permission,
Granted: &permissionGranted, Granted: &permissionGranted,
}}, }},
@ -102,7 +105,7 @@ func newOrganizationAuditEvent(
objectIdentifier := &auditV1.ObjectIdentifier{ objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(), Identifier: identifier.String(),
Type: string(ObjectTypeOrganization), Type: string(pkgAuditCommon.ObjectTypeOrganization),
} }
if customization != nil { if customization != nil {
@ -112,7 +115,7 @@ func newOrganizationAuditEvent(
return auditEvent, objectIdentifier return auditEvent, objectIdentifier
} }
func newFolderAuditEvent( func NewFolderAuditEvent(
customization *func( customization *func(
*auditV1.AuditLogEntry, *auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier, *auditV1.ObjectIdentifier,
@ -131,20 +134,21 @@ func newFolderAuditEvent(
headers["Content-Type"] = "application/json" headers["Content-Type"] = "application/json"
labels := make(map[string]string) labels := make(map[string]string)
labels["label1"] = "value1" labels["label1"] = "value1"
email := "user@example.com"
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeFolder.Plural(), identifier, EventTypeAdminActivity), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier, pkgAuditCommon.EventTypeAdminActivity),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.folder.created", OperationName: "stackit.resourcemanager.v2.folder.created",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeFolder.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: uuid.NewString(), PrincipalId: uuid.NewString(),
PrincipalEmail: "user@example.com", PrincipalEmail: &email,
ServiceAccountName: nil, ServiceAccountName: nil,
ServiceAccountDelegationInfo: nil, ServiceAccountDelegationInfo: nil,
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeFolder.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier),
Permission: &permission, Permission: &permission,
Granted: &permissionGranted, Granted: &permissionGranted,
}}, }},
@ -192,7 +196,7 @@ func newFolderAuditEvent(
objectIdentifier := &auditV1.ObjectIdentifier{ objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(), Identifier: identifier.String(),
Type: string(ObjectTypeFolder), Type: string(pkgAuditCommon.ObjectTypeFolder),
} }
if customization != nil { if customization != nil {
@ -202,7 +206,7 @@ func newFolderAuditEvent(
return auditEvent, objectIdentifier return auditEvent, objectIdentifier
} }
func newProjectAuditEvent( func NewProjectAuditEvent(
customization *func( customization *func(
*auditV1.AuditLogEntry, *auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier, *auditV1.ObjectIdentifier,
@ -221,20 +225,21 @@ func newProjectAuditEvent(
headers["Content-Type"] = "application/json" headers["Content-Type"] = "application/json"
labels := make(map[string]string) labels := make(map[string]string)
labels["label1"] = "value1" labels["label1"] = "value1"
email := "user@example.com"
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeProject.Plural(), identifier, EventTypeAdminActivity), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier, pkgAuditCommon.EventTypeAdminActivity),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.project.created", OperationName: "stackit.resourcemanager.v2.project.created",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: uuid.NewString(), PrincipalId: uuid.NewString(),
PrincipalEmail: "user@example.com", PrincipalEmail: &email,
ServiceAccountName: nil, ServiceAccountName: nil,
ServiceAccountDelegationInfo: nil, ServiceAccountDelegationInfo: nil,
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
Permission: &permission, Permission: &permission,
Granted: &permissionGranted, Granted: &permissionGranted,
}}, }},
@ -282,7 +287,7 @@ func newProjectAuditEvent(
objectIdentifier := &auditV1.ObjectIdentifier{ objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(), Identifier: identifier.String(),
Type: string(ObjectTypeProject), Type: string(pkgAuditCommon.ObjectTypeProject),
} }
if customization != nil { if customization != nil {
@ -292,7 +297,7 @@ func newProjectAuditEvent(
return auditEvent, objectIdentifier return auditEvent, objectIdentifier
} }
func newProjectSystemAuditEvent( func NewProjectSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry { customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {
identifier := uuid.New() identifier := uuid.New()
@ -306,20 +311,21 @@ func newProjectSystemAuditEvent(
serviceAccountId := uuid.NewString() serviceAccountId := uuid.NewString()
serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId) serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId)
delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}} delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}}
email := "service-account@sa.stackit.cloud"
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", SystemIdentifier.Type, SystemIdentifier.Identifier, EventTypeSystemEvent), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.SystemIdentifier.Type, pkgAuditCommon.SystemIdentifier.Identifier, pkgAuditCommon.EventTypeSystemEvent),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.system.changed", OperationName: "stackit.resourcemanager.v2.system.changed",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: serviceAccountId, PrincipalId: serviceAccountId,
PrincipalEmail: "service-account@sa.stackit.cloud", PrincipalEmail: &email,
ServiceAccountName: &serviceAccountName, ServiceAccountName: &serviceAccountName,
ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal}, ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal},
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
Permission: nil, Permission: nil,
Granted: nil, Granted: nil,
}}, }},
@ -372,7 +378,7 @@ func newProjectSystemAuditEvent(
return auditEvent return auditEvent
} }
func newSystemAuditEvent( func NewSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry { customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {
identifier := uuid.Nil identifier := uuid.Nil
@ -386,20 +392,21 @@ func newSystemAuditEvent(
serviceAccountId := uuid.NewString() serviceAccountId := uuid.NewString()
serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId) serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId)
delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}} delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}}
email := "service-account@sa.stackit.cloud"
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeSystem.Plural(), identifier, EventTypeSystemEvent), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier, pkgAuditCommon.EventTypeSystemEvent),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.system.changed", OperationName: "stackit.resourcemanager.v2.system.changed",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeSystem.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: serviceAccountId, PrincipalId: serviceAccountId,
PrincipalEmail: "service-account@sa.stackit.cloud", PrincipalEmail: &email,
ServiceAccountName: &serviceAccountName, ServiceAccountName: &serviceAccountName,
ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal}, ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal},
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeSystem.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier),
Permission: nil, Permission: nil,
Granted: nil, Granted: nil,
}}, }},

View file

@ -2,6 +2,7 @@ package api
import ( import (
"context" "context"
"go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/propagation"
) )
@ -24,7 +25,7 @@ func TraceParentAndStateFromContext(ctx context.Context) (string, string) {
} }
// AddTraceParentAndStateToContext adds trace and state related information to the given context. // AddTraceParentAndStateToContext adds trace and state related information to the given context.
func AddTraceParentAndStateToContext(ctx context.Context, traceParent string, traceState string) context.Context { func AddTraceParentAndStateToContext(ctx context.Context, traceParent, traceState string) context.Context {
mapCarrier := propagation.MapCarrier{} mapCarrier := propagation.MapCarrier{}
mapCarrier[traceParentHeader] = traceParent mapCarrier[traceParentHeader] = traceParent
mapCarrier[traceStateHeader] = traceState mapCarrier[traceStateHeader] = traceState

View file

@ -2,10 +2,11 @@ package api
import ( import (
"context" "context"
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"testing"
) )
func Test_AddTraceParentAndStateToContext(t *testing.T) { func Test_AddTraceParentAndStateToContext(t *testing.T) {

View file

@ -0,0 +1,237 @@
package messaging
import (
"context"
"errors"
"fmt"
"log/slog"
"sync"
"time"
"github.com/Azure/go-amqp"
pkgCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
)
const connectionTimeoutSeconds = 10
var ErrConnectionClosed = errors.New("amqp connection is closed")
type AmqpConnection struct {
ConnectionName string
Lock sync.RWMutex
BrokerUrl string
Username string
Password string
Conn AmqpConn
Dialer amqpDial
}
// AmqpConn is an abstraction of amqp.Conn
type AmqpConn interface {
NewSession(ctx context.Context, opts *amqp.SessionOptions) (AmqpSession, error)
Close() error
Done() <-chan struct{}
}
type defaultAmqpConn struct {
Conn *amqp.Conn
}
func newDefaultAmqpConn(conn *amqp.Conn) *defaultAmqpConn {
return &defaultAmqpConn{
Conn: conn,
}
}
func (d defaultAmqpConn) NewSession(ctx context.Context, opts *amqp.SessionOptions) (AmqpSession, error) {
session, err := d.Conn.NewSession(ctx, opts)
if err != nil {
return nil, err
}
return newDefaultAmqpSession(session), nil
}
func (d defaultAmqpConn) Close() error {
return d.Conn.Close()
}
func (d defaultAmqpConn) Done() <-chan struct{} {
return d.Conn.Done()
}
var _ AmqpConn = (*defaultAmqpConn)(nil)
type amqpDial interface {
Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (AmqpConn, error)
}
type AmqpSession interface {
NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (AmqpSender, error)
Close(ctx context.Context) error
}
type defaultAmqpSession struct {
Session *amqp.Session
}
func newDefaultAmqpSession(session *amqp.Session) *defaultAmqpSession {
return &defaultAmqpSession{
Session: session,
}
}
func (s *defaultAmqpSession) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (AmqpSender, error) {
return s.Session.NewSender(ctx, target, opts)
}
func (s *defaultAmqpSession) Close(ctx context.Context) error {
return s.Session.Close(ctx)
}
var _ AmqpSession = (*defaultAmqpSession)(nil)
type defaultAmqpDialer struct{}
func (d *defaultAmqpDialer) Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (AmqpConn, error) {
dial, err := amqp.Dial(ctx, addr, opts)
if err != nil {
return nil, err
}
return newDefaultAmqpConn(dial), nil
}
var _ amqpDial = (*defaultAmqpDialer)(nil)
func NewAmqpConnection(config pkgCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection {
return &AmqpConnection{
ConnectionName: connectionName,
Lock: sync.RWMutex{},
BrokerUrl: config.BrokerUrl,
Username: config.Username,
Password: config.Password,
Dialer: &defaultAmqpDialer{},
}
}
func (c *AmqpConnection) NewSender(ctx context.Context, topic string) (*AmqpSenderSession, error) {
if c.Conn == nil {
return nil, errors.New("connection is not initialized")
}
if c.internalIsClosed() {
return nil, ErrConnectionClosed
}
c.Lock.RLock()
defer c.Lock.RUnlock()
// new session
newSession, err := c.Conn.NewSession(ctx, nil)
if err != nil {
return nil, fmt.Errorf("new session: %w", err)
}
// new sender
newSender, err := newSession.NewSender(ctx, topic, nil)
if err != nil {
err = fmt.Errorf("new internal sender: %w", err)
closeErr := newSession.Close(ctx)
if closeErr != nil {
return nil, errors.Join(err, fmt.Errorf("close session: %w", closeErr))
}
return nil, err
}
return &AmqpSenderSession{newSession, newSender}, nil
}
func As[T any](value any, err error) (*T, error) {
if err != nil {
return nil, err
}
if value == nil {
return nil, nil
}
castedValue, isType := value.(*T)
if !isType {
return nil, fmt.Errorf("could not cast value: %T", value)
}
return castedValue, nil
}
func (c *AmqpConnection) Connect() error {
c.Lock.Lock()
defer c.Lock.Unlock()
subCtx, cancel := context.WithTimeout(context.Background(), connectionTimeoutSeconds*time.Second)
defer cancel()
if err := c.internalConnect(subCtx); err != nil {
return fmt.Errorf("internal connect: %w", err)
}
return nil
}
func (c *AmqpConnection) internalConnect(ctx context.Context) error {
if c.Conn == nil {
// Set credentials if specified
auth := amqp.SASLTypeAnonymous()
if c.Username != "" && c.Password != "" {
auth = amqp.SASLTypePlain(c.Username, c.Password)
} else {
slog.Debug("amqp connection: connect: using anonymous messaging")
}
options := &amqp.ConnOptions{
SASLType: auth,
}
// Initialize connection
conn, err := c.Dialer.Dial(ctx, c.BrokerUrl, options)
if err != nil {
return fmt.Errorf("dial: %w", err)
}
c.Conn = conn
}
return nil
}
func (c *AmqpConnection) Close() error {
c.Lock.Lock()
defer c.Lock.Unlock()
if err := c.internalClose(); err != nil {
return fmt.Errorf("internal close: %w", err)
}
return nil
}
func (c *AmqpConnection) internalClose() error {
if c.Conn != nil {
if err := c.Conn.Close(); err != nil {
return fmt.Errorf("connection close: %w", err)
}
c.Conn = nil
}
return nil
}
func (c *AmqpConnection) IsClosed() bool {
c.Lock.RLock()
defer c.Lock.RUnlock()
return c.internalIsClosed()
}
func (c *AmqpConnection) internalIsClosed() bool {
if c.Conn == nil {
return true
}
select {
case <-c.Conn.Done():
return true
default:
return false
}
}

View file

@ -0,0 +1,231 @@
package messaging
import (
"errors"
"fmt"
"log/slog"
"sync"
pkgCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
)
type connectionProvider interface {
NewAmqpConnection(config pkgCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection
}
type defaultAmqpConnectionProvider struct{}
func (p defaultAmqpConnectionProvider) NewAmqpConnection(config pkgCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection {
return NewAmqpConnection(config, connectionName)
}
var _ connectionProvider = (*defaultAmqpConnectionProvider)(nil)
type ConnectionPool interface {
Close() error
NewHandle() *ConnectionPoolHandle
GetConnection(handle *ConnectionPoolHandle) (*AmqpConnection, error)
}
type AmqpConnectionPool struct {
Config pkgCommon.AmqpConnectionPoolConfig
ConnectionName string
Connections []*AmqpConnection
ConnectionProvider connectionProvider
HandleOffset int
Lock sync.RWMutex
}
type ConnectionPoolHandle struct {
ConnectionOffset int
}
func NewDefaultAmqpConnectionPool(config pkgCommon.AmqpConnectionConfig, connectionName string) (ConnectionPool, error) {
poolConfig := pkgCommon.AmqpConnectionPoolConfig{
Parameters: config,
PoolSize: 2,
}
return NewAmqpConnectionPool(poolConfig, connectionName)
}
func NewAmqpConnectionPool(config pkgCommon.AmqpConnectionPoolConfig, connectionName string) (ConnectionPool, error) {
if config.PoolSize == 0 {
config.PoolSize = 2
}
pool := &AmqpConnectionPool{
Config: config,
ConnectionName: connectionName,
Connections: make([]*AmqpConnection, 0),
ConnectionProvider: defaultAmqpConnectionProvider{},
HandleOffset: 0,
Lock: sync.RWMutex{},
}
if err := pool.initializeConnections(); err != nil {
if closeErr := pool.Close(); closeErr != nil {
return nil, errors.Join(err, fmt.Errorf("initialize amqp connection: pool closed: %w", closeErr))
}
return nil, fmt.Errorf("initialize connections: %w", err)
}
return pool, nil
}
func (p *AmqpConnectionPool) initializeConnections() error {
if len(p.Connections) < p.Config.PoolSize {
p.Lock.Lock()
defer p.Lock.Unlock()
numMissingConnections := p.Config.PoolSize - len(p.Connections)
for i := 0; i < numMissingConnections; i++ {
if err := p.internalAddConnection(); err != nil {
return err
}
}
}
return nil
}
func (p *AmqpConnectionPool) internalAddConnection() error {
newConnection, err := p.internalNewConnection()
if err != nil {
return fmt.Errorf("new connection: %w", err)
}
p.Connections = append(p.Connections, newConnection)
return nil
}
func (p *AmqpConnectionPool) internalNewConnection() (*AmqpConnection, error) {
conn := p.ConnectionProvider.NewAmqpConnection(p.Config.Parameters, p.ConnectionName)
if err := conn.Connect(); err != nil {
slog.Warn("amqp connection: failed to connect to amqp broker", slog.Any("err", err))
// retry
if err = conn.Connect(); err != nil {
connectErr := fmt.Errorf("new internal connection: %w", err)
if closeErr := conn.Close(); closeErr != nil {
// this case should never happen as the inner connection should always be null, therefore
// it should not have to be closed, i.e. be able to return errors.
return nil, errors.Join(connectErr, fmt.Errorf("close connection: %w", closeErr))
}
return nil, connectErr
}
}
return conn, nil
}
func (p *AmqpConnectionPool) Close() error {
p.Lock.Lock()
defer p.Lock.Unlock()
closeErrors := make([]error, 0)
for _, conn := range p.Connections {
if conn != nil {
if err := conn.Close(); err != nil {
closeErrors = append(closeErrors, fmt.Errorf("pooled connection: %w", err))
}
}
}
p.Connections = make([]*AmqpConnection, p.Config.PoolSize)
if len(closeErrors) > 0 {
return errors.Join(closeErrors...)
}
return nil
}
func (p *AmqpConnectionPool) NewHandle() *ConnectionPoolHandle {
p.Lock.Lock()
defer p.Lock.Unlock()
offset := p.HandleOffset
p.HandleOffset++
offset %= p.Config.PoolSize
return &ConnectionPoolHandle{
ConnectionOffset: offset,
}
}
func (p *AmqpConnectionPool) GetConnection(handle *ConnectionPoolHandle) (*AmqpConnection, error) {
// get the requested connection or another one
conn, addConnection := p.nextConnectionForHandle(handle)
// renew the requested connection if the request connection is closed
if conn == nil || addConnection {
p.Lock.Lock()
// check that accessing the pool only with a valid index (out of bounds should only occur on shutdown)
connectionIndex := p.connectionIndex(handle, 0)
if p.Connections[connectionIndex] == nil {
connection, err := p.internalNewConnection()
if err != nil {
if conn == nil {
// case: connection could not be renewed and no connection to return has been found
p.Lock.Unlock()
return nil, fmt.Errorf("renew connection: %w", err)
}
// case: connection could not be renewed but another connection will be returned
slog.Warn("amqp connection pool: get connection: renew connection: ", slog.Any("err", err))
} else {
// case: connection could be renewed and will be added to pool
p.Connections[connectionIndex] = connection
conn = connection
}
}
p.Lock.Unlock()
}
if conn == nil {
return nil, fmt.Errorf("amqp connection pool: get connection: failed to obtain connection")
}
return conn, nil
}
func (p *AmqpConnectionPool) nextConnectionForHandle(handle *ConnectionPoolHandle) (*AmqpConnection, bool) {
// retry as long as there are remaining connections in the pool
var conn *AmqpConnection
var addConnection bool
for i := 0; i < p.Config.PoolSize; i++ {
// get the next possible connection (considering the retry index)
idx := p.connectionIndex(handle, i)
p.Lock.RLock()
if idx < len(p.Connections) {
conn = p.Connections[idx]
} else {
// handle the edge case that the pool is empty on shutdown
conn = nil
}
p.Lock.RUnlock()
// remember that the requested is closed, retry with the next
if conn == nil {
addConnection = true
continue
}
// if the connection is closed, mark it by setting it to nil
if conn.IsClosed() {
p.Lock.Lock()
p.Connections[idx] = nil
p.Lock.Unlock()
addConnection = true
continue
}
return conn, addConnection
}
return nil, true
}
func (p *AmqpConnectionPool) connectionIndex(handle *ConnectionPoolHandle, iteration int) int {
if iteration+handle.ConnectionOffset >= p.Config.PoolSize {
return (iteration + handle.ConnectionOffset) % p.Config.PoolSize
}
return iteration + handle.ConnectionOffset
}

View file

@ -0,0 +1,581 @@
package messaging
import (
"errors"
"fmt"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
pkgMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
)
type connectionProviderMock struct {
mock.Mock
}
func (p *connectionProviderMock) NewAmqpConnection(config pkgMessagingCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection {
args := p.Called(config, connectionName)
return args.Get(0).(*AmqpConnection)
}
var _ connectionProvider = (*connectionProviderMock)(nil)
func Test_AmqpConnectionPool_GetHandle(t *testing.T) {
t.Run("next handle", func(t *testing.T) {
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
}
handle := pool.NewHandle()
assert.NotNil(t, handle)
assert.Equal(t, 0, handle.ConnectionOffset)
assert.Equal(t, 1, pool.HandleOffset)
})
t.Run("next handle high offset", func(t *testing.T) {
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 13,
Lock: sync.RWMutex{},
}
handle := pool.NewHandle()
assert.NotNil(t, handle)
assert.Equal(t, 3, handle.ConnectionOffset)
assert.Equal(t, 14, pool.HandleOffset)
})
}
func Test_AmqpConnectionPool_internalAddConnection(t *testing.T) {
t.Run("internal add connection", func(t *testing.T) {
conn := &amqpConnMock{}
dialer := &amqpDialMock{}
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.internalAddConnection()
assert.NoError(t, err)
assert.Equal(t, 1, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
dialer.AssertNumberOfCalls(t, "Dial", 1)
})
t.Run("dialer error", func(t *testing.T) {
conn := &amqpConnMock{}
dialer := &amqpDialMock{}
var c *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error")).Once()
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.internalAddConnection()
assert.NoError(t, err)
assert.Equal(t, 1, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
dialer.AssertNumberOfCalls(t, "Dial", 2)
})
t.Run("repetitive dialer error", func(t *testing.T) {
dialer := &amqpDialMock{}
var c *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error"))
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.internalAddConnection()
assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error")
assert.Equal(t, 0, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
dialer.AssertNumberOfCalls(t, "Dial", 2)
})
}
func Test_AmqpConnectionPool_initializeConnections(t *testing.T) {
t.Run("initialize connections successfully", func(t *testing.T) {
conn := &amqpConnMock{}
dialer := &amqpDialMock{}
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.initializeConnections()
assert.NoError(t, err)
assert.Equal(t, 5, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 5)
})
t.Run("fail initialization of connections", func(t *testing.T) {
var c *amqpConnMock = nil
failingDialer := &amqpDialMock{}
failingDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error"))
failingConnection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: failingDialer,
}
conn := &amqpConnMock{}
successfulDialer := &amqpDialMock{}
successfulDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
successfulConnection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: successfulDialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(4)
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(failingConnection)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.initializeConnections()
assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error")
assert.Equal(t, 4, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 5)
})
}
func Test_AmqpConnectionPool_Close(t *testing.T) {
t.Run("close connection successfully", func(t *testing.T) {
// add 5 connections to the pool
conn := &amqpConnMock{}
conn.On("Close").Return(nil)
dialer := &amqpDialMock{}
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.initializeConnections()
assert.NoError(t, err)
assert.Equal(t, 5, len(pool.Connections))
// close the pool
err = pool.Close()
assert.NoError(t, err)
assert.Equal(t, 5, len(pool.Connections))
for _, c := range pool.Connections {
assert.Nil(t, c)
}
})
t.Run("close connection fail", func(t *testing.T) {
// add 5 connections to the pool
failingConn := &amqpConnMock{}
failingConn.On("Close").Return(errors.New("test error"))
failingDialer := &amqpDialMock{}
failingDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(failingConn, nil)
failingConnection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: failingDialer,
}
successfulConn := &amqpConnMock{}
successfulConn.On("Close").Return(nil)
successfulDialer := &amqpDialMock{}
successfulDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(successfulConn, nil)
successfulConnection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: successfulDialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(2)
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(failingConnection).Times(2)
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(1)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.initializeConnections()
assert.NoError(t, err)
assert.Equal(t, 5, len(pool.Connections))
// close the pool
err = pool.Close()
assert.EqualError(t, err, "pooled connection: internal close: connection close: test error\npooled connection: internal close: connection close: test error")
assert.Equal(t, 5, len(pool.Connections))
for _, c := range pool.Connections {
assert.Nil(t, c)
}
})
}
func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
channelReceiver := func(channel chan struct{}) <-chan struct{} {
return channel
}
newActiveConnection := func() *AmqpConnection {
channel := make(chan struct{})
conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Conn: conn,
}
}
newClosedConnection := func() *AmqpConnection {
channel := make(chan struct{})
close(channel)
conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Conn: conn,
}
}
t.Run("next connection for requested handle", func(t *testing.T) {
connections := make([]*AmqpConnection, 0)
for i := 0; i < 5; i++ {
connections = append(connections, newActiveConnection())
}
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NotNil(t, connection)
assert.False(t, addConnection)
})
t.Run("nil connection for requested handle", func(t *testing.T) {
connections := make([]*AmqpConnection, 0)
connections = append(connections, newActiveConnection())
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, newActiveConnection())
connections = append(connections, newActiveConnection())
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NotNil(t, connection)
assert.True(t, addConnection)
})
t.Run("closed connection for requested handle", func(t *testing.T) {
connections := make([]*AmqpConnection, 0)
connections = append(connections, newActiveConnection())
connections = append(connections, newClosedConnection())
connections = append(connections, newClosedConnection())
connections = append(connections, newActiveConnection())
connections = append(connections, newActiveConnection())
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NotNil(t, connection)
assert.True(t, addConnection)
})
t.Run("no connection for requested handle", func(t *testing.T) {
connections := make([]*AmqpConnection, 0)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, nil)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.Nil(t, connection)
assert.True(t, addConnection)
})
t.Run("connection for requested handle with large index", func(t *testing.T) {
connections := make([]*AmqpConnection, 0)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, newActiveConnection())
connections = append(connections, nil)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 23})
assert.NotNil(t, connection)
assert.False(t, addConnection)
})
t.Run("connection for requested handle nil with large index", func(t *testing.T) {
connections := make([]*AmqpConnection, 0)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, newActiveConnection())
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 23})
assert.NotNil(t, connection)
assert.True(t, addConnection)
})
}
func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
channelReceiver := func(channel chan struct{}) <-chan struct{} {
return channel
}
newActiveConnection := func() *AmqpConnection {
channel := make(chan struct{})
conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Conn: conn,
}
}
t.Run("get connection for requested handle", func(t *testing.T) {
connections := make([]*AmqpConnection, 0)
for i := 0; i < 5; i++ {
connections = append(connections, newActiveConnection())
}
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NoError(t, err)
assert.NotNil(t, connection)
assert.Equal(t, connections[1], connection)
assert.Equal(t, 5, len(connections))
})
t.Run("add connection if missing", func(t *testing.T) {
connections := make([]*AmqpConnection, 5)
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(newActiveConnection())
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
ConnectionProvider: connectionProvider,
}
connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NoError(t, err)
assert.NotNil(t, connection)
assert.Equal(t, connections[1], connection)
assert.Equal(t, 5, len(connections))
})
t.Run("add connection fails returns alternative connection", func(t *testing.T) {
connections := make([]*AmqpConnection, 0)
connections = append(connections, newActiveConnection())
connections = append(connections, nil)
connections = append(connections, newActiveConnection())
connections = append(connections, newActiveConnection())
connections = append(connections, newActiveConnection())
connectionProvider := &connectionProviderMock{}
dialer := &amqpDialMock{}
var c *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error"))
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
ConnectionProvider: connectionProvider,
}
connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NoError(t, err)
assert.NotNil(t, connection)
assert.Nil(t, connections[1])
assert.Equal(t, connections[2], connection)
assert.Equal(t, 5, len(connections))
})
t.Run("add connection fails", func(t *testing.T) {
connections := make([]*AmqpConnection, 0)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, nil)
connections = append(connections, nil)
connectionProvider := &connectionProviderMock{}
dialer := &amqpDialMock{}
var c *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error"))
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
ConnectionProvider: connectionProvider,
}
connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.EqualError(t, err, "renew connection: new internal connection: internal connect: dial: dial error")
assert.Nil(t, connection)
assert.Equal(t, 5, len(connections))
})
}

View file

@ -0,0 +1,309 @@
package messaging
import (
"context"
"errors"
"sync"
"testing"
"github.com/Azure/go-amqp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
pkgCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
)
type amqpConnMock struct {
mock.Mock
}
func (m *amqpConnMock) Done() <-chan struct{} {
args := m.Called()
return args.Get(0).(<-chan struct{})
}
func (m *amqpConnMock) NewSession(ctx context.Context, opts *amqp.SessionOptions) (AmqpSession, error) {
args := m.Called(ctx, opts)
return args.Get(0).(AmqpSession), args.Error(1)
}
func (m *amqpConnMock) Close() error {
args := m.Called()
return args.Error(0)
}
var _ AmqpConn = (*amqpConnMock)(nil)
type amqpDialMock struct {
mock.Mock
}
func (m *amqpDialMock) Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (AmqpConn, error) {
args := m.Called(ctx, addr, opts)
return args.Get(0).(AmqpConn), args.Error(1)
}
var _ amqpDial = (*amqpDialMock)(nil)
type amqpSessionMock struct {
mock.Mock
}
func (m *amqpSessionMock) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (AmqpSender, error) {
args := m.Called(ctx, target, opts)
return args.Get(0).(AmqpSender), args.Error(1)
}
func (m *amqpSessionMock) Close(ctx context.Context) error {
args := m.Called(ctx)
return args.Error(0)
}
var _ AmqpSession = (*amqpSessionMock)(nil)
func Test_AmqpConnection_IsClosed(t *testing.T) {
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
}
channelReceiver := func(channel chan struct{}) <-chan struct{} {
return channel
}
t.Run("is closed - connection nil", func(t *testing.T) {
assert.True(t, connection.IsClosed())
})
t.Run("is closed", func(t *testing.T) {
channel := make(chan struct{})
close(channel)
amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Done").Return(channelReceiver(channel))
connection.Conn = amqpConnMock
assert.True(t, connection.IsClosed())
})
t.Run("is not closed", func(t *testing.T) {
channel := make(chan struct{})
amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Done").Return(channelReceiver(channel))
connection.Conn = amqpConnMock
assert.False(t, connection.IsClosed())
})
}
func Test_AmqpConnection_Close(t *testing.T) {
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
}
t.Run("already closed", func(t *testing.T) {
assert.NoError(t, connection.Close())
})
t.Run("close error", func(t *testing.T) {
err := errors.New("test error")
amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Close").Return(err)
connection.Conn = amqpConnMock
assert.EqualError(t, connection.Close(), "internal close: connection close: test error")
assert.NotNil(t, connection.Conn)
amqpConnMock.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("close without error", func(t *testing.T) {
amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Close").Return(nil)
connection.Conn = amqpConnMock
assert.Nil(t, connection.Close())
assert.Nil(t, connection.Conn)
amqpConnMock.AssertNumberOfCalls(t, "Close", 1)
})
}
func Test_AmqpConnection_Connect(t *testing.T) {
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
}
t.Run("already connected", func(t *testing.T) {
connection.Conn = &amqpConnMock{}
assert.NoError(t, connection.Connect())
})
t.Run("dial error", func(t *testing.T) {
connection.Conn = nil
connection.Username = "user"
connection.Password = "pass"
amqpDialMock := &amqpDialMock{}
var c *amqpConnMock = nil
amqpDialMock.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error"))
connection.Dialer = amqpDialMock
assert.EqualError(t, connection.Connect(), "internal connect: dial: test error")
assert.Nil(t, connection.Conn)
})
t.Run("connect without error", func(t *testing.T) {
connection.Conn = nil
amqpDialMock := &amqpDialMock{}
amqpConn := &amqpConnMock{}
amqpDialMock.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(amqpConn, nil)
connection.Dialer = amqpDialMock
assert.NoError(t, connection.Connect())
assert.Equal(t, amqpConn, connection.Conn)
})
}
func Test_AmqpConnection_NewSender(t *testing.T) {
connection := &AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
}
channelReceiver := func(channel chan struct{}) <-chan struct{} {
return channel
}
t.Run("connection not initialized", func(t *testing.T) {
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "connection is not initialized")
assert.Nil(t, sender)
})
t.Run("connection is closed", func(t *testing.T) {
channel := make(chan struct{})
close(channel)
conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel))
connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "amqp connection is closed")
assert.Nil(t, sender)
})
t.Run("session error", func(t *testing.T) {
channel := make(chan struct{})
var session *amqpSessionMock = nil
conn := &amqpConnMock{}
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, errors.New("test error"))
conn.On("Done").Return(channelReceiver(channel))
connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "new session: test error")
assert.Nil(t, sender)
})
t.Run("sender error", func(t *testing.T) {
channel := make(chan struct{})
sessionMock := &amqpSessionMock{}
var amqpSender *amqp.Sender = nil
sessionMock.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(amqpSender, errors.New("test error"))
sessionMock.On("Close", mock.Anything).Return(nil)
conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel))
conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil)
connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "new internal sender: test error")
assert.Nil(t, sender)
})
t.Run("session close error", func(t *testing.T) {
channel := make(chan struct{})
sessionMock := &amqpSessionMock{}
var amqpSender *amqp.Sender = nil
sessionMock.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(amqpSender, errors.New("test error"))
sessionMock.On("Close", mock.Anything).Return(errors.New("close error"))
conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel))
conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil)
connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "new internal sender: test error\nclose session: close error")
assert.Nil(t, sender)
})
t.Run("get sender", func(t *testing.T) {
channel := make(chan struct{})
amqpSender := &amqp.Sender{}
sessionMock := &amqpSessionMock{}
sessionMock.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(amqpSender, nil)
conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel))
conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil)
connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.NoError(t, err)
assert.NotNil(t, sender)
assert.Equal(t, amqpSender, sender.Sender)
assert.Equal(t, sessionMock, sender.Session)
})
}
func Test_AmqpConnection_NewAmqpConnection(t *testing.T) {
config := pkgCommon.AmqpConnectionConfig{
BrokerUrl: "brokerUrl",
Username: "username",
Password: "password",
}
connection := NewAmqpConnection(config, "connectionName")
assert.NotNil(t, connection)
assert.Equal(t, connection.ConnectionName, "connectionName")
assert.Equal(t, connection.BrokerUrl, "brokerUrl")
assert.Equal(t, connection.Username, "username")
assert.Equal(t, connection.Password, "password")
assert.NotNil(t, connection.Dialer)
}
func Test_As(t *testing.T) {
t.Run("error", func(t *testing.T) {
value, err := As[amqp.Message](nil, errors.New("test error"))
assert.EqualError(t, err, "test error")
assert.Nil(t, value)
})
t.Run("value nil", func(t *testing.T) {
value, err := As[amqp.Message](nil, nil)
assert.NoError(t, err)
assert.Nil(t, value)
})
t.Run("value not not type", func(t *testing.T) {
value, err := As[amqp.Message](struct{}{}, nil)
assert.EqualError(t, err, "could not cast value: struct {}")
assert.Nil(t, value)
})
t.Run("cast", func(t *testing.T) {
var sessionAny any = &amqpSessionMock{}
value, err := As[amqpSessionMock](sessionAny, nil)
assert.NoError(t, err)
assert.NotNil(t, value)
})
}

View file

@ -0,0 +1,81 @@
package messaging
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/Azure/go-amqp"
)
const amqpTopicPrefix = "topic://"
type AmqpSender interface {
Send(ctx context.Context, msg *amqp.Message, opts *amqp.SendOptions) error
Close(ctx context.Context) error
}
type AmqpSenderSession struct {
Session AmqpSession
Sender AmqpSender
}
func (s *AmqpSenderSession) Send(
topic string,
data [][]byte,
contentType string,
applicationProperties map[string]any,
) error {
// check topic name
if !strings.HasPrefix(topic, amqpTopicPrefix) {
return fmt.Errorf(
"topic %q name lacks mandatory prefix %q",
topic,
amqpTopicPrefix,
)
}
if contentType == "" {
return errors.New("content-type is required")
}
// prepare the amqp message
message := amqp.Message{
Header: &amqp.MessageHeader{
Durable: true,
},
Properties: &amqp.MessageProperties{
To: &topic,
ContentType: &contentType,
},
ApplicationProperties: applicationProperties,
Data: data,
}
// send
ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn()
return s.Sender.Send(ctx, &message, nil)
}
func (s *AmqpSenderSession) Close() error {
ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn()
var closeErrors []error
senderErr := s.Sender.Close(ctx)
if senderErr != nil {
closeErrors = append(closeErrors, senderErr)
}
sessionErr := s.Session.Close(ctx)
if sessionErr != nil {
closeErrors = append(closeErrors, sessionErr)
}
if len(closeErrors) > 0 {
return errors.Join(closeErrors...)
}
return nil
}

View file

@ -0,0 +1,187 @@
package messaging
import (
"context"
"errors"
"testing"
"github.com/Azure/go-amqp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type amqpSenderMock struct {
mock.Mock
}
func (m *amqpSenderMock) Send(ctx context.Context, msg *amqp.Message, opts *amqp.SendOptions) error {
return m.Called(ctx, msg, opts).Error(0)
}
func (m *amqpSenderMock) Close(ctx context.Context) error {
return m.Called(ctx).Error(0)
}
var _ AmqpSender = (*amqpSenderMock)(nil)
func Test_AmqpSenderSession_Close(t *testing.T) {
t.Run("close without errors", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(nil)
session := &amqpSessionMock{}
session.On("Close", mock.Anything).Return(nil)
senderSession := &AmqpSenderSession{
Sender: sender,
Session: session,
}
err := senderSession.Close()
assert.NoError(t, err)
sender.AssertNumberOfCalls(t, "Close", 1)
session.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("close with sender error", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(errors.New("sender error"))
session := &amqpSessionMock{}
session.On("Close", mock.Anything).Return(nil)
senderSession := &AmqpSenderSession{
Sender: sender,
Session: session,
}
err := senderSession.Close()
assert.EqualError(t, err, "sender error")
sender.AssertNumberOfCalls(t, "Close", 1)
session.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("close with session error", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(nil)
session := &amqpSessionMock{}
session.On("Close", mock.Anything).Return(errors.New("session error"))
senderSession := &AmqpSenderSession{
Sender: sender,
Session: session,
}
err := senderSession.Close()
assert.EqualError(t, err, "session error")
sender.AssertNumberOfCalls(t, "Close", 1)
session.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("close with sender and session error", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(errors.New("sender error"))
session := &amqpSessionMock{}
session.On("Close", mock.Anything).Return(errors.New("session error"))
senderSession := &AmqpSenderSession{
Sender: sender,
Session: session,
}
err := senderSession.Close()
assert.EqualError(t, err, "sender error\nsession error")
sender.AssertNumberOfCalls(t, "Close", 1)
session.AssertNumberOfCalls(t, "Close", 1)
})
}
func Test_AmqpSenderSession_Send(t *testing.T) {
t.Run("invalid topic name", func(t *testing.T) {
sender := &amqpSenderMock{}
session := &amqpSessionMock{}
senderSession := &AmqpSenderSession{
Sender: sender,
Session: session,
}
data := [][]byte{[]byte("data")}
err := senderSession.Send("invalid", data, "application/json", map[string]interface{}{})
assert.EqualError(t, err, "topic \"invalid\" name lacks mandatory prefix \"topic://\"")
})
t.Run("content type missing", func(t *testing.T) {
sender := &amqpSenderMock{}
session := &amqpSessionMock{}
senderSession := &AmqpSenderSession{
Sender: sender,
Session: session,
}
data := [][]byte{[]byte("data")}
err := senderSession.Send("topic://some/name", data, "", map[string]interface{}{})
assert.EqualError(t, err, "content-type is required")
})
t.Run("send", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Send", mock.Anything, mock.Anything, mock.Anything).Return(nil)
session := &amqpSessionMock{}
senderSession := &AmqpSenderSession{
Sender: sender,
Session: session,
}
data := [][]byte{[]byte("data")}
applicationProperties := map[string]interface{}{}
applicationProperties["key"] = "value"
err := senderSession.Send("topic://some/name", data, "application/json", applicationProperties)
assert.NoError(t, err)
sender.AssertNumberOfCalls(t, "Send", 1)
calls := sender.Calls
assert.Equal(t, 1, len(calls))
ctx, isCtx := calls[0].Arguments[0].(context.Context)
assert.True(t, isCtx)
assert.NotNil(t, ctx)
message, isMsg := calls[0].Arguments[1].(*amqp.Message)
assert.True(t, isMsg)
assert.True(t, message.Header.Durable)
assert.Equal(t, "topic://some/name", *message.Properties.To)
assert.Equal(t, "application/json", *message.Properties.ContentType)
assert.Equal(t, applicationProperties, message.ApplicationProperties)
assert.Equal(t, data, message.Data)
senderOptions, isSenderOptions := calls[0].Arguments[2].(*amqp.SendOptions)
assert.True(t, isSenderOptions)
assert.Nil(t, senderOptions)
})
t.Run("send fails", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Send", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("send fail"))
session := &amqpSessionMock{}
senderSession := &AmqpSenderSession{
Sender: sender,
Session: session,
}
data := [][]byte{[]byte("data")}
applicationProperties := map[string]interface{}{}
applicationProperties["key"] = "value"
err := senderSession.Send("topic://some/name", data, "application/json", applicationProperties)
assert.EqualError(t, err, "send fail")
sender.AssertNumberOfCalls(t, "Send", 1)
})
}

View file

@ -2,31 +2,24 @@ package api
import ( import (
"context" "context"
"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"
"errors" "errors"
"go.opentelemetry.io/otel" "fmt"
"go.opentelemetry.io/otel/trace"
"strings" "strings"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
) )
const DataTypeLegacyAuditEventV1 = "audit.v1.LegacyAuditEvent" const DataTypeLegacyAuditEventV1 = "audit.v1.LegacyAuditEvent"
// LegacyTopicNameResolver implements TopicNameResolver. // StaticTopicNameConfig provides topic name information required for the topic name resolution.
// A hard-coded topic name is used, routing identifiers are ignored. type StaticTopicNameConfig struct {
type LegacyTopicNameResolver struct {
topicName string
}
// Resolve implements TopicNameResolver.Resolve
func (r *LegacyTopicNameResolver) Resolve(*RoutableIdentifier) (string, error) {
return r.topicName, nil
}
// LegacyTopicNameConfig provides topic name information required for the topic name resolution.
type LegacyTopicNameConfig struct {
TopicName string TopicName string
} }
@ -34,33 +27,39 @@ type LegacyTopicNameConfig struct {
// //
// Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented // Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented
type LegacyAuditApi struct { type LegacyAuditApi struct {
messagingApi messaging.Api messagingApi pkgMessagingApi.Api
topicNameResolver TopicNameResolver topicNameResolver pkgAuditCommon.TopicNameResolver
tracer trace.Tracer tracer trace.Tracer
validator ProtobufValidator validator pkgAuditCommon.ProtobufValidator
} }
// NewLegacyAuditApi can be used to initialize the audit log api. // NewLegacyAuditApi 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 // Note: The NewLegacyAuditApi method will be deprecated and replaced with "newRoutableAuditApi" once the new audit log routing is implemented
func NewLegacyAuditApi( func NewLegacyAuditApi(
messagingApi messaging.Api, messagingApi pkgMessagingApi.Api,
topicNameConfig LegacyTopicNameConfig, topicNameConfig StaticTopicNameConfig,
validator ProtobufValidator, validator pkgAuditCommon.ProtobufValidator,
) (AuditApi, error) { ) (pkgAuditCommon.AuditApi, error) {
if messagingApi == nil { if messagingApi == nil {
return nil, ErrMessagingApiNil return nil, pkgAuditCommon.ErrMessagingApiNil
} }
// Topic resolver // Topic resolver
if topicNameConfig.TopicName == "" { if topicNameConfig.TopicName == "" {
return nil, errors.New("topic name is required") return nil, errors.New("topic name is required")
} }
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: topicNameConfig.TopicName} if !pkgAuditCommon.TopicNamePattern.MatchString(topicNameConfig.TopicName) {
return nil, fmt.Errorf("invalid topic name: %s - "+
"expected stackit-platform/t/swz/audit-log/{region}/{version}/{eventSource}/{additionalParts} "+
"where region is one of [conway, eu01, eu02, sx-stoi01], version is vX.Y, eventSource is the service name "+
"and additionalParts is a describing string the audit log relates to or 'events'", topicNameConfig.TopicName)
}
var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: topicNameConfig.TopicName}
// Audit api // Audit api
var auditApi AuditApi = &LegacyAuditApi{ var auditApi pkgAuditCommon.AuditApi = &LegacyAuditApi{
messagingApi: messagingApi, messagingApi: messagingApi,
topicNameResolver: topicNameResolver, topicNameResolver: topicNameResolver,
tracer: otel.Tracer("legacy-audit-api"), tracer: otel.Tracer("legacy-audit-api"),
@ -75,7 +74,7 @@ func (a *LegacyAuditApi) Log(
ctx context.Context, ctx context.Context,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error { ) error {
cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier) cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier)
@ -92,21 +91,21 @@ func (a *LegacyAuditApi) ValidateAndSerialize(
ctx context.Context, ctx context.Context,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*CloudEvent, error) { ) (*pkgAuditCommon.CloudEvent, error) {
ctx, span := a.tracer.Start(ctx, "validate-and-serialize") ctx, span := a.tracer.Start(ctx, "validate-and-serialize")
defer span.End() defer span.End()
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routableIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Reject event type data-access as the downstream services // Reject event type data-access as the downstream services
// cannot handle it at the moment // cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) { if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
} }
// Do nothing with the serialized data in the legacy solution // Do nothing with the serialized data in the legacy solution
@ -116,19 +115,19 @@ func (a *LegacyAuditApi) ValidateAndSerialize(
} }
// Convert attributes // Convert attributes
legacyBytes, err := convertAndSerializeIntoLegacyFormat(event, routableEvent) legacyBytes, err := internalAuditApi.ConvertAndSerializeIntoLegacyFormat(event, routableEvent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
traceParent, traceState := TraceParentAndStateFromContext(ctx) traceParent, traceState := internalAuditApi.TraceParentAndStateFromContext(ctx)
message := CloudEvent{ message := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0", SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName, Source: event.ProtoPayload.ServiceName,
Id: event.InsertId, Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsJson, DataContentType: pkgAuditCommon.ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1, DataType: DataTypeLegacyAuditEventV1,
Subject: event.ProtoPayload.ResourceName, Subject: event.ProtoPayload.ResourceName,
Data: legacyBytes, Data: legacyBytes,
@ -141,15 +140,15 @@ func (a *LegacyAuditApi) ValidateAndSerialize(
// Send implements AuditApi.Send // Send implements AuditApi.Send
func (a *LegacyAuditApi) Send( func (a *LegacyAuditApi) Send(
ctx context.Context, ctx context.Context,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *CloudEvent, cloudEvent *pkgAuditCommon.CloudEvent,
) error { ) error {
if cloudEvent != nil && cloudEvent.TraceParent != nil && cloudEvent.TraceState != nil { if cloudEvent != nil && cloudEvent.TraceParent != nil && cloudEvent.TraceState != nil {
ctx = AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState) ctx = internalAuditApi.AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState)
} }
ctx, span := a.tracer.Start(ctx, "send") ctx, span := a.tracer.Start(ctx, "send")
defer span.End() defer span.End()
return send(a.topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent) return internalAuditApi.Send(a.topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
} }

View file

@ -4,14 +4,16 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"strings" "strings"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging" "go.opentelemetry.io/otel"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1" "go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
) )
type ContextKey string type ContextKey string
@ -26,25 +28,25 @@ var ErrTopicNameEmpty = errors.New("empty topic name provided")
// //
// Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented // Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented
type DynamicLegacyAuditApi struct { type DynamicLegacyAuditApi struct {
messagingApi messaging.Api messagingApi pkgMessagingApi.Api
tracer trace.Tracer tracer trace.Tracer
validator ProtobufValidator validator pkgAuditCommon.ProtobufValidator
} }
// NewDynamicLegacyAuditApi can be used to initialize the audit log api. // 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 // Note: The NewLegacyAuditApi method will be deprecated and replaced with "newRoutableAuditApi" once the new audit log routing is implemented
func NewDynamicLegacyAuditApi( func NewDynamicLegacyAuditApi(
messagingApi messaging.Api, messagingApi pkgMessagingApi.Api,
validator ProtobufValidator, validator pkgAuditCommon.ProtobufValidator,
) (AuditApi, error) { ) (pkgAuditCommon.AuditApi, error) {
if messagingApi == nil { if messagingApi == nil {
return nil, ErrMessagingApiNil return nil, pkgAuditCommon.ErrMessagingApiNil
} }
// Audit api // Audit api
var auditApi AuditApi = &DynamicLegacyAuditApi{ var auditApi pkgAuditCommon.AuditApi = &DynamicLegacyAuditApi{
messagingApi: messagingApi, messagingApi: messagingApi,
tracer: otel.Tracer("dynamic-legacy-audit-api"), tracer: otel.Tracer("dynamic-legacy-audit-api"),
validator: validator, validator: validator,
@ -58,7 +60,7 @@ func (a *DynamicLegacyAuditApi) Log(
ctx context.Context, ctx context.Context,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error { ) error {
cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier) cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier)
@ -75,21 +77,21 @@ func (a *DynamicLegacyAuditApi) ValidateAndSerialize(
ctx context.Context, ctx context.Context,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*CloudEvent, error) { ) (*pkgAuditCommon.CloudEvent, error) {
ctx, span := a.tracer.Start(ctx, "validate-and-serialize") ctx, span := a.tracer.Start(ctx, "validate-and-serialize")
defer span.End() defer span.End()
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routableIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Reject event type data-access as the downstream services // Reject event type data-access as the downstream services
// cannot handle it at the moment // cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) { if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
} }
// Do nothing with the serialized data in the legacy solution // Do nothing with the serialized data in the legacy solution
@ -99,19 +101,19 @@ func (a *DynamicLegacyAuditApi) ValidateAndSerialize(
} }
// Convert attributes // Convert attributes
legacyBytes, err := convertAndSerializeIntoLegacyFormat(event, routableEvent) legacyBytes, err := internalAuditApi.ConvertAndSerializeIntoLegacyFormat(event, routableEvent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
traceParent, traceState := TraceParentAndStateFromContext(ctx) traceParent, traceState := internalAuditApi.TraceParentAndStateFromContext(ctx)
message := CloudEvent{ message := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0", SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName, Source: event.ProtoPayload.ServiceName,
Id: event.InsertId, Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsJson, DataContentType: pkgAuditCommon.ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1, DataType: DataTypeLegacyAuditEventV1,
Subject: event.ProtoPayload.ResourceName, Subject: event.ProtoPayload.ResourceName,
Data: legacyBytes, Data: legacyBytes,
@ -126,8 +128,8 @@ func (a *DynamicLegacyAuditApi) ValidateAndSerialize(
// Requires to have the topic name set as key "topic" in the context. // Requires to have the topic name set as key "topic" in the context.
func (a *DynamicLegacyAuditApi) Send( func (a *DynamicLegacyAuditApi) Send(
ctx context.Context, ctx context.Context,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *CloudEvent, cloudEvent *pkgAuditCommon.CloudEvent,
) error { ) error {
rawTopicName := ctx.Value(ContextKeyTopic) rawTopicName := ctx.Value(ContextKeyTopic)
@ -135,17 +137,23 @@ func (a *DynamicLegacyAuditApi) Send(
return ErrNoTopicNameProvided return ErrNoTopicNameProvided
} }
topicName := fmt.Sprintf("%s", rawTopicName) topicName := fmt.Sprintf("%s", rawTopicName)
if len(topicName) == 0 { if topicName == "" {
return ErrTopicNameEmpty return ErrTopicNameEmpty
} }
if !pkgAuditCommon.TopicNamePattern.MatchString(topicName) {
return fmt.Errorf("invalid topic name: %s - "+
"expected stackit-platform/t/swz/audit-log/{region}/{version}/{eventSource}/{additionalParts} "+
"where region is one of [conway, eu01, eu02, sx-stoi01], version is vX.Y, eventSource is the service name "+
"and additionalParts is a describing string the audit log relates to or 'events'", topicName)
}
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: topicName} var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: topicName}
if cloudEvent != nil && cloudEvent.TraceParent != nil && cloudEvent.TraceState != nil { if cloudEvent != nil && cloudEvent.TraceParent != nil && cloudEvent.TraceState != nil {
ctx = AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState) ctx = internalAuditApi.AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState)
} }
ctx, span := a.tracer.Start(ctx, "send") ctx, span := a.tracer.Start(ctx, "send")
defer span.End() defer span.End()
return send(topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent) return internalAuditApi.Send(topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
} }

View file

@ -4,18 +4,22 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"go.opentelemetry.io/otel"
"net/url" "net/url"
"strings" "strings"
"testing" "testing"
"time" "time"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging" "buf.build/go/protovalidate"
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/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"go.opentelemetry.io/otel"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
pkgMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
pkgMessagingTest "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/test"
) )
func TestDynamicLegacyAuditApi(t *testing.T) { func TestDynamicLegacyAuditApi(t *testing.T) {
@ -25,19 +29,23 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
defer cancelFn() defer cancelFn()
// Start solace docker container // Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background()) solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err) assert.NoError(t, err)
defer solaceContainer.Stop() defer solaceContainer.Stop()
// Instantiate the messaging api // Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString}) amqpApi, err := pkgMessagingApi.NewAmqpApi(
pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
assert.NoError(t, err) assert.NoError(t, err)
// Validator // Validator
validator, err := protovalidate.New() validator, err := protovalidate.New()
assert.NoError(t, err) assert.NoError(t, err)
topicSubscriptionTopicPattern := "audit-log/>" topicSubscriptionTopicPattern := "stackit-platform/t/swz/audit-log/>"
// Check that event-type data-access is rejected as it is currently // Check that event-type data-access is rejected as it is currently
// not supported by downstream services // not supported by downstream services
@ -49,29 +57,29 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-rejected" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-rejected"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1) event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.ErrorIs(t, auditApi.Log( assert.ErrorIs(t, auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
), ErrUnsupportedEventTypeDataAccess) ), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
}) })
// Check logging of organization events // Check logging of organization events
@ -83,27 +91,27 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log( assert.NoError(t, auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -120,27 +128,27 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log( assert.NoError(t, auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -158,27 +166,27 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log( assert.NoError(t, auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -195,27 +203,27 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log( assert.NoError(t, auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -233,27 +241,27 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log( assert.NoError(t, auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -270,27 +278,27 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log( assert.NoError(t, auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -307,28 +315,28 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-system-changed" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event := newProjectSystemAuditEvent(nil) event := internalAuditApi.NewProjectSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, assert.NoError(t,
auditApi.Log( auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -341,7 +349,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName) assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
@ -363,28 +371,28 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/system-changed" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, assert.NoError(t,
auditApi.Log( auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -397,7 +405,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName) assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -418,29 +426,29 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
escapedQuery := url.QueryEscape("param=value") escapedQuery := url.QueryEscape("param=value")
event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctx := context.WithValue(ctx, ContextKeyTopic, topicName) ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log( assert.NoError(t, auditApi.Log(
ctx, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -461,15 +469,15 @@ func TestDynamicLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{ auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
@ -478,22 +486,22 @@ func TestDynamicLegacyAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{ auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
func TestDynamicLegacyAuditApi_Log_NilEvent(t *testing.T) { func TestDynamicLegacyAuditApi_Log_NilEvent(t *testing.T) {
auditApi := DynamicLegacyAuditApi{tracer: otel.Tracer("test")} auditApi := DynamicLegacyAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
} }
func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) { func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) {
@ -501,16 +509,16 @@ func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectI
objectIdentifier *auditV1.ObjectIdentifier) { objectIdentifier *auditV1.ObjectIdentifier) {
objectIdentifier.Type = "invalid" objectIdentifier.Type = "invalid"
} }
event, objectIdentifier := newProjectAuditEvent(&customization) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(&customization)
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(nil) validator.On("Validate", mock.Anything).Return(nil)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{ auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType) assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
} }

View file

@ -5,19 +5,23 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"go.opentelemetry.io/otel"
"net/url" "net/url"
"strings" "strings"
"testing" "testing"
"time" "time"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging" "buf.build/go/protovalidate"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/Azure/go-amqp" "github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"go.opentelemetry.io/otel"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
pkgMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
pkgMessagingTest "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/test"
) )
func TestLegacyAuditApi(t *testing.T) { func TestLegacyAuditApi(t *testing.T) {
@ -27,19 +31,22 @@ func TestLegacyAuditApi(t *testing.T) {
defer cancelFn() defer cancelFn()
// Start solace docker container // Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background()) solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err) assert.NoError(t, err)
defer solaceContainer.Stop() defer solaceContainer.Stop()
// Instantiate the messaging api // Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString}) amqpApi, err := pkgMessagingApi.NewAmqpApi(pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
assert.NoError(t, err) assert.NoError(t, err)
// Validator // Validator
validator, err := protovalidate.New() validator, err := protovalidate.New()
assert.NoError(t, err) assert.NoError(t, err)
topicSubscriptionTopicPattern := "audit-log/>" topicSubscriptionTopicPattern := "stackit-platform/t/swz/audit-log/>"
// Check that event-type data-access is rejected as it is currently // Check that event-type data-access is rejected as it is currently
// not supported by downstream services // not supported by downstream services
@ -51,28 +58,28 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-rejected" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-rejected"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1) event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
// Log the event to solace // Log the event to solace
assert.ErrorIs(t, auditApi.Log( assert.ErrorIs(t, auditApi.Log(
ctx, ctx,
event, event,
auditV1.Visibility_VISIBILITY_PUBLIC, auditV1.Visibility_VISIBILITY_PUBLIC,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
), ErrUnsupportedEventTypeDataAccess) ), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
}) })
// Check logging of organization events // Check logging of organization events
@ -84,19 +91,19 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -104,7 +111,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -121,19 +128,19 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -141,7 +148,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -159,19 +166,19 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -179,7 +186,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -196,19 +203,19 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -216,7 +223,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -234,19 +241,19 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -254,7 +261,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -271,19 +278,19 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -291,7 +298,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -308,19 +315,19 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/project-system-changed" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event := newProjectSystemAuditEvent(nil) event := internalAuditApi.NewProjectSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -329,7 +336,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -342,7 +349,7 @@ func TestLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName) assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
@ -364,19 +371,19 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/system-changed" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -385,7 +392,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -398,7 +405,7 @@ func TestLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName) assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -419,19 +426,19 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created" topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
escapedQuery := url.QueryEscape("param=value") escapedQuery := url.QueryEscape("param=value")
event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery
@ -441,7 +448,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -464,7 +471,7 @@ func validateSentMessage(
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
var severity string var severity string
@ -510,13 +517,13 @@ func validateSentMessageWithDetails(
// Check topic name // Check topic name
assert.Equal(t, topicName, *message.Properties.To) assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, ContentTypeCloudEventsJson, message.ApplicationProperties["cloudEvents:datacontenttype"]) assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsJson, message.ApplicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, DataTypeLegacyAuditEventV1, message.ApplicationProperties["cloudEvents:type"]) assert.Equal(t, DataTypeLegacyAuditEventV1, message.ApplicationProperties["cloudEvents:type"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName) assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -567,26 +574,29 @@ func validateSentMessageWithDetails(
func TestLegacyAuditApi_NewLegacyAuditApi(t *testing.T) { func TestLegacyAuditApi_NewLegacyAuditApi(t *testing.T) {
t.Run("messaging api nil", func(t *testing.T) { t.Run("messaging api nil", func(t *testing.T) {
auditApi, err := NewLegacyAuditApi(nil, LegacyTopicNameConfig{}, nil) auditApi, err := NewLegacyAuditApi(nil, StaticTopicNameConfig{}, nil)
assert.Nil(t, auditApi) assert.Nil(t, auditApi)
assert.EqualError(t, err, "messaging api nil") assert.EqualError(t, err, "messaging api nil")
}) })
t.Run("topic name is blank", func(t *testing.T) { t.Run("topic name is blank", func(t *testing.T) {
// Start solace docker container // Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background()) solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err) assert.NoError(t, err)
defer solaceContainer.Stop() defer solaceContainer.Stop()
// Instantiate the messaging api // Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString}) amqpApi, err := pkgMessagingApi.NewAmqpApi(pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
assert.NoError(t, err) assert.NoError(t, err)
// Validator // Validator
validator, err := protovalidate.New() validator, err := protovalidate.New()
assert.NoError(t, err) assert.NoError(t, err)
auditApi, err := NewLegacyAuditApi(messagingApi, LegacyTopicNameConfig{ auditApi, err := NewLegacyAuditApi(amqpApi, StaticTopicNameConfig{
TopicName: "", TopicName: "",
}, validator) }, validator)
@ -600,15 +610,15 @@ func TestLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := LegacyAuditApi{ auditApi := LegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
@ -617,22 +627,22 @@ func TestLegacyAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := LegacyAuditApi{ auditApi := LegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
func TestLegacyAuditApi_Log_NilEvent(t *testing.T) { func TestLegacyAuditApi_Log_NilEvent(t *testing.T) {
auditApi := LegacyAuditApi{tracer: otel.Tracer("test")} auditApi := LegacyAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
} }
func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) { func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) {
@ -640,16 +650,16 @@ func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifi
objectIdentifier *auditV1.ObjectIdentifier) { objectIdentifier *auditV1.ObjectIdentifier) {
objectIdentifier.Type = "invalid" objectIdentifier.Type = "invalid"
} }
event, objectIdentifier := newProjectAuditEvent(&customization) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(&customization)
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(nil) validator.On("Validate", mock.Anything).Return(nil)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := LegacyAuditApi{ auditApi := LegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType) assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
} }

View file

@ -3,30 +3,31 @@ package api
import ( import (
"context" "context"
"fmt" "fmt"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"strings" "strings"
"buf.build/go/protovalidate"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
"github.com/bufbuild/protovalidate-go" pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
// MockAuditApi is an implementation of AuditApi that does nothing and has no dependency to external systems. // MockAuditApi is an implementation of AuditApi that does nothing and has no dependency to external systems.
type MockAuditApi struct { type MockAuditApi struct {
tracer trace.Tracer tracer trace.Tracer
validator ProtobufValidator validator pkgAuditCommon.ProtobufValidator
} }
func NewMockAuditApi() (AuditApi, error) { func NewMockAuditApi() (pkgAuditCommon.AuditApi, error) {
validator, err := protovalidate.New() validator, err := protovalidate.New()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var auditApi AuditApi = &MockAuditApi{ var auditApi pkgAuditCommon.AuditApi = &MockAuditApi{
tracer: otel.Tracer("mock-audit-api"), tracer: otel.Tracer("mock-audit-api"),
validator: protobufValidator, validator: protobufValidator,
} }
@ -39,7 +40,7 @@ func (a *MockAuditApi) Log(
ctx context.Context, ctx context.Context,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error { ) error {
_, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier) _, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier)
@ -51,21 +52,21 @@ func (a *MockAuditApi) ValidateAndSerialize(
ctx context.Context, ctx context.Context,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*CloudEvent, error) { ) (*pkgAuditCommon.CloudEvent, error) {
ctx, span := a.tracer.Start(ctx, "validate-and-serialize") ctx, span := a.tracer.Start(ctx, "validate-and-serialize")
defer span.End() defer span.End()
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routableIdentifier) routableEvent, err := internalApi.ValidateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Reject event type data-access as the downstream services // Reject event type data-access as the downstream services
// cannot handle it at the moment // cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) { if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
} }
routableEventBytes, err := proto.Marshal(routableEvent) routableEventBytes, err := proto.Marshal(routableEvent)
@ -73,9 +74,9 @@ func (a *MockAuditApi) ValidateAndSerialize(
return nil, err return nil, err
} }
traceParent, traceState := TraceParentAndStateFromContext(ctx) traceParent, traceState := internalApi.TraceParentAndStateFromContext(ctx)
message := CloudEvent{ message := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0", SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName, Source: event.ProtoPayload.ServiceName,
Id: event.InsertId, Id: event.InsertId,
@ -92,6 +93,6 @@ func (a *MockAuditApi) ValidateAndSerialize(
} }
// Send implements AuditApi.Send // Send implements AuditApi.Send
func (a *MockAuditApi) Send(context.Context, *RoutableIdentifier, *CloudEvent) error { func (a *MockAuditApi) Send(context.Context, *pkgAuditCommon.RoutableIdentifier, *pkgAuditCommon.CloudEvent) error {
return nil return nil
} }

View file

@ -5,19 +5,38 @@ import (
"strings" "strings"
"testing" "testing"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1" "buf.build/go/protovalidate"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
type ProtobufValidatorMock struct {
mock.Mock
}
func (m *ProtobufValidatorMock) Validate(msg proto.Message, options ...protovalidate.ValidationOption) error {
var args mock.Arguments
if len(options) > 0 {
args = m.Called(msg, options)
} else {
args = m.Called(msg)
}
return args.Error(0)
}
func TestMockAuditApi_Log(t *testing.T) { func TestMockAuditApi_Log(t *testing.T) {
auditApi, err := NewMockAuditApi() auditApi, err := NewMockAuditApi()
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
routableObjectIdentifier := NewRoutableIdentifier(objectIdentifier) routableObjectIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)
// Test // Test
t.Run("Log", func(t *testing.T) { t.Run("Log", func(t *testing.T) {
@ -26,13 +45,13 @@ func TestMockAuditApi_Log(t *testing.T) {
}) })
t.Run("reject data access event", func(t *testing.T) { t.Run("reject data access event", func(t *testing.T) {
event, objectIdentifier := newOrganizationAuditEvent(nil) orgEvent, objIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1) orgEvent.LogName = strings.Replace(orgEvent.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
routableObjectIdentifier := NewRoutableIdentifier(objectIdentifier) rtIdentifier := pkgAuditCommon.NewRoutableIdentifier(objIdentifier)
assert.ErrorIs(t, auditApi.Log( assert.ErrorIs(t, auditApi.Log(
context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableObjectIdentifier), context.Background(), orgEvent, auditV1.Visibility_VISIBILITY_PUBLIC, rtIdentifier),
ErrUnsupportedEventTypeDataAccess) pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
}) })
t.Run("ValidateAndSerialize", func(t *testing.T) { t.Run("ValidateAndSerialize", func(t *testing.T) {
@ -50,11 +69,11 @@ func TestMockAuditApi_Log(t *testing.T) {
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
_, err := auditApi.ValidateAndSerialize(context.Background(), nil, visibility, routableObjectIdentifier) _, err := auditApi.ValidateAndSerialize(context.Background(), nil, visibility, routableObjectIdentifier)
assert.ErrorIs(t, err, ErrEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
}) })
t.Run("Send", func(t *testing.T) { t.Run("Send", func(t *testing.T) {
var cloudEvent = CloudEvent{} var cloudEvent = pkgAuditCommon.CloudEvent{}
assert.Nil(t, auditApi.Send(context.Background(), routableObjectIdentifier, &cloudEvent)) assert.Nil(t, auditApi.Send(context.Background(), routableObjectIdentifier, &cloudEvent))
}) })

View file

@ -4,14 +4,16 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"strings" "strings"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"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" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
) )
// routableTopicNameResolver implements TopicNameResolver. // routableTopicNameResolver implements TopicNameResolver.
@ -25,23 +27,23 @@ type routableTopicNameResolver struct {
} }
// Resolve implements TopicNameResolver.Resolve. // Resolve implements TopicNameResolver.Resolve.
func (r *routableTopicNameResolver) Resolve(routableIdentifier *RoutableIdentifier) (string, error) { func (r *routableTopicNameResolver) Resolve(routableIdentifier *pkgAuditCommon.RoutableIdentifier) (string, error) {
if routableIdentifier == nil { if routableIdentifier == nil {
return "", ErrObjectIdentifierNil return "", pkgAuditCommon.ErrObjectIdentifierNil
} }
switch routableIdentifier.Type { switch routableIdentifier.Type {
case ObjectTypeOrganization: case pkgAuditCommon.ObjectTypeOrganization:
return fmt.Sprintf("topic://%s/%s", r.organizationTopicPrefix, routableIdentifier.Identifier), nil return fmt.Sprintf("topic://%s/%s", r.organizationTopicPrefix, routableIdentifier.Identifier), nil
case ObjectTypeProject: case pkgAuditCommon.ObjectTypeProject:
return fmt.Sprintf("topic://%s/%s", r.projectTopicPrefix, routableIdentifier.Identifier), nil return fmt.Sprintf("topic://%s/%s", r.projectTopicPrefix, routableIdentifier.Identifier), nil
case ObjectTypeFolder: case pkgAuditCommon.ObjectTypeFolder:
return fmt.Sprintf("topic://%s/%s", r.folderTopicPrefix, routableIdentifier.Identifier), nil return fmt.Sprintf("topic://%s/%s", r.folderTopicPrefix, routableIdentifier.Identifier), nil
case ObjectTypeSystem: case pkgAuditCommon.ObjectTypeSystem:
return r.systemTopicName, nil return r.systemTopicName, nil
default: default:
return "", ErrUnsupportedObjectIdentifierType return "", pkgAuditCommon.ErrUnsupportedObjectIdentifierType
} }
} }
@ -57,18 +59,18 @@ type topicNameConfig struct {
// Warning: It is only there for local (compatibility) testing. // Warning: It is only there for local (compatibility) testing.
// DO NOT USE IT! // DO NOT USE IT!
type routableAuditApi struct { type routableAuditApi struct {
messagingApi messaging.Api messagingApi pkgMessagingApi.Api
topicNameResolver TopicNameResolver topicNameResolver pkgAuditCommon.TopicNameResolver
tracer trace.Tracer tracer trace.Tracer
validator ProtobufValidator validator pkgAuditCommon.ProtobufValidator
} }
// NewRoutableAuditApi can be used to initialize the audit log api. // NewRoutableAuditApi can be used to initialize the audit log api.
func newRoutableAuditApi( func newRoutableAuditApi(
messagingApi messaging.Api, messagingApi pkgMessagingApi.Api,
topicNameConfig topicNameConfig, topicNameConfig topicNameConfig,
validator ProtobufValidator, validator pkgAuditCommon.ProtobufValidator,
) (AuditApi, error) { ) (pkgAuditCommon.AuditApi, error) {
if messagingApi == nil { if messagingApi == nil {
return nil, errors.New("messaging api nil") return nil, errors.New("messaging api nil")
@ -88,7 +90,7 @@ func newRoutableAuditApi(
return nil, errors.New("system topic name is required") return nil, errors.New("system topic name is required")
} }
var topicNameResolver TopicNameResolver = &routableTopicNameResolver{ var topicNameResolver pkgAuditCommon.TopicNameResolver = &routableTopicNameResolver{
folderTopicPrefix: topicNameConfig.FolderTopicPrefix, folderTopicPrefix: topicNameConfig.FolderTopicPrefix,
organizationTopicPrefix: topicNameConfig.OrganizationTopicPrefix, organizationTopicPrefix: topicNameConfig.OrganizationTopicPrefix,
projectTopicPrefix: topicNameConfig.ProjectTopicPrefix, projectTopicPrefix: topicNameConfig.ProjectTopicPrefix,
@ -96,7 +98,7 @@ func newRoutableAuditApi(
} }
// Audit api // Audit api
var auditApi AuditApi = &routableAuditApi{ var auditApi pkgAuditCommon.AuditApi = &routableAuditApi{
messagingApi: messagingApi, messagingApi: messagingApi,
topicNameResolver: topicNameResolver, topicNameResolver: topicNameResolver,
tracer: otel.Tracer("routable-audit-api"), tracer: otel.Tracer("routable-audit-api"),
@ -111,7 +113,7 @@ func (a *routableAuditApi) Log(
ctx context.Context, ctx context.Context,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error { ) error {
cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier) cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier)
@ -127,13 +129,13 @@ func (a *routableAuditApi) ValidateAndSerialize(
ctx context.Context, ctx context.Context,
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
visibility auditV1.Visibility, visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*CloudEvent, error) { ) (*pkgAuditCommon.CloudEvent, error) {
ctx, span := a.tracer.Start(ctx, "validate-and-serialize") ctx, span := a.tracer.Start(ctx, "validate-and-serialize")
defer span.End() defer span.End()
routableEvent, err := validateAndSerializePartially( routableEvent, err := internalAuditApi.ValidateAndSerializePartially(
a.validator, a.validator,
event, event,
visibility, visibility,
@ -145,8 +147,8 @@ func (a *routableAuditApi) ValidateAndSerialize(
// Reject event type data-access as the downstream services // Reject event type data-access as the downstream services
// cannot handle it at the moment // cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) { if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
} }
routableEventBytes, err := proto.Marshal(routableEvent) routableEventBytes, err := proto.Marshal(routableEvent)
@ -154,14 +156,14 @@ func (a *routableAuditApi) ValidateAndSerialize(
return nil, err return nil, err
} }
traceParent, traceState := TraceParentAndStateFromContext(ctx) traceParent, traceState := internalAuditApi.TraceParentAndStateFromContext(ctx)
message := CloudEvent{ message := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0", SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName, Source: event.ProtoPayload.ServiceName,
Id: event.InsertId, Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsProtobuf, DataContentType: pkgAuditCommon.ContentTypeCloudEventsProtobuf,
DataType: fmt.Sprintf("%v", routableEvent.ProtoReflect().Descriptor().FullName()), DataType: fmt.Sprintf("%v", routableEvent.ProtoReflect().Descriptor().FullName()),
Subject: event.ProtoPayload.ResourceName, Subject: event.ProtoPayload.ResourceName,
Data: routableEventBytes, Data: routableEventBytes,
@ -175,15 +177,15 @@ func (a *routableAuditApi) ValidateAndSerialize(
// Send implements AuditApi.Send // Send implements AuditApi.Send
func (a *routableAuditApi) Send( func (a *routableAuditApi) Send(
ctx context.Context, ctx context.Context,
routableIdentifier *RoutableIdentifier, routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *CloudEvent, cloudEvent *pkgAuditCommon.CloudEvent,
) error { ) error {
if cloudEvent != nil && cloudEvent.TraceParent != nil && cloudEvent.TraceState != nil { if cloudEvent != nil && cloudEvent.TraceParent != nil && cloudEvent.TraceState != nil {
ctx = AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState) ctx = internalAuditApi.AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState)
} }
ctx, span := a.tracer.Start(ctx, "send") ctx, span := a.tracer.Start(ctx, "send")
defer span.End() defer span.End()
return send(a.topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent) return internalAuditApi.Send(a.topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
} }

View file

@ -4,21 +4,24 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"go.opentelemetry.io/otel"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/google/uuid" "buf.build/go/protovalidate"
"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/Azure/go-amqp" "github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"go.opentelemetry.io/otel"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
pgkMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
pkgMessagingTest "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/test"
) )
func TestRoutableAuditApi(t *testing.T) { func TestRoutableAuditApi(t *testing.T) {
@ -28,12 +31,15 @@ func TestRoutableAuditApi(t *testing.T) {
defer cancelFn() defer cancelFn()
// Start solace docker container // Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background()) solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err) assert.NoError(t, err)
defer solaceContainer.Stop() defer solaceContainer.Stop()
// Instantiate the messaging api // Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString}) amqpApi, err := pkgMessagingApi.NewAmqpApi(pgkMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pgkMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
assert.NoError(t, err) assert.NoError(t, err)
// Validator // Validator
@ -47,7 +53,7 @@ func TestRoutableAuditApi(t *testing.T) {
systemTopicName := "topic://system/admin-events" systemTopicName := "topic://system/admin-events"
auditApi, err := newRoutableAuditApi( auditApi, err := newRoutableAuditApi(
messagingApi, amqpApi,
topicNameConfig{ topicNameConfig{
FolderTopicPrefix: folderTopicPrefix, FolderTopicPrefix: folderTopicPrefix,
OrganizationTopicPrefix: organizationTopicPrefix, OrganizationTopicPrefix: organizationTopicPrefix,
@ -68,8 +74,8 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1) event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -77,7 +83,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier)), ErrUnsupportedEventTypeDataAccess) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
}) })
// Check logging of organization events // Check logging of organization events
@ -90,7 +96,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -98,7 +104,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err) assert.NoError(t, err)
@ -121,7 +127,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
topicName := fmt.Sprintf("org/%s", objectIdentifier.Identifier) topicName := fmt.Sprintf("org/%s", objectIdentifier.Identifier)
assert.NoError( assert.NoError(
t, t,
@ -134,7 +140,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace // Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -160,7 +166,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -168,7 +174,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err) assert.NoError(t, err)
@ -191,7 +197,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
topicName := fmt.Sprintf("folder/%s", objectIdentifier.Identifier) topicName := fmt.Sprintf("folder/%s", objectIdentifier.Identifier)
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicName))
@ -202,7 +208,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace // Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -227,7 +233,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -236,7 +242,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace // Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -260,7 +266,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -269,7 +275,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace // Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -294,7 +300,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*"))
// Instantiate test data // Instantiate test data
event := newProjectSystemAuditEvent(nil) event := internalAuditApi.NewProjectSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -303,7 +309,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -329,7 +335,7 @@ func TestRoutableAuditApi(t *testing.T) {
validateRoutableEventPayload( validateRoutableEventPayload(
t, t,
message.Data[0], message.Data[0],
RoutableSystemIdentifier.ToObjectIdentifier(), pkgAuditCommon.RoutableSystemIdentifier.ToObjectIdentifier(),
event, event,
"stackit.resourcemanager.v2.system.changed", "stackit.resourcemanager.v2.system.changed",
visibility) visibility)
@ -344,7 +350,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*"))
// Instantiate test data // Instantiate test data
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -353,7 +359,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -379,7 +385,7 @@ func TestRoutableAuditApi(t *testing.T) {
validateRoutableEventPayload( validateRoutableEventPayload(
t, t,
message.Data[0], message.Data[0],
SystemIdentifier, pkgAuditCommon.SystemIdentifier,
event, event,
"stackit.resourcemanager.v2.system.changed", "stackit.resourcemanager.v2.system.changed",
visibility) visibility)
@ -395,7 +401,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -403,7 +409,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err) assert.NoError(t, err)
@ -441,7 +447,7 @@ func validateSentEvent(
_, isUuid := uuid.Parse(fmt.Sprintf("%s", applicationProperties["cloudEvents:id"])) _, isUuid := uuid.Parse(fmt.Sprintf("%s", applicationProperties["cloudEvents:id"]))
assert.True(t, true, isUuid) assert.True(t, true, isUuid)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().UnixMilli(), applicationProperties["cloudEvents:time"]) assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"]) assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "audit.v1.RoutableAuditEvent", applicationProperties["cloudEvents:type"]) assert.Equal(t, "audit.v1.RoutableAuditEvent", applicationProperties["cloudEvents:type"])
assert.Equal(t, "", applicationProperties["cloudEvents:traceparent"]) assert.Equal(t, "", applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", applicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", applicationProperties["cloudEvents:tracestate"])
@ -483,8 +489,8 @@ func validateRoutableEventPayload(
func TestRoutableTopicNameResolver_Resolve_UnsupportedIdentifierType(t *testing.T) { func TestRoutableTopicNameResolver_Resolve_UnsupportedIdentifierType(t *testing.T) {
resolver := routableTopicNameResolver{} resolver := routableTopicNameResolver{}
_, err := resolver.Resolve(NewRoutableIdentifier(&auditV1.ObjectIdentifier{Type: "unsupported"})) _, err := resolver.Resolve(pkgAuditCommon.NewRoutableIdentifier(&auditV1.ObjectIdentifier{Type: "unsupported"}))
assert.ErrorIs(t, err, ErrUnsupportedObjectIdentifierType) assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedObjectIdentifierType)
} }
func TestNewRoutableAuditApi_NewRoutableAuditApi_MessagingApiNil(t *testing.T) { func TestNewRoutableAuditApi_NewRoutableAuditApi_MessagingApiNil(t *testing.T) {
@ -498,15 +504,15 @@ func TestRoutableAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := routableAuditApi{ auditApi := routableAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
@ -515,20 +521,20 @@ func TestRoutableAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := routableAuditApi{ auditApi := routableAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
func TestRoutableAuditApi_Log_NilEvent(t *testing.T) { func TestRoutableAuditApi_Log_NilEvent(t *testing.T) {
auditApi := routableAuditApi{tracer: otel.Tracer("test")} auditApi := routableAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
} }

View file

@ -5,6 +5,8 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"strings" "strings"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
const base64AuditEventV1 = "v1" const base64AuditEventV1 = "v1"
@ -14,16 +16,16 @@ var ErrRoutableIdentifierNil = errors.New("routableIdentifier must not be nil")
var ErrUnsupportedBase64StringVersion = errors.New("unsupported base64 cloud event string version") var ErrUnsupportedBase64StringVersion = errors.New("unsupported base64 cloud event string version")
type serializableEvent struct { type serializableEvent struct {
CloudEvent CloudEvent `json:"cloudEvent"` CloudEvent pkgAuditCommon.CloudEvent `json:"cloudEvent"`
RoutableIdentifier RoutableIdentifier `json:"routableIdentifier"` RoutableIdentifier pkgAuditCommon.RoutableIdentifier `json:"routableIdentifier"`
} }
func ToBase64( func ToBase64(
cloudEvent *CloudEvent, cloudEvent *pkgAuditCommon.CloudEvent,
routableIdentifier *RoutableIdentifier) (*string, error) { routableIdentifier *pkgAuditCommon.RoutableIdentifier) (*string, error) {
if cloudEvent == nil { if cloudEvent == nil {
return nil, ErrCloudEventNil return nil, pkgAuditCommon.ErrCloudEventNil
} }
if routableIdentifier == nil { if routableIdentifier == nil {
@ -41,11 +43,11 @@ func ToBase64(
} }
base64Str := base64.StdEncoding.EncodeToString(serializedEvent) base64Str := base64.StdEncoding.EncodeToString(serializedEvent)
base64Str = base64Str + base64AuditEventV1 base64Str += base64AuditEventV1
return &base64Str, nil return &base64Str, nil
} }
func FromBase64(base64Str string) (*CloudEvent, *RoutableIdentifier, error) { func FromBase64(base64Str string) (*pkgAuditCommon.CloudEvent, *pkgAuditCommon.RoutableIdentifier, error) {
if base64Str == "" { if base64Str == "" {
return nil, nil, ErrBase64StringEmpty return nil, nil, ErrBase64StringEmpty
} }

View file

@ -2,24 +2,27 @@ package api
import ( import (
"encoding/base64" "encoding/base64"
"github.com/stretchr/testify/assert"
"testing" "testing"
"github.com/stretchr/testify/assert"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
func Test_ToBase64(t *testing.T) { func Test_ToBase64(t *testing.T) {
t.Run("cloud event nil", func(t *testing.T) { t.Run("cloud event nil", func(t *testing.T) {
var cloudEvent *CloudEvent = nil var cloudEvent *pkgAuditCommon.CloudEvent = nil
routableIdentifier := RoutableSystemIdentifier routableIdentifier := pkgAuditCommon.RoutableSystemIdentifier
base64str, err := ToBase64(cloudEvent, routableIdentifier) base64str, err := ToBase64(cloudEvent, routableIdentifier)
assert.ErrorIs(t, err, ErrCloudEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrCloudEventNil)
assert.Nil(t, base64str) assert.Nil(t, base64str)
}) })
t.Run("routable identifier nil", func(t *testing.T) { t.Run("routable identifier nil", func(t *testing.T) {
cloudEvent := &CloudEvent{} cloudEvent := &pkgAuditCommon.CloudEvent{}
var routableIdentifier *RoutableIdentifier = nil var routableIdentifier *pkgAuditCommon.RoutableIdentifier = nil
base64str, err := ToBase64(cloudEvent, routableIdentifier) base64str, err := ToBase64(cloudEvent, routableIdentifier)
assert.ErrorIs(t, err, ErrRoutableIdentifierNil) assert.ErrorIs(t, err, ErrRoutableIdentifierNil)
@ -27,8 +30,8 @@ func Test_ToBase64(t *testing.T) {
}) })
t.Run("encoded event", func(t *testing.T) { t.Run("encoded event", func(t *testing.T) {
e := &CloudEvent{} e := &pkgAuditCommon.CloudEvent{}
r := RoutableSystemIdentifier r := pkgAuditCommon.RoutableSystemIdentifier
base64str, err := ToBase64(e, r) base64str, err := ToBase64(e, r)
assert.NoError(t, err) assert.NoError(t, err)
@ -72,8 +75,8 @@ func Test_FromBase64(t *testing.T) {
}) })
t.Run("decoded event", func(t *testing.T) { t.Run("decoded event", func(t *testing.T) {
e := &CloudEvent{} e := &pkgAuditCommon.CloudEvent{}
r := RoutableSystemIdentifier r := pkgAuditCommon.RoutableSystemIdentifier
base64str, err := ToBase64(e, r) base64str, err := ToBase64(e, r)
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -2,14 +2,18 @@ package api
import ( import (
"context" "context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/utils"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"errors" "errors"
"fmt" "fmt"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"time"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgAuditUtils "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/utils"
) )
const quadZero = "0.0.0.0" const quadZero = "0.0.0.0"
@ -22,7 +26,7 @@ type AuditParameters struct {
Details map[string]interface{} Details map[string]interface{}
// The type of the event // The type of the event
EventType EventType EventType pkgAuditCommon.EventType
// A set of user-defined (key, value) data that provides additional // A set of user-defined (key, value) data that provides additional
// information about the log entry. // information about the log entry.
@ -32,7 +36,7 @@ type AuditParameters struct {
ObjectId string ObjectId string
// Type of the object, the audit event refers to // Type of the object, the audit event refers to
ObjectType ObjectType ObjectType pkgAuditCommon.ObjectType
ResponseBody any ResponseBody any
@ -42,14 +46,14 @@ type AuditParameters struct {
func getObjectIdAndTypeFromAuditParams( func getObjectIdAndTypeFromAuditParams(
auditParams *AuditParameters, auditParams *AuditParameters,
) (string, *ObjectType, error) { ) (string, *pkgAuditCommon.ObjectType, error) {
objectId := auditParams.ObjectId objectId := auditParams.ObjectId
if objectId == "" { if objectId == "" {
return "", nil, errors.New("object id missing") return "", nil, errors.New("object id missing")
} }
var objectType *ObjectType var objectType *pkgAuditCommon.ObjectType
if auditParams.ObjectType != "" { if auditParams.ObjectType != "" {
objectType = &auditParams.ObjectType objectType = &auditParams.ObjectType
} }
@ -66,9 +70,9 @@ func getObjectIdAndTypeFromAuditParams(
// AuditLogEntryBuilder collects audit params to construct auditV1.AuditLogEntry // AuditLogEntryBuilder collects audit params to construct auditV1.AuditLogEntry
type AuditLogEntryBuilder struct { type AuditLogEntryBuilder struct {
auditParams AuditParameters auditParams AuditParameters
auditRequest AuditRequest auditRequest internalAuditApi.AuditRequest
auditResponse AuditResponse auditResponse internalAuditApi.AuditResponse
auditMetadata AuditMetadata auditMetadata internalAuditApi.AuditMetadata
// Region and optional zone id. If both, separated with a - (dash). // Region and optional zone id. If both, separated with a - (dash).
// Example: eu01 // Example: eu01
@ -88,23 +92,23 @@ func NewAuditLogEntryBuilder() *AuditLogEntryBuilder {
return &AuditLogEntryBuilder{ return &AuditLogEntryBuilder{
auditParams: AuditParameters{ auditParams: AuditParameters{
EventType: EventTypeAdminActivity, EventType: pkgAuditCommon.EventTypeAdminActivity,
}, },
auditRequest: AuditRequest{ auditRequest: internalAuditApi.AuditRequest{
Request: &ApiRequest{}, Request: &pkgAuditCommon.ApiRequest{},
RequestClientIP: quadZero, RequestClientIP: quadZero,
RequestCorrelationId: nil, RequestCorrelationId: nil,
RequestId: nil, RequestId: nil,
RequestTime: &requestTime, RequestTime: &requestTime,
}, },
auditResponse: AuditResponse{ auditResponse: internalAuditApi.AuditResponse{
ResponseBodyBytes: nil, ResponseBodyBytes: nil,
ResponseStatusCode: 200, ResponseStatusCode: 200,
ResponseHeaders: make(map[string][]string), ResponseHeaders: make(map[string][]string),
ResponseNumItems: nil, ResponseNumItems: nil,
ResponseTime: nil, ResponseTime: nil,
}, },
auditMetadata: AuditMetadata{ auditMetadata: internalAuditApi.AuditMetadata{
AuditInsertId: "", AuditInsertId: "",
AuditLabels: nil, AuditLabels: nil,
AuditLogName: "", AuditLogName: "",
@ -124,7 +128,7 @@ func NewAuditLogEntryBuilder() *AuditLogEntryBuilder {
func (builder *AuditLogEntryBuilder) AsSystemEvent() *AuditLogEntryBuilder { func (builder *AuditLogEntryBuilder) AsSystemEvent() *AuditLogEntryBuilder {
if builder.auditRequest.Request == nil { if builder.auditRequest.Request == nil {
builder.auditRequest.Request = &ApiRequest{} builder.auditRequest.Request = &pkgAuditCommon.ApiRequest{}
} }
if builder.auditRequest.Request.Header == nil { if builder.auditRequest.Request.Header == nil {
builder.auditRequest.Request.Header = map[string][]string{"user-agent": {"none"}} builder.auditRequest.Request.Header = map[string][]string{"user-agent": {"none"}}
@ -147,16 +151,25 @@ func (builder *AuditLogEntryBuilder) AsSystemEvent() *AuditLogEntryBuilder {
if builder.auditRequest.RequestClientIP == "" { if builder.auditRequest.RequestClientIP == "" {
builder.auditRequest.RequestClientIP = quadZero builder.auditRequest.RequestClientIP = quadZero
} }
builder.WithEventType(EventTypeSystemEvent) builder.WithEventType(pkgAuditCommon.EventTypeSystemEvent)
return builder return builder
} }
// WithRequiredApiRequest adds api request details // WithRequiredApiRequest adds api request details
func (builder *AuditLogEntryBuilder) WithRequiredApiRequest(request ApiRequest) *AuditLogEntryBuilder { func (builder *AuditLogEntryBuilder) WithRequiredApiRequest(request pkgAuditCommon.ApiRequest) *AuditLogEntryBuilder {
builder.auditRequest.Request = &request builder.auditRequest.Request = &request
return builder return builder
} }
// GetApiRequest returns the api request details
func (builder *AuditLogEntryBuilder) GetApiRequest() *pkgAuditCommon.ApiRequest {
return builder.auditRequest.Request
}
func (builder *AuditLogEntryBuilder) GetApiRequestBody() *pkgAuditCommon.ApiRequest {
return builder.auditRequest.Request
}
// WithRequiredLocation adds the region and optional zone id. If both, separated with a - (dash). // WithRequiredLocation adds the region and optional zone id. If both, separated with a - (dash).
// Example: eu01 // Example: eu01
func (builder *AuditLogEntryBuilder) WithRequiredLocation(location string) *AuditLogEntryBuilder { func (builder *AuditLogEntryBuilder) WithRequiredLocation(location string) *AuditLogEntryBuilder {
@ -209,7 +222,7 @@ func (builder *AuditLogEntryBuilder) WithRequiredObjectId(objectId string) *Audi
// WithRequiredObjectType adds the object type. // WithRequiredObjectType adds the object type.
// May be prefilled by audit middleware (if the type can be extracted from the url path). // May be prefilled by audit middleware (if the type can be extracted from the url path).
func (builder *AuditLogEntryBuilder) WithRequiredObjectType(objectType ObjectType) *AuditLogEntryBuilder { func (builder *AuditLogEntryBuilder) WithRequiredObjectType(objectType pkgAuditCommon.ObjectType) *AuditLogEntryBuilder {
builder.auditParams.ObjectType = objectType builder.auditParams.ObjectType = objectType
return builder return builder
} }
@ -266,7 +279,7 @@ func (builder *AuditLogEntryBuilder) WithNumResponseItems(numResponseItems int64
} }
// WithEventType overwrites the default event type EventTypeAdminActivity // WithEventType overwrites the default event type EventTypeAdminActivity
func (builder *AuditLogEntryBuilder) WithEventType(eventType EventType) *AuditLogEntryBuilder { func (builder *AuditLogEntryBuilder) WithEventType(eventType pkgAuditCommon.EventType) *AuditLogEntryBuilder {
builder.auditParams.EventType = eventType builder.auditParams.EventType = eventType
return builder return builder
} }
@ -296,7 +309,7 @@ func (builder *AuditLogEntryBuilder) WithResponseBody(responseBody any) *AuditLo
} }
// WithResponseBodyBytes adds the response body as bytes (serialized json or protobuf message expected) // WithResponseBodyBytes adds the response body as bytes (serialized json or protobuf message expected)
func (builder *AuditLogEntryBuilder) WithResponseBodyBytes(responseBody *[]byte) *AuditLogEntryBuilder { func (builder *AuditLogEntryBuilder) WithResponseBodyBytes(responseBody []byte) *AuditLogEntryBuilder {
builder.auditResponse.ResponseBodyBytes = responseBody builder.auditResponse.ResponseBodyBytes = responseBody
return builder return builder
} }
@ -346,26 +359,26 @@ func (builder *AuditLogEntryBuilder) Build(ctx context.Context, sequenceNumber S
resourceName := fmt.Sprintf("%s/%s", objectType.Plural(), objectId) resourceName := fmt.Sprintf("%s/%s", objectType.Plural(), objectId)
var logIdentifier string var logIdentifier string
var logType ObjectType var logType pkgAuditCommon.ObjectType
if builder.auditParams.EventType == EventTypeSystemEvent { if builder.auditParams.EventType == pkgAuditCommon.EventTypeSystemEvent {
logIdentifier = SystemIdentifier.Identifier logIdentifier = pkgAuditCommon.SystemIdentifier.Identifier
logType = ObjectTypeSystem logType = pkgAuditCommon.ObjectTypeSystem
} else { } else {
logIdentifier = objectId logIdentifier = objectId
logType = *objectType logType = *objectType
} }
builder.auditMetadata.AuditInsertId = NewInsertId(time.Now().UTC(), builder.location, builder.workerId, uint64(sequenceNumber)) builder.auditMetadata.AuditInsertId = internalAuditApi.NewInsertId(time.Now().UTC(), builder.location, builder.workerId, uint64(sequenceNumber))
builder.auditMetadata.AuditLogName = fmt.Sprintf("%s/%s/logs/%s", logType.Plural(), logIdentifier, builder.auditParams.EventType) builder.auditMetadata.AuditLogName = fmt.Sprintf("%s/%s/logs/%s", logType.Plural(), logIdentifier, builder.auditParams.EventType)
builder.auditMetadata.AuditResourceName = resourceName builder.auditMetadata.AuditResourceName = resourceName
var details *map[string]interface{} = nil var details map[string]interface{}
if len(builder.auditParams.Details) > 0 { if len(builder.auditParams.Details) > 0 {
details = &builder.auditParams.Details details = builder.auditParams.Details
} }
// Instantiate the audit event // Instantiate the audit event
return NewAuditLogEntry( return internalAuditApi.NewAuditLogEntry(
builder.auditRequest, builder.auditRequest,
builder.auditResponse, builder.auditResponse,
details, details,
@ -378,7 +391,7 @@ func (builder *AuditLogEntryBuilder) Build(ctx context.Context, sequenceNumber S
type AuditEventBuilder struct { type AuditEventBuilder struct {
// The audit api used to validate, serialize and send events // The audit api used to validate, serialize and send events
api AuditApi api pkgAuditCommon.AuditApi
// The audit log entry builder which is used to build the actual protobuf message // The audit log entry builder which is used to build the actual protobuf message
auditLogEntryBuilder *AuditLogEntryBuilder auditLogEntryBuilder *AuditLogEntryBuilder
@ -387,7 +400,7 @@ type AuditEventBuilder struct {
built bool built bool
// Sequence number generator providing sequential increasing numbers for the insert IDs // Sequence number generator providing sequential increasing numbers for the insert IDs
sequenceNumberGenerator utils.SequenceNumberGenerator sequenceNumberGenerator pkgAuditUtils.SequenceNumberGenerator
// Opentelemetry tracer // Opentelemetry tracer
tracer trace.Tracer tracer trace.Tracer
@ -400,10 +413,10 @@ type AuditEventBuilder struct {
// validates input and returns a cloud event that can be sent to the audit log system. // validates input and returns a cloud event that can be sent to the audit log system.
func NewAuditEventBuilder( func NewAuditEventBuilder(
// The audit api used to validate, serialize and send events // The audit api used to validate, serialize and send events
api AuditApi, api pkgAuditCommon.AuditApi,
// The sequence number generator can be used to get and revert sequence numbers to build audit log events // The sequence number generator can be used to get and revert sequence numbers to build audit log events
sequenceNumberGenerator utils.SequenceNumberGenerator, sequenceNumberGenerator pkgAuditUtils.SequenceNumberGenerator,
// The service name in lowercase (allowed characters are [a-z-]). // The service name in lowercase (allowed characters are [a-z-]).
serviceName string, serviceName string,
@ -450,11 +463,16 @@ func (builder *AuditEventBuilder) WithAuditLogEntryBuilder(auditLogEntryBuilder
} }
// WithRequiredApiRequest adds api request details // WithRequiredApiRequest adds api request details
func (builder *AuditEventBuilder) WithRequiredApiRequest(request ApiRequest) *AuditEventBuilder { func (builder *AuditEventBuilder) WithRequiredApiRequest(request pkgAuditCommon.ApiRequest) *AuditEventBuilder {
builder.auditLogEntryBuilder.WithRequiredApiRequest(request) builder.auditLogEntryBuilder.WithRequiredApiRequest(request)
return builder return builder
} }
// GetApiRequest returns the api request details
func (builder *AuditEventBuilder) GetApiRequest() *pkgAuditCommon.ApiRequest {
return builder.auditLogEntryBuilder.GetApiRequest()
}
// WithRequiredRequestClientIp adds the client ip // WithRequiredRequestClientIp adds the client ip
func (builder *AuditEventBuilder) WithRequiredRequestClientIp(requestClientIp string) *AuditEventBuilder { func (builder *AuditEventBuilder) WithRequiredRequestClientIp(requestClientIp string) *AuditEventBuilder {
builder.auditLogEntryBuilder.WithRequiredRequestClientIp(requestClientIp) builder.auditLogEntryBuilder.WithRequiredRequestClientIp(requestClientIp)
@ -488,7 +506,7 @@ func (builder *AuditEventBuilder) WithRequiredObjectId(objectId string) *AuditEv
// WithRequiredObjectType adds the object type. // WithRequiredObjectType adds the object type.
// May be prefilled by audit middleware (if the type can be extracted from the url path). // May be prefilled by audit middleware (if the type can be extracted from the url path).
func (builder *AuditEventBuilder) WithRequiredObjectType(objectType ObjectType) *AuditEventBuilder { func (builder *AuditEventBuilder) WithRequiredObjectType(objectType pkgAuditCommon.ObjectType) *AuditEventBuilder {
builder.auditLogEntryBuilder.WithRequiredObjectType(objectType) builder.auditLogEntryBuilder.WithRequiredObjectType(objectType)
return builder return builder
} }
@ -545,7 +563,7 @@ func (builder *AuditEventBuilder) WithNumResponseItems(numResponseItems int64) *
} }
// WithEventType overwrites the default event type EventTypeAdminActivity // WithEventType overwrites the default event type EventTypeAdminActivity
func (builder *AuditEventBuilder) WithEventType(eventType EventType) *AuditEventBuilder { func (builder *AuditEventBuilder) WithEventType(eventType pkgAuditCommon.EventType) *AuditEventBuilder {
builder.auditLogEntryBuilder.WithEventType(eventType) builder.auditLogEntryBuilder.WithEventType(eventType)
return builder return builder
} }
@ -575,7 +593,7 @@ func (builder *AuditEventBuilder) WithResponseBody(responseBody any) *AuditEvent
} }
// WithResponseBodyBytes adds the response body as bytes (serialized json or protobuf message expected) // WithResponseBodyBytes adds the response body as bytes (serialized json or protobuf message expected)
func (builder *AuditEventBuilder) WithResponseBodyBytes(responseBody *[]byte) *AuditEventBuilder { func (builder *AuditEventBuilder) WithResponseBodyBytes(responseBody []byte) *AuditEventBuilder {
builder.auditLogEntryBuilder.WithResponseBodyBytes(responseBody) builder.auditLogEntryBuilder.WithResponseBodyBytes(responseBody)
return builder return builder
} }
@ -621,7 +639,7 @@ func (builder *AuditEventBuilder) MarkAsBuilt() {
// - The RoutableIdentifier required for routing the cloud event // - The RoutableIdentifier required for routing the cloud event
// - The operation name // - The operation name
// - Error if the event cannot be built // - Error if the event cannot be built
func (builder *AuditEventBuilder) Build(ctx context.Context, sequenceNumber SequenceNumber) (*CloudEvent, *RoutableIdentifier, error) { func (builder *AuditEventBuilder) Build(ctx context.Context, sequenceNumber SequenceNumber) (*pkgAuditCommon.CloudEvent, *pkgAuditCommon.RoutableIdentifier, error) {
if builder.auditLogEntryBuilder == nil { if builder.auditLogEntryBuilder == nil {
return nil, nil, fmt.Errorf("audit log entry builder not set") return nil, nil, fmt.Errorf("audit log entry builder not set")
} }
@ -632,19 +650,19 @@ func (builder *AuditEventBuilder) Build(ctx context.Context, sequenceNumber Sequ
visibility := builder.visibility visibility := builder.visibility
objectId := builder.auditLogEntryBuilder.auditParams.ObjectId objectId := builder.auditLogEntryBuilder.auditParams.ObjectId
objectType := builder.auditLogEntryBuilder.auditParams.ObjectType objectType := builder.auditLogEntryBuilder.auditParams.ObjectType
var routingIdentifier *RoutableIdentifier var routingIdentifier *pkgAuditCommon.RoutableIdentifier
if builder.auditLogEntryBuilder.auditParams.EventType == EventTypeSystemEvent { if builder.auditLogEntryBuilder.auditParams.EventType == pkgAuditCommon.EventTypeSystemEvent {
routingIdentifier = NewAuditRoutingIdentifier(uuid.Nil.String(), ObjectTypeSystem) routingIdentifier = internalAuditApi.NewAuditRoutingIdentifier(uuid.Nil.String(), pkgAuditCommon.ObjectTypeSystem)
if objectId == "" { if objectId == "" {
objectId = uuid.Nil.String() objectId = uuid.Nil.String()
builder.WithRequiredObjectId(objectId) builder.WithRequiredObjectId(objectId)
} }
if objectType == "" { if objectType == "" {
objectType = ObjectTypeSystem objectType = pkgAuditCommon.ObjectTypeSystem
builder.WithRequiredObjectType(objectType) builder.WithRequiredObjectType(objectType)
} }
} else { } else {
routingIdentifier = NewAuditRoutingIdentifier(objectId, objectType) routingIdentifier = internalAuditApi.NewAuditRoutingIdentifier(objectId, objectType)
} }
auditLogEntry, err := builder.auditLogEntryBuilder.Build(ctx, sequenceNumber) auditLogEntry, err := builder.auditLogEntryBuilder.Build(ctx, sequenceNumber)

View file

@ -2,17 +2,21 @@ package api
import ( import (
"context" "context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/utils"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"fmt" "fmt"
"github.com/bufbuild/protovalidate-go" "testing"
"time"
"buf.build/go/protovalidate"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb" "google.golang.org/protobuf/types/known/wrapperspb"
"testing"
"time" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgAuditUtils "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/utils"
) )
func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) { func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
@ -40,7 +44,7 @@ func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
objectId, objectType, err := getObjectIdAndTypeFromAuditParams( objectId, objectType, err := getObjectIdAndTypeFromAuditParams(
&AuditParameters{ &AuditParameters{
ObjectId: "value", ObjectId: "value",
ObjectType: ObjectTypeFromPluralString("invalid"), ObjectType: pkgAuditCommon.ObjectTypeFromPluralString("invalid"),
}, },
) )
assert.EqualError(t, err, "unknown object type") assert.EqualError(t, err, "unknown object type")
@ -54,12 +58,12 @@ func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
objectId, objectType, err := getObjectIdAndTypeFromAuditParams( objectId, objectType, err := getObjectIdAndTypeFromAuditParams(
&AuditParameters{ &AuditParameters{
ObjectId: "value", ObjectId: "value",
ObjectType: ObjectTypeProject, ObjectType: pkgAuditCommon.ObjectTypeProject,
}, },
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "value", objectId) assert.Equal(t, "value", objectId)
assert.Equal(t, ObjectTypeProject, *objectType) assert.Equal(t, pkgAuditCommon.ObjectTypeProject, *objectType)
}, },
) )
} }
@ -76,7 +80,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
t.Run("details missing", func(t *testing.T) { t.Run("details missing", func(t *testing.T) {
logEntry, err := NewAuditLogEntryBuilder().WithRequiredLocation("eu01"). logEntry, err := NewAuditLogEntryBuilder().WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
Build(context.Background(), SequenceNumber(1)) Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err) assert.NoError(t, err)
@ -86,23 +90,23 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
err = validator.Validate(logEntry) err = validator.Validate(logEntry)
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, "validation error:\n - proto_payload.service_name: value is required [required]\n - proto_payload.operation_name: value is required [required]\n - proto_payload.request_metadata.caller_supplied_user_agent: value is required [required]\n - proto_payload.request_metadata.request_attributes.method: value is required [required]\n - proto_payload.request_metadata.request_attributes.headers: value is required [required]\n - proto_payload.request_metadata.request_attributes.path: value is required [required]\n - proto_payload.request_metadata.request_attributes.host: value is required [required]\n - proto_payload.request_metadata.request_attributes.scheme: value is required [required]\n - proto_payload.request_metadata.request_attributes.protocol: value is required [required]\n - insert_id: value does not match regex pattern `^[0-9]+/[a-z0-9-]+/[a-z0-9-]+/[0-9]+$` [string.pattern]", err.Error()) assert.Equal(t, "validation errors:\n - proto_payload.service_name: value is required\n - proto_payload.operation_name: value is required\n - proto_payload.request_metadata.caller_supplied_user_agent: value is required\n - proto_payload.request_metadata.request_attributes.method: value is required\n - proto_payload.request_metadata.request_attributes.headers: value is required\n - proto_payload.request_metadata.request_attributes.path: value is required\n - proto_payload.request_metadata.request_attributes.host: value is required\n - proto_payload.request_metadata.request_attributes.scheme: value is required\n - proto_payload.request_metadata.request_attributes.protocol: value is required\n - insert_id: value does not match regex pattern `^[0-9]+/[a-z0-9-]+/[a-z0-9-]+/[0-9]+$`", err.Error())
}) })
t.Run("required only", func(t *testing.T) { t.Run("required only", func(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -126,7 +130,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", *authenticationInfo.PrincipalEmail)
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -195,16 +199,16 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -215,7 +219,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
WithAuditPermission(permission). WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult). WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details). WithDetails(details).
WithEventType(EventTypePolicyDenied). WithEventType(pkgAuditCommon.EventTypePolicyDenied).
WithLabels(map[string]string{"key": "label"}). WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)). WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId"). WithRequestCorrelationId("correlationId").
@ -242,7 +246,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", *authenticationInfo.PrincipalEmail)
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -306,11 +310,105 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("with service account token", func(t *testing.T) {
builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeadersSa,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id")
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
assert.NotNil(t, logEntry)
assert.Equal(t, "projects/1/logs/admin-activity", logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity)
assert.NotNil(t, logEntry.Timestamp)
assert.Nil(t, logEntry.CorrelationId)
assert.Regexp(t, "[0-9]+/eu01/worker-id/1", logEntry.InsertId)
assert.NotNil(t, logEntry.ProtoPayload)
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo)
assert.Equal(t, "my-service-yifc9e1@sa.stackit.cloud", *authenticationInfo.PrincipalEmail)
assert.Equal(t, "10f38b01_534b_47bb_a03a_e294ca2be4de", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Equal(t, "projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/10f38b01-534b-47bb-a03a-e294ca2be4de", *authenticationInfo.ServiceAccountName)
assert.Nil(t, logEntry.ProtoPayload.AuthorizationInfo)
assert.Nil(t, logEntry.ProtoPayload.Metadata)
assert.Equal(t, "stackit.demo-service.v1.operation", logEntry.ProtoPayload.OperationName)
assert.Nil(t, logEntry.ProtoPayload.Request)
requestMetadata := logEntry.ProtoPayload.RequestMetadata
assert.NotNil(t, requestMetadata)
assert.Equal(t, "127.0.0.1", requestMetadata.CallerIp)
assert.Equal(t, "custom", requestMetadata.CallerSuppliedUserAgent)
requestAttributes := requestMetadata.RequestAttributes
assert.NotNil(t, requestAttributes)
assert.Equal(t, "/", requestAttributes.Path)
assert.NotNil(t, requestAttributes.Time)
assert.Equal(t, "localhost", requestAttributes.Host)
assert.Equal(t, auditV1.AttributeContext_HTTP_METHOD_POST, requestAttributes.Method)
assert.Nil(t, requestAttributes.Id)
assert.Equal(t, "https", requestAttributes.Scheme)
assert.Equal(t, map[string]string{"user-agent": "custom"}, requestAttributes.Headers)
assert.Nil(t, requestAttributes.Query)
assert.Equal(t, "HTTP/1.1", requestAttributes.Protocol)
requestAttributesAuth := requestAttributes.Auth
assert.NotNil(t, requestAttributesAuth)
assert.Equal(t, "10f38b01_534b_47bb_a03a_e294ca2be4de/stackit%2Fserviceaccount", requestAttributesAuth.Principal)
assert.Equal(t, []string{"stackit", "api"}, requestAttributesAuth.Audiences)
assert.NotNil(t, requestAttributesAuth.Claims)
assert.Equal(t, "projects/1", logEntry.ProtoPayload.ResourceName)
assert.Nil(t, logEntry.ProtoPayload.Response)
responseMetadata := logEntry.ProtoPayload.ResponseMetadata
assert.NotNil(t, responseMetadata)
assert.Nil(t, responseMetadata.ErrorDetails)
assert.Nil(t, responseMetadata.ErrorMessage)
assert.Equal(t, wrapperspb.Int32(200), responseMetadata.StatusCode)
responseAttributes := responseMetadata.ResponseAttributes
assert.NotNil(t, responseAttributes)
assert.Nil(t, responseAttributes.Headers)
assert.Nil(t, responseAttributes.NumResponseItems)
assert.Nil(t, responseAttributes.Size)
assert.NotNil(t, responseAttributes.Time)
assert.Equal(t, "demo-service", logEntry.ProtoPayload.ServiceName)
validator, err := protovalidate.New()
assert.NoError(t, err)
err = validator.Validate(logEntry)
assert.NoError(t, err)
})
t.Run("system event", func(t *testing.T) { t.Run("system event", func(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredServiceName("demo-service"). WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id"). WithRequiredWorkerId("worker-id").
@ -331,7 +429,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail) assert.Nil(t, authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId) assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -399,16 +497,16 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -419,7 +517,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
WithAuditPermission(permission). WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult). WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details). WithDetails(details).
WithEventType(EventTypeSystemEvent). WithEventType(pkgAuditCommon.EventTypeSystemEvent).
WithLabels(map[string]string{"key": "label"}). WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)). WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId"). WithRequestCorrelationId("correlationId").
@ -451,21 +549,21 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
responseBodyBytes, err := ResponseBodyToBytes(responseBody) responseBodyBytes, err := ResponseBodyToBytes(responseBody)
assert.NoError(t, err) assert.NoError(t, err)
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
}). }).
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1"). WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service"). WithRequiredServiceName("demo-service").
@ -480,21 +578,21 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
t.Run("with invalid response body", func(t *testing.T) { t.Run("with invalid response body", func(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
}). }).
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1"). WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service"). WithRequiredServiceName("demo-service").
@ -505,13 +603,141 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
assert.EqualError(t, err, "json: cannot unmarshal string into Go value of type map[string]interface {}\ninvalid response") assert.EqualError(t, err, "json: cannot unmarshal string into Go value of type map[string]interface {}\ninvalid response")
assert.Nil(t, logEntry) assert.Nil(t, logEntry)
}) })
t.Run("get api request", func(t *testing.T) {
requestBody := map[string]interface{}{"key": "response"}
requestBodyBytes, err := ResponseBodyToBytes(requestBody)
assert.NoError(t, err)
builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: requestBodyBytes,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id")
// get the request before building the auditlog entry
apiRequest := builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
assert.NotNil(t, logEntry)
// get the request after building the auditlog entry
apiRequest = builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
})
t.Run("get invalid api request", func(t *testing.T) {
requestBodyBytes := []byte("invalid")
builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: requestBodyBytes,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id")
// get the request before building the auditlog entry
apiRequest := builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
assert.EqualError(t, err, "invalid character 'i' looking for beginning of value\ninvalid request body")
assert.Nil(t, logEntry)
// get the request after building the auditlog entry
apiRequest = builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
})
t.Run("modify request body", func(t *testing.T) {
requestBodyBytes := []byte("{\"key\":\"value\"}")
builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: requestBodyBytes,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id")
// get the request before building the auditlog entry
apiRequest := builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, requestBodyBytes, apiRequest.Body)
// update the request body
updatedBodyBytes := []byte("{\"key\":\"updated\"}")
apiRequest.Body = updatedBodyBytes
// build the audit log entry
logEntry, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
assert.NotNil(t, logEntry)
// check the request body from the serialized event
requestBodyJson, err := logEntry.ProtoPayload.Request.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, updatedBodyBytes, requestBodyJson)
// check the request after building the auditlog entry
apiRequest = builder.GetApiRequest()
assert.NotNil(t, apiRequest)
assert.Equal(t, updatedBodyBytes, apiRequest.Body)
})
} }
func Test_AuditEventBuilder(t *testing.T) { func Test_AuditEventBuilder(t *testing.T) {
t.Run("nothing set", func(t *testing.T) { t.Run("nothing set", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
Build(context.Background(), SequenceNumber(1)) Build(context.Background(), SequenceNumber(1))
@ -524,44 +750,44 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("details missing", func(t *testing.T) { t.Run("details missing", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId("objectId"). WithRequiredObjectId("objectId").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
Build(context.Background(), SequenceNumber(1)) Build(context.Background(), SequenceNumber(1))
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, "validation error:\n - log_name: value does not match regex pattern `^[a-z-]+/[a-z0-9-]+/logs/(?:admin-activity|system-event|policy-denied|data-access)$` [string.pattern]\n - proto_payload.operation_name: value is required [required]\n - proto_payload.resource_name: value does not match regex pattern `^[a-z]+/[a-z0-9-]+(?:/[a-z0-9-]+/[a-z0-9-_]+)*$` [string.pattern]\n - proto_payload.request_metadata.caller_supplied_user_agent: value is required [required]\n - proto_payload.request_metadata.request_attributes.method: value is required [required]\n - proto_payload.request_metadata.request_attributes.headers: value is required [required]\n - proto_payload.request_metadata.request_attributes.path: value is required [required]\n - proto_payload.request_metadata.request_attributes.host: value is required [required]\n - proto_payload.request_metadata.request_attributes.scheme: value is required [required]\n - proto_payload.request_metadata.request_attributes.protocol: value is required [required]", err.Error()) assert.Equal(t, "validation errors:\n - log_name: value does not match regex pattern `^[a-z-]+/[a-z0-9-]+/logs/(?:admin-activity|system-event|policy-denied|data-access)$`\n - proto_payload.operation_name: value is required\n - proto_payload.resource_name: value does not match regex pattern `^[a-z]+/[a-z0-9-]+(?:/[a-z0-9-]+/[a-z0-9-_]+)*$`\n - proto_payload.request_metadata.caller_supplied_user_agent: value is required\n - proto_payload.request_metadata.request_attributes.method: value is required\n - proto_payload.request_metadata.request_attributes.headers: value is required\n - proto_payload.request_metadata.request_attributes.path: value is required\n - proto_payload.request_metadata.request_attributes.host: value is required\n - proto_payload.request_metadata.request_attributes.scheme: value is required\n - proto_payload.request_metadata.request_attributes.protocol: value is required", err.Error())
assert.Nil(t, cloudEvent) assert.Nil(t, cloudEvent)
assert.Nil(t, routingIdentifier) assert.Nil(t, routingIdentifier)
}) })
t.Run("required only", func(t *testing.T) { t.Run("required only", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString() objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId). WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation). WithRequiredOperation(operation).
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
}). }).
WithRequiredRequestClientIp("127.0.0.1") WithRequiredRequestClientIp("127.0.0.1")
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject} routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.ObjectTypeProject}
cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err) assert.NoError(t, err)
@ -604,7 +830,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", *authenticationInfo.PrincipalEmail)
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -663,7 +889,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("with details", func(t *testing.T) { t.Run("with details", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString() objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
@ -677,16 +903,16 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId). WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation). WithRequiredOperation(operation).
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -695,7 +921,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithAuditPermission(permission). WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult). WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details). WithDetails(details).
WithEventType(EventTypeAdminActivity). WithEventType(pkgAuditCommon.EventTypeAdminActivity).
WithLabels(map[string]string{"key": "label"}). WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)). WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId"). WithRequestCorrelationId("correlationId").
@ -708,7 +934,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithStatusCode(400). WithStatusCode(400).
WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE) WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE)
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject} routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.ObjectTypeProject}
cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err) assert.NoError(t, err)
@ -751,7 +977,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", *authenticationInfo.PrincipalEmail)
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -817,13 +1043,13 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("system event with object reference", func(t *testing.T) { t.Run("system event with object reference", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString() objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId). WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation). WithRequiredOperation(operation).
AsSystemEvent() AsSystemEvent()
@ -831,8 +1057,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, builder.IsBuilt()) assert.True(t, builder.IsBuilt())
assert.Equal(t, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
assert.NotNil(t, cloudEvent) assert.NotNil(t, cloudEvent)
assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType) assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType)
@ -849,8 +1075,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NotNil(t, cloudEvent.Data) assert.NotNil(t, cloudEvent.Data)
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent)) assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
assert.Equal(t, SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type)
assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility) assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility)
assert.Equal(t, operation, routableAuditEvent.OperationName) assert.Equal(t, operation, routableAuditEvent.OperationName)
@ -869,7 +1095,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail) assert.Nil(t, authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId) assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -929,7 +1155,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("system event", func(t *testing.T) { t.Run("system event", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
@ -940,8 +1166,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, builder.IsBuilt()) assert.True(t, builder.IsBuilt())
assert.Equal(t, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
assert.NotNil(t, cloudEvent) assert.NotNil(t, cloudEvent)
assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType) assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType)
@ -958,8 +1184,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NotNil(t, cloudEvent.Data) assert.NotNil(t, cloudEvent.Data)
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent)) assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
assert.Equal(t, SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type)
assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility) assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility)
assert.Equal(t, operation, routableAuditEvent.OperationName) assert.Equal(t, operation, routableAuditEvent.OperationName)
@ -978,7 +1204,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail) assert.Nil(t, authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId) assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -1038,7 +1264,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("with response body unserialized", func(t *testing.T) { t.Run("with response body unserialized", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString() objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
@ -1050,16 +1276,16 @@ func Test_AuditEventBuilder(t *testing.T) {
responseBody := map[string]interface{}{"key": "response"} responseBody := map[string]interface{}{"key": "response"}
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId). WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation). WithRequiredOperation(operation).
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -1068,7 +1294,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithAuditPermission(permission). WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult). WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details). WithDetails(details).
WithEventType(EventTypeAdminActivity). WithEventType(pkgAuditCommon.EventTypeAdminActivity).
WithLabels(map[string]string{"key": "label"}). WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)). WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId"). WithRequestCorrelationId("correlationId").
@ -1081,7 +1307,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithStatusCode(400). WithStatusCode(400).
WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE) WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE)
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject} routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.ObjectTypeProject}
cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err) assert.NoError(t, err)
@ -1111,7 +1337,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("mark as built", func(t *testing.T) { t.Run("mark as built", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01") builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
builder.MarkAsBuilt() builder.MarkAsBuilt()
@ -1121,7 +1347,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("no entry builder", func(t *testing.T) { t.Run("no entry builder", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithAuditLogEntryBuilder(nil).Build(context.Background(), SequenceNumber(1)) WithAuditLogEntryBuilder(nil).Build(context.Background(), SequenceNumber(1))
@ -1133,7 +1359,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("next sequence number", func(t *testing.T) { t.Run("next sequence number", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01") builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber()) assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber())
@ -1142,7 +1368,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("revert sequence number", func(t *testing.T) { t.Run("revert sequence number", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01") builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber()) assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber())

View file

@ -1,20 +1,23 @@
package api package api
import ( import (
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/log"
"encoding/json" "encoding/json"
"errors" "errors"
"time"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"time"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgLog "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/log"
) )
// LogEvent logs an event to the terminal // LogEvent logs an event to the terminal
func LogEvent(event *CloudEvent) error { func LogEvent(event *pkgAuditCommon.CloudEvent) error {
if event.DataType == DataTypeLegacyAuditEventV1 { if event.DataType == DataTypeLegacyAuditEventV1 {
log.AuditLogger.Info(string(event.Data)) pkgLog.AuditLogger.Info(string(event.Data))
return nil return nil
} else if event.DataType != "audit.v1.RoutableAuditEvent" { } else if event.DataType != "audit.v1.RoutableAuditEvent" {
return errors.New("Unsupported data type " + event.DataType) return errors.New("Unsupported data type " + event.DataType)
@ -75,7 +78,7 @@ func LogEvent(event *CloudEvent) error {
return err return err
} }
log.AuditLogger.Info(string(cloudEventJson)) pkgLog.AuditLogger.Info(string(cloudEventJson))
return nil return nil
} }

View file

@ -2,37 +2,41 @@ package api
import ( import (
"context" "context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/utils" "testing"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/bufbuild/protovalidate-go" "buf.build/go/protovalidate"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgAuditUtils "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/utils"
) )
func Test_LogEvent(t *testing.T) { func Test_LogEvent(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
t.Run("new format", func(t *testing.T) { t.Run("new format", func(t *testing.T) {
eventBuilder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", uuid.NewString(), "eu01") eventBuilder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", uuid.NewString(), "eu01")
cloudEvent, _, err := eventBuilder. cloudEvent, _, err := eventBuilder.
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "GET", Method: "GET",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
}). }).
WithRequiredObjectId(uuid.NewString()). WithRequiredObjectId(uuid.NewString()).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.project.update"). WithRequiredOperation("stackit.demo-service.v1.project.update").
WithRequiredRequestClientIp("0.0.0.0"). WithRequiredRequestClientIp("0.0.0.0").
Build(context.Background(), eventBuilder.NextSequenceNumber()) Build(context.Background(), eventBuilder.NextSequenceNumber())
@ -44,21 +48,21 @@ func Test_LogEvent(t *testing.T) {
t.Run("legacy format", func(t *testing.T) { t.Run("legacy format", func(t *testing.T) {
objectId := uuid.NewString() objectId := uuid.NewString()
entry, err := NewAuditLogEntryBuilder(). entry, err := NewAuditLogEntryBuilder().
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "GET", Method: "GET",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
}). }).
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId(objectId). WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.project.update"). WithRequiredOperation("stackit.demo-service.v1.project.update").
WithRequiredRequestClientIp("0.0.0.0"). WithRequiredRequestClientIp("0.0.0.0").
WithRequiredServiceName("demo-service"). WithRequiredServiceName("demo-service").
@ -68,25 +72,25 @@ func Test_LogEvent(t *testing.T) {
validator, err := protovalidate.New() validator, err := protovalidate.New()
assert.NoError(t, err) assert.NoError(t, err)
var protoValidator ProtobufValidator = validator var protoValidator pkgAuditCommon.ProtobufValidator = validator
routableIdentifier := RoutableIdentifier{ routableIdentifier := pkgAuditCommon.RoutableIdentifier{
Identifier: objectId, Identifier: objectId,
Type: ObjectTypeProject, Type: pkgAuditCommon.ObjectTypeProject,
} }
routableEvent, err := validateAndSerializePartially(protoValidator, entry, auditV1.Visibility_VISIBILITY_PUBLIC, &routableIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(protoValidator, entry, auditV1.Visibility_VISIBILITY_PUBLIC, &routableIdentifier)
assert.NoError(t, err) assert.NoError(t, err)
legacyBytes, err := convertAndSerializeIntoLegacyFormat(entry, routableEvent) legacyBytes, err := internalAuditApi.ConvertAndSerializeIntoLegacyFormat(entry, routableEvent)
assert.NoError(t, err) assert.NoError(t, err)
cloudEvent := CloudEvent{ cloudEvent := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0", SpecVersion: "1.0",
Source: entry.ProtoPayload.ServiceName, Source: entry.ProtoPayload.ServiceName,
Id: entry.InsertId, Id: entry.InsertId,
Time: entry.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(), Time: entry.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsJson, DataContentType: pkgAuditCommon.ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1, DataType: DataTypeLegacyAuditEventV1,
Subject: entry.ProtoPayload.ResourceName, Subject: entry.ProtoPayload.ResourceName,
Data: legacyBytes, Data: legacyBytes,

91
pkg/audit/api/utils.go Normal file
View file

@ -0,0 +1,91 @@
package api
import (
"encoding/json"
"net"
"regexp"
"strings"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
)
var objectTypeIdPattern = regexp.MustCompile(".*/(projects|folders|organizations)/([0-9a-fA-F-]{36})(?:/.*)?")
// GetCalledServiceNameFromRequest extracts the called service name from subdomain name
func GetCalledServiceNameFromRequest(request *pkgAuditCommon.ApiRequest, fallbackName string) string {
if request == nil {
return fallbackName
}
var calledServiceName = fallbackName
host := request.Host
ip := net.ParseIP(host)
if ip == nil && !strings.Contains(host, "localhost") {
dotIdx := strings.Index(host, ".")
if dotIdx != -1 {
calledServiceName = host[0:dotIdx]
}
}
return calledServiceName
}
func GetObjectIdAndTypeFromUrlPath(path string) (
string,
*pkgAuditCommon.ObjectType,
error,
) {
// Extract object id and type from request url
objectTypeIdMatches := objectTypeIdPattern.FindStringSubmatch(path)
if len(objectTypeIdMatches) > 0 {
objectType := pkgAuditCommon.ObjectTypeFromPluralString(objectTypeIdMatches[1])
err := objectType.IsSupportedType()
if err != nil {
return "", nil, err
}
objectId := objectTypeIdMatches[2]
return objectId, &objectType, nil
}
return "", nil, nil
}
// ResponseBodyToBytes converts a JSON or Protobuf response into a byte array
func ResponseBodyToBytes(response any) ([]byte, error) {
if response == nil {
return nil, nil
}
responseBytes, isBytes := response.([]byte)
if isBytes {
return responseBytes, nil
}
responseProtoMessage, isProtoMessage := response.(proto.Message)
if isProtoMessage {
responseJson, err := protojson.Marshal(responseProtoMessage)
if err != nil {
return nil, err
}
return responseJson, nil
}
responseJson, err := json.Marshal(response)
if err != nil {
return nil, err
}
return responseJson, nil
}
func ToArrayMap(input map[string]string) map[string][]string {
output := map[string][]string{}
for key, value := range input {
output[key] = []string{value}
}
return output
}

154
pkg/audit/api/utils_test.go Normal file
View file

@ -0,0 +1,154 @@
package api
import (
"encoding/json"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/encoding/protojson"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
)
func Test_GetCalledServiceNameFromRequest(t *testing.T) {
t.Run("request is nil", func(t *testing.T) {
serviceName := GetCalledServiceNameFromRequest(nil, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
})
t.Run("localhost", func(t *testing.T) {
request := pkgAuditCommon.ApiRequest{Host: "localhost:8080"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
})
t.Run("cf", func(t *testing.T) {
request := pkgAuditCommon.ApiRequest{Host: "stackit-resource-manager-go-dev.apps.01.cf.eu01.stackit.cloud"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "stackit-resource-manager-go-dev", serviceName)
})
t.Run("cf invalid host", func(t *testing.T) {
request := pkgAuditCommon.ApiRequest{Host: ""}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
})
t.Run("ip", func(t *testing.T) {
request := pkgAuditCommon.ApiRequest{Host: "127.0.0.1"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
},
)
t.Run("ip short", func(t *testing.T) {
request := pkgAuditCommon.ApiRequest{Host: "::1"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
},
)
}
func Test_GetObjectIdAndTypeFromUrlPath(t *testing.T) {
t.Run("object id and type not in url", func(t *testing.T) {
objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/projects/audit")
assert.NoError(t, err)
assert.Equal(t, "", objectId)
assert.Nil(t, objectType)
})
t.Run("object id and type in url", func(t *testing.T) {
objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/projects/f17d4064-9b65-4334-b6a7-8fed96340124")
assert.NoError(t, err)
assert.Equal(t, "f17d4064-9b65-4334-b6a7-8fed96340124", objectId)
assert.Equal(t, pkgAuditCommon.ObjectTypeProject, *objectType)
})
t.Run("multiple object ids and types in url", func(t *testing.T) {
objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/organization/8ee58bec-d496-4bb9-af8d-72fda4d78b6b/projects/f17d4064-9b65-4334-b6a7-8fed96340124")
assert.NoError(t, err)
assert.Equal(t, "f17d4064-9b65-4334-b6a7-8fed96340124", objectId)
assert.Equal(t, pkgAuditCommon.ObjectTypeProject, *objectType)
})
}
func Test_ResponseBodyToBytes(t *testing.T) {
t.Run(
"nil response body", func(t *testing.T) {
bytes, err := ResponseBodyToBytes(nil)
assert.Nil(t, bytes)
assert.Nil(t, err)
},
)
t.Run(
"bytes", func(t *testing.T) {
responseBody := []byte("data")
bytes, err := ResponseBodyToBytes(responseBody)
assert.Nil(t, err)
assert.Equal(t, responseBody, bytes)
},
)
t.Run(
"Protobuf message", func(t *testing.T) {
protobufMessage := auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(pkgAuditCommon.ObjectTypeProject)}
bytes, err := ResponseBodyToBytes(&protobufMessage)
assert.Nil(t, err)
expected, err := protojson.Marshal(&protobufMessage)
assert.Nil(t, err)
assert.Equal(t, expected, bytes)
},
)
t.Run(
"struct", func(t *testing.T) {
type CustomObject struct {
Value string
}
responseBody := CustomObject{Value: "data"}
bytes, err := ResponseBodyToBytes(responseBody)
assert.Nil(t, err)
expected, err := json.Marshal(responseBody)
assert.Nil(t, err)
assert.Equal(t, expected, bytes)
},
)
t.Run(
"map", func(t *testing.T) {
responseBody := map[string]interface{}{"value": "data"}
bytes, err := ResponseBodyToBytes(responseBody)
assert.Nil(t, err)
expected, err := json.Marshal(responseBody)
assert.Nil(t, err)
assert.Equal(t, expected, bytes)
},
)
}
func Test_ToArrayMap(t *testing.T) {
t.Run("empty map", func(t *testing.T) {
result := ToArrayMap(map[string]string{})
assert.Equal(t, map[string][]string{}, result)
})
t.Run("empty map", func(t *testing.T) {
result := ToArrayMap(map[string]string{"key1": "value1", "key2": "value2"})
assert.Equal(t, map[string][]string{
"key1": {"value1"},
"key2": {"value2"},
}, result)
})
}

View file

@ -1,16 +1,23 @@
package api package common
import ( import (
"context" "context"
"regexp"
"time" "time"
"buf.build/go/protovalidate"
"github.com/google/uuid" "github.com/google/uuid"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"google.golang.org/protobuf/proto"
) )
// ContentTypeCloudEventsProtobuf the cloudevents protobuf content-type sent in metadata of messages
const ContentTypeCloudEventsProtobuf = "application/cloudevents+protobuf"
const ContentTypeCloudEventsJson = "application/cloudevents+json; charset=UTF-8"
var TopicNamePattern = regexp.MustCompile(`^topic://stackit-platform/t/swz/audit-log/(?:conway|eu01|eu02|sx-stoi01)/[Vv][1-9](?:\.\d)?/[A-Za-z0-9-]+/[A-Za-z0-9-/]+`)
type EventType string type EventType string
const ( const (
@ -47,13 +54,7 @@ func ObjectTypeFromPluralString(value string) ObjectType {
func (t ObjectType) IsSupportedType() error { func (t ObjectType) IsSupportedType() error {
switch t { switch t {
case ObjectTypeOrganization: case ObjectTypeOrganization, ObjectTypeFolder, ObjectTypeProject, ObjectTypeSystem:
fallthrough
case ObjectTypeFolder:
fallthrough
case ObjectTypeProject:
fallthrough
case ObjectTypeSystem:
return nil return nil
default: default:
return ErrUnknownObjectType return ErrUnknownObjectType
@ -148,7 +149,7 @@ type AuditApi interface {
// ProtobufValidator is an abstraction for validators. // ProtobufValidator is an abstraction for validators.
// Concrete implementations are e.g. protovalidate.Validator // Concrete implementations are e.g. protovalidate.Validator
type ProtobufValidator interface { type ProtobufValidator interface {
Validate(msg proto.Message) error Validate(msg proto.Message, options ...protovalidate.ValidationOption) error
} }
// CloudEvent is a representation of a cloudevents.io object. // CloudEvent is a representation of a cloudevents.io object.
@ -223,6 +224,17 @@ type TopicNameResolver interface {
Resolve(routableIdentifier *RoutableIdentifier) (string, error) Resolve(routableIdentifier *RoutableIdentifier) (string, error)
} }
// StaticTopicNameTestResolver implements TopicNameResolver.
// A hard-coded topic name is used, routable identifiers are ignored.
type StaticTopicNameTestResolver struct {
TopicName string
}
// Resolve implements TopicNameResolver.Resolve
func (r *StaticTopicNameTestResolver) Resolve(*RoutableIdentifier) (string, error) {
return r.TopicName, nil
}
type RoutableIdentifier struct { type RoutableIdentifier struct {
Identifier string Identifier string
Type ObjectType Type ObjectType

View file

@ -1,4 +1,4 @@
package api package common
import ( import (
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"

View file

@ -0,0 +1,49 @@
package common
import (
"errors"
)
// ErrAttributeIdentifierInvalid indicates that the object identifier
// and the identifier in the checked attribute do not match
var ErrAttributeIdentifierInvalid = errors.New("attribute identifier invalid")
// ErrAttributeTypeInvalid indicates that an invalid type has been provided.
var ErrAttributeTypeInvalid = errors.New("attribute type invalid")
// ErrCloudEventNil states that the given cloud event is nil
var ErrCloudEventNil = errors.New("cloud event nil")
// ErrEventNil indicates that the event was nil
var ErrEventNil = errors.New("event is nil")
// ErrInvalidRoutableIdentifierForSystemEvent states that the routable identifier is not valid for a system event
var ErrInvalidRoutableIdentifierForSystemEvent = errors.New("invalid identifier for system event")
// ErrMessagingApiNil states that the messaging api is nilØØ
var ErrMessagingApiNil = errors.New("messaging api nil")
// ErrObjectIdentifierNil indicates that the object identifier was nil
var ErrObjectIdentifierNil = errors.New("object identifier is nil")
// ErrObjectIdentifierVisibilityMismatch indicates that a reference mismatch was detected.
//
// Valid combinations are:
// * Visibility: Public, ObjectIdentifier: <type>
// * Visibility: Private, ObjectIdentifier: <type | system>
var ErrObjectIdentifierVisibilityMismatch = errors.New("object reference visibility mismatch")
// ErrTopicNameResolverNil states that the topic name resolve is nil
var ErrTopicNameResolverNil = errors.New("topic name resolver nil")
// ErrUnknownObjectType indicates that the given input is an unknown object type
var ErrUnknownObjectType = errors.New("unknown object type")
// ErrUnsupportedEventTypeDataAccess states that the event type "data-access" is currently not supported
var ErrUnsupportedEventTypeDataAccess = errors.New("unsupported event type data access")
// ErrUnsupportedObjectIdentifierType indicates that an unsupported object identifier type has been provided
var ErrUnsupportedObjectIdentifierType = errors.New("unsupported object identifier type")
// ErrUnsupportedRoutableType indicates that the given input is an unsupported routable type
var ErrUnsupportedRoutableType = errors.New("unsupported routable type")

59
pkg/audit/common/model.go Normal file
View file

@ -0,0 +1,59 @@
package common
type ApiRequest struct {
// Body
//
// Required: false
Body []byte
// The (HTTP) request headers / gRPC metadata.
//
// Internal IP-Addresses have to be removed (e.g. in x-forwarded-xxx headers).
//
// Required: true
Header map[string][]string
// The HTTP request `Host` header value.
//
// Required: true
Host string
// Method
//
// Required: true
Method string
// The URL scheme, such as `http`, `https` or `gRPC`.
//
// Required: true
Scheme string
// The network protocol used with the request, such as "http/1.1",
// "spdy/3", "h2", "h2c", "webrtc", "tcp", "udp", "quic". See
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
// for details.
//
// Required: true
Proto string
// The url
//
// Required: true
URL RequestUrl
}
type RequestUrl struct {
// The gRPC / HTTP URL path.
//
// Required: true
Path string
// The HTTP URL query in the format of "name1=value1&name2=value2", as it
// appears in the first line of the HTTP request.
// The input should be escaped to not contain any special characters.
//
// Required: false
RawQuery *string
}

View file

@ -40,7 +40,7 @@ func (g *DefaultSequenceNumberGenerator) Next() uint64 {
var next uint64 var next uint64
if len(g.backlog) == 0 { if len(g.backlog) == 0 {
next = g.sequenceNumber next = g.sequenceNumber
g.sequenceNumber += 1 g.sequenceNumber++
} else { } else {
next = g.backlog[0] next = g.backlog[0]
g.backlog = g.backlog[1:] g.backlog = g.backlog[1:]
@ -53,7 +53,7 @@ func (g *DefaultSequenceNumberGenerator) Revert(value uint64) {
g.sequenceNumberLock.Lock() g.sequenceNumberLock.Lock()
defer g.sequenceNumberLock.Unlock() defer g.sequenceNumberLock.Unlock()
if value == g.sequenceNumber-1 { if value == g.sequenceNumber-1 {
g.sequenceNumber -= 1 g.sequenceNumber--
} else if !slices.Contains(g.backlog, value) { } else if !slices.Contains(g.backlog, value) {
g.backlog = append(g.backlog, value) g.backlog = append(g.backlog, value)
} }

View file

@ -1,8 +1,9 @@
package utils package utils
import ( import (
"github.com/stretchr/testify/assert"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func Test_DefaultSequenceNumberGenerator(t *testing.T) { func Test_DefaultSequenceNumberGenerator(t *testing.T) {

View file

@ -16,11 +16,12 @@ type Logger interface {
func wrapErr(err []error) error { func wrapErr(err []error) error {
var e error var e error
if len(err) == 0 { switch {
case len(err) == 0:
e = nil e = nil
} else if len(err) == 1 { case len(err) == 1:
e = err[0] e = err[0]
} else { default:
e = errors.Join(err...) e = errors.Join(err...)
} }
return e return e

148
pkg/messaging/api/amqp.go Normal file
View file

@ -0,0 +1,148 @@
package api
import (
"context"
"errors"
"fmt"
"sync"
"time"
internalMessaging "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/messaging"
pkgLog "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/log"
pkgMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
)
// AmqpApi implements Api.
type AmqpApi struct {
connection *internalMessaging.AmqpConnection
connectionPool internalMessaging.ConnectionPool
connectionPoolHandle *internalMessaging.ConnectionPoolHandle
senderCache map[string]*internalMessaging.AmqpSenderSession
lock sync.RWMutex
}
var _ Api = &AmqpApi{}
func NewDefaultAmqpApi(amqpConfig pkgMessagingCommon.AmqpConnectionConfig) (Api, error) {
connectionPool, err := internalMessaging.NewDefaultAmqpConnectionPool(amqpConfig, "sdk")
if err != nil {
return nil, fmt.Errorf("new amqp connection pool: %w", err)
}
amqpApi := &AmqpApi{
connectionPool: connectionPool,
connectionPoolHandle: connectionPool.NewHandle(),
senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
}
var messagingApi Api = amqpApi
return messagingApi, nil
}
func NewAmqpApi(amqpConfig pkgMessagingCommon.AmqpConnectionPoolConfig) (Api, error) {
connectionPool, err := internalMessaging.NewAmqpConnectionPool(amqpConfig, "sdk")
if err != nil {
return nil, fmt.Errorf("new amqp connection pool: %w", err)
}
amqpApi := &AmqpApi{
connectionPool: connectionPool,
connectionPoolHandle: connectionPool.NewHandle(),
senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
}
var messagingApi Api = amqpApi
return messagingApi, nil
}
// Send implements Api.Send.
// If errors occur the connection to the messaging system will be closed and re-established.
func (a *AmqpApi) Send(_ context.Context, topic string, data []byte, contentType string, applicationProperties map[string]any) error {
// create or get sender from cache
var sender = a.senderFromCache(topic)
if sender == nil {
if err := a.newSender(topic); err != nil {
return err
}
sender = a.senderFromCache(topic)
}
// first attempt to send
var sendErr error
wrappedData := [][]byte{data}
if err := sender.Send(topic, wrappedData, contentType, applicationProperties); err != nil {
sendErr = fmt.Errorf("send: %w", err)
} else {
return nil
}
// renew sender
if err := a.newSender(topic); err != nil {
return errors.Join(sendErr, err)
}
sender = a.senderFromCache(topic)
// retry send
if err := sender.Send(topic, wrappedData, contentType, applicationProperties); err != nil {
return errors.Join(sendErr, fmt.Errorf("retry send: %w", err))
}
return nil
}
func (a *AmqpApi) senderFromCache(topic string) *internalMessaging.AmqpSenderSession {
a.lock.RLock()
defer a.lock.RUnlock()
return a.senderCache[topic]
}
func (a *AmqpApi) newSender(topic string) error {
a.lock.Lock()
defer a.lock.Unlock()
connectionIsClosed := a.connection == nil || a.connection.IsClosed()
if connectionIsClosed {
connection, err := a.connectionPool.GetConnection(a.connectionPoolHandle)
if err != nil {
return fmt.Errorf("get connection: %w", err)
}
a.connection = connection
}
ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
sender, err := a.connection.NewSender(ctx, topic)
cancelFn()
if err != nil {
return fmt.Errorf("new sender: %w", err)
}
a.senderCache[topic] = sender
return nil
}
// Close implements Api.Close
func (a *AmqpApi) Close(_ context.Context) error {
pkgLog.AuditLogger.Info("close audit amqp connection pool")
a.lock.Lock()
defer a.lock.Unlock()
// cached senders
var closeErrors []error
for _, session := range a.senderCache {
if err := session.Close(); err != nil {
closeErrors = append(closeErrors, fmt.Errorf("close session: %w", err))
}
}
clear(a.senderCache)
// pool
if err := a.connectionPool.Close(); err != nil {
closeErrors = append(closeErrors, fmt.Errorf("close pool: %w", err))
}
if len(closeErrors) > 0 {
return fmt.Errorf("close: %w", errors.Join(closeErrors...))
}
return nil
}

View file

@ -0,0 +1,463 @@
package api
import (
"context"
"errors"
"fmt"
"sync"
"testing"
"time"
"github.com/Azure/go-amqp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
internalMessaging "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/messaging"
pkgMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
pkgMessagingTest "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/test"
)
type amqpConnMock struct {
mock.Mock
}
func (m *amqpConnMock) Done() <-chan struct{} {
args := m.Called()
return args.Get(0).(<-chan struct{})
}
func (m *amqpConnMock) NewSession(ctx context.Context, opts *amqp.SessionOptions) (internalMessaging.AmqpSession, error) {
args := m.Called(ctx, opts)
return args.Get(0).(internalMessaging.AmqpSession), args.Error(1)
}
func (m *amqpConnMock) Close() error {
args := m.Called()
return args.Error(0)
}
var _ internalMessaging.AmqpConn = (*amqpConnMock)(nil)
type amqpSenderMock struct {
mock.Mock
}
func (m *amqpSenderMock) Send(ctx context.Context, msg *amqp.Message, opts *amqp.SendOptions) error {
return m.Called(ctx, msg, opts).Error(0)
}
func (m *amqpSenderMock) Close(ctx context.Context) error {
return m.Called(ctx).Error(0)
}
var _ internalMessaging.AmqpSender = (*amqpSenderMock)(nil)
type amqpSessionMock struct {
mock.Mock
}
func (m *amqpSessionMock) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (internalMessaging.AmqpSender, error) {
args := m.Called(ctx, target, opts)
return args.Get(0).(internalMessaging.AmqpSender), args.Error(1)
}
func (m *amqpSessionMock) Close(ctx context.Context) error {
args := m.Called(ctx)
return args.Error(0)
}
var _ internalMessaging.AmqpSession = (*amqpSessionMock)(nil)
type connectionPoolMock struct {
mock.Mock
}
func (m *connectionPoolMock) Close() error {
return m.Called().Error(0)
}
func (m *connectionPoolMock) NewHandle() *internalMessaging.ConnectionPoolHandle {
return m.Called().Get(0).(*internalMessaging.ConnectionPoolHandle)
}
func (m *connectionPoolMock) GetConnection(handle *internalMessaging.ConnectionPoolHandle) (*internalMessaging.AmqpConnection, error) {
return m.Called(handle).Get(0).(*internalMessaging.AmqpConnection), m.Called(handle).Error(1)
}
var _ internalMessaging.ConnectionPool = (*connectionPoolMock)(nil)
func Test_NewAmqpMessagingApi(t *testing.T) {
_, err := NewAmqpApi(
pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: "not-handled-protocol://localhost:5672"},
PoolSize: 1,
})
assert.EqualError(t, err, "new amqp connection pool: initialize connections: new connection: new internal connection: internal connect: dial: unsupported scheme \"not-handled-protocol\"")
}
func Test_AmqpMessagingApi_Send(t *testing.T) {
// Specify test timeout
ctx, cancelFn := context.WithTimeout(context.Background(), 120*time.Second)
defer cancelFn()
// Start solace docker container
solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
t.Run("Missing topic prefix", func(t *testing.T) {
defer solaceContainer.StopOnError()
api, err := NewAmqpApi(pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
assert.NoError(t, err)
err = api.Send(ctx, "topic-name", []byte{}, "application/json", make(map[string]any))
assert.EqualError(t, err, "send: topic \"topic-name\" name lacks mandatory prefix \"topic://\"\nretry send: topic \"topic-name\" name lacks mandatory prefix \"topic://\"")
})
t.Run("send successfully", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Initialize the solace queue
topicSubscriptionTopicPattern := "auditlog/>"
queueName := "send-successfully"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := fmt.Sprintf("topic://auditlog/%s", "amqp-send-successfully")
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
api, err := NewDefaultAmqpApi(pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString})
assert.NoError(t, err)
data := []byte("data")
applicationProperties := make(map[string]interface{})
applicationProperties["key"] = "value"
err = api.Send(ctx, topicName, data, "application/json", applicationProperties)
assert.NoError(t, err)
message, err := solaceContainer.NextMessage(ctx, fmt.Sprintf("queue://%s", queueName), true)
assert.NoError(t, err)
assert.Equal(t, "data", string(message.Data[0]))
assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, "application/json", *message.Properties.ContentType)
assert.Equal(t, applicationProperties, message.ApplicationProperties)
err = api.Close(ctx)
assert.NoError(t, err)
})
}
func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
channelReceiver := func(channel chan struct{}) <-chan struct{} {
return channel
}
newActiveConnection := func() *internalMessaging.AmqpConnection {
channel := make(chan struct{})
conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &internalMessaging.AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Conn: conn,
}
}
newClosedConnection := func() *internalMessaging.AmqpConnection {
channel := make(chan struct{})
close(channel)
conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &internalMessaging.AmqpConnection{
ConnectionName: "test",
Lock: sync.RWMutex{},
Conn: conn,
}
}
t.Run("connection nil sender nil", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Send", mock.Anything, mock.Anything, mock.Anything).Return(nil)
session := &amqpSessionMock{}
session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil)
connection := newActiveConnection()
conn := connection.Conn.(*amqpConnMock)
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil)
pool := &connectionPoolMock{}
pool.On("GetConnection", mock.Anything).Return(connection, nil)
amqpApi := &AmqpApi{
connectionPool: pool,
connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
}
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
assert.NoError(t, err)
sender.AssertNumberOfCalls(t, "Send", 1)
session.AssertNumberOfCalls(t, "NewSender", 1)
pool.AssertNumberOfCalls(t, "GetConnection", 2)
})
t.Run("connection closed sender nil", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Send", mock.Anything, mock.Anything, mock.Anything).Return(nil)
session := &amqpSessionMock{}
session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil)
connection := newActiveConnection()
conn := connection.Conn.(*amqpConnMock)
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil)
pool := &connectionPoolMock{}
pool.On("GetConnection", mock.Anything).Return(connection, nil)
closedConnection := newClosedConnection()
closedConnMock := closedConnection.Conn.(*amqpConnMock)
amqpApi := &AmqpApi{
connection: closedConnection,
connectionPool: pool,
connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
}
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
assert.NoError(t, err)
sender.AssertNumberOfCalls(t, "Send", 1)
session.AssertNumberOfCalls(t, "NewSender", 1)
pool.AssertNumberOfCalls(t, "GetConnection", 2)
closedConnMock.AssertNumberOfCalls(t, "Done", 1)
})
t.Run("connection nil get connection fail", func(t *testing.T) {
var connection *internalMessaging.AmqpConnection = nil
pool := &connectionPoolMock{}
pool.On("GetConnection", mock.Anything).Return(connection, errors.New("connection error"))
amqpApi := &AmqpApi{
connectionPool: pool,
connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
}
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
assert.EqualError(t, err, "get connection: connection error")
pool.AssertNumberOfCalls(t, "GetConnection", 2)
})
t.Run("connection active sender nil", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Send", mock.Anything, mock.Anything, mock.Anything).Return(nil)
session := &amqpSessionMock{}
session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil)
connection := newActiveConnection()
conn := connection.Conn.(*amqpConnMock)
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil)
amqpApi := &AmqpApi{
connection: connection,
senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
}
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
assert.NoError(t, err)
sender.AssertNumberOfCalls(t, "Send", 1)
session.AssertNumberOfCalls(t, "NewSender", 1)
})
t.Run("connection active new sender fail", func(t *testing.T) {
var sender *amqpSenderMock = nil
session := &amqpSessionMock{}
session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, errors.New("new sender error"))
session.On("Close", mock.Anything).Return(nil)
connection := newActiveConnection()
conn := connection.Conn.(*amqpConnMock)
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil)
amqpApi := &AmqpApi{
connection: connection,
senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
}
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
assert.EqualError(t, err, "new sender: new internal sender: new sender error")
session.AssertNumberOfCalls(t, "NewSender", 1)
session.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("connection active sender set", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Send", mock.Anything, mock.Anything, mock.Anything).Return(nil)
topic := "topic://some-topic"
amqpApi := &AmqpApi{
connection: newActiveConnection(),
senderCache: map[string]*internalMessaging.AmqpSenderSession{topic: {Sender: sender}},
}
err := amqpApi.Send(context.Background(), topic, []byte("data"), "application/json", make(map[string]any))
assert.NoError(t, err)
sender.AssertNumberOfCalls(t, "Send", 1)
})
t.Run("send fail", func(t *testing.T) {
sender := &amqpSenderMock{}
sender.On("Send", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("send error"))
session := &amqpSessionMock{}
session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil)
topic := "topic://some-topic"
connection := newActiveConnection()
connection.Conn.(*amqpConnMock).On("NewSession", mock.Anything, mock.Anything, mock.Anything).Return(session, nil)
amqpApi := &AmqpApi{
connection: connection,
senderCache: map[string]*internalMessaging.AmqpSenderSession{topic: {Sender: sender}},
}
err := amqpApi.Send(context.Background(), topic, []byte("data"), "application/json", make(map[string]any))
assert.EqualError(t, err, "send: send error\nretry send: send error")
sender.AssertNumberOfCalls(t, "Send", 2)
})
}
func Test_AmqpMessagingApi_Close(t *testing.T) {
t.Run("close without cached senders", func(t *testing.T) {
pool := &connectionPoolMock{}
pool.On("Close").Return(nil)
amqpApi := &AmqpApi{
connectionPool: pool,
connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
}
err := amqpApi.Close(context.Background())
assert.NoError(t, err)
pool.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("close fail without cached senders", func(t *testing.T) {
pool := &connectionPoolMock{}
pool.On("Close").Return(errors.New("close error"))
amqpApi := &AmqpApi{
connectionPool: pool,
connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
}
err := amqpApi.Close(context.Background())
assert.EqualError(t, err, "close: close pool: close error")
pool.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("close with cached senders", func(t *testing.T) {
pool := &connectionPoolMock{}
pool.On("Close").Return(nil)
session := &amqpSessionMock{}
session.On("Close", mock.Anything).Return(nil)
sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(nil)
senderSession := &internalMessaging.AmqpSenderSession{
Session: session,
Sender: sender,
}
amqpApi := &AmqpApi{
connectionPool: pool,
connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: map[string]*internalMessaging.AmqpSenderSession{"key": senderSession},
}
err := amqpApi.Close(context.Background())
assert.NoError(t, err)
assert.Equal(t, 0, len(amqpApi.senderCache))
pool.AssertNumberOfCalls(t, "Close", 1)
session.AssertNumberOfCalls(t, "Close", 1)
sender.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("close fail with cached senders", func(t *testing.T) {
pool := &connectionPoolMock{}
pool.On("Close").Return(nil)
session := &amqpSessionMock{}
session.On("Close", mock.Anything).Return(nil)
sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(errors.New("close sender error"))
senderSession := &internalMessaging.AmqpSenderSession{
Session: session,
Sender: sender,
}
amqpApi := &AmqpApi{
connectionPool: pool,
connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: map[string]*internalMessaging.AmqpSenderSession{"key": senderSession},
}
err := amqpApi.Close(context.Background())
assert.EqualError(t, err, "close: close session: close sender error")
assert.Equal(t, 0, len(amqpApi.senderCache))
pool.AssertNumberOfCalls(t, "Close", 1)
session.AssertNumberOfCalls(t, "Close", 1)
sender.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("close fail", func(t *testing.T) {
pool := &connectionPoolMock{}
pool.On("Close").Return(errors.New("close pool error"))
session := &amqpSessionMock{}
session.On("Close", mock.Anything).Return(errors.New("close session error"))
sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(errors.New("close sender error"))
senderSession := &internalMessaging.AmqpSenderSession{
Session: session,
Sender: sender,
}
amqpApi := &AmqpApi{
connectionPool: pool,
connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: map[string]*internalMessaging.AmqpSenderSession{"key": senderSession},
}
err := amqpApi.Close(context.Background())
assert.EqualError(t, err, "close: close session: close sender error\nclose session error\nclose pool: close pool error")
assert.Equal(t, 0, len(amqpApi.senderCache))
pool.AssertNumberOfCalls(t, "Close", 1)
session.AssertNumberOfCalls(t, "Close", 1)
sender.AssertNumberOfCalls(t, "Close", 1)
})
}

View file

@ -0,0 +1,28 @@
package api
import (
"context"
)
// Api is an abstraction for a messaging system that can be used to send
// audit logs to the audit log system.
type Api interface {
// Send method will send the given data to the specified topic synchronously.
// Parameters:
// * ctx - the context object
// * topic - the messaging topic where to send the data to
// * data - the serialized data as byte array
// * contentType - the contentType of the serialized data
// * applicationProperties - properties to send with the message (i.e. cloud event headers)
//
// It returns technical errors for connection issues or sending problems.
Send(ctx context.Context, topic string, data []byte, contentType string, applicationProperties map[string]any) error
// Close the underlying connection to the messaging system.
// Parameters:
// * ctx - the context object
//
// It returns an error if the connection cannot be closed successfully
Close(ctx context.Context) error
}

View file

@ -0,0 +1,12 @@
package common
type AmqpConnectionConfig struct {
BrokerUrl string `json:"brokerUrl"`
Username string `json:"username"`
Password string `json:"password"`
}
type AmqpConnectionPoolConfig struct {
Parameters AmqpConnectionConfig `json:"parameters"`
PoolSize int `json:"poolSize"`
}

View file

@ -1,26 +1,28 @@
package messaging package test
import ( import (
"bytes" "bytes"
"context" "context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/log"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/Azure/go-amqp"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"io" "io"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/Azure/go-amqp"
docker "github.com/docker/docker/api/types/container"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
pkgLog "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/log"
) )
const ( const AmqpQueuePrefix = "queue://"
AmqpTopicPrefix = "topic://" const AmqpTopicPrefix = "topic://"
AmqpQueuePrefix = "queue://" const dockerImage = "schwarzit-docker.jfrog.io/solace/solace-pubsub-standard:10.8.1.241"
)
var ErrResourceNotFound = errors.New("resource not found") var ErrResourceNotFound = errors.New("resource not found")
@ -133,12 +135,13 @@ func NewSolaceContainer(ctx context.Context) (*SolaceContainer, error) {
// Start docker container // Start docker container
request := testcontainers.ContainerRequest{ request := testcontainers.ContainerRequest{
Image: "solace/solace-pubsub-standard:10.8", Image: dockerImage,
ExposedPorts: []string{"5672/tcp", "8080/tcp"}, ExposedPorts: []string{"5672/tcp", "8080/tcp"},
SkipReaper: true, HostConfigModifier: func(config *docker.HostConfig) {
AutoRemove: true, config.AutoRemove = true
ShmSize: 1024 * 1024 * 1024, // 1 GB, config.ShmSize = 1024 * 1024 * 1024 // 1 GB,
Env: env, },
Env: env,
WaitingFor: wait.ForLog("Running pre-startup checks:"). WaitingFor: wait.ForLog("Running pre-startup checks:").
WithStartupTimeout(90 * time.Second), WithStartupTimeout(90 * time.Second),
} }
@ -168,7 +171,7 @@ func NewSolaceContainer(ctx context.Context) (*SolaceContainer, error) {
_ = container.Terminate(ctx) _ = container.Terminate(ctx)
return nil, err return nil, err
} }
log.AuditLogger.Info("UI Port: " + sempPort.Port()) pkgLog.AuditLogger.Info("UI Port: " + sempPort.Port())
// Construct connection strings // Construct connection strings
amqpConnectionString := fmt.Sprintf("amqp://%s:%s/", host, amqpPort.Port()) amqpConnectionString := fmt.Sprintf("amqp://%s:%s/", host, amqpPort.Port())
@ -296,8 +299,8 @@ func (c SolaceContainer) NewAmqpConnection(ctx context.Context) (*amqp.Conn, err
func (c SolaceContainer) ValidateTopicName(topicSubscriptionTopicPattern string, topicName string) error { func (c SolaceContainer) ValidateTopicName(topicSubscriptionTopicPattern string, topicName string) error {
// Cut off the topic:// prefix // Cut off the topic:// prefix
var name string var name string
if strings.HasPrefix(topicName, "topic://") { if strings.HasPrefix(topicName, AmqpTopicPrefix) {
name = topicName[len("topic://"):] name = topicName[len(AmqpTopicPrefix):]
} else { } else {
name = topicName name = topicName
} }

View file

@ -131,7 +131,8 @@ message AuditLog {
// Required: true // Required: true
string service_name = 1 [ string service_name = 1 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1 (buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
]; ];
// The name of the service method or operation. // The name of the service method or operation.
@ -232,17 +233,18 @@ message AuthenticationInfo {
// Required: true // Required: true
string principal_id = 1 [ string principal_id = 1 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1 (buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
]; ];
// The email address of the authenticated user. // The email address of the authenticated user.
// Service accounts have email addresses that can be used. // Service accounts have email addresses that can be used.
// //
// Required: true // Required: false
string principal_email = 2 [ optional string principal_email = 2 [
(buf.validate.field).required = true, (buf.validate.field).string.min_len = 5,
(buf.validate.field).string.min_len = 1, (buf.validate.field).string.max_len = 255,
(buf.validate.field).string.max_len = 255 (buf.validate.field).string.email = true
]; ];
// The name of the service account used to create or exchange // The name of the service account used to create or exchange
@ -325,7 +327,7 @@ message AttributeContext {
// Required: true // Required: true
string principal = 1 [ string principal = 1 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.pattern = "^[a-zA-Z0-9-%.]+/[a-zA-Z0-9-%.]+$" (buf.validate.field).string.pattern = "^[a-zA-Z0-9-%._]+/[a-zA-Z0-9-%.]+$"
]; ];
// The intended audience(s) for this authentication information. Reflects // The intended audience(s) for this authentication information. Reflects
@ -414,7 +416,8 @@ message AttributeContext {
string path = 4 [ string path = 4 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1, (buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 255 (buf.validate.field).string.max_len = 255,
(buf.validate.field).string.pattern = ".*\\S.*"
]; ];
// The HTTP request `Host` header value. // The HTTP request `Host` header value.
@ -422,7 +425,8 @@ message AttributeContext {
// Required: true // Required: true
string host = 5 [ string host = 5 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1 (buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
]; ];
// The URL scheme, such as `http`, `https` or `gRPC`. // The URL scheme, such as `http`, `https` or `gRPC`.
@ -430,7 +434,8 @@ message AttributeContext {
// Required: true // Required: true
string scheme = 6 [ string scheme = 6 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1 (buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
]; ];
// The HTTP URL query in the format of "name1=value1&name2=value2", as it // The HTTP URL query in the format of "name1=value1&name2=value2", as it
@ -457,7 +462,8 @@ message AttributeContext {
// Required: true // Required: true
string protocol = 9 [ string protocol = 9 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1 (buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
]; ];
// The request authentication. // The request authentication.
@ -521,7 +527,8 @@ message RequestMetadata {
string caller_supplied_user_agent = 2 [ string caller_supplied_user_agent = 2 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1, (buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 255 (buf.validate.field).string.max_len = 255,
(buf.validate.field).string.pattern = ".*\\S.*"
]; ];
// This field contains request attributes like request url, time, etc. // This field contains request attributes like request url, time, etc.
@ -577,7 +584,8 @@ message ServiceAccountDelegationInfo {
// Required: true // Required: true
string principal_id = 1 [ string principal_id = 1 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1 (buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
]; ];
// The email address of the authenticated user. // The email address of the authenticated user.
@ -587,7 +595,8 @@ message ServiceAccountDelegationInfo {
string principal_email = 2 [ string principal_email = 2 [
(buf.validate.field).required = true, (buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1, (buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 255 (buf.validate.field).string.max_len = 255,
(buf.validate.field).string.pattern = ".*\\S.*"
]; ];
// Metadata about the service that uses the service account. // Metadata about the service that uses the service account.

13
sonar-project.properties Normal file
View file

@ -0,0 +1,13 @@
sonar.projectKey=xx-sit-odj-sec-ident:audit-go
sonar.host.url=https://sonarqube.schwarz
sonar.projectName=audit-go
sonar.sources=.
sonar.exclusions=**/*_test.go,**/vendor/**,**/mocks/**,**/*.yml,**/gen/**, **/test/solace.go
sonar.tests=.
sonar.test.inclusions=**/*_test.go
sonar.test.exclusions=**/vendor/**,**/mocks/**
sonar.issuesReport.html.enable=true
sonar.log.level=INFO
sonar.go.coverage.reportPaths=out/cover.out
sonar.go.tests.reportPaths=out/report.json
sonar.go.golangci-lint.reportPaths=out/lint.xml