Merged PR 779949: feat: Refactor module structure to reflect best practices

Security-concept-update-needed: false.

JIRA Work Item: STACKITALO-259
This commit is contained in:
Christian Schaible (EXT) 2025-05-19 11:54:00 +00:00
parent 56b04b94cb
commit 85aae1c2e7
53 changed files with 1713 additions and 1739 deletions

View file

@ -205,21 +205,19 @@ linters:
- legacy - legacy
- std-error-handling - std-error-handling
rules: rules:
- path: audit/api/api_common.go - path: internal/audit/api/api_common.go
text: context-as-argument text: context-as-argument
- linters: - linters:
- gochecknoglobals - gochecknoglobals
path: audit/api/api.go|log/log.go|audit/api/model.go|telemetry/telemetry.go path: pkg/audit/common/api.go|pkg/log/log.go|internal/telemetry/telemetry.go
- linters: - linters:
- dupl - dupl
path: audit/api/api_.*.go path: pkg/audit/api/api_.*.go
- path: audit/api/model.go - path: internal/audit/api/model_test.go|internal/audit/api/model.go
text: 'exported: type name will be used as api.ApiRequest by other packages'
- path: audit/api/model_test.go|audit/api/model.go
text: G115 text: G115
- linters: - linters:
- gosec - gosec
path: audit/api/test_data.go path: internal/audit/api/test_data.go
- linters: - linters:
- dogsled - dogsled
- dupl - dupl
@ -268,7 +266,7 @@ linters:
- unparam - unparam
- wastedassign - wastedassign
- wsl - wsl
path: test_.*\.go|audit/messaging/solace.go path: test_.*\.go|pkg/messaging/test/solace.go
paths: paths:
- third_party$ - third_party$
- builtin$ - builtin$

28
go.mod
View file

@ -3,23 +3,23 @@ module dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git
go 1.23.4 go 1.23.4
require ( require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1 buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1
buf.build/go/protovalidate v0.12.0
github.com/Azure/go-amqp v1.4.0 github.com/Azure/go-amqp v1.4.0
github.com/bufbuild/protovalidate-go v0.9.3
github.com/docker/docker v28.1.1+incompatible github.com/docker/docker v28.1.1+incompatible
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lestrrat-go/jwx/v2 v2.1.5 github.com/lestrrat-go/jwx/v2 v2.1.6
github.com/rs/zerolog v1.34.0 github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/testcontainers/testcontainers-go v0.36.0 github.com/testcontainers/testcontainers-go v0.37.0
go.opentelemetry.io/otel v1.35.0 go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/trace v1.35.0 go.opentelemetry.io/otel/trace v1.35.0
google.golang.org/protobuf v1.36.6 google.golang.org/protobuf v1.36.6
) )
require ( require (
cel.dev/expr v0.23.1 // indirect cel.dev/expr v0.24.0 // indirect
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
@ -32,7 +32,7 @@ require (
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.8.2 // indirect github.com/ebitengine/purego v0.8.3 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
@ -66,7 +66,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.3 // indirect github.com/shirou/gopsutil/v4 v4.25.4 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
@ -77,12 +77,12 @@ require (
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect
golang.org/x/crypto v0.37.0 // indirect golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/time v0.11.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

62
go.sum
View file

@ -1,9 +1,11 @@
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1 h1:zgJPqo17m28+Lf5BW4xv3PvU20BnrmTcGYrog22lLIU= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 h1:YhMSc48s25kr7kv31Z8vf7sPUIq5YJva9z1mn/hAt0M=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= buf.build/go/protovalidate v0.12.0 h1:4GKJotbspQjRCcqZMGVSuC8SjwZ/FmgtSuKDpKUTZew=
cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
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 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-amqp v1.4.0 h1:Xj3caqi4comOF/L1Uc5iuBxR/pB6KumejC01YQOqOR4= github.com/Azure/go-amqp v1.4.0 h1:Xj3caqi4comOF/L1Uc5iuBxR/pB6KumejC01YQOqOR4=
@ -14,8 +16,6 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= 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/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/bufbuild/protovalidate-go v0.9.3 h1:XvdtwQuppS3wjzGfpOirsqwN5ExH2+PiIuA/XZd3MTM=
github.com/bufbuild/protovalidate-go v0.9.3/go.mod h1:2lUDP6fNd3wxznRNH3Nj64VB07+PySeslamkerwP6tE=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 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/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
@ -40,10 +40,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 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 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@ -85,8 +83,8 @@ github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCG
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.1.5 h1:PQI5gzadLfJ22ckLrejPVX6eGXKM4M4eGi5fW2jjA3o= github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA=
github.com/lestrrat-go/jwx/v2 v2.1.5/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
@ -135,8 +133,8 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw=
github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
@ -153,8 +151,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.36.0 h1:YpffyLuHtdp5EUsI5mT4sRw8GZhO/5ozyDT1xWGXt00= github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg=
github.com/testcontainers/testcontainers-go v0.36.0/go.mod h1:yk73GVJ0KUZIHUtFna6MO7QS144qYpoY8lEEtU9Hed0= github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
@ -186,10 +184,10 @@ go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -212,14 +210,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -230,10 +228,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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-20250422160041-2d3770c4ea7f h1:tjZsroqekhC63+WMqzmWyW5Twj/ZfR5HAlpd5YQ1Vs0= google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 h1:WvBuA5rjZx9SNIzgcU53OohgZy6lKSus++uY4xLaWKc=
google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ= google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:W3S/3np0/dPWsWLi1h/UymYctGXaGBM2StwzD0y140U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f h1:N/PrbTw4kdkqNRzVfWPrBekzLuarFREcbFOiOLkXon4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=

View file

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

View file

@ -8,14 +8,15 @@ import (
"testing" "testing"
"time" "time"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging" "buf.build/go/protovalidate"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/bufbuild/protovalidate-go"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
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 { type MessagingApiMock struct {
@ -38,32 +39,18 @@ func (m *MessagingApiMock) Close(_ context.Context) error {
return nil return nil
} }
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)
}
type TopicNameResolverMock struct { type TopicNameResolverMock struct {
mock.Mock mock.Mock
} }
func (m *TopicNameResolverMock) Resolve(routableIdentifier *RoutableIdentifier) (string, error) { func (m *TopicNameResolverMock) Resolve(routableIdentifier *pkgAuditCommon.RoutableIdentifier) (string, error) {
args := m.Called(routableIdentifier) args := m.Called(routableIdentifier)
return args.String(0), args.Error(1) return args.String(0), args.Error(1)
} }
func NewValidator(t *testing.T) ProtobufValidator { func NewValidator(t *testing.T) pkgAuditCommon.ProtobufValidator {
validator, err := protovalidate.New() validator, err := protovalidate.New()
var protoValidator ProtobufValidator = validator var protoValidator pkgAuditCommon.ProtobufValidator = validator
assert.NoError(t, err) assert.NoError(t, err)
return protoValidator return protoValidator
@ -72,20 +59,20 @@ func NewValidator(t *testing.T) ProtobufValidator {
func Test_ValidateAndSerializePartially_EventNil(t *testing.T) { func Test_ValidateAndSerializePartially_EventNil(t *testing.T) {
validator := NewValidator(t) validator := NewValidator(t)
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, nil, auditV1.Visibility_VISIBILITY_PUBLIC, nil) validator, nil, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
assert.ErrorIs(t, err, ErrEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
} }
func Test_ValidateAndSerializePartially_AuditEventValidationFailed(t *testing.T) { func Test_ValidateAndSerializePartially_AuditEventValidationFailed(t *testing.T) {
validator := NewValidator(t) validator := NewValidator(t)
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := NewOrganizationAuditEvent(nil)
event.LogName = "" event.LogName = ""
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.EqualError(t, err, "validation error:\n - log_name: value is required [required]") assert.EqualError(t, err, "validation error:\n - log_name: value is required [required]")
} }
@ -93,8 +80,8 @@ func Test_ValidateAndSerializePartially_AuditEventValidationFailed(t *testing.T)
func Test_ValidateAndSerializePartially_RoutableEventValidationFailed(t *testing.T) { func Test_ValidateAndSerializePartially_RoutableEventValidationFailed(t *testing.T) {
validator := NewValidator(t) validator := NewValidator(t)
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := NewOrganizationAuditEvent(nil)
_, err := validateAndSerializePartially(validator, event, 3, NewRoutableIdentifier(objectIdentifier)) _, err := ValidateAndSerializePartially(validator, event, 3, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.EqualError(t, err, "validation error:\n - visibility: value must be one of the defined enum values [enum.defined_only]") assert.EqualError(t, err, "validation error:\n - visibility: value must be one of the defined enum values [enum.defined_only]")
} }
@ -102,47 +89,47 @@ func Test_ValidateAndSerializePartially_RoutableEventValidationFailed(t *testing
func Test_ValidateAndSerializePartially_CheckVisibility_Event(t *testing.T) { func Test_ValidateAndSerializePartially_CheckVisibility_Event(t *testing.T) {
validator := NewValidator(t) validator := NewValidator(t)
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := NewOrganizationAuditEvent(nil)
t.Run("Visibility public - object identifier nil", func(t *testing.T) { t.Run("Visibility public - object identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
}) })
t.Run("Visibility private - object identifier nil", func(t *testing.T) { t.Run("Visibility private - object identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil) validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
}) })
t.Run("Visibility public - object identifier system", func(t *testing.T) { t.Run("Visibility public - object identifier system", func(t *testing.T) {
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrObjectIdentifierVisibilityMismatch) assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierVisibilityMismatch)
}) })
t.Run("Visibility public - object identifier set", func(t *testing.T) { t.Run("Visibility public - object identifier set", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially( routableEvent, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, routableEvent) assert.NotNil(t, routableEvent)
}) })
t.Run("Visibility private - object identifier system", func(t *testing.T) { t.Run("Visibility private - object identifier system", func(t *testing.T) {
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, RoutableSystemIdentifier) validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid) assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeIdentifierInvalid)
}) })
t.Run("Visibility private - object identifier set", func(t *testing.T) { t.Run("Visibility private - object identifier set", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially( routableEvent, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, NewRoutableIdentifier(objectIdentifier)) validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, routableEvent) assert.NotNil(t, routableEvent)
@ -152,130 +139,130 @@ func Test_ValidateAndSerializePartially_CheckVisibility_Event(t *testing.T) {
func Test_ValidateAndSerializePartially_CheckVisibility_SystemEvent(t *testing.T) { func Test_ValidateAndSerializePartially_CheckVisibility_SystemEvent(t *testing.T) {
validator := NewValidator(t) validator := NewValidator(t)
event := newSystemAuditEvent(nil) event := NewSystemAuditEvent(nil)
t.Run("Visibility public - object identifier nil", func(t *testing.T) { t.Run("Visibility public - object identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
}) })
t.Run("Visibility private - object identifier nil", func(t *testing.T) { t.Run("Visibility private - object identifier nil", func(t *testing.T) {
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil) validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil)
assert.ErrorIs(t, err, ErrObjectIdentifierNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
}) })
t.Run("Visibility public - object identifier system", func(t *testing.T) { t.Run("Visibility public - object identifier system", func(t *testing.T) {
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrObjectIdentifierVisibilityMismatch) assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierVisibilityMismatch)
}) })
t.Run("Visibility public - object identifier set", func(t *testing.T) { t.Run("Visibility public - object identifier set", func(t *testing.T) {
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier( validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(ObjectTypeOrganization)})) &auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(pkgAuditCommon.ObjectTypeOrganization)}))
assert.ErrorIs(t, err, ErrInvalidRoutableIdentifierForSystemEvent) assert.ErrorIs(t, err, pkgAuditCommon.ErrInvalidRoutableIdentifierForSystemEvent)
}) })
t.Run("Visibility private - object identifier system", func(t *testing.T) { t.Run("Visibility private - object identifier system", func(t *testing.T) {
routableEvent, err := validateAndSerializePartially( routableEvent, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, RoutableSystemIdentifier) validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.RoutableSystemIdentifier)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, routableEvent) assert.NotNil(t, routableEvent)
}) })
t.Run("Visibility private - object identifier set", func(t *testing.T) { t.Run("Visibility private - object identifier set", func(t *testing.T) {
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, NewRoutableIdentifier( validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.NewRoutableIdentifier(
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(ObjectTypeOrganization)})) &auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(pkgAuditCommon.ObjectTypeOrganization)}))
assert.ErrorIs(t, err, ErrInvalidRoutableIdentifierForSystemEvent) assert.ErrorIs(t, err, pkgAuditCommon.ErrInvalidRoutableIdentifierForSystemEvent)
}) })
} }
func Test_ValidateAndSerializePartially_UnsupportedIdentifierType(t *testing.T) { func Test_ValidateAndSerializePartially_UnsupportedIdentifierType(t *testing.T) {
validator := NewValidator(t) validator := NewValidator(t)
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := NewFolderAuditEvent(nil)
objectIdentifier.Type = "invalid" objectIdentifier.Type = "invalid"
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType) assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
} }
func Test_ValidateAndSerializePartially_LogNameIdentifierMismatch(t *testing.T) { func Test_ValidateAndSerializePartially_LogNameIdentifierMismatch(t *testing.T) {
validator := NewValidator(t) validator := NewValidator(t)
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := NewFolderAuditEvent(nil)
parts := strings.Split(event.LogName, "/") parts := strings.Split(event.LogName, "/")
identifier := parts[1] identifier := parts[1]
t.Run("LogName type mismatch", func(t *testing.T) { t.Run("LogName type mismatch", func(t *testing.T) {
event.LogName = fmt.Sprintf("projects/%s/logs/admin-activity", identifier) event.LogName = fmt.Sprintf("projects/%s/logs/admin-activity", identifier)
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrAttributeTypeInvalid) assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeTypeInvalid)
}) })
t.Run("LogName identifier mismatch", func(t *testing.T) { t.Run("LogName identifier mismatch", func(t *testing.T) {
event.LogName = fmt.Sprintf("folders/%s/logs/admin-activity", uuid.NewString()) event.LogName = fmt.Sprintf("folders/%s/logs/admin-activity", uuid.NewString())
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid) assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeIdentifierInvalid)
}) })
} }
func Test_ValidateAndSerializePartially_ResourceNameIdentifierMismatch(t *testing.T) { func Test_ValidateAndSerializePartially_ResourceNameIdentifierMismatch(t *testing.T) {
validator := NewValidator(t) validator := NewValidator(t)
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := NewFolderAuditEvent(nil)
parts := strings.Split(event.ProtoPayload.ResourceName, "/") parts := strings.Split(event.ProtoPayload.ResourceName, "/")
identifier := parts[1] identifier := parts[1]
t.Run("ResourceName type mismatch", func(t *testing.T) { t.Run("ResourceName type mismatch", func(t *testing.T) {
event.ProtoPayload.ResourceName = fmt.Sprintf("projects/%s", identifier) event.ProtoPayload.ResourceName = fmt.Sprintf("projects/%s", identifier)
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrAttributeTypeInvalid) assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeTypeInvalid)
}) })
t.Run("ResourceName identifier mismatch", func(t *testing.T) { t.Run("ResourceName identifier mismatch", func(t *testing.T) {
event.ProtoPayload.ResourceName = fmt.Sprintf("folders/%s", uuid.NewString()) event.ProtoPayload.ResourceName = fmt.Sprintf("folders/%s", uuid.NewString())
_, err := validateAndSerializePartially( _, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid) assert.ErrorIs(t, err, pkgAuditCommon.ErrAttributeIdentifierInvalid)
}) })
} }
func Test_ValidateAndSerializePartially_SystemEvent(t *testing.T) { func Test_ValidateAndSerializePartially_SystemEvent(t *testing.T) {
validator := NewValidator(t) validator := NewValidator(t)
event := newSystemAuditEvent(nil) event := NewSystemAuditEvent(nil)
routableEvent, err := validateAndSerializePartially( routableEvent, err := ValidateAndSerializePartially(
validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, RoutableSystemIdentifier) validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, pkgAuditCommon.RoutableSystemIdentifier)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, event.LogName, fmt.Sprintf("system/%s/logs/%s", SystemIdentifier.Identifier, EventTypeSystemEvent)) assert.Equal(t, event.LogName, fmt.Sprintf("system/%s/logs/%s", pkgAuditCommon.SystemIdentifier.Identifier, pkgAuditCommon.EventTypeSystemEvent))
assert.True(t, proto.Equal(routableEvent.ObjectIdentifier, SystemIdentifier)) assert.True(t, proto.Equal(routableEvent.ObjectIdentifier, pkgAuditCommon.SystemIdentifier))
} }
func Test_Send_TopicNameResolverNil(t *testing.T) { func Test_Send_TopicNameResolverNil(t *testing.T) {
err := send(nil, nil, context.Background(), nil, nil) err := Send(nil, nil, context.Background(), nil, nil)
assert.ErrorIs(t, err, ErrTopicNameResolverNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrTopicNameResolverNil)
} }
func Test_Send_TopicNameResolutionError(t *testing.T) { func Test_Send_TopicNameResolutionError(t *testing.T) {
@ -283,86 +270,86 @@ func Test_Send_TopicNameResolutionError(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{} topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", expectedError) topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", expectedError)
var topicNameResolver TopicNameResolver = &topicNameResolverMock var topicNameResolver pkgAuditCommon.TopicNameResolver = &topicNameResolverMock
var cloudEvent = CloudEvent{} var cloudEvent = pkgAuditCommon.CloudEvent{}
var messagingApi messaging.Api = &messaging.AmqpApi{} var messagingApi pkgMessagingApi.Api = &pkgMessagingApi.AmqpApi{}
err := send(topicNameResolver, messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent) err := Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.RoutableSystemIdentifier, &cloudEvent)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
func Test_Send_MessagingApiNil(t *testing.T) { func Test_Send_MessagingApiNil(t *testing.T) {
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"} var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: "test"}
err := send(topicNameResolver, nil, context.Background(), nil, nil) err := Send(topicNameResolver, nil, context.Background(), nil, nil)
assert.ErrorIs(t, err, ErrMessagingApiNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrMessagingApiNil)
} }
func Test_Send_CloudEventNil(t *testing.T) { func Test_Send_CloudEventNil(t *testing.T) {
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"} var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: "test"}
var messagingApi messaging.Api = &messaging.AmqpApi{} var messagingApi pkgMessagingApi.Api = &pkgMessagingApi.AmqpApi{}
err := send(topicNameResolver, messagingApi, context.Background(), nil, nil) err := Send(topicNameResolver, messagingApi, context.Background(), nil, nil)
assert.ErrorIs(t, err, ErrCloudEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrCloudEventNil)
} }
func Test_Send_ObjectIdentifierNil(t *testing.T) { func Test_Send_ObjectIdentifierNil(t *testing.T) {
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"} var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: "test"}
var messagingApi messaging.Api = &messaging.AmqpApi{} var messagingApi pkgMessagingApi.Api = &pkgMessagingApi.AmqpApi{}
var cloudEvent = CloudEvent{} var cloudEvent = pkgAuditCommon.CloudEvent{}
err := send(topicNameResolver, messagingApi, context.Background(), nil, &cloudEvent) err := Send(topicNameResolver, messagingApi, context.Background(), nil, &cloudEvent)
assert.ErrorIs(t, err, ErrObjectIdentifierNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrObjectIdentifierNil)
} }
func Test_Send_UnsupportedObjectIdentifierType(t *testing.T) { func Test_Send_UnsupportedObjectIdentifierType(t *testing.T) {
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"} var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: "test"}
var messagingApi messaging.Api = &messaging.AmqpApi{} var messagingApi pkgMessagingApi.Api = &pkgMessagingApi.AmqpApi{}
var cloudEvent = CloudEvent{} var cloudEvent = pkgAuditCommon.CloudEvent{}
var objectIdentifier = auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: "unsupported"} var objectIdentifier = auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: "unsupported"}
err := send(topicNameResolver, messagingApi, context.Background(), NewRoutableIdentifier(&objectIdentifier), &cloudEvent) err := Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.NewRoutableIdentifier(&objectIdentifier), &cloudEvent)
assert.ErrorIs(t, err, ErrUnsupportedRoutableType) assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
} }
func Test_Send(t *testing.T) { func Test_Send(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{} topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil) topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
var topicNameResolver TopicNameResolver = &topicNameResolverMock var topicNameResolver pkgAuditCommon.TopicNameResolver = &topicNameResolverMock
messagingApiMock := MessagingApiMock{} messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi messaging.Api = &messagingApiMock var messagingApi pkgMessagingApi.Api = &messagingApiMock
var cloudEvent = CloudEvent{} var cloudEvent = pkgAuditCommon.CloudEvent{}
assert.NoError(t, send(topicNameResolver, messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent)) assert.NoError(t, Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.RoutableSystemIdentifier, &cloudEvent))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1)) assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
} }
func Test_SendAllHeadersSet(t *testing.T) { func Test_SendAllHeadersSet(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{} topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil) topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
var topicNameResolver TopicNameResolver = &topicNameResolverMock var topicNameResolver pkgAuditCommon.TopicNameResolver = &topicNameResolverMock
messagingApiMock := MessagingApiMock{} messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi messaging.Api = &messagingApiMock var messagingApi pkgMessagingApi.Api = &messagingApiMock
traceState := "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" traceState := "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
traceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" traceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
expectedTime := time.Now() expectedTime := time.Now()
var cloudEvent = CloudEvent{ var cloudEvent = pkgAuditCommon.CloudEvent{
SpecVersion: "1.0", SpecVersion: "1.0",
Source: "resourcemanager", Source: "resourcemanager",
Id: "id", Id: "id",
Time: expectedTime, Time: expectedTime,
DataContentType: ContentTypeCloudEventsProtobuf, DataContentType: pkgAuditCommon.ContentTypeCloudEventsProtobuf,
DataType: "type", DataType: "type",
Subject: "subject", Subject: "subject",
TraceParent: &traceParent, TraceParent: &traceParent,
TraceState: &traceState, TraceState: &traceState,
} }
assert.NoError(t, send(topicNameResolver, messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent)) assert.NoError(t, Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.RoutableSystemIdentifier, &cloudEvent))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1)) assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
arguments := messagingApiMock.Calls[0].Arguments arguments := messagingApiMock.Calls[0].Arguments
@ -370,14 +357,14 @@ func Test_SendAllHeadersSet(t *testing.T) {
assert.Equal(t, "topic", topic) assert.Equal(t, "topic", topic)
contentType := arguments.Get(3).(string) contentType := arguments.Get(3).(string)
assert.Equal(t, ContentTypeCloudEventsProtobuf, contentType) assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, contentType)
applicationProperties := arguments.Get(4).(map[string]any) applicationProperties := arguments.Get(4).(map[string]any)
assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"]) assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"])
assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"]) assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"])
assert.Equal(t, "id", applicationProperties["cloudEvents:id"]) assert.Equal(t, "id", applicationProperties["cloudEvents:id"])
assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"]) assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"]) assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "type", applicationProperties["cloudEvents:type"]) assert.Equal(t, "type", applicationProperties["cloudEvents:type"])
assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"]) assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"])
assert.Equal(t, traceParent, applicationProperties["cloudEvents:traceparent"]) assert.Equal(t, traceParent, applicationProperties["cloudEvents:traceparent"])
@ -388,23 +375,23 @@ func Test_SendAllHeadersSet(t *testing.T) {
func Test_SendWithoutOptionalHeadersSet(t *testing.T) { func Test_SendWithoutOptionalHeadersSet(t *testing.T) {
topicNameResolverMock := TopicNameResolverMock{} topicNameResolverMock := TopicNameResolverMock{}
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil) topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
var topicNameResolver TopicNameResolver = &topicNameResolverMock var topicNameResolver pkgAuditCommon.TopicNameResolver = &topicNameResolverMock
messagingApiMock := MessagingApiMock{} messagingApiMock := MessagingApiMock{}
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
var messagingApi messaging.Api = &messagingApiMock var messagingApi pkgMessagingApi.Api = &messagingApiMock
expectedTime := time.Now() expectedTime := time.Now()
var cloudEvent = CloudEvent{ var cloudEvent = pkgAuditCommon.CloudEvent{
SpecVersion: "1.0", SpecVersion: "1.0",
Source: "resourcemanager", Source: "resourcemanager",
Id: "id", Id: "id",
Time: expectedTime, Time: expectedTime,
DataContentType: ContentTypeCloudEventsProtobuf, DataContentType: pkgAuditCommon.ContentTypeCloudEventsProtobuf,
DataType: "type", DataType: "type",
Subject: "subject", Subject: "subject",
} }
assert.NoError(t, send(topicNameResolver, messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent)) assert.NoError(t, Send(topicNameResolver, messagingApi, context.Background(), pkgAuditCommon.RoutableSystemIdentifier, &cloudEvent))
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1)) assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
arguments := messagingApiMock.Calls[0].Arguments arguments := messagingApiMock.Calls[0].Arguments
@ -412,14 +399,14 @@ func Test_SendWithoutOptionalHeadersSet(t *testing.T) {
assert.Equal(t, "topic", topic) assert.Equal(t, "topic", topic)
contentType := arguments.Get(3).(string) contentType := arguments.Get(3).(string)
assert.Equal(t, ContentTypeCloudEventsProtobuf, contentType) assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, contentType)
applicationProperties := arguments.Get(4).(map[string]any) applicationProperties := arguments.Get(4).(map[string]any)
assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"]) assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"])
assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"]) assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"])
assert.Equal(t, "id", applicationProperties["cloudEvents:id"]) assert.Equal(t, "id", applicationProperties["cloudEvents:id"])
assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"]) assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"]) assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "type", applicationProperties["cloudEvents:type"]) assert.Equal(t, "type", applicationProperties["cloudEvents:type"])
assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"]) assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"])
assert.Equal(t, nil, applicationProperties["cloudEvents:traceparent"]) assert.Equal(t, nil, applicationProperties["cloudEvents:traceparent"])

