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
- std-error-handling
rules:
- path: audit/api/api_common.go
- path: internal/audit/api/api_common.go
text: context-as-argument
- linters:
- 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:
- dupl
path: audit/api/api_.*.go
- path: 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
path: pkg/audit/api/api_.*.go
- path: internal/audit/api/model_test.go|internal/audit/api/model.go
text: G115
- linters:
- gosec
path: audit/api/test_data.go
path: internal/audit/api/test_data.go
- linters:
- dogsled
- dupl
@ -268,7 +266,7 @@ linters:
- unparam
- wastedassign
- wsl
path: test_.*\.go|audit/messaging/solace.go
path: test_.*\.go|pkg/messaging/test/solace.go
paths:
- third_party$
- 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
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/bufbuild/protovalidate-go v0.9.3
github.com/docker/docker v28.1.1+incompatible
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/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/trace v1.35.0
google.golang.org/protobuf v1.36.6
)
require (
cel.dev/expr v0.23.1 // indirect
dario.cat/mergo v1.0.1 // indirect
cel.dev/expr v0.24.0 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // 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/docker/go-connections 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/go-logr/logr v1.4.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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // 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/stoewer/go-strcase v1.3.0 // 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/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.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/rpc 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-20250512202823-5a2f75b736a9 // 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-20250307204501-0409229c3780.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg=
cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
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-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
buf.build/go/protovalidate v0.12.0 h1:4GKJotbspQjRCcqZMGVSuC8SjwZ/FmgtSuKDpKUTZew=
buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
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/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
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/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/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/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
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-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/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/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@ -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/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.1.5 h1:PQI5gzadLfJ22ckLrejPVX6eGXKM4M4eGi5fW2jjA3o=
github.com/lestrrat-go/jwx/v2 v2.1.5/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU=
github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA=
github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lufia/plan9stats v0.0.0-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/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
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.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw=
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/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
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.36.0/go.mod h1:yk73GVJ0KUZIHUtFna6MO7QS144qYpoY8lEEtU9Hed0=
github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg=
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/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.6.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.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
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/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f h1:tjZsroqekhC63+WMqzmWyW5Twj/ZfR5HAlpd5YQ1Vs0=
google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f h1:N/PrbTw4kdkqNRzVfWPrBekzLuarFREcbFOiOLkXon4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 h1:WvBuA5rjZx9SNIzgcU53OohgZy6lKSus++uY4xLaWKc=
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-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg=
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/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=

View file

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

View file

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

View file

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

View file

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

View file

@ -2,23 +2,21 @@ package api
import (
"context"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"encoding/json"
"errors"
"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"
"regexp"
"slices"
"strings"
"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"
@ -31,66 +29,6 @@ var ErrInvalidAuthorizationHeaderValue = errors.New("invalid authorization heade
var ErrInvalidBearerToken = errors.New("invalid 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
type AuditRequest struct {
@ -100,7 +38,7 @@ type AuditRequest struct {
// It should never include user-generated data, such as file contents.
//
// Required: true
Request *ApiRequest
Request *pkgAuditCommon.ApiRequest
// The IP address of the caller.
// For caller from internet, this will be public IPv4 or IPv6 address.
@ -409,24 +347,6 @@ func NewAuditLogEntry(
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.
func NewPbInt64Value(value *int64) *wrapperspb.Int64Value {
if value != nil {
@ -437,7 +357,7 @@ func NewPbInt64Value(value *int64) *wrapperspb.Int64Value {
// NewRequestMetadata returns initialized protobuf RequestMetadata object.
func NewRequestMetadata(
request *ApiRequest,
request *pkgAuditCommon.ApiRequest,
requestHeaders map[string]string,
requestId *string,
requestScheme string,
@ -469,7 +389,7 @@ func NewRequestMetadata(
// NewRequestAttributes returns initialized protobuf AttributeContext_Request object.
func NewRequestAttributes(
request *ApiRequest,
request *pkgAuditCommon.ApiRequest,
requestHeaders map[string]string,
requestId *string,
requestScheme string,
@ -488,7 +408,7 @@ func NewRequestAttributes(
return &auditV1.AttributeContext_Request{
Id: requestId,
Method: StringToHttpMethod(request.Method),
Method: pkgAuditCommon.StringToHttpMethod(request.Method),
Headers: requestHeaders,
Path: request.URL.Path,
Host: request.Host,
@ -560,7 +480,7 @@ func NewResponseBody(response []byte) (*structpb.Struct, error) {
}
// 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 {
return nil, nil
@ -616,8 +536,8 @@ func FilterAndMergeHeaders(headers map[string][]string) map[string]string {
// NewAuditRoutingIdentifier instantiates a new auditApi.RoutableIdentifier for
// the given object ID and object type.
func NewAuditRoutingIdentifier(objectId string, objectType ObjectType) *RoutableIdentifier {
return &RoutableIdentifier{
func NewAuditRoutingIdentifier(objectId string, objectType pkgAuditCommon.ObjectType) *pkgAuditCommon.RoutableIdentifier {
return &pkgAuditCommon.RoutableIdentifier{
Identifier: objectId,
Type: objectType,
}
@ -628,7 +548,7 @@ func NewAuditRoutingIdentifier(objectId string, objectType ObjectType) *Routable
// - authenticationPrincipal - principal identifier
// - audiences - list of audience claims
// - authenticationInfo - information about the user or service-account authentication
func AuditAttributesFromAuthorizationHeader(request *ApiRequest) (
func AuditAttributesFromAuthorizationHeader(request *pkgAuditCommon.ApiRequest) (
*structpb.Struct,
string,
[]string,
@ -832,133 +752,3 @@ func extractSubjectAndEmail(token jwt.Token) (string, string) {
}
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
import (
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"encoding/json"
"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/url"
"testing"
"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) {
t.Run("nil", func(t *testing.T) {
@ -123,9 +85,9 @@ func Test_NewRequestMetadata(t *testing.T) {
requestHeaders["Custom"] = []string{"customHeader"}
queryString := "topic=project"
request := ApiRequest{
request := pkgAuditCommon.ApiRequest{
Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Scheme: "http",
@ -187,9 +149,9 @@ func Test_NewRequestMetadata(t *testing.T) {
})
t.Run("without query parameters", func(t *testing.T) {
request := ApiRequest{
request := pkgAuditCommon.ApiRequest{
Method: "GET",
URL: RequestUrl{Path: "/audit/new"},
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new"},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -213,9 +175,9 @@ func Test_NewRequestMetadata(t *testing.T) {
t.Run("with empty query parameters", func(t *testing.T) {
emptyQuery := ""
request := ApiRequest{
request := pkgAuditCommon.ApiRequest{
Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery},
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -238,9 +200,9 @@ func Test_NewRequestMetadata(t *testing.T) {
})
t.Run("without request id", func(t *testing.T) {
request := ApiRequest{
request := pkgAuditCommon.ApiRequest{
Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -262,9 +224,9 @@ func Test_NewRequestMetadata(t *testing.T) {
t.Run("various default http methods", func(t *testing.T) {
httpMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"}
for _, httpMethod := range httpMethods {
request := ApiRequest{
request := pkgAuditCommon.ApiRequest{
Method: httpMethod,
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -286,9 +248,9 @@ func Test_NewRequestMetadata(t *testing.T) {
})
t.Run("unknown http method", func(t *testing.T) {
request := ApiRequest{
request := pkgAuditCommon.ApiRequest{
Method: "",
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &queryString},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Header: requestHeaders,
@ -377,7 +339,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Basic username:password"
headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrTokenIsNotBearerToken)
@ -387,7 +349,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "a b c"
headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidAuthorizationHeaderValue)
@ -397,7 +359,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Bearer a.b.c.d"
headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidBearerToken)
@ -407,7 +369,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
headerValue := "Bearer a.b.c"
headers := make(map[string][]string)
headers["Authorization"] = []string{headerValue}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
assert.ErrorIs(t, err, ErrInvalidBearerToken)
@ -416,7 +378,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("client credentials token", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{clientCredentialsToken}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
@ -451,7 +413,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("service account access token", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountToken}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
@ -491,7 +453,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("impersonated token of access token", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountTokenImpersonated}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
@ -539,7 +501,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("impersonated token of impersonated access token", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{serviceAccountTokenRepeatedlyImpersonated}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
@ -592,7 +554,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("user token", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{userToken}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
@ -629,7 +591,7 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
t.Run("user token with simple aud claim", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{userTokenWithSimpleAudience}
request := ApiRequest{Header: headers}
request := pkgAuditCommon.ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
@ -671,9 +633,9 @@ func Test_NewAuditLogEntry(t *testing.T) {
requestHeaders["User-Agent"] = []string{userAgent}
requestHeaders["Custom"] = []string{"customHeader"}
request := ApiRequest{
request := pkgAuditCommon.ApiRequest{
Method: "GET",
URL: RequestUrl{Path: "/audit/new"},
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new"},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Scheme: "http",
@ -700,7 +662,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
}
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"
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
resourceName := fmt.Sprintf("projects/%s", objectId)
@ -780,9 +742,9 @@ func Test_NewAuditLogEntry(t *testing.T) {
requestBody["key"] = "request"
requestBodyBytes, _ := json.Marshal(requestBody)
query := "topic=project"
request := ApiRequest{
request := pkgAuditCommon.ApiRequest{
Method: "GET",
URL: RequestUrl{Path: "/audit/new", RawQuery: &query},
URL: pkgAuditCommon.RequestUrl{Path: "/audit/new", RawQuery: &query},
Host: "localhost:8080",
Proto: "HTTP/1.1",
Scheme: "http",
@ -824,7 +786,7 @@ func Test_NewAuditLogEntry(t *testing.T) {
auditTime := time.Now().UTC()
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"
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
resourceName := fmt.Sprintf("projects/%s", objectId)
@ -921,231 +883,9 @@ func Test_NewInsertId(t *testing.T) {
func Test_NewNewAuditRoutingIdentifier(t *testing.T) {
objectId := uuid.NewString()
objectType := ObjectTypeProject
objectType := pkgAuditCommon.ObjectTypeProject
routingIdentifier := NewAuditRoutingIdentifier(objectId, objectType)
assert.Equal(t, objectId, routingIdentifier.Identifier)
assert.Equal(t, objectType, routingIdentifier.Type)
}
func Test_OperationNameFromUrlPath(t *testing.T) {
t.Run("empty path", func(t *testing.T) {
operationName := OperationNameFromUrlPath("", "GET")
assert.Equal(t, "", operationName)
})
t.Run("root path", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/", "GET")
assert.Equal(t, "", operationName)
})
t.Run("path without version", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("path with uuid without version", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("path with uuid and version", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/v2/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET")
assert.Equal(t, "v2.projects.read", operationName)
})
t.Run("concatenated path", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/folders/167fc176-9d8e-477b-a56c-b50d7b26adcf/projects/0a2a4f9b-4e67-4562-ad02-c2d200e05aa6/audit/policy", "GET")
assert.Equal(t, "v2.organizations.folders.projects.audit.policy.read", operationName)
})
t.Run("path with query params", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/audit/policy?since=2024-08-27", "GET")
assert.Equal(t, "v2.organizations.audit.policy.read", operationName)
})
t.Run("path trailing slash", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("path trailing slash and query params", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/?changeDate=2024-10-13", "GET")
assert.Equal(t, "projects.read", operationName)
})
t.Run("http method post", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "POST")
assert.Equal(t, "projects.create", operationName)
})
t.Run("http method put", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "PUT")
assert.Equal(t, "projects.update", operationName)
})
t.Run("http method patch", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "PATCH")
assert.Equal(t, "projects.update", operationName)
})
t.Run("http method delete", func(t *testing.T) {
operationName := OperationNameFromUrlPath("/projects", "DELETE")
assert.Equal(t, "projects.delete", operationName)
})
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
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"
"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) {
@ -18,7 +21,7 @@ func Test_RoutableAuditEvent(t *testing.T) {
Visibility: auditV1.Visibility_VISIBILITY_PUBLIC,
ObjectIdentifier: &auditV1.ObjectIdentifier{
Identifier: "14f7aa86-77ba-4d77-a091-a2cf3395a221",
Type: string(ObjectTypeProject),
Type: string(pkgAuditCommon.ObjectTypeProject),
},
Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &auditV1.UnencryptedData{
Data: []byte("data"),

View file

@ -4,13 +4,13 @@ import (
"fmt"
"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"
"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 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}}
func newOrganizationAuditEvent(
func NewOrganizationAuditEvent(
customization *func(
*auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier,
@ -42,11 +42,11 @@ func newOrganizationAuditEvent(
labels := make(map[string]string)
labels["label1"] = "value1"
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{
ServiceName: "resource-manager",
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{
PrincipalId: uuid.NewString(),
PrincipalEmail: "user@example.com",
@ -54,7 +54,7 @@ func newOrganizationAuditEvent(
ServiceAccountDelegationInfo: nil,
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeOrganization.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeOrganization.Plural(), identifier),
Permission: &permission,
Granted: &permissionGranted,
}},
@ -102,7 +102,7 @@ func newOrganizationAuditEvent(
objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(),
Type: string(ObjectTypeOrganization),
Type: string(pkgAuditCommon.ObjectTypeOrganization),
}
if customization != nil {
@ -112,7 +112,7 @@ func newOrganizationAuditEvent(
return auditEvent, objectIdentifier
}
func newFolderAuditEvent(
func NewFolderAuditEvent(
customization *func(
*auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier,
@ -132,11 +132,11 @@ func newFolderAuditEvent(
labels := make(map[string]string)
labels["label1"] = "value1"
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{
ServiceName: "resource-manager",
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{
PrincipalId: uuid.NewString(),
PrincipalEmail: "user@example.com",
@ -144,7 +144,7 @@ func newFolderAuditEvent(
ServiceAccountDelegationInfo: nil,
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeFolder.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeFolder.Plural(), identifier),
Permission: &permission,
Granted: &permissionGranted,
}},
@ -192,7 +192,7 @@ func newFolderAuditEvent(
objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(),
Type: string(ObjectTypeFolder),
Type: string(pkgAuditCommon.ObjectTypeFolder),
}
if customization != nil {
@ -202,7 +202,7 @@ func newFolderAuditEvent(
return auditEvent, objectIdentifier
}
func newProjectAuditEvent(
func NewProjectAuditEvent(
customization *func(
*auditV1.AuditLogEntry,
*auditV1.ObjectIdentifier,
@ -222,11 +222,11 @@ func newProjectAuditEvent(
labels := make(map[string]string)
labels["label1"] = "value1"
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{
ServiceName: "resource-manager",
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{
PrincipalId: uuid.NewString(),
PrincipalEmail: "user@example.com",
@ -234,7 +234,7 @@ func newProjectAuditEvent(
ServiceAccountDelegationInfo: nil,
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
Permission: &permission,
Granted: &permissionGranted,
}},
@ -282,7 +282,7 @@ func newProjectAuditEvent(
objectIdentifier := &auditV1.ObjectIdentifier{
Identifier: identifier.String(),
Type: string(ObjectTypeProject),
Type: string(pkgAuditCommon.ObjectTypeProject),
}
if customization != nil {
@ -292,7 +292,7 @@ func newProjectAuditEvent(
return auditEvent, objectIdentifier
}
func newProjectSystemAuditEvent(
func NewProjectSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {
identifier := uuid.New()
@ -307,11 +307,11 @@ func newProjectSystemAuditEvent(
serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId)
delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}}
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{
ServiceName: "resource-manager",
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{
PrincipalId: serviceAccountId,
PrincipalEmail: "service-account@sa.stackit.cloud",
@ -319,7 +319,7 @@ func newProjectSystemAuditEvent(
ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal},
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeProject.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeProject.Plural(), identifier),
Permission: nil,
Granted: nil,
}},
@ -372,7 +372,7 @@ func newProjectSystemAuditEvent(
return auditEvent
}
func newSystemAuditEvent(
func NewSystemAuditEvent(
customization *func(*auditV1.AuditLogEntry)) *auditV1.AuditLogEntry {
identifier := uuid.Nil
@ -387,11 +387,11 @@ func newSystemAuditEvent(
serviceAccountName := fmt.Sprintf("projects/%s/service-accounts/%s", identifier, serviceAccountId)
delegationPrincipal := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_SystemPrincipal_{}}
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{
ServiceName: "resource-manager",
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{
PrincipalId: serviceAccountId,
PrincipalEmail: "service-account@sa.stackit.cloud",
@ -399,7 +399,7 @@ func newSystemAuditEvent(
ServiceAccountDelegationInfo: []*auditV1.ServiceAccountDelegationInfo{&delegationPrincipal},
},
AuthorizationInfo: []*auditV1.AuthorizationInfo{{
Resource: fmt.Sprintf("%s/%s", ObjectTypeSystem.Plural(), identifier),
Resource: fmt.Sprintf("%s/%s", pkgAuditCommon.ObjectTypeSystem.Plural(), identifier),
Permission: nil,
Granted: nil,
}},

View file

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

View file

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

View file

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

View file

@ -5,15 +5,17 @@ import (
"fmt"
"log/slog"
"sync"
pkgCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
)
type connectionProvider interface {
NewAmqpConnection(config AmqpConnectionConfig, connectionName string) *AmqpConnection
NewAmqpConnection(config pkgCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection
}
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)
}
@ -26,37 +28,37 @@ type ConnectionPool interface {
}
type AmqpConnectionPool struct {
config AmqpConnectionPoolConfig
connectionName string
connections []*AmqpConnection
connectionProvider connectionProvider
handleOffset int
lock sync.RWMutex
Config pkgCommon.AmqpConnectionPoolConfig
ConnectionName string
Connections []*AmqpConnection
ConnectionProvider connectionProvider
HandleOffset int
Lock sync.RWMutex
}
type ConnectionPoolHandle struct {
connectionOffset int
ConnectionOffset int
}
func NewDefaultAmqpConnectionPool(config AmqpConnectionConfig, connectionName string) (ConnectionPool, error) {
poolConfig := AmqpConnectionPoolConfig{
func NewDefaultAmqpConnectionPool(config pkgCommon.AmqpConnectionConfig, connectionName string) (ConnectionPool, error) {
poolConfig := pkgCommon.AmqpConnectionPoolConfig{
Parameters: config,
PoolSize: 2,
}
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 {
config.PoolSize = 2
}
pool := &AmqpConnectionPool{
config: config,
connectionName: connectionName,
connections: make([]*AmqpConnection, 0),
connectionProvider: defaultAmqpConnectionProvider{},
handleOffset: 0,
lock: sync.RWMutex{},
Config: config,
ConnectionName: connectionName,
Connections: make([]*AmqpConnection, 0),
ConnectionProvider: defaultAmqpConnectionProvider{},
HandleOffset: 0,
Lock: sync.RWMutex{},
}
if err := pool.initializeConnections(); err != nil {
@ -70,11 +72,11 @@ func NewAmqpConnectionPool(config AmqpConnectionPoolConfig, connectionName strin
}
func (p *AmqpConnectionPool) initializeConnections() error {
if len(p.connections) < p.config.PoolSize {
p.lock.Lock()
defer p.lock.Unlock()
if len(p.Connections) < p.Config.PoolSize {
p.Lock.Lock()
defer p.Lock.Unlock()
numMissingConnections := p.config.PoolSize - len(p.connections)
numMissingConnections := p.Config.PoolSize - len(p.Connections)
for i := 0; i < numMissingConnections; i++ {
if err := p.internalAddConnection(); err != nil {
@ -90,12 +92,12 @@ func (p *AmqpConnectionPool) internalAddConnection() error {
if err != nil {
return fmt.Errorf("new connection: %w", err)
}
p.connections = append(p.connections, newConnection)
p.Connections = append(p.Connections, newConnection)
return nil
}
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 {
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 {
p.lock.Lock()
defer p.lock.Unlock()
p.Lock.Lock()
defer p.Lock.Unlock()
closeErrors := make([]error, 0)
for _, conn := range p.connections {
for _, conn := range p.Connections {
if conn != nil {
if err := conn.Close(); err != nil {
closeErrors = append(closeErrors, fmt.Errorf("pooled connection: %w", err))
}
}
}
p.connections = make([]*AmqpConnection, p.config.PoolSize)
p.Connections = make([]*AmqpConnection, p.Config.PoolSize)
if len(closeErrors) > 0 {
return errors.Join(closeErrors...)
}
@ -133,16 +135,16 @@ func (p *AmqpConnectionPool) Close() error {
}
func (p *AmqpConnectionPool) NewHandle() *ConnectionPoolHandle {
p.lock.Lock()
defer p.lock.Unlock()
p.Lock.Lock()
defer p.Lock.Unlock()
offset := p.handleOffset
p.handleOffset++
offset := p.HandleOffset
p.HandleOffset++
offset %= p.config.PoolSize
offset %= p.Config.PoolSize
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
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)
connectionIndex := p.connectionIndex(handle, 0)
if p.connections[connectionIndex] == nil {
if p.Connections[connectionIndex] == nil {
connection, err := p.internalNewConnection()
if err != nil {
if conn == nil {
// case: connection could not be renewed and no connection to return has been found
p.lock.Unlock()
p.Lock.Unlock()
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))
} else {
// case: connection could be renewed and will be added to pool
p.connections[connectionIndex] = connection
p.Connections[connectionIndex] = connection
conn = connection
}
}
p.lock.Unlock()
p.Lock.Unlock()
}
if conn == nil {
@ -187,18 +189,18 @@ func (p *AmqpConnectionPool) nextConnectionForHandle(handle *ConnectionPoolHandl
// retry as long as there are remaining connections in the pool
var conn *AmqpConnection
var addConnection bool
for i := 0; i < p.config.PoolSize; i++ {
for i := 0; i < p.Config.PoolSize; i++ {
// get the next possible connection (considering the retry index)
idx := p.connectionIndex(handle, i)
p.lock.RLock()
if idx < len(p.connections) {
conn = p.connections[idx]
p.Lock.RLock()
if idx < len(p.Connections) {
conn = p.Connections[idx]
} else {
// handle the edge case that the pool is empty on shutdown
conn = nil
}
p.lock.RUnlock()
p.Lock.RUnlock()
// remember that the requested is closed, retry with the next
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 conn.IsClosed() {
p.lock.Lock()
p.connections[idx] = nil
p.lock.Unlock()
p.Lock.Lock()
p.Connections[idx] = nil
p.Lock.Unlock()
addConnection = true
continue
@ -222,8 +224,8 @@ func (p *AmqpConnectionPool) nextConnectionForHandle(handle *ConnectionPoolHandl
}
func (p *AmqpConnectionPool) connectionIndex(handle *ConnectionPoolHandle, iteration int) int {
if iteration+handle.connectionOffset >= p.config.PoolSize {
return (iteration + handle.connectionOffset) % p.config.PoolSize
if 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 (
"errors"
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
pkgMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
)
type connectionProviderMock struct {
mock.Mock
}
func (p *connectionProviderMock) NewAmqpConnection(config AmqpConnectionConfig, connectionName string) *AmqpConnection {
func (p *connectionProviderMock) NewAmqpConnection(config pkgMessagingCommon.AmqpConnectionConfig, connectionName string) *AmqpConnection {
args := p.Called(config, connectionName)
return args.Get(0).(*AmqpConnection)
}
@ -24,28 +27,28 @@ func Test_AmqpConnectionPool_GetHandle(t *testing.T) {
t.Run("next handle", func(t *testing.T) {
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
}
handle := pool.NewHandle()
assert.NotNil(t, handle)
assert.Equal(t, 0, handle.connectionOffset)
assert.Equal(t, 1, pool.handleOffset)
assert.Equal(t, 0, handle.ConnectionOffset)
assert.Equal(t, 1, pool.HandleOffset)
})
t.Run("next handle high offset", func(t *testing.T) {
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 13,
lock: sync.RWMutex{},
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 13,
Lock: sync.RWMutex{},
}
handle := pool.NewHandle()
assert.NotNil(t, handle)
assert.Equal(t, 3, handle.connectionOffset)
assert.Equal(t, 14, pool.handleOffset)
assert.Equal(t, 3, handle.ConnectionOffset)
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)
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: dialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.internalAddConnection()
assert.NoError(t, err)
assert.Equal(t, 1, len(pool.connections))
assert.Equal(t, 1, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 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)
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: dialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.internalAddConnection()
assert.NoError(t, err)
assert.Equal(t, 1, len(pool.connections))
assert.Equal(t, 1, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
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"))
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: dialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.internalAddConnection()
assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error")
assert.Equal(t, 0, len(pool.connections))
assert.Equal(t, 0, len(pool.Connections))
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
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)
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: dialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.initializeConnections()
assert.NoError(t, err)
assert.Equal(t, 5, len(pool.connections))
assert.Equal(t, 5, len(pool.Connections))
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"))
failingConnection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: failingDialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: failingDialer,
}
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)
successfulConnection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: successfulDialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: successfulDialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(4)
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(failingConnection)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.initializeConnections()
assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error")
assert.Equal(t, 4, len(pool.connections))
assert.Equal(t, 4, len(pool.Connections))
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)
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: dialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider := &connectionProviderMock{}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.initializeConnections()
assert.NoError(t, err)
assert.Equal(t, 5, len(pool.connections))
assert.Equal(t, 5, len(pool.Connections))
// close the pool
err = pool.Close()
assert.NoError(t, err)
assert.Equal(t, 5, len(pool.connections))
for _, c := range pool.connections {
assert.Equal(t, 5, len(pool.Connections))
for _, c := range pool.Connections {
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)
failingConnection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: failingDialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: failingDialer,
}
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)
successfulConnection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: successfulDialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: successfulDialer,
}
connectionProvider := &connectionProviderMock{}
@ -280,22 +283,22 @@ func Test_AmqpConnectionPool_Close(t *testing.T) {
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(1)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
ConnectionProvider: connectionProvider,
}
err := pool.initializeConnections()
assert.NoError(t, err)
assert.Equal(t, 5, len(pool.connections))
assert.Equal(t, 5, len(pool.Connections))
// close the pool
err = pool.Close()
assert.EqualError(t, err, "pooled connection: internal close: connection close: test error\npooled connection: internal close: connection close: test error")
assert.Equal(t, 5, len(pool.connections))
for _, c := range pool.connections {
assert.Equal(t, 5, len(pool.Connections))
for _, c := range pool.Connections {
assert.Nil(t, c)
}
})
@ -311,9 +314,9 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
conn: conn,
ConnectionName: "test",
Lock: sync.RWMutex{},
Conn: conn,
}
}
@ -324,9 +327,9 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
conn: conn,
ConnectionName: "test",
Lock: sync.RWMutex{},
Conn: conn,
}
}
@ -337,13 +340,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
}
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1})
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NotNil(t, connection)
assert.False(t, addConnection)
})
@ -357,13 +360,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, newActiveConnection())
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1})
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NotNil(t, connection)
assert.True(t, addConnection)
})
@ -377,13 +380,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, newActiveConnection())
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1})
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NotNil(t, connection)
assert.True(t, addConnection)
})
@ -397,13 +400,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, nil)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1})
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.Nil(t, connection)
assert.True(t, addConnection)
})
@ -417,13 +420,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, nil)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 23})
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 23})
assert.NotNil(t, connection)
assert.False(t, addConnection)
})
@ -437,13 +440,13 @@ func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
connections = append(connections, newActiveConnection())
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 23})
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{ConnectionOffset: 23})
assert.NotNil(t, connection)
assert.True(t, addConnection)
})
@ -459,9 +462,9 @@ func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
conn := &amqpConnMock{}
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
return &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
conn: conn,
ConnectionName: "test",
Lock: sync.RWMutex{},
Conn: conn,
}
}
@ -472,13 +475,13 @@ func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
}
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
}
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1})
connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NoError(t, err)
assert.NotNil(t, 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())
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
ConnectionProvider: connectionProvider,
}
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1})
connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NoError(t, err)
assert.NotNil(t, connection)
assert.Equal(t, connections[1], connection)
@ -520,21 +523,21 @@ func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
var c *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error"))
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: dialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
ConnectionProvider: connectionProvider,
}
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1})
connection, err := pool.GetConnection(&ConnectionPoolHandle{ConnectionOffset: 1})
assert.NoError(t, err)
assert.NotNil(t, connection)
assert.Nil(t, connections[1])
@ -556,21 +559,21 @@ func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
var c *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error"))
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
dialer: dialer,
ConnectionName: "test",
Lock: sync.RWMutex{},
Dialer: dialer,
}
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
pool := AmqpConnectionPool{
config: AmqpConnectionPoolConfig{PoolSize: 5},
handleOffset: 0,
lock: sync.RWMutex{},
connections: connections,
connectionProvider: connectionProvider,
Config: pkgMessagingCommon.AmqpConnectionPoolConfig{PoolSize: 5},
HandleOffset: 0,
Lock: sync.RWMutex{},
Connections: connections,
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.Nil(t, connection)
assert.Equal(t, 5, len(connections))

View file

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

View file

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

View file

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

View file

@ -2,32 +2,24 @@ package api
import (
"context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"errors"
"fmt"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"strings"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
)
const DataTypeLegacyAuditEventV1 = "audit.v1.LegacyAuditEvent"
// LegacyTopicNameResolver implements TopicNameResolver.
// A hard-coded topic name is used, routing identifiers are ignored.
type LegacyTopicNameResolver struct {
topicName string
}
// Resolve implements TopicNameResolver.Resolve
func (r *LegacyTopicNameResolver) Resolve(*RoutableIdentifier) (string, error) {
return r.topicName, nil
}
// LegacyTopicNameConfig provides topic name information required for the topic name resolution.
type LegacyTopicNameConfig struct {
// StaticTopicNameConfig provides topic name information required for the topic name resolution.
type StaticTopicNameConfig struct {
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
type LegacyAuditApi struct {
messagingApi messaging.Api
topicNameResolver TopicNameResolver
messagingApi pkgMessagingApi.Api
topicNameResolver pkgAuditCommon.TopicNameResolver
tracer trace.Tracer
validator ProtobufValidator
validator pkgAuditCommon.ProtobufValidator
}
// NewLegacyAuditApi can be used to initialize the audit log api.
//
// Note: The NewLegacyAuditApi method will be deprecated and replaced with "newRoutableAuditApi" once the new audit log routing is implemented
func NewLegacyAuditApi(
messagingApi messaging.Api,
topicNameConfig LegacyTopicNameConfig,
validator ProtobufValidator,
) (AuditApi, error) {
messagingApi pkgMessagingApi.Api,
topicNameConfig StaticTopicNameConfig,
validator pkgAuditCommon.ProtobufValidator,
) (pkgAuditCommon.AuditApi, error) {
if messagingApi == nil {
return nil, ErrMessagingApiNil
return nil, pkgAuditCommon.ErrMessagingApiNil
}
// Topic resolver
if topicNameConfig.TopicName == "" {
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 - "+
"expected stackit-platform/t/swz/audit-log/{region}/{version}/{eventSource}/{additionalParts} "+
"where region is one of [conway, eu01, eu02, sx-stoi01], version is vX.Y, eventSource is the service name "+
"and additionalParts is a describing string the audit log relates to or 'events'", topicNameConfig.TopicName)
}
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: topicNameConfig.TopicName}
var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: topicNameConfig.TopicName}
// Audit api
var auditApi AuditApi = &LegacyAuditApi{
var auditApi pkgAuditCommon.AuditApi = &LegacyAuditApi{
messagingApi: messagingApi,
topicNameResolver: topicNameResolver,
tracer: otel.Tracer("legacy-audit-api"),
@ -82,7 +74,7 @@ func (a *LegacyAuditApi) Log(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error {
cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier)
@ -99,21 +91,21 @@ func (a *LegacyAuditApi) ValidateAndSerialize(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) (*CloudEvent, error) {
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*pkgAuditCommon.CloudEvent, error) {
ctx, span := a.tracer.Start(ctx, "validate-and-serialize")
defer span.End()
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
routableEvent, err := internalAuditApi.ValidateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
// Reject event type data-access as the downstream services
// cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess
if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) {
return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
}
// Do nothing with the serialized data in the legacy solution
@ -123,19 +115,19 @@ func (a *LegacyAuditApi) ValidateAndSerialize(
}
// Convert attributes
legacyBytes, err := convertAndSerializeIntoLegacyFormat(event, routableEvent)
legacyBytes, err := internalAuditApi.ConvertAndSerializeIntoLegacyFormat(event, routableEvent)
if err != nil {
return nil, err
}
traceParent, traceState := TraceParentAndStateFromContext(ctx)
traceParent, traceState := internalAuditApi.TraceParentAndStateFromContext(ctx)
message := CloudEvent{
message := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName,
Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsJson,
DataContentType: pkgAuditCommon.ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1,
Subject: event.ProtoPayload.ResourceName,
Data: legacyBytes,
@ -148,15 +140,15 @@ func (a *LegacyAuditApi) ValidateAndSerialize(
// Send implements AuditApi.Send
func (a *LegacyAuditApi) Send(
ctx context.Context,
routableIdentifier *RoutableIdentifier,
cloudEvent *CloudEvent,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *pkgAuditCommon.CloudEvent,
) error {
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")
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"
"errors"
"fmt"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"strings"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
)
type ContextKey string
@ -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
type DynamicLegacyAuditApi struct {
messagingApi messaging.Api
messagingApi pkgMessagingApi.Api
tracer trace.Tracer
validator ProtobufValidator
validator pkgAuditCommon.ProtobufValidator
}
// NewDynamicLegacyAuditApi can be used to initialize the audit log api.
//
// Note: The NewLegacyAuditApi method will be deprecated and replaced with "newRoutableAuditApi" once the new audit log routing is implemented
func NewDynamicLegacyAuditApi(
messagingApi messaging.Api,
validator ProtobufValidator,
) (AuditApi, error) {
messagingApi pkgMessagingApi.Api,
validator pkgAuditCommon.ProtobufValidator,
) (pkgAuditCommon.AuditApi, error) {
if messagingApi == nil {
return nil, ErrMessagingApiNil
return nil, pkgAuditCommon.ErrMessagingApiNil
}
// Audit api
var auditApi AuditApi = &DynamicLegacyAuditApi{
var auditApi pkgAuditCommon.AuditApi = &DynamicLegacyAuditApi{
messagingApi: messagingApi,
tracer: otel.Tracer("dynamic-legacy-audit-api"),
validator: validator,
@ -58,7 +60,7 @@ func (a *DynamicLegacyAuditApi) Log(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error {
cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier)
@ -75,21 +77,21 @@ func (a *DynamicLegacyAuditApi) ValidateAndSerialize(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) (*CloudEvent, error) {
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*pkgAuditCommon.CloudEvent, error) {
ctx, span := a.tracer.Start(ctx, "validate-and-serialize")
defer span.End()
routableEvent, err := validateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
routableEvent, err := internalAuditApi.ValidateAndSerializePartially(a.validator, event, visibility, routableIdentifier)
if err != nil {
return nil, err
}
// Reject event type data-access as the downstream services
// cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess
if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) {
return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
}
// Do nothing with the serialized data in the legacy solution
@ -99,19 +101,19 @@ func (a *DynamicLegacyAuditApi) ValidateAndSerialize(
}
// Convert attributes
legacyBytes, err := convertAndSerializeIntoLegacyFormat(event, routableEvent)
legacyBytes, err := internalAuditApi.ConvertAndSerializeIntoLegacyFormat(event, routableEvent)
if err != nil {
return nil, err
}
traceParent, traceState := TraceParentAndStateFromContext(ctx)
traceParent, traceState := internalAuditApi.TraceParentAndStateFromContext(ctx)
message := CloudEvent{
message := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName,
Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsJson,
DataContentType: pkgAuditCommon.ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1,
Subject: event.ProtoPayload.ResourceName,
Data: legacyBytes,
@ -126,8 +128,8 @@ func (a *DynamicLegacyAuditApi) ValidateAndSerialize(
// Requires to have the topic name set as key "topic" in the context.
func (a *DynamicLegacyAuditApi) Send(
ctx context.Context,
routableIdentifier *RoutableIdentifier,
cloudEvent *CloudEvent,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *pkgAuditCommon.CloudEvent,
) error {
rawTopicName := ctx.Value(ContextKeyTopic)
@ -138,20 +140,20 @@ func (a *DynamicLegacyAuditApi) Send(
if topicName == "" {
return ErrTopicNameEmpty
}
if !TopicNamePattern.MatchString(topicName) {
if !pkgAuditCommon.TopicNamePattern.MatchString(topicName) {
return fmt.Errorf("invalid topic name: %s - "+
"expected stackit-platform/t/swz/audit-log/{region}/{version}/{eventSource}/{additionalParts} "+
"where region is one of [conway, eu01, eu02, sx-stoi01], version is vX.Y, eventSource is the service name "+
"and additionalParts is a describing string the audit log relates to or 'events'", topicName)
}
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: topicName}
var topicNameResolver pkgAuditCommon.TopicNameResolver = &pkgAuditCommon.StaticTopicNameTestResolver{TopicName: topicName}
if cloudEvent != nil && cloudEvent.TraceParent != nil && cloudEvent.TraceState != nil {
ctx = AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState)
ctx = internalAuditApi.AddTraceParentAndStateToContext(ctx, *cloudEvent.TraceParent, *cloudEvent.TraceState)
}
ctx, span := a.tracer.Start(ctx, "send")
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"
"encoding/json"
"errors"
"go.opentelemetry.io/otel"
"net/url"
"strings"
"testing"
"time"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/bufbuild/protovalidate-go"
"buf.build/go/protovalidate"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.opentelemetry.io/otel"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
pkgMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
pkgMessagingTest "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/test"
)
func TestDynamicLegacyAuditApi(t *testing.T) {
@ -25,14 +29,14 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
defer cancelFn()
// Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background())
solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(
messaging.AmqpConnectionPoolConfig{
Parameters: messaging.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
amqpApi, err := pkgMessagingApi.NewAmqpApi(
pkgMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pkgMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
assert.NoError(t, err)
@ -58,14 +62,14 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1)
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -74,8 +78,8 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
), ErrUnsupportedEventTypeDataAccess)
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
})
// Check logging of organization events
@ -92,13 +96,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -107,7 +111,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -129,13 +133,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -144,7 +148,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -167,13 +171,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -182,7 +186,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -204,13 +208,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -219,7 +223,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -242,13 +246,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -257,7 +261,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -279,13 +283,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -294,7 +298,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -316,13 +320,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := newProjectSystemAuditEvent(nil)
event := internalAuditApi.NewProjectSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -332,7 +336,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
RoutableSystemIdentifier,
pkgAuditCommon.RoutableSystemIdentifier,
))
// Receive the event from solace
@ -345,7 +349,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent LegacyAuditEvent
var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.ResourceName, *auditEvent.ResourceName)
@ -372,13 +376,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event := newSystemAuditEvent(nil)
event := internalAuditApi.NewSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -388,7 +392,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
RoutableSystemIdentifier,
pkgAuditCommon.RoutableSystemIdentifier,
))
// Receive the event from solace
@ -401,7 +405,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
assert.Equal(t, "", message.ApplicationProperties["cloudEvents:tracestate"])
// Check deserialized message
var auditEvent LegacyAuditEvent
var auditEvent internalAuditApi.LegacyAuditEvent
assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent))
assert.Equal(t, event.ProtoPayload.OperationName, auditEvent.EventName)
@ -427,13 +431,13 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
// Instantiate audit api
auditApi, err := NewDynamicLegacyAuditApi(
messagingApi,
amqpApi,
validator,
)
assert.NoError(t, err)
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
escapedQuery := url.QueryEscape("param=value")
event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery
@ -444,7 +448,7 @@ func TestDynamicLegacyAuditApi(t *testing.T) {
ctxWithTopic,
event,
visibility,
NewRoutableIdentifier(objectIdentifier),
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier),
))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -465,15 +469,15 @@ func TestDynamicLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
event := newSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
@ -482,22 +486,22 @@ func TestDynamicLegacyAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
event := newSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
func TestDynamicLegacyAuditApi_Log_NilEvent(t *testing.T) {
auditApi := DynamicLegacyAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil)
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
}
func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) {
@ -505,16 +509,16 @@ func TestDynamicLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectI
objectIdentifier *auditV1.ObjectIdentifier) {
objectIdentifier.Type = "invalid"
}
event, objectIdentifier := newProjectAuditEvent(&customization)
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(&customization)
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(nil)
var protobufValidator ProtobufValidator = validator
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := DynamicLegacyAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.NewRoutableIdentifier(objectIdentifier))
assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedRoutableType)
}

View file

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

View file

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

View file

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

View file

@ -4,14 +4,16 @@ import (
"context"
"errors"
"fmt"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"strings"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/proto"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/messaging"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
)
// routableTopicNameResolver implements TopicNameResolver.
@ -25,23 +27,23 @@ type routableTopicNameResolver struct {
}
// Resolve implements TopicNameResolver.Resolve.
func (r *routableTopicNameResolver) Resolve(routableIdentifier *RoutableIdentifier) (string, error) {
func (r *routableTopicNameResolver) Resolve(routableIdentifier *pkgAuditCommon.RoutableIdentifier) (string, error) {
if routableIdentifier == nil {
return "", ErrObjectIdentifierNil
return "", pkgAuditCommon.ErrObjectIdentifierNil
}
switch routableIdentifier.Type {
case ObjectTypeOrganization:
case pkgAuditCommon.ObjectTypeOrganization:
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
case ObjectTypeFolder:
case pkgAuditCommon.ObjectTypeFolder:
return fmt.Sprintf("topic://%s/%s", r.folderTopicPrefix, routableIdentifier.Identifier), nil
case ObjectTypeSystem:
case pkgAuditCommon.ObjectTypeSystem:
return r.systemTopicName, nil
default:
return "", ErrUnsupportedObjectIdentifierType
return "", pkgAuditCommon.ErrUnsupportedObjectIdentifierType
}
}
@ -57,18 +59,18 @@ type topicNameConfig struct {
// Warning: It is only there for local (compatibility) testing.
// DO NOT USE IT!
type routableAuditApi struct {
messagingApi messaging.Api
topicNameResolver TopicNameResolver
messagingApi pkgMessagingApi.Api
topicNameResolver pkgAuditCommon.TopicNameResolver
tracer trace.Tracer
validator ProtobufValidator
validator pkgAuditCommon.ProtobufValidator
}
// NewRoutableAuditApi can be used to initialize the audit log api.
func newRoutableAuditApi(
messagingApi messaging.Api,
messagingApi pkgMessagingApi.Api,
topicNameConfig topicNameConfig,
validator ProtobufValidator,
) (AuditApi, error) {
validator pkgAuditCommon.ProtobufValidator,
) (pkgAuditCommon.AuditApi, error) {
if messagingApi == nil {
return nil, errors.New("messaging api nil")
@ -88,7 +90,7 @@ func newRoutableAuditApi(
return nil, errors.New("system topic name is required")
}
var topicNameResolver TopicNameResolver = &routableTopicNameResolver{
var topicNameResolver pkgAuditCommon.TopicNameResolver = &routableTopicNameResolver{
folderTopicPrefix: topicNameConfig.FolderTopicPrefix,
organizationTopicPrefix: topicNameConfig.OrganizationTopicPrefix,
projectTopicPrefix: topicNameConfig.ProjectTopicPrefix,
@ -96,7 +98,7 @@ func newRoutableAuditApi(
}
// Audit api
var auditApi AuditApi = &routableAuditApi{
var auditApi pkgAuditCommon.AuditApi = &routableAuditApi{
messagingApi: messagingApi,
topicNameResolver: topicNameResolver,
tracer: otel.Tracer("routable-audit-api"),
@ -111,7 +113,7 @@ func (a *routableAuditApi) Log(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) error {
cloudEvent, err := a.ValidateAndSerialize(ctx, event, visibility, routableIdentifier)
@ -127,13 +129,13 @@ func (a *routableAuditApi) ValidateAndSerialize(
ctx context.Context,
event *auditV1.AuditLogEntry,
visibility auditV1.Visibility,
routableIdentifier *RoutableIdentifier,
) (*CloudEvent, error) {
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
) (*pkgAuditCommon.CloudEvent, error) {
ctx, span := a.tracer.Start(ctx, "validate-and-serialize")
defer span.End()
routableEvent, err := validateAndSerializePartially(
routableEvent, err := internalAuditApi.ValidateAndSerializePartially(
a.validator,
event,
visibility,
@ -145,8 +147,8 @@ func (a *routableAuditApi) ValidateAndSerialize(
// Reject event type data-access as the downstream services
// cannot handle it at the moment
if strings.HasSuffix(event.LogName, string(EventTypeDataAccess)) {
return nil, ErrUnsupportedEventTypeDataAccess
if strings.HasSuffix(event.LogName, string(pkgAuditCommon.EventTypeDataAccess)) {
return nil, pkgAuditCommon.ErrUnsupportedEventTypeDataAccess
}
routableEventBytes, err := proto.Marshal(routableEvent)
@ -154,14 +156,14 @@ func (a *routableAuditApi) ValidateAndSerialize(
return nil, err
}
traceParent, traceState := TraceParentAndStateFromContext(ctx)
traceParent, traceState := internalAuditApi.TraceParentAndStateFromContext(ctx)
message := CloudEvent{
message := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0",
Source: event.ProtoPayload.ServiceName,
Id: event.InsertId,
Time: event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsProtobuf,
DataContentType: pkgAuditCommon.ContentTypeCloudEventsProtobuf,
DataType: fmt.Sprintf("%v", routableEvent.ProtoReflect().Descriptor().FullName()),
Subject: event.ProtoPayload.ResourceName,
Data: routableEventBytes,
@ -175,15 +177,15 @@ func (a *routableAuditApi) ValidateAndSerialize(
// Send implements AuditApi.Send
func (a *routableAuditApi) Send(
ctx context.Context,
routableIdentifier *RoutableIdentifier,
cloudEvent *CloudEvent,
routableIdentifier *pkgAuditCommon.RoutableIdentifier,
cloudEvent *pkgAuditCommon.CloudEvent,
) error {
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")
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"
"errors"
"fmt"
"go.opentelemetry.io/otel"
"strings"
"testing"
"time"
"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"
"buf.build/go/protovalidate"
"github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.opentelemetry.io/otel"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
pkgMessagingApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/api"
pgkMessagingCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/common"
pkgMessagingTest "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/messaging/test"
)
func TestRoutableAuditApi(t *testing.T) {
@ -28,13 +31,13 @@ func TestRoutableAuditApi(t *testing.T) {
defer cancelFn()
// Start solace docker container
solaceContainer, err := messaging.NewSolaceContainer(context.Background())
solaceContainer, err := pkgMessagingTest.NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
messagingApi, err := messaging.NewAmqpApi(messaging.AmqpConnectionPoolConfig{
Parameters: messaging.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
amqpApi, err := pkgMessagingApi.NewAmqpApi(pgkMessagingCommon.AmqpConnectionPoolConfig{
Parameters: pgkMessagingCommon.AmqpConnectionConfig{BrokerUrl: solaceContainer.AmqpConnectionString},
PoolSize: 1,
})
assert.NoError(t, err)
@ -50,7 +53,7 @@ func TestRoutableAuditApi(t *testing.T) {
systemTopicName := "topic://system/admin-events"
auditApi, err := newRoutableAuditApi(
messagingApi,
amqpApi,
topicNameConfig{
FolderTopicPrefix: folderTopicPrefix,
OrganizationTopicPrefix: organizationTopicPrefix,
@ -71,8 +74,8 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(EventTypeAdminActivity), string(EventTypeDataAccess), 1)
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
event.LogName = strings.Replace(event.LogName, string(pkgAuditCommon.EventTypeAdminActivity), string(pkgAuditCommon.EventTypeDataAccess), 1)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -80,7 +83,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier)), ErrUnsupportedEventTypeDataAccess)
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)), pkgAuditCommon.ErrUnsupportedEventTypeDataAccess)
})
// Check logging of organization events
@ -93,7 +96,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -101,7 +104,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier)))
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
@ -124,7 +127,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
topicName := fmt.Sprintf("org/%s", objectIdentifier.Identifier)
assert.NoError(
t,
@ -137,7 +140,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier)))
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -163,7 +166,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*"))
// Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -171,7 +174,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier)))
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
@ -194,7 +197,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "folder/*"))
// Instantiate test data
event, objectIdentifier := newFolderAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil)
topicName := fmt.Sprintf("folder/%s", objectIdentifier.Identifier)
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicName))
@ -205,7 +208,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier)))
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -230,7 +233,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -239,7 +242,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier)))
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -263,7 +266,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// Instantiate test data
event, objectIdentifier := newProjectAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -272,7 +275,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier)))
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
@ -297,7 +300,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*"))
// Instantiate test data
event := newProjectSystemAuditEvent(nil)
event := internalAuditApi.NewProjectSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -306,7 +309,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
RoutableSystemIdentifier,
pkgAuditCommon.RoutableSystemIdentifier,
))
// Receive the event from solace
@ -332,7 +335,7 @@ func TestRoutableAuditApi(t *testing.T) {
validateRoutableEventPayload(
t,
message.Data[0],
RoutableSystemIdentifier.ToObjectIdentifier(),
pkgAuditCommon.RoutableSystemIdentifier.ToObjectIdentifier(),
event,
"stackit.resourcemanager.v2.system.changed",
visibility)
@ -347,7 +350,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "system/*"))
// Instantiate test data
event := newSystemAuditEvent(nil)
event := internalAuditApi.NewSystemAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
@ -356,7 +359,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
RoutableSystemIdentifier,
pkgAuditCommon.RoutableSystemIdentifier,
))
// Receive the event from solace
@ -382,7 +385,7 @@ func TestRoutableAuditApi(t *testing.T) {
validateRoutableEventPayload(
t,
message.Data[0],
SystemIdentifier,
pkgAuditCommon.SystemIdentifier,
event,
"stackit.resourcemanager.v2.system.changed",
visibility)
@ -398,7 +401,7 @@ func TestRoutableAuditApi(t *testing.T) {
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, objectIdentifier := newOrganizationAuditEvent(nil)
event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil)
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PUBLIC
@ -406,7 +409,7 @@ func TestRoutableAuditApi(t *testing.T) {
ctx,
event,
visibility,
NewRoutableIdentifier(objectIdentifier)))
pkgAuditCommon.NewRoutableIdentifier(objectIdentifier)))
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
@ -444,7 +447,7 @@ func validateSentEvent(
_, isUuid := uuid.Parse(fmt.Sprintf("%s", applicationProperties["cloudEvents:id"]))
assert.True(t, true, isUuid)
assert.Equal(t, event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().UnixMilli(), applicationProperties["cloudEvents:time"])
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, pkgAuditCommon.ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
assert.Equal(t, "audit.v1.RoutableAuditEvent", applicationProperties["cloudEvents:type"])
assert.Equal(t, "", applicationProperties["cloudEvents:traceparent"])
assert.Equal(t, "", applicationProperties["cloudEvents:tracestate"])
@ -486,8 +489,8 @@ func validateRoutableEventPayload(
func TestRoutableTopicNameResolver_Resolve_UnsupportedIdentifierType(t *testing.T) {
resolver := routableTopicNameResolver{}
_, err := resolver.Resolve(NewRoutableIdentifier(&auditV1.ObjectIdentifier{Type: "unsupported"}))
assert.ErrorIs(t, err, ErrUnsupportedObjectIdentifierType)
_, err := resolver.Resolve(pkgAuditCommon.NewRoutableIdentifier(&auditV1.ObjectIdentifier{Type: "unsupported"}))
assert.ErrorIs(t, err, pkgAuditCommon.ErrUnsupportedObjectIdentifierType)
}
func TestNewRoutableAuditApi_NewRoutableAuditApi_MessagingApiNil(t *testing.T) {
@ -501,15 +504,15 @@ func TestRoutableAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := routableAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
event := newSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
event := internalAuditApi.NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
@ -518,20 +521,20 @@ func TestRoutableAuditApi_Log_ValidationFailed(t *testing.T) {
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator
var protobufValidator pkgAuditCommon.ProtobufValidator = validator
auditApi := routableAuditApi{
tracer: otel.Tracer("test"),
validator: protobufValidator,
}
event := newSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
event := internalAuditApi.NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, expectedError)
}
func TestRoutableAuditApi_Log_NilEvent(t *testing.T) {
auditApi := routableAuditApi{tracer: otel.Tracer("test")}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
assert.ErrorIs(t, err, ErrEventNil)
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, pkgAuditCommon.RoutableSystemIdentifier)
assert.ErrorIs(t, err, pkgAuditCommon.ErrEventNil)
}

View file

@ -5,6 +5,8 @@ import (
"encoding/json"
"errors"
"strings"
pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common"
)
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")
type serializableEvent struct {
CloudEvent CloudEvent `json:"cloudEvent"`
RoutableIdentifier RoutableIdentifier `json:"routableIdentifier"`
CloudEvent pkgAuditCommon.CloudEvent `json:"cloudEvent"`
RoutableIdentifier pkgAuditCommon.RoutableIdentifier `json:"routableIdentifier"`
}
func ToBase64(
cloudEvent *CloudEvent,
routableIdentifier *RoutableIdentifier) (*string, error) {
cloudEvent *pkgAuditCommon.CloudEvent,
routableIdentifier *pkgAuditCommon.RoutableIdentifier) (*string, error) {
if cloudEvent == nil {
return nil, ErrCloudEventNil
return nil, pkgAuditCommon.ErrCloudEventNil
}
if routableIdentifier == nil {
@ -45,7 +47,7 @@ func ToBase64(
return &base64Str, nil
}
func FromBase64(base64Str string) (*CloudEvent, *RoutableIdentifier, error) {
func FromBase64(base64Str string) (*pkgAuditCommon.CloudEvent, *pkgAuditCommon.RoutableIdentifier, error) {
if base64Str == "" {
return nil, nil, ErrBase64StringEmpty
}

View file

@ -2,24 +2,27 @@ package api
import (
"encoding/base64"
"github.com/stretchr/testify/assert"
"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) {
t.Run("cloud event nil", func(t *testing.T) {
var cloudEvent *CloudEvent = nil
routableIdentifier := RoutableSystemIdentifier
var cloudEvent *pkgAuditCommon.CloudEvent = nil
routableIdentifier := pkgAuditCommon.RoutableSystemIdentifier
base64str, err := ToBase64(cloudEvent, routableIdentifier)
assert.ErrorIs(t, err, ErrCloudEventNil)
assert.ErrorIs(t, err, pkgAuditCommon.ErrCloudEventNil)
assert.Nil(t, base64str)
})
t.Run("routable identifier nil", func(t *testing.T) {
cloudEvent := &CloudEvent{}
var routableIdentifier *RoutableIdentifier = nil
cloudEvent := &pkgAuditCommon.CloudEvent{}
var routableIdentifier *pkgAuditCommon.RoutableIdentifier = nil
base64str, err := ToBase64(cloudEvent, routableIdentifier)
assert.ErrorIs(t, err, ErrRoutableIdentifierNil)
@ -27,8 +30,8 @@ func Test_ToBase64(t *testing.T) {
})
t.Run("encoded event", func(t *testing.T) {
e := &CloudEvent{}
r := RoutableSystemIdentifier
e := &pkgAuditCommon.CloudEvent{}
r := pkgAuditCommon.RoutableSystemIdentifier
base64str, err := ToBase64(e, r)
assert.NoError(t, err)
@ -72,8 +75,8 @@ func Test_FromBase64(t *testing.T) {
})
t.Run("decoded event", func(t *testing.T) {
e := &CloudEvent{}
r := RoutableSystemIdentifier
e := &pkgAuditCommon.CloudEvent{}
r := pkgAuditCommon.RoutableSystemIdentifier
base64str, err := ToBase64(e, r)
assert.NoError(t, err)

View file

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

View file

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

View file

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

View file

@ -2,37 +2,41 @@ package api
import (
"context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/audit/utils"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"github.com/bufbuild/protovalidate-go"
"testing"
"buf.build/go/protovalidate"
"github.com/google/uuid"
"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) {
api, _ := NewMockAuditApi()
sequenceNumberGenerator := utils.NewDefaultSequenceNumberGenerator()
sequenceNumberGenerator := pkgAuditUtils.NewDefaultSequenceNumberGenerator()
t.Run("new format", func(t *testing.T) {
eventBuilder := NewAuditEventBuilder(api, sequenceNumberGenerator, "demo-service", uuid.NewString(), "eu01")
cloudEvent, _, err := eventBuilder.
WithRequiredApiRequest(ApiRequest{
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: TestHeaders,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "GET",
Scheme: "https",
Proto: "HTTP/1.1",
URL: RequestUrl{
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredObjectId(uuid.NewString()).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.project.update").
WithRequiredRequestClientIp("0.0.0.0").
Build(context.Background(), eventBuilder.NextSequenceNumber())
@ -44,21 +48,21 @@ func Test_LogEvent(t *testing.T) {
t.Run("legacy format", func(t *testing.T) {
objectId := uuid.NewString()
entry, err := NewAuditLogEntryBuilder().
WithRequiredApiRequest(ApiRequest{
WithRequiredApiRequest(pkgAuditCommon.ApiRequest{
Body: nil,
Header: TestHeaders,
Header: internalAuditApi.TestHeaders,
Host: "localhost",
Method: "GET",
Scheme: "https",
Proto: "HTTP/1.1",
URL: RequestUrl{
URL: pkgAuditCommon.RequestUrl{
Path: "/",
RawQuery: nil,
},
}).
WithRequiredLocation("eu01").
WithRequiredObjectId(objectId).
WithRequiredObjectType(ObjectTypeProject).
WithRequiredObjectType(pkgAuditCommon.ObjectTypeProject).
WithRequiredOperation("stackit.demo-service.v1.project.update").
WithRequiredRequestClientIp("0.0.0.0").
WithRequiredServiceName("demo-service").
@ -68,25 +72,25 @@ func Test_LogEvent(t *testing.T) {
validator, err := protovalidate.New()
assert.NoError(t, err)
var protoValidator ProtobufValidator = validator
var protoValidator pkgAuditCommon.ProtobufValidator = validator
routableIdentifier := RoutableIdentifier{
routableIdentifier := pkgAuditCommon.RoutableIdentifier{
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)
legacyBytes, err := convertAndSerializeIntoLegacyFormat(entry, routableEvent)
legacyBytes, err := internalAuditApi.ConvertAndSerializeIntoLegacyFormat(entry, routableEvent)
assert.NoError(t, err)
cloudEvent := CloudEvent{
cloudEvent := pkgAuditCommon.CloudEvent{
SpecVersion: "1.0",
Source: entry.ProtoPayload.ServiceName,
Id: entry.InsertId,
Time: entry.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime(),
DataContentType: ContentTypeCloudEventsJson,
DataContentType: pkgAuditCommon.ContentTypeCloudEventsJson,
DataType: DataTypeLegacyAuditEventV1,
Subject: entry.ProtoPayload.ResourceName,
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 (
"context"
"github.com/bufbuild/protovalidate-go"
"regexp"
"time"
"buf.build/go/protovalidate"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"google.golang.org/protobuf/proto"
)
// ContentTypeCloudEventsProtobuf the cloudevents protobuf content-type sent in metadata of messages
const ContentTypeCloudEventsProtobuf = "application/cloudevents+protobuf"
const ContentTypeCloudEventsJson = "application/cloudevents+json; charset=UTF-8"
var TopicNamePattern = regexp.MustCompile(`^topic://stackit-platform/t/swz/audit-log/(?:conway|eu01|eu02|sx-stoi01)/[Vv][1-9](?:\.\d)?/[A-Za-z0-9-]+/[A-Za-z0-9-/]+`)
type EventType string
const (
@ -218,6 +224,17 @@ type TopicNameResolver interface {
Resolve(routableIdentifier *RoutableIdentifier) (string, error)
}
// StaticTopicNameTestResolver implements TopicNameResolver.
// A hard-coded topic name is used, routable identifiers are ignored.
type StaticTopicNameTestResolver struct {
TopicName string
}
// Resolve implements TopicNameResolver.Resolve
func (r *StaticTopicNameTestResolver) Resolve(*RoutableIdentifier) (string, error) {
return r.TopicName, nil
}
type RoutableIdentifier struct {
Identifier string
Type ObjectType

View file

@ -1,4 +1,4 @@
package api
package common
import (
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
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_DefaultSequenceNumberGenerator(t *testing.T) {

View file

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

View file

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

View file

@ -1,24 +1,27 @@
package messaging
package test
import (
"bytes"
"context"
"dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/log"
"encoding/json"
"errors"
"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"
"net/http"
"regexp"
"strings"
"time"
"github.com/Azure/go-amqp"
docker "github.com/docker/docker/api/types/container"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
pkgLog "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/log"
)
const AmqpQueuePrefix = "queue://"
const AmqpTopicPrefix = "topic://"
const dockerImage = "schwarzit-docker.jfrog.io/solace/solace-pubsub-standard:10.8.1.241"
var ErrResourceNotFound = errors.New("resource not found")
@ -136,9 +139,9 @@ func NewSolaceContainer(ctx context.Context) (*SolaceContainer, error) {
ExposedPorts: []string{"5672/tcp", "8080/tcp"},
HostConfigModifier: func(config *docker.HostConfig) {
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:").
WithStartupTimeout(90 * time.Second),
}
@ -168,7 +171,7 @@ func NewSolaceContainer(ctx context.Context) (*SolaceContainer, error) {
_ = container.Terminate(ctx)
return nil, err
}
log.AuditLogger.Info("UI Port: " + sempPort.Port())
pkgLog.AuditLogger.Info("UI Port: " + sempPort.Port())
// Construct connection strings
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 {
// Cut off the topic:// prefix
var name string
if strings.HasPrefix(topicName, "topic://") {
name = topicName[len("topic://"):]
if strings.HasPrefix(topicName, AmqpTopicPrefix) {
name = topicName[len(AmqpTopicPrefix):]
} else {
name = topicName
}