Compare commits

..

74 commits
main ... v0.0.1

Author SHA1 Message Date
Christian Schaible
18a6f6f07d Add telemetry data to cloud event properties 2024-10-23 12:38:34 +02:00
Christian Schaible
26673e8962 Add snyk check 2024-10-23 07:48:34 +02:00
Christian Schaible
baeb466421 Remove operation name from base64 serialization utilities 2024-10-21 08:28:32 +02:00
Christian Schaible
c222e6f79a Do not return the operation name from AuditEventBuilder's build method 2024-10-21 08:02:23 +02:00
Christian Schaible
81dff7b29a Update module name 2024-10-18 08:55:14 +02:00
Christian Schaible
a7d26e55dd Update dependencies 2024-10-17 15:40:08 +02:00
Christian Schaible
c62161173e Add cloud event base64 serialization convenience functions 2024-10-17 14:35:07 +02:00
Christian Schaible
fdecdd935c Configure version numbers of build dependencies in pipeline variables 2024-10-16 14:21:50 +02:00
Christian Schaible
7d299ada4c Add build pipeline yml 2024-10-16 14:21:50 +02:00
Christian Schaible
e9a6279ab2 Increase solace test container startup timeouts 2024-10-16 13:21:40 +02:00
Christian Schaible
d415c97559 Update default buf linting rule 2024-10-16 11:07:51 +02:00
Christian Schaible
f9cefe876b Replace Singular-/PluralType with ObjectType 2024-10-15 15:09:28 +02:00
Christian Schaible
03d4ae5d1b Fix event type mapping in legacy converter and system event handling 2024-10-15 11:30:03 +02:00
Christian Schaible
66773aad3d Do not return plural type in GetObjectIdAndTypeFromUrlPath 2024-10-10 15:18:14 +02:00
Christian Schaible
965fa4e617 Filter :authority gRPC header 2024-10-10 07:12:28 +02:00
Christian Schaible
d1b1ae040f Make test data factory methods private 2024-10-09 11:49:09 +02:00
Christian Schaible
a990233715 Format protobuf files 2024-10-09 11:15:01 +02:00
Christian Schaible
63ac2962e9 Add AsSystemEvent method to event builders 2024-10-09 08:23:23 +02:00
Christian Schaible
51cf882c93 Move http-method converter and schema validation test to api package 2024-10-08 14:02:20 +02:00
Christian Schaible
3f222fc2be Split log implementations 2024-10-07 14:20:39 +02:00
Christian Schaible
a98e802f55 Add log abstraction 2024-10-07 13:57:06 +02:00
Christian Schaible
3bbf1cca71 Replace SempClient implementation 2024-10-07 10:27:17 +02:00
Christian Schaible
8263ad9d5f Reject event type data-access
as it is currently not supported by downstream services.
2024-10-07 07:19:15 +02:00
Christian Schaible
a05c8b54b2 Fix service account name extraction from token 2024-10-02 14:07:36 +02:00
Christian Schaible
abab0eb0a4 Unescape query parameters in legacy message converter 2024-10-02 10:55:47 +02:00
Christian Schaible
e9bd1575aa Support logging of cloud events in legacy format 2024-10-01 14:58:52 +02:00
Christian Schaible
0e6aac7370 Fix data and content-type in legacy apis 2024-10-01 14:36:10 +02:00
Christian Schaible
6fd141e227 Fix logging of cloud events 2024-10-01 14:04:22 +02:00
Christian Schaible
a364d42c7d Add optional response body serialization to event builder 2024-10-01 12:17:36 +02:00
Christian Schaible
0caeabedbd Simplify logging of cloud events 2024-10-01 11:31:18 +02:00
Christian Schaible
f8f0b48437 Add event builder 2024-09-30 13:08:43 +02:00
Christian Schaible
ab40008fb1 Rename Request into ApiRequest 2024-09-30 11:28:04 +02:00
Christian Schaible
fc895948bb Filter out ip adresses in service name detection 2024-09-12 13:14:33 +02:00
Christian Schaible
be23f50c5a Fix additional http method resolution 2024-09-09 15:49:55 +02:00
Christian Schaible
959d7a642c Add additional http method to support non-standard methods 2024-09-09 15:39:40 +02:00
Christian Schaible
0798ddd690 Add fallback to lowercase user-agent header name 2024-09-09 15:21:52 +02:00
Christian Schaible
3297776240 Add fallback to lowercase authoriation header lookup 2024-09-09 15:02:51 +02:00
Christian Schaible
f8d0efe6c3 Allow nil as query string in convenience code 2024-09-09 13:29:37 +02:00
Christian Schaible
313486e30b Update schema description to reflect gRPC as communication protocol 2024-09-09 13:28:51 +02:00
Christian Schaible
61ac703743 Add reusable code to create audit events 2024-09-04 15:01:27 +02:00
Christian Schaible
8ad633a5c4 Update regular expressions
- Allow encoded request query parameters
- Remove strict formats at other places
2024-09-04 11:21:32 +02:00
Christian Schaible
1a9cd436e9 Rephrase description of IAM and response item parameter documentation 2024-09-02 08:59:19 +02:00
Christian Schaible
e355e25136 Add max length constraints to the AuditLogEntry 2024-08-29 15:02:09 +02:00
Christian Schaible
4d993237ff Write AuditLogEntry.Metadata into LegacyAuditEvent.Details 2024-08-29 14:04:11 +02:00
Christian Schaible
eb22259346 Update README.md 2024-08-29 14:04:04 +02:00
Christian Schaible
0a8516f4a3 Update buf configuration 2024-08-29 13:03:39 +02:00
Christian Schaible
07d8183f88 Remove python and java configurations 2024-08-28 10:34:39 +02:00
Christian Schaible
3d833fe301 Remove insert id and request header TODOs from schema 2024-08-27 08:57:47 +02:00
Christian Schaible
543e8e9ea2 Merge response messages 2024-08-27 08:44:59 +02:00
Christian Schaible
d2246889e0 Update go to 1.23 and the dependencies versions 2024-08-23 07:44:13 +02:00
Christian Schaible
81a9c32bcc Remove commented out code from buf.gen.yaml 2024-08-23 07:29:10 +02:00
Christian Schaible
50be98360c Rename module 2024-08-23 07:28:15 +02:00
Christian Schaible
dc9d331b85 Allow operation names without versions and versions with characters (v1alpha, etc) 2024-08-22 13:00:04 +02:00
Christian Schaible
b3adb22680 Add request attributes to event details 2024-08-22 11:38:11 +02:00
Christian Schaible
07a26f986a Loosen operation name constraint to allow longer names 2024-08-22 11:38:10 +02:00
Christian Schaible
a353d4fa0c Introduce additional legacy api to dynamically set the topic name per message via context 2024-08-20 12:58:24 +02:00
Christian Schaible
e759ae397a Add http method conversion convenience function 2024-08-01 11:42:30 +02:00
Christian Schaible
db9440206f Rename fields, fix ToDo's and linter issues 2024-08-01 11:32:47 +02:00
Christian Schaible
fd8892aad8 Make cloud event attributes accessible 2024-07-31 13:45:14 +02:00
Christian Schaible
03fb0dd5ed Simplify routable type conversion 2024-07-31 13:44:52 +02:00
Christian Schaible
92fb9923a7 Allow users of the library to send tracing headers with the events 2024-07-30 14:55:33 +02:00
Christian Schaible
23f99b0668 Add mutex api wrapper 2024-07-30 13:17:10 +02:00
Christian Schaible
5d8aa9ee94 Remove strict identifier type specification from schema 2024-07-30 12:59:37 +02:00
Christian Schaible
ed68f3c6d9 Update schema and fix tests 2024-07-29 14:05:37 +02:00
Christian Schaible
da9ef5f707 Fix visibility mapping in legacy api 2024-07-24 13:26:18 +02:00
Christian Schaible
9bd5be52b7 Additional schema changes 2024-07-24 13:25:55 +02:00
Christian Schaible
65fac35d23 Add remarks to rework to the schema 2024-07-19 10:02:56 +02:00
Christian Schaible
957896b4a0 Use google audit schema 2024-07-18 14:33:11 +02:00
Christian Schaible
dff37867e5 Add event source, region and container reference to audit event and
replace wrapping protobuf message type with cloud event wrapper
2024-07-18 14:09:07 +02:00
Christian Schaible
6968ddb5c9 Add sequence number 2024-07-16 16:13:29 +02:00
Christian Schaible
85f07ec14a Fix linter issues 2024-07-15 12:16:44 +02:00
Christian Schaible
a509aae2a6 Add generated go files to git 2024-07-15 11:13:35 +02:00
Christian Schaible
4437c7b510 Rename module to dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git 2024-07-15 09:24:45 +02:00
Christian Schaible
de5d0a8948 Add Schema and API draft 2024-07-05 10:38:18 +02:00
75 changed files with 4099 additions and 6737 deletions

View file

@ -1,22 +1,17 @@
pool:
vmImage: 'ubuntu-24.04'
vmImage: 'ubuntu-latest'
variables:
- name: bufVersion
# go install github.com/bufbuild/buf/cmd/buf@
value: v1.63.0
value: v1.45.0
- name: golangCiLintVersion
# github.com/golangci/golangci-lint
value: v2.8.0
value: v1.61.0
- name: goVersion
# github.com/golang/go
value: 1.24.0
value: 1.23.2
- name: protobufValidateVersion
# go install github.com/envoyproxy/protoc-gen-validate@
value: v1.3.0
value: v1.1.0
- name: protobufVersion
# go install google.golang.org/protobuf/cmd/protoc-gen-go@
value: v1.36.11
value: v1.35.1
- name: GOPATH
value: '$(system.defaultWorkingDirectory)/gopath'
@ -26,7 +21,6 @@ stages:
- job: GoBuildTest
displayName: Run build and tests
variables:
- group: artifactory-xx-sit-odj-sec-ident
- name: isCiBuild
value: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]
steps:
@ -86,10 +80,6 @@ stages:
condition: succeeded()
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 ./...
condition: succeeded()
displayName: Build

View file

@ -1,46 +0,0 @@
---
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

@ -1,11 +0,0 @@
[Describe what was done in this PR]
[Describe why it was done]
[Describe how it was done if the change needs explanation]
[Describe how the change was tested if it needs explanation]
Security-concept-update-needed: false.
JIRA Work Item: [STACKITRMA-XXX](https://jira.schwarz/browse/STACKITRMA-XXX)

View file

@ -1,66 +0,0 @@
trigger: none
pool:
vmImage: 'ubuntu-24.04'
parameters:
- name: releaseType
displayName: Type of the release
type: string
default: minor
values:
- major
- minor
- patch
stages:
- stage: Release
variables:
- name: isMainBranch
value: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]
jobs:
- job: Release
condition: eq(variables.isMainBranch, true)
displayName: Release
steps:
- checkout: self
persistCredentials: true
fetchDepth: 0
- bash: |
set -e
RELEASE_TYPE="${{ parameters.releaseType }}"
TAG_VERSION=$(git describe --tags --abbrev=0 --match v\*)
VERSION_NUMBER=$(echo ${TAG_VERSION} | sed 's/v//g' | sed 's/-.*//g')
MAJOR=$(echo ${VERSION_NUMBER} | cut -d. -f1)
MINOR=$(echo ${VERSION_NUMBER} | cut -d. -f2)
PATCH=$(echo ${VERSION_NUMBER} | cut -d. -f3)
echo "Current version ${VERSION_NUMBER}"
echo "Major version: ${MAJOR}"
echo "Minor version: ${MINOR}"
echo "Patch version: ${PATCH}"
if [ "${RELEASE_TYPE}" == "major" ]; then
RELEASE_VERSION=$((MAJOR + 1)).0.0
elif [ "${RELEASE_TYPE}" == "minor" ]; then
RELEASE_VERSION=${MAJOR}.$((MINOR + 1)).0
elif [ "${RELEASE_TYPE}" == "patch" ]; then
RELEASE_VERSION=${MAJOR}.${MINOR}.$((PATCH + 1))
else
echo "No release type specified"
exit 0
fi
RELEASE_VERSION="v${RELEASE_VERSION}"
echo "Release version: ${RELEASE_VERSION}"
COMMIT_ID=$(git rev-parse HEAD)
echo "Commit: ${COMMIT_ID}"
git tag ${RELEASE_VERSION} ${COMMIT_ID}
git push origin ${RELEASE_VERSION}
displayName: Release new ${{ parameters.releaseType }} version

View file

@ -1,287 +0,0 @@
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$

View file