View file

@ -8,14 +8,16 @@ import (
"strings" "strings"
"time" "time"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
var ErrUnsupportedSeverity = errors.New("unsupported severity level") var ErrUnsupportedSeverity = errors.New("unsupported severity level")
// convertAndSerializeIntoLegacyFormat converts the protobuf events into the json serialized legacy audit log format // ConvertAndSerializeIntoLegacyFormat converts the protobuf events into the json serialized legacy audit log format
func convertAndSerializeIntoLegacyFormat( func ConvertAndSerializeIntoLegacyFormat(
event *auditV1.AuditLogEntry, event *auditV1.AuditLogEntry,
routableEvent *auditV1.RoutableAuditEvent, routableEvent *auditV1.RoutableAuditEvent,
) ([]byte, error) { ) ([]byte, error) {
@ -23,14 +25,14 @@ func convertAndSerializeIntoLegacyFormat(
// Event type // Event type
var eventType string var eventType string
switch { switch {
case strings.HasSuffix(event.LogName, string(EventTypeAdminActivity)): case strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity)):
eventType = "ADMIN_ACTIVITY" eventType = "ADMIN_ACTIVITY"
case strings.HasSuffix(event.LogName, string(EventTypeSystemEvent)): case strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeSystemEvent)):
eventType = "SYSTEM_EVENT" eventType = "SYSTEM_EVENT"
case strings.HasSuffix(event.LogName, string(EventTypePolicyDenied)): case strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypePolicyDenied)):
eventType = "POLICY_DENIED" eventType = "POLICY_DENIED"
case strings.HasSuffix(event.LogName, string(EventTypeDataAccess)): case strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)):
return nil, ErrUnsupportedEventTypeDataAccess return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
default: default:
return nil, errors.New("unsupported event type") return nil, errors.New("unsupported event type")
} }
@ -117,34 +119,34 @@ func convertAndSerializeIntoLegacyFormat(
} }
if routableEvent.ObjectIdentifier == nil { if routableEvent.ObjectIdentifier == nil {
return nil, ErrObjectIdentifierNil return nil, pkgAuditCommon.ErrObjectIdentifierNil
} }
// Context and event type // Context and event type
var messageContext *LegacyAuditEventContext var messageContext *LegacyAuditEventContext
switch routableEvent.ObjectIdentifier.Type { switch routableEvent.ObjectIdentifier.Type {
case string(ObjectTypeProject): case string(pkgAuditCommon.ObjectTypeProject):
messageContext = &LegacyAuditEventContext{ messageContext = &LegacyAuditEventContext{
OrganizationId: nil, OrganizationId: nil,
FolderId: nil, FolderId: nil,
ProjectId: &routableEvent.ObjectIdentifier.Identifier, ProjectId: &routableEvent.ObjectIdentifier.Identifier,
} }
case string(ObjectTypeFolder): case string(pkgAuditCommon.ObjectTypeFolder):
messageContext = &LegacyAuditEventContext{ messageContext = &LegacyAuditEventContext{
OrganizationId: nil, OrganizationId: nil,
FolderId: &routableEvent.ObjectIdentifier.Identifier, FolderId: &routableEvent.ObjectIdentifier.Identifier,
ProjectId: nil, ProjectId: nil,
} }
case string(ObjectTypeOrganization): case string(pkgAuditCommon.ObjectTypeOrganization):
messageContext = &LegacyAuditEventContext{ messageContext = &LegacyAuditEventContext{
OrganizationId: &routableEvent.ObjectIdentifier.Identifier, OrganizationId: &routableEvent.ObjectIdentifier.Identifier,
FolderId: nil, FolderId: nil,
ProjectId: nil, ProjectId: nil,
} }
case string(ObjectTypeSystem): case string(pkgAuditCommon.ObjectTypeSystem):
messageContext = nil messageContext = nil
default: default:
return nil, ErrUnsupportedObjectIdentifierType return nil, pkgAuditCommon.ErrUnsupportedObjectIdentifierType
} }
var visibility string var visibility string

View file

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

View file

@ -2,23 +2,21 @@ package api
import ( import (
"context" "context"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/google/uuid"
"github.com/lestrrat-go/jwx/v2/jwt"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
"net"
"net/url" "net/url"
"regexp"
"slices" "slices"
"strings" "strings"
"time" "time"
"github.com/lestrrat-go/jwx/v2/jwt"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
const EmailAddressDoNotReplyAtStackItDotCloud = "do-not-reply@stackit.cloud" const EmailAddressDoNotReplyAtStackItDotCloud = "do-not-reply@stackit.cloud"
@ -31,66 +29,6 @@ var ErrInvalidAuthorizationHeaderValue = errors.New("invalid authorization heade
var ErrInvalidBearerToken = errors.New("invalid bearer token") var ErrInvalidBearerToken = errors.New("invalid bearer token")
var ErrTokenIsNotBearerToken = errors.New("token is not a bearer token") var ErrTokenIsNotBearerToken = errors.New("token is not a bearer token")
var objectTypeIdPattern = regexp.MustCompile(".*/(projects|folders|organizations)/([0-9a-fA-F-]{36})(?:/.*)?")
type ApiRequest struct {
// Body
//
// Required: false
Body []byte
// The (HTTP) request headers / gRPC metadata.
//
// Internal IP-Addresses have to be removed (e.g. in x-forwarded-xxx headers).
//
// Required: true
Header map[string][]string
// The HTTP request `Host` header value.
//
// Required: true
Host string
// Method
//
// Required: true
Method string
// The URL scheme, such as `http`, `https` or `gRPC`.
//
// Required: true
Scheme string
// The network protocol used with the request, such as "http/1.1",
// "spdy/3", "h2", "h2c", "webrtc", "tcp", "udp", "quic". See
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
// for details.
//
// Required: true
Proto string
// The url
//
// Required: true
URL RequestUrl
}
type RequestUrl struct {
// The gRPC / HTTP URL path.
//
// Required: true
Path string
// The HTTP URL query in the format of "name1=value1&name2=value2", as it
// appears in the first line of the HTTP request.
// The input should be escaped to not contain any special characters.
//
// Required: false
RawQuery *string
}
// AuditRequest bundles request related parameters // AuditRequest bundles request related parameters
type AuditRequest struct { type AuditRequest struct {
@ -100,7 +38,7 @@ type AuditRequest struct {
// It should never include user-generated data, such as file contents. // It should never include user-generated data, such as file contents.
// //
// Required: true // Required: true
Request *ApiRequest Request *pkgAuditCommon.ApiRequest
// The IP address of the caller. // The IP address of the caller.
// For caller from internet, this will be public IPv4 or IPv6 address. // For caller from internet, this will be public IPv4 or IPv6 address.
@ -409,24 +347,6 @@ func NewAuditLogEntry(
return &event, nil return &event, nil
} }
// GetCalledServiceNameFromRequest extracts the called service name from subdomain name
func GetCalledServiceNameFromRequest(request *ApiRequest, fallbackName string) string {
if request == nil {
return fallbackName
}
var calledServiceName = fallbackName
host := request.Host
ip := net.ParseIP(host)
if ip == nil && !strings.Contains(host, "localhost") {
dotIdx := strings.Index(host, ".")
if dotIdx != -1 {
calledServiceName = host[0:dotIdx]
}
}
return calledServiceName
}
// NewPbInt64Value returns protobuf int64 wrapper if value is not nil. // NewPbInt64Value returns protobuf int64 wrapper if value is not nil.
func NewPbInt64Value(value *int64) *wrapperspb.Int64Value { func NewPbInt64Value(value *int64) *wrapperspb.Int64Value {
if value != nil { if value != nil {
@ -437,7 +357,7 @@ func NewPbInt64Value(value *int64) *wrapperspb.Int64Value {
// NewRequestMetadata returns initialized protobuf RequestMetadata object. // NewRequestMetadata returns initialized protobuf RequestMetadata object.
func NewRequestMetadata( func NewRequestMetadata(
request *ApiRequest, request *pkgAuditCommon.ApiRequest,
requestHeaders map[string]string, requestHeaders map[string]string,
requestId *string, requestId *string,
requestScheme string, requestScheme string,
@ -469,7 +389,7 @@ func NewRequestMetadata(
// NewRequestAttributes returns initialized protobuf AttributeContext_Request object. // NewRequestAttributes returns initialized protobuf AttributeContext_Request object.
func NewRequestAttributes( func NewRequestAttributes(
request *ApiRequest, request *pkgAuditCommon.ApiRequest,
requestHeaders map[string]string, requestHeaders map[string]string,
requestId *string, requestId *string,
requestScheme string, requestScheme string,
@ -488,7 +408,7 @@ func NewRequestAttributes(
return &auditV1.AttributeContext_Request{ return &auditV1.AttributeContext_Request{
Id: requestId, Id: requestId,
Method: StringToHttpMethod(request.Method), Method: pkgAuditCommon.StringToHttpMethod(request.Method),
Headers: requestHeaders, Headers: requestHeaders,
Path: request.URL.Path, Path: request.URL.Path,
Host: request.Host, Host: request.Host,
@ -560,7 +480,7 @@ func NewResponseBody(response []byte) (*structpb.Struct, error) {
} }
// NewRequestBody converts the request body into a protobuf struct. // NewRequestBody converts the request body into a protobuf struct.
func NewRequestBody(request *ApiRequest) (*structpb.Struct, error) { func NewRequestBody(request *pkgAuditCommon.ApiRequest) (*structpb.Struct, error) {
if len(request.Body) == 0 { if len(request.Body) == 0 {
return nil, nil return nil, nil
@ -616,8 +536,8 @@ func FilterAndMergeHeaders(headers map[string][]string) map[string]string {
// NewAuditRoutingIdentifier instantiates a new auditApi.RoutableIdentifier for // NewAuditRoutingIdentifier instantiates a new auditApi.RoutableIdentifier for
// the given object ID and object type. // the given object ID and object type.
func NewAuditRoutingIdentifier(objectId string, objectType ObjectType) *RoutableIdentifier { func NewAuditRoutingIdentifier(objectId string, objectType pkgAuditCommon.ObjectType) *pkgAuditCommon.RoutableIdentifier {
return &RoutableIdentifier{ return &pkgAuditCommon.RoutableIdentifier{
Identifier: objectId, Identifier: objectId,
Type: objectType, Type: objectType,
} }
@ -628,7 +548,7 @@ func NewAuditRoutingIdentifier(objectId string, objectType ObjectType) *Routable
// - authenticationPrincipal - principal identifier // - authenticationPrincipal - principal identifier
// - audiences - list of audience claims // - audiences - list of audience claims
// - authenticationInfo - information about the user or service-account authentication // - authenticationInfo - information about the user or service-account authentication
func AuditAttributesFromAuthorizationHeader(request *ApiRequest) ( func AuditAttributesFromAuthorizationHeader(request *pkgAuditCommon.ApiRequest) (
*structpb.Struct, *structpb.Struct,
string, string,
[]string, []string,
@ -832,133 +752,3 @@ func extractSubjectAndEmail(token jwt.Token) (string, string) {
} }
return principalId, principalEmail return principalId, principalEmail
} }
// OperationNameFromUrlPath converts the request url path into an operation name.
// UUIDs and query parameters are filtered out, slashes replaced by dots.
// HTTP methods are added as suffix as follows:
// - POST - create
// - PUT - update
// - PATCH - update
// - DELETE - delete
// - others - read
func OperationNameFromUrlPath(path, 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 operation != "" {
method := StringToHttpMethod(requestMethod)
var action string
switch method {
case auditV1.AttributeContext_HTTP_METHOD_PUT,
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
}
responseJson, err := json.Marshal(response)
if err != nil {
return nil, err
}
return responseJson, nil
}

View file

@ -1,61 +1,23 @@
package api package api
import ( import (
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
"time" "time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
func Test_GetCalledServiceNameFromRequest(t *testing.T) {
t.Run("request is nil", func(t *testing.T) {
serviceName := GetCalledServiceNameFromRequest(nil, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
})
t.Run("localhost", func(t *testing.T) {
request := ApiRequest{Host: "localhost:8080"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
})
t.Run("cf", func(t *testing.T) {
request := ApiRequest{Host: "stackit-resource-manager-go-dev.apps.01.cf.eu01.stackit.cloud"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "stackit-resource-manager-go-dev", serviceName)
})
t.Run("cf invalid host", func(t *testing.T) {
request := ApiRequest{Host: ""}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
})
t.Run("ip", func(t *testing.T) {
request := ApiRequest{Host: "127.0.0.1"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
},
)
t.Run("ip short", func(t *testing.T) {
request := ApiRequest{Host: "::1"}
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
assert.Equal(t, "resource-manager", serviceName)
},
)
}
func Test_NewPbInt64Value(t *testing.T) { func Test_NewPbInt64Value(t *testing.T) {
t.Run("nil", func(t *testing.T) { t.Run("nil", func(t *testing.T) {
@ -123,9 +85,9 @@ func Test_NewRequestMetadata(t *testing.T) {
requestHeaders["Custom"] = []string{"customHeader"} requestHeaders["Custom"] = []string{"customHeader"}
queryString := "topic=project" queryString := "topic=project"
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Scheme: "http", Scheme: "http",
@ -187,9 +149,9 @@ func Test_NewRequestMetadata(t *testing.T) {
}) })
t.Run("without query parameters", func(t *testing.T) { t.Run("without query parameters", func(t *testing.T) {
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new"}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new"},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -213,9 +175,9 @@ func Test_NewRequestMetadata(t *testing.T) {
t.Run("with empty query parameters", func(t *testing.T) { t.Run("with empty query parameters", func(t *testing.T) {
emptyQuery := "" emptyQuery := ""
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -238,9 +200,9 @@ func Test_NewRequestMetadata(t *testing.T) {
}) })
t.Run("without request id", func(t *testing.T) { t.Run("without request id", func(t *testing.T) {
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -262,9 +224,9 @@ func Test_NewRequestMetadata(t *testing.T) {
t.Run("various default http methods", func(t *testing.T) { t.Run("various default http methods", func(t *testing.T) {
httpMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"} httpMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"}
for _, httpMethod := range httpMethods { for _, httpMethod := range httpMethods {
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: httpMethod, Method: httpMethod,
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -286,9 +248,9 @@ func Test_NewRequestMetadata(t *testing.T) {
}) })
t.Run("unknown http method", func(t *testing.T) { t.Run("unknown http method", func(t *testing.T) {
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "", Method: "",
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Header: requestHeaders, Header: requestHeaders,
@ -377,7 +339,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Basic username:password" headerValue := "Basic username:password"
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue} headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrTokenIsNotBearerToken) assert.ErrorIs(t, err, ErrTokenIsNotBearerToken)
@ -387,7 +349,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "a b c" headerValue := "a b c"
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue} headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidAuthorizationHeaderValue) assert.ErrorIs(t, err, ErrInvalidAuthorizationHeaderValue)
@ -397,7 +359,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Bearer a.b.c.d" headerValue := "Bearer a.b.c.d"
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue} headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidBearerToken) assert.ErrorIs(t, err, ErrInvalidBearerToken)
@ -407,7 +369,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Bearer a.b.c" headerValue := "Bearer a.b.c"
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue} headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidBearerToken) assert.ErrorIs(t, err, ErrInvalidBearerToken)
@ -416,7 +378,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("client credentials token", func(t *testing.T) { t.Run("client credentials token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{clientCredentialsToken} headers["Authorization"] = []string{clientCredentialsToken}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -451,7 +413,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("service account access token", func(t *testing.T) { t.Run("service account access token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountToken} headers["Authorization"] = []string{serviceAccountToken}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -491,7 +453,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("impersonated token of access token", func(t *testing.T) { t.Run("impersonated token of access token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountTokenImpersonated} headers["Authorization"] = []string{serviceAccountTokenImpersonated}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -539,7 +501,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("impersonated token of impersonated access token", func(t *testing.T) { t.Run("impersonated token of impersonated access token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountTokenRepeatedlyImpersonated} headers["Authorization"] = []string{serviceAccountTokenRepeatedlyImpersonated}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -592,7 +554,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("user token", func(t *testing.T) { t.Run("user token", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{userToken} headers["Authorization"] = []string{userToken}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -629,7 +591,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("user token with simple aud claim", func(t *testing.T) { t.Run("user token with simple aud claim", func(t *testing.T) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers["Authorization"] = []string{userTokenWithSimpleAudience} headers["Authorization"] = []string{userTokenWithSimpleAudience}
request := ApiRequest{Header: headers} request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request) AuditAttributesFromAuthorizationHeader(&request)
@ -671,9 +633,9 @@ func Test_NewAuditLogEntry(t *testing.T) {
requestHeaders["User-Agent"] = []string{userAgent} requestHeaders["User-Agent"] = []string{userAgent}
requestHeaders["Custom"] = []string{"customHeader"} requestHeaders["Custom"] = []string{"customHeader"}
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new"}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new"},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Scheme: "http", Scheme: "http",
@ -700,7 +662,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
} }
objectId := uuid.NewString() objectId := uuid.NewString()
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity) logName := fmt.Sprintf("projects/%s/logs/%s", objectId, pkgAuditCommon.EventTypeAdminActivity)
serviceName := "resource-manager" serviceName := "resource-manager"
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName) operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
resourceName := fmt.Sprintf("projects/%s", objectId) resourceName := fmt.Sprintf("projects/%s", objectId)
@ -780,9 +742,9 @@ func Test_NewAuditLogEntry(t *testing.T) {
requestBody["key"] = "request" requestBody["key"] = "request"
requestBodyBytes, _ := json.Marshal(requestBody) requestBodyBytes, _ := json.Marshal(requestBody)
query := "topic=project" query := "topic=project"
request := ApiRequest{ request := pkgAuditCommon.ApiRequest{
Method: "GET", Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &query}, URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &query},
Host: "localhost:8080", Host: "localhost:8080",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
Scheme: "http", Scheme: "http",
@ -824,7 +786,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
auditTime := time.Now().UTC() auditTime := time.Now().UTC()
objectId := uuid.NewString() objectId := uuid.NewString()
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity) logName := fmt.Sprintf("projects/%s/logs/%s", objectId, pkgAuditCommon.EventTypeAdminActivity)
serviceName := "resource-manager" serviceName := "resource-manager"
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName) operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
resourceName := fmt.Sprintf("projects/%s", objectId) resourceName := fmt.Sprintf("projects/%s", objectId)
@ -921,231 +883,9 @@ func Test_NewInsertId(t *testing.T) {
func Test_NewNewAuditRoutingIdentifier(t *testing.T) { func Test_NewNewAuditRoutingIdentifier(t *testing.T) {
objectId := uuid.NewString() objectId := uuid.NewString()
objectType := ObjectTypeProject objectType := pkgAuditCommon.ObjectTypeProject
routingIdentifier := NewAuditRoutingIdentifier(objectId, objectType) routingIdentifier := NewAuditRoutingIdentifier(objectId, objectType)
assert.Equal(t, objectId, routingIdentifier.Identifier) assert.Equal(t, objectId, routingIdentifier.Identifier)
assert.Equal(t, objectType, routingIdentifier.Type) assert.Equal(t, objectType, routingIdentifier.Type)
} }
func Test_OperationNameFromUrlPath(t *testing.T) {
t.Run("empty path", func(t *testing.T) {
operationName := OperationNameFromUrlPath("", "GET")
assert.Equal(t, "", operationName)
})
t.Run("root path", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/", "GET")
assert.Equal(t, "", operationName)
})
t.Run("path without version", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("path with uuid without version", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("path with uuid and version", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/v2/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET")
assert.Equal(t, "v2.projects.read", operationName)
})
t.Run("concatenated path", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/folders/167fc176-9d8e-477b-a56c-b50d7b26adcf/projects/0a2a4f9b-4e67-4562-ad02-c2d200e05aa6/audit/policy", "GET")
assert.Equal(t, "v2.organizations.folders.projects.audit.policy.read", operationName)
})
t.Run("path with query params", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/audit/policy?since=2024-08-27", "GET")
assert.Equal(t, "v2.organizations.audit.policy.read", operationName)
})
t.Run("path trailing slash", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("path trailing slash and query params", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/?changeDate=2024-10-13", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("http method post", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "POST")
assert.Equal(t, "projects.create", operationName)
})
t.Run("http method put", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "PUT")
assert.Equal(t, "projects.update", operationName)
})
t.Run("http method patch", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "PATCH")
assert.Equal(t, "projects.update", operationName)
})
t.Run("http method delete", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "DELETE")
assert.Equal(t, "projects.delete", operationName)
})
t.Run("operation name fallback on options", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "OPTIONS")
assert.Equal(t, "projects.read", operationName)
})
t.Run("operation name fallback on unknown", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "UNKNOWN")
assert.Equal(t, "projects.read", operationName)
})
}
func Test_OperationNameFromGrpcMethod(t *testing.T) {
t.Run("empty path", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("")
assert.Equal(t, "", operationName)
})
t.Run("root path", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("/")
assert.Equal(t, "", operationName)
})
t.Run("path without version", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("/example.ExampleService/ManualAuditEvent")
assert.Equal(t, "example.exampleservice.manualauditevent", operationName)
})
t.Run("path with version", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("/example.v1.ExampleService/ManualAuditEvent")
assert.Equal(t, "example.v1.exampleservice.manualauditevent", operationName)
})
t.Run("path trailing slash", func(t *testing.T) {
operationName := OperationNameFromGrpcMethod("/example.v1.ExampleService/ManualAuditEvent/")
assert.Equal(t, "example.v1.exampleservice.manualauditevent", operationName)
})
}
func Test_GetObjectIdAndTypeFromUrlPath(t *testing.T) {
t.Run("object id and type not in url", func(t *testing.T) {
objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/projects/audit")
assert.NoError(t, err)
assert.Equal(t, "", objectId)
assert.Nil(t, objectType)
})
t.Run("object id and type in url", func(t *testing.T) {
objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/projects/f17d4064-9b65-4334-b6a7-8fed96340124")
assert.NoError(t, err)
assert.Equal(t, "f17d4064-9b65-4334-b6a7-8fed96340124", objectId)
assert.Equal(t, ObjectTypeProject, *objectType)
})
t.Run("multiple object ids and types in url", func(t *testing.T) {
objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/organization/8ee58bec-d496-4bb9-af8d-72fda4d78b6b/projects/f17d4064-9b65-4334-b6a7-8fed96340124")
assert.NoError(t, err)
assert.Equal(t, "f17d4064-9b65-4334-b6a7-8fed96340124", objectId)
assert.Equal(t, ObjectTypeProject, *objectType)
})
}
func Test_ToArrayMap(t *testing.T) {
t.Run("empty map", func(t *testing.T) {
result := ToArrayMap(map[string]string{})
assert.Equal(t, map[string][]string{}, result)
})
t.Run("empty map", func(t *testing.T) {
result := ToArrayMap(map[string]string{"key1": "value1", "key2": "value2"})
assert.Equal(t, map[string][]string{
"key1": {"value1"},
"key2": {"value2"},
}, result)
})
}
func Test_StringAttributeFromMetadata(t *testing.T) {
metadata := map[string][]string{"key1": {"value1"}, "key2": {"value2"}}
t.Run("not found", func(t *testing.T) {
attribute := StringAttributeFromMetadata(metadata, "key3")
assert.Equal(t, "", attribute)
})
t.Run("found", func(t *testing.T) {
attribute := StringAttributeFromMetadata(metadata, "key2")
assert.Equal(t, "value2", attribute)
})
}
func Test_ResponseBodyToBytes(t *testing.T) {
t.Run(
"nil response body", func(t *testing.T) {
bytes, err := ResponseBodyToBytes(nil)
assert.Nil(t, bytes)
assert.Nil(t, err)
},
)
t.Run(
"bytes", func(t *testing.T) {
responseBody := []byte("data")
bytes, err := ResponseBodyToBytes(responseBody)
assert.Nil(t, err)
assert.Equal(t, responseBody, bytes)
},
)
t.Run(
"Protobuf message", func(t *testing.T) {
protobufMessage := auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(ObjectTypeProject)}
bytes, err := ResponseBodyToBytes(&protobufMessage)
assert.Nil(t, err)
expected, err := protojson.Marshal(&protobufMessage)
assert.Nil(t, err)
assert.Equal(t, expected, bytes)
},
)
t.Run(
"struct", func(t *testing.T) {
type CustomObject struct {
Value string
}
responseBody := CustomObject{Value: "data"}
bytes, err := ResponseBodyToBytes(responseBody)
assert.Nil(t, err)
expected, err := json.Marshal(responseBody)
assert.Nil(t, err)
assert.Equal(t, expected, bytes)
},
)
t.Run(
"map", func(t *testing.T) {
responseBody := map[string]interface{}{"value": "data"}
bytes, err := ResponseBodyToBytes(responseBody)
assert.Nil(t, err)
expected, err := json.Marshal(responseBody)
assert.Nil(t, err)
assert.Equal(t, expected, bytes)
},
)
}

View file

@ -1,10 +1,13 @@
package api package api
import ( import (
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"testing" "testing"
"buf.build/go/protovalidate"
"github.com/stretchr/testify/assert"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
func Test_RoutableAuditEvent(t *testing.T) { func Test_RoutableAuditEvent(t *testing.T) {
@ -18,7 +21,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
Visibility: auditV1.Visibility_VISIBILITY_PUBLIC, Visibility: auditV1.Visibility_VISIBILITY_PUBLIC,
ObjectIdentifier: &auditV1.ObjectIdentifier{ ObjectIdentifier: &auditV1.ObjectIdentifier{
Identifier: "14f7aa86-77ba-4d77-a091-a2cf3395a221", Identifier: "14f7aa86-77ba-4d77-a091-a2cf3395a221",
Type: string(ObjectTypeProject), Type: string(pkgAuditCommon.ObjectTypeProject),
}, },
Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &auditV1.UnencryptedData{ Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &auditV1.UnencryptedData{
Data: []byte("data"), Data: []byte("data"),

View file

@ -4,13 +4,13 @@ import (
"fmt" "fmt"
"time" "time"
"google.golang.org/protobuf/types/known/wrapperspb"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/google/uuid" "github.com/google/uuid"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
) )
const clientCredentialsToken = "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1yZXNvdXJjZS1tYW5hZ2VyLWRldiJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2IiwiZXhwIjoxNzI0NDA1MzI2LCJpYXQiOjE3MjQ0MDQ0MjYsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZGV2LnN0YWNraXQuY2xvdWQiLCJqdGkiOiJlNDZlYmEzOC1kZWRiLTQ1NDEtOTRmMy00OWY5N2E5MzRkNTgiLCJuYmYiOjE3MjQ0MDQ0MjYsInNjb3BlIjoidWFhLm5vbmUiLCJzdWIiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2In0.JP5Uy7AMdK4ukzQ6aOYzbVwEmq0Tp2ppQGRqGOhuVQgbqs6yJ33GKXo7RPsJVLw3FR7XAxENIVqNvzGotbDXr0NjBGdzyxIHzrOaUqM4w1iLzD1KF51dXFwkoigqDdD7Ze9eI_Uo3tSn8FwGLTSoO-ONQYpnceCiGut2Gc6VIL8HOLdh8dzlRENGQtgYd-3Y5zqpoLrsR2Bd-0sv15sF-5aI0CqcC8gE70JPImKf2u_IYI-TYMDNk86YSCtaYO5-alOrHXXWwgzSoH-r2s5qoOhPbei9myV_P4fdcKXxMqfap9hImXPUooVhpdUr1AabZw3MtW7rION8tJAiauhMQA" const clientCredentialsToken = "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1yZXNvdXJjZS1tYW5hZ2VyLWRldiJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2IiwiZXhwIjoxNzI0NDA1MzI2LCJpYXQiOjE3MjQ0MDQ0MjYsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZGV2LnN0YWNraXQuY2xvdWQiLCJqdGkiOiJlNDZlYmEzOC1kZWRiLTQ1NDEtOTRmMy00OWY5N2E5MzRkNTgiLCJuYmYiOjE3MjQ0MDQ0MjYsInNjb3BlIjoidWFhLm5vbmUiLCJzdWIiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2In0.JP5Uy7AMdK4ukzQ6aOYzbVwEmq0Tp2ppQGRqGOhuVQgbqs6yJ33GKXo7RPsJVLw3FR7XAxENIVqNvzGotbDXr0NjBGdzyxIHzrOaUqM4w1iLzD1KF51dXFwkoigqDdD7Ze9eI_Uo3tSn8FwGLTSoO-ONQYpnceCiGut2Gc6VIL8HOLdh8dzlRENGQtgYd-3Y5zqpoLrsR2Bd-0sv15sF-5aI0CqcC8gE70JPImKf2u_IYI-TYMDNk86YSCtaYO5-alOrHXXWwgzSoH-r2s5qoOhPbei9myV_P4fdcKXxMqfap9hImXPUooVhpdUr1AabZw3MtW7rION8tJAiauhMQA"
@ -22,7 +22,7 @@ const userTokenWithSimpleAudience = "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6InNlcnZp
var TestHeaders = map[string][]string{"user-agent": {"custom"}, "authorization": {userToken}} var TestHeaders = map[string][]string{"user-agent": {"custom"}, "authorization": {userToken}}
func newOrganizationAuditEvent( func NewOrganizationAuditEvent(
customization *func( customization *func(
*auditV1.AuditLogEntry, *auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier, *auditV1.ObjectIdentifier,
@ -42,11 +42,11 @@ func newOrganizationAuditEvent(
labels := make(map[string]string) labels := make(map[string]string)
labels["label1"] = "value1" labels["label1"] = "value1"
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeOrganization.Plural(), identifier, EventTypeAdminActivity), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier, pkgAuditCommon.EventTypeAdminActivity),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.organization.created", OperationName: "stackit.resourcemanager.v2.organization.created",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeOrganization.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: uuid.NewString(), PrincipalId: uuid.NewString(),
PrincipalEmail: "user@example.com", PrincipalEmail: "user@example.com",
@ -54,7 +54,7 @@ func newOrganizationAuditEvent(
ServiceAccountDelegationInfo: nil, ServiceAccountDelegationInfo: nil,
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeOrganization.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier),
Permission: &permission, Permission: &permission,
Granted: &permissionGranted, Granted: &permissionGranted,
}}, }},
@ -102,7 +102,7 @@ func newOrganizationAuditEvent(
objectIdentifier := &auditV1.ObjectIdentifier{ objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(), Identifier: identifier.String(),
Type: string(ObjectTypeOrganization), Type: string(pkgAuditCommon.ObjectTypeOrganization),
} }
if customization != nil { if customization != nil {
@ -112,7 +112,7 @@ func newOrganizationAuditEvent(
return auditEvent, objectIdentifier return auditEvent, objectIdentifier
} }
func newFolderAuditEvent( func NewFolderAuditEvent(
customization *func( customization *func(
*auditV1.AuditLogEntry, *auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier, *auditV1.ObjectIdentifier,
@ -132,11 +132,11 @@ func newFolderAuditEvent(
labels := make(map[string]string) labels := make(map[string]string)
labels["label1"] = "value1" labels["label1"] = "value1"
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeFolder.Plural(), identifier, EventTypeAdminActivity), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier, pkgAuditCommon.EventTypeAdminActivity),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.folder.created", OperationName: "stackit.resourcemanager.v2.folder.created",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeFolder.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: uuid.NewString(), PrincipalId: uuid.NewString(),
PrincipalEmail: "user@example.com", PrincipalEmail: "user@example.com",
@ -144,7 +144,7 @@ func newFolderAuditEvent(
ServiceAccountDelegationInfo: nil, ServiceAccountDelegationInfo: nil,
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeFolder.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier),
Permission: &permission, Permission: &permission,
Granted: &permissionGranted, Granted: &permissionGranted,
}}, }},
@ -192,7 +192,7 @@ func newFolderAuditEvent(
objectIdentifier := &auditV1.ObjectIdentifier{ objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(), Identifier: identifier.String(),
Type: string(ObjectTypeFolder), Type: string(pkgAuditCommon.ObjectTypeFolder),
} }
if customization != nil { if customization != nil {
@ -202,7 +202,7 @@ func newFolderAuditEvent(
return auditEvent, objectIdentifier return auditEvent, objectIdentifier
} }
func newProjectAuditEvent( func NewProjectAuditEvent(
customization *func( customization *func(
*auditV1.AuditLogEntry, *auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier, *auditV1.ObjectIdentifier,
@ -222,11 +222,11 @@ func newProjectAuditEvent(
labels := make(map[string]string) labels := make(map[string]string)
labels["label1"] = "value1" labels["label1"] = "value1"
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeProject.Plural(), identifier, EventTypeAdminActivity), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier, pkgAuditCommon.EventTypeAdminActivity),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.project.created", OperationName: "stackit.resourcemanager.v2.project.created",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: uuid.NewString(), PrincipalId: uuid.NewString(),
PrincipalEmail: "user@example.com", PrincipalEmail: "user@example.com",
@ -234,7 +234,7 @@ func newProjectAuditEvent(
ServiceAccountDelegationInfo: nil, ServiceAccountDelegationInfo: nil,
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
Permission: &permission, Permission: &permission,
Granted: &permissionGranted, Granted: &permissionGranted,
}}, }},
@ -282,7 +282,7 @@ func newProjectAuditEvent(
objectIdentifier := &auditV1.ObjectIdentifier{ objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(), Identifier: identifier.String(),
Type: string(ObjectTypeProject), Type: string(pkgAuditCommon.ObjectTypeProject),
} }
if customization != nil { if customization != nil {
@ -292,7 +292,7 @@ func newProjectAuditEvent(
return auditEvent, objectIdentifier return auditEvent, objectIdentifier
} }
func newProjectSystemAuditEvent( func NewProjectSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry { customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {
identifier := uuid.New() identifier := uuid.New()
@ -307,11 +307,11 @@ func newProjectSystemAuditEvent(
serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId) serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId)
delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}} delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}}
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", SystemIdentifier.Type, SystemIdentifier.Identifier, EventTypeSystemEvent), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.SystemIdentifier.Type, pkgAuditCommon.SystemIdentifier.Identifier, pkgAuditCommon.EventTypeSystemEvent),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.system.changed", OperationName: "stackit.resourcemanager.v2.system.changed",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: serviceAccountId, PrincipalId: serviceAccountId,
PrincipalEmail: "service-account@sa.stackit.cloud", PrincipalEmail: "service-account@sa.stackit.cloud",
@ -319,7 +319,7 @@ func newProjectSystemAuditEvent(
ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal}, ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal},
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
Permission: nil, Permission: nil,
Granted: nil, Granted: nil,
}}, }},
@ -372,7 +372,7 @@ func newProjectSystemAuditEvent(
return auditEvent return auditEvent
} }
func newSystemAuditEvent( func NewSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry { customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {
identifier := uuid.Nil identifier := uuid.Nil
@ -387,11 +387,11 @@ func newSystemAuditEvent(
serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId) serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId)
delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}} delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}}
auditEvent := &auditV1.AuditLogEntry{ auditEvent := &auditV1.AuditLogEntry{
LogName: fmt.Sprintf("%s/%s/logs/%s", ObjectTypeSystem.Plural(), identifier, EventTypeSystemEvent), LogName: fmt.Sprintf("%s/%s/logs/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier, pkgAuditCommon.EventTypeSystemEvent),
ProtoPayload: &auditV1.AuditLog{ ProtoPayload: &auditV1.AuditLog{
ServiceName: "resource-manager", ServiceName: "resource-manager",
OperationName: "stackit.resourcemanager.v2.system.changed", OperationName: "stackit.resourcemanager.v2.system.changed",
ResourceName: fmt.Sprintf("%s/%s", ObjectTypeSystem.Plural(), identifier), ResourceName: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier),
AuthenticationInfo: &auditV1.AuthenticationInfo{ AuthenticationInfo: &auditV1.AuthenticationInfo{
PrincipalId: serviceAccountId, PrincipalId: serviceAccountId,
PrincipalEmail: "service-account@sa.stackit.cloud", PrincipalEmail: "service-account@sa.stackit.cloud",
@ -399,7 +399,7 @@ func newSystemAuditEvent(
ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal}, ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal},
}, },
AuthorizationInfo: []*auditV1.AuthorizationInfo{{ AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeSystem.Plural(), identifier), Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier),
Permission: nil, Permission: nil,
Granted: nil, Granted: nil,
}}, }},

View file

@ -2,6 +2,7 @@ package api
import ( import (
"context" "context"
"go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/propagation"
) )

View file

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

View file

@ -4,43 +4,48 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/Azure/go-amqp"
"log/slog" "log/slog"
"sync" "sync"
"time" "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") var ErrConnectionClosed = errors.New("amqp connection is closed")
type AmqpConnection struct { type AmqpConnection struct {
connectionName string ConnectionName string
lock sync.RWMutex Lock sync.RWMutex
brokerUrl string BrokerUrl string
username string Username string
password string Password string
conn amqpConn Conn AmqpConn
dialer amqpDial Dialer amqpDial
} }
// amqpConn is an abstraction of amqp.Conn // AmqpConn is an abstraction of amqp.Conn
type amqpConn interface { type AmqpConn interface {
NewSession(ctx context.Context, opts *amqp.SessionOptions) (amqpSession, error) NewSession(ctx context.Context, opts *amqp.SessionOptions) (AmqpSession, error)
Close() error Close() error
Done() <-chan struct{} Done() <-chan struct{}
} }
type defaultAmqpConn struct { type defaultAmqpConn struct {
conn *amqp.Conn Conn *amqp.Conn
} }
func newDefaultAmqpConn(conn *amqp.Conn) *defaultAmqpConn { func newDefaultAmqpConn(conn *amqp.Conn) *defaultAmqpConn {
return &defaultAmqpConn{ return &defaultAmqpConn{
conn: conn, Conn: conn,
} }
} }
func (d defaultAmqpConn) NewSession(ctx context.Context, opts *amqp.SessionOptions) (amqpSession, error) { func (d defaultAmqpConn) NewSession(ctx context.Context, opts *amqp.SessionOptions) (AmqpSession, error) {
session, err := d.conn.NewSession(ctx, opts) session, err := d.Conn.NewSession(ctx, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -48,47 +53,47 @@ func (d defaultAmqpConn) NewSession(ctx context.Context, opts *amqp.SessionOptio
} }
func (d defaultAmqpConn) Close() error { func (d defaultAmqpConn) Close() error {
return d.conn.Close() return d.Conn.Close()
} }
func (d defaultAmqpConn) Done() <-chan struct{} { func (d defaultAmqpConn) Done() <-chan struct{} {
return d.conn.Done() return d.Conn.Done()
} }
var _ amqpConn = (*defaultAmqpConn)(nil) var _ AmqpConn = (*defaultAmqpConn)(nil)
type amqpDial interface { type amqpDial interface {
Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (amqpConn, error) Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (AmqpConn, error)
} }
type amqpSession interface { type AmqpSession interface {
NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (amqpSender, error) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (AmqpSender, error)
Close(ctx context.Context) error Close(ctx context.Context) error
} }
type defaultAmqpSession struct { type defaultAmqpSession struct {
session *amqp.Session Session *amqp.Session
} }
func newDefaultAmqpSession(session *amqp.Session) *defaultAmqpSession { func newDefaultAmqpSession(session *amqp.Session) *defaultAmqpSession {
return &defaultAmqpSession{ return &defaultAmqpSession{
session: session, Session: session,
} }
} }
func (s *defaultAmqpSession) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (amqpSender, error) { func (s *defaultAmqpSession) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (AmqpSender, error) {
return s.session.NewSender(ctx, target, opts) return s.Session.NewSender(ctx, target, opts)
} }
func (s *defaultAmqpSession) Close(ctx context.Context) error { func (s *defaultAmqpSession) Close(ctx context.Context) error {
return s.session.Close(ctx) return s.Session.Close(ctx)
} }
var _ amqpSession = (*defaultAmqpSession)(nil) var _ AmqpSession = (*defaultAmqpSession)(nil)
type defaultAmqpDialer struct{} type defaultAmqpDialer struct{}
func (d *defaultAmqpDialer) Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (amqpConn, error) { func (d *defaultAmqpDialer) Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (AmqpConn, error) {
dial, err := amqp.Dial(ctx, addr, opts) dial, err := amqp.Dial(ctx, addr, opts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -98,19 +103,19 @@ func (d *defaultAmqpDialer) Dial(ctx context.Context, addr string, opts *amqp.Co
var _ amqpDial = (*defaultAmqpDialer)(nil) var _ amqpDial = (*defaultAmqpDialer)(nil)
func NewAmqpConnection(config AmqpConnectionConfig, connectionName string) *AmqpConnection { func NewAmqpConnection(config pkgCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection {
return &AmqpConnection{ return &AmqpConnection{
connectionName: connectionName, ConnectionName: connectionName,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
brokerUrl: config.BrokerUrl, BrokerUrl: config.BrokerUrl,
username: config.Username, Username: config.Username,
password: config.Password, Password: config.Password,
dialer: &defaultAmqpDialer{}, Dialer: &defaultAmqpDialer{},
} }
} }
func (c *AmqpConnection) NewSender(ctx context.Context, topic string) (*AmqpSenderSession, error) { func (c *AmqpConnection) NewSender(ctx context.Context, topic string) (*AmqpSenderSession, error) {
if c.conn == nil { if c.Conn == nil {
return nil, errors.New("connection is not initialized") return nil, errors.New("connection is not initialized")
} }
@ -118,11 +123,11 @@ func (c *AmqpConnection) NewSender(ctx context.Context, topic string) (*AmqpSend
return nil, ErrConnectionClosed return nil, ErrConnectionClosed
} }
c.lock.RLock() c.Lock.RLock()
defer c.lock.RUnlock() defer c.Lock.RUnlock()
// new session // new session
newSession, err := c.conn.NewSession(ctx, nil) newSession, err := c.Conn.NewSession(ctx, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("new session: %w", err) return nil, fmt.Errorf("new session: %w", err)
} }
@ -157,8 +162,8 @@ func As[T any](value any, err error) (*T, error) {
} }
func (c *AmqpConnection) Connect() error { func (c *AmqpConnection) Connect() error {
c.lock.Lock() c.Lock.Lock()
defer c.lock.Unlock() defer c.Lock.Unlock()
subCtx, cancel := context.WithTimeout(context.Background(), connectionTimeoutSeconds*time.Second) subCtx, cancel := context.WithTimeout(context.Background(), connectionTimeoutSeconds*time.Second)
defer cancel() defer cancel()
@ -170,11 +175,11 @@ func (c *AmqpConnection) Connect() error {
} }
func (c *AmqpConnection) internalConnect(ctx context.Context) error { func (c *AmqpConnection) internalConnect(ctx context.Context) error {
if c.conn == nil { if c.Conn == nil {
// Set credentials if specified // Set credentials if specified
auth := amqp.SASLTypeAnonymous() auth := amqp.SASLTypeAnonymous()
if c.username != "" && c.password != "" { if c.Username != "" && c.Password != "" {
auth = amqp.SASLTypePlain(c.username, c.password) auth = amqp.SASLTypePlain(c.Username, c.Password)
} else { } else {
slog.Debug("amqp connection: connect: using anonymous messaging") slog.Debug("amqp connection: connect: using anonymous messaging")
} }
@ -183,18 +188,18 @@ func (c *AmqpConnection) internalConnect(ctx context.Context) error {
} }
// Initialize connection // Initialize connection
conn, err := c.dialer.Dial(ctx, c.brokerUrl, options) conn, err := c.Dialer.Dial(ctx, c.BrokerUrl, options)
if err != nil { if err != nil {
return fmt.Errorf("dial: %w", err) return fmt.Errorf("dial: %w", err)
} }
c.conn = conn c.Conn = conn
} }
return nil return nil
} }
func (c *AmqpConnection) Close() error { func (c *AmqpConnection) Close() error {
c.lock.Lock() c.Lock.Lock()
defer c.lock.Unlock() defer c.Lock.Unlock()
if err := c.internalClose(); err != nil { if err := c.internalClose(); err != nil {
return fmt.Errorf("internal close: %w", err) return fmt.Errorf("internal close: %w", err)
@ -203,28 +208,28 @@ func (c *AmqpConnection) Close() error {
} }
func (c *AmqpConnection) internalClose() error { func (c *AmqpConnection) internalClose() error {
if c.conn != nil { if c.Conn != nil {
if err := c.conn.Close(); err != nil { if err := c.Conn.Close(); err != nil {
return fmt.Errorf("connection close: %w", err) return fmt.Errorf("connection close: %w", err)
} }
c.conn = nil c.Conn = nil
} }
return nil return nil
} }
func (c *AmqpConnection) IsClosed() bool { func (c *AmqpConnection) IsClosed() bool {
c.lock.RLock() c.Lock.RLock()
defer c.lock.RUnlock() defer c.Lock.RUnlock()
return c.internalIsClosed() return c.internalIsClosed()
} }
func (c *AmqpConnection) internalIsClosed() bool { func (c *AmqpConnection) internalIsClosed() bool {
if c.conn == nil { if c.Conn == nil {
return true return true
} }
select { select {
case <-c.conn.Done(): case <-c.Conn.Done():
return true return true
default: default:
return false return false

View file

@ -5,15 +5,17 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"sync" "sync"
pkgCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
) )
type connectionProvider interface { type connectionProvider interface {
NewAmqpConnection(config AmqpConnectionConfig, connectionName string) *AmqpConnection NewAmqpConnection(config pkgCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection
} }
type defaultAmqpConnectionProvider struct{} type defaultAmqpConnectionProvider struct{}
func (p defaultAmqpConnectionProvider) NewAmqpConnection(config AmqpConnectionConfig, connectionName string) *AmqpConnection { func (p defaultAmqpConnectionProvider) NewAmqpConnection(config pkgCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection {
return NewAmqpConnection(config, connectionName) return NewAmqpConnection(config, connectionName)
} }
@ -26,37 +28,37 @@ type ConnectionPool interface {
} }
type AmqpConnectionPool struct { type AmqpConnectionPool struct {
config AmqpConnectionPoolConfig Config pkgCommon.AmqpConnectionPoolConfig
connectionName string ConnectionName string
connections []*AmqpConnection Connections []*AmqpConnection
connectionProvider connectionProvider ConnectionProvider connectionProvider
handleOffset int HandleOffset int
lock sync.RWMutex Lock sync.RWMutex
} }
type ConnectionPoolHandle struct { type ConnectionPoolHandle struct {
connectionOffset int ConnectionOffset int
} }
func NewDefaultAmqpConnectionPool(config AmqpConnectionConfig, connectionName string) (ConnectionPool, error) { func NewDefaultAmqpConnectionPool(config pkgCommon.AmqpConnectionConfig, connectionName string) (ConnectionPool, error) {
poolConfig := AmqpConnectionPoolConfig{ poolConfig := pkgCommon.AmqpConnectionPoolConfig{
Parameters: config, Parameters: config,
PoolSize: 2, PoolSize: 2,
} }
return NewAmqpConnectionPool(poolConfig, connectionName) return NewAmqpConnectionPool(poolConfig, connectionName)
} }
func NewAmqpConnectionPool(config AmqpConnectionPoolConfig, connectionName string) (ConnectionPool, error) { func NewAmqpConnectionPool(config pkgCommon.AmqpConnectionPoolConfig, connectionName string) (ConnectionPool, error) {
if config.PoolSize == 0 { if config.PoolSize == 0 {
config.PoolSize = 2 config.PoolSize = 2
} }
pool := &AmqpConnectionPool{ pool := &AmqpConnectionPool{
config: config, Config: config,
connectionName: connectionName, ConnectionName: connectionName,
connections: make([]*AmqpConnection, 0), Connections: make([]*AmqpConnection, 0),
connectionProvider: defaultAmqpConnectionProvider{}, ConnectionProvider: defaultAmqpConnectionProvider{},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
} }
if err := pool.initializeConnections(); err != nil { if err := pool.initializeConnections(); err != nil {
@ -70,11 +72,11 @@ func NewAmqpConnectionPool(config AmqpConnectionPoolConfig, connectionName strin
} }
func (p *AmqpConnectionPool) initializeConnections() error { func (p *AmqpConnectionPool) initializeConnections() error {
if len(p.connections) < p.config.PoolSize { if len(p.Connections) < p.Config.PoolSize {
p.lock.Lock() p.Lock.Lock()
defer p.lock.Unlock() defer p.Lock.Unlock()
numMissingConnections := p.config.PoolSize - len(p.connections) numMissingConnections := p.Config.PoolSize - len(p.Connections)
for i := 0; i < numMissingConnections; i++ { for i := 0; i < numMissingConnections; i++ {
if err := p.internalAddConnection(); err != nil { if err := p.internalAddConnection(); err != nil {
@ -90,12 +92,12 @@ func (p *AmqpConnectionPool) internalAddConnection() error {
if err != nil { if err != nil {
return fmt.Errorf("new connection: %w", err) return fmt.Errorf("new connection: %w", err)
} }
p.connections = append(p.connections, newConnection) p.Connections = append(p.Connections, newConnection)
return nil return nil
} }
func (p *AmqpConnectionPool) internalNewConnection() (*AmqpConnection, error) { func (p *AmqpConnectionPool) internalNewConnection() (*AmqpConnection, error) {
conn := p.connectionProvider.NewAmqpConnection(p.config.Parameters, p.connectionName) conn := p.ConnectionProvider.NewAmqpConnection(p.Config.Parameters, p.ConnectionName)
if err := conn.Connect(); err != nil { if err := conn.Connect(); err != nil {
slog.Warn("amqp connection: failed to connect to amqp broker", slog.Any("err", err)) slog.Warn("amqp connection: failed to connect to amqp broker", slog.Any("err", err))
@ -114,18 +116,18 @@ func (p *AmqpConnectionPool) internalNewConnection() (*AmqpConnection, error) {
} }
func (p *AmqpConnectionPool) Close() error { func (p *AmqpConnectionPool) Close() error {
p.lock.Lock() p.Lock.Lock()
defer p.lock.Unlock() defer p.Lock.Unlock()
closeErrors := make([]error, 0) closeErrors := make([]error, 0)
for _, conn := range p.connections { for _, conn := range p.Connections {
if conn != nil { if conn != nil {
if err := conn.Close(); err != nil { if err := conn.Close(); err != nil {
closeErrors = append(closeErrors, fmt.Errorf("pooled connection: %w", err)) closeErrors = append(closeErrors, fmt.Errorf("pooled connection: %w", err))
} }
} }
} }
p.connections = make([]*AmqpConnection, p.config.PoolSize) p.Connections = make([]*AmqpConnection, p.Config.PoolSize)
if len(closeErrors) > 0 { if len(closeErrors) > 0 {
return errors.Join(closeErrors...) return errors.Join(closeErrors...)
} }
@ -133,16 +135,16 @@ func (p *AmqpConnectionPool) Close() error {
} }
func (p *AmqpConnectionPool) NewHandle() *ConnectionPoolHandle { func (p *AmqpConnectionPool) NewHandle() *ConnectionPoolHandle {
p.lock.Lock() p.Lock.Lock()
defer p.lock.Unlock() defer p.Lock.Unlock()
offset := p.handleOffset offset := p.HandleOffset
p.handleOffset++ p.HandleOffset++
offset %= p.config.PoolSize offset %= p.Config.PoolSize
return &ConnectionPoolHandle{ return &ConnectionPoolHandle{
connectionOffset: offset, ConnectionOffset: offset,
} }
} }
@ -152,16 +154,16 @@ func (p *AmqpConnectionPool) GetConnection(handle *ConnectionPoolHandle) (*AmqpC
// renew the requested connection if the request connection is closed // renew the requested connection if the request connection is closed
if conn == nil || addConnection { if conn == nil || addConnection {
p.lock.Lock() p.Lock.Lock()
// check that accessing the pool only with a valid index (out of bounds should only occur on shutdown) // check that accessing the pool only with a valid index (out of bounds should only occur on shutdown)
connectionIndex := p.connectionIndex(handle, 0) connectionIndex := p.connectionIndex(handle, 0)
if p.connections[connectionIndex] == nil { if p.Connections[connectionIndex] == nil {
connection, err := p.internalNewConnection() connection, err := p.internalNewConnection()
if err != nil { if err != nil {
if conn == nil { if conn == nil {
// case: connection could not be renewed and no connection to return has been found // case: connection could not be renewed and no connection to return has been found
p.lock.Unlock() p.Lock.Unlock()
return nil, fmt.Errorf("renew connection: %w", err) return nil, fmt.Errorf("renew connection: %w", err)
} }
@ -169,11 +171,11 @@ func (p *AmqpConnectionPool) GetConnection(handle *ConnectionPoolHandle) (*AmqpC
slog.Warn("amqp connection pool: get connection: renew connection: ", slog.Any("err", err)) slog.Warn("amqp connection pool: get connection: renew connection: ", slog.Any("err", err))
} else { } else {
// case: connection could be renewed and will be added to pool // case: connection could be renewed and will be added to pool
p.connections[connectionIndex] = connection p.Connections[connectionIndex] = connection
conn = connection conn = connection
} }
} }
p.lock.Unlock() p.Lock.Unlock()
} }
if conn == nil { if conn == nil {
@ -187,18 +189,18 @@ func (p *AmqpConnectionPool) nextConnectionForHandle(handle *ConnectionPoolHandl
// retry as long as there are remaining connections in the pool // retry as long as there are remaining connections in the pool
var conn *AmqpConnection var conn *AmqpConnection
var addConnection bool var addConnection bool
for i := 0; i < p.config.PoolSize; i++ { for i := 0; i < p.Config.PoolSize; i++ {
// get the next possible connection (considering the retry index) // get the next possible connection (considering the retry index)
idx := p.connectionIndex(handle, i) idx := p.connectionIndex(handle, i)
p.lock.RLock() p.Lock.RLock()
if idx < len(p.connections) { if idx < len(p.Connections) {
conn = p.connections[idx] conn = p.Connections[idx]
} else { } else {
// handle the edge case that the pool is empty on shutdown // handle the edge case that the pool is empty on shutdown
conn = nil conn = nil
} }
p.lock.RUnlock() p.Lock.RUnlock()
// remember that the requested is closed, retry with the next // remember that the requested is closed, retry with the next
if conn == nil { if conn == nil {
@ -208,9 +210,9 @@ func (p *AmqpConnectionPool) nextConnectionForHandle(handle *ConnectionPoolHandl
// if the connection is closed, mark it by setting it to nil // if the connection is closed, mark it by setting it to nil
if conn.IsClosed() { if conn.IsClosed() {
p.lock.Lock() p.Lock.Lock()
p.connections[idx] = nil p.Connections[idx] = nil
p.lock.Unlock() p.Lock.Unlock()
addConnection = true addConnection = true
continue continue
@ -222,8 +224,8 @@ func (p *AmqpConnectionPool) nextConnectionForHandle(handle *ConnectionPoolHandl
} }
func (p *AmqpConnectionPool) connectionIndex(handle *ConnectionPoolHandle, iteration int) int { func (p *AmqpConnectionPool) connectionIndex(handle *ConnectionPoolHandle, iteration int) int {
if iteration+handle.connectionOffset >= p.config.PoolSize { if iteration+handle.ConnectionOffset >= p.Config.PoolSize {
return (iteration + handle.connectionOffset) % p.config.PoolSize return (iteration + handle.ConnectionOffset) % p.Config.PoolSize
} }
return iteration + handle.connectionOffset return iteration + handle.ConnectionOffset
} }

View file

@ -3,17 +3,20 @@ package messaging
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"sync" "sync"
"testing" "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 { type connectionProviderMock struct {
mock.Mock mock.Mock
} }
func (p *connectionProviderMock) NewAmqpConnection(config AmqpConnectionConfig, connectionName string) *AmqpConnection { func (p *connectionProviderMock) NewAmqpConnection(config pkgMessagingCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection {
args := p.Called(config, connectionName) args := p.Called(config, connectionName)
return args.Get(0).(*AmqpConnection) return args.Get(0).(*AmqpConnection)
} }
@ -24,28 +27,28 @@ func Test_AmqpConnectionPool_GetHandle(t *testing.T) {
t.Run("next handle", func(t *testing.T) { t.Run("next handle", func(t *testing.T) {
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
} }
handle := pool.NewHandle() handle := pool.NewHandle()
assert.NotNil(t, handle) assert.NotNil(t, handle)
assert.Equal(t, 0, handle.connectionOffset) assert.Equal(t, 0, handle.ConnectionOffset)
assert.Equal(t, 1, pool.handleOffset) assert.Equal(t, 1, pool.HandleOffset)
}) })
t.Run("next handle high offset", func(t *testing.T) { t.Run("next handle high offset", func(t *testing.T) {
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 13, HandleOffset: 13,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
} }
handle := pool.NewHandle() handle := pool.NewHandle()
assert.NotNil(t, handle) assert.NotNil(t, handle)
assert.Equal(t, 3, handle.connectionOffset) assert.Equal(t, 3, handle.ConnectionOffset)
assert.Equal(t, 14, pool.handleOffset) assert.Equal(t, 14, pool.HandleOffset)
}) })
} }
@ -58,24 +61,24 @@ func Test_AmqpConnectionPool_internalAddConnection(t *testing.T) {
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil) dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: dialer, Dialer: dialer,
} }
connectionProvider := &connectionProviderMock{} connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
err := pool.internalAddConnection() err := pool.internalAddConnection()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, len(pool.connections)) assert.Equal(t, 1, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1) connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
dialer.AssertNumberOfCalls(t, "Dial", 1) dialer.AssertNumberOfCalls(t, "Dial", 1)
}) })
@ -89,24 +92,24 @@ func Test_AmqpConnectionPool_internalAddConnection(t *testing.T) {
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil) dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: dialer, Dialer: dialer,
} }
connectionProvider := &connectionProviderMock{} connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
err := pool.internalAddConnection() err := pool.internalAddConnection()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, len(pool.connections)) assert.Equal(t, 1, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1) connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
dialer.AssertNumberOfCalls(t, "Dial", 2) dialer.AssertNumberOfCalls(t, "Dial", 2)
}) })
@ -117,24 +120,24 @@ func Test_AmqpConnectionPool_internalAddConnection(t *testing.T) {
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error")) dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error"))
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: dialer, Dialer: dialer,
} }
connectionProvider := &connectionProviderMock{} connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
err := pool.internalAddConnection() err := pool.internalAddConnection()
assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error") assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error")
assert.Equal(t, 0, len(pool.connections)) assert.Equal(t, 0, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1) connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
dialer.AssertNumberOfCalls(t, "Dial", 2) dialer.AssertNumberOfCalls(t, "Dial", 2)
}) })
@ -149,24 +152,24 @@ func Test_AmqpConnectionPool_initializeConnections(t *testing.T) {
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil) dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: dialer, Dialer: dialer,
} }
connectionProvider := &connectionProviderMock{} connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
err := pool.initializeConnections() err := pool.initializeConnections()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 5, len(pool.connections)) assert.Equal(t, 5, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 5) connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 5)
}) })
@ -177,9 +180,9 @@ func Test_AmqpConnectionPool_initializeConnections(t *testing.T) {
failingDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error")) failingDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error"))
failingConnection := &AmqpConnection{ failingConnection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: failingDialer, Dialer: failingDialer,
} }
conn := &amqpConnMock{} conn := &amqpConnMock{}
@ -187,25 +190,25 @@ func Test_AmqpConnectionPool_initializeConnections(t *testing.T) {
successfulDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil) successfulDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
successfulConnection := &AmqpConnection{ successfulConnection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: successfulDialer, Dialer: successfulDialer,
} }
connectionProvider := &connectionProviderMock{} connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(4) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(4)
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(failingConnection) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(failingConnection)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
err := pool.initializeConnections() err := pool.initializeConnections()
assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error") assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error")
assert.Equal(t, 4, len(pool.connections)) assert.Equal(t, 4, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 5) connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 5)
}) })
} }
@ -221,30 +224,30 @@ func Test_AmqpConnectionPool_Close(t *testing.T) {
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil) dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: dialer, Dialer: dialer,
} }
connectionProvider := &connectionProviderMock{} connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
err := pool.initializeConnections() err := pool.initializeConnections()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 5, len(pool.connections)) assert.Equal(t, 5, len(pool.Connections))
// close the pool // close the pool
err = pool.Close() err = pool.Close()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 5, len(pool.connections)) assert.Equal(t, 5, len(pool.Connections))
for _, c := range pool.connections { for _, c := range pool.Connections {
assert.Nil(t, c) assert.Nil(t, c)
} }
}) })
@ -258,9 +261,9 @@ func Test_AmqpConnectionPool_Close(t *testing.T) {
failingDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(failingConn, nil) failingDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(failingConn, nil)
failingConnection := &AmqpConnection{ failingConnection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: failingDialer, Dialer: failingDialer,
} }
successfulConn := &amqpConnMock{} successfulConn := &amqpConnMock{}
@ -269,9 +272,9 @@ func Test_AmqpConnectionPool_Close(t *testing.T) {
successfulDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(successfulConn, nil) successfulDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(successfulConn, nil)
successfulConnection := &AmqpConnection{ successfulConnection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: successfulDialer, Dialer: successfulDialer,
} }
connectionProvider := &connectionProviderMock{} connectionProvider := &connectionProviderMock{}
@ -280,22 +283,22 @@ func Test_AmqpConnectionPool_Close(t *testing.T) {
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(1) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(1)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
err := pool.initializeConnections() err := pool.initializeConnections()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 5, len(pool.connections)) assert.Equal(t, 5, len(pool.Connections))
// close the pool // close the pool
err = pool.Close() err = pool.Close()
assert.EqualError(t, err, "pooled connection: internal close: connection close: test error\npooled connection: internal close: connection close: test error") 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)) assert.Equal(t, 5, len(pool.Connections))
for _, c := range pool.connections { for _, c := range pool.Connections {
assert.Nil(t, c) assert.Nil(t, c)
} }
}) })
@ -311,9 +314,9 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel)) conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{ return &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
conn: conn, Conn: conn,
} }
} }
@ -324,9 +327,9 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel)) conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{ return &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
conn: conn, Conn: conn,
} }
} }
@ -337,13 +340,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
} }
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
} }
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1}) connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NotNil(t, connection) assert.NotNil(t, connection)
assert.False(t, addConnection) assert.False(t, addConnection)
}) })
@ -357,13 +360,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, newActiveConnection()) connections = append(connections, newActiveConnection())
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
} }
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1}) connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NotNil(t, connection) assert.NotNil(t, connection)
assert.True(t, addConnection) assert.True(t, addConnection)
}) })
@ -377,13 +380,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, newActiveConnection()) connections = append(connections, newActiveConnection())
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
} }
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1}) connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NotNil(t, connection) assert.NotNil(t, connection)
assert.True(t, addConnection) assert.True(t, addConnection)
}) })
@ -397,13 +400,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, nil) connections = append(connections, nil)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
} }
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1}) connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.Nil(t, connection) assert.Nil(t, connection)
assert.True(t, addConnection) assert.True(t, addConnection)
}) })
@ -417,13 +420,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, nil) connections = append(connections, nil)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
} }
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 23}) connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 23})
assert.NotNil(t, connection) assert.NotNil(t, connection)
assert.False(t, addConnection) assert.False(t, addConnection)
}) })
@ -437,13 +440,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, newActiveConnection()) connections = append(connections, newActiveConnection())
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
} }
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 23}) connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 23})
assert.NotNil(t, connection) assert.NotNil(t, connection)
assert.True(t, addConnection) assert.True(t, addConnection)
}) })
@ -459,9 +462,9 @@ func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel)) conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{ return &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
conn: conn, Conn: conn,
} }
} }
@ -472,13 +475,13 @@ func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
} }
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
} }
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1}) connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, connection) assert.NotNil(t, connection)
assert.Equal(t, connections[1], connection) assert.Equal(t, connections[1], connection)
@ -492,14 +495,14 @@ func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(newActiveConnection()) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(newActiveConnection())
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1}) connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, connection) assert.NotNil(t, connection)
assert.Equal(t, connections[1], connection) assert.Equal(t, connections[1], connection)
@ -520,21 +523,21 @@ func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
var c *amqpConnMock = nil var c *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error")) dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error"))
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: dialer, Dialer: dialer,
} }
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1}) connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, connection) assert.NotNil(t, connection)
assert.Nil(t, connections[1]) assert.Nil(t, connections[1])
@ -556,21 +559,21 @@ func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
var c *amqpConnMock = nil var c *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error")) dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error"))
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
dialer: dialer, Dialer: dialer,
} }
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection) connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{ pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5}, Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0, HandleOffset: 0,
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
connections: connections, Connections: connections,
connectionProvider: connectionProvider, ConnectionProvider: connectionProvider,
} }
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1}) connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.EqualError(t, err, "renew connection: new internal connection: internal connect: dial: dial error") assert.EqualError(t, err, "renew connection: new internal connection: internal connect: dial: dial error")
assert.Nil(t, connection) assert.Nil(t, connection)
assert.Equal(t, 5, len(connections)) assert.Equal(t, 5, len(connections))