@ -1,82 +0,0 @@
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,42 +1,10 @@
> ## 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
The audit-go library is the core library for validation and sending of audit events.
### API Documentation
The api documentation can be found
The api documentation can be found
[here](https://developers.stackit.schwarz/domains/core-platform/audit-log/sdk/overview/).
### Supported data types for routing
@ -63,37 +31,20 @@ The code can be found in the [api_routable.go](./api_routable.go) and
### Development
#### Go
The current minimum Go version is **go1.24.0**.
#### Linter
The linter *golangci-lint* can either be installed via package manager (e.g. brew) or
by running the following command in the terminal:
```shell
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.8.0
```
#### Schema Generation
Go structs are generated from Protobuf schema by using [Buf](https://buf.build) and some plugins.
The buf plugins are referenced in the *proto/buf.gen.yaml* file and are expected
to be installed locally.
Go structs are generated from Protobuf schema by using [Buf](https://buf.build).
The schema generator also generates code to validate constraints specified
in the schema.
Buf and the required plugins can either be installed via package manager (e.g. brew)
or manually by running:
It may be required to install required plugins manually by running:
```shell
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.11 #Pipeline: protobufVersion, go.mod: buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go
go install github.com/envoyproxy/protoc-gen-validate@v1.3.0 #Pipeline: protobufValidateVersion, go.mod: google.golang.org/protobuf
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.35.1
go install github.com/envoyproxy/protoc-gen-validate@v1.1.0
```
Please check that the versions above match the versions in the *go.mod* file
and the *.azuredevops/build-pipeline.yml* file.
Please check that the version matches the version in the *go.mod* file.
Then the schema can be generated:

View file

@ -1,23 +1,16 @@
package common
package api
import (
"context"
"regexp"
"time"
"buf.build/go/protovalidate"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
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
const (
@ -37,15 +30,14 @@ const (
)
func ObjectTypeFromPluralString(value string) ObjectType {
pluralSuffix := "s"
switch value {
case string(ObjectTypeOrganization) + pluralSuffix:
case "organizations":
return ObjectTypeOrganization
case string(ObjectTypeFolder) + pluralSuffix:
case "folders":
return ObjectTypeFolder
case string(ObjectTypeProject) + pluralSuffix:
case "projects":
return ObjectTypeProject
case string(ObjectTypeSystem):
case "system":
return ObjectTypeSystem
default:
return ObjectType(value)
@ -54,7 +46,13 @@ func ObjectTypeFromPluralString(value string) ObjectType {
func (t ObjectType) IsSupportedType() error {
switch t {
case ObjectTypeOrganization, ObjectTypeFolder, ObjectTypeProject, ObjectTypeSystem:
case ObjectTypeOrganization:
fallthrough
case ObjectTypeFolder:
fallthrough
case ObjectTypeProject:
fallthrough
case ObjectTypeSystem:
return nil
default:
return ErrUnknownObjectType
@ -62,16 +60,15 @@ func (t ObjectType) IsSupportedType() error {
}
func (t ObjectType) Plural() string {
pluralSuffix := "s"
switch t {
case ObjectTypeOrganization:
return string(ObjectTypeOrganization) + pluralSuffix
return "organizations"
case ObjectTypeFolder:
return string(ObjectTypeFolder) + pluralSuffix
return "folders"
case ObjectTypeProject:
return string(ObjectTypeProject) + pluralSuffix
return "projects"
case ObjectTypeSystem:
return string(ObjectTypeSystem)
return "system"
default:
return ""
}
@ -83,24 +80,19 @@ var RoutableSystemIdentifier = NewRoutableIdentifier(SystemIdentifier)
// AuditApi is the interface to log audit events.
//
// It provides a Log method that can be used to validate and directly send events.
// If the transactional outbox pattern should be used, the ValidateAndSerialize and Send methods
// If the transactional outbox patter should be used, the ValidateAndSerialize and Send methods
// can be called manually to decouple operations.
type AuditApi interface {
// Log is a convenience method that validates, serializes and sends data over the wire.
// If the transactional outbox pattern should be used, the ValidateAndSerialize method
// and Send method can be called separately.
// If an error is returned it is the responsibility of the caller to retry. The api does
// not store, buffer events or retry failed invocation automatically.
// If the transactional outbox patter should be used, the ValidateAndSerialize method
// and Send method can be called manually.
//
// Parameters:
// * ctx - the context object
// * event - the auditV1.AuditEvent
// * visibility - route the event only internally or to the customer (no routing in the legacy solution)
// * routableIdentifier - the identifier of the object
//
// Returns:
// * an error if the validation, serialization or send failed
Log(
ctx context.Context,
event *auditV1.AuditLogEntry,
@ -108,37 +100,45 @@ type AuditApi interface {
routableIdentifier *RoutableIdentifier,
) error
// ValidateAndSerialize validates and serializes the event into a byte representation.
// The result has to be sent explicitly by calling the Send method.
// LogWithTrace is a convenience method that validates, serializes and sends data over the wire.
// If the transactional outbox patter should be used, the ValidateAndSerialize method
// and Send method can be called manually.
//
// Parameters:
// * ctx - the context object
// * event - the auditV1.AuditEvent
// * visibility - route the event only internally or to the customer (no routing in the legacy solution)
// * routableIdentifier - the identifier of the object
//
// Returns:
// * the CloudEvent (i.e. the serialized AuditLogEntry with metadata)
// * an error if validation or serialization failed
ValidateAndSerialize(
// * traceParent - optional trace parent
// * traceState - optional trace state
LogWithTrace(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
traceParent *string,
traceState *string,
) error
// ValidateAndSerialize validates and serializes the event into a byte representation.
// The result has to be sent explicitly by calling the Send method.
ValidateAndSerialize(
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) (*CloudEvent, error)
// ValidateAndSerializeWithTrace validates and serializes the event into a byte representation.
// The result has to be sent explicitly by calling the Send method.
ValidateAndSerializeWithTrace(
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
traceParent *string,
traceState *string,
) (*CloudEvent, error)
// Send the serialized content as byte array to the audit log system.
// If an error is returned it is the responsibility of the caller to
// retry. The api does not store, buffer events or retry failed
// invocation automatically.
//
// Parameters:
// * ctx - the context object
// * routableIdentifier - the identifier of the object
// * cloudEvent - the serialized AuditLogEntry with metadata
//
// Returns:
// * an error if the event could not be sent
Send(
ctx context.Context,
routableIdentifier *RoutableIdentifier,
@ -149,7 +149,7 @@ type AuditApi interface {
// ProtobufValidator is an abstraction for validators.
// Concrete implementations are e.g. protovalidate.Validator
type ProtobufValidator interface {
Validate(msg proto.Message, options ...protovalidate.ValidationOption) error
Validate(msg proto.Message) error
}
// CloudEvent is a representation of a cloudevents.io object.
@ -224,17 +224,6 @@ type TopicNameResolver interface {
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 {
Identifier string
Type ObjectType
@ -244,7 +233,6 @@ func NewRoutableIdentifier(objectIdentifier *auditV1.ObjectIdentifier) *Routable
if objectIdentifier == nil {
return nil
}
return &RoutableIdentifier{
Identifier: objectIdentifier.Identifier,
Type: ObjectType(objectIdentifier.Type),

249
audit/api/api_common.go Normal file
View file

@ -0,0 +1,249 @@
package api
import (
"context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/telemetry"
"errors"
"fmt"
"strings"
"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"
)
// ContentTypeCloudEventsProtobuf the cloudevents protobuf content-type sent in metadata of messages
const ContentTypeCloudEventsProtobuf = "application/cloudevents+protobuf"
const ContentTypeCloudEventsJson = "application/cloudevents+json; charset=UTF-8"
// 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")
func validateAndSerializePartially(
validator *ProtobufValidator,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) (*auditV1.RoutableAuditEvent, error) {
// Return error if the given event or object identifier is nil
if event == nil {
return nil, ErrEventNil
}
if routableIdentifier == nil {
return nil, ErrObjectIdentifierNil
}
// Validate the actual event
err := (*validator).Validate(event)
if err != nil {
return nil, err
}
// Ensure that a valid object identifier is set if the event is public
if isSystemIdentifier(routableIdentifier) && visibility == auditV1.Visibility_VISIBILITY_PUBLIC {
return nil, ErrObjectIdentifierVisibilityMismatch
}
// Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, ErrUnknownObjectType) {
return nil, ErrUnsupportedRoutableType
}
return nil, err
}
// Check identifier consistency across event attributes
if strings.HasSuffix(event.LogName, string(EventTypeSystemEvent)) {
if !(routableIdentifier.Identifier == SystemIdentifier.Identifier && routableIdentifier.Type == ObjectTypeSystem) {
return nil, ErrInvalidRoutableIdentifierForSystemEvent
}
// The resource name can either contain the system identifier or another resource identifier
} else {
if err := areIdentifiersIdentical(routableIdentifier, event.LogName); err != nil {
return nil, err
}
if err := areIdentifiersIdentical(routableIdentifier, event.ProtoPayload.ResourceName); err != nil {
return nil, err
}
}
// Test serialization even if the data is dropped later when logging to the legacy solution
auditEventBytes, err := proto.Marshal(event)
if err != nil {
return nil, err
}
payload := auditV1.UnencryptedData{
Data: auditEventBytes,
ProtobufType: fmt.Sprintf("%v", event.ProtoReflect().Descriptor().FullName()),
}
routableEvent := auditV1.RoutableAuditEvent{
OperationName: event.ProtoPayload.OperationName,
ObjectIdentifier: routableIdentifier.ToObjectIdentifier(),
Visibility: visibility,
Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &payload},
}
err = (*validator).Validate(&routableEvent)
if err != nil {
return nil, err
}
return &routableEvent, nil
}
// Send implements AuditApi.Send
func send(
topicNameResolver *TopicNameResolver,
messagingApi *messaging.Api,
ctx context.Context,
routableIdentifier *RoutableIdentifier,
cloudEvent *CloudEvent,
) error {
// Check that given objects are not nil
if topicNameResolver == nil {
return ErrTopicNameResolverNil
}
if messagingApi == nil {
return ErrMessagingApiNil
}
if cloudEvent == nil {
return ErrCloudEventNil
}
if routableIdentifier == nil {
return ErrObjectIdentifierNil
}
// Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, ErrUnknownObjectType) {
return ErrUnsupportedRoutableType
}
return err
}
topic, err := (*topicNameResolver).Resolve(routableIdentifier)
if err != nil {
return err
}
// Naming according to AMQP protocol binding spec
// https://github.com/cloudevents/spec/blob/main/cloudevents/bindings/amqp-protocol-binding.md
applicationAttributes := make(map[string]any)
applicationAttributes["cloudEvents:specversion"] = cloudEvent.SpecVersion
applicationAttributes["cloudEvents:source"] = cloudEvent.Source
applicationAttributes["cloudEvents:id"] = cloudEvent.Id
applicationAttributes["cloudEvents:time"] = cloudEvent.Time.UnixMilli()
applicationAttributes["cloudEvents:datacontenttype"] = cloudEvent.DataContentType
applicationAttributes["cloudEvents:type"] = cloudEvent.DataType
applicationAttributes["cloudEvents:subject"] = cloudEvent.Subject
if cloudEvent.TraceParent != nil {
applicationAttributes["cloudEvents:traceparent"] = *cloudEvent.TraceParent
}
if cloudEvent.TraceState != nil {
applicationAttributes["cloudEvents:tracestate"] = *cloudEvent.TraceState
}
// Telemetry
applicationAttributes["cloudEvents:sdklanguage"] = "go"
auditGoVersion := telemetry.AuditGoVersion
if auditGoVersion != "" {
applicationAttributes["cloudEvents:sdkversion"] = auditGoVersion
}
auditGoGrpcVersion := telemetry.AuditGoGrpcVersion
if auditGoGrpcVersion != "" {
applicationAttributes["cloudEvents:sdkgrpcversion"] = auditGoGrpcVersion
}
auditGoHttpVersion := telemetry.AuditGoHttpVersion
if auditGoHttpVersion != "" {
applicationAttributes["cloudEvents:sdkhttpversion"] = auditGoHttpVersion
}
return (*messagingApi).Send(
ctx,
topic,
(*cloudEvent).Data,
(*cloudEvent).DataContentType,
applicationAttributes)
}
func isSystemIdentifier(identifier *RoutableIdentifier) bool {
if identifier.Identifier == uuid.Nil.String() && identifier.Type == ObjectTypeSystem {
return true
}
return false
}
func areIdentifiersIdentical(routableIdentifier *RoutableIdentifier, logName string) error {
dataType, identifier := getTypeAndIdentifierFromString(logName)
objectType := ObjectTypeFromPluralString(dataType)
err := objectType.IsSupportedType()
if err != nil {
return err
}
return areTypeAndIdentifierIdentical(routableIdentifier, objectType, identifier)
}
func areTypeAndIdentifierIdentical(routableIdentifier *RoutableIdentifier, dataType ObjectType, identifier string) error {
if routableIdentifier.Identifier != identifier {
return ErrAttributeIdentifierInvalid
}
if routableIdentifier.Type != dataType {
return ErrAttributeTypeInvalid
}
return nil
}
func getTypeAndIdentifierFromString(input string) (string, string) {
parts := strings.Split(input, "/")
dataType := parts[0]
identifier := parts[1]
return dataType, identifier
}

View file

@ -0,0 +1,414 @@
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)
}
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)
}

164
audit/api/api_legacy.go Normal file
View file

@ -0,0 +1,164 @@
package api
import (
"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"
"strings"
"google.golang.org/protobuf/proto"
)
const DataTypeLegacyAuditEventV1 = "audit.v1.LegacyAuditEvent"
// LegacyTopicNameResolver implements TopicNameResolver.
// A hard-coded topic name is used, routing identifiers are ignored.
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
}
// LegacyAuditApi is an implementation of AuditApi to send events to the legacy audit log system.
//
// Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented
type LegacyAuditApi struct {
messagingApi *messaging.Api
topicNameResolver *TopicNameResolver
validator *ProtobufValidator
}
// 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
func NewLegacyAuditApi(
messagingApi *messaging.Api,
topicNameConfig LegacyTopicNameConfig,
validator ProtobufValidator,
) (*AuditApi, error) {
if messagingApi == nil {
return nil, ErrMessagingApiNil
}
// Topic resolver
if topicNameConfig.TopicName == "" {
return nil, errors.New("topic name is required")
}
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: topicNameConfig.TopicName}
// Audit api
var auditApi AuditApi = &LegacyAuditApi{
messagingApi: messagingApi,
topicNameResolver: &topicNameResolver,
validator: &validator,
}
return &auditApi, nil
}
// Log implements AuditApi.Log
func (a *LegacyAuditApi) Log(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) error {
return a.LogWithTrace(ctx, event, visibility, routableIdentifier, nil, nil)
}
// LogWithTrace implements AuditApi.LogWithTrace
func (a *LegacyAuditApi) LogWithTrace(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
traceParent *string,
traceState *string,
) error {
cloudEvent, err := a.ValidateAndSerializeWithTrace(event, visibility, routableIdentifier, traceParent, traceState)
if err != nil {
return err
}
return a.Send(ctx, routableIdentifier, cloudEvent)
}
// ValidateAndSerialize implements AuditApi.ValidateAndSerialize.
// It serializes the event into the byte representation of the legacy audit log system.
func (a *LegacyAuditApi) ValidateAndSerialize(
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) (*CloudEvent, error) {
return a.ValidateAndSerializeWithTrace(event, visibility, routableIdentifier, nil, nil)
}
// ValidateAndSerializeWithTrace implements AuditApi.ValidateAndSerializeWithTrace.
// It serializes the event into the byte representation of the legacy audit log system.
func (a *LegacyAuditApi) ValidateAndSerializeWithTrace(
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
traceParent *string,
traceState *string,
) (*CloudEvent, error) {
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
// Reject event type data-access as the downstream services
// cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess
}
// Do nothing with the serialized data in the legacy solution
_, err = proto.Marshal(routableEvent)
if err != nil {
return nil, err
}
// Convert attributes
legacyBytes, err := convertAndSerializeIntoLegacyFormat(event, routableEvent)
if err != nil {
return nil, err
}
message := CloudEvent{
SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName,
Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1,
Subject: event.ProtoPayload.ResourceName,
Data: legacyBytes,
TraceParent: traceParent,
TraceState: traceState,
}
return &message, nil
}
// Send implements AuditApi.Send
func (a *LegacyAuditApi) Send(
ctx context.Context,
routableIdentifier *RoutableIdentifier,
cloudEvent *CloudEvent,
) error {
return send(a.topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
}

View file

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

View file

@ -3,14 +3,12 @@ package api
import (
"testing"
"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"
"github.com/stretchr/testify/assert"
)
func Test_ConvertAndSerializeIntoLegacyFormat_NoObjectIdentifier(t *testing.T) {
event, _ := NewProjectAuditEvent(nil)
event, _ := newProjectAuditEvent(nil)
routableEvent := auditV1.RoutableAuditEvent{
OperationName: event.ProtoPayload.OperationName,
Visibility: auditV1.Visibility_VISIBILITY_PUBLIC,
@ -18,6 +16,6 @@ func Test_ConvertAndSerializeIntoLegacyFormat_NoObjectIdentifier(t *testing.T) {
Data: nil,
}
_, err := ConvertAndSerializeIntoLegacyFormat(event, &routableEvent)
assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
_, err := convertAndSerializeIntoLegacyFormat(event, &routableEvent)
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
}

View file

@ -0,0 +1,160 @@
package api
import (
"context"
"errors"
"fmt"
"strings"
"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"
)
type ContextKey string
const ContextKeyTopic ContextKey = "topic"
var ErrNoTopicNameProvided = errors.New("no topic name provided")
var ErrTopicNameEmpty = errors.New("empty topic name provided")
// DynamicLegacyAuditApi is an implementation of AuditApi to send events to the legacy audit log system
// by setting the topic name explicitly in the context with the key "topic".
//
// Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented
type DynamicLegacyAuditApi struct {
messagingApi *messaging.Api
validator *ProtobufValidator
}
// NewDynamicLegacyAuditApi can be used to initialize the audit log api.
//
// Note: The NewLegacyAuditApi method will be deprecated and replaced with "newRoutableAuditApi" once the new audit log routing is implemented
func NewDynamicLegacyAuditApi(
messagingApi *messaging.Api,
validator ProtobufValidator,
) (*AuditApi, error) {
if messagingApi == nil {
return nil, ErrMessagingApiNil
}
// Audit api
var auditApi AuditApi = &DynamicLegacyAuditApi{
messagingApi: messagingApi,
validator: &validator,
}
return &auditApi, nil
}
// Log implements AuditApi.Log
func (a *DynamicLegacyAuditApi) Log(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) error {
return a.LogWithTrace(ctx, event, visibility, routableIdentifier, nil, nil)
}
// LogWithTrace implements AuditApi.LogWithTrace
func (a *DynamicLegacyAuditApi) LogWithTrace(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
traceParent *string,
traceState *string,
) error {
cloudEvent, err := a.ValidateAndSerializeWithTrace(event, visibility, routableIdentifier, traceParent, traceState)
if err != nil {
return err
}
return a.Send(ctx, routableIdentifier, cloudEvent)
}
// ValidateAndSerialize implements AuditApi.ValidateAndSerialize.
// It serializes the event into the byte representation of the legacy audit log system.
func (a *DynamicLegacyAuditApi) ValidateAndSerialize(
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) (*CloudEvent, error) {
return a.ValidateAndSerializeWithTrace(event, visibility, routableIdentifier, nil, nil)
}
// ValidateAndSerializeWithTrace implements AuditApi.ValidateAndSerializeWithTrace.
// It serializes the event into the byte representation of the legacy audit log system.
func (a *DynamicLegacyAuditApi) ValidateAndSerializeWithTrace(
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
traceParent *string,
traceState *string,
) (*CloudEvent, error) {
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
// Reject event type data-access as the downstream services
// cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess
}
// Do nothing with the serialized data in the legacy solution
_, err = proto.Marshal(routableEvent)
if err != nil {
return nil, err
}
// Convert attributes
legacyBytes, err := convertAndSerializeIntoLegacyFormat(event, routableEvent)
if err != nil {
return nil, err
}
message := CloudEvent{
SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName,
Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1,
Subject: event.ProtoPayload.ResourceName,
Data: legacyBytes,
TraceParent: traceParent,
TraceState: traceState,
}
return &message, nil
}
// Send implements AuditApi.Send
//
// Requires to have the topic name set as key "topic" in the context.
func (a *DynamicLegacyAuditApi) Send(
ctx context.Context,
routableIdentifier *RoutableIdentifier,
cloudEvent *CloudEvent,
) error {
rawTopicName := ctx.Value(ContextKeyTopic)
if rawTopicName == nil {
return ErrNoTopicNameProvided
}
topicName := fmt.Sprintf("%s", rawTopicName)
if len(topicName) == 0 {
return ErrTopicNameEmpty
}
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: topicName}
return send(&topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
}

View file

@ -9,17 +9,12 @@ import (
"testing"
"time"
"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/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"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) {
@ -29,23 +24,21 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
defer cancelFn()
// Start solace docker container
solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
solaceContainer, err := messaging.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
amqpApi, err := pkgMessagingApi.NewAmqpApi(
pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString})
assert.NoError(t, err)
// Validator
validator, err := protovalidate.New()
assert.NoError(t, err)
topicSubscriptionTopicPattern := "stackit-platform/t/swz/audit-log/>"
topicSubscriptionTopicPattern := "audit-log/>"
traceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
traceState := "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
// Check that event-type data-access is rejected as it is currently
// not supported by downstream services
@ -57,29 +50,31 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-rejected"
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-rejected"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
event, objectIdentifier := newOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.ErrorIs(t, auditApi.Log(
ctxWithTopic,
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.ErrorIs(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
), ErrUnsupportedEventTypeDataAccess)
})
// Check logging of organization events
@ -91,33 +86,35 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event, objectIdentifier := newOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctxWithTopic,
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
t.Run("Log private organization event", func(t *testing.T) {
@ -128,33 +125,35 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event, objectIdentifier := newOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctxWithTopic,
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
// Check logging of folder events
@ -166,33 +165,35 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/folder-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
event, objectIdentifier := newFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctxWithTopic,
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
t.Run("Log private folder event", func(t *testing.T) {
@ -203,33 +204,35 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/folder-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
event, objectIdentifier := newFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctxWithTopic,
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
// Check logging of project events
@ -241,33 +244,35 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
event, objectIdentifier := newProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctxWithTopic,
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
t.Run("Log private project event", func(t *testing.T) {
@ -278,33 +283,35 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
event, objectIdentifier := newProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctxWithTopic,
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
// Check logging of system events with identifier
@ -315,28 +322,30 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-system-changed"
topicName := "topic://audit-log/eu01/v1/resource-manager/project-system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := internalAuditApi.NewProjectSystemAuditEvent(nil)
event := newProjectSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t,
auditApi.Log(
ctxWithTopic,
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.RoutableSystemIdentifier,
RoutableSystemIdentifier,
nil,
nil,
))
// Receive the event from solace
@ -345,11 +354,11 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Check topic name
assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:traceparent"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent internalAuditApi.LegacyAuditEvent
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
@ -371,28 +380,30 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/system-changed"
topicName := "topic://audit-log/eu01/v1/resource-manager/system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := internalAuditApi.NewSystemAuditEvent(nil)
event := newSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t,
auditApi.Log(
ctxWithTopic,
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.RoutableSystemIdentifier,
RoutableSystemIdentifier,
nil,
nil,
))
// Receive the event from solace
@ -401,11 +412,11 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Check topic name
assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:traceparent"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent internalAuditApi.LegacyAuditEvent
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -426,35 +437,37 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
amqpApi,
messagingApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event, objectIdentifier := newOrganizationAuditEvent(nil)
escapedQuery := url.QueryEscape("param=value")
event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
ctxWithTopic := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, auditApi.Log(
ctxWithTopic,
ctx := context.WithValue(ctx, ContextKeyTopic, topicName)
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessageWithDetails(t, topicName, message, event)
validateSentMessageWithDetails(t, topicName, message, event, &traceParent, &traceState)
})
}
@ -469,15 +482,12 @@ func TestDynamicLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var protobufValidator ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
auditApi := DynamicLegacyAuditApi{validator: &protobufValidator}
event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
event := newSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
@ -486,22 +496,19 @@ func TestDynamicLegacyAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var protobufValidator ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
auditApi := DynamicLegacyAuditApi{validator: &protobufValidator}
event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
event := newSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
func TestDynamicLegacyAuditApi_Log_NilEvent(t *testing.T) {
auditApi := DynamicLegacyAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
auditApi := DynamicLegacyAuditApi{}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil)
}
func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) {
@ -509,16 +516,13 @@ func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectI
objectIdentifier *auditV1.ObjectIdentifier) {
objectIdentifier.Type = "invalid"
}
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(&customization)
event, objectIdentifier := newProjectAuditEvent(&customization)
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(nil)
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var protobufValidator ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
auditApi := DynamicLegacyAuditApi{validator: &protobufValidator}
_, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
}

View file

@ -10,18 +10,13 @@ import (
"testing"
"time"
"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/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"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) {
@ -31,22 +26,21 @@ func TestLegacyAuditApi(t *testing.T) {
defer cancelFn()
// Start solace docker container
solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
solaceContainer, err := messaging.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
amqpApi, err := pkgMessagingApi.NewAmqpApi(pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString})
assert.NoError(t, err)
// Validator
validator, err := protovalidate.New()
assert.NoError(t, err)
topicSubscriptionTopicPattern := "stackit-platform/t/swz/audit-log/>"
topicSubscriptionTopicPattern := "audit-log/>"
traceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
traceState := "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
// Check that event-type data-access is rejected as it is currently
// not supported by downstream services
@ -58,28 +52,30 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-rejected"
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-rejected"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
event, objectIdentifier := newOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1)
// Log the event to solace
assert.ErrorIs(t, auditApi.Log(
assert.ErrorIs(t, (*auditApi).LogWithTrace(
ctx,
event,
auditV1.Visibility_VISIBILITY_PUBLIC,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
), ErrUnsupportedEventTypeDataAccess)
})
// Check logging of organization events
@ -91,33 +87,35 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event, objectIdentifier := newOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
t.Run("Log private organization event", func(t *testing.T) {
@ -128,33 +126,35 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event, objectIdentifier := newOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
// Check logging of folder events
@ -166,33 +166,35 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/folder-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
event, objectIdentifier := newFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
t.Run("Log private folder event", func(t *testing.T) {
@ -203,33 +205,35 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/folder-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
event, objectIdentifier := newFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
// Check logging of project events
@ -241,33 +245,35 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
event, objectIdentifier := newProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
t.Run("Log private project event", func(t *testing.T) {
@ -278,33 +284,35 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/project-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
event, objectIdentifier := newProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessage(t, topicName, message, event)
validateSentMessage(t, topicName, message, event, &traceParent, &traceState)
})
// Check logging of system events with identifier
@ -315,28 +323,30 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/project-system-changed"
topicName := "topic://audit-log/eu01/v1/resource-manager/project-system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := internalAuditApi.NewProjectSystemAuditEvent(nil)
event := newProjectSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
auditApi.Log(
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.RoutableSystemIdentifier,
RoutableSystemIdentifier,
nil,
nil,
))
// Receive the event from solace
@ -345,11 +355,11 @@ func TestLegacyAuditApi(t *testing.T) {
// Check topic name
assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:traceparent"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent internalAuditApi.LegacyAuditEvent
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
@ -371,28 +381,30 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/system-changed"
topicName := "topic://audit-log/eu01/v1/resource-manager/system-changed"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := internalAuditApi.NewSystemAuditEvent(nil)
event := newSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
auditApi.Log(
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.RoutableSystemIdentifier,
RoutableSystemIdentifier,
nil,
nil,
))
// Receive the event from solace
@ -401,11 +413,11 @@ func TestLegacyAuditApi(t *testing.T) {
// Check topic name
assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:traceparent"])
assert.Nil(t, message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent internalAuditApi.LegacyAuditEvent
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -426,35 +438,37 @@ func TestLegacyAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern))
topicName := "topic://stackit-platform/t/swz/audit-log/eu01/v1/resource-manager/organization-created"
topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created"
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
// Instantiate audit api
auditApi, err := NewLegacyAuditApi(
amqpApi,
StaticTopicNameConfig{TopicName: topicName},
messagingApi,
LegacyTopicNameConfig{TopicName: topicName},
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event, objectIdentifier := newOrganizationAuditEvent(nil)
escapedQuery := url.QueryEscape("param=value")
event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentMessageWithDetails(t, topicName, message, event)
validateSentMessageWithDetails(t, topicName, message, event, &traceParent, &traceState)
})
}
@ -463,15 +477,17 @@ func validateSentMessage(
topicName string,
message *amqp.Message,
event *auditV1.AuditLogEntry,
traceParent *string,
traceState *string,
) {
// Check message properties
assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
assert.Equal(t, *traceParent, message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, *traceState, message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent internalAuditApi.LegacyAuditEvent
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
var severity string
@ -513,17 +529,19 @@ func validateSentMessageWithDetails(
topicName string,
message *amqp.Message,
event *auditV1.AuditLogEntry,
traceParent *string,
traceState *string,
) {
// Check topic name
assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsJson, message.ApplicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, ContentTypeCloudEventsJson, message.ApplicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, DataTypeLegacyAuditEventV1, message.ApplicationProperties["cloudEvents:type"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
assert.Equal(t, *traceParent, message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, *traceState, message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent internalAuditApi.LegacyAuditEvent
var auditEvent LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -571,38 +589,10 @@ func validateSentMessageWithDetails(
}
}
func TestLegacyAuditApi_NewLegacyAuditApi(t *testing.T) {
t.Run("messaging api nil", func(t *testing.T) {
auditApi, err := NewLegacyAuditApi(nil, StaticTopicNameConfig{}, nil)
assert.Nil(t, auditApi)
assert.EqualError(t, err, "messaging api nil")
})
t.Run("topic name is blank", func(t *testing.T) {
// Start solace docker container
solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
amqpApi, err := pkgMessagingApi.NewAmqpApi(pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
assert.NoError(t, err)
// Validator
validator, err := protovalidate.New()
assert.NoError(t, err)
auditApi, err := NewLegacyAuditApi(amqpApi, StaticTopicNameConfig{
TopicName: "",
}, validator)
assert.Nil(t, auditApi)
assert.EqualError(t, err, "topic name is required")
})
func TestLegacyAuditApi_NewLegacyAuditApi_MessagingApiNil(t *testing.T) {
auditApi, err := NewLegacyAuditApi(nil, LegacyTopicNameConfig{}, nil)
assert.Nil(t, auditApi)
assert.EqualError(t, err, "messaging api nil")
}
func TestLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
@ -610,15 +600,12 @@ func TestLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var protobufValidator ProtobufValidator = validator
auditApi := LegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
auditApi := LegacyAuditApi{validator: &protobufValidator}
event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
event := newSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
@ -627,22 +614,19 @@ func TestLegacyAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var protobufValidator ProtobufValidator = validator
auditApi := LegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
auditApi := LegacyAuditApi{validator: &protobufValidator}
event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
event := newSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
func TestLegacyAuditApi_Log_NilEvent(t *testing.T) {
auditApi := LegacyAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
auditApi := LegacyAuditApi{}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil)
}
func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) {
@ -650,16 +634,13 @@ func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifi
objectIdentifier *auditV1.ObjectIdentifier) {
objectIdentifier.Type = "invalid"
}
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(&customization)
event, objectIdentifier := newProjectAuditEvent(&customization)
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(nil)
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var protobufValidator ProtobufValidator = validator
auditApi := LegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
auditApi := LegacyAuditApi{validator: &protobufValidator}
_, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
}

111
audit/api/api_mock.go Normal file
View file

@ -0,0 +1,111 @@
package api
import (
"context"
"fmt"
"strings"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/bufbuild/protovalidate-go"
)
// MockAuditApi is an implementation of AuditApi that does nothing and has no dependency to external systems.
type MockAuditApi struct {
validator *ProtobufValidator
}
func NewMockAuditApi() (*AuditApi, error) {
validator, err := protovalidate.New()
if err != nil {
return nil, err
}
var protobufValidator ProtobufValidator = validator
var auditApi AuditApi = &MockAuditApi{validator: &protobufValidator}
return &auditApi, nil
}
// Log implements AuditApi.Log.
// Validates and serializes the event but doesn't send it.
func (a *MockAuditApi) Log(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) error {
return a.LogWithTrace(ctx, event, visibility, routableIdentifier, nil, nil)
}
// LogWithTrace implements AuditApi.LogWithTrace.
// Validates and serializes the event but doesn't send it.
func (a *MockAuditApi) LogWithTrace(
_ context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
traceParent *string,
traceState *string,
) error {
_, err := a.ValidateAndSerializeWithTrace(event, visibility, routableIdentifier, traceParent, traceState)
return err
}
// ValidateAndSerialize implements AuditApi.ValidateAndSerialize
func (a *MockAuditApi) ValidateAndSerialize(
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) (*CloudEvent, error) {
return a.ValidateAndSerializeWithTrace(event, visibility, routableIdentifier, nil, nil)
}
// ValidateAndSerializeWithTrace implements AuditApi.ValidateAndSerializeWithTrace
func (a *MockAuditApi) ValidateAndSerializeWithTrace(
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
traceParent *string,
traceState *string,
) (*CloudEvent, error) {
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
// Reject event type data-access as the downstream services
// cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess
}
routableEventBytes, err := proto.Marshal(routableEvent)
if err != nil {
return nil, err
}
message := CloudEvent{
SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName,
Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: "application/cloudevents+protobuf",
DataType: fmt.Sprintf("%v", routableEvent.ProtoReflect().Descriptor().FullName()),
Subject: event.ProtoPayload.ResourceName,
Data: routableEventBytes,
TraceParent: traceParent,
TraceState: traceState,
}
return &message, nil
}
// Send implements AuditApi.Send
func (a *MockAuditApi) Send(context.Context, *RoutableIdentifier, *CloudEvent) error {
return nil
}

View file

@ -0,0 +1,61 @@
package api
import (
"context"
"strings"
"testing"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/stretchr/testify/assert"
)
func TestMockAuditApi_Log(t *testing.T) {
auditApi, err := NewMockAuditApi()
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
routableObjectIdentifier := NewRoutableIdentifier(objectIdentifier)
// Test
t.Run("Log", func(t *testing.T) {
assert.Nil(t, (*auditApi).Log(
context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableObjectIdentifier))
})
t.Run("reject data access event", func(t *testing.T) {
event, objectIdentifier := newOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1)
routableObjectIdentifier := NewRoutableIdentifier(objectIdentifier)
assert.ErrorIs(t, (*auditApi).Log(
context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableObjectIdentifier),
ErrUnsupportedEventTypeDataAccess)
})
t.Run("ValidateAndSerialize", func(t *testing.T) {
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
cloudEvent, err := (*auditApi).ValidateAndSerializeWithTrace(
event, visibility, routableObjectIdentifier, nil, nil)
assert.NoError(t, err)
validateRoutableEventPayload(
t, cloudEvent.Data, objectIdentifier, event, event.ProtoPayload.OperationName, visibility)
})
t.Run("ValidateAndSerialize event nil", func(t *testing.T) {
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
_, err := (*auditApi).ValidateAndSerialize(nil, visibility, routableObjectIdentifier)
assert.ErrorIs(t, err, ErrEventNil)
})
t.Run("Send", func(t *testing.T) {
var cloudEvent = CloudEvent{}
assert.Nil(t, (*auditApi).Send(context.Background(), routableObjectIdentifier, &cloudEvent))
})
}

View file

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

View file

@ -8,20 +8,16 @@ import (
"testing"
"time"
"buf.build/go/protovalidate"
"github.com/Azure/go-amqp"
"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"
"github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.opentelemetry.io/otel"
"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) {
@ -31,21 +27,21 @@ func TestRoutableAuditApi(t *testing.T) {
defer cancelFn()
// Start solace docker container
solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
solaceContainer, err := messaging.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
amqpApi, err := pkgMessagingApi.NewAmqpApi(pgkMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pgkMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString})
assert.NoError(t, err)
// Validator
validator, err := protovalidate.New()
assert.NoError(t, err)
traceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
traceState := "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
// Instantiate the audit api
organizationTopicPrefix := "org"
projectTopicPrefix := "project"
@ -53,7 +49,7 @@ func TestRoutableAuditApi(t *testing.T) {
systemTopicName := "topic://system/admin-events"
auditApi, err := newRoutableAuditApi(
amqpApi,
messagingApi,
topicNameConfig{
FolderTopicPrefix: folderTopicPrefix,
OrganizationTopicPrefix: organizationTopicPrefix,
@ -74,16 +70,19 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
event, objectIdentifier := newOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
assert.ErrorIs(t, auditApi.Log(
assert.ErrorIs(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
), ErrUnsupportedEventTypeDataAccess)
})
// Check logging of organization events
@ -96,15 +95,18 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event, objectIdentifier := newOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
@ -116,7 +118,9 @@ func TestRoutableAuditApi(t *testing.T) {
objectIdentifier,
event,
"stackit.resourcemanager.v2.organization.created",
visibility)
visibility,
&traceParent,
&traceState)
})
t.Run("Log private organization event", func(t *testing.T) {
@ -127,7 +131,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event, objectIdentifier := newOrganizationAuditEvent(nil)
topicName := fmt.Sprintf("org/%s", objectIdentifier.Identifier)
assert.NoError(
t,
@ -136,11 +140,14 @@ func TestRoutableAuditApi(t *testing.T) {
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
auditApi.Log(
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -153,7 +160,9 @@ func TestRoutableAuditApi(t *testing.T) {
objectIdentifier,
event,
"stackit.resourcemanager.v2.organization.created",
visibility)
visibility,
&traceParent,
&traceState)
})
// Check logging of folder events
@ -166,15 +175,18 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*"))
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
event, objectIdentifier := newFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
@ -186,7 +198,9 @@ func TestRoutableAuditApi(t *testing.T) {
objectIdentifier,
event,
"stackit.resourcemanager.v2.folder.created",
visibility)
visibility,
&traceParent,
&traceState)
})
t.Run("Log private folder event", func(t *testing.T) {
@ -197,18 +211,21 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*"))
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
event, objectIdentifier := newFolderAuditEvent(nil)
topicName := fmt.Sprintf("folder/%s", objectIdentifier.Identifier)
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicName))
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
auditApi.Log(
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -221,7 +238,9 @@ func TestRoutableAuditApi(t *testing.T) {
objectIdentifier,
event,
"stackit.resourcemanager.v2.folder.created",
visibility)
visibility,
&traceParent,
&traceState)
})
// Check logging of project events
@ -233,16 +252,19 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
event, objectIdentifier := newProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
assert.NoError(t,
auditApi.Log(
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -255,7 +277,9 @@ func TestRoutableAuditApi(t *testing.T) {
objectIdentifier,
event,
"stackit.resourcemanager.v2.project.created",
visibility)
visibility,
&traceParent,
&traceState)
})
t.Run("Log private project event", func(t *testing.T) {
@ -266,16 +290,19 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
event, objectIdentifier := newProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
auditApi.Log(
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -288,7 +315,9 @@ func TestRoutableAuditApi(t *testing.T) {
objectIdentifier,
event,
"stackit.resourcemanager.v2.project.created",
visibility)
visibility,
&traceParent,
&traceState)
})
// Check logging of system events with identifier
@ -300,16 +329,18 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*"))
// Instantiate test data
event := internalAuditApi.NewProjectSystemAuditEvent(nil)
event := newProjectSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
auditApi.Log(
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.RoutableSystemIdentifier,
RoutableSystemIdentifier,
nil,
nil,
))
// Receive the event from solace
@ -328,14 +359,14 @@ func TestRoutableAuditApi(t *testing.T) {
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, "application/cloudevents+protobuf", applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "audit.v1.RoutableAuditEvent", applicationProperties["cloudEvents:type"])
assert.Equal(t, "", applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", applicationProperties["cloudEvents:tracestate"])
assert.Nil(t, applicationProperties["cloudEvents:traceparent"])
assert.Nil(t, applicationProperties["cloudEvents:tracestate"])
// Check deserialized message
validateRoutableEventPayload(
t,
message.Data[0],
pkgAuditCommon.RoutableSystemIdentifier.ToObjectIdentifier(),
RoutableSystemIdentifier.ToObjectIdentifier(),
event,
"stackit.resourcemanager.v2.system.changed",
visibility)
@ -350,16 +381,18 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*"))
// Instantiate test data
event := internalAuditApi.NewSystemAuditEvent(nil)
event := newSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
auditApi.Log(
(*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.RoutableSystemIdentifier,
RoutableSystemIdentifier,
nil,
nil,
))
// Receive the event from solace
@ -378,14 +411,14 @@ func TestRoutableAuditApi(t *testing.T) {
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, "application/cloudevents+protobuf", applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "audit.v1.RoutableAuditEvent", applicationProperties["cloudEvents:type"])
assert.Equal(t, "", applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", applicationProperties["cloudEvents:tracestate"])
assert.Nil(t, applicationProperties["cloudEvents:traceparent"])
assert.Nil(t, applicationProperties["cloudEvents:tracestate"])
// Check deserialized message
validateRoutableEventPayload(
t,
message.Data[0],
pkgAuditCommon.SystemIdentifier,
SystemIdentifier,
event,
"stackit.resourcemanager.v2.system.changed",
visibility)
@ -401,15 +434,18 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event, objectIdentifier := newOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
assert.NoError(t, auditApi.Log(
assert.NoError(t, (*auditApi).LogWithTrace(
ctx,
event,
visibility,
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
NewRoutableIdentifier(objectIdentifier),
&traceParent,
&traceState,
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
@ -421,7 +457,9 @@ func TestRoutableAuditApi(t *testing.T) {
objectIdentifier,
event,
"stackit.resourcemanager.v2.organization.created",
visibility)
visibility,
&traceParent,
&traceState)
})
}
@ -433,6 +471,8 @@ func validateSentEvent(
event *auditV1.AuditLogEntry,
operationName string,
visibility auditV1.Visibility,
traceParent *string,
traceState *string,
) {
// Check topic name
@ -447,10 +487,10 @@ func validateSentEvent(
_, isUuid := uuid.Parse(fmt.Sprintf("%s", applicationProperties["cloudEvents:id"]))
assert.True(t, true, isUuid)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "audit.v1.RoutableAuditEvent", applicationProperties["cloudEvents:type"])
assert.Equal(t, "", applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", applicationProperties["cloudEvents:tracestate"])
assert.Equal(t, *traceParent, applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, *traceState, applicationProperties["cloudEvents:tracestate"])
// Check deserialized message
validateRoutableEventPayload(
@ -489,8 +529,8 @@ func validateRoutableEventPayload(
func TestRoutableTopicNameResolver_Resolve_UnsupportedIdentifierType(t *testing.T) {
resolver := routableTopicNameResolver{}
_, err := resolver.Resolve(pkgAuditCommon.NewRoutableIdentifier(&auditV1.ObjectIdentifier{Type: "unsupported"}))
assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedObjectIdentifierType)
_, err := resolver.Resolve(NewRoutableIdentifier(&auditV1.ObjectIdentifier{Type: "unsupported"}))
assert.ErrorIs(t, err, ErrUnsupportedObjectIdentifierType)
}
func TestNewRoutableAuditApi_NewRoutableAuditApi_MessagingApiNil(t *testing.T) {
@ -504,15 +544,12 @@ func TestRoutableAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var protobufValidator ProtobufValidator = validator
auditApi := routableAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
auditApi := routableAuditApi{validator: &protobufValidator}
event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
event := newSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
@ -521,20 +558,17 @@ func TestRoutableAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var protobufValidator ProtobufValidator = validator
auditApi := routableAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
auditApi := routableAuditApi{validator: &protobufValidator}
event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
event := newSystemAuditEvent(nil)
err := auditApi.LogWithTrace(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier, nil, nil)
assert.ErrorIs(t, err, expectedError)
}
func TestRoutableAuditApi_Log_NilEvent(t *testing.T) {
auditApi := routableAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
auditApi := routableAuditApi{}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil)
}

View file

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

View file

@ -2,27 +2,24 @@ package api
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
"testing"
)
func Test_ToBase64(t *testing.T) {
t.Run("cloud event nil", func(t *testing.T) {
var cloudEvent *pkgAuditCommon.CloudEvent = nil
routableIdentifier := pkgAuditCommon.RoutableSystemIdentifier
var cloudEvent *CloudEvent = nil
routableIdentifier := RoutableSystemIdentifier
base64str, err := ToBase64(cloudEvent, routableIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrCloudEventNil)
assert.ErrorIs(t, err, ErrCloudEventNil)
assert.Nil(t, base64str)
})
t.Run("routable identifier nil", func(t *testing.T) {
cloudEvent := &pkgAuditCommon.CloudEvent{}
var routableIdentifier *pkgAuditCommon.RoutableIdentifier = nil
cloudEvent := &CloudEvent{}
var routableIdentifier *RoutableIdentifier = nil
base64str, err := ToBase64(cloudEvent, routableIdentifier)
assert.ErrorIs(t, err, ErrRoutableIdentifierNil)
@ -30,8 +27,8 @@ func Test_ToBase64(t *testing.T) {
})
t.Run("encoded event", func(t *testing.T) {
e := &pkgAuditCommon.CloudEvent{}
r := pkgAuditCommon.RoutableSystemIdentifier
e := &CloudEvent{}
r := RoutableSystemIdentifier
base64str, err := ToBase64(e, r)
assert.NoError(t, err)
@ -67,7 +64,7 @@ func Test_FromBase64(t *testing.T) {
t.Run("no json serialized event", func(t *testing.T) {
base64Str := base64.StdEncoding.EncodeToString([]byte("not expected"))
base64Str = base64Str + base64AuditEventV1
base64Str = base64Str + "v1"
cloudEvent, routableIdentifier, err := FromBase64(base64Str)
assert.EqualError(t, err, "invalid character 'o' in literal null (expecting 'u')")
assert.Nil(t, cloudEvent)
@ -75,8 +72,8 @@ func Test_FromBase64(t *testing.T) {
})
t.Run("decoded event", func(t *testing.T) {
e := &pkgAuditCommon.CloudEvent{}
r := pkgAuditCommon.RoutableSystemIdentifier
e := &CloudEvent{}
r := RoutableSystemIdentifier
base64str, err := ToBase64(e, r)
assert.NoError(t, err)

View file

@ -2,18 +2,13 @@ package api
import (
"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"
"fmt"
"time"
"github.com/google/uuid"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
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"
"time"
)
const quadZero = "0.0.0.0"
@ -26,7 +21,7 @@ type AuditParameters struct {
Details map[string]interface{}
// The type of the event
EventType pkgAuditCommon.EventType
EventType EventType
// A set of user-defined (key, value) data that provides additional
// information about the log entry.
@ -36,7 +31,7 @@ type AuditParameters struct {
ObjectId string
// Type of the object, the audit event refers to
ObjectType pkgAuditCommon.ObjectType
ObjectType ObjectType
ResponseBody any
@ -46,14 +41,14 @@ type AuditParameters struct {
func getObjectIdAndTypeFromAuditParams(
auditParams *AuditParameters,
) (string, *pkgAuditCommon.ObjectType, error) {
) (string, *ObjectType, error) {
objectId := auditParams.ObjectId
if objectId == "" {
return "", nil, errors.New("object id missing")
}
var objectType *pkgAuditCommon.ObjectType
var objectType *ObjectType
if auditParams.ObjectType != "" {
objectType = &auditParams.ObjectType
}
@ -70,18 +65,15 @@ func getObjectIdAndTypeFromAuditParams(
// AuditLogEntryBuilder collects audit params to construct auditV1.AuditLogEntry
type AuditLogEntryBuilder struct {
auditParams AuditParameters
auditRequest internalAuditApi.AuditRequest
auditResponse internalAuditApi.AuditResponse
auditMetadata internalAuditApi.AuditMetadata
auditRequest AuditRequest
auditResponse AuditResponse
auditMetadata AuditMetadata
// Region and optional zone id. If both, separated with a - (dash).
// Example: eu01
location string
// Opentelemetry tracer
tracer trace.Tracer
// The ID of the K8s Pod, Service-Instance, etc. (must be unique for a sending service)
// The ID of the K8s Pod, Service-Instance, etc (must be unique for a sending service)
workerId string
}
@ -92,23 +84,23 @@ func NewAuditLogEntryBuilder() *AuditLogEntryBuilder {
return &AuditLogEntryBuilder{
auditParams: AuditParameters{
EventType: pkgAuditCommon.EventTypeAdminActivity,
EventType: EventTypeAdminActivity,
},
auditRequest: internalAuditApi.AuditRequest{
Request: &pkgAuditCommon.ApiRequest{},
auditRequest: AuditRequest{
Request: &ApiRequest{},
RequestClientIP: quadZero,
RequestCorrelationId: nil,
RequestId: nil,
RequestTime: &requestTime,
},
auditResponse: internalAuditApi.AuditResponse{
auditResponse: AuditResponse{
ResponseBodyBytes: nil,
ResponseStatusCode: 200,
ResponseHeaders: make(map[string][]string),
ResponseNumItems: nil,
ResponseTime: nil,
},
auditMetadata: internalAuditApi.AuditMetadata{
auditMetadata: AuditMetadata{
AuditInsertId: "",
AuditLabels: nil,
AuditLogName: "",
@ -121,14 +113,13 @@ func NewAuditLogEntryBuilder() *AuditLogEntryBuilder {
AuditTime: nil,
},
location: "",
tracer: otel.Tracer("audit-log-entry-builder"),
workerId: "",
}
}
func (builder *AuditLogEntryBuilder) AsSystemEvent() *AuditLogEntryBuilder {
if builder.auditRequest.Request == nil {
builder.auditRequest.Request = &pkgAuditCommon.ApiRequest{}
builder.auditRequest.Request = &ApiRequest{}
}
if builder.auditRequest.Request.Header == nil {
builder.auditRequest.Request.Header = map[string][]string{"user-agent": {"none"}}
@ -151,25 +142,16 @@ func (builder *AuditLogEntryBuilder) AsSystemEvent() *AuditLogEntryBuilder {
if builder.auditRequest.RequestClientIP == "" {
builder.auditRequest.RequestClientIP = quadZero
}
builder.WithEventType(pkgAuditCommon.EventTypeSystemEvent)
builder.WithEventType(EventTypeSystemEvent)
return builder
}
// WithRequiredApiRequest adds api request details
func (builder *AuditLogEntryBuilder) WithRequiredApiRequest(request pkgAuditCommon.ApiRequest) *AuditLogEntryBuilder {
func (builder *AuditLogEntryBuilder) WithRequiredApiRequest(request ApiRequest) *AuditLogEntryBuilder {
builder.auditRequest.Request = &request
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).
// Example: eu01
func (builder *AuditLogEntryBuilder) WithRequiredLocation(location string) *AuditLogEntryBuilder {
@ -222,7 +204,7 @@ func (builder *AuditLogEntryBuilder) WithRequiredObjectId(objectId string) *Audi
// WithRequiredObjectType adds the object type.
// May be prefilled by audit middleware (if the type can be extracted from the url path).
func (builder *AuditLogEntryBuilder) WithRequiredObjectType(objectType pkgAuditCommon.ObjectType) *AuditLogEntryBuilder {
func (builder *AuditLogEntryBuilder) WithRequiredObjectType(objectType ObjectType) *AuditLogEntryBuilder {
builder.auditParams.ObjectType = objectType
return builder
}
@ -279,7 +261,7 @@ func (builder *AuditLogEntryBuilder) WithNumResponseItems(numResponseItems int64
}
// WithEventType overwrites the default event type EventTypeAdminActivity
func (builder *AuditLogEntryBuilder) WithEventType(eventType pkgAuditCommon.EventType) *AuditLogEntryBuilder {
func (builder *AuditLogEntryBuilder) WithEventType(eventType EventType) *AuditLogEntryBuilder {
builder.auditParams.EventType = eventType
return builder
}
@ -309,7 +291,7 @@ func (builder *AuditLogEntryBuilder) WithResponseBody(responseBody any) *AuditLo
}
// 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
return builder
}
@ -336,9 +318,6 @@ func (builder *AuditLogEntryBuilder) WithResponseTime(responseTime time.Time) *A
// - The auditV1.AuditLogEntry protobuf message or
// - Error if the entry cannot be built
func (builder *AuditLogEntryBuilder) Build(ctx context.Context, sequenceNumber SequenceNumber) (*auditV1.AuditLogEntry, error) {
_, span := builder.tracer.Start(ctx, "build-audit-log-entry")
defer span.End()
auditTime := time.Now()
builder.auditMetadata.AuditTime = &auditTime
@ -359,30 +338,32 @@ func (builder *AuditLogEntryBuilder) Build(ctx context.Context, sequenceNumber S
resourceName := fmt.Sprintf("%s/%s", objectType.Plural(), objectId)
var logIdentifier string
var logType pkgAuditCommon.ObjectType
if builder.auditParams.EventType == pkgAuditCommon.EventTypeSystemEvent {
logIdentifier = pkgAuditCommon.SystemIdentifier.Identifier
logType = pkgAuditCommon.ObjectTypeSystem
var logType ObjectType
if builder.auditParams.EventType == EventTypeSystemEvent {
logIdentifier = SystemIdentifier.Identifier
logType = ObjectTypeSystem
} else {
logIdentifier = objectId
logType = *objectType
}
builder.auditMetadata.AuditInsertId = internalAuditApi.NewInsertId(time.Now().UTC(), builder.location, builder.workerId, uint64(sequenceNumber))
builder.auditMetadata.AuditInsertId = 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.AuditResourceName = resourceName
var details map[string]interface{}
var details *map[string]interface{} = nil
if len(builder.auditParams.Details) > 0 {
details = builder.auditParams.Details
details = &builder.auditParams.Details
}
// Instantiate the audit event
return internalAuditApi.NewAuditLogEntry(
return NewAuditLogEntry(
builder.auditRequest,
builder.auditResponse,
details,
builder.auditMetadata,
nil,
nil,
)
}
@ -391,7 +372,7 @@ func (builder *AuditLogEntryBuilder) Build(ctx context.Context, sequenceNumber S
type AuditEventBuilder struct {
// The audit api used to validate, serialize and send events
api pkgAuditCommon.AuditApi
api *AuditApi
// The audit log entry builder which is used to build the actual protobuf message
auditLogEntryBuilder *AuditLogEntryBuilder
@ -400,9 +381,9 @@ type AuditEventBuilder struct {
built bool
// Sequence number generator providing sequential increasing numbers for the insert IDs
sequenceNumberGenerator pkgAuditUtils.SequenceNumberGenerator
sequenceNumberGenerator *utils.SequenceNumberGenerator
// Opentelemetry tracer
// Opentelemtry tracer
tracer trace.Tracer
// Visibility of the event
@ -413,10 +394,13 @@ type AuditEventBuilder struct {
// validates input and returns a cloud event that can be sent to the audit log system.
func NewAuditEventBuilder(
// The audit api used to validate, serialize and send events
api pkgAuditCommon.AuditApi,
api *AuditApi,
// The sequence number generator can be used to get and revert sequence numbers to build audit log events
sequenceNumberGenerator pkgAuditUtils.SequenceNumberGenerator,
sequenceNumberGenerator *utils.SequenceNumberGenerator,
// Tracer
tracer trace.Tracer,
// The service name in lowercase (allowed characters are [a-z-]).
serviceName string,
@ -434,7 +418,7 @@ func NewAuditEventBuilder(
WithRequiredWorkerId(workerId).
WithRequiredLocation(location),
sequenceNumberGenerator: sequenceNumberGenerator,
tracer: otel.Tracer("audit-event-builder"),
tracer: tracer,
visibility: auditV1.Visibility_VISIBILITY_PUBLIC,
}
}
@ -442,12 +426,12 @@ func NewAuditEventBuilder(
// NextSequenceNumber returns the next sequence number from utils.SequenceNumberGenerator.
// In case of an error RevertSequenceNumber must be called to prevent gaps in the sequence of numbers.
func (builder *AuditEventBuilder) NextSequenceNumber() SequenceNumber {
return SequenceNumber(builder.sequenceNumberGenerator.Next())
return SequenceNumber((*builder.sequenceNumberGenerator).Next())
}
// RevertSequenceNumber can be called to decrease the sequence number on the utils.SequenceNumberGenerator in case of an error
func (builder *AuditEventBuilder) RevertSequenceNumber(number SequenceNumber) {
builder.sequenceNumberGenerator.Revert(uint64(number))
func (builder *AuditEventBuilder) RevertSequenceNumber() {
(*builder.sequenceNumberGenerator).Revert()
}
func (builder *AuditEventBuilder) AsSystemEvent() *AuditEventBuilder {
@ -463,16 +447,11 @@ func (builder *AuditEventBuilder) WithAuditLogEntryBuilder(auditLogEntryBuilder
}
// WithRequiredApiRequest adds api request details
func (builder *AuditEventBuilder) WithRequiredApiRequest(request pkgAuditCommon.ApiRequest) *AuditEventBuilder {
func (builder *AuditEventBuilder) WithRequiredApiRequest(request ApiRequest) *AuditEventBuilder {
builder.auditLogEntryBuilder.WithRequiredApiRequest(request)
return builder
}
// GetApiRequest returns the api request details
func (builder *AuditEventBuilder) GetApiRequest() *pkgAuditCommon.ApiRequest {
return builder.auditLogEntryBuilder.GetApiRequest()
}
// WithRequiredRequestClientIp adds the client ip
func (builder *AuditEventBuilder) WithRequiredRequestClientIp(requestClientIp string) *AuditEventBuilder {
builder.auditLogEntryBuilder.WithRequiredRequestClientIp(requestClientIp)
@ -506,7 +485,7 @@ func (builder *AuditEventBuilder) WithRequiredObjectId(objectId string) *AuditEv
// WithRequiredObjectType adds the object type.
// May be prefilled by audit middleware (if the type can be extracted from the url path).
func (builder *AuditEventBuilder) WithRequiredObjectType(objectType pkgAuditCommon.ObjectType) *AuditEventBuilder {
func (builder *AuditEventBuilder) WithRequiredObjectType(objectType ObjectType) *AuditEventBuilder {
builder.auditLogEntryBuilder.WithRequiredObjectType(objectType)
return builder
}
@ -563,7 +542,7 @@ func (builder *AuditEventBuilder) WithNumResponseItems(numResponseItems int64) *
}
// WithEventType overwrites the default event type EventTypeAdminActivity
func (builder *AuditEventBuilder) WithEventType(eventType pkgAuditCommon.EventType) *AuditEventBuilder {
func (builder *AuditEventBuilder) WithEventType(eventType EventType) *AuditEventBuilder {
builder.auditLogEntryBuilder.WithEventType(eventType)
return builder
}
@ -593,7 +572,7 @@ func (builder *AuditEventBuilder) WithResponseBody(responseBody any) *AuditEvent
}
// 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)
return builder
}
@ -621,13 +600,6 @@ func (builder *AuditEventBuilder) IsBuilt() bool {
return builder.built
}
// MarkAsBuilt sets the internal built flag to true.
// This is useful in middlewares where the IsBuilt method is used
// to check if an event has been constructed.
func (builder *AuditEventBuilder) MarkAsBuilt() {
builder.built = true
}
// Build constructs the CloudEvent.
//
// Parameters:
@ -639,30 +611,26 @@ func (builder *AuditEventBuilder) MarkAsBuilt() {
// - The RoutableIdentifier required for routing the cloud event
// - The operation name
// - Error if the event cannot be built
func (builder *AuditEventBuilder) Build(ctx context.Context, sequenceNumber SequenceNumber) (*pkgAuditCommon.CloudEvent, *pkgAuditCommon.RoutableIdentifier, error) {
func (builder *AuditEventBuilder) Build(ctx context.Context, sequenceNumber SequenceNumber) (*CloudEvent, *RoutableIdentifier, error) {
if builder.auditLogEntryBuilder == nil {
return nil, nil, fmt.Errorf("audit log entry builder not set")
}
ctx, span := builder.tracer.Start(ctx, "build-audit-event")
defer span.End()
visibility := builder.visibility
objectId := builder.auditLogEntryBuilder.auditParams.ObjectId
objectType := builder.auditLogEntryBuilder.auditParams.ObjectType
var routingIdentifier *pkgAuditCommon.RoutableIdentifier
if builder.auditLogEntryBuilder.auditParams.EventType == pkgAuditCommon.EventTypeSystemEvent {
routingIdentifier = internalAuditApi.NewAuditRoutingIdentifier(uuid.Nil.String(), pkgAuditCommon.ObjectTypeSystem)
var routingIdentifier *RoutableIdentifier
if builder.auditLogEntryBuilder.auditParams.EventType == EventTypeSystemEvent {
routingIdentifier = NewAuditRoutingIdentifier(uuid.Nil.String(), ObjectTypeSystem)
if objectId == "" {
objectId = uuid.Nil.String()
builder.WithRequiredObjectId(objectId)
}
if objectType == "" {
objectType = pkgAuditCommon.ObjectTypeSystem
objectType = ObjectTypeSystem
builder.WithRequiredObjectType(objectType)
}
} else {
routingIdentifier = internalAuditApi.NewAuditRoutingIdentifier(objectId, objectType)
routingIdentifier = NewAuditRoutingIdentifier(objectId, objectType)
}
auditLogEntry, err := builder.auditLogEntryBuilder.Build(ctx, sequenceNumber)
@ -670,8 +638,19 @@ func (builder *AuditEventBuilder) Build(ctx context.Context, sequenceNumber Sequ
return nil, nil, err
}
ctx, span := builder.tracer.Start(ctx, "create-audit-event")
defer span.End()
w3cTraceParent := TraceParentFromSpan(span)
var traceParent = &w3cTraceParent
var traceState *string = nil
visibility := builder.visibility
// Validate and serialize the protobuf event into a cloud event
cloudEvent, err := builder.api.ValidateAndSerialize(ctx, auditLogEntry, visibility, routingIdentifier)
_, validateSerializeSpan := builder.tracer.Start(ctx, "validate-and-serialize-audit-event")
cloudEvent, err := (*builder.api).ValidateAndSerializeWithTrace(auditLogEntry, visibility, routingIdentifier, traceParent, traceState)
validateSerializeSpan.End()
if err != nil {
return nil, nil, err
}

View file

@ -2,21 +2,18 @@ package api
import (
"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"
"testing"
"time"
"buf.build/go/protovalidate"
"github.com/bufbuild/protovalidate-go"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb"
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"
"testing"
"time"
)
func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
@ -44,7 +41,7 @@ func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
objectId, objectType, err := getObjectIdAndTypeFromAuditParams(
&AuditParameters{
ObjectId: "value",
ObjectType: pkgAuditCommon.ObjectTypeFromPluralString("invalid"),
ObjectType: ObjectTypeFromPluralString("invalid"),
},
)
assert.EqualError(t, err, "unknown object type")
@ -58,12 +55,12 @@ func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
objectId, objectType, err := getObjectIdAndTypeFromAuditParams(
&AuditParameters{
ObjectId: "value",
ObjectType: pkgAuditCommon.ObjectTypeProject,
ObjectType: ObjectTypeProject,
},
)
assert.NoError(t, err)
assert.Equal(t, "value", objectId)
assert.Equal(t, pkgAuditCommon.ObjectTypeProject, *objectType)
assert.Equal(t, ObjectTypeProject, *objectType)
},
)
}
@ -80,7 +77,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
t.Run("details missing", func(t *testing.T) {
logEntry, err := NewAuditLogEntryBuilder().WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
@ -90,23 +87,23 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
assert.NoError(t, err)
err = validator.Validate(logEntry)
assert.Error(t, err)
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())
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())
})
t.Run("required only", func(t *testing.T) {
builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
@ -121,6 +118,8 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
assert.Equal(t, "projects/1/logs/admin-activity", logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity)
assert.NotNil(t, logEntry.Timestamp)
assert.Nil(t, logEntry.CorrelationId)
@ -130,7 +129,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.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.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -199,16 +198,16 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
@ -219,7 +218,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(pkgAuditCommon.EventTypePolicyDenied).
WithEventType(EventTypePolicyDenied).
WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId").
@ -237,6 +236,8 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
assert.Equal(t, "projects/1/logs/policy-denied", logEntry.LogName)
assert.Equal(t, map[string]string{"key": "label"}, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_ERROR, logEntry.Severity)
assert.NotNil(t, logEntry.Timestamp)
assert.Equal(t, "correlationId", *logEntry.CorrelationId)
@ -246,7 +247,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.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.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -310,105 +311,11 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
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) {
builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id").
@ -420,6 +327,8 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
assert.Equal(t, fmt.Sprintf("system/%s/logs/system-event", uuid.Nil.String()), logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity)
assert.NotNil(t, logEntry.Timestamp)
assert.Nil(t, logEntry.CorrelationId)
@ -429,7 +338,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo)
assert.Nil(t, authenticationInfo.PrincipalEmail)
assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -497,16 +406,16 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
@ -517,7 +426,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(pkgAuditCommon.EventTypeSystemEvent).
WithEventType(EventTypeSystemEvent).
WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId").
@ -549,21 +458,21 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
responseBodyBytes, err := ResponseBodyToBytes(responseBody)
assert.NoError(t, err)
builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
@ -578,21 +487,21 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
t.Run("with invalid response body", func(t *testing.T) {
builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId("1").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service").
@ -603,143 +512,16 @@ 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.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) {
t.Run("nothing set", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
Build(context.Background(), SequenceNumber(1))
assert.Error(t, err)
@ -750,44 +532,46 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("details missing", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
WithRequiredObjectId("objectId").
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
Build(context.Background(), SequenceNumber(1))
assert.Error(t, err)
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.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.Nil(t, cloudEvent)
assert.Nil(t, routingIdentifier)
})
t.Run("required only", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId).
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation(operation).
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredRequestClientIp("127.0.0.1")
routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.ObjectTypeProject}
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject}
cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
@ -803,8 +587,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.Equal(t, "1.0", cloudEvent.SpecVersion)
assert.Equal(t, fmt.Sprintf("projects/%s", objectId), cloudEvent.Subject)
assert.NotNil(t, cloudEvent.Time)
assert.Equal(t, "", *cloudEvent.TraceParent)
assert.Equal(t, "", *cloudEvent.TraceState)
assert.Equal(t, "00-00000000000000000000000000000000-0000000000000000-00", *cloudEvent.TraceParent)
assert.Nil(t, cloudEvent.TraceState)
var routableAuditEvent auditV1.RoutableAuditEvent
assert.NotNil(t, cloudEvent.Data)
@ -821,6 +605,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.Equal(t, fmt.Sprintf("projects/%s/logs/admin-activity", objectId), logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity)
assert.NotNil(t, logEntry.Timestamp)
assert.Nil(t, logEntry.CorrelationId)
@ -830,7 +616,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.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.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -889,7 +675,8 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("with details", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation"
@ -901,18 +688,18 @@ func Test_AuditEventBuilder(t *testing.T) {
responseBody := map[string]interface{}{"key": "response"}
responseBodyBytes, err := ResponseBodyToBytes(responseBody)
assert.NoError(t, err)
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId).
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation(operation).
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
@ -921,7 +708,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(pkgAuditCommon.EventTypeAdminActivity).
WithEventType(EventTypeAdminActivity).
WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId").
@ -934,7 +721,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithStatusCode(400).
WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE)
routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.ObjectTypeProject}
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject}
cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
@ -950,8 +737,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.Equal(t, "1.0", cloudEvent.SpecVersion)
assert.Equal(t, fmt.Sprintf("projects/%s", objectId), cloudEvent.Subject)
assert.NotNil(t, cloudEvent.Time)
assert.Equal(t, "", *cloudEvent.TraceParent)
assert.Equal(t, "", *cloudEvent.TraceState)
assert.Equal(t, "00-00000000000000000000000000000000-0000000000000000-00", *cloudEvent.TraceParent)
assert.Nil(t, cloudEvent.TraceState)
var routableAuditEvent auditV1.RoutableAuditEvent
assert.NotNil(t, cloudEvent.Data)
@ -968,6 +755,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.Equal(t, fmt.Sprintf("projects/%s/logs/admin-activity", objectId), logEntry.LogName)
assert.Equal(t, map[string]string{"key": "label"}, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_ERROR, logEntry.Severity)
assert.NotNil(t, logEntry.Timestamp)
assert.Equal(t, "correlationId", *logEntry.CorrelationId)
@ -977,7 +766,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.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.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -1043,13 +832,14 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("system event with object reference", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId).
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation(operation).
AsSystemEvent()
@ -1057,8 +847,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err)
assert.True(t, builder.IsBuilt())
assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
assert.Equal(t, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
assert.NotNil(t, cloudEvent)
assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType)
@ -1068,15 +858,15 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.Equal(t, "1.0", cloudEvent.SpecVersion)
assert.Equal(t, fmt.Sprintf("projects/%s", objectId), cloudEvent.Subject)
assert.NotNil(t, cloudEvent.Time)
assert.Equal(t, "", *cloudEvent.TraceParent)
assert.Equal(t, "", *cloudEvent.TraceState)
assert.Equal(t, "00-00000000000000000000000000000000-0000000000000000-00", *cloudEvent.TraceParent)
assert.Nil(t, cloudEvent.TraceState)
var routableAuditEvent auditV1.RoutableAuditEvent
assert.NotNil(t, cloudEvent.Data)
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type)
assert.Equal(t, SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type)
assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility)
assert.Equal(t, operation, routableAuditEvent.OperationName)
@ -1086,6 +876,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.Equal(t, fmt.Sprintf("system/%s/logs/system-event", uuid.Nil.String()), logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity)
assert.NotNil(t, logEntry.Timestamp)
assert.Nil(t, logEntry.CorrelationId)
@ -1095,7 +887,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo)
assert.Nil(t, authenticationInfo.PrincipalEmail)
assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -1155,10 +947,11 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("system event", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
WithRequiredOperation(operation).
AsSystemEvent()
@ -1166,8 +959,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err)
assert.True(t, builder.IsBuilt())
assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
assert.Equal(t, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
assert.NotNil(t, cloudEvent)
assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType)
@ -1177,15 +970,15 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.Equal(t, "1.0", cloudEvent.SpecVersion)
assert.Equal(t, fmt.Sprintf("system/%s", uuid.Nil.String()), cloudEvent.Subject)
assert.NotNil(t, cloudEvent.Time)
assert.Equal(t, "", *cloudEvent.TraceParent)
assert.Equal(t, "", *cloudEvent.TraceState)
assert.Equal(t, "00-00000000000000000000000000000000-0000000000000000-00", *cloudEvent.TraceParent)
assert.Nil(t, cloudEvent.TraceState)
var routableAuditEvent auditV1.RoutableAuditEvent
assert.NotNil(t, cloudEvent.Data)
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type)
assert.Equal(t, SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type)
assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility)
assert.Equal(t, operation, routableAuditEvent.OperationName)
@ -1195,6 +988,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.Equal(t, fmt.Sprintf("system/%s/logs/system-event", uuid.Nil.String()), logEntry.LogName)
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceState)
assert.Nil(t, logEntry.TraceParent)
assert.Equal(t, auditV1.LogSeverity_LOG_SEVERITY_DEFAULT, logEntry.Severity)
assert.NotNil(t, logEntry.Timestamp)
assert.Nil(t, logEntry.CorrelationId)
@ -1204,7 +999,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo)
assert.Nil(t, authenticationInfo.PrincipalEmail)
assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -1262,9 +1057,10 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err)
})
t.Run("with response body unserialized", func(t *testing.T) {
t.Run("with responsebody unserialized", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation"
@ -1274,18 +1070,18 @@ func Test_AuditEventBuilder(t *testing.T) {
requestTime := time.Now().AddDate(0, 0, -2).UTC()
responseTime := time.Now().AddDate(0, 0, -1).UTC()
responseBody := map[string]interface{}{"key": "response"}
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId).
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation(operation).
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "POST",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
@ -1294,7 +1090,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details).
WithEventType(pkgAuditCommon.EventTypeAdminActivity).
WithEventType(EventTypeAdminActivity).
WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId").
@ -1307,7 +1103,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithStatusCode(400).
WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE)
routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.ObjectTypeProject}
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject}
cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
@ -1335,21 +1131,12 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err)
})
t.Run("mark as built", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
builder.MarkAsBuilt()
assert.True(t, builder.IsBuilt())
})
t.Run("no entry builder", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01").
WithAuditLogEntryBuilder(nil).Build(context.Background(), SequenceNumber(1))
assert.EqualError(t, err, "audit log entry builder not set")
@ -1359,23 +1146,22 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("next sequence number", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01")
assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber())
assert.Equal(t, SequenceNumber(1), builder.NextSequenceNumber())
})
t.Run("revert sequence number", func(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test")
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", "worker-id", "eu01")
assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber())
builder.RevertSequenceNumber()
assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber())
assert.Equal(t, SequenceNumber(1), builder.NextSequenceNumber())
assert.Equal(t, SequenceNumber(2), builder.NextSequenceNumber())
builder.RevertSequenceNumber(SequenceNumber(1))
assert.Equal(t, SequenceNumber(1), builder.NextSequenceNumber())
assert.Equal(t, SequenceNumber(3), builder.NextSequenceNumber())
})
}

View file

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

View file

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

101
audit/api/log_test.go Normal file
View file

@ -0,0 +1,101 @@
package api
import (
"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"
"github.com/bufbuild/protovalidate-go"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
"testing"
)
func Test_LogEvent(t *testing.T) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
tracer := otel.Tracer("test-tracer")
t.Run("new format", func(t *testing.T) {
eventBuilder := NewAuditEventBuilder(api, sequenceNumberGenerator, tracer, "demo-service", uuid.NewString(), "eu01")
cloudEvent, _, err := eventBuilder.
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "GET",
Scheme: "https",
Proto: "HTTP/1.1",
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredObjectId(uuid.NewString()).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.project.update").
WithRequiredRequestClientIp("0.0.0.0").
Build(context.Background(), eventBuilder.NextSequenceNumber())
assert.NoError(t, err)
assert.NoError(t, LogEvent(cloudEvent))
})
t.Run("legacy format", func(t *testing.T) {
object_id := uuid.NewString()
entry, err := NewAuditLogEntryBuilder().
WithRequiredApiRequest(ApiRequest{
Body: nil,
Header: map[string][]string{"user-agent": {"custom"}, "authorization": {"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}},
Host: "localhost",
Method: "GET",
Scheme: "https",
Proto: "HTTP/1.1",
URL: RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId(object_id).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.project.update").
WithRequiredRequestClientIp("0.0.0.0").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId(uuid.NewString()).
Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
validator, err := protovalidate.New()
assert.NoError(t, err)
var protoValidator ProtobufValidator = validator
routableIdentifier := RoutableIdentifier{
Identifier: object_id,
Type: ObjectTypeProject,
}
routableEvent, err := validateAndSerializePartially(&protoValidator, entry, auditV1.Visibility_VISIBILITY_PUBLIC, &routableIdentifier)
assert.NoError(t, err)
legacyBytes, err := convertAndSerializeIntoLegacyFormat(entry, routableEvent)
assert.NoError(t, err)
cloudEvent := CloudEvent{
SpecVersion: "1.0",
Source: entry.ProtoPayload.ServiceName,
Id: entry.InsertId,
Time: entry.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1,
Subject: entry.ProtoPayload.ResourceName,
Data: legacyBytes,
TraceParent: nil,
TraceState: nil,
}
assert.NoError(t, LogEvent(&cloudEvent))
})
}

View file

@ -1,34 +1,92 @@
package api
import (
"context"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
"slices"
"strings"
"time"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/google/uuid"
"go.opentelemetry.io/otel/trace"
"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"
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"
"net"
"net/url"
"regexp"
"slices"
"strings"
"time"
)
const EmailAddressDoNotReplyAtStackItDotCloud = "do-not-reply@stackit.cloud"
const TokenClaimStackitProjectId = "stackit/project/project.id"
const TokenClaimStackitServiceAccountId = "stackit/serviceaccount/service-account.uid"
var ErrInvalidRequestBody = errors.New("invalid request body")
var ErrInvalidResponse = errors.New("invalid response")
var ErrInvalidAuthorizationHeaderValue = errors.New("invalid authorization header value")
var ErrInvalidBearerToken = errors.New("invalid 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
type AuditRequest struct {
@ -38,16 +96,16 @@ type AuditRequest struct {
// It should never include user-generated data, such as file contents.
//
// Required: true
Request *pkgAuditCommon.ApiRequest
Request *ApiRequest
// The IP address of the caller.
// For caller from internet, this will be public IPv4 or IPv6 address.
// For caller from a VM / K8s Service / etc., this will be the SIT proxy's IPv4 address.
// For caller from a VM / K8s Service / etc, this will be the SIT proxy's IPv4 address.
//
// Required: true
RequestClientIP string
// Correlate multiple audit logs by setting the same id
// Correlate multiple auFdit logs by setting the same id
//
// Required: false
RequestCorrelationId *string
@ -60,7 +118,7 @@ type AuditRequest struct {
//
// Format: <idempotency-key>
// Where:
// Idempotency-key: Typically consists of an id + version
// Idempotency-key: Typically consists of a id + version
//
// Examples:
// 5e3952a9-b628-4be6-ac61-b1c6eb4a110c/5
@ -83,7 +141,7 @@ type AuditResponse struct {
// elsewhere in the log record.
//
// Required: false
ResponseBodyBytes []byte
ResponseBodyBytes *[]byte
// The http or gRPC status code.
//
@ -122,7 +180,7 @@ type AuditMetadata struct {
// Where:
// Unix-Timestamp: A UTC unix timestamp in seconds is expected
// Region-Zone: The region and (optional) zone id. If both, separated with a - (dash)
// Worker-Id: The ID of the K8s Pod, Service-Instance, etc. (must be unique for a sending service)
// Worker-Id: The ID of the K8s Pod, Service-Instance, etc (must be unique for a sending service)
// Sequence-Number: Increasing number, representing the message offset per Worker-Id
// If the Worker-Id changes, the sequence-number has to be reset to 0.
//
@ -162,7 +220,7 @@ type AuditMetadata struct {
// Where:
// Product: The name of the service in lowercase
// Version: Optional API version
// Type-Chain: Optional chained path to object
// Type-Chain: Chained path to object
// Operation: The name of the operation in lowercase
//
// Examples:
@ -202,8 +260,7 @@ type AuditMetadata struct {
// "organizations/40ab14ad-b7b0-4b1c-be41-5bc820a968d1"
// "projects/7046e7b6-5ae9-441c-99fe-2cd28a5078ec/locations/_/instances/instance-20240723-174217"
// "projects/7046e7b6-5ae9-441c-99fe-2cd28a5078ec/locations/eu01/instances/instance-20240723-174217"
// "projects/7046e7b6-5ae9-441c-99fe-2cd28a5078ec/locations/sx-stoi01/instances/instance-20240723-174217"
// "projects/dd7d1807-54e9-4426-8994-721758b5b554/locations/eu01-m/vms/b6851b4e-7a9d-4973-ab0f-a80a13ee3060/ports/78f8bad4-a291-4fa3-b07f-4a1985d3dbe8"
// "projects/dd7d1807-54e9-4426-8994-721758b5b554/locations/eu01/vms/b6851b4e-7a9d-4973-ab0f-a80a13ee3060/ports/78f8bad4-a291-4fa3-b07f-4a1985d3dbe8"
//
// Required: true
AuditResourceName string
@ -232,10 +289,16 @@ func NewAuditLogEntry(
auditResponse AuditResponse,
// Optional map that is added as "details" to the message
eventMetadata map[string]interface{},
eventMetadata *map[string]interface{},
// Required metadata
auditMetadata AuditMetadata,
// Optional W3C trace parent
userProvidedTraceParent *string,
// Optional W3C trace state
userProvidedTraceState *string,
) (*auditV1.AuditLogEntry, error) {
// Get request headers
@ -247,9 +310,9 @@ func NewAuditLogEntry(
if err != nil {
return nil, errors.Join(err, ErrInvalidResponse)
}
var responseLength *int64
var responseLength *int64 = nil
if responseBody != nil {
length := int64(len(auditResponse.ResponseBodyBytes))
length := int64(len(*auditResponse.ResponseBodyBytes))
responseLength = &length
}
@ -270,7 +333,7 @@ func NewAuditLogEntry(
scheme := auditRequest.Request.Scheme
// Initialize authorization info if available
var authorizationInfo []*auditV1.AuthorizationInfo
var authorizationInfo []*auditV1.AuthorizationInfo = nil
if auditMetadata.AuditPermission != nil && auditMetadata.AuditPermissionGranted != nil {
authorizationInfo = []*auditV1.AuthorizationInfo{
NewAuthorizationInfo(
@ -280,15 +343,15 @@ func NewAuditLogEntry(
}
// Initialize labels if available
var labels map[string]string
var labels map[string]string = nil
if auditMetadata.AuditLabels != nil {
labels = *auditMetadata.AuditLabels
}
// Initialize metadata/details
var metadata *structpb.Struct
var metadata *structpb.Struct = nil
if eventMetadata != nil {
metadataStruct, err := structpb.NewStruct(eventMetadata)
metadataStruct, err := structpb.NewStruct(*eventMetadata)
if err != nil {
return nil, err
}
@ -343,10 +406,49 @@ func NewAuditLogEntry(
CorrelationId: auditRequest.RequestCorrelationId,
Timestamp: timestamppb.New(concreteAuditTime),
Severity: auditMetadata.AuditLogSeverity,
TraceParent: userProvidedTraceParent,
TraceState: userProvidedTraceState,
}
return &event, nil
}
// GetCalledServiceNameFromRequest extracts the called service name from subdomain name
func GetCalledServiceNameFromRequest(request *ApiRequest, fallbackName string) string {
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
}
// AuditSpan is an abstraction for trace.Span that can easier be tested
type AuditSpan interface {
SpanContext() trace.SpanContext
}
// TraceParentFromSpan returns W3C conform trace parent from AuditSpan
func TraceParentFromSpan(span AuditSpan) string {
traceVersion := "00"
traceId := span.SpanContext().TraceID().String()
parentId := span.SpanContext().SpanID().String()
// Trace flags according to W3C documentation:
// https://www.w3.org/TR/trace-context/#sampled-flag
var traceFlags = "00"
if span.SpanContext().TraceFlags().IsSampled() {
traceFlags = "01"
}
// Format: <version>-<trace-id>-<parent-id>-<trace-flags>
// Example: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
w3cTraceParent := fmt.Sprintf("%s-%s-%s-%s", traceVersion, traceId, parentId, traceFlags)
return w3cTraceParent
}
// NewPbInt64Value returns protobuf int64 wrapper if value is not nil.
func NewPbInt64Value(value *int64) *wrapperspb.Int64Value {
if value != nil {
@ -357,7 +459,7 @@ func NewPbInt64Value(value *int64) *wrapperspb.Int64Value {
// NewRequestMetadata returns initialized protobuf RequestMetadata object.
func NewRequestMetadata(
request *pkgAuditCommon.ApiRequest,
request *ApiRequest,
requestHeaders map[string]string,
requestId *string,
requestScheme string,
@ -389,7 +491,7 @@ func NewRequestMetadata(
// NewRequestAttributes returns initialized protobuf AttributeContext_Request object.
func NewRequestAttributes(
request *pkgAuditCommon.ApiRequest,
request *ApiRequest,
requestHeaders map[string]string,
requestId *string,
requestScheme string,
@ -400,7 +502,7 @@ func NewRequestAttributes(
) *auditV1.AttributeContext_Request {
rawQuery := request.URL.RawQuery
var query *string
var query *string = nil
if rawQuery != nil && *rawQuery != "" {
escapedQuery := url.QueryEscape(*rawQuery)
query = &escapedQuery
@ -408,7 +510,7 @@ func NewRequestAttributes(
return &auditV1.AttributeContext_Request{
Id: requestId,
Method: pkgAuditCommon.StringToHttpMethod(request.Method),
Method: StringToHttpMethod(request.Method),
Headers: requestHeaders,
Path: request.URL.Path,
Host: request.Host,
@ -425,7 +527,7 @@ func NewRequestAttributes(
}
// NewAuthorizationInfo returns protobuf AuthorizationInfo for the given parameters.
func NewAuthorizationInfo(resourceName, permission string, granted bool) *auditV1.AuthorizationInfo {
func NewAuthorizationInfo(resourceName string, permission string, granted bool) *auditV1.AuthorizationInfo {
return &auditV1.AuthorizationInfo{
Resource: resourceName,
Permission: &permission,
@ -433,15 +535,15 @@ func NewAuthorizationInfo(resourceName, permission string, granted bool) *auditV
}
}
// NewInsertId returns a correctly formatted insert id.
func NewInsertId(insertTime time.Time, location, workerId string, eventSequenceNumber uint64) string {
// NewInsertId returns a correctly formatted insert it.
func NewInsertId(insertTime time.Time, location string, workerId string, eventSequenceNumber uint64) string {
return fmt.Sprintf("%d/%s/%s/%d", insertTime.UnixNano(), location, workerId, eventSequenceNumber)
}
// NewResponseMetadata returns protobuf response status with status code and short message.
func NewResponseMetadata(statusCode int, numResponseItems, responseSize *int64, headers map[string]string, responseTime time.Time) *auditV1.ResponseMetadata {
func NewResponseMetadata(statusCode int, numResponseItems *int64, responseSize *int64, headers map[string]string, responseTime time.Time) *auditV1.ResponseMetadata {
var message *string
var message *string = nil
if statusCode >= 400 && statusCode < 500 {
text := "Client error"
message = &text
@ -450,7 +552,7 @@ func NewResponseMetadata(statusCode int, numResponseItems, responseSize *int64,
message = &text
}
var size *wrapperspb.Int64Value
var size *wrapperspb.Int64Value = nil
if responseSize != nil {
size = wrapperspb.Int64(*responseSize)
}
@ -468,26 +570,26 @@ func NewResponseMetadata(statusCode int, numResponseItems, responseSize *int64,
}
// 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
if len(response) == 0 {
if response == nil || len(*response) == 0 {
return nil, nil
}
// Convert to protobuf struct
return byteArrayToPbStruct(response)
return byteArrayToPbStruct(*response)
}
// NewRequestBody converts the request body into a protobuf struct.
func NewRequestBody(request *pkgAuditCommon.ApiRequest) (*structpb.Struct, error) {
func NewRequestBody(request *ApiRequest) (*structpb.Struct, error) {
if len(request.Body) == 0 {
if request.Body == nil || len(*request.Body) == 0 {
return nil, nil
}
// Convert to protobuf struct
return byteArrayToPbStruct(request.Body)
return byteArrayToPbStruct(*request.Body)
}
// byteArrayToPbStruct converts a given json byte array into a protobuf struct.
@ -501,13 +603,13 @@ func byteArrayToPbStruct(bytes []byte) (*structpb.Struct, error) {
return structpb.NewStruct(bodyMap)
}
// FilterAndMergeHeaders filters ":authority", "Authorization", "B3" and "Host" headers as well as
// all headers starting with the prefixes "X-", "STACKIT-" and "grpcgateway-".
// FilterAndMergeHeaders filters ":authority", "Authorization" and "B3" headers as well as
// all headers starting with the prefixes "X-" and "STACKIT-".
// Headers are merged if there is more than one value for a given name.
func FilterAndMergeHeaders(headers map[string][]string) map[string]string {
var resultMap = make(map[string]string)
skipHeaders := []string{":authority", "authorization", "b3", "host"}
skipPrefixHeaders := []string{"x-", "stackit-", "grpcgateway-"}
skipHeaders := []string{":authority", "authorization", "b3"}
skipPrefixHeaders := []string{"x-", "stackit-"}
if len(headers) == 0 {
return nil
@ -521,7 +623,6 @@ func FilterAndMergeHeaders(headers map[string][]string) map[string]string {
for _, skipPrefix := range skipPrefixHeaders {
if strings.HasPrefix(headerLower, skipPrefix) {
skip = true
break
}
}
@ -536,8 +637,8 @@ func FilterAndMergeHeaders(headers map[string][]string) map[string]string {
// NewAuditRoutingIdentifier instantiates a new auditApi.RoutableIdentifier for
// the given object ID and object type.
func NewAuditRoutingIdentifier(objectId string, objectType pkgAuditCommon.ObjectType) *pkgAuditCommon.RoutableIdentifier {
return &pkgAuditCommon.RoutableIdentifier{
func NewAuditRoutingIdentifier(objectId string, objectType ObjectType) *RoutableIdentifier {
return &RoutableIdentifier{
Identifier: objectId,
Type: objectType,
}
@ -548,7 +649,7 @@ func NewAuditRoutingIdentifier(objectId string, objectType pkgAuditCommon.Object
// - authenticationPrincipal - principal identifier
// - audiences - list of audience claims
// - authenticationInfo - information about the user or service-account authentication
func AuditAttributesFromAuthorizationHeader(request *pkgAuditCommon.ApiRequest) (
func AuditAttributesFromAuthorizationHeader(request *ApiRequest) (
*structpb.Struct,
string,
[]string,
@ -556,17 +657,14 @@ func AuditAttributesFromAuthorizationHeader(request *pkgAuditCommon.ApiRequest)
error,
) {
var authenticationPrincipal = "none/none"
var principalId = "none"
var principalEmail *string
emptyClaims, err := structpb.NewStruct(make(map[string]interface{}))
if err != nil {
return nil, authenticationPrincipal, nil, nil, err
}
var principalEmail = "do-not-reply@stackit.cloud"
emptyClaims, _ := structpb.NewStruct(make(map[string]interface{}))
var auditClaims = emptyClaims
var serviceAccountName *string
var authenticationPrincipal = "none/none"
var serviceAccountName *string = nil
audiences := make([]string, 0)
var delegationInfo []*auditV1.ServiceAccountDelegationInfo
var delegationInfo []*auditV1.ServiceAccountDelegationInfo = nil
authorizationHeaders := request.Header["Authorization"]
if len(authorizationHeaders) == 0 {
@ -575,15 +673,10 @@ func AuditAttributesFromAuthorizationHeader(request *pkgAuditCommon.ApiRequest)
}
authorizationHeader := strings.Join(authorizationHeaders, ",")
trimmedAuthorizationHeader := strings.TrimSpace(authorizationHeader)
if trimmedAuthorizationHeader != "" {
if len(trimmedAuthorizationHeader) > 0 {
// Parse claims
token, err := parseToken(trimmedAuthorizationHeader)
if err != nil {
return nil, authenticationPrincipal, nil, nil, err
}
filteredClaims, err := parseClaimsFromAuthorizationHeader(token)
parsedClaims, filteredClaims, err := parseClaimsFromAuthorizationHeader(trimmedAuthorizationHeader)
if err != nil {
return nil, authenticationPrincipal, nil, nil, err
}
@ -596,24 +689,23 @@ func AuditAttributesFromAuthorizationHeader(request *pkgAuditCommon.ApiRequest)
auditClaims = auditClaimsStruct
// Extract principal data
authenticationPrincipal = extractAuthenticationPrincipal(token)
principalId, principalEmail = extractSubjectAndEmail(token)
authenticationPrincipal = extractAuthenticationPrincipal(parsedClaims)
principalId, principalEmail = extractSubjectAndEmail(parsedClaims)
// Extract service account delegation info data
actClaim, hasActClaim := token.Get("act")
if hasActClaim {
actMap := map[string]interface{}{"act": actClaim}
delegationInfo = extractServiceAccountDelegationInfo(actMap)
}
delegationInfo = extractServiceAccountDelegationInfo(parsedClaims)
// Extract audiences data
audiences = token.Audience()
audiences, err = extractAudiences(parsedClaims)
if err != nil {
return nil, authenticationPrincipal, nil, nil, err
}
// Extract project id and service account id
projectId := getTokenClaim(token, TokenClaimStackitProjectId)
serviceAccountId := getTokenClaim(token, TokenClaimStackitServiceAccountId)
projectId := extractServiceAccountProjectId(parsedClaims)
serviceAccountId := extractServiceAccountId(parsedClaims)
// Calculate service account name if project and service account ids are available
// Calculate service account name if project id and service account id are available
if projectId != nil && serviceAccountId != nil {
accountName := fmt.Sprintf("projects/%s/service-accounts/%s", *projectId, *serviceAccountId)
serviceAccountName = &accountName
@ -630,79 +722,91 @@ func AuditAttributesFromAuthorizationHeader(request *pkgAuditCommon.ApiRequest)
return auditClaims, authenticationPrincipal, audiences, &authenticationInfo, nil
}
func getTokenClaim(token jwt.Token, claimName string) *string {
claim, claimExists := token.Get(claimName)
if claimExists {
claimString := fmt.Sprintf("%s", claim)
return &claimString
func extractServiceAccountId(parsedClaims map[string]interface{}) *string {
projectId, projectIdExists := parsedClaims["stackit/serviceaccount/service-account.uid"]
if projectIdExists {
projectIdString := fmt.Sprintf("%s", projectId)
return &projectIdString
} else {
return nil
}
return nil
}
func extractAuthenticationPrincipal(token jwt.Token) string {
subject := token.Subject()
issuer := token.Issuer()
func extractServiceAccountProjectId(parsedClaims map[string]interface{}) *string {
projectId, projectIdExists := parsedClaims["stackit/project/project.id"]
if projectIdExists {
projectIdString := fmt.Sprintf("%s", projectId)
return &projectIdString
} else {
return nil
}
}
func extractAudiences(parsedClaims map[string]interface{}) ([]string, error) {
audClaim, _ := json.Marshal(parsedClaims["aud"])
var audiences []string
if err := json.Unmarshal(audClaim, &audiences); err != nil {
return nil, err
}
return audiences, nil
}
func extractAuthenticationPrincipal(parsedClaims map[string]interface{}) string {
subClaim, subExists := parsedClaims["sub"]
issuerClaim, issuerExists := parsedClaims["iss"]
var principal = "none/none"
if subject != "" && issuer != "" {
principal = fmt.Sprintf("%s/%s", url.QueryEscape(subject), url.QueryEscape(issuer))
if subExists && issuerExists {
principal = fmt.Sprintf("%s/%s", url.QueryEscape(subClaim.(string)), url.QueryEscape(issuerClaim.(string)))
}
return principal
}
func parseToken(authorizationHeader string) (jwt.Token, error) {
func parseClaimsFromAuthorizationHeader(authorizationHeader string) (map[string]interface{}, map[string]interface{}, error) {
parts := strings.Split(authorizationHeader, " ")
if len(parts) != 2 {
return nil, ErrInvalidAuthorizationHeaderValue
return nil, nil, ErrInvalidAuthorizationHeaderValue
}
if !strings.EqualFold(parts[0], "Bearer") {
return nil, ErrTokenIsNotBearerToken
return nil, nil, ErrTokenIsNotBearerToken
}
jwtString := parts[1]
authorizationHeaderParts := strings.Split(jwtString, ".")
jwt := parts[1]
authorizationHeaderParts := strings.Split(jwt, ".")
parsedClaims := make(map[string]interface{})
if len(authorizationHeaderParts) == 3 {
token, err := jwt.ParseInsecure([]byte(parts[1]))
// base64 decoding
decodedString, err := base64.RawURLEncoding.DecodeString(authorizationHeaderParts[1])
if err != nil {
return nil, ErrInvalidBearerToken
return parsedClaims, nil, errors.Join(err, ErrInvalidBearerToken)
}
return token, nil
// unmarshall claim part of token
err = json.Unmarshal(decodedString, &parsedClaims)
if err != nil {
return parsedClaims, nil, err
}
// Collect user-friendly filtered subset of claims
filteredClaims := make(map[string]interface{})
_ = json.Unmarshal(decodedString, &filteredClaims)
keysToDelete := make([]string, 0)
for key := range filteredClaims {
if key != "aud" && key != "email" && key != "iss" && key != "jti" && key != "sub" {
keysToDelete = append(keysToDelete, key)
}
}
for _, key := range keysToDelete {
delete(filteredClaims, key)
}
return parsedClaims, filteredClaims, nil
}
return nil, ErrInvalidBearerToken
return parsedClaims, nil, ErrInvalidBearerToken
}
func parseClaimsFromAuthorizationHeader(token jwt.Token) (map[string]interface{}, error) {
claimsMap, err := token.AsMap(context.Background())
if err != nil {
return nil, err
}
claims := map[string]interface{}{}
if len(token.Audience()) > 0 {
var audiences []any
for _, audience := range token.Audience() {
audiences = append(audiences, audience)
}
claims["aud"] = audiences
}
for key := range claimsMap {
if key == "aud" {
continue
}
value := claimsMap[key]
t, isTime := value.(time.Time)
if isTime {
claims[key] = t.String()
} else if value != nil && value != "" {
claims[key] = value
}
}
return claims, nil
}
func extractServiceAccountDelegationInfoDetails(actClaims map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo {
principalId, principalEmail := extractSubjectAndEmailFromActClaims(actClaims)
func extractServiceAccountDelegationInfoDetails(token map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo {
principalId, principalEmail := extractSubjectAndEmail(token)
delegation := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_IdpPrincipal_{IdpPrincipal: &auditV1.ServiceAccountDelegationInfo_IdpPrincipal{
PrincipalId: principalId,
@ -711,16 +815,17 @@ func extractServiceAccountDelegationInfoDetails(actClaims map[string]interface{}
}}}
delegations := []*auditV1.ServiceAccountDelegationInfo{&delegation}
nestedDelegations := extractServiceAccountDelegationInfo(actClaims)
nestedDelegations := extractServiceAccountDelegationInfo(token)
if len(nestedDelegations) > 0 {
return append(delegations, nestedDelegations...)
} else {
return delegations
}
return delegations
}
func extractServiceAccountDelegationInfo(claims map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo {
actor, hasActor := claims["act"]
if hasActor {
func extractServiceAccountDelegationInfo(token map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo {
actor := token["act"]
if actor != nil {
actorMap, hasActorClaim := actor.(map[string]interface{})
if hasActorClaim {
return extractServiceAccountDelegationInfoDetails(actorMap)
@ -729,27 +834,144 @@ func extractServiceAccountDelegationInfo(claims map[string]interface{}) []*audit
return nil
}
func extractSubjectAndEmailFromActClaims(actClaim map[string]interface{}) (string, string) {
func extractSubjectAndEmail(token map[string]interface{}) (string, string) {
var principalEmail string
principalId := fmt.Sprintf("%s", actClaim["sub"])
principalEmailRaw := actClaim["email"]
principalId := fmt.Sprintf("%s", token["sub"])
principalEmailRaw := token["email"]
if principalEmailRaw == nil {
principalEmail = EmailAddressDoNotReplyAtStackItDotCloud
principalEmail = "do-not-reply@stackit.cloud"
} else {
principalEmail = fmt.Sprintf("%s", principalEmailRaw)
}
return principalId, principalEmail
}
func extractSubjectAndEmail(token jwt.Token) (string, *string) {
var principalEmail *string
principalId := token.Subject()
emailClaim, hasEmail := token.Get("email")
if hasEmail {
trimmedEmail := strings.TrimSpace(fmt.Sprintf("%s", emailClaim))
if trimmedEmail != "" {
principalEmail = &trimmedEmail
}
// 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
}
return principalId, principalEmail
}

View file

@ -1,23 +1,110 @@
package api
import (
"context"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"encoding/json"
"fmt"
"net/http"
"net/url"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"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"
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"
"net/http"
"net/url"
"strings"
"testing"
"time"
)
type mockSpan struct {
spanContext trace.SpanContext
}
func (s *mockSpan) SpanContext() trace.SpanContext {
return s.spanContext
}
func Test_TraceParentFromSpan(t *testing.T) {
tracer := otel.Tracer("test")
verifyTraceParent := func(traceParent string, span trace.Span, isSampled bool) {
parts := strings.Split(traceParent, "-")
assert.Equal(t, 4, len(parts))
// trace version
assert.Equal(t, "00", parts[0])
assert.Equal(t, span.SpanContext().TraceID().String(), parts[1])
assert.Equal(t, span.SpanContext().SpanID().String(), parts[2])
var traceFlags = "00"
if isSampled {
traceFlags = "01"
}
assert.Equal(t, traceFlags, parts[3])
}
t.Run("sampled", func(t *testing.T) {
_, span := tracer.Start(context.Background(), "test")
updatedFlags := span.SpanContext().TraceFlags().WithSampled(true)
updatedContext := span.SpanContext().WithTraceFlags(updatedFlags)
mockedSpan := mockSpan{
spanContext: updatedContext,
}
traceParent := TraceParentFromSpan(&mockedSpan)
verifyTraceParent(traceParent, span, true)
})
t.Run("non-sampled", func(t *testing.T) {
_, span := tracer.Start(context.Background(), "test")
traceParent := TraceParentFromSpan(span)
verifyTraceParent(traceParent, span, false)
})
}
func Test_GetCalledServiceNameFromRequest(t *testing.T) {
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) {
t.Run("nil", func(t *testing.T) {
@ -85,9 +172,9 @@ func Test_NewRequestMetadata(t *testing.T) {
requestHeaders["Custom"] = []string{"customHeader"}
queryString := "topic=project"
request := pkgAuditCommon.ApiRequest{
request := ApiRequest{
Method: "GET",
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Scheme: "http",
@ -149,9 +236,9 @@ func Test_NewRequestMetadata(t *testing.T) {
})
t.Run("without query parameters", func(t *testing.T) {
request := pkgAuditCommon.ApiRequest{
request := ApiRequest{
Method: "GET",
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new"},
URL: RequestUrl{Path: "/audit/new"},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -175,9 +262,9 @@ func Test_NewRequestMetadata(t *testing.T) {
t.Run("with empty query parameters", func(t *testing.T) {
emptyQuery := ""
request := pkgAuditCommon.ApiRequest{
request := ApiRequest{
Method: "GET",
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery},
URL: RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -200,9 +287,9 @@ func Test_NewRequestMetadata(t *testing.T) {
})
t.Run("without request id", func(t *testing.T) {
request := pkgAuditCommon.ApiRequest{
request := ApiRequest{
Method: "GET",
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -224,9 +311,9 @@ func Test_NewRequestMetadata(t *testing.T) {
t.Run("various default http methods", func(t *testing.T) {
httpMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"}
for _, httpMethod := range httpMethods {
request := pkgAuditCommon.ApiRequest{
request := ApiRequest{
Method: httpMethod,
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -248,9 +335,9 @@ func Test_NewRequestMetadata(t *testing.T) {
})
t.Run("unknown http method", func(t *testing.T) {
request := pkgAuditCommon.ApiRequest{
request := ApiRequest{
Method: "",
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -278,11 +365,7 @@ func Test_FilterAndMergeRequestHeaders(t *testing.T) {
t.Run("skip headers", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{"ey..."}
headers["authorization"] = []string{"ey..."}
headers["B3"] = []string{"80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90"}
headers["b3"] = []string{"80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90"}
headers["Host"] = []string{"localhost:9090"}
headers["host"] = []string{"localhost:9090"}
headers[":authority"] = []string{"localhost:9090"}
filteredHeaders := FilterAndMergeHeaders(headers)
@ -293,7 +376,6 @@ func Test_FilterAndMergeRequestHeaders(t *testing.T) {
headers := make(map[string][]string)
headers["X-Forwarded-Proto"] = []string{"https"}
headers["Stackit-test"] = []string{"test"}
headers["grpcgateway-authorization"] = []string{userToken}
filteredHeaders := FilterAndMergeHeaders(headers)
assert.Equal(t, 0, len(filteredHeaders))
@ -309,28 +391,6 @@ func Test_FilterAndMergeRequestHeaders(t *testing.T) {
assert.Equal(t, "value1,value2", filteredHeaders["Custom1"])
assert.Equal(t, "value3,value4", filteredHeaders["Custom2"])
})
t.Run("skip merge headers mixed", func(t *testing.T) {
headers := make(map[string][]string)
headers["Custom1"] = []string{"value1", "value2"}
headers["Custom2"] = []string{"value3"}
headers["STACKIT-MIXED"] = []string{"test"}
filteredHeaders := FilterAndMergeHeaders(headers)
assert.Equal(t, 2, len(filteredHeaders))
assert.Equal(t, "value1,value2", filteredHeaders["Custom1"])
assert.Equal(t, "value3", filteredHeaders["Custom2"])
})
t.Run("Keep empty and blank header values", func(t *testing.T) {
headers := make(map[string][]string)
headers["empty"] = []string{""}
headers["blank"] = []string{" "}
filteredHeaders := FilterAndMergeHeaders(headers)
assert.Equal(t, 2, len(filteredHeaders))
assert.Equal(t, "", filteredHeaders["empty"])
assert.Equal(t, " ", filteredHeaders["blank"])
})
}
func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
@ -339,7 +399,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Basic username:password"
headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue}
request := pkgAuditCommon.ApiRequest{Header: headers}
request := ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrTokenIsNotBearerToken)
@ -349,7 +409,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "a b c"
headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue}
request := pkgAuditCommon.ApiRequest{Header: headers}
request := ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidAuthorizationHeaderValue)
@ -359,7 +419,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Bearer a.b.c.d"
headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue}
request := pkgAuditCommon.ApiRequest{Header: headers}
request := ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidBearerToken)
@ -369,32 +429,27 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Bearer a.b.c"
headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue}
request := pkgAuditCommon.ApiRequest{Header: headers}
request := ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidBearerToken)
})
t.Run("client credentials token", func(t *testing.T) {
headerValue := "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1yZXNvdXJjZS1tYW5hZ2VyLWRldiJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2IiwiZXhwIjoxNzI0NDA1MzI2LCJpYXQiOjE3MjQ0MDQ0MjYsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZGV2LnN0YWNraXQuY2xvdWQiLCJqdGkiOiJlNDZlYmEzOC1kZWRiLTQ1NDEtOTRmMy00OWY5N2E5MzRkNTgiLCJuYmYiOjE3MjQ0MDQ0MjYsInNjb3BlIjoidWFhLm5vbmUiLCJzdWIiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2In0.JP5Uy7AMdK4ukzQ6aOYzbVwEmq0Tp2ppQGRqGOhuVQgbqs6yJ33GKXo7RPsJVLw3FR7XAxENIVqNvzGotbDXr0NjBGdzyxIHzrOaUqM4w1iLzD1KF51dXFwkoigqDdD7Ze9eI_Uo3tSn8FwGLTSoO-ONQYpnceCiGut2Gc6VIL8HOLdh8dzlRENGQtgYd-3Y5zqpoLrsR2Bd-0sv15sF-5aI0CqcC8gE70JPImKf2u_IYI-TYMDNk86YSCtaYO5-alOrHXXWwgzSoH-r2s5qoOhPbei9myV_P4fdcKXxMqfap9hImXPUooVhpdUr1AabZw3MtW7rION8tJAiauhMQA"
headers := make(map[string][]string)
headers["Authorization"] = []string{clientCredentialsToken}
request := pkgAuditCommon.ApiRequest{Header: headers}
headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Len(t, auditClaimsMap, 9)
assert.Equal(t, []interface{}{"stackit-resource-manager-dev"}, auditClaimsMap["aud"])
assert.Equal(t, "stackit-resource-manager-dev", auditClaimsMap["client_id"])
assert.Equal(t, "2024-08-23 09:28:46 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-23 09:13:46 +0000 UTC", auditClaimsMap["iat"])
assert.Nil(t, err)
assert.Equal(t, "https://accounts.dev.stackit.cloud", auditClaimsMap["iss"])
assert.Equal(t, "e46eba38-dedb-4541-94f3-49f97a934d58", auditClaimsMap["jti"])
assert.Equal(t, "2024-08-23 09:13:46 +0000 UTC", auditClaimsMap["nbf"])
assert.Equal(t, "uaa.none", auditClaimsMap["scope"])
assert.Equal(t, "stackit-resource-manager-dev", auditClaimsMap["sub"])
assert.Equal(t, []interface{}{"stackit-resource-manager-dev"}, auditClaimsMap["aud"].([]interface{}))
assert.Equal(t, "e46eba38-dedb-4541-94f3-49f97a934d58", auditClaimsMap["jti"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("stackit-resource-manager-dev"),
@ -404,35 +459,27 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"stackit-resource-manager-dev"}, audiences)
assert.Equal(t, "stackit-resource-manager-dev", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.PrincipalEmail)
assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail)
assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
})
t.Run("service account access token", func(t *testing.T) {
headerValue := "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxMGYzOGIwMS01MzRiLTQ3YmItYTAzYS1lMjk0Y2EyYmU0ZGUiLCJhdWQiOlsic3RhY2tpdCIsImFwaSJdLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3Rva2VuLnNvdXJjZSI6ImxlZ2FjeSIsInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiJjZDk0ZjAxYS1kZjJlLTQ0NTYtOTAyZS00OGY1ZTU3ZjBiNjMiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50Iiwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMTBmMzhiMDEtNTM0Yi00N2JiLWEwM2EtZTI5NGNhMmJlNGRlIiwiZXhwIjoxNzIyNjY5MzQzLCJpYXQiOjE3MjI1ODI5NDMsImVtYWlsIjoibXktc2VydmljZS15aWZjOWUxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiI4NGMzMGE0Ni0xMDAxLTQzNmYtODU5Zi04OWMwYmExOWJlMWUifQ.hb8X9VKc9xViHgNMyFHT9ePj_lyEwTV1D2es8E278WtoCJ9-4GPPQGjhcLGGrigjnvpRYV2LKzNqpQslerT5lFT_pHACsryaAE0ImYjmoe-nutA7BBpYuM_JN6pk5VIjVFLTqRKeIvFexPacqS2Vo3YoK1GvxPB8WPWBbGIsBtMl-PTm8OTwwzooBOoCRhhMR-E1lFbAymLsc1JI4yDQKLLomvhEopgmocCnQ-P1QkiKMqdkNxiD_YYLLYTOApg6d62BhqpH66ziqx493AStdZ8d5Kjvf3e1knDhaxVwNCghQj7lSo2kNAqZe__g2tiXpiZNTXBFJ_5HgQMLh67wng"
headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountToken}
request := pkgAuditCommon.ApiRequest{Header: headers}
headers["Authorization"] = []string{headerValue}
request := 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.Nil(t, err)
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"])
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"].([]interface{}))
assert.Equal(t, "84c30a46-1001-436f-859f-89c0ba19be1e", auditClaimsMap["jti"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("10f38b01-534b-47bb-a03a-e294ca2be4de"),
@ -442,47 +489,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
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,
"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, "my-service-yifc9e1@sa.stackit.cloud", authenticationInfo.PrincipalEmail)
assert.Equal(t,
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/10f38b01-534b-47bb-a03a-e294ca2be4de",
@ -491,32 +498,20 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
})
t.Run("impersonated token of access token", func(t *testing.T) {
headerValue := "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiMDJhZWY1MTYtMzE3Zi00ZWMxLWExZGYtMWFjYmQ0ZDQ5ZmUzIn0sInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiIwMmFlZjUxNi0zMTdmLTRlYzEtYTFkZi0xYWNiZDRkNDlmZTMiLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJleHAiOjE3MjQwNjI5MDcsImlhdCI6MTcyNDA1OTMwNywiZW1haWwiOiJzZXJ2aWNlLWFjY291bnQtMi10ajlzcnQxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiIzNzU1NTE4My0wMWI5LTQyNzAtYmRjMS02OWI0ZmNmZDVlZTkifQ.auBvvsIesFMAlWOCPCPC77DrrHF7gSKZwKs_Zry5KFvu2bpZZC1BcSXOc8b9eh0SzANI9M9aGJBhOzOm39-ZZ5XOQ-6_y1aWuEenYQ6kT5D3GzCUTMDzSi1lcZ4IG5nFMa_AAlVEN_7AMv7LHGtz49bWLJnAgeTo1cvof-OgP4mCQ5O6E0iyAq-5u8V8NJL7HIZy7BDe4J1mjfYhwKagrN7QFWu4fhN4TNS7d922X_6V489BhjRFRYjLW_qDnv912JorbGRz_XwNy_dPA81EkdMyKE0BJUezguJUEKEG2_JEi9O64Flcoi6x8cFHYhaDuMMSLipzePaHdyk2lQtH7Q"
headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountTokenImpersonated}
request := pkgAuditCommon.ApiRequest{Header: headers}
headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Len(t, auditClaimsMap, 13)
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"])
assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", auditClaimsMap["azp"])
assert.Equal(t, "service-account-2-tj9srt1@sa.stackit.cloud", auditClaimsMap["email"])
assert.Equal(t, "2024-08-19 10:21:47 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-19 09:21:47 +0000 UTC", auditClaimsMap["iat"])
assert.Nil(t, err)
assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"])
assert.Equal(t, "37555183-01b9-4270-bdc1-69b4fcfd5ee9", auditClaimsMap["jti"])
assert.Equal(t, "api", auditClaimsMap["stackit/serviceaccount/namespace"])
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", auditClaimsMap[TokenClaimStackitServiceAccountId])
assert.Equal(t, "oauth2", auditClaimsMap["stackit/serviceaccount/token.source"])
assert.Equal(t, "dacc7830-843e-4c5e-86ff-aa0fb51d636f", auditClaimsMap[TokenClaimStackitProjectId])
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", auditClaimsMap["sub"])
assert.NotNil(t, auditClaimsMap["act"])
act := auditClaimsMap["act"].(map[string]interface{})
assert.NotNil(t, act)
assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", act["sub"])
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"].([]interface{}))
assert.Equal(t, "37555183-01b9-4270-bdc1-69b4fcfd5ee9", auditClaimsMap["jti"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("f45009b2-6433-43c1-b6c7-618c44359e71"),
@ -526,7 +521,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"stackit", "api"}, audiences)
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,
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/f45009b2-6433-43c1-b6c7-618c44359e71",
@ -539,35 +534,20 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
})
t.Run("impersonated token of impersonated access token", func(t *testing.T) {
headerValue := "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxNzM0YjRiNi0xZDVlLTQ4MTktOWI1MC0yOTkxN2ExYjlhZDUiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiZjQ1MDA5YjItNjQzMy00M2MxLWI2YzctNjE4YzQ0MzU5ZTcxIiwiYWN0Ijp7InN1YiI6IjAyYWVmNTE2LTMxN2YtNGVjMS1hMWRmLTFhY2JkNGQ0OWZlMyJ9fSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhcGkiLCJzdGFja2l0L3Byb2plY3QvcHJvamVjdC5pZCI6ImRhY2M3ODMwLTg0M2UtNGM1ZS04NmZmLWFhMGZiNTFkNjM2ZiIsImF6cCI6ImY0NTAwOWIyLTY0MzMtNDNjMS1iNmM3LTYxOGM0NDM1OWU3MSIsInN0YWNraXQvc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjE3MzRiNGI2LTFkNWUtNDgxOS05YjUwLTI5OTE3YTFiOWFkNSIsImV4cCI6MTcyNDA2Mjk2MywiaWF0IjoxNzI0MDU5MzYzLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC0zLWZnaHN4dzFAc2Euc3RhY2tpdC5jbG91ZCIsImp0aSI6IjFmN2YxZWZjLTMzNDktNDExYS1hNWQ3LTIyNTVlMGE1YThhZSJ9.c1ae17bAtyOdmwXQbK37W-NTyOxo7iER5aHS_C0fU1qKl2BjOz708GLjH-_vxx9eKPeYznfI21_xlTaAvuG4Aco9f5YDK7fooTVHnDaOSSggqcEaDzDPrNXhhKEDxotJeq9zRMVCEStcbirjTounnLbuULRbO5GSY5jo-8n2UKxSZ2j5G_SjFHajdJwmzwvOttp08tdL8ck1uDdgVNBfcm0VIdb6WmgrCIUq5rmoa-cRPkdEurNtIEgEB_9U0Xh-SpmmsvFsWWeNIKz0e_5RCIyJonm_wMkGmblGegemkYL76ypeMNXTQsly1RozDIePfzHuZOWbySHSCd-vKQa2kw"
headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountTokenRepeatedlyImpersonated}
request := pkgAuditCommon.ApiRequest{Header: headers}
headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Len(t, auditClaimsMap, 13)
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"])
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", auditClaimsMap["azp"])
assert.Equal(t, "service-account-3-fghsxw1@sa.stackit.cloud", auditClaimsMap["email"])
assert.Equal(t, "2024-08-19 10:22:43 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-19 09:22:43 +0000 UTC", auditClaimsMap["iat"])
assert.Nil(t, err)
assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"])
assert.Equal(t, "1f7f1efc-3349-411a-a5d7-2255e0a5a8ae", auditClaimsMap["jti"])
assert.Equal(t, "api", auditClaimsMap["stackit/serviceaccount/namespace"])
assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", auditClaimsMap[TokenClaimStackitServiceAccountId])
assert.Equal(t, "oauth2", auditClaimsMap["stackit/serviceaccount/token.source"])
assert.Equal(t, "dacc7830-843e-4c5e-86ff-aa0fb51d636f", auditClaimsMap[TokenClaimStackitProjectId])
assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", auditClaimsMap["sub"])
assert.NotNil(t, auditClaimsMap["act"])
act := auditClaimsMap["act"].(map[string]interface{})
assert.NotNil(t, act)
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", act["sub"])
nestedAct := act["act"].(map[string]interface{})
assert.NotNil(t, nestedAct)
assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", nestedAct["sub"])
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"].([]interface{}))
assert.Equal(t, "1f7f1efc-3349-411a-a5d7-2255e0a5a8ae", auditClaimsMap["jti"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("1734b4b6-1d5e-4819-9b50-29917a1b9ad5"),
@ -577,7 +557,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"stackit", "api"}, audiences)
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,
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/1734b4b6-1d5e-4819-9b50-29917a1b9ad5",
@ -592,27 +572,20 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
})
t.Run("user token", func(t *testing.T) {
headerValue := "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"
headers := make(map[string][]string)
headers["Authorization"] = []string{userToken}
request := pkgAuditCommon.ApiRequest{Header: headers}
headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Len(t, auditClaimsMap, 11)
assert.Equal(t, []interface{}{"stackit-portal-login-dev-client-id"}, auditClaimsMap["aud"])
assert.Equal(t, "stackit-portal-login-dev-client-id", auditClaimsMap["client_id"])
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", auditClaimsMap["email"])
assert.True(t, auditClaimsMap["email_verified"].(bool))
assert.Equal(t, "2024-08-02 09:19:27 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-02 08:19:27 +0000 UTC", auditClaimsMap["iat"])
assert.Nil(t, err)
assert.Equal(t, "https://accounts.dev.stackit.cloud", auditClaimsMap["iss"])
assert.Equal(t, "d73a67ac-d1ec-4b55-99d4-e953275f022a", auditClaimsMap["jti"])
assert.Equal(t, "2024-08-02 08:19:27 +0000 UTC", auditClaimsMap["nbf"])
assert.Equal(t, "openid email", auditClaimsMap["scope"])
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", auditClaimsMap["sub"])
assert.Equal(t, []interface{}{"stackit-portal-login-dev-client-id"}, auditClaimsMap["aud"].([]interface{}))
assert.Equal(t, "d73a67ac-d1ec-4b55-99d4-e953275f022a", auditClaimsMap["jti"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("cd94f01a-df2e-4456-902e-48f5e57f0b63"),
@ -622,42 +595,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Equal(t, []string{"stackit-portal-login-dev-client-id"}, audiences)
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", *authenticationInfo.PrincipalEmail)
assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
})
t.Run("user token with simple aud claim", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{userTokenWithSimpleAudience}
request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.NoError(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Len(t, auditClaimsMap, 9)
assert.Equal(t, []interface{}{"https://stackit-service-account-dev.apps.01.cf.eu01.stackit.cloud"}, auditClaimsMap["aud"])
assert.Equal(t, "Lukas.Schmitt@stackit.cloud", auditClaimsMap["email"])
assert.Equal(t, "2024-11-21 09:40:35 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-11-21 08:40:35 +0000 UTC", auditClaimsMap["iat"])
assert.Equal(t, "https://api.dev.stackit.cloud", auditClaimsMap["iss"])
assert.Equal(t, "c2be1651-1e54-4e6e-bac3-ef072b3f0149", auditClaimsMap["jti"])
assert.Equal(t, "2024-11-21 08:40:18 +0000 UTC", auditClaimsMap["nbf"])
assert.Equal(t, "openid email portal-bff", auditClaimsMap["scope"])
assert.Equal(t, "5e426aed-c487-4c48-af25-87f69cf9cdd4", auditClaimsMap["sub"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("5e426aed-c487-4c48-af25-87f69cf9cdd4"),
url.QueryEscape("https://api.dev.stackit.cloud"))
assert.Equal(t, principal, authenticationPrincipal)
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, "Lukas.Schmitt@stackit.cloud", *authenticationInfo.PrincipalEmail)
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail)
assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
@ -669,13 +607,13 @@ func Test_NewAuditLogEntry(t *testing.T) {
t.Run("minimum attributes set", func(t *testing.T) {
userAgent := "userAgent"
requestHeaders := make(map[string][]string)
requestHeaders["Authorization"] = []string{userToken}
requestHeaders["Authorization"] = []string{"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}
requestHeaders["User-Agent"] = []string{userAgent}
requestHeaders["Custom"] = []string{"customHeader"}
request := pkgAuditCommon.ApiRequest{
request := ApiRequest{
Method: "GET",
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new"},
URL: RequestUrl{Path: "/audit/new"},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Scheme: "http",
@ -702,7 +640,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
}
objectId := uuid.NewString()
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, pkgAuditCommon.EventTypeAdminActivity)
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity)
serviceName := "resource-manager"
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
resourceName := fmt.Sprintf("projects/%s", objectId)
@ -727,7 +665,9 @@ func Test_NewAuditLogEntry(t *testing.T) {
auditRequest,
auditResponse,
nil,
auditMetadata)
auditMetadata,
nil,
nil)
assert.Equal(t, logName, logEntry.LogName)
assert.Equal(t, insertId, logEntry.InsertId)
@ -735,6 +675,8 @@ func Test_NewAuditLogEntry(t *testing.T) {
assert.Equal(t, severity, logEntry.Severity)
assert.NoError(t, logEntry.Timestamp.CheckValid())
assert.Nil(t, logEntry.Labels)
assert.Nil(t, logEntry.TraceParent)
assert.Nil(t, logEntry.TraceState)
payload := logEntry.ProtoPayload
assert.NotNil(t, payload)
@ -756,7 +698,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
authenticationInfo := payload.AuthenticationInfo
assert.NotNil(t, authenticationInfo)
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.ServiceAccountDelegationInfo)
@ -774,7 +716,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
t.Run("all attributes set", func(t *testing.T) {
userAgent := "userAgent"
requestHeaders := make(map[string][]string)
requestHeaders["Authorization"] = []string{userToken}
requestHeaders["Authorization"] = []string{"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}
requestHeaders["User-Agent"] = []string{userAgent}
requestHeaders["Custom"] = []string{"customHeader"}
@ -782,14 +724,14 @@ func Test_NewAuditLogEntry(t *testing.T) {
requestBody["key"] = "request"
requestBodyBytes, _ := json.Marshal(requestBody)
query := "topic=project"
request := pkgAuditCommon.ApiRequest{
request := ApiRequest{
Method: "GET",
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &query},
URL: RequestUrl{Path: "/audit/new", RawQuery: &query},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Scheme: "http",
Header: requestHeaders,
Body: requestBodyBytes,
Body: &requestBodyBytes,
}
clientIp := "127.0.0.1"
@ -816,7 +758,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
responseTime := time.Now().UTC()
auditResponse := AuditResponse{
ResponseBodyBytes: responseBody,
ResponseBodyBytes: &responseBody,
ResponseStatusCode: responseStatusCode,
ResponseHeaders: responseHeader,
ResponseNumItems: &responseNumItems,
@ -826,7 +768,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
auditTime := time.Now().UTC()
objectId := uuid.NewString()
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, pkgAuditCommon.EventTypeAdminActivity)
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity)
serviceName := "resource-manager"
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
resourceName := fmt.Sprintf("projects/%s", objectId)
@ -852,11 +794,16 @@ func Test_NewAuditLogEntry(t *testing.T) {
}
eventMetadata := map[string]interface{}{"key": "value"}
traceParent := "traceParent"
traceState := "traceState"
logEntry, _ := NewAuditLogEntry(
auditRequest,
auditResponse,
eventMetadata,
auditMetadata)
&eventMetadata,
auditMetadata,
&traceParent,
&traceState)
assert.Equal(t, logName, logEntry.LogName)
assert.Equal(t, insertId, logEntry.InsertId)
@ -864,6 +811,8 @@ func Test_NewAuditLogEntry(t *testing.T) {
assert.Equal(t, correlationId, *logEntry.CorrelationId)
assert.Equal(t, timestamppb.New(auditTime), logEntry.Timestamp)
assert.Equal(t, severity, logEntry.Severity)
assert.Equal(t, &traceParent, logEntry.TraceParent)
assert.Equal(t, &traceState, logEntry.TraceState)
assert.NotNil(t, logEntry.ProtoPayload)
payload := logEntry.ProtoPayload
@ -889,7 +838,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
authenticationInfo := payload.AuthenticationInfo
assert.NotNil(t, authenticationInfo)
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.ServiceAccountDelegationInfo)
@ -923,9 +872,221 @@ func Test_NewInsertId(t *testing.T) {
func Test_NewNewAuditRoutingIdentifier(t *testing.T) {
objectId := uuid.NewString()
objectType := pkgAuditCommon.ObjectTypeProject
objectType := ObjectTypeProject
routingIdentifier := NewAuditRoutingIdentifier(objectId, objectType)
assert.Equal(t, objectId, routingIdentifier.Identifier)
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)
})
}
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,13 +1,10 @@
package api
import (
"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"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"testing"
)
func Test_RoutableAuditEvent(t *testing.T) {
@ -21,7 +18,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
Visibility: auditV1.Visibility_VISIBILITY_PUBLIC,
ObjectIdentifier: &auditV1.ObjectIdentifier{
Identifier: "14f7aa86-77ba-4d77-a091-a2cf3395a221",
Type: string(pkgAuditCommon.ObjectTypeProject),
Type: string(ObjectTypeProject),
},
Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &auditV1.UnencryptedData{
Data: []byte("data"),
@ -40,7 +37,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.OperationName = ""
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: operation_name: value is required")
assert.EqualError(t, err, "validation error:\n - operation_name: value is required [required]")
})
t.Run("invalid operation name", func(t *testing.T) {
@ -48,7 +45,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.OperationName = "stackit.resource-manager.v1.INVALID.organizations.create"
err := validator.Validate(&event)
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-]+$`")
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]")
})
t.Run("visibility invalid", func(t *testing.T) {
@ -56,7 +53,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.Visibility = -1
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: visibility: value must be one of the defined enum values")
assert.EqualError(t, err, "validation error:\n - visibility: value must be one of the defined enum values [enum.defined_only]")
})
t.Run("visibility unspecified", func(t *testing.T) {
@ -64,7 +61,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.Visibility = auditV1.Visibility_VISIBILITY_UNSPECIFIED
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: visibility: value is required")
assert.EqualError(t, err, "validation error:\n - visibility: value is required [required]")
})
t.Run("object identifier nil", func(t *testing.T) {
@ -72,7 +69,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.ObjectIdentifier = nil
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: object_identifier: value is required")
assert.EqualError(t, err, "validation error:\n - object_identifier: value is required [required]")
})
t.Run("object identifier id empty", func(t *testing.T) {
@ -80,7 +77,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.ObjectIdentifier.Identifier = ""
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: object_identifier.identifier: value is required")
assert.EqualError(t, err, "validation error:\n - object_identifier.identifier: value is required [required]")
})
t.Run("object identifier id not uuid", func(t *testing.T) {
@ -88,7 +85,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.ObjectIdentifier.Identifier = "invalid"
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: object_identifier.identifier: value must be a valid UUID")
assert.EqualError(t, err, "validation error:\n - object_identifier.identifier: value must be a valid UUID [string.uuid]")
})
t.Run("object identifier type empty", func(t *testing.T) {
@ -96,7 +93,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.ObjectIdentifier.Type = ""
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: object_identifier.type: value is required")
assert.EqualError(t, err, "validation error:\n - object_identifier.type: value is required [required]")
})
t.Run("data nil", func(t *testing.T) {
@ -104,7 +101,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
event.Data = nil
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: data: exactly one field is required in oneof")
assert.EqualError(t, err, "validation error:\n - data: exactly one field is required in oneof [required]")
})
t.Run("data empty", func(t *testing.T) {
@ -115,7 +112,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
}}
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: unencrypted_data.data: value is required")
assert.EqualError(t, err, "validation error:\n - unencrypted_data.data: value is required [required]")
})
t.Run("data protobuf type empty", func(t *testing.T) {
@ -126,59 +123,6 @@ func Test_RoutableAuditEvent(t *testing.T) {
}}
err := validator.Validate(&event)
assert.EqualError(t, err, "validation error: unencrypted_data.protobuf_type: value is required")
assert.EqualError(t, err, "validation error:\n - unencrypted_data.protobuf_type: value is required [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,27 +4,16 @@ import (
"fmt"
"time"
"github.com/google/uuid"
"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"
"github.com/google/uuid"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
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 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 userToken = "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"
const userTokenWithSimpleAudience = "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6InNlcnZpY2UtYWNjb3VudC1mMDdiZjZhOC02MjA3LTRmOGItYjNlOS03M2VkMGJlYjg4ZjUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3N0YWNraXQtc2VydmljZS1hY2NvdW50LWRldi5hcHBzLjAxLmNmLmV1MDEuc3RhY2tpdC5jbG91ZCIsImVtYWlsIjoiTHVrYXMuU2NobWl0dEBzdGFja2l0LmNsb3VkIiwiZXhwIjoxNzMyMTgyMDM1LCJpYXQiOjE3MzIxNzg0MzUsImlzcyI6Imh0dHBzOi8vYXBpLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiYzJiZTE2NTEtMWU1NC00ZTZlLWJhYzMtZWYwNzJiM2YwMTQ5IiwibmJmIjoxNzMyMTc4NDE4LCJyb2xlcyI6bnVsbCwic2NvcGUiOiJvcGVuaWQgZW1haWwgcG9ydGFsLWJmZiIsInN1YiI6IjVlNDI2YWVkLWM0ODctNGM0OC1hZjI1LTg3ZjY5Y2Y5Y2RkNCIsInVzZXJfaWQiOiIiLCJ4X2NsaWVudF9pZCI6IiIsInppZCI6IiJ9.notavailable"
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(
*auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier,
@ -43,21 +32,20 @@ func NewOrganizationAuditEvent(
headers["Content-Type"] = "application/json"
labels := make(map[string]string)
labels["label1"] = "value1"
email := "user@example.com"
auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier, pkgAuditCommon.EventTypeAdminActivity),
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeOrganization.Plural(), identifier, EventTypeAdminActivity),
ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.organization.created",
ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier),
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeOrganization.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: uuid.NewString(),
PrincipalEmail: &email,
PrincipalEmail: "user@example.com",
ServiceAccountName: nil,
ServiceAccountDelegationInfo: nil,
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", ObjectTypeOrganization.Plural(), identifier),
Permission: &permission,
Granted: &permissionGranted,
}},
@ -101,11 +89,13 @@ func NewOrganizationAuditEvent(
CorrelationId: &correlationId,
Timestamp: timestamppb.New(time.Now()),
Severity: auditV1.LogSeverity_LOG_SEVERITY_DEFAULT,
TraceParent: nil,
TraceState: nil,
}
objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(),
Type: string(pkgAuditCommon.ObjectTypeOrganization),
Type: string(ObjectTypeOrganization),
}
if customization != nil {
@ -115,7 +105,7 @@ func NewOrganizationAuditEvent(
return auditEvent, objectIdentifier
}
func NewFolderAuditEvent(
func newFolderAuditEvent(
customization *func(
*auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier,
@ -134,21 +124,20 @@ func NewFolderAuditEvent(
headers["Content-Type"] = "application/json"
labels := make(map[string]string)
labels["label1"] = "value1"
email := "user@example.com"
auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier, pkgAuditCommon.EventTypeAdminActivity),
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeFolder.Plural(), identifier, EventTypeAdminActivity),
ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.folder.created",
ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier),
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeFolder.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: uuid.NewString(),
PrincipalEmail: &email,
PrincipalEmail: "user@example.com",
ServiceAccountName: nil,
ServiceAccountDelegationInfo: nil,
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", ObjectTypeFolder.Plural(), identifier),
Permission: &permission,
Granted: &permissionGranted,
}},
@ -192,11 +181,13 @@ func NewFolderAuditEvent(
CorrelationId: &correlationId,
Timestamp: timestamppb.New(time.Now()),
Severity: auditV1.LogSeverity_LOG_SEVERITY_DEFAULT,
TraceParent: nil,
TraceState: nil,
}
objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(),
Type: string(pkgAuditCommon.ObjectTypeFolder),
Type: string(ObjectTypeFolder),
}
if customization != nil {
@ -206,7 +197,7 @@ func NewFolderAuditEvent(
return auditEvent, objectIdentifier
}
func NewProjectAuditEvent(
func newProjectAuditEvent(
customization *func(
*auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier,
@ -225,21 +216,20 @@ func NewProjectAuditEvent(
headers["Content-Type"] = "application/json"
labels := make(map[string]string)
labels["label1"] = "value1"
email := "user@example.com"
auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier, pkgAuditCommon.EventTypeAdminActivity),
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeProject.Plural(), identifier, EventTypeAdminActivity),
ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.project.created",
ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: uuid.NewString(),
PrincipalEmail: &email,
PrincipalEmail: "user@example.com",
ServiceAccountName: nil,
ServiceAccountDelegationInfo: nil,
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier),
Permission: &permission,
Granted: &permissionGranted,
}},
@ -283,11 +273,13 @@ func NewProjectAuditEvent(
CorrelationId: &correlationId,
Timestamp: timestamppb.New(time.Now()),
Severity: auditV1.LogSeverity_LOG_SEVERITY_DEFAULT,
TraceParent: nil,
TraceState: nil,
}
objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(),
Type: string(pkgAuditCommon.ObjectTypeProject),
Type: string(ObjectTypeProject),
}
if customization != nil {
@ -297,7 +289,7 @@ func NewProjectAuditEvent(
return auditEvent, objectIdentifier
}
func NewProjectSystemAuditEvent(
func newProjectSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {
identifier := uuid.New()
@ -311,21 +303,20 @@ func NewProjectSystemAuditEvent(
serviceAccountId := uuid.NewString()
serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId)
delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}}
email := "service-account@sa.stackit.cloud"
auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.SystemIdentifier.Type, pkgAuditCommon.SystemIdentifier.Identifier, pkgAuditCommon.EventTypeSystemEvent),
LogName: fmt.Sprintf("%s/%s/logs/%s", SystemIdentifier.Type, SystemIdentifier.Identifier, EventTypeSystemEvent),
ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.system.changed",
ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: serviceAccountId,
PrincipalEmail: &email,
PrincipalEmail: "service-account@sa.stackit.cloud",
ServiceAccountName: &serviceAccountName,
ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal},
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier),
Permission: nil,
Granted: nil,
}},
@ -369,6 +360,8 @@ func NewProjectSystemAuditEvent(
CorrelationId: &correlationId,
Timestamp: timestamppb.New(time.Now()),
Severity: auditV1.LogSeverity_LOG_SEVERITY_DEFAULT,
TraceParent: nil,
TraceState: nil,
}
if customization != nil {
@ -378,7 +371,7 @@ func NewProjectSystemAuditEvent(
return auditEvent
}
func NewSystemAuditEvent(
func newSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {
identifier := uuid.Nil
@ -392,21 +385,20 @@ func NewSystemAuditEvent(
serviceAccountId := uuid.NewString()
serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId)
delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}}
email := "service-account@sa.stackit.cloud"
auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier, pkgAuditCommon.EventTypeSystemEvent),
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeSystem.Plural(), identifier, EventTypeSystemEvent),
ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.system.changed",
ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier),
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeSystem.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: serviceAccountId,
PrincipalEmail: &email,
PrincipalEmail: "service-account@sa.stackit.cloud",
ServiceAccountName: &serviceAccountName,
ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal},
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", ObjectTypeSystem.Plural(), identifier),
Permission: nil,
Granted: nil,
}},
@ -450,6 +442,8 @@ func NewSystemAuditEvent(
CorrelationId: &correlationId,
Timestamp: timestamppb.New(time.Now()),
Severity: auditV1.LogSeverity_LOG_SEVERITY_DEFAULT,
TraceParent: nil,
TraceState: nil,
}
if customization != nil {

View file

@ -0,0 +1,218 @@
package messaging
import (
"context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/log"
"errors"
"fmt"
"github.com/Azure/go-amqp"
"strings"
"sync"
"time"
)
// 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
}
// MutexApi is wrapper around an API implementation that controls mutual exclusive access to the api.
type MutexApi struct {
mutex *sync.Mutex
api *Api
}
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)
}
// 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) {
sender, err := w.session.NewSender(ctx, target, opts)
var amqpSender AmqpSender = sender
return &amqpSender, err
}
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
}
func NewAmqpApi(amqpConfig AmqpConfig) (*Api, error) {
amqpApi := &AmqpApi{config: amqpConfig}
err := amqpApi.connect()
if err != nil {
return nil, err
}
var messagingApi Api = amqpApi
return &messagingApi, 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 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 err
}
a.connection = conn
// Initialize session
session, err := conn.NewSession(context.Background(), nil)
if err != nil {
return 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 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 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 {
_ = (*sender).Close(ctx)
return err
}
return nil
}
// resetConnection closes the current session and connection and reconnects to the messaging system.
func (a *AmqpApi) resetConnection(ctx context.Context) error {
_ = (*a.session).Close(ctx)
err := a.connection.Close()
if err != nil {
log.AuditLogger.Error("failed to close message connection", err)
}
return a.connect()
}

View file

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

View file

@ -1,9 +1,6 @@
package utils
import (
"slices"
"sync"
)
import "sync"
// SequenceNumberGenerator can be used to generate increasing numbers.
type SequenceNumberGenerator interface {
@ -11,50 +8,38 @@ type SequenceNumberGenerator interface {
// Next returns the next number
Next() uint64
// Revert can be used to revert a specific number (e.g. in case of an error)
Revert(uint64)
// Revert can be used to decrease the number (e.g. in case of an error)
Revert()
}
// DefaultSequenceNumberGenerator is a mutex protected implementation of SequenceNumberGenerator
type DefaultSequenceNumberGenerator struct {
backlog []uint64
sequenceNumber uint64
sequenceNumberLock sync.Mutex
}
// NewDefaultSequenceNumberGenerator returns an instance of DefaultSequenceNumberGenerator as pointer
// of SequenceNumberGenerator.
func NewDefaultSequenceNumberGenerator() SequenceNumberGenerator {
func NewDefaultSequenceNumberGenerator() *SequenceNumberGenerator {
var generator SequenceNumberGenerator = &DefaultSequenceNumberGenerator{
backlog: make([]uint64, 0),
sequenceNumber: 0,
sequenceNumberLock: sync.Mutex{},
}
return generator
return &generator
}
// Next implements SequenceNumberGenerator.Next
func (g *DefaultSequenceNumberGenerator) Next() uint64 {
g.sequenceNumberLock.Lock()
defer g.sequenceNumberLock.Unlock()
var next uint64
if len(g.backlog) == 0 {
next = g.sequenceNumber
g.sequenceNumber++
} else {
next = g.backlog[0]
g.backlog = g.backlog[1:]
}
next := g.sequenceNumber
g.sequenceNumber += 1
return next
}
// Revert implements SequenceNumberGenerator.Revert
func (g *DefaultSequenceNumberGenerator) Revert(value uint64) {
func (g *DefaultSequenceNumberGenerator) Revert() {
g.sequenceNumberLock.Lock()
defer g.sequenceNumberLock.Unlock()
if value == g.sequenceNumber-1 {
g.sequenceNumber--
} else if !slices.Contains(g.backlog, value) {
g.backlog = append(g.backlog, value)
}
g.sequenceNumber -= 1
}

View file

@ -0,0 +1,22 @@
package utils
import (
"github.com/stretchr/testify/assert"
"testing"
)
func Test_DefaultSequenceNumberGenerator(t *testing.T) {
t.Run("next", func(t *testing.T) {
var sequenceGenerator = NewDefaultSequenceNumberGenerator()
assert.Equal(t, uint64(0), (*sequenceGenerator).Next())
})
t.Run("revert", func(t *testing.T) {
var sequenceGenerator = NewDefaultSequenceNumberGenerator()
assert.Equal(t, uint64(0), (*sequenceGenerator).Next())
assert.Equal(t, uint64(1), (*sequenceGenerator).Next())
(*sequenceGenerator).Revert()
assert.Equal(t, uint64(1), (*sequenceGenerator).Next())
})
}

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc-gen-go v1.35.1
// protoc (unknown)
// source: audit/v1/audit_event.proto
@ -15,7 +15,6 @@ import (
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@ -31,24 +30,24 @@ type LogSeverity int32
const (
LogSeverity_LOG_SEVERITY_UNSPECIFIED LogSeverity = 0
// The log entry has no assigned severity level.
// (1) The log entry has no assigned severity level.
LogSeverity_LOG_SEVERITY_DEFAULT LogSeverity = 100
// Debug or trace information.
// (100) Debug or trace information.
LogSeverity_LOG_SEVERITY_DEBUG LogSeverity = 200
// Routine information, such as ongoing status or performance.
// (200) Routine information, such as ongoing status or performance.
LogSeverity_LOG_SEVERITY_INFO LogSeverity = 300
// Normal but significant events, such as start up, shut down, or
// (300) Normal but significant events, such as start up, shut down, or
// a configuration change.
LogSeverity_LOG_SEVERITY_NOTICE LogSeverity = 400
// Warning events might cause problems.
// (400) Warning events might cause problems.
LogSeverity_LOG_SEVERITY_WARNING LogSeverity = 500
// Error events are likely to cause problems.
// (500) Error events are likely to cause problems.
LogSeverity_LOG_SEVERITY_ERROR LogSeverity = 600
// Critical events cause more severe problems or outages.
// (600) Critical events cause more severe problems or outages.
LogSeverity_LOG_SEVERITY_CRITICAL LogSeverity = 700
// A person must take an action immediately.
// (700) A person must take an action immediately.
LogSeverity_LOG_SEVERITY_ALERT LogSeverity = 800
// One or more systems are unusable.
// (800) One or more systems are unusable.
LogSeverity_LOG_SEVERITY_EMERGENCY LogSeverity = 900
)
@ -182,7 +181,10 @@ func (AttributeContext_HttpMethod) EnumDescriptor() ([]byte, []int) {
// The audit log entry can be used to record an incident in the audit log.
type AuditLogEntry struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The resource name of the log to which this log entry belongs.
//
// Format: <pluralType>/<identifier>/logs/<eventType>
@ -217,7 +219,6 @@ type AuditLogEntry struct {
// Examples:
//
// "1721899117/eu01/319a7fb9-edd2-46c6-953a-a724bb377c61/8792726390909855142"
// "1721899117/eu01-m/319a7fb9-edd2-46c6-953a-a724bb377c61/8792726390909855142"
//
// Required: true
InsertId string `protobuf:"bytes,3,opt,name=insert_id,json=insertId,proto3" json:"insert_id,omitempty"`
@ -225,7 +226,7 @@ type AuditLogEntry struct {
// information about the log entry.
//
// Required: false
Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Correlate multiple audit logs by setting the same id
//
// Required: false
@ -237,9 +238,29 @@ type AuditLogEntry struct {
// The severity of the log entry.
//
// Required: true
Severity LogSeverity `protobuf:"varint,7,opt,name=severity,proto3,enum=audit.v1.LogSeverity" json:"severity,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Severity LogSeverity `protobuf:"varint,7,opt,name=severity,proto3,enum=audit.v1.LogSeverity" json:"severity,omitempty"`
// Customer set W3C conform trace parent header:
// https://www.w3.org/TR/trace-context/#traceparent-header
//
// Format: <version>-<trace-id>-<parent-id>-<trace-flags>
//
// Examples:
//
// "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
//
// Required: false
TraceParent *string `protobuf:"bytes,8,opt,name=trace_parent,json=traceParent,proto3,oneof" json:"trace_parent,omitempty"`
// Customer set W3C conform trace state header:
// https://www.w3.org/TR/trace-context/#tracestate-header
//
// Format: <key1>=<value1>[,<keyN>=<valueN>]
//
// Examples:
//
// "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
//
// Required: false
TraceState *string `protobuf:"bytes,9,opt,name=trace_state,json=traceState,proto3,oneof" json:"trace_state,omitempty"`
}
func (x *AuditLogEntry) Reset() {
@ -321,9 +342,26 @@ func (x *AuditLogEntry) GetSeverity() LogSeverity {
return LogSeverity_LOG_SEVERITY_UNSPECIFIED
}
func (x *AuditLogEntry) GetTraceParent() string {
if x != nil && x.TraceParent != nil {
return *x.TraceParent
}
return ""
}
func (x *AuditLogEntry) GetTraceState() string {
if x != nil && x.TraceState != nil {
return *x.TraceState
}
return ""
}
// Common audit log format for STACKIT API operations.
type AuditLog struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The name of the API service performing the operation.
//
// Examples:
@ -368,7 +406,6 @@ type AuditLog struct {
// "projects/7046e7b6-5ae9-441c-99fe-2cd28a5078ec/locations/_/instances/instance-20240723-174217"
// "projects/7046e7b6-5ae9-441c-99fe-2cd28a5078ec/locations/sx-stoi01/instances/instance-20240723-174217"
// "projects/dd7d1807-54e9-4426-8994-721758b5b554/locations/eu01/vms/b6851b4e-7a9d-4973-ab0f-a80a13ee3060/ports/78f8bad4-a291-4fa3-b07f-4a1985d3dbe8"
// "projects/dd7d1807-54e9-4426-8994-721758b5b554/locations/eu01-m/vms/b6851b4e-7a9d-4973-ab0f-a80a13ee3060/ports/78f8bad4-a291-4fa3-b07f-4a1985d3dbe8"
//
// Required: true
ResourceName string `protobuf:"bytes,3,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"`
@ -406,9 +443,7 @@ type AuditLog struct {
// information associated with the current audited event.
//
// Required: false
Metadata *structpb.Struct `protobuf:"bytes,10,opt,name=metadata,proto3,oneof" json:"metadata,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Metadata *structpb.Struct `protobuf:"bytes,10,opt,name=metadata,proto3,oneof" json:"metadata,omitempty"`
}
func (x *AuditLog) Reset() {
@ -513,7 +548,10 @@ func (x *AuditLog) GetMetadata() *structpb.Struct {
// Authentication information for the operation.
type AuthenticationInfo struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// STACKIT principal id
//
// Required: true
@ -521,8 +559,8 @@ type AuthenticationInfo struct {
// The email address of the authenticated user.
// Service accounts have email addresses that can be used.
//
// Required: false
PrincipalEmail *string `protobuf:"bytes,2,opt,name=principal_email,json=principalEmail,proto3,oneof" json:"principal_email,omitempty"`
// Required: true
PrincipalEmail string `protobuf:"bytes,2,opt,name=principal_email,json=principalEmail,proto3" json:"principal_email,omitempty"`
// The name of the service account used to create or exchange
// credentials for authenticating the service account making the request.
//
@ -542,8 +580,6 @@ type AuthenticationInfo struct {
//
// Required: false
ServiceAccountDelegationInfo []*ServiceAccountDelegationInfo `protobuf:"bytes,4,rep,name=service_account_delegation_info,json=serviceAccountDelegationInfo,proto3" json:"service_account_delegation_info,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AuthenticationInfo) Reset() {
@ -584,8 +620,8 @@ func (x *AuthenticationInfo) GetPrincipalId() string {
}
func (x *AuthenticationInfo) GetPrincipalEmail() string {
if x != nil && x.PrincipalEmail != nil {
return *x.PrincipalEmail
if x != nil {
return x.PrincipalEmail
}
return ""
}
@ -606,7 +642,10 @@ func (x *AuthenticationInfo) GetServiceAccountDelegationInfo() []*ServiceAccount
// Authorization information for the operation.
type AuthorizationInfo struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The resource being accessed, as a REST-style string.
//
// Format: <pluralType>/<id>[/<details>]
@ -636,9 +675,7 @@ type AuthorizationInfo struct {
// IAM permission check result.
//
// Required: false
Granted *bool `protobuf:"varint,3,opt,name=granted,proto3,oneof" json:"granted,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Granted *bool `protobuf:"varint,3,opt,name=granted,proto3,oneof" json:"granted,omitempty"`
}
func (x *AuthorizationInfo) Reset() {
@ -697,9 +734,9 @@ func (x *AuthorizationInfo) GetGranted() bool {
// An attribute is a piece of metadata that describes an activity on a network
// service.
type AttributeContext struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *AttributeContext) Reset() {
@ -734,7 +771,10 @@ func (*AttributeContext) Descriptor() ([]byte, []int) {
// Metadata about the request.
type RequestMetadata struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The IP address of the caller.
// For caller from internet, this will be public IPv4 or IPv6 address.
// For caller from a VM / K8s Service / etc, this will be the SIT proxy's IPv4 address.
@ -756,8 +796,6 @@ type RequestMetadata struct {
//
// Required: true
RequestAttributes *AttributeContext_Request `protobuf:"bytes,3,opt,name=request_attributes,json=requestAttributes,proto3" json:"request_attributes,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RequestMetadata) Reset() {
@ -813,7 +851,10 @@ func (x *RequestMetadata) GetRequestAttributes() *AttributeContext_Request {
// Metadata about the response
type ResponseMetadata struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The http or gRPC status code.
//
// Examples:
@ -835,8 +876,6 @@ type ResponseMetadata struct {
//
// Required: true
ResponseAttributes *AttributeContext_Response `protobuf:"bytes,4,opt,name=response_attributes,json=responseAttributes,proto3" json:"response_attributes,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResponseMetadata) Reset() {
@ -899,17 +938,18 @@ func (x *ResponseMetadata) GetResponseAttributes() *AttributeContext_Response {
// Identity delegation history of an authenticated service account.
type ServiceAccountDelegationInfo struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Entity that creates credentials for service account and assumes its
// identity for authentication.
//
// Types that are valid to be assigned to Authority:
// Types that are assignable to Authority:
//
// *ServiceAccountDelegationInfo_SystemPrincipal_
// *ServiceAccountDelegationInfo_IdpPrincipal_
Authority isServiceAccountDelegationInfo_Authority `protobuf_oneof:"authority"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Authority isServiceAccountDelegationInfo_Authority `protobuf_oneof:"authority"`
}
func (x *ServiceAccountDelegationInfo) Reset() {
@ -942,27 +982,23 @@ func (*ServiceAccountDelegationInfo) Descriptor() ([]byte, []int) {
return file_audit_v1_audit_event_proto_rawDescGZIP(), []int{7}
}
func (x *ServiceAccountDelegationInfo) GetAuthority() isServiceAccountDelegationInfo_Authority {
if x != nil {
return x.Authority
func (m *ServiceAccountDelegationInfo) GetAuthority() isServiceAccountDelegationInfo_Authority {
if m != nil {
return m.Authority
}
return nil
}
func (x *ServiceAccountDelegationInfo) GetSystemPrincipal() *ServiceAccountDelegationInfo_SystemPrincipal {
if x != nil {
if x, ok := x.Authority.(*ServiceAccountDelegationInfo_SystemPrincipal_); ok {
return x.SystemPrincipal
}
if x, ok := x.GetAuthority().(*ServiceAccountDelegationInfo_SystemPrincipal_); ok {
return x.SystemPrincipal
}
return nil
}
func (x *ServiceAccountDelegationInfo) GetIdpPrincipal() *ServiceAccountDelegationInfo_IdpPrincipal {
if x != nil {
if x, ok := x.Authority.(*ServiceAccountDelegationInfo_IdpPrincipal_); ok {
return x.IdpPrincipal
}
if x, ok := x.GetAuthority().(*ServiceAccountDelegationInfo_IdpPrincipal_); ok {
return x.IdpPrincipal
}
return nil
}
@ -989,7 +1025,10 @@ func (*ServiceAccountDelegationInfo_IdpPrincipal_) isServiceAccountDelegationInf
// based on the JSON Web Token (JWT) standard, but the terms also
// correlate to concepts in other standards.
type AttributeContext_Auth struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The authenticated principal. Reflects the issuer ("iss") and subject
// ("sub") claims within a JWT.
//
@ -1031,9 +1070,7 @@ type AttributeContext_Auth struct {
// }
//
// Required: true
Claims *structpb.Struct `protobuf:"bytes,3,opt,name=claims,proto3" json:"claims,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Claims *structpb.Struct `protobuf:"bytes,3,opt,name=claims,proto3" json:"claims,omitempty"`
}
func (x *AttributeContext_Auth) Reset() {
@ -1091,7 +1128,10 @@ func (x *AttributeContext_Auth) GetClaims() *structpb.Struct {
// request is not an HTTP request, the runtime system should try to map
// the actual request to an equivalent HTTP request.
type AttributeContext_Request struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique ID for a request, which can be propagated to downstream
// systems. The ID should have low probability of collision
// within a single day for a specific service.
@ -1120,7 +1160,7 @@ type AttributeContext_Request struct {
// Internal IP-Addresses have to be removed (e.g. in x-forwarded-xxx headers).
//
// Required: true
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// The gRPC / HTTP URL path.
//
// Required: true
@ -1154,9 +1194,7 @@ type AttributeContext_Request struct {
// The request authentication.
//
// Required: true
Auth *AttributeContext_Auth `protobuf:"bytes,10,opt,name=auth,proto3" json:"auth,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Auth *AttributeContext_Auth `protobuf:"bytes,10,opt,name=auth,proto3" json:"auth,omitempty"`
}
func (x *AttributeContext_Request) Reset() {
@ -1262,7 +1300,10 @@ func (x *AttributeContext_Request) GetAuth() *AttributeContext_Auth {
// This message defines attributes for a typical network response. It
// generally models semantics of an HTTP response.
type AttributeContext_Response struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The number of items returned to the client if applicable.
//
// Required: false
@ -1276,14 +1317,12 @@ type AttributeContext_Response struct {
// lowercased, because HTTP header keys are case-insensitive.
//
// Required: false
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// The timestamp when the "destination" service generates the first byte of
// the response.
//
// Required: true
Time *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=time,proto3" json:"time,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Time *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=time,proto3" json:"time,omitempty"`
}
func (x *AttributeContext_Response) Reset() {
@ -1346,13 +1385,14 @@ func (x *AttributeContext_Response) GetTime() *timestamppb.Timestamp {
// Anonymous system principal to be used when no user identity is available.
type ServiceAccountDelegationInfo_SystemPrincipal struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Metadata about the service that uses the service account.
//
// Required: false
ServiceMetadata *structpb.Struct `protobuf:"bytes,1,opt,name=service_metadata,json=serviceMetadata,proto3,oneof" json:"service_metadata,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ServiceAccountDelegationInfo_SystemPrincipal) Reset() {
@ -1394,7 +1434,10 @@ func (x *ServiceAccountDelegationInfo_SystemPrincipal) GetServiceMetadata() *str
// STACKIT idp principal.
type ServiceAccountDelegationInfo_IdpPrincipal struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// STACKIT principal id
//
// Required: true
@ -1408,8 +1451,6 @@ type ServiceAccountDelegationInfo_IdpPrincipal struct {
//
// Required: false
ServiceMetadata *structpb.Struct `protobuf:"bytes,3,opt,name=service_metadata,json=serviceMetadata,proto3,oneof" json:"service_metadata,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ServiceAccountDelegationInfo_IdpPrincipal) Reset() {
@ -1465,152 +1506,345 @@ func (x *ServiceAccountDelegationInfo_IdpPrincipal) GetServiceMetadata() *struct
var File_audit_v1_audit_event_proto protoreflect.FileDescriptor
const file_audit_v1_audit_event_proto_rawDesc = "" +
"\n" +
"\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" +
"\rAuditLogEntry\x12x\n" +
"\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" +
"\rproto_payload\x18\x02 \x01(\v2\x12.audit.v1.AuditLogB\x06\xbaH\x03\xc8\x01\x01R\fprotoPayload\x12L\n" +
"\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" +
"\x06labels\x18\x04 \x03(\v2#.audit.v1.AuditLogEntry.LabelsEntryR\x06labels\x126\n" +
"\x0ecorrelation_id\x18\x05 \x01(\tB\n" +
"\xbaH\ar\x05\x10\x01\x18\xff\x01H\x00R\rcorrelationId\x88\x01\x01\x12E\n" +
"\ttimestamp\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampB\v\xbaH\b\xc8\x01\x01\xb2\x01\x028\x01R\ttimestamp\x12>\n" +
"\bseverity\x18\a \x01(\x0e2\x15.audit.v1.LogSeverityB\v\xbaH\b\xc8\x01\x01\x82\x01\x02\x10\x01R\bseverity\x1a9\n" +
"\vLabelsEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x11\n" +
"\x0f_correlation_id\"\xb3\x06\n" +
"\bAuditLog\x125\n" +
"\fservice_name\x18\x01 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
"\x10\x012\x06.*\\S.*R\vserviceName\x12w\n" +
"\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" +
"\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" +
"\x13authentication_info\x18\x04 \x01(\v2\x1c.audit.v1.AuthenticationInfoB\x06\xbaH\x03\xc8\x01\x01R\x12authenticationInfo\x12J\n" +
"\x12authorization_info\x18\x05 \x03(\v2\x1b.audit.v1.AuthorizationInfoR\x11authorizationInfo\x12L\n" +
"\x10request_metadata\x18\x06 \x01(\v2\x19.audit.v1.RequestMetadataB\x06\xbaH\x03\xc8\x01\x01R\x0frequestMetadata\x126\n" +
"\arequest\x18\a \x01(\v2\x17.google.protobuf.StructH\x00R\arequest\x88\x01\x01\x12O\n" +
"\x11response_metadata\x18\b \x01(\v2\x1a.audit.v1.ResponseMetadataB\x06\xbaH\x03\xc8\x01\x01R\x10responseMetadata\x128\n" +
"\bresponse\x18\t \x01(\v2\x17.google.protobuf.StructH\x01R\bresponse\x88\x01\x01\x128\n" +
"\bmetadata\x18\n" +
" \x01(\v2\x17.google.protobuf.StructH\x02R\bmetadata\x88\x01\x01B\n" +
"\n" +
"\b_requestB\v\n" +
"\t_responseB\v\n" +
"\t_metadata\"\x93\x03\n" +
"\x12AuthenticationInfo\x125\n" +
"\fprincipal_id\x18\x01 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
"\x10\x012\x06.*\\S.*R\vprincipalId\x12:\n" +
"\x0fprincipal_email\x18\x02 \x01(\tB\f\xbaH\tr\a\x10\x05\x18\xff\x01`\x01H\x00R\x0eprincipalEmail\x88\x01\x01\x12n\n" +
"\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" +
"\x1fservice_account_delegation_info\x18\x04 \x03(\v2&.audit.v1.ServiceAccountDelegationInfoR\x1cserviceAccountDelegationInfoB\x12\n" +
"\x10_principal_emailB\x17\n" +
"\x15_service_account_name\"\xf2\x01\n" +
"\x11AuthorizationInfo\x12U\n" +
"\bresource\x18\x01 \x01(\tB9\xbaH6\xc8\x01\x01r12/^[a-z]+/[a-z0-9-]+(?:/[a-z0-9-]+/[a-z0-9-_]+)*$R\bresource\x12L\n" +
"\n" +
"permission\x18\x02 \x01(\tB'\xbaH$r\"2 ^[a-z-]+(?:\\.[a-z-]+)*\\.[a-z-]+$H\x00R\n" +
"permission\x88\x01\x01\x12\x1d\n" +
"\agranted\x18\x03 \x01(\bH\x01R\agranted\x88\x01\x01B\r\n" +
"\v_permissionB\n" +
"\n" +
"\b_granted\"\xaa\v\n" +
"\x10AttributeContext\x1a\xa9\x01\n" +
"\x04Auth\x12J\n" +
"\tprincipal\x18\x01 \x01(\tB,\xbaH)\xc8\x01\x01r$2\"^[a-zA-Z0-9-%._]+/[a-zA-Z0-9-%.]+$R\tprincipal\x12\x1c\n" +
"\taudiences\x18\x02 \x03(\tR\taudiences\x127\n" +
"\x06claims\x18\x03 \x01(\v2\x17.google.protobuf.StructB\x06\xbaH\x03\xc8\x01\x01R\x06claims\x1a\xce\x04\n" +
"\aRequest\x12\x13\n" +
"\x02id\x18\x01 \x01(\tH\x00R\x02id\x88\x01\x01\x12J\n" +
"\x06method\x18\x02 \x01(\x0e2%.audit.v1.AttributeContext.HttpMethodB\v\xbaH\b\xc8\x01\x01\x82\x01\x02\x10\x01R\x06method\x12Q\n" +
"\aheaders\x18\x03 \x03(\v2/.audit.v1.AttributeContext.Request.HeadersEntryB\x06\xbaH\x03\xc8\x01\x01R\aheaders\x12)\n" +
"\x04path\x18\x04 \x01(\tB\x15\xbaH\x12\xc8\x01\x01r\r\x10\x01\x18\xff\x012\x06.*\\S.*R\x04path\x12&\n" +
"\x04host\x18\x05 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
"\x10\x012\x06.*\\S.*R\x04host\x12*\n" +
"\x06scheme\x18\x06 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
"\x10\x012\x06.*\\S.*R\x06scheme\x12\x19\n" +
"\x05query\x18\a \x01(\tH\x01R\x05query\x88\x01\x01\x12;\n" +
"\x04time\x18\b \x01(\v2\x1a.google.protobuf.TimestampB\v\xbaH\b\xc8\x01\x01\xb2\x01\x028\x01R\x04time\x12.\n" +
"\bprotocol\x18\t \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
"\x10\x012\x06.*\\S.*R\bprotocol\x12;\n" +
"\x04auth\x18\n" +
" \x01(\v2\x1f.audit.v1.AttributeContext.AuthB\x06\xbaH\x03\xc8\x01\x01R\x04auth\x1a:\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x05\n" +
"\x03_idB\b\n" +
"\x06_query\x1a\x87\x03\n" +
"\bResponse\x12W\n" +
"\x12num_response_items\x18\x01 \x01(\v2\x1b.google.protobuf.Int64ValueB\a\xbaH\x04\"\x02(\x00H\x00R\x10numResponseItems\x88\x01\x01\x12=\n" +
"\x04size\x18\x02 \x01(\v2\x1b.google.protobuf.Int64ValueB\a\xbaH\x04\"\x02(\x00H\x01R\x04size\x88\x01\x01\x12J\n" +
"\aheaders\x18\x03 \x03(\v20.audit.v1.AttributeContext.Response.HeadersEntryR\aheaders\x12;\n" +
"\x04time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\v\xbaH\b\xc8\x01\x01\xb2\x01\x028\x01R\x04time\x1a:\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x15\n" +
"\x13_num_response_itemsB\a\n" +
"\x05_size\"\x8e\x02\n" +
"\n" +
"HttpMethod\x12\x1b\n" +
"\x17HTTP_METHOD_UNSPECIFIED\x10\x00\x12\x15\n" +
"\x11HTTP_METHOD_OTHER\x10\x01\x12\x13\n" +
"\x0fHTTP_METHOD_GET\x10\x02\x12\x14\n" +
"\x10HTTP_METHOD_HEAD\x10\x03\x12\x14\n" +
"\x10HTTP_METHOD_POST\x10\x04\x12\x13\n" +
"\x0fHTTP_METHOD_PUT\x10\x05\x12\x16\n" +
"\x12HTTP_METHOD_DELETE\x10\x06\x12\x17\n" +
"\x13HTTP_METHOD_CONNECT\x10\a\x12\x17\n" +
"\x13HTTP_METHOD_OPTIONS\x10\b\x12\x15\n" +
"\x11HTTP_METHOD_TRACE\x10\t\x12\x15\n" +
"\x11HTTP_METHOD_PATCH\x10\n" +
"\"\xe9\x01\n" +
"\x0fRequestMetadata\x12'\n" +
"\tcaller_ip\x18\x01 \x01(\tB\n" +
"\xbaH\a\xc8\x01\x01r\x02p\x01R\bcallerIp\x12R\n" +
"\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" +
"\x12request_attributes\x18\x03 \x01(\v2\".audit.v1.AttributeContext.RequestB\x06\xbaH\x03\xc8\x01\x01R\x11requestAttributes\"\xb4\x02\n" +
"\x10ResponseMetadata\x12H\n" +
"\vstatus_code\x18\x01 \x01(\v2\x1b.google.protobuf.Int32ValueB\n" +
"\xbaH\a\xc8\x01\x01\x1a\x02(\x00R\n" +
"statusCode\x12(\n" +
"\rerror_message\x18\x02 \x01(\tH\x00R\ferrorMessage\x88\x01\x01\x12<\n" +
"\rerror_details\x18\x03 \x03(\v2\x17.google.protobuf.StructR\ferrorDetails\x12\\\n" +
"\x13response_attributes\x18\x04 \x01(\v2#.audit.v1.AttributeContext.ResponseB\x06\xbaH\x03\xc8\x01\x01R\x12responseAttributesB\x10\n" +
"\x0e_error_message\"\xca\x04\n" +
"\x1cServiceAccountDelegationInfo\x12c\n" +
"\x10system_principal\x18\x01 \x01(\v26.audit.v1.ServiceAccountDelegationInfo.SystemPrincipalH\x00R\x0fsystemPrincipal\x12Z\n" +
"\ridp_principal\x18\x02 \x01(\v23.audit.v1.ServiceAccountDelegationInfo.IdpPrincipalH\x00R\fidpPrincipal\x1ao\n" +
"\x0fSystemPrincipal\x12G\n" +
"\x10service_metadata\x18\x01 \x01(\v2\x17.google.protobuf.StructH\x00R\x0fserviceMetadata\x88\x01\x01B\x13\n" +
"\x11_service_metadata\x1a\xe3\x01\n" +
"\fIdpPrincipal\x125\n" +
"\fprincipal_id\x18\x01 \x01(\tB\x12\xbaH\x0f\xc8\x01\x01r\n" +
"\x10\x012\x06.*\\S.*R\vprincipalId\x12>\n" +
"\x0fprincipal_email\x18\x02 \x01(\tB\x15\xbaH\x12\xc8\x01\x01r\r\x10\x01\x18\xff\x012\x06.*\\S.*R\x0eprincipalEmail\x12G\n" +
"\x10service_metadata\x18\x03 \x01(\v2\x17.google.protobuf.StructH\x00R\x0fserviceMetadata\x88\x01\x01B\x13\n" +
"\x11_service_metadataB\x12\n" +
"\tauthority\x12\x05\xbaH\x02\b\x01*\x96\x02\n" +
"\vLogSeverity\x12\x1c\n" +
"\x18LOG_SEVERITY_UNSPECIFIED\x10\x00\x12\x18\n" +
"\x14LOG_SEVERITY_DEFAULT\x10d\x12\x17\n" +
"\x12LOG_SEVERITY_DEBUG\x10\xc8\x01\x12\x16\n" +
"\x11LOG_SEVERITY_INFO\x10\xac\x02\x12\x18\n" +
"\x13LOG_SEVERITY_NOTICE\x10\x90\x03\x12\x19\n" +
"\x14LOG_SEVERITY_WARNING\x10\xf4\x03\x12\x17\n" +
"\x12LOG_SEVERITY_ERROR\x10\xd8\x04\x12\x1a\n" +
"\x15LOG_SEVERITY_CRITICAL\x10\xbc\x05\x12\x17\n" +
"\x12LOG_SEVERITY_ALERT\x10\xa0\x06\x12\x1b\n" +
"\x16LOG_SEVERITY_EMERGENCY\x10\x84\aB1\n" +
"\x1ccom.schwarz.stackit.audit.v1P\x01Z\x0f./audit;auditV1b\x06proto3"
var file_audit_v1_audit_event_proto_rawDesc = []byte{
0x0a, 0x1a, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x64, 0x69, 0x74,
0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x61, 0x75,
0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69,
0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x22, 0xfd, 0x05, 0x0a, 0x0d, 0x41, 0x75, 0x64, 0x69, 0x74, 0x4c, 0x6f, 0x67, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x12, 0x78, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x5d, 0xba, 0x48, 0x5a, 0xc8, 0x01, 0x01, 0x72, 0x55,
0x32, 0x53, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x2d, 0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30,
0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x28, 0x3f, 0x3a, 0x61, 0x64,
0x6d, 0x69, 0x6e, 0x2d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x7c, 0x73, 0x79, 0x73,
0x74, 0x65, 0x6d, 0x2d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x7c, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79,
0x2d, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x7c, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x61, 0x63, 0x63,
0x65, 0x73, 0x73, 0x29, 0x24, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3f,
0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x4c, 0x6f, 0x67, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01,
0x01, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12,
0x4c, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x42, 0x2f, 0xba, 0x48, 0x2c, 0xc8, 0x01, 0x01, 0x72, 0x27, 0x32, 0x25, 0x5e, 0x5b,
0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b,
0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x2f, 0x5b, 0x30, 0x2d, 0x39,
0x5d, 0x2b, 0x24, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x49, 0x64, 0x12, 0x3b, 0x0a,
0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e,
0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x4c, 0x6f,
0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x36, 0x0a, 0x0e, 0x63, 0x6f,
0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01,
0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x48, 0x00,
0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x88,
0x01, 0x01, 0x12, 0x45, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18,
0x06, 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, 0x09,
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3e, 0x0a, 0x08, 0x73, 0x65, 0x76,
0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x61, 0x75,
0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69,
0x74, 0x79, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52,
0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x52, 0x0a, 0x0c, 0x74, 0x72, 0x61,
0x63, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x42,
0x2a, 0xba, 0x48, 0x27, 0x72, 0x25, 0x32, 0x23, 0x5e, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x2d,
0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x2d, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d,
0x39, 0x5d, 0x2b, 0x2d, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x24, 0x48, 0x01, 0x52, 0x0b, 0x74,
0x72, 0x61, 0x63, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a,
0x0b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01,
0x28, 0x09, 0x48, 0x02, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65,
0x88, 0x01, 0x01, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 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, 0x11,
0x0a, 0x0f, 0x5f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x65,
0x6e, 0x74, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61,
0x74, 0x65, 0x22, 0xab, 0x06, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x74, 0x4c, 0x6f, 0x67, 0x12,
0x2d, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10,
0x01, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x77,
0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x50, 0xba, 0x48, 0x4d, 0xc8, 0x01, 0x01, 0x72, 0x48,
0x10, 0x01, 0x18, 0xff, 0x01, 0x32, 0x41, 0x5e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x69, 0x74, 0x5c,
0x2e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x5c, 0x2e, 0x28, 0x3f, 0x3a,
0x76, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x5c, 0x2e, 0x29, 0x3f, 0x28, 0x3f, 0x3a, 0x5b, 0x61,
0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x2e, 0x5d, 0x2b, 0x5c, 0x2e, 0x29, 0x3f, 0x5b, 0x61, 0x2d,
0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x0d, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x63, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x3e,
0xba, 0x48, 0x3b, 0xc8, 0x01, 0x01, 0x72, 0x36, 0x10, 0x01, 0x18, 0xff, 0x01, 0x32, 0x2f, 0x5e,
0x5b, 0x61, 0x2d, 0x7a, 0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d,
0x2b, 0x28, 0x3f, 0x3a, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x2f,
0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x29, 0x2a, 0x24, 0x52, 0x0c,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x55, 0x0a, 0x13,
0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x75, 0x64, 0x69,
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52,
0x12, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49,
0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x1b, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f,
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x11, 0x61, 0x75,
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12,
0x4c, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x75, 0x64, 0x69,
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0f, 0x72, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a,
0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x07, 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, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x4f, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x06, 0xba, 0x48,
0x03, 0xc8, 0x01, 0x01, 0x52, 0x10, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x18, 0x09, 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, 0x01, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x88, 0x01, 0x01,
0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 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, 0x02, 0x52, 0x08, 0x6d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x72,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x22, 0xf3, 0x02, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 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, 0x6e,
0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x37, 0xba, 0x48,
0x34, 0x72, 0x32, 0x32, 0x30, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x2d, 0x5d, 0x2b, 0x2f, 0x5b, 0x61,
0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x2d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d,
0x39, 0x2d, 0x5d, 0x2b, 0x24, 0x48, 0x00, 0x52, 0x12, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x6d,
0x0a, 0x1f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66,
0x6f, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 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, 0x52,
0x1c, 0x73, 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, 0x42, 0x17, 0x0a,
0x15, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xf2, 0x01, 0x0a, 0x11, 0x41, 0x75, 0x74, 0x68, 0x6f,
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x55, 0x0a, 0x08,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x39,
0xba, 0x48, 0x36, 0xc8, 0x01, 0x01, 0x72, 0x31, 0x32, 0x2f, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x5d,
0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x28, 0x3f, 0x3a, 0x2f,
0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x2f, 0x5b, 0x61, 0x2d, 0x7a, 0x30,
0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x29, 0x2a, 0x24, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x27, 0xba, 0x48, 0x24, 0x72, 0x22, 0x32, 0x20,
0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x2d, 0x5d, 0x2b, 0x28, 0x3f, 0x3a, 0x5c, 0x2e, 0x5b, 0x61, 0x2d,
0x7a, 0x2d, 0x5d, 0x2b, 0x29, 0x2a, 0x5c, 0x2e, 0x5b, 0x61, 0x2d, 0x7a, 0x2d, 0x5d, 0x2b, 0x24,
0x48, 0x00, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01,
0x01, 0x12, 0x1d, 0x0a, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01,
0x28, 0x08, 0x48, 0x01, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 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 (
file_audit_v1_audit_event_proto_rawDescOnce sync.Once
file_audit_v1_audit_event_proto_rawDescData []byte
file_audit_v1_audit_event_proto_rawDescData = file_audit_v1_audit_event_proto_rawDesc
)
func file_audit_v1_audit_event_proto_rawDescGZIP() []byte {
file_audit_v1_audit_event_proto_rawDescOnce.Do(func() {
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)))
file_audit_v1_audit_event_proto_rawDescData = protoimpl.X.CompressGZIP(file_audit_v1_audit_event_proto_rawDescData)
})
return file_audit_v1_audit_event_proto_rawDescData
}
@ -1700,7 +1934,7 @@ func file_audit_v1_audit_event_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_audit_v1_audit_event_proto_rawDesc), len(file_audit_v1_audit_event_proto_rawDesc)),
RawDescriptor: file_audit_v1_audit_event_proto_rawDesc,
NumEnums: 2,
NumMessages: 16,
NumExtensions: 0,
@ -1712,6 +1946,7 @@ func file_audit_v1_audit_event_proto_init() {
MessageInfos: file_audit_v1_audit_event_proto_msgTypes,
}.Build()
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_depIdxs = nil
}

View file

@ -127,6 +127,14 @@ func (m *AuditLogEntry) validate(all bool) error {
// no validation rules for CorrelationId
}
if m.TraceParent != nil {
// no validation rules for TraceParent
}
if m.TraceState != nil {
// no validation rules for TraceState
}
if len(errors) > 0 {
return AuditLogEntryMultiError(errors)
}
@ -141,7 +149,7 @@ type AuditLogEntryMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m AuditLogEntryMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -466,7 +474,7 @@ type AuditLogMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m AuditLogMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -554,6 +562,8 @@ func (m *AuthenticationInfo) validate(all bool) error {
// no validation rules for PrincipalId
// no validation rules for PrincipalEmail
for idx, item := range m.GetServiceAccountDelegationInfo() {
_, _ = idx, item
@ -588,10 +598,6 @@ func (m *AuthenticationInfo) validate(all bool) error {
}
if m.PrincipalEmail != nil {
// no validation rules for PrincipalEmail
}
if m.ServiceAccountName != nil {
// no validation rules for ServiceAccountName
}
@ -610,7 +616,7 @@ type AuthenticationInfoMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m AuthenticationInfoMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -722,7 +728,7 @@ type AuthorizationInfoMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m AuthorizationInfoMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -824,7 +830,7 @@ type AttributeContextMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m AttributeContextMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -957,7 +963,7 @@ type RequestMetadataMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m RequestMetadataMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -1153,7 +1159,7 @@ type ResponseMetadataMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m ResponseMetadataMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -1340,7 +1346,7 @@ type ServiceAccountDelegationInfoMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m ServiceAccountDelegationInfoMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -1474,7 +1480,7 @@ type AttributeContext_AuthMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m AttributeContext_AuthMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -1654,7 +1660,7 @@ type AttributeContext_RequestMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m AttributeContext_RequestMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -1853,7 +1859,7 @@ type AttributeContext_ResponseMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m AttributeContext_ResponseMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -1991,7 +1997,7 @@ type ServiceAccountDelegationInfo_SystemPrincipalMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m ServiceAccountDelegationInfo_SystemPrincipalMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
@ -2135,7 +2141,7 @@ type ServiceAccountDelegationInfo_IdpPrincipalMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m ServiceAccountDelegationInfo_IdpPrincipalMultiError) Error() string {
msgs := make([]string, 0, len(m))
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}

View file

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc-gen-go v1.35.1
// protoc (unknown)
// source: audit/v1/routable_event.proto
@ -12,7 +12,6 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@ -77,7 +76,10 @@ func (Visibility) EnumDescriptor() ([]byte, []int) {
//
// For system events, the nil UUID must be used: 00000000-0000-0000-0000-000000000000.
type ObjectIdentifier struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Identifier of the respective entity (e.g. Identifier of an organization)
//
// Required: true
@ -85,9 +87,7 @@ type ObjectIdentifier struct {
// Entity data type relevant for routing - one of the list of supported object types.
//
// Required: true
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
}
func (x *ObjectIdentifier) Reset() {
@ -135,7 +135,10 @@ func (x *ObjectIdentifier) GetType() string {
}
type EncryptedData struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Encrypted serialized protobuf content (the actual audit event)
//
// Required: true
@ -151,9 +154,7 @@ type EncryptedData struct {
// Version of the encrypted key
//
// Required: true
KeyVersion int32 `protobuf:"varint,4,opt,name=key_version,json=keyVersion,proto3" json:"key_version,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
KeyVersion int32 `protobuf:"varint,4,opt,name=key_version,json=keyVersion,proto3" json:"key_version,omitempty"`
}
func (x *EncryptedData) Reset() {
@ -215,7 +216,10 @@ func (x *EncryptedData) GetKeyVersion() int32 {
}
type UnencryptedData struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Unencrypted serialized protobuf content (the actual audit event)
//
// Required: true
@ -223,9 +227,7 @@ type UnencryptedData struct {
// Name of the protobuf type
//
// Required: true
ProtobufType string `protobuf:"bytes,2,opt,name=protobuf_type,json=protobufType,proto3" json:"protobuf_type,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
ProtobufType string `protobuf:"bytes,2,opt,name=protobuf_type,json=protobufType,proto3" json:"protobuf_type,omitempty"`
}
func (x *UnencryptedData) Reset() {
@ -273,7 +275,10 @@ func (x *UnencryptedData) GetProtobufType() string {
}
type RoutableAuditEvent struct {
state protoimpl.MessageState `protogen:"open.v1"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Functional event name with pattern
//
// Format: stackit.<product>.<version>.<type-chain>.<operation>
@ -308,13 +313,11 @@ type RoutableAuditEvent struct {
//
// Required: true
//
// Types that are valid to be assigned to Data:
// Types that are assignable to Data:
//
// *RoutableAuditEvent_UnencryptedData
// *RoutableAuditEvent_EncryptedData
Data isRoutableAuditEvent_Data `protobuf_oneof:"data"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Data isRoutableAuditEvent_Data `protobuf_oneof:"data"`
}
func (x *RoutableAuditEvent) Reset() {
@ -368,27 +371,23 @@ func (x *RoutableAuditEvent) GetObjectIdentifier() *ObjectIdentifier {
return nil
}
func (x *RoutableAuditEvent) GetData() isRoutableAuditEvent_Data {
if x != nil {
return x.Data
func (m *RoutableAuditEvent) GetData() isRoutableAuditEvent_Data {
if m != nil {
return m.Data
}
return nil
}
func (x *RoutableAuditEvent) GetUnencryptedData() *UnencryptedData {
if x != nil {
if x, ok := x.Data.(*RoutableAuditEvent_UnencryptedData); ok {
return x.UnencryptedData
}
if x, ok := x.GetData().(*RoutableAuditEvent_UnencryptedData); ok {
return x.UnencryptedData
}
return nil
}
func (x *RoutableAuditEvent) GetEncryptedData() *EncryptedData {
if x != nil {
if x, ok := x.Data.(*RoutableAuditEvent_EncryptedData); ok {
return x.EncryptedData
}
if x, ok := x.GetData().(*RoutableAuditEvent_EncryptedData); ok {
return x.EncryptedData
}
return nil
}
@ -411,53 +410,83 @@ func (*RoutableAuditEvent_EncryptedData) isRoutableAuditEvent_Data() {}
var File_audit_v1_routable_event_proto protoreflect.FileDescriptor
const file_audit_v1_routable_event_proto_rawDesc = "" +
"\n" +
"\x1daudit/v1/routable_event.proto\x12\baudit.v1\x1a\x1bbuf/validate/validate.proto\"_\n" +
"\x10ObjectIdentifier\x12+\n" +
"\n" +
"identifier\x18\x01 \x01(\tB\v\xbaH\b\xc8\x01\x01r\x03\xb0\x01\x01R\n" +
"identifier\x12\x1e\n" +
"\x04type\x18\x02 \x01(\tB\n" +
"\xbaH\a\xc8\x01\x01r\x02\x10\x01R\x04type\"\xc5\x01\n" +
"\rEncryptedData\x12\x1e\n" +
"\x04data\x18\x01 \x01(\fB\n" +
"\xbaH\a\xc8\x01\x01z\x02\x10\x01R\x04data\x12/\n" +
"\rprotobuf_type\x18\x02 \x01(\tB\n" +
"\xbaH\a\xc8\x01\x01r\x02\x10\x01R\fprotobufType\x129\n" +
"\x12encrypted_password\x18\x03 \x01(\tB\n" +
"\xbaH\a\xc8\x01\x01r\x02\x10\x01R\x11encryptedPassword\x12(\n" +
"\vkey_version\x18\x04 \x01(\x05B\a\xbaH\x04\x1a\x02(\x01R\n" +
"keyVersion\"b\n" +
"\x0fUnencryptedData\x12\x1e\n" +
"\x04data\x18\x01 \x01(\fB\n" +
"\xbaH\a\xc8\x01\x01z\x02\x10\x01R\x04data\x12/\n" +
"\rprotobuf_type\x18\x02 \x01(\tB\n" +
"\xbaH\a\xc8\x01\x01r\x02\x10\x01R\fprotobufType\"\xb5\x03\n" +
"\x12RoutableAuditEvent\x12r\n" +
"\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" +
"\n" +
"visibility\x18\x02 \x01(\x0e2\x14.audit.v1.VisibilityB\v\xbaH\b\xc8\x01\x01\x82\x01\x02\x10\x01R\n" +
"visibility\x12O\n" +
"\x11object_identifier\x18\x03 \x01(\v2\x1a.audit.v1.ObjectIdentifierB\x06\xbaH\x03\xc8\x01\x01R\x10objectIdentifier\x12F\n" +
"\x10unencrypted_data\x18\x04 \x01(\v2\x19.audit.v1.UnencryptedDataH\x00R\x0funencryptedData\x12@\n" +
"\x0eencrypted_data\x18\x05 \x01(\v2\x17.audit.v1.EncryptedDataH\x00R\rencryptedDataB\r\n" +
"\x04data\x12\x05\xbaH\x02\b\x01*W\n" +
"\n" +
"Visibility\x12\x1a\n" +
"\x16VISIBILITY_UNSPECIFIED\x10\x00\x12\x15\n" +
"\x11VISIBILITY_PUBLIC\x10\x01\x12\x16\n" +
"\x12VISIBILITY_PRIVATE\x10\x02B1\n" +
"\x1ccom.schwarz.stackit.audit.v1P\x01Z\x0f./audit;auditV1b\x06proto3"
var file_audit_v1_routable_event_proto_rawDesc = []byte{
0x0a, 0x1d, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x61,
0x62, 0x6c, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x08, 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76,
0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5f, 0x0a, 0x10, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74,
0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x0a, 0x69, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b,
0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x0a, 0x69, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10,
0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xc5, 0x01, 0x0a, 0x0d, 0x45, 0x6e, 0x63, 0x72,
0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x04, 0x64, 0x61, 0x74,
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x7a,
0x02, 0x10, 0x01, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2f, 0x0a, 0x0d, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x54, 0x79, 0x70, 0x65, 0x12, 0x39, 0x0a, 0x12, 0x65, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02,
0x10, 0x01, 0x52, 0x11, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x50, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x28, 0x0a, 0x0b, 0x6b, 0x65, 0x79, 0x5f, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, 0x04, 0x1a,
0x02, 0x28, 0x01, 0x52, 0x0a, 0x6b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22,
0x62, 0x0a, 0x0f, 0x55, 0x6e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61,
0x74, 0x61, 0x12, 0x1e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x7a, 0x02, 0x10, 0x01, 0x52, 0x04, 0x64, 0x61,
0x74, 0x61, 0x12, 0x2f, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x5f, 0x74,
0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01,
0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x54,
0x79, 0x70, 0x65, 0x22, 0xb5, 0x03, 0x0a, 0x12, 0x52, 0x6f, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65,
0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x72, 0x0a, 0x0e, 0x6f, 0x70,
0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x42, 0x4b, 0xba, 0x48, 0x48, 0xc8, 0x01, 0x01, 0x72, 0x43, 0x32, 0x41, 0x5e, 0x73,
0x74, 0x61, 0x63, 0x6b, 0x69, 0x74, 0x5c, 0x2e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d,
0x5d, 0x2b, 0x5c, 0x2e, 0x28, 0x3f, 0x3a, 0x76, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x5c, 0x2e,
0x29, 0x3f, 0x28, 0x3f, 0x3a, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x2e, 0x5d, 0x2b,
0x5c, 0x2e, 0x29, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5d, 0x2b, 0x24, 0x52,
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 (
file_audit_v1_routable_event_proto_rawDescOnce sync.Once
file_audit_v1_routable_event_proto_rawDescData []byte
file_audit_v1_routable_event_proto_rawDescData = file_audit_v1_routable_event_proto_rawDesc
)
func file_audit_v1_routable_event_proto_rawDescGZIP() []byte {
file_audit_v1_routable_event_proto_rawDescOnce.Do(func() {
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)))
file_audit_v1_routable_event_proto_rawDescData = protoimpl.X.CompressGZIP(file_audit_v1_routable_event_proto_rawDescData)
})
return file_audit_v1_routable_event_proto_rawDescData
}
@ -496,7 +525,7 @@ func file_audit_v1_routable_event_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_audit_v1_routable_event_proto_rawDesc), len(file_audit_v1_routable_event_proto_rawDesc)),
RawDescriptor: file_audit_v1_routable_event_proto_rawDesc,
NumEnums: 1,
NumMessages: 4,
NumExtensions: 0,
@ -508,6 +537,7 @@ func file_audit_v1_routable_event_proto_init() {
MessageInfos: file_audit_v1_routable_event_proto_msgTypes,
}.Build()
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_depIdxs = nil
}

View file

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

113
go.mod
View file

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

322
go.sum
View file

@ -1,227 +1,223 @@
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.11-20251209175733-2a1774d88802.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
buf.build/go/protovalidate v1.1.0 h1:pQqEQRpOo4SqS60qkvmhLTTQU9JwzEvdyiqAtXa5SeY=
buf.build/go/protovalidate v1.1.0/go.mod h1:bGZcPiAQDC3ErCHK3t74jSoJDFOs2JH3d7LWuTEIdss=
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-amqp v1.5.1 h1:WyiPTz2C3zVvDL7RLAqwWdeoYhMtX62MZzQoP09fzsU=
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=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.1-20240920164238-5a7b106cbb87.1 h1:9wP6ZZYWnF2Z0TxmII7m3XNykxnP4/w8oXeth6ekcRI=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.1-20240920164238-5a7b106cbb87.1/go.mod h1:Duw/9JoXkXIydyASnLYIiufkzySThoqavOsF+IihqvM=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-amqp v1.2.0 h1:NNyfN3/cRszWzMvjmm64yaPZDHX/2DJkowv8Ub9y01I=
github.com/Azure/go-amqp v1.2.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
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/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/bufbuild/protovalidate-go v0.7.2 h1:UuvKyZHl5p7u3ztEjtRtqtDxOjRKX5VUOgKFq6p6ETk=
github.com/bufbuild/protovalidate-go v0.7.2/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao=
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4=
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/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
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/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
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/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.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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-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.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
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/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI=
github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc=
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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
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/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/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
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/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA=
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/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/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.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.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.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/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/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
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/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
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/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8=
github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
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/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw=
github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
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-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-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-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.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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38=
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=

View file

@ -1,233 +0,0 @@
package api
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/google/uuid"
"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"
)
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-/]+`)
func ValidateAndSerializePartially(
validator pkgAuditCommon.ProtobufValidator,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*auditV1.RoutableAuditEvent, error) {
// Check preconditions
err := validateAuditLogEntry(validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
// Serialize the AuditLogEntry and wrap it into a RoutableAuditEvent
routableEvent, err := newValidatedRoutableAuditEvent(validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
return routableEvent, nil
}
func newValidatedRoutableAuditEvent(
validator pkgAuditCommon.ProtobufValidator,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier) (*auditV1.RoutableAuditEvent, error) {
// Test serialization even if the data is dropped later when logging to the legacy solution
auditEventBytes, err := proto.Marshal(event)
if err != nil {
return nil, err
}
payload := auditV1.UnencryptedData{
Data: auditEventBytes,
ProtobufType: fmt.Sprintf("%v", event.ProtoReflect().Descriptor().FullName()),
}
routableEvent := auditV1.RoutableAuditEvent{
OperationName: event.ProtoPayload.OperationName,
ObjectIdentifier: routableIdentifier.ToObjectIdentifier(),
Visibility: visibility,
Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &payload},
}
err = validator.Validate(&routableEvent)
if err != nil {
return nil, err
}
return &routableEvent, nil
}
func validateAuditLogEntry(
validator pkgAuditCommon.ProtobufValidator,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error {
// Return error if the given event or object identifier is nil
if event == nil {
return pkgAuditCommon.ErrEventNil
}
if routableIdentifier == nil {
return pkgAuditCommon.ErrObjectIdentifierNil
}
// Validate the actual event
err := validator.Validate(event)
if err != nil {
return err
}
// Ensure that a valid object identifier is set if the event is public
if isSystemIdentifier(routableIdentifier) && visibility == auditV1.Visibility_VISIBILITY_PUBLIC {
return pkgAuditCommon.ErrObjectIdentifierVisibilityMismatch
}
// Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, pkgAuditCommon.ErrUnknownObjectType) {
return pkgAuditCommon.ErrUnsupportedRoutableType
}
return err
}
// Check identifier consistency across event attributes
if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeSystemEvent)) {
if routableIdentifier.Identifier != pkgAuditCommon.SystemIdentifier.Identifier || routableIdentifier.Type != pkgAuditCommon.ObjectTypeSystem {
return pkgAuditCommon.ErrInvalidRoutableIdentifierForSystemEvent
}
// The resource name can either contain the system identifier or another resource identifier
} else {
if err := areIdentifiersIdentical(routableIdentifier, event.LogName); err != nil {
return err
}
if err := areIdentifiersIdentical(routableIdentifier, event.ProtoPayload.ResourceName); err != nil {
return err
}
}
return nil
}
// Send implements AuditApi.Send
func Send(
topicNameResolver pkgAuditCommon.TopicNameResolver,
messagingApi pkgMessagingApi.Api,
ctx context.Context,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *pkgAuditCommon.CloudEvent,
) error {
// Check that given objects are not nil
if topicNameResolver == nil {
return pkgAuditCommon.ErrTopicNameResolverNil
}
if messagingApi == nil {
return pkgAuditCommon.ErrMessagingApiNil
}
if cloudEvent == nil {
return pkgAuditCommon.ErrCloudEventNil
}
if routableIdentifier == nil {
return pkgAuditCommon.ErrObjectIdentifierNil
}
// Check that provided identifier type is supported
if err := routableIdentifier.Type.IsSupportedType(); err != nil {
if errors.Is(err, pkgAuditCommon.ErrUnknownObjectType) {
return pkgAuditCommon.ErrUnsupportedRoutableType
}
return err
}
topic, err := topicNameResolver.Resolve(routableIdentifier)
if err != nil {
return err
}
// Naming according to AMQP protocol binding spec
// https://github.com/cloudevents/spec/blob/main/cloudevents/bindings/amqp-protocol-binding.md
applicationAttributes := make(map[string]any)
applicationAttributes["cloudEvents:specversion"] = cloudEvent.SpecVersion
applicationAttributes["cloudEvents:source"] = cloudEvent.Source
applicationAttributes["cloudEvents:id"] = cloudEvent.Id
applicationAttributes["cloudEvents:time"] = cloudEvent.Time.UnixMilli()
applicationAttributes["cloudEvents:datacontenttype"] = cloudEvent.DataContentType
applicationAttributes["cloudEvents:type"] = cloudEvent.DataType
applicationAttributes["cloudEvents:subject"] = cloudEvent.Subject
if cloudEvent.TraceParent != nil {
applicationAttributes["cloudEvents:traceparent"] = *cloudEvent.TraceParent
}
if cloudEvent.TraceState != nil {
applicationAttributes["cloudEvents:tracestate"] = *cloudEvent.TraceState
}
// Telemetry
applicationAttributes["cloudEvents:sdklanguage"] = "go"
auditGoVersion := internalTelemetry.AuditGoVersion
if auditGoVersion != "" {
applicationAttributes["cloudEvents:sdkversion"] = auditGoVersion
}
auditGoGrpcVersion := internalTelemetry.AuditGoGrpcVersion
if auditGoGrpcVersion != "" {
applicationAttributes["cloudEvents:sdkgrpcversion"] = auditGoGrpcVersion
}
auditGoHttpVersion := internalTelemetry.AuditGoHttpVersion
if auditGoHttpVersion != "" {
applicationAttributes["cloudEvents:sdkhttpversion"] = auditGoHttpVersion
}
return messagingApi.Send(
ctx,
topic,
cloudEvent.Data,
cloudEvent.DataContentType,
applicationAttributes)
}
func isSystemIdentifier(identifier *pkgAuditCommon.RoutableIdentifier) bool {
if identifier.Identifier == uuid.Nil.String() && identifier.Type == pkgAuditCommon.ObjectTypeSystem {
return true
}
return false
}
func areIdentifiersIdentical(routableIdentifier *pkgAuditCommon.RoutableIdentifier, logName string) error {
dataType, identifier := getTypeAndIdentifierFromString(logName)
objectType := pkgAuditCommon.ObjectTypeFromPluralString(dataType)
err := objectType.IsSupportedType()
if err != nil {
return err
}
return areTypeAndIdentifierIdentical(routableIdentifier, objectType, identifier)
}
func areTypeAndIdentifierIdentical(routableIdentifier *pkgAuditCommon.RoutableIdentifier, dataType pkgAuditCommon.ObjectType, identifier string) error {
if routableIdentifier.Identifier != identifier {
return pkgAuditCommon.ErrAttributeIdentifierInvalid
}
if routableIdentifier.Type != dataType {
return pkgAuditCommon.ErrAttributeTypeInvalid
}
return nil
}
func getTypeAndIdentifierFromString(input string) (string, string) {
parts := strings.Split(input, "/")
dataType := parts[0]
identifier := parts[1]
return dataType, identifier
}

View file

@ -1,477 +0,0 @@
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

@ -1,35 +0,0 @@
package api
import (
"context"
"go.opentelemetry.io/otel/propagation"
)
const traceParentHeader = "traceparent"
const traceStateHeader = "tracestate"
// TraceParentAndStateFromContext returns W3C conform trace parent and state from context
func TraceParentAndStateFromContext(ctx context.Context) (string, string) {
mapCarrier := propagation.MapCarrier{}
propagator := propagation.TraceContext{}
propagator.Inject(ctx, mapCarrier)
// Get trace parent from context w3c conform format
// Format: <version>-<trace-id>-<parent-id>-<trace-flags>
// Example: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
traceParent := mapCarrier[traceParentHeader]
traceState := mapCarrier[traceStateHeader]
return traceParent, traceState
}
// AddTraceParentAndStateToContext adds trace and state related information to the given context.
func AddTraceParentAndStateToContext(ctx context.Context, traceParent, traceState string) context.Context {
mapCarrier := propagation.MapCarrier{}
mapCarrier[traceParentHeader] = traceParent
mapCarrier[traceStateHeader] = traceState
propagator := propagation.TraceContext{}
return propagator.Extract(ctx, mapCarrier)
}

View file

@ -1,43 +0,0 @@
package api
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
func Test_AddTraceParentAndStateToContext(t *testing.T) {
expectedTraceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
expectedTraceState := "key1=value1,key2=value2"
ctx := AddTraceParentAndStateToContext(
context.Background(),
expectedTraceParent,
expectedTraceState)
span := trace.SpanFromContext(ctx)
assert.Equal(t, "00f067aa0ba902b7", span.SpanContext().SpanID().String())
assert.Equal(t, "4bf92f3577b34da6a3ce929d0e0e4736", span.SpanContext().TraceID().String())
}
func Test_TraceParentAndStateFromContext(t *testing.T) {
tracer := otel.Tracer("test")
expectedTraceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
expectedTraceState := "key1=value1,key2=value2"
ctx := AddTraceParentAndStateToContext(
context.Background(),
expectedTraceParent,
expectedTraceState)
ctx, span := tracer.Start(ctx, "test")
traceParent, traceState := TraceParentAndStateFromContext(ctx)
assert.Equal(t, expectedTraceParent, traceParent)
assert.Equal(t, expectedTraceState, traceState)
assert.Equal(t, "00f067aa0ba902b7", span.SpanContext().SpanID().String())
assert.Equal(t, "4bf92f3577b34da6a3ce929d0e0e4736", span.SpanContext().TraceID().String())
}

View file

@ -1,237 +0,0 @@
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

@ -1,231 +0,0 @@
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

@ -1,581 +0,0 @@
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

@ -1,309 +0,0 @@
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

@ -1,81 +0,0 @@
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

@ -1,187 +0,0 @@
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

@ -5,7 +5,7 @@ import (
"log/slog"
)
var AuditLogger Logger = &SlogLogger{logger: slog.Default()}
var AuditLogger Logger = SlogLogger{logger: slog.Default()}
type Logger interface {
Debug(msg string, err ...error)
@ -16,12 +16,11 @@ type Logger interface {
func wrapErr(err []error) error {
var e error
switch {
case len(err) == 0:
if len(err) == 0 {
e = nil
case len(err) == 1:
} else if len(err) == 1 {
e = err[0]
default:
} else {
e = errors.Join(err...)
}
return e

View file

@ -1,154 +0,0 @@
package api
import (
"context"
"errors"
"fmt"
"strings"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
)
const DataTypeLegacyAuditEventV1 = "audit.v1.LegacyAuditEvent"
// StaticTopicNameConfig provides topic name information required for the topic name resolution.
type StaticTopicNameConfig struct {
TopicName string
}
// LegacyAuditApi is an implementation of AuditApi to send events to the legacy audit log system.
//
// Note: The implementation will be deprecated and replaced with the "routableAuditApi" once the new audit log routing is implemented
type LegacyAuditApi struct {
messagingApi pkgMessagingApi.Api
topicNameResolver pkgAuditCommon.TopicNameResolver
tracer trace.Tracer
validator pkgAuditCommon.ProtobufValidator
}
// 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
func NewLegacyAuditApi(
messagingApi pkgMessagingApi.Api,
topicNameConfig StaticTopicNameConfig,
validator pkgAuditCommon.ProtobufValidator,
) (pkgAuditCommon.AuditApi, error) {
if messagingApi == nil {
return nil, pkgAuditCommon.ErrMessagingApiNil
}
// Topic resolver
if topicNameConfig.TopicName == "" {
return nil, errors.New("topic name is required")
}
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
var auditApi pkgAuditCommon.AuditApi = &LegacyAuditApi{
messagingApi: messagingApi,
topicNameResolver: topicNameResolver,
tracer: otel.Tracer("legacy-audit-api"),
validator: validator,
}
return auditApi, nil
}
// Log implements AuditApi.Log
func (a *LegacyAuditApi) Log(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error {
cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier)
if err != nil {
return err
}
return a.Send(ctx, routableIdentifier, cloudEvent)
}
// ValidateAndSerialize implements AuditApi.ValidateAndSerialize.
// It serializes the event into the byte representation of the legacy audit log system.
func (a *LegacyAuditApi) ValidateAndSerialize(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*pkgAuditCommon.CloudEvent, error) {
ctx, span := a.tracer.Start(ctx, "validate-and-serialize")
defer span.End()
routableEvent, err := internalAuditApi.ValidateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
// Reject event type data-access as the downstream services
// cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) {
return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
}
// Do nothing with the serialized data in the legacy solution
_, err = proto.Marshal(routableEvent)
if err != nil {
return nil, err
}
// Convert attributes
legacyBytes, err := internalAuditApi.ConvertAndSerializeIntoLegacyFormat(event, routableEvent)
if err != nil {
return nil, err
}
traceParent, traceState := internalAuditApi.TraceParentAndStateFromContext(ctx)
message := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName,
Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: pkgAuditCommon.ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1,
Subject: event.ProtoPayload.ResourceName,
Data: legacyBytes,
TraceParent: &traceParent,
TraceState: &traceState,
}
return &message, nil
}
// Send implements AuditApi.Send
func (a *LegacyAuditApi) Send(
ctx context.Context,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *pkgAuditCommon.CloudEvent,
) error {
if cloudEvent != nil && cloudEvent.TraceParent != nil && cloudEvent.TraceState != nil {
ctx = internalAuditApi.AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState)
}
ctx, span := a.tracer.Start(ctx, "send")
defer span.End()
return internalAuditApi.Send(a.topicNameResolver, a.messagingApi, ctx, routableIdentifier, cloudEvent)
}

View file

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

View file

@ -1,98 +0,0 @@
package api
import (
"context"
"fmt"
"strings"
"buf.build/go/protovalidate"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalApi "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"
)
// MockAuditApi is an implementation of AuditApi that does nothing and has no dependency to external systems.
type MockAuditApi struct {
tracer trace.Tracer
validator pkgAuditCommon.ProtobufValidator
}
func NewMockAuditApi() (pkgAuditCommon.AuditApi, error) {
validator, err := protovalidate.New()
if err != nil {
return nil, err
}
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
var auditApi pkgAuditCommon.AuditApi = &MockAuditApi{
tracer: otel.Tracer("mock-audit-api"),
validator: protobufValidator,
}
return auditApi, nil
}
// Log implements AuditApi.Log.
// Validates and serializes the event but doesn't send it.
func (a *MockAuditApi) Log(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error {
_, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier)
return err
}
// ValidateAndSerialize implements AuditApi.ValidateAndSerialize
func (a *MockAuditApi) ValidateAndSerialize(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*pkgAuditCommon.CloudEvent, error) {
ctx, span := a.tracer.Start(ctx, "validate-and-serialize")
defer span.End()
routableEvent, err := internalApi.ValidateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
// Reject event type data-access as the downstream services
// cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) {
return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
}
routableEventBytes, err := proto.Marshal(routableEvent)
if err != nil {
return nil, err
}
traceParent, traceState := internalApi.TraceParentAndStateFromContext(ctx)
message := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName,
Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: "application/cloudevents+protobuf",
DataType: fmt.Sprintf("%v", routableEvent.ProtoReflect().Descriptor().FullName()),
Subject: event.ProtoPayload.ResourceName,
Data: routableEventBytes,
TraceParent: &traceParent,
TraceState: &traceState,
}
return &message, nil
}
// Send implements AuditApi.Send
func (a *MockAuditApi) Send(context.Context, *pkgAuditCommon.RoutableIdentifier, *pkgAuditCommon.CloudEvent) error {
return nil
}

View file

@ -1,80 +0,0 @@
package api
import (
"context"
"strings"
"testing"
"buf.build/go/protovalidate"
"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) {
auditApi, err := NewMockAuditApi()
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
routableObjectIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)
// Test
t.Run("Log", func(t *testing.T) {
assert.Nil(t, auditApi.Log(
context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableObjectIdentifier))
})
t.Run("reject data access event", func(t *testing.T) {
orgEvent, objIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
orgEvent.LogName = strings.Replace(orgEvent.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
rtIdentifier := pkgAuditCommon.NewRoutableIdentifier(objIdentifier)
assert.ErrorIs(t, auditApi.Log(
context.Background(), orgEvent, auditV1.Visibility_VISIBILITY_PUBLIC, rtIdentifier),
pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
})
t.Run("ValidateAndSerialize", func(t *testing.T) {
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
cloudEvent, err := auditApi.ValidateAndSerialize(
context.Background(), event, visibility, routableObjectIdentifier)
assert.NoError(t, err)
validateRoutableEventPayload(
t, cloudEvent.Data, objectIdentifier, event, event.ProtoPayload.OperationName, visibility)
})
t.Run("ValidateAndSerialize event nil", func(t *testing.T) {
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
_, err := auditApi.ValidateAndSerialize(context.Background(), nil, visibility, routableObjectIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
})
t.Run("Send", func(t *testing.T) {
var cloudEvent = pkgAuditCommon.CloudEvent{}
assert.Nil(t, auditApi.Send(context.Background(), routableObjectIdentifier, &cloudEvent))
})
}

View file

@ -1,103 +0,0 @@
package api
import (
"context"
"testing"
"buf.build/go/protovalidate"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
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) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
t.Run("new format", func(t *testing.T) {
eventBuilder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", uuid.NewString(), "eu01")
cloudEvent, _, err := eventBuilder.
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "GET",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredObjectId(uuid.NewString()).
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.project.update").
WithRequiredRequestClientIp("0.0.0.0").
Build(context.Background(), eventBuilder.NextSequenceNumber())
assert.NoError(t, err)
assert.NoError(t, LogEvent(cloudEvent))
})
t.Run("legacy format", func(t *testing.T) {
objectId := uuid.NewString()
entry, err := NewAuditLogEntryBuilder().
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "GET",
Scheme: "https",
Proto: "HTTP/1.1",
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId(objectId).
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.project.update").
WithRequiredRequestClientIp("0.0.0.0").
WithRequiredServiceName("demo-service").
WithRequiredWorkerId(uuid.NewString()).
Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err)
validator, err := protovalidate.New()
assert.NoError(t, err)
var protoValidator pkgAuditCommon.ProtobufValidator = validator
routableIdentifier := pkgAuditCommon.RoutableIdentifier{
Identifier: objectId,
Type: pkgAuditCommon.ObjectTypeProject,
}
routableEvent, err := internalAuditApi.ValidateAndSerializePartially(protoValidator, entry, auditV1.Visibility_VISIBILITY_PUBLIC, &routableIdentifier)
assert.NoError(t, err)
legacyBytes, err := internalAuditApi.ConvertAndSerializeIntoLegacyFormat(entry, routableEvent)
assert.NoError(t, err)
cloudEvent := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0",
Source: entry.ProtoPayload.ServiceName,
Id: entry.InsertId,
Time: entry.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: pkgAuditCommon.ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1,
Subject: entry.ProtoPayload.ResourceName,
Data: legacyBytes,
TraceParent: nil,
TraceState: nil,
}
assert.NoError(t, LogEvent(&cloudEvent))
})
}

View file

@ -1,91 +0,0 @@
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
}

View file

@ -1,154 +0,0 @@
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,49 +0,0 @@
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")

View file

@ -1,59 +0,0 @@
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

@ -1,59 +0,0 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_DefaultSequenceNumberGenerator(t *testing.T) {
t.Run("next", func(t *testing.T) {
var sequenceGenerator = NewDefaultSequenceNumberGenerator()
assert.Equal(t, uint64(0), sequenceGenerator.Next())
})
t.Run("revert", func(t *testing.T) {
var sequenceGenerator = NewDefaultSequenceNumberGenerator()
assert.Equal(t, uint64(0), sequenceGenerator.Next())
assert.Equal(t, uint64(1), sequenceGenerator.Next())
sequenceGenerator.Revert(uint64(1))
assert.Equal(t, uint64(1), sequenceGenerator.Next())
})
t.Run("revert first", func(t *testing.T) {
var sequenceGenerator = NewDefaultSequenceNumberGenerator()
assert.Equal(t, uint64(0), sequenceGenerator.Next())
assert.Equal(t, uint64(1), sequenceGenerator.Next())
sequenceGenerator.Revert(uint64(0))
assert.Equal(t, uint64(0), sequenceGenerator.Next())
})
t.Run("revert same value multiple times", func(t *testing.T) {
var sequenceGenerator = NewDefaultSequenceNumberGenerator()
assert.Equal(t, uint64(0), sequenceGenerator.Next())
assert.Equal(t, uint64(1), sequenceGenerator.Next())
assert.Equal(t, uint64(2), sequenceGenerator.Next())
sequenceGenerator.Revert(uint64(1))
sequenceGenerator.Revert(uint64(1))
assert.Equal(t, uint64(1), sequenceGenerator.Next())
assert.Equal(t, uint64(3), sequenceGenerator.Next())
})
t.Run("get and revert multiple", func(t *testing.T) {
var sequenceGenerator = NewDefaultSequenceNumberGenerator()
assert.Equal(t, uint64(0), sequenceGenerator.Next())
assert.Equal(t, uint64(1), sequenceGenerator.Next())
sequenceGenerator.Revert(uint64(1))
assert.Equal(t, uint64(1), sequenceGenerator.Next())
assert.Equal(t, uint64(2), sequenceGenerator.Next())
assert.Equal(t, uint64(3), sequenceGenerator.Next())
assert.Equal(t, uint64(4), sequenceGenerator.Next())
sequenceGenerator.Revert(uint64(2))
sequenceGenerator.Revert(uint64(3))
assert.Equal(t, uint64(2), sequenceGenerator.Next())
assert.Equal(t, uint64(3), sequenceGenerator.Next())
assert.Equal(t, uint64(5), sequenceGenerator.Next())
assert.Equal(t, uint64(6), sequenceGenerator.Next())
})
}

View file

@ -1,148 +0,0 @@
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

@ -1,463 +0,0 @@
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

@ -1,28 +0,0 @@
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

@ -1,12 +0,0 @@
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

@ -48,7 +48,6 @@ message AuditLogEntry {
//
// Examples:
// "1721899117/eu01/319a7fb9-edd2-46c6-953a-a724bb377c61/8792726390909855142"
// "1721899117/eu01-m/319a7fb9-edd2-46c6-953a-a724bb377c61/8792726390909855142"
//
// Required: true
string insert_id = 3 [
@ -85,6 +84,28 @@ message AuditLogEntry {
(buf.validate.field).required = true,
(buf.validate.field).enum.defined_only = true
];
// Customer set W3C conform trace parent header:
// https://www.w3.org/TR/trace-context/#traceparent-header
//
// Format: <version>-<trace-id>-<parent-id>-<trace-flags>
//
// Examples:
// "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
//
// Required: false
optional string trace_parent = 8 [(buf.validate.field).string.pattern = "^[0-9]+-[a-z0-9]+-[a-z0-9]+-[0-9]+$"];
// Customer set W3C conform trace state header:
// https://www.w3.org/TR/trace-context/#tracestate-header
//
// Format: <key1>=<value1>[,<keyN>=<valueN>]
//
// Examples:
// "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
//
// Required: false
optional string trace_state = 9;
}
// The severity of the event described in a log entry, expressed as one of the
@ -92,32 +113,32 @@ message AuditLogEntry {
enum LogSeverity {
LOG_SEVERITY_UNSPECIFIED = 0;
// The log entry has no assigned severity level.
// (1) The log entry has no assigned severity level.
LOG_SEVERITY_DEFAULT = 100;
// Debug or trace information.
// (100) Debug or trace information.
LOG_SEVERITY_DEBUG = 200;
// Routine information, such as ongoing status or performance.
// (200) Routine information, such as ongoing status or performance.
LOG_SEVERITY_INFO = 300;
// Normal but significant events, such as start up, shut down, or
// (300) Normal but significant events, such as start up, shut down, or
// a configuration change.
LOG_SEVERITY_NOTICE = 400;
// Warning events might cause problems.
// (400) Warning events might cause problems.
LOG_SEVERITY_WARNING = 500;
// Error events are likely to cause problems.
// (500) Error events are likely to cause problems.
LOG_SEVERITY_ERROR = 600;
// Critical events cause more severe problems or outages.
// (600) Critical events cause more severe problems or outages.
LOG_SEVERITY_CRITICAL = 700;
// A person must take an action immediately.
// (700) A person must take an action immediately.
LOG_SEVERITY_ALERT = 800;
// One or more systems are unusable.
// (800) One or more systems are unusable.
LOG_SEVERITY_EMERGENCY = 900;
}
@ -131,8 +152,7 @@ message AuditLog {
// Required: true
string service_name = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
(buf.validate.field).string.min_len = 1
];
// The name of the service method or operation.
@ -173,7 +193,6 @@ message AuditLog {
// "projects/7046e7b6-5ae9-441c-99fe-2cd28a5078ec/locations/_/instances/instance-20240723-174217"
// "projects/7046e7b6-5ae9-441c-99fe-2cd28a5078ec/locations/sx-stoi01/instances/instance-20240723-174217"
// "projects/dd7d1807-54e9-4426-8994-721758b5b554/locations/eu01/vms/b6851b4e-7a9d-4973-ab0f-a80a13ee3060/ports/78f8bad4-a291-4fa3-b07f-4a1985d3dbe8"
// "projects/dd7d1807-54e9-4426-8994-721758b5b554/locations/eu01-m/vms/b6851b4e-7a9d-4973-ab0f-a80a13ee3060/ports/78f8bad4-a291-4fa3-b07f-4a1985d3dbe8"
//
// Required: true
string resource_name = 3 [
@ -233,18 +252,17 @@ message AuthenticationInfo {
// Required: true
string principal_id = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
(buf.validate.field).string.min_len = 1
];
// The email address of the authenticated user.
// Service accounts have email addresses that can be used.
//
// Required: false
optional string principal_email = 2 [
(buf.validate.field).string.min_len = 5,
(buf.validate.field).string.max_len = 255,
(buf.validate.field).string.email = true
// Required: true
string principal_email = 2 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 255
];
// The name of the service account used to create or exchange
@ -327,7 +345,7 @@ message AttributeContext {
// Required: true
string principal = 1 [
(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
@ -416,8 +434,7 @@ message AttributeContext {
string path = 4 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 255,
(buf.validate.field).string.pattern = ".*\\S.*"
(buf.validate.field).string.max_len = 255
];
// The HTTP request `Host` header value.
@ -425,8 +442,7 @@ message AttributeContext {
// Required: true
string host = 5 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
(buf.validate.field).string.min_len = 1
];
// The URL scheme, such as `http`, `https` or `gRPC`.
@ -434,8 +450,7 @@ message AttributeContext {
// Required: true
string scheme = 6 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
(buf.validate.field).string.min_len = 1
];
// The HTTP URL query in the format of "name1=value1&name2=value2", as it
@ -462,8 +477,7 @@ message AttributeContext {
// Required: true
string protocol = 9 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
(buf.validate.field).string.min_len = 1
];
// The request authentication.
@ -527,8 +541,7 @@ message RequestMetadata {
string caller_supplied_user_agent = 2 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 255,
(buf.validate.field).string.pattern = ".*\\S.*"
(buf.validate.field).string.max_len = 255
];
// This field contains request attributes like request url, time, etc.
@ -584,8 +597,7 @@ message ServiceAccountDelegationInfo {
// Required: true
string principal_id = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.pattern = ".*\\S.*"
(buf.validate.field).string.min_len = 1
];
// The email address of the authenticated user.
@ -595,8 +607,7 @@ message ServiceAccountDelegationInfo {
string principal_email = 2 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 255,
(buf.validate.field).string.pattern = ".*\\S.*"
(buf.validate.field).string.max_len = 255
];
// Metadata about the service that uses the service account.

View file

@ -1,13 +0,0 @@
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