View file

@ -3,11 +3,14 @@ package messaging
import ( import (
"context" "context"
"errors" "errors"
"sync"
"testing"
"github.com/Azure/go-amqp" "github.com/Azure/go-amqp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"sync"
"testing" pkgCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
) )
type amqpConnMock struct { type amqpConnMock struct {
@ -19,9 +22,9 @@ func (m *amqpConnMock) Done() <-chan struct{} {
return args.Get(0).(<-chan struct{}) return args.Get(0).(<-chan struct{})
} }
func (m *amqpConnMock) NewSession(ctx context.Context, opts *amqp.SessionOptions) (amqpSession, error) { func (m *amqpConnMock) NewSession(ctx context.Context, opts *amqp.SessionOptions) (AmqpSession, error) {
args := m.Called(ctx, opts) args := m.Called(ctx, opts)
return args.Get(0).(amqpSession), args.Error(1) return args.Get(0).(AmqpSession), args.Error(1)
} }
func (m *amqpConnMock) Close() error { func (m *amqpConnMock) Close() error {
@ -29,15 +32,15 @@ func (m *amqpConnMock) Close() error {
return args.Error(0) return args.Error(0)
} }
var _ amqpConn = (*amqpConnMock)(nil) var _ AmqpConn = (*amqpConnMock)(nil)
type amqpDialMock struct { type amqpDialMock struct {
mock.Mock mock.Mock
} }
func (m *amqpDialMock) Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (amqpConn, error) { func (m *amqpDialMock) Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (AmqpConn, error) {
args := m.Called(ctx, addr, opts) args := m.Called(ctx, addr, opts)
return args.Get(0).(amqpConn), args.Error(1) return args.Get(0).(AmqpConn), args.Error(1)
} }
var _ amqpDial = (*amqpDialMock)(nil) var _ amqpDial = (*amqpDialMock)(nil)
@ -46,9 +49,9 @@ type amqpSessionMock struct {
mock.Mock mock.Mock
} }
func (m *amqpSessionMock) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (amqpSender, error) { func (m *amqpSessionMock) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (AmqpSender, error) {
args := m.Called(ctx, target, opts) args := m.Called(ctx, target, opts)
return args.Get(0).(amqpSender), args.Error(1) return args.Get(0).(AmqpSender), args.Error(1)
} }
func (m *amqpSessionMock) Close(ctx context.Context) error { func (m *amqpSessionMock) Close(ctx context.Context) error {
@ -56,12 +59,12 @@ func (m *amqpSessionMock) Close(ctx context.Context) error {
return args.Error(0) return args.Error(0)
} }
var _ amqpSession = (*amqpSessionMock)(nil) var _ AmqpSession = (*amqpSessionMock)(nil)
func Test_AmqpConnection_IsClosed(t *testing.T) { func Test_AmqpConnection_IsClosed(t *testing.T) {
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
} }
channelReceiver := func(channel chan struct{}) <-chan struct{} { channelReceiver := func(channel chan struct{}) <-chan struct{} {
@ -77,7 +80,7 @@ func Test_AmqpConnection_IsClosed(t *testing.T) {
close(channel) close(channel)
amqpConnMock := &amqpConnMock{} amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Done").Return(channelReceiver(channel)) amqpConnMock.On("Done").Return(channelReceiver(channel))
connection.conn = amqpConnMock connection.Conn = amqpConnMock
assert.True(t, connection.IsClosed()) assert.True(t, connection.IsClosed())
}) })
@ -86,7 +89,7 @@ func Test_AmqpConnection_IsClosed(t *testing.T) {
channel := make(chan struct{}) channel := make(chan struct{})
amqpConnMock := &amqpConnMock{} amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Done").Return(channelReceiver(channel)) amqpConnMock.On("Done").Return(channelReceiver(channel))
connection.conn = amqpConnMock connection.Conn = amqpConnMock
assert.False(t, connection.IsClosed()) assert.False(t, connection.IsClosed())
}) })
@ -94,8 +97,8 @@ func Test_AmqpConnection_IsClosed(t *testing.T) {
func Test_AmqpConnection_Close(t *testing.T) { func Test_AmqpConnection_Close(t *testing.T) {
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
} }
t.Run("already closed", func(t *testing.T) { t.Run("already closed", func(t *testing.T) {
@ -107,66 +110,66 @@ func Test_AmqpConnection_Close(t *testing.T) {
amqpConnMock := &amqpConnMock{} amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Close").Return(err) amqpConnMock.On("Close").Return(err)
connection.conn = amqpConnMock connection.Conn = amqpConnMock
assert.EqualError(t, connection.Close(), "internal close: connection close: test error") assert.EqualError(t, connection.Close(), "internal close: connection close: test error")
assert.NotNil(t, connection.conn) assert.NotNil(t, connection.Conn)
amqpConnMock.AssertNumberOfCalls(t, "Close", 1) amqpConnMock.AssertNumberOfCalls(t, "Close", 1)
}) })
t.Run("close without error", func(t *testing.T) { t.Run("close without error", func(t *testing.T) {
amqpConnMock := &amqpConnMock{} amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Close").Return(nil) amqpConnMock.On("Close").Return(nil)
connection.conn = amqpConnMock connection.Conn = amqpConnMock
assert.Nil(t, connection.Close()) assert.Nil(t, connection.Close())
assert.Nil(t, connection.conn) assert.Nil(t, connection.Conn)
amqpConnMock.AssertNumberOfCalls(t, "Close", 1) amqpConnMock.AssertNumberOfCalls(t, "Close", 1)
}) })
} }
func Test_AmqpConnection_Connect(t *testing.T) { func Test_AmqpConnection_Connect(t *testing.T) {
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
} }
t.Run("already connected", func(t *testing.T) { t.Run("already connected", func(t *testing.T) {
connection.conn = &amqpConnMock{} connection.Conn = &amqpConnMock{}
assert.NoError(t, connection.Connect()) assert.NoError(t, connection.Connect())
}) })
t.Run("dial error", func(t *testing.T) { t.Run("dial error", func(t *testing.T) {
connection.conn = nil connection.Conn = nil
connection.username = "user" connection.Username = "user"
connection.password = "pass" connection.Password = "pass"
amqpDialMock := &amqpDialMock{} amqpDialMock := &amqpDialMock{}
var c *amqpConnMock = nil var c *amqpConnMock = nil
amqpDialMock.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error")) amqpDialMock.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error"))
connection.dialer = amqpDialMock connection.Dialer = amqpDialMock
assert.EqualError(t, connection.Connect(), "internal connect: dial: test error") assert.EqualError(t, connection.Connect(), "internal connect: dial: test error")
assert.Nil(t, connection.conn) assert.Nil(t, connection.Conn)
}) })
t.Run("connect without error", func(t *testing.T) { t.Run("connect without error", func(t *testing.T) {
connection.conn = nil connection.Conn = nil
amqpDialMock := &amqpDialMock{} amqpDialMock := &amqpDialMock{}
amqpConn := &amqpConnMock{} amqpConn := &amqpConnMock{}
amqpDialMock.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(amqpConn, nil) amqpDialMock.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(amqpConn, nil)
connection.dialer = amqpDialMock connection.Dialer = amqpDialMock
assert.NoError(t, connection.Connect()) assert.NoError(t, connection.Connect())
assert.Equal(t, amqpConn, connection.conn) assert.Equal(t, amqpConn, connection.Conn)
}) })
} }
func Test_AmqpConnection_NewSender(t *testing.T) { func Test_AmqpConnection_NewSender(t *testing.T) {
connection := &AmqpConnection{ connection := &AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
} }
channelReceiver := func(channel chan struct{}) <-chan struct{} { channelReceiver := func(channel chan struct{}) <-chan struct{} {
@ -185,7 +188,7 @@ func Test_AmqpConnection_NewSender(t *testing.T) {
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel)) conn.On("Done").Return(channelReceiver(channel))
connection.conn = conn connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic") sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "amqp connection is closed") assert.EqualError(t, err, "amqp connection is closed")
@ -199,7 +202,7 @@ func Test_AmqpConnection_NewSender(t *testing.T) {
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, errors.New("test error")) conn.On("NewSession", mock.Anything, mock.Anything).Return(session, errors.New("test error"))
conn.On("Done").Return(channelReceiver(channel)) conn.On("Done").Return(channelReceiver(channel))
connection.conn = conn connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic") sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "new session: test error") assert.EqualError(t, err, "new session: test error")
@ -217,7 +220,7 @@ func Test_AmqpConnection_NewSender(t *testing.T) {
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel)) conn.On("Done").Return(channelReceiver(channel))
conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil) conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil)
connection.conn = conn connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic") sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "new internal sender: test error") assert.EqualError(t, err, "new internal sender: test error")
@ -235,7 +238,7 @@ func Test_AmqpConnection_NewSender(t *testing.T) {
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel)) conn.On("Done").Return(channelReceiver(channel))
conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil) conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil)
connection.conn = conn connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic") sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "new internal sender: test error\nclose session: close error") assert.EqualError(t, err, "new internal sender: test error\nclose session: close error")
@ -252,29 +255,29 @@ func Test_AmqpConnection_NewSender(t *testing.T) {
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel)) conn.On("Done").Return(channelReceiver(channel))
conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil) conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil)
connection.conn = conn connection.Conn = conn
sender, err := connection.NewSender(context.Background(), "topic") sender, err := connection.NewSender(context.Background(), "topic")
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, sender) assert.NotNil(t, sender)
assert.Equal(t, amqpSender, sender.sender) assert.Equal(t, amqpSender, sender.Sender)
assert.Equal(t, sessionMock, sender.session) assert.Equal(t, sessionMock, sender.Session)
}) })
} }
func Test_AmqpConnection_NewAmqpConnection(t *testing.T) { func Test_AmqpConnection_NewAmqpConnection(t *testing.T) {
config := AmqpConnectionConfig{ config := pkgCommon.AmqpConnectionConfig{
BrokerUrl: "brokerUrl", BrokerUrl: "brokerUrl",
Username: "username", Username: "username",
Password: "password", Password: "password",
} }
connection := NewAmqpConnection(config, "connectionName") connection := NewAmqpConnection(config, "connectionName")
assert.NotNil(t, connection) assert.NotNil(t, connection)
assert.Equal(t, connection.connectionName, "connectionName") assert.Equal(t, connection.ConnectionName, "connectionName")
assert.Equal(t, connection.brokerUrl, "brokerUrl") assert.Equal(t, connection.BrokerUrl, "brokerUrl")
assert.Equal(t, connection.username, "username") assert.Equal(t, connection.Username, "username")
assert.Equal(t, connection.password, "password") assert.Equal(t, connection.Password, "password")
assert.NotNil(t, connection.dialer) assert.NotNil(t, connection.Dialer)
} }
func Test_As(t *testing.T) { func Test_As(t *testing.T) {

View file

@ -4,19 +4,22 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/Azure/go-amqp"
"strings" "strings"
"time" "time"
"github.com/Azure/go-amqp"
) )
type amqpSender interface { const amqpTopicPrefix = "topic://"
type AmqpSender interface {
Send(ctx context.Context, msg *amqp.Message, opts *amqp.SendOptions) error Send(ctx context.Context, msg *amqp.Message, opts *amqp.SendOptions) error
Close(ctx context.Context) error Close(ctx context.Context) error
} }
type AmqpSenderSession struct { type AmqpSenderSession struct {
session amqpSession Session AmqpSession
sender amqpSender Sender AmqpSender
} }
func (s *AmqpSenderSession) Send( func (s *AmqpSenderSession) Send(
@ -26,11 +29,11 @@ func (s *AmqpSenderSession) Send(
applicationProperties map[string]any, applicationProperties map[string]any,
) error { ) error {
// check topic name // check topic name
if !strings.HasPrefix(topic, AmqpTopicPrefix) { if !strings.HasPrefix(topic, amqpTopicPrefix) {
return fmt.Errorf( return fmt.Errorf(
"topic %q name lacks mandatory prefix %q", "topic %q name lacks mandatory prefix %q",
topic, topic,
AmqpTopicPrefix, amqpTopicPrefix,
) )
} }
@ -54,7 +57,7 @@ func (s *AmqpSenderSession) Send(
// send // send
ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn() defer cancelFn()
return s.sender.Send(ctx, &message, nil) return s.Sender.Send(ctx, &message, nil)
} }
func (s *AmqpSenderSession) Close() error { func (s *AmqpSenderSession) Close() error {
@ -62,11 +65,11 @@ func (s *AmqpSenderSession) Close() error {
defer cancelFn() defer cancelFn()
var closeErrors []error var closeErrors []error
senderErr := s.sender.Close(ctx) senderErr := s.Sender.Close(ctx)
if senderErr != nil { if senderErr != nil {
closeErrors = append(closeErrors, senderErr) closeErrors = append(closeErrors, senderErr)
} }
sessionErr := s.session.Close(ctx) sessionErr := s.Session.Close(ctx)
if sessionErr != nil { if sessionErr != nil {
closeErrors = append(closeErrors, sessionErr) closeErrors = append(closeErrors, sessionErr)
} }

View file

@ -3,10 +3,11 @@ package messaging
import ( import (
"context" "context"
"errors" "errors"
"testing"
"github.com/Azure/go-amqp" "github.com/Azure/go-amqp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"testing"
) )
type amqpSenderMock struct { type amqpSenderMock struct {
@ -21,7 +22,7 @@ func (m *amqpSenderMock) Close(ctx context.Context) error {
return m.Called(ctx).Error(0) return m.Called(ctx).Error(0)
} }
var _ amqpSender = (*amqpSenderMock)(nil) var _ AmqpSender = (*amqpSenderMock)(nil)
func Test_AmqpSenderSession_Close(t *testing.T) { func Test_AmqpSenderSession_Close(t *testing.T) {
@ -32,8 +33,8 @@ func Test_AmqpSenderSession_Close(t *testing.T) {
session.On("Close", mock.Anything).Return(nil) session.On("Close", mock.Anything).Return(nil)
senderSession := &AmqpSenderSession{ senderSession := &AmqpSenderSession{
sender: sender, Sender: sender,
session: session, Session: session,
} }
err := senderSession.Close() err := senderSession.Close()
@ -50,8 +51,8 @@ func Test_AmqpSenderSession_Close(t *testing.T) {
session.On("Close", mock.Anything).Return(nil) session.On("Close", mock.Anything).Return(nil)
senderSession := &AmqpSenderSession{ senderSession := &AmqpSenderSession{
sender: sender, Sender: sender,
session: session, Session: session,
} }
err := senderSession.Close() err := senderSession.Close()
@ -68,8 +69,8 @@ func Test_AmqpSenderSession_Close(t *testing.T) {
session.On("Close", mock.Anything).Return(errors.New("session error")) session.On("Close", mock.Anything).Return(errors.New("session error"))
senderSession := &AmqpSenderSession{ senderSession := &AmqpSenderSession{
sender: sender, Sender: sender,
session: session, Session: session,
} }
err := senderSession.Close() err := senderSession.Close()
@ -86,8 +87,8 @@ func Test_AmqpSenderSession_Close(t *testing.T) {
session.On("Close", mock.Anything).Return(errors.New("session error")) session.On("Close", mock.Anything).Return(errors.New("session error"))
senderSession := &AmqpSenderSession{ senderSession := &AmqpSenderSession{
sender: sender, Sender: sender,
session: session, Session: session,
} }
err := senderSession.Close() err := senderSession.Close()
@ -105,8 +106,8 @@ func Test_AmqpSenderSession_Send(t *testing.T) {
session := &amqpSessionMock{} session := &amqpSessionMock{}
senderSession := &AmqpSenderSession{ senderSession := &AmqpSenderSession{
sender: sender, Sender: sender,
session: session, Session: session,
} }
data := [][]byte{[]byte("data")} data := [][]byte{[]byte("data")}
@ -119,8 +120,8 @@ func Test_AmqpSenderSession_Send(t *testing.T) {
session := &amqpSessionMock{} session := &amqpSessionMock{}
senderSession := &AmqpSenderSession{ senderSession := &AmqpSenderSession{
sender: sender, Sender: sender,
session: session, Session: session,
} }
data := [][]byte{[]byte("data")} data := [][]byte{[]byte("data")}
@ -134,8 +135,8 @@ func Test_AmqpSenderSession_Send(t *testing.T) {
session := &amqpSessionMock{} session := &amqpSessionMock{}
senderSession := &AmqpSenderSession{ senderSession := &AmqpSenderSession{
sender: sender, Sender: sender,
session: session, Session: session,
} }
data := [][]byte{[]byte("data")} data := [][]byte{[]byte("data")}
@ -171,8 +172,8 @@ func Test_AmqpSenderSession_Send(t *testing.T) {
session := &amqpSessionMock{} session := &amqpSessionMock{}
senderSession := &AmqpSenderSession{ senderSession := &AmqpSenderSession{
sender: sender, Sender: sender,
session: session, Session: session,
} }
data := [][]byte{[]byte("data")} data := [][]byte{[]byte("data")}

View file

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

View file

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

View file

@ -4,18 +4,22 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"go.opentelemetry.io/otel"
"net/url" "net/url"
"strings" "strings"
"testing" "testing"
"time" "time"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging" "buf.build/go/protovalidate"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"go.opentelemetry.io/otel"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
pkgMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
pkgMessagingTest "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/test"
) )
func TestDynamicLegacyAuditApi(t *testing.T) { func TestDynamicLegacyAuditApi(t *testing.T) {
@ -25,14 +29,14 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
defer cancelFn() defer cancelFn()
// Start solace docker container // Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background()) solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err) assert.NoError(t, err)
defer solaceContainer.Stop() defer solaceContainer.Stop()
// Instantiate the messaging api // Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi( amqpApi, err := pkgMessagingApi.NewAmqpApi(
messaging.AmqpConnectionPoolConfig{ pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: messaging.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString}, Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1, PoolSize: 1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -58,14 +62,14 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1) event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -74,8 +78,8 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
), ErrUnsupportedEventTypeDataAccess) ), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
}) })
// Check logging of organization events // Check logging of organization events
@ -92,13 +96,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -107,7 +111,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -129,13 +133,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -144,7 +148,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -167,13 +171,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -182,7 +186,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -204,13 +208,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -219,7 +223,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -242,13 +246,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -257,7 +261,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -279,13 +283,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -294,7 +298,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -316,13 +320,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event := newProjectSystemAuditEvent(nil) event := internalAuditApi.NewProjectSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -332,7 +336,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -345,7 +349,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName) assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
@ -372,13 +376,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -388,7 +392,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -401,7 +405,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName) assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -427,13 +431,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi( auditApi, err := NewDynamicLegacyAuditApi(
messagingApi, amqpApi,
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
escapedQuery := url.QueryEscape("param=value") escapedQuery := url.QueryEscape("param=value")
event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery
@ -444,7 +448,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic, ctxWithTopic,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -465,15 +469,15 @@ func TestDynamicLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{ auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
@ -482,22 +486,22 @@ func TestDynamicLegacyAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{ auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
func TestDynamicLegacyAuditApi_Log_NilEvent(t *testing.T) { func TestDynamicLegacyAuditApi_Log_NilEvent(t *testing.T) {
auditApi := DynamicLegacyAuditApi{tracer: otel.Tracer("test")} auditApi := DynamicLegacyAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
} }
func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) { func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) {
@ -505,16 +509,16 @@ func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectI
objectIdentifier *auditV1.ObjectIdentifier) { objectIdentifier *auditV1.ObjectIdentifier) {
objectIdentifier.Type = "invalid" objectIdentifier.Type = "invalid"
} }
event, objectIdentifier := newProjectAuditEvent(&customization) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(&customization)
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(nil) validator.On("Validate", mock.Anything).Return(nil)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{ auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType) assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
} }

View file

@ -5,19 +5,23 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"go.opentelemetry.io/otel"
"net/url" "net/url"
"strings" "strings"
"testing" "testing"
"time" "time"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging" "buf.build/go/protovalidate"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/Azure/go-amqp" "github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"go.opentelemetry.io/otel"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
pkgMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
pkgMessagingTest "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/test"
) )
func TestLegacyAuditApi(t *testing.T) { func TestLegacyAuditApi(t *testing.T) {
@ -27,13 +31,13 @@ func TestLegacyAuditApi(t *testing.T) {
defer cancelFn() defer cancelFn()
// Start solace docker container // Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background()) solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err) assert.NoError(t, err)
defer solaceContainer.Stop() defer solaceContainer.Stop()
// Instantiate the messaging api // Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConnectionPoolConfig{ amqpApi, err := pkgMessagingApi.NewAmqpApi(pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: messaging.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString}, Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1, PoolSize: 1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -59,23 +63,23 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1) event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
// Log the event to solace // Log the event to solace
assert.ErrorIs(t, auditApi.Log( assert.ErrorIs(t, auditApi.Log(
ctx, ctx,
event, event,
auditV1.Visibility_VISIBILITY_PUBLIC, auditV1.Visibility_VISIBILITY_PUBLIC,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
), ErrUnsupportedEventTypeDataAccess) ), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
}) })
// Check logging of organization events // Check logging of organization events
@ -92,14 +96,14 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -107,7 +111,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -129,14 +133,14 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -144,7 +148,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -167,14 +171,14 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -182,7 +186,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -204,14 +208,14 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -219,7 +223,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -242,14 +246,14 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -257,7 +261,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -279,14 +283,14 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -294,7 +298,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -316,14 +320,14 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event := newProjectSystemAuditEvent(nil) event := internalAuditApi.NewProjectSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -332,7 +336,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -345,7 +349,7 @@ func TestLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName) assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
@ -372,14 +376,14 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -388,7 +392,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -401,7 +405,7 @@ func TestLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName) assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -427,14 +431,14 @@ func TestLegacyAuditApi(t *testing.T) {
// Instantiate audit api // Instantiate audit api
auditApi, err := NewLegacyAuditApi( auditApi, err := NewLegacyAuditApi(
messagingApi, amqpApi,
LegacyTopicNameConfig{TopicName: topicName}, StaticTopicNameConfig{TopicName: topicName},
validator, validator,
) )
assert.NoError(t, err) assert.NoError(t, err)
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
escapedQuery := url.QueryEscape("param=value") escapedQuery := url.QueryEscape("param=value")
event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery
@ -444,7 +448,7 @@ func TestLegacyAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier), pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
)) ))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -467,7 +471,7 @@ func validateSentMessage(
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
var severity string var severity string
@ -513,13 +517,13 @@ func validateSentMessageWithDetails(
// Check topic name // Check topic name
assert.Equal(t, topicName, *message.Properties.To) assert.Equal(t, topicName, *message.Properties.To)
assert.Equal(t, ContentTypeCloudEventsJson, message.ApplicationProperties["cloudEvents:datacontenttype"]) assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsJson, message.ApplicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, DataTypeLegacyAuditEventV1, message.ApplicationProperties["cloudEvents:type"]) assert.Equal(t, DataTypeLegacyAuditEventV1, message.ApplicationProperties["cloudEvents:type"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message // Check deserialized message
var auditEvent LegacyAuditEvent var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName) assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -570,20 +574,20 @@ func validateSentMessageWithDetails(
func TestLegacyAuditApi_NewLegacyAuditApi(t *testing.T) { func TestLegacyAuditApi_NewLegacyAuditApi(t *testing.T) {
t.Run("messaging api nil", func(t *testing.T) { t.Run("messaging api nil", func(t *testing.T) {
auditApi, err := NewLegacyAuditApi(nil, LegacyTopicNameConfig{}, nil) auditApi, err := NewLegacyAuditApi(nil, StaticTopicNameConfig{}, nil)
assert.Nil(t, auditApi) assert.Nil(t, auditApi)
assert.EqualError(t, err, "messaging api nil") assert.EqualError(t, err, "messaging api nil")
}) })
t.Run("topic name is blank", func(t *testing.T) { t.Run("topic name is blank", func(t *testing.T) {
// Start solace docker container // Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background()) solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err) assert.NoError(t, err)
defer solaceContainer.Stop() defer solaceContainer.Stop()
// Instantiate the messaging api // Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConnectionPoolConfig{ amqpApi, err := pkgMessagingApi.NewAmqpApi(pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: messaging.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString}, Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1, PoolSize: 1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -592,7 +596,7 @@ func TestLegacyAuditApi_NewLegacyAuditApi(t *testing.T) {
validator, err := protovalidate.New() validator, err := protovalidate.New()
assert.NoError(t, err) assert.NoError(t, err)
auditApi, err := NewLegacyAuditApi(messagingApi, LegacyTopicNameConfig{ auditApi, err := NewLegacyAuditApi(amqpApi, StaticTopicNameConfig{
TopicName: "", TopicName: "",
}, validator) }, validator)
@ -606,15 +610,15 @@ func TestLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := LegacyAuditApi{ auditApi := LegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
@ -623,22 +627,22 @@ func TestLegacyAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := LegacyAuditApi{ auditApi := LegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
func TestLegacyAuditApi_Log_NilEvent(t *testing.T) { func TestLegacyAuditApi_Log_NilEvent(t *testing.T) {
auditApi := LegacyAuditApi{tracer: otel.Tracer("test")} auditApi := LegacyAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
} }
func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) { func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) {
@ -646,16 +650,16 @@ func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifi
objectIdentifier *auditV1.ObjectIdentifier) { objectIdentifier *auditV1.ObjectIdentifier) {
objectIdentifier.Type = "invalid" objectIdentifier.Type = "invalid"
} }
event, objectIdentifier := newProjectAuditEvent(&customization) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(&customization)
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(nil) validator.On("Validate", mock.Anything).Return(nil)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := LegacyAuditApi{ auditApi := LegacyAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier)) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType) assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
} }

View file

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

View file

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

View file

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

View file

@ -4,21 +4,24 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"go.opentelemetry.io/otel"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/google/uuid" "buf.build/go/protovalidate"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/Azure/go-amqp" "github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"go.opentelemetry.io/otel"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
pgkMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
pkgMessagingTest "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/test"
) )
func TestRoutableAuditApi(t *testing.T) { func TestRoutableAuditApi(t *testing.T) {
@ -28,13 +31,13 @@ func TestRoutableAuditApi(t *testing.T) {
defer cancelFn() defer cancelFn()
// Start solace docker container // Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background()) solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err) assert.NoError(t, err)
defer solaceContainer.Stop() defer solaceContainer.Stop()
// Instantiate the messaging api // Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConnectionPoolConfig{ amqpApi, err := pkgMessagingApi.NewAmqpApi(pgkMessagingCommon.AmqpConnectionPoolConfig{
Parameters: messaging.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString}, Parameters: pgkMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1, PoolSize: 1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -50,7 +53,7 @@ func TestRoutableAuditApi(t *testing.T) {
systemTopicName := "topic://system/admin-events" systemTopicName := "topic://system/admin-events"
auditApi, err := newRoutableAuditApi( auditApi, err := newRoutableAuditApi(
messagingApi, amqpApi,
topicNameConfig{ topicNameConfig{
FolderTopicPrefix: folderTopicPrefix, FolderTopicPrefix: folderTopicPrefix,
OrganizationTopicPrefix: organizationTopicPrefix, OrganizationTopicPrefix: organizationTopicPrefix,
@ -71,8 +74,8 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1) event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -80,7 +83,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier)), ErrUnsupportedEventTypeDataAccess) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
}) })
// Check logging of organization events // Check logging of organization events
@ -93,7 +96,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -101,7 +104,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err) assert.NoError(t, err)
@ -124,7 +127,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
topicName := fmt.Sprintf("org/%s", objectIdentifier.Identifier) topicName := fmt.Sprintf("org/%s", objectIdentifier.Identifier)
assert.NoError( assert.NoError(
t, t,
@ -137,7 +140,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace // Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -163,7 +166,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -171,7 +174,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err) assert.NoError(t, err)
@ -194,7 +197,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
topicName := fmt.Sprintf("folder/%s", objectIdentifier.Identifier) topicName := fmt.Sprintf("folder/%s", objectIdentifier.Identifier)
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicName))
@ -205,7 +208,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace // Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -230,7 +233,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -239,7 +242,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace // Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -263,7 +266,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -272,7 +275,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace // Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -297,7 +300,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*"))
// Instantiate test data // Instantiate test data
event := newProjectSystemAuditEvent(nil) event := internalAuditApi.NewProjectSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -306,7 +309,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -332,7 +335,7 @@ func TestRoutableAuditApi(t *testing.T) {
validateRoutableEventPayload( validateRoutableEventPayload(
t, t,
message.Data[0], message.Data[0],
RoutableSystemIdentifier.ToObjectIdentifier(), pkgAuditCommon.RoutableSystemIdentifier.ToObjectIdentifier(),
event, event,
"stackit.resourcemanager.v2.system.changed", "stackit.resourcemanager.v2.system.changed",
visibility) visibility)
@ -347,7 +350,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*"))
// Instantiate test data // Instantiate test data
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -356,7 +359,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
RoutableSystemIdentifier, pkgAuditCommon.RoutableSystemIdentifier,
)) ))
// Receive the event from solace // Receive the event from solace
@ -382,7 +385,7 @@ func TestRoutableAuditApi(t *testing.T) {
validateRoutableEventPayload( validateRoutableEventPayload(
t, t,
message.Data[0], message.Data[0],
SystemIdentifier, pkgAuditCommon.SystemIdentifier,
event, event,
"stackit.resourcemanager.v2.system.changed", "stackit.resourcemanager.v2.system.changed",
visibility) visibility)
@ -398,7 +401,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*")) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data // Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil) event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace // Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -406,7 +409,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx, ctx,
event, event,
visibility, visibility,
NewRoutableIdentifier(objectIdentifier))) pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err) assert.NoError(t, err)
@ -444,7 +447,7 @@ func validateSentEvent(
_, isUuid := uuid.Parse(fmt.Sprintf("%s", applicationProperties["cloudEvents:id"])) _, isUuid := uuid.Parse(fmt.Sprintf("%s", applicationProperties["cloudEvents:id"]))
assert.True(t, true, isUuid) assert.True(t, true, isUuid)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().UnixMilli(), applicationProperties["cloudEvents:time"]) assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"]) assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "audit.v1.RoutableAuditEvent", applicationProperties["cloudEvents:type"]) assert.Equal(t, "audit.v1.RoutableAuditEvent", applicationProperties["cloudEvents:type"])
assert.Equal(t, "", applicationProperties["cloudEvents:traceparent"]) assert.Equal(t, "", applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", applicationProperties["cloudEvents:tracestate"]) assert.Equal(t, "", applicationProperties["cloudEvents:tracestate"])
@ -486,8 +489,8 @@ func validateRoutableEventPayload(
func TestRoutableTopicNameResolver_Resolve_UnsupportedIdentifierType(t *testing.T) { func TestRoutableTopicNameResolver_Resolve_UnsupportedIdentifierType(t *testing.T) {
resolver := routableTopicNameResolver{} resolver := routableTopicNameResolver{}
_, err := resolver.Resolve(NewRoutableIdentifier(&auditV1.ObjectIdentifier{Type: "unsupported"})) _, err := resolver.Resolve(pkgAuditCommon.NewRoutableIdentifier(&auditV1.ObjectIdentifier{Type: "unsupported"}))
assert.ErrorIs(t, err, ErrUnsupportedObjectIdentifierType) assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedObjectIdentifierType)
} }
func TestNewRoutableAuditApi_NewRoutableAuditApi_MessagingApiNil(t *testing.T) { func TestNewRoutableAuditApi_NewRoutableAuditApi_MessagingApiNil(t *testing.T) {
@ -501,15 +504,15 @@ func TestRoutableAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := routableAuditApi{ auditApi := routableAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) _, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
@ -518,20 +521,20 @@ func TestRoutableAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{} validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError) validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := routableAuditApi{ auditApi := routableAuditApi{
tracer: otel.Tracer("test"), tracer: otel.Tracer("test"),
validator: protobufValidator, validator: protobufValidator,
} }
event := newSystemAuditEvent(nil) event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError) assert.ErrorIs(t, err, expectedError)
} }
func TestRoutableAuditApi_Log_NilEvent(t *testing.T) { func TestRoutableAuditApi_Log_NilEvent(t *testing.T) {
auditApi := routableAuditApi{tracer: otel.Tracer("test")} auditApi := routableAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier) err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil) assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
} }

View file

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

View file

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

View file

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

View file

@ -2,17 +2,21 @@ package api
import ( import (
"context" "context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/utils"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"fmt" "fmt"
"github.com/bufbuild/protovalidate-go" "testing"
"time"
"buf.build/go/protovalidate"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb" "google.golang.org/protobuf/types/known/wrapperspb"
"testing"
"time" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgAuditUtils "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/utils"
) )
func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) { func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
@ -40,7 +44,7 @@ func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
objectId, objectType, err := getObjectIdAndTypeFromAuditParams( objectId, objectType, err := getObjectIdAndTypeFromAuditParams(
&AuditParameters{ &AuditParameters{
ObjectId: "value", ObjectId: "value",
ObjectType: ObjectTypeFromPluralString("invalid"), ObjectType: pkgAuditCommon.ObjectTypeFromPluralString("invalid"),
}, },
) )
assert.EqualError(t, err, "unknown object type") assert.EqualError(t, err, "unknown object type")
@ -54,12 +58,12 @@ func Test_getObjectIdAndTypeFromAuditParams(t *testing.T) {
objectId, objectType, err := getObjectIdAndTypeFromAuditParams( objectId, objectType, err := getObjectIdAndTypeFromAuditParams(
&AuditParameters{ &AuditParameters{
ObjectId: "value", ObjectId: "value",
ObjectType: ObjectTypeProject, ObjectType: pkgAuditCommon.ObjectTypeProject,
}, },
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "value", objectId) assert.Equal(t, "value", objectId)
assert.Equal(t, ObjectTypeProject, *objectType) assert.Equal(t, pkgAuditCommon.ObjectTypeProject, *objectType)
}, },
) )
} }
@ -76,7 +80,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
t.Run("details missing", func(t *testing.T) { t.Run("details missing", func(t *testing.T) {
logEntry, err := NewAuditLogEntryBuilder().WithRequiredLocation("eu01"). logEntry, err := NewAuditLogEntryBuilder().WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
Build(context.Background(), SequenceNumber(1)) Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err) assert.NoError(t, err)
@ -93,16 +97,16 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -195,16 +199,16 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -215,7 +219,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
WithAuditPermission(permission). WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult). WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details). WithDetails(details).
WithEventType(EventTypePolicyDenied). WithEventType(pkgAuditCommon.EventTypePolicyDenied).
WithLabels(map[string]string{"key": "label"}). WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)). WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId"). WithRequestCorrelationId("correlationId").
@ -310,7 +314,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredServiceName("demo-service"). WithRequiredServiceName("demo-service").
WithRequiredWorkerId("worker-id"). WithRequiredWorkerId("worker-id").
@ -331,7 +335,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail) assert.Equal(t, internalAuditApi.EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId) assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -399,16 +403,16 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -419,7 +423,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
WithAuditPermission(permission). WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult). WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details). WithDetails(details).
WithEventType(EventTypeSystemEvent). WithEventType(pkgAuditCommon.EventTypeSystemEvent).
WithLabels(map[string]string{"key": "label"}). WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)). WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId"). WithRequestCorrelationId("correlationId").
@ -451,21 +455,21 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
responseBodyBytes, err := ResponseBodyToBytes(responseBody) responseBodyBytes, err := ResponseBodyToBytes(responseBody)
assert.NoError(t, err) assert.NoError(t, err)
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
}). }).
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1"). WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service"). WithRequiredServiceName("demo-service").
@ -480,21 +484,21 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
t.Run("with invalid response body", func(t *testing.T) { t.Run("with invalid response body", func(t *testing.T) {
builder := NewAuditLogEntryBuilder(). builder := NewAuditLogEntryBuilder().
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
}). }).
WithRequiredLocation("eu01"). WithRequiredLocation("eu01").
WithRequiredObjectId("1"). WithRequiredObjectId("1").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.operation"). WithRequiredOperation("stackit.demo-service.v1.operation").
WithRequiredRequestClientIp("127.0.0.1"). WithRequiredRequestClientIp("127.0.0.1").
WithRequiredServiceName("demo-service"). WithRequiredServiceName("demo-service").
@ -511,7 +515,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("nothing set", func(t *testing.T) { t.Run("nothing set", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
Build(context.Background(), SequenceNumber(1)) Build(context.Background(), SequenceNumber(1))
@ -524,11 +528,11 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("details missing", func(t *testing.T) { t.Run("details missing", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId("objectId"). WithRequiredObjectId("objectId").
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
Build(context.Background(), SequenceNumber(1)) Build(context.Background(), SequenceNumber(1))
assert.Error(t, err) assert.Error(t, err)
@ -539,29 +543,29 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("required only", func(t *testing.T) { t.Run("required only", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString() objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId). WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation). WithRequiredOperation(operation).
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
}). }).
WithRequiredRequestClientIp("127.0.0.1") WithRequiredRequestClientIp("127.0.0.1")
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject} routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.ObjectTypeProject}
cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err) assert.NoError(t, err)
@ -663,7 +667,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("with details", func(t *testing.T) { t.Run("with details", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString() objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
@ -677,16 +681,16 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId). WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation). WithRequiredOperation(operation).
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -695,7 +699,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithAuditPermission(permission). WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult). WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details). WithDetails(details).
WithEventType(EventTypeAdminActivity). WithEventType(pkgAuditCommon.EventTypeAdminActivity).
WithLabels(map[string]string{"key": "label"}). WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)). WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId"). WithRequestCorrelationId("correlationId").
@ -708,7 +712,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithStatusCode(400). WithStatusCode(400).
WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE) WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE)
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject} routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.ObjectTypeProject}
cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err) assert.NoError(t, err)
@ -817,13 +821,13 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("system event with object reference", func(t *testing.T) { t.Run("system event with object reference", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString() objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId). WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation). WithRequiredOperation(operation).
AsSystemEvent() AsSystemEvent()
@ -831,8 +835,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, builder.IsBuilt()) assert.True(t, builder.IsBuilt())
assert.Equal(t, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
assert.NotNil(t, cloudEvent) assert.NotNil(t, cloudEvent)
assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType) assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType)
@ -849,8 +853,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NotNil(t, cloudEvent.Data) assert.NotNil(t, cloudEvent.Data)
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent)) assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
assert.Equal(t, SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type)
assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility) assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility)
assert.Equal(t, operation, routableAuditEvent.OperationName) assert.Equal(t, operation, routableAuditEvent.OperationName)
@ -869,7 +873,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail) assert.Equal(t, internalAuditApi.EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId) assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -929,7 +933,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("system event", func(t *testing.T) { t.Run("system event", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
@ -940,8 +944,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, builder.IsBuilt()) assert.True(t, builder.IsBuilt())
assert.Equal(t, SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routingIdentifier.ToObjectIdentifier().Identifier)
assert.Equal(t, SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routingIdentifier.ToObjectIdentifier().Type)
assert.NotNil(t, cloudEvent) assert.NotNil(t, cloudEvent)
assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType) assert.Equal(t, "application/cloudevents+protobuf", cloudEvent.DataContentType)
@ -958,8 +962,8 @@ func Test_AuditEventBuilder(t *testing.T) {
assert.NotNil(t, cloudEvent.Data) assert.NotNil(t, cloudEvent.Data)
assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent)) assert.NoError(t, proto.Unmarshal(cloudEvent.Data, &routableAuditEvent))
assert.Equal(t, SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Identifier, routableAuditEvent.ObjectIdentifier.Identifier)
assert.Equal(t, SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type) assert.Equal(t, pkgAuditCommon.SystemIdentifier.Type, routableAuditEvent.ObjectIdentifier.Type)
assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility) assert.Equal(t, auditV1.Visibility_VISIBILITY_PRIVATE, routableAuditEvent.Visibility)
assert.Equal(t, operation, routableAuditEvent.OperationName) assert.Equal(t, operation, routableAuditEvent.OperationName)
@ -978,7 +982,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo) assert.NotNil(t, authenticationInfo)
assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail) assert.Equal(t, internalAuditApi.EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId) assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -1038,7 +1042,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("with response body unserialized", func(t *testing.T) { t.Run("with response body unserialized", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
objectId := uuid.NewString() objectId := uuid.NewString()
operation := "stackit.demo-service.v1.operation" operation := "stackit.demo-service.v1.operation"
@ -1050,16 +1054,16 @@ func Test_AuditEventBuilder(t *testing.T) {
responseBody := map[string]interface{}{"key": "response"} responseBody := map[string]interface{}{"key": "response"}
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithRequiredObjectId(objectId). WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject). WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation(operation). WithRequiredOperation(operation).
WithRequiredApiRequest(ApiRequest{ WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil, Body: nil,
Header: TestHeaders, Header: internalAuditApi.TestHeaders,
Host: "localhost", Host: "localhost",
Method: "POST", Method: "POST",
Scheme: "https", Scheme: "https",
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
URL: RequestUrl{ URL: pkgAuditCommon.RequestUrl{
Path: "/", Path: "/",
RawQuery: nil, RawQuery: nil,
}, },
@ -1068,7 +1072,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithAuditPermission(permission). WithAuditPermission(permission).
WithAuditPermissionCheckResult(permissionCheckResult). WithAuditPermissionCheckResult(permissionCheckResult).
WithDetails(details). WithDetails(details).
WithEventType(EventTypeAdminActivity). WithEventType(pkgAuditCommon.EventTypeAdminActivity).
WithLabels(map[string]string{"key": "label"}). WithLabels(map[string]string{"key": "label"}).
WithNumResponseItems(int64(10)). WithNumResponseItems(int64(10)).
WithRequestCorrelationId("correlationId"). WithRequestCorrelationId("correlationId").
@ -1081,7 +1085,7 @@ func Test_AuditEventBuilder(t *testing.T) {
WithStatusCode(400). WithStatusCode(400).
WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE) WithVisibility(auditV1.Visibility_VISIBILITY_PRIVATE)
routableIdentifier := RoutableIdentifier{Identifier: objectId, Type: ObjectTypeProject} routableIdentifier := pkgAuditCommon.RoutableIdentifier{Identifier: objectId, Type: pkgAuditCommon.ObjectTypeProject}
cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1)) cloudEvent, routingIdentifier, err := builder.Build(context.Background(), SequenceNumber(1))
assert.NoError(t, err) assert.NoError(t, err)
@ -1111,7 +1115,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("mark as built", func(t *testing.T) { t.Run("mark as built", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01") builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
builder.MarkAsBuilt() builder.MarkAsBuilt()
@ -1121,7 +1125,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("no entry builder", func(t *testing.T) { t.Run("no entry builder", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01"). cloudEvent, routingIdentifier, err := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01").
WithAuditLogEntryBuilder(nil).Build(context.Background(), SequenceNumber(1)) WithAuditLogEntryBuilder(nil).Build(context.Background(), SequenceNumber(1))
@ -1133,7 +1137,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("next sequence number", func(t *testing.T) { t.Run("next sequence number", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01") builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber()) assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber())
@ -1142,7 +1146,7 @@ func Test_AuditEventBuilder(t *testing.T) {
t.Run("revert sequence number", func(t *testing.T) { t.Run("revert sequence number", func(t *testing.T) {
api, _ := NewMockAuditApi() api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator() sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01") builder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", "worker-id", "eu01")
assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber()) assert.Equal(t, SequenceNumber(0), builder.NextSequenceNumber())

View file

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

View file

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

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

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

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

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

View file

@ -1,17 +1,23 @@
package api package common
import ( import (
"context" "context"
"github.com/bufbuild/protovalidate-go" "regexp"
"time" "time"
"buf.build/go/protovalidate"
"github.com/google/uuid" "github.com/google/uuid"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"google.golang.org/protobuf/proto"
) )
// ContentTypeCloudEventsProtobuf the cloudevents protobuf content-type sent in metadata of messages
const ContentTypeCloudEventsProtobuf = "application/cloudevents+protobuf"
const ContentTypeCloudEventsJson = "application/cloudevents+json; charset=UTF-8"
var TopicNamePattern = regexp.MustCompile(`^topic://stackit-platform/t/swz/audit-log/(?:conway|eu01|eu02|sx-stoi01)/[Vv][1-9](?:\.\d)?/[A-Za-z0-9-]+/[A-Za-z0-9-/]+`)
type EventType string type EventType string
const ( const (
@ -218,6 +224,17 @@ type TopicNameResolver interface {
Resolve(routableIdentifier *RoutableIdentifier) (string, error) Resolve(routableIdentifier *RoutableIdentifier) (string, error)
} }
// StaticTopicNameTestResolver implements TopicNameResolver.
// A hard-coded topic name is used, routable identifiers are ignored.
type StaticTopicNameTestResolver struct {
TopicName string
}
// Resolve implements TopicNameResolver.Resolve
func (r *StaticTopicNameTestResolver) Resolve(*RoutableIdentifier) (string, error) {
return r.TopicName, nil
}
type RoutableIdentifier struct { type RoutableIdentifier struct {
Identifier string Identifier string
Type ObjectType Type ObjectType

View file

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

View file

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

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

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

View file

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

View file

@ -1,50 +1,30 @@
package messaging package api
import ( import (
"context" "context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/log"
"errors" "errors"
"fmt" "fmt"
"sync" "sync"
"time" "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"
) )
// 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
}
// AmqpApi implements Api. // AmqpApi implements Api.
type AmqpApi struct { type AmqpApi struct {
connection *AmqpConnection connection *internalMessaging.AmqpConnection
connectionPool ConnectionPool connectionPool internalMessaging.ConnectionPool
connectionPoolHandle *ConnectionPoolHandle connectionPoolHandle *internalMessaging.ConnectionPoolHandle
senderCache map[string]*AmqpSenderSession senderCache map[string]*internalMessaging.AmqpSenderSession
lock sync.RWMutex lock sync.RWMutex
} }
var _ Api = &AmqpApi{} var _ Api = &AmqpApi{}
func NewDefaultAmqpApi(amqpConfig AmqpConnectionConfig) (Api, error) { func NewDefaultAmqpApi(amqpConfig pkgMessagingCommon.AmqpConnectionConfig) (Api, error) {
connectionPool, err := NewDefaultAmqpConnectionPool(amqpConfig, "sdk") connectionPool, err := internalMessaging.NewDefaultAmqpConnectionPool(amqpConfig, "sdk")
if err != nil { if err != nil {
return nil, fmt.Errorf("new amqp connection pool: %w", err) return nil, fmt.Errorf("new amqp connection pool: %w", err)
} }
@ -52,15 +32,15 @@ func NewDefaultAmqpApi(amqpConfig AmqpConnectionConfig) (Api, error) {
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connectionPool: connectionPool, connectionPool: connectionPool,
connectionPoolHandle: connectionPool.NewHandle(), connectionPoolHandle: connectionPool.NewHandle(),
senderCache: make(map[string]*AmqpSenderSession), senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
} }
var messagingApi Api = amqpApi var messagingApi Api = amqpApi
return messagingApi, nil return messagingApi, nil
} }
func NewAmqpApi(amqpConfig AmqpConnectionPoolConfig) (Api, error) { func NewAmqpApi(amqpConfig pkgMessagingCommon.AmqpConnectionPoolConfig) (Api, error) {
connectionPool, err := NewAmqpConnectionPool(amqpConfig, "sdk") connectionPool, err := internalMessaging.NewAmqpConnectionPool(amqpConfig, "sdk")
if err != nil { if err != nil {
return nil, fmt.Errorf("new amqp connection pool: %w", err) return nil, fmt.Errorf("new amqp connection pool: %w", err)
} }
@ -68,7 +48,7 @@ func NewAmqpApi(amqpConfig AmqpConnectionPoolConfig) (Api, error) {
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connectionPool: connectionPool, connectionPool: connectionPool,
connectionPoolHandle: connectionPool.NewHandle(), connectionPoolHandle: connectionPool.NewHandle(),
senderCache: make(map[string]*AmqpSenderSession), senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
} }
var messagingApi Api = amqpApi var messagingApi Api = amqpApi
@ -110,7 +90,7 @@ func (a *AmqpApi) Send(_ context.Context, topic string, data []byte, contentType
return nil return nil
} }
func (a *AmqpApi) senderFromCache(topic string) *AmqpSenderSession { func (a *AmqpApi) senderFromCache(topic string) *internalMessaging.AmqpSenderSession {
a.lock.RLock() a.lock.RLock()
defer a.lock.RUnlock() defer a.lock.RUnlock()
return a.senderCache[topic] return a.senderCache[topic]
@ -142,7 +122,7 @@ func (a *AmqpApi) newSender(topic string) error {
// Close implements Api.Close // Close implements Api.Close
func (a *AmqpApi) Close(_ context.Context) error { func (a *AmqpApi) Close(_ context.Context) error {
log.AuditLogger.Info("close audit amqp connection pool") pkgLog.AuditLogger.Info("close audit amqp connection pool")
a.lock.Lock() a.lock.Lock()
defer a.lock.Unlock() defer a.lock.Unlock()

View file

@ -1,16 +1,73 @@
package messaging package api
import ( import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"sync" "sync"
"testing" "testing"
"time" "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 { type connectionPoolMock struct {
mock.Mock mock.Mock
} }
@ -19,20 +76,20 @@ func (m *connectionPoolMock) Close() error {
return m.Called().Error(0) return m.Called().Error(0)
} }
func (m *connectionPoolMock) NewHandle() *ConnectionPoolHandle { func (m *connectionPoolMock) NewHandle() *internalMessaging.ConnectionPoolHandle {
return m.Called().Get(0).(*ConnectionPoolHandle) return m.Called().Get(0).(*internalMessaging.ConnectionPoolHandle)
} }
func (m *connectionPoolMock) GetConnection(handle *ConnectionPoolHandle) (*AmqpConnection, error) { func (m *connectionPoolMock) GetConnection(handle *internalMessaging.ConnectionPoolHandle) (*internalMessaging.AmqpConnection, error) {
return m.Called(handle).Get(0).(*AmqpConnection), m.Called(handle).Error(1) return m.Called(handle).Get(0).(*internalMessaging.AmqpConnection), m.Called(handle).Error(1)
} }
var _ ConnectionPool = (*connectionPoolMock)(nil) var _ internalMessaging.ConnectionPool = (*connectionPoolMock)(nil)
func Test_NewAmqpMessagingApi(t *testing.T) { func Test_NewAmqpMessagingApi(t *testing.T) {
_, err := NewAmqpApi( _, err := NewAmqpApi(
AmqpConnectionPoolConfig{ pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: AmqpConnectionConfig{BrokerUrl: "not-handled-protocol://localhost:5672"}, Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: "not-handled-protocol://localhost:5672"},
PoolSize: 1, 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\"") assert.EqualError(t, err, "new amqp connection pool: initialize connections: new connection: new internal connection: internal connect: dial: unsupported scheme \"not-handled-protocol\"")
@ -44,15 +101,15 @@ func Test_AmqpMessagingApi_Send(t *testing.T) {
defer cancelFn() defer cancelFn()
// Start solace docker container // Start solace docker container
solaceContainer, err := NewSolaceContainer(context.Background()) solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err) assert.NoError(t, err)
defer solaceContainer.Stop() defer solaceContainer.Stop()
t.Run("Missing topic prefix", func(t *testing.T) { t.Run("Missing topic prefix", func(t *testing.T) {
defer solaceContainer.StopOnError() defer solaceContainer.StopOnError()
api, err := NewAmqpApi(AmqpConnectionPoolConfig{ api, err := NewAmqpApi(pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString}, Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1, PoolSize: 1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -72,7 +129,7 @@ func Test_AmqpMessagingApi_Send(t *testing.T) {
topicName := fmt.Sprintf("topic://auditlog/%s", "amqp-send-successfully") topicName := fmt.Sprintf("topic://auditlog/%s", "amqp-send-successfully")
assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName))
api, err := NewDefaultAmqpApi(AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString}) api, err := NewDefaultAmqpApi(pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString})
assert.NoError(t, err) assert.NoError(t, err)
data := []byte("data") data := []byte("data")
@ -100,27 +157,27 @@ func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
return channel return channel
} }
newActiveConnection := func() *AmqpConnection { newActiveConnection := func() *internalMessaging.AmqpConnection {
channel := make(chan struct{}) channel := make(chan struct{})
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel)) conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{ return &internalMessaging.AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
conn: conn, Conn: conn,
} }
} }
newClosedConnection := func() *AmqpConnection { newClosedConnection := func() *internalMessaging.AmqpConnection {
channel := make(chan struct{}) channel := make(chan struct{})
close(channel) close(channel)
conn := &amqpConnMock{} conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel)) conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{ return &internalMessaging.AmqpConnection{
connectionName: "test", ConnectionName: "test",
lock: sync.RWMutex{}, Lock: sync.RWMutex{},
conn: conn, Conn: conn,
} }
} }
@ -132,7 +189,7 @@ func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil) session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil)
connection := newActiveConnection() connection := newActiveConnection()
conn := connection.conn.(*amqpConnMock) conn := connection.Conn.(*amqpConnMock)
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil) conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil)
pool := &connectionPoolMock{} pool := &connectionPoolMock{}
@ -140,8 +197,8 @@ func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connectionPool: pool, connectionPool: pool,
connectionPoolHandle: &ConnectionPoolHandle{connectionOffset: 0}, connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*AmqpSenderSession), senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
} }
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any)) err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
@ -160,19 +217,19 @@ func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil) session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil)
connection := newActiveConnection() connection := newActiveConnection()
conn := connection.conn.(*amqpConnMock) conn := connection.Conn.(*amqpConnMock)
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil) conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil)
pool := &connectionPoolMock{} pool := &connectionPoolMock{}
pool.On("GetConnection", mock.Anything).Return(connection, nil) pool.On("GetConnection", mock.Anything).Return(connection, nil)
closedConnection := newClosedConnection() closedConnection := newClosedConnection()
closedConnMock := closedConnection.conn.(*amqpConnMock) closedConnMock := closedConnection.Conn.(*amqpConnMock)
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connection: closedConnection, connection: closedConnection,
connectionPool: pool, connectionPool: pool,
connectionPoolHandle: &ConnectionPoolHandle{connectionOffset: 0}, connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*AmqpSenderSession), senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
} }
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any)) err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
@ -185,15 +242,15 @@ func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
}) })
t.Run("connection nil get connection fail", func(t *testing.T) { t.Run("connection nil get connection fail", func(t *testing.T) {
var connection *AmqpConnection = nil var connection *internalMessaging.AmqpConnection = nil
pool := &connectionPoolMock{} pool := &connectionPoolMock{}
pool.On("GetConnection", mock.Anything).Return(connection, errors.New("connection error")) pool.On("GetConnection", mock.Anything).Return(connection, errors.New("connection error"))
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connectionPool: pool, connectionPool: pool,
connectionPoolHandle: &ConnectionPoolHandle{connectionOffset: 0}, connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*AmqpSenderSession), senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
} }
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any)) err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
@ -210,12 +267,12 @@ func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil) session.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(sender, nil)
connection := newActiveConnection() connection := newActiveConnection()
conn := connection.conn.(*amqpConnMock) conn := connection.Conn.(*amqpConnMock)
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil) conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil)
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connection: connection, connection: connection,
senderCache: make(map[string]*AmqpSenderSession), senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
} }
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any)) err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
@ -233,12 +290,12 @@ func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
session.On("Close", mock.Anything).Return(nil) session.On("Close", mock.Anything).Return(nil)
connection := newActiveConnection() connection := newActiveConnection()
conn := connection.conn.(*amqpConnMock) conn := connection.Conn.(*amqpConnMock)
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil) conn.On("NewSession", mock.Anything, mock.Anything).Return(session, nil)
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connection: connection, connection: connection,
senderCache: make(map[string]*AmqpSenderSession), senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
} }
err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any)) err := amqpApi.Send(context.Background(), "topic://some-topic", []byte("data"), "application/json", make(map[string]any))
@ -255,7 +312,7 @@ func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
topic := "topic://some-topic" topic := "topic://some-topic"
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connection: newActiveConnection(), connection: newActiveConnection(),
senderCache: map[string]*AmqpSenderSession{topic: {sender: sender}}, senderCache: map[string]*internalMessaging.AmqpSenderSession{topic: {Sender: sender}},
} }
err := amqpApi.Send(context.Background(), topic, []byte("data"), "application/json", make(map[string]any)) err := amqpApi.Send(context.Background(), topic, []byte("data"), "application/json", make(map[string]any))
@ -273,10 +330,10 @@ func Test_AmqpMessagingApi_Send_Special_Cases(t *testing.T) {
topic := "topic://some-topic" topic := "topic://some-topic"
connection := newActiveConnection() connection := newActiveConnection()
connection.conn.(*amqpConnMock).On("NewSession", mock.Anything, mock.Anything, mock.Anything).Return(session, nil) connection.Conn.(*amqpConnMock).On("NewSession", mock.Anything, mock.Anything, mock.Anything).Return(session, nil)
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connection: connection, connection: connection,
senderCache: map[string]*AmqpSenderSession{topic: {sender: sender}}, senderCache: map[string]*internalMessaging.AmqpSenderSession{topic: {Sender: sender}},
} }
err := amqpApi.Send(context.Background(), topic, []byte("data"), "application/json", make(map[string]any)) err := amqpApi.Send(context.Background(), topic, []byte("data"), "application/json", make(map[string]any))
@ -294,8 +351,8 @@ func Test_AmqpMessagingApi_Close(t *testing.T) {
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connectionPool: pool, connectionPool: pool,
connectionPoolHandle: &ConnectionPoolHandle{connectionOffset: 0}, connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*AmqpSenderSession), senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
} }
err := amqpApi.Close(context.Background()) err := amqpApi.Close(context.Background())
@ -310,8 +367,8 @@ func Test_AmqpMessagingApi_Close(t *testing.T) {
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connectionPool: pool, connectionPool: pool,
connectionPoolHandle: &ConnectionPoolHandle{connectionOffset: 0}, connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: make(map[string]*AmqpSenderSession), senderCache: make(map[string]*internalMessaging.AmqpSenderSession),
} }
err := amqpApi.Close(context.Background()) err := amqpApi.Close(context.Background())
@ -328,15 +385,15 @@ func Test_AmqpMessagingApi_Close(t *testing.T) {
session.On("Close", mock.Anything).Return(nil) session.On("Close", mock.Anything).Return(nil)
sender := &amqpSenderMock{} sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(nil) sender.On("Close", mock.Anything).Return(nil)
senderSession := &AmqpSenderSession{ senderSession := &internalMessaging.AmqpSenderSession{
session: session, Session: session,
sender: sender, Sender: sender,
} }
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connectionPool: pool, connectionPool: pool,
connectionPoolHandle: &ConnectionPoolHandle{connectionOffset: 0}, connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: map[string]*AmqpSenderSession{"key": senderSession}, senderCache: map[string]*internalMessaging.AmqpSenderSession{"key": senderSession},
} }
err := amqpApi.Close(context.Background()) err := amqpApi.Close(context.Background())
@ -356,15 +413,15 @@ func Test_AmqpMessagingApi_Close(t *testing.T) {
session.On("Close", mock.Anything).Return(nil) session.On("Close", mock.Anything).Return(nil)
sender := &amqpSenderMock{} sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(errors.New("close sender error")) sender.On("Close", mock.Anything).Return(errors.New("close sender error"))
senderSession := &AmqpSenderSession{ senderSession := &internalMessaging.AmqpSenderSession{
session: session, Session: session,
sender: sender, Sender: sender,
} }
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connectionPool: pool, connectionPool: pool,
connectionPoolHandle: &ConnectionPoolHandle{connectionOffset: 0}, connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: map[string]*AmqpSenderSession{"key": senderSession}, senderCache: map[string]*internalMessaging.AmqpSenderSession{"key": senderSession},
} }
err := amqpApi.Close(context.Background()) err := amqpApi.Close(context.Background())
@ -384,15 +441,15 @@ func Test_AmqpMessagingApi_Close(t *testing.T) {
session.On("Close", mock.Anything).Return(errors.New("close session error")) session.On("Close", mock.Anything).Return(errors.New("close session error"))
sender := &amqpSenderMock{} sender := &amqpSenderMock{}
sender.On("Close", mock.Anything).Return(errors.New("close sender error")) sender.On("Close", mock.Anything).Return(errors.New("close sender error"))
senderSession := &AmqpSenderSession{ senderSession := &internalMessaging.AmqpSenderSession{
session: session, Session: session,
sender: sender, Sender: sender,
} }
amqpApi := &AmqpApi{ amqpApi := &AmqpApi{
connectionPool: pool, connectionPool: pool,
connectionPoolHandle: &ConnectionPoolHandle{connectionOffset: 0}, connectionPoolHandle: &internalMessaging.ConnectionPoolHandle{ConnectionOffset: 0},
senderCache: map[string]*AmqpSenderSession{"key": senderSession}, senderCache: map[string]*internalMessaging.AmqpSenderSession{"key": senderSession},
} }
err := amqpApi.Close(context.Background()) err := amqpApi.Close(context.Background())

View file

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

View file

@ -1,7 +1,4 @@
package messaging package common
const AmqpTopicPrefix = "topic://"
const connectionTimeoutSeconds = 10
type AmqpConnectionConfig struct { type AmqpConnectionConfig struct {
BrokerUrl string `json:"brokerUrl"` BrokerUrl string `json:"brokerUrl"`

View file

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