mirror of
https://dev.azure.com/schwarzit/schwarzit.stackit-public/_git/audit-go
synced 2026-02-08 00:57:24 +00:00
1094 lines
44 KiB
Go
1094 lines
44 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/gen/go/audit/v1"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type mockSpan struct {
|
|
spanContext trace.SpanContext
|
|
}
|
|
|
|
func (s *mockSpan) SpanContext() trace.SpanContext {
|
|
return s.spanContext
|
|
}
|
|
|
|
func Test_TraceParentFromSpan(t *testing.T) {
|
|
tracer := otel.Tracer("test")
|
|
|
|
verifyTraceParent := func(traceParent string, span trace.Span, isSampled bool) {
|
|
parts := strings.Split(traceParent, "-")
|
|
assert.Equal(t, 4, len(parts))
|
|
|
|
// trace version
|
|
assert.Equal(t, "00", parts[0])
|
|
assert.Equal(t, span.SpanContext().TraceID().String(), parts[1])
|
|
assert.Equal(t, span.SpanContext().SpanID().String(), parts[2])
|
|
|
|
var traceFlags = "00"
|
|
if isSampled {
|
|
traceFlags = "01"
|
|
}
|
|
assert.Equal(t, traceFlags, parts[3])
|
|
}
|
|
|
|
t.Run("sampled", func(t *testing.T) {
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
updatedFlags := span.SpanContext().TraceFlags().WithSampled(true)
|
|
updatedContext := span.SpanContext().WithTraceFlags(updatedFlags)
|
|
|
|
mockedSpan := mockSpan{
|
|
spanContext: updatedContext,
|
|
}
|
|
|
|
traceParent := TraceParentFromSpan(&mockedSpan)
|
|
verifyTraceParent(traceParent, span, true)
|
|
})
|
|
|
|
t.Run("non-sampled", func(t *testing.T) {
|
|
_, span := tracer.Start(context.Background(), "test")
|
|
traceParent := TraceParentFromSpan(span)
|
|
verifyTraceParent(traceParent, span, false)
|
|
})
|
|
|
|
}
|
|
|
|
func Test_GetCalledServiceNameFromRequest(t *testing.T) {
|
|
|
|
t.Run("localhost", func(t *testing.T) {
|
|
request := ApiRequest{Host: "localhost:8080"}
|
|
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
|
|
assert.Equal(t, "resource-manager", serviceName)
|
|
})
|
|
|
|
t.Run("cf", func(t *testing.T) {
|
|
request := ApiRequest{Host: "stackit-resource-manager-go-dev.apps.01.cf.eu01.stackit.cloud"}
|
|
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
|
|
assert.Equal(t, "stackit-resource-manager-go-dev", serviceName)
|
|
})
|
|
|
|
t.Run("cf invalid host", func(t *testing.T) {
|
|
request := ApiRequest{Host: ""}
|
|
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
|
|
assert.Equal(t, "resource-manager", serviceName)
|
|
})
|
|
|
|
t.Run(
|
|
"ip", func(t *testing.T) {
|
|
request := ApiRequest{Host: "127.0.0.1"}
|
|
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
|
|
assert.Equal(t, "resource-manager", serviceName)
|
|
},
|
|
)
|
|
|
|
t.Run(
|
|
"ip short", func(t *testing.T) {
|
|
request := ApiRequest{Host: "::1"}
|
|
serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager")
|
|
assert.Equal(t, "resource-manager", serviceName)
|
|
},
|
|
)
|
|
}
|
|
|
|
func Test_NewPbInt64Value(t *testing.T) {
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
value := NewPbInt64Value(nil)
|
|
assert.Nil(t, value)
|
|
})
|
|
|
|
t.Run("value", func(t *testing.T) {
|
|
var input int64 = 1
|
|
value := NewPbInt64Value(&input)
|
|
assert.Equal(t, wrapperspb.Int64Value{Value: 1}.Value, value.Value)
|
|
})
|
|
}
|
|
|
|
func Test_NewResponseMetadata(t *testing.T) {
|
|
|
|
headers := make(map[string]string)
|
|
headers["Content-Type"] = "application/json"
|
|
responseTime := time.Now().UTC()
|
|
responseItems := int64(10)
|
|
responseSize := int64(100)
|
|
|
|
t.Run("no error", func(t *testing.T) {
|
|
for code := 1; code < 400; code++ {
|
|
metadata := NewResponseMetadata(code, &responseItems, &responseSize, headers, responseTime)
|
|
assert.Equal(t, wrapperspb.Int32(int32(code)).Value, metadata.StatusCode.Value)
|
|
assert.Nil(t, metadata.ErrorMessage)
|
|
assert.Nil(t, metadata.ErrorDetails)
|
|
assert.Equal(t, wrapperspb.Int64(responseItems), metadata.ResponseAttributes.NumResponseItems)
|
|
assert.Equal(t, wrapperspb.Int64(responseSize), metadata.ResponseAttributes.Size)
|
|
assert.Equal(t, timestamppb.New(responseTime), metadata.ResponseAttributes.Time)
|
|
}
|
|
})
|
|
|
|
t.Run("client error", func(t *testing.T) {
|
|
for code := 400; code < 500; code++ {
|
|
metadata := NewResponseMetadata(code, nil, nil, headers, responseTime)
|
|
assert.Equal(t, wrapperspb.Int32(int32(code)).Value, metadata.StatusCode.Value)
|
|
assert.Equal(t, "Client error", *metadata.ErrorMessage)
|
|
assert.Nil(t, metadata.ErrorDetails)
|
|
assert.Nil(t, metadata.ResponseAttributes.NumResponseItems)
|
|
assert.Nil(t, metadata.ResponseAttributes.Size)
|
|
assert.Equal(t, timestamppb.New(responseTime), metadata.ResponseAttributes.Time)
|
|
}
|
|
})
|
|
|
|
t.Run("server error", func(t *testing.T) {
|
|
for code := 500; code < 600; code++ {
|
|
metadata := NewResponseMetadata(code, nil, nil, headers, responseTime)
|
|
assert.Equal(t, wrapperspb.Int32(int32(code)).Value, metadata.StatusCode.Value)
|
|
assert.Equal(t, "Server error", *metadata.ErrorMessage)
|
|
assert.Nil(t, metadata.ErrorDetails)
|
|
assert.Nil(t, metadata.ResponseAttributes.NumResponseItems)
|
|
assert.Nil(t, metadata.ResponseAttributes.Size)
|
|
assert.Equal(t, timestamppb.New(responseTime), metadata.ResponseAttributes.Time)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test_NewRequestMetadata(t *testing.T) {
|
|
|
|
userAgent := "userAgent"
|
|
requestHeaders := make(map[string][]string)
|
|
requestHeaders["User-Agent"] = []string{userAgent}
|
|
requestHeaders["Custom"] = []string{"customHeader"}
|
|
|
|
queryString := "topic=project"
|
|
request := ApiRequest{
|
|
Method: "GET",
|
|
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
|
|
Host: "localhost:8080",
|
|
Proto: "HTTP/1.1",
|
|
Scheme: "http",
|
|
Header: requestHeaders,
|
|
}
|
|
|
|
requestId := "requestId"
|
|
requestScheme := "requestScheme"
|
|
requestTime := time.Now().UTC()
|
|
|
|
audiences := []string{"audience"}
|
|
authenticationPrincipal := "authenticationPrincipal"
|
|
|
|
claimMap := make(map[string]interface{})
|
|
auditClaims, _ := structpb.NewStruct(claimMap)
|
|
|
|
clientIp := "clientIp"
|
|
|
|
filteredHeaders := make(map[string]string)
|
|
filteredHeaders["Custom"] = "customHeader"
|
|
filteredHeaders["User-Agent"] = userAgent
|
|
|
|
verifyRequestMetadata := func(requestMetadata *auditV1.RequestMetadata, requestId *string) {
|
|
assert.Equal(t, clientIp, requestMetadata.CallerIp)
|
|
assert.Equal(t, userAgent, requestMetadata.CallerSuppliedUserAgent)
|
|
assert.NotNil(t, requestMetadata.RequestAttributes)
|
|
|
|
attributes := requestMetadata.RequestAttributes
|
|
assert.Equal(t, requestId, attributes.Id)
|
|
assert.Equal(t, filteredHeaders, attributes.Headers)
|
|
assert.Equal(t, request.URL.Path, attributes.Path)
|
|
assert.Equal(t, request.Host, attributes.Host)
|
|
assert.Equal(t, requestScheme, attributes.Scheme)
|
|
assert.Equal(t, timestamppb.New(requestTime), attributes.Time)
|
|
assert.Equal(t, request.Proto, attributes.Protocol)
|
|
assert.NotNil(t, attributes.Auth)
|
|
|
|
auth := attributes.Auth
|
|
assert.Equal(t, authenticationPrincipal, auth.Principal)
|
|
assert.Equal(t, audiences, auth.Audiences)
|
|
assert.Equal(t, auditClaims, auth.Claims)
|
|
}
|
|
|
|
t.Run("with query parameters", func(t *testing.T) {
|
|
requestMetadata := NewRequestMetadata(
|
|
&request,
|
|
filteredHeaders,
|
|
&requestId,
|
|
requestScheme,
|
|
requestTime,
|
|
clientIp,
|
|
authenticationPrincipal,
|
|
audiences,
|
|
auditClaims,
|
|
)
|
|
|
|
verifyRequestMetadata(requestMetadata, &requestId)
|
|
assert.Equal(t, "topic%3Dproject", *requestMetadata.RequestAttributes.Query)
|
|
})
|
|
|
|
t.Run("without query parameters", func(t *testing.T) {
|
|
request := ApiRequest{
|
|
Method: "GET",
|
|
URL: RequestUrl{Path: "/audit/new"},
|
|
Host: "localhost:8080",
|
|
Proto: "HTTP/1.1",
|
|
Header: requestHeaders,
|
|
}
|
|
|
|
requestMetadata := NewRequestMetadata(
|
|
&request,
|
|
filteredHeaders,
|
|
&requestId,
|
|
requestScheme,
|
|
requestTime,
|
|
clientIp,
|
|
authenticationPrincipal,
|
|
audiences,
|
|
auditClaims,
|
|
)
|
|
|
|
verifyRequestMetadata(requestMetadata, &requestId)
|
|
assert.Nil(t, requestMetadata.RequestAttributes.Query)
|
|
})
|
|
|
|
t.Run("with empty query parameters", func(t *testing.T) {
|
|
emptyQuery := ""
|
|
request := ApiRequest{
|
|
Method: "GET",
|
|
URL: RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery},
|
|
Host: "localhost:8080",
|
|
Proto: "HTTP/1.1",
|
|
Header: requestHeaders,
|
|
}
|
|
|
|
requestMetadata := NewRequestMetadata(
|
|
&request,
|
|
filteredHeaders,
|
|
&requestId,
|
|
requestScheme,
|
|
requestTime,
|
|
clientIp,
|
|
authenticationPrincipal,
|
|
audiences,
|
|
auditClaims,
|
|
)
|
|
|
|
verifyRequestMetadata(requestMetadata, &requestId)
|
|
assert.Nil(t, requestMetadata.RequestAttributes.Query)
|
|
})
|
|
|
|
t.Run("without request id", func(t *testing.T) {
|
|
request := ApiRequest{
|
|
Method: "GET",
|
|
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
|
|
Host: "localhost:8080",
|
|
Proto: "HTTP/1.1",
|
|
Header: requestHeaders,
|
|
}
|
|
|
|
requestMetadata := NewRequestMetadata(
|
|
&request, filteredHeaders,
|
|
nil,
|
|
requestScheme,
|
|
requestTime,
|
|
clientIp,
|
|
authenticationPrincipal,
|
|
audiences,
|
|
auditClaims,
|
|
)
|
|
verifyRequestMetadata(requestMetadata, nil)
|
|
})
|
|
|
|
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{
|
|
Method: httpMethod,
|
|
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
|
|
Host: "localhost:8080",
|
|
Proto: "HTTP/1.1",
|
|
Header: requestHeaders,
|
|
}
|
|
|
|
requestMetadata := NewRequestMetadata(
|
|
&request, filteredHeaders,
|
|
&requestId, requestScheme,
|
|
requestTime, clientIp,
|
|
authenticationPrincipal,
|
|
audiences,
|
|
auditClaims,
|
|
)
|
|
|
|
verifyRequestMetadata(requestMetadata, &requestId)
|
|
expectedMethod := fmt.Sprintf("HTTP_METHOD_%s", httpMethod)
|
|
assert.Equal(t, expectedMethod, requestMetadata.RequestAttributes.Method.String())
|
|
}
|
|
})
|
|
|
|
t.Run("unknown http method", func(t *testing.T) {
|
|
request := ApiRequest{
|
|
Method: "",
|
|
URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString},
|
|
Host: "localhost:8080",
|
|
Proto: "HTTP/1.1",
|
|
Header: requestHeaders,
|
|
}
|
|
|
|
requestMetadata := NewRequestMetadata(
|
|
&request, filteredHeaders,
|
|
&requestId, requestScheme,
|
|
requestTime, clientIp,
|
|
authenticationPrincipal,
|
|
audiences,
|
|
auditClaims,
|
|
)
|
|
|
|
verifyRequestMetadata(requestMetadata, &requestId)
|
|
assert.Equal(t,
|
|
auditV1.AttributeContext_HTTP_METHOD_UNSPECIFIED.String(),
|
|
requestMetadata.RequestAttributes.Method.String())
|
|
|
|
})
|
|
}
|
|
|
|
func Test_FilterAndMergeRequestHeaders(t *testing.T) {
|
|
|
|
t.Run("skip headers", func(t *testing.T) {
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{"ey..."}
|
|
headers["B3"] = []string{"80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90"}
|
|
|
|
filteredHeaders := FilterAndMergeHeaders(headers)
|
|
assert.Equal(t, 0, len(filteredHeaders))
|
|
})
|
|
|
|
t.Run("skip headers by prefix", func(t *testing.T) {
|
|
headers := make(map[string][]string)
|
|
headers["X-Forwarded-Proto"] = []string{"https"}
|
|
headers["Stackit-test"] = []string{"test"}
|
|
|
|
filteredHeaders := FilterAndMergeHeaders(headers)
|
|
assert.Equal(t, 0, len(filteredHeaders))
|
|
})
|
|
|
|
t.Run("merge headers", func(t *testing.T) {
|
|
headers := make(map[string][]string)
|
|
headers["Custom1"] = []string{"value1", "value2"}
|
|
headers["Custom2"] = []string{"value3", "value4"}
|
|
|
|
filteredHeaders := FilterAndMergeHeaders(headers)
|
|
assert.Equal(t, 2, len(filteredHeaders))
|
|
assert.Equal(t, "value1,value2", filteredHeaders["Custom1"])
|
|
assert.Equal(t, "value3,value4", filteredHeaders["Custom2"])
|
|
})
|
|
}
|
|
|
|
func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
|
|
|
|
t.Run("basic token", func(t *testing.T) {
|
|
headerValue := "Basic username:password"
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{headerValue}
|
|
request := ApiRequest{Header: headers}
|
|
|
|
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
|
|
assert.ErrorIs(t, err, ErrTokenIsNotBearerToken)
|
|
})
|
|
|
|
t.Run("invalid header value", func(t *testing.T) {
|
|
headerValue := "a b c"
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{headerValue}
|
|
request := ApiRequest{Header: headers}
|
|
|
|
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
|
|
assert.ErrorIs(t, err, ErrInvalidAuthorizationHeaderValue)
|
|
})
|
|
|
|
t.Run("invalid token too many parts", func(t *testing.T) {
|
|
headerValue := "Bearer a.b.c.d"
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{headerValue}
|
|
request := ApiRequest{Header: headers}
|
|
|
|
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
|
|
assert.ErrorIs(t, err, ErrInvalidBearerToken)
|
|
})
|
|
|
|
t.Run("invalid bearer token", func(t *testing.T) {
|
|
headerValue := "Bearer a.b.c"
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{headerValue}
|
|
request := ApiRequest{Header: headers}
|
|
|
|
_, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request)
|
|
assert.ErrorIs(t, err, ErrInvalidBearerToken)
|
|
})
|
|
|
|
t.Run("client credentials token", func(t *testing.T) {
|
|
headerValue := "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1yZXNvdXJjZS1tYW5hZ2VyLWRldiJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2IiwiZXhwIjoxNzI0NDA1MzI2LCJpYXQiOjE3MjQ0MDQ0MjYsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZGV2LnN0YWNraXQuY2xvdWQiLCJqdGkiOiJlNDZlYmEzOC1kZWRiLTQ1NDEtOTRmMy00OWY5N2E5MzRkNTgiLCJuYmYiOjE3MjQ0MDQ0MjYsInNjb3BlIjoidWFhLm5vbmUiLCJzdWIiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2In0.JP5Uy7AMdK4ukzQ6aOYzbVwEmq0Tp2ppQGRqGOhuVQgbqs6yJ33GKXo7RPsJVLw3FR7XAxENIVqNvzGotbDXr0NjBGdzyxIHzrOaUqM4w1iLzD1KF51dXFwkoigqDdD7Ze9eI_Uo3tSn8FwGLTSoO-ONQYpnceCiGut2Gc6VIL8HOLdh8dzlRENGQtgYd-3Y5zqpoLrsR2Bd-0sv15sF-5aI0CqcC8gE70JPImKf2u_IYI-TYMDNk86YSCtaYO5-alOrHXXWwgzSoH-r2s5qoOhPbei9myV_P4fdcKXxMqfap9hImXPUooVhpdUr1AabZw3MtW7rION8tJAiauhMQA"
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{headerValue}
|
|
request := ApiRequest{Header: headers}
|
|
|
|
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
|
|
AuditAttributesFromAuthorizationHeader(&request)
|
|
|
|
auditClaimsMap := auditClaims.AsMap()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "https://accounts.dev.stackit.cloud", auditClaimsMap["iss"])
|
|
assert.Equal(t, "stackit-resource-manager-dev", auditClaimsMap["sub"])
|
|
assert.Equal(t, []interface{}{"stackit-resource-manager-dev"}, auditClaimsMap["aud"].([]interface{}))
|
|
assert.Equal(t, "e46eba38-dedb-4541-94f3-49f97a934d58", auditClaimsMap["jti"])
|
|
|
|
principal := fmt.Sprintf("%s/%s",
|
|
url.QueryEscape("stackit-resource-manager-dev"),
|
|
url.QueryEscape("https://accounts.dev.stackit.cloud"))
|
|
assert.Equal(t, principal, authenticationPrincipal)
|
|
|
|
assert.Equal(t, []string{"stackit-resource-manager-dev"}, audiences)
|
|
|
|
assert.Equal(t, "stackit-resource-manager-dev", authenticationInfo.PrincipalId)
|
|
assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail)
|
|
|
|
assert.Nil(t, authenticationInfo.ServiceAccountName)
|
|
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
|
|
})
|
|
|
|
t.Run("service account access token", func(t *testing.T) {
|
|
headerValue := "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxMGYzOGIwMS01MzRiLTQ3YmItYTAzYS1lMjk0Y2EyYmU0ZGUiLCJhdWQiOlsic3RhY2tpdCIsImFwaSJdLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3Rva2VuLnNvdXJjZSI6ImxlZ2FjeSIsInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiJjZDk0ZjAxYS1kZjJlLTQ0NTYtOTAyZS00OGY1ZTU3ZjBiNjMiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50Iiwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMTBmMzhiMDEtNTM0Yi00N2JiLWEwM2EtZTI5NGNhMmJlNGRlIiwiZXhwIjoxNzIyNjY5MzQzLCJpYXQiOjE3MjI1ODI5NDMsImVtYWlsIjoibXktc2VydmljZS15aWZjOWUxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiI4NGMzMGE0Ni0xMDAxLTQzNmYtODU5Zi04OWMwYmExOWJlMWUifQ.hb8X9VKc9xViHgNMyFHT9ePj_lyEwTV1D2es8E278WtoCJ9-4GPPQGjhcLGGrigjnvpRYV2LKzNqpQslerT5lFT_pHACsryaAE0ImYjmoe-nutA7BBpYuM_JN6pk5VIjVFLTqRKeIvFexPacqS2Vo3YoK1GvxPB8WPWBbGIsBtMl-PTm8OTwwzooBOoCRhhMR-E1lFbAymLsc1JI4yDQKLLomvhEopgmocCnQ-P1QkiKMqdkNxiD_YYLLYTOApg6d62BhqpH66ziqx493AStdZ8d5Kjvf3e1knDhaxVwNCghQj7lSo2kNAqZe__g2tiXpiZNTXBFJ_5HgQMLh67wng"
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{headerValue}
|
|
request := ApiRequest{Header: headers}
|
|
|
|
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
|
|
AuditAttributesFromAuthorizationHeader(&request)
|
|
|
|
auditClaimsMap := auditClaims.AsMap()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"])
|
|
assert.Equal(t, "10f38b01-534b-47bb-a03a-e294ca2be4de", auditClaimsMap["sub"])
|
|
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"].([]interface{}))
|
|
assert.Equal(t, "84c30a46-1001-436f-859f-89c0ba19be1e", auditClaimsMap["jti"])
|
|
|
|
principal := fmt.Sprintf("%s/%s",
|
|
url.QueryEscape("10f38b01-534b-47bb-a03a-e294ca2be4de"),
|
|
url.QueryEscape("stackit/serviceaccount"))
|
|
assert.Equal(t, principal, authenticationPrincipal)
|
|
|
|
assert.Equal(t, []string{"stackit", "api"}, audiences)
|
|
|
|
assert.Equal(t, "10f38b01-534b-47bb-a03a-e294ca2be4de", authenticationInfo.PrincipalId)
|
|
assert.Equal(t, "my-service-yifc9e1@sa.stackit.cloud", authenticationInfo.PrincipalEmail)
|
|
|
|
assert.Equal(t,
|
|
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/10f38b01-534b-47bb-a03a-e294ca2be4de",
|
|
*authenticationInfo.ServiceAccountName)
|
|
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
|
|
})
|
|
|
|
t.Run("impersonated token of access token", func(t *testing.T) {
|
|
headerValue := "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiMDJhZWY1MTYtMzE3Zi00ZWMxLWExZGYtMWFjYmQ0ZDQ5ZmUzIn0sInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiIwMmFlZjUxNi0zMTdmLTRlYzEtYTFkZi0xYWNiZDRkNDlmZTMiLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJleHAiOjE3MjQwNjI5MDcsImlhdCI6MTcyNDA1OTMwNywiZW1haWwiOiJzZXJ2aWNlLWFjY291bnQtMi10ajlzcnQxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiIzNzU1NTE4My0wMWI5LTQyNzAtYmRjMS02OWI0ZmNmZDVlZTkifQ.auBvvsIesFMAlWOCPCPC77DrrHF7gSKZwKs_Zry5KFvu2bpZZC1BcSXOc8b9eh0SzANI9M9aGJBhOzOm39-ZZ5XOQ-6_y1aWuEenYQ6kT5D3GzCUTMDzSi1lcZ4IG5nFMa_AAlVEN_7AMv7LHGtz49bWLJnAgeTo1cvof-OgP4mCQ5O6E0iyAq-5u8V8NJL7HIZy7BDe4J1mjfYhwKagrN7QFWu4fhN4TNS7d922X_6V489BhjRFRYjLW_qDnv912JorbGRz_XwNy_dPA81EkdMyKE0BJUezguJUEKEG2_JEi9O64Flcoi6x8cFHYhaDuMMSLipzePaHdyk2lQtH7Q"
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{headerValue}
|
|
request := ApiRequest{Header: headers}
|
|
|
|
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
|
|
AuditAttributesFromAuthorizationHeader(&request)
|
|
|
|
auditClaimsMap := auditClaims.AsMap()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"])
|
|
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", auditClaimsMap["sub"])
|
|
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"].([]interface{}))
|
|
assert.Equal(t, "37555183-01b9-4270-bdc1-69b4fcfd5ee9", auditClaimsMap["jti"])
|
|
|
|
principal := fmt.Sprintf("%s/%s",
|
|
url.QueryEscape("f45009b2-6433-43c1-b6c7-618c44359e71"),
|
|
url.QueryEscape("stackit/serviceaccount"))
|
|
assert.Equal(t, principal, authenticationPrincipal)
|
|
|
|
assert.Equal(t, []string{"stackit", "api"}, audiences)
|
|
|
|
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", authenticationInfo.PrincipalId)
|
|
assert.Equal(t, "service-account-2-tj9srt1@sa.stackit.cloud", authenticationInfo.PrincipalEmail)
|
|
|
|
assert.Equal(t,
|
|
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/f45009b2-6433-43c1-b6c7-618c44359e71",
|
|
*authenticationInfo.ServiceAccountName)
|
|
assert.NotNil(t, authenticationInfo.ServiceAccountDelegationInfo)
|
|
|
|
serviceAccountDelegationInfo := authenticationInfo.ServiceAccountDelegationInfo
|
|
assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", serviceAccountDelegationInfo[0].GetIdpPrincipal().PrincipalId)
|
|
assert.Equal(t, "do-not-reply@stackit.cloud", serviceAccountDelegationInfo[0].GetIdpPrincipal().PrincipalEmail)
|
|
})
|
|
|
|
t.Run("impersonated token of impersonated access token", func(t *testing.T) {
|
|
headerValue := "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxNzM0YjRiNi0xZDVlLTQ4MTktOWI1MC0yOTkxN2ExYjlhZDUiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiZjQ1MDA5YjItNjQzMy00M2MxLWI2YzctNjE4YzQ0MzU5ZTcxIiwiYWN0Ijp7InN1YiI6IjAyYWVmNTE2LTMxN2YtNGVjMS1hMWRmLTFhY2JkNGQ0OWZlMyJ9fSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhcGkiLCJzdGFja2l0L3Byb2plY3QvcHJvamVjdC5pZCI6ImRhY2M3ODMwLTg0M2UtNGM1ZS04NmZmLWFhMGZiNTFkNjM2ZiIsImF6cCI6ImY0NTAwOWIyLTY0MzMtNDNjMS1iNmM3LTYxOGM0NDM1OWU3MSIsInN0YWNraXQvc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjE3MzRiNGI2LTFkNWUtNDgxOS05YjUwLTI5OTE3YTFiOWFkNSIsImV4cCI6MTcyNDA2Mjk2MywiaWF0IjoxNzI0MDU5MzYzLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC0zLWZnaHN4dzFAc2Euc3RhY2tpdC5jbG91ZCIsImp0aSI6IjFmN2YxZWZjLTMzNDktNDExYS1hNWQ3LTIyNTVlMGE1YThhZSJ9.c1ae17bAtyOdmwXQbK37W-NTyOxo7iER5aHS_C0fU1qKl2BjOz708GLjH-_vxx9eKPeYznfI21_xlTaAvuG4Aco9f5YDK7fooTVHnDaOSSggqcEaDzDPrNXhhKEDxotJeq9zRMVCEStcbirjTounnLbuULRbO5GSY5jo-8n2UKxSZ2j5G_SjFHajdJwmzwvOttp08tdL8ck1uDdgVNBfcm0VIdb6WmgrCIUq5rmoa-cRPkdEurNtIEgEB_9U0Xh-SpmmsvFsWWeNIKz0e_5RCIyJonm_wMkGmblGegemkYL76ypeMNXTQsly1RozDIePfzHuZOWbySHSCd-vKQa2kw"
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{headerValue}
|
|
request := ApiRequest{Header: headers}
|
|
|
|
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
|
|
AuditAttributesFromAuthorizationHeader(&request)
|
|
|
|
auditClaimsMap := auditClaims.AsMap()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"])
|
|
assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", auditClaimsMap["sub"])
|
|
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"].([]interface{}))
|
|
assert.Equal(t, "1f7f1efc-3349-411a-a5d7-2255e0a5a8ae", auditClaimsMap["jti"])
|
|
|
|
principal := fmt.Sprintf("%s/%s",
|
|
url.QueryEscape("1734b4b6-1d5e-4819-9b50-29917a1b9ad5"),
|
|
url.QueryEscape("stackit/serviceaccount"))
|
|
assert.Equal(t, principal, authenticationPrincipal)
|
|
|
|
assert.Equal(t, []string{"stackit", "api"}, audiences)
|
|
|
|
assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", authenticationInfo.PrincipalId)
|
|
assert.Equal(t, "service-account-3-fghsxw1@sa.stackit.cloud", authenticationInfo.PrincipalEmail)
|
|
|
|
assert.Equal(t,
|
|
"projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/1734b4b6-1d5e-4819-9b50-29917a1b9ad5",
|
|
*authenticationInfo.ServiceAccountName)
|
|
assert.NotNil(t, authenticationInfo.ServiceAccountDelegationInfo)
|
|
|
|
serviceAccountDelegationInfo := authenticationInfo.ServiceAccountDelegationInfo
|
|
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", serviceAccountDelegationInfo[0].GetIdpPrincipal().PrincipalId)
|
|
assert.Equal(t, "do-not-reply@stackit.cloud", serviceAccountDelegationInfo[0].GetIdpPrincipal().PrincipalEmail)
|
|
assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", serviceAccountDelegationInfo[1].GetIdpPrincipal().PrincipalId)
|
|
assert.Equal(t, "do-not-reply@stackit.cloud", serviceAccountDelegationInfo[1].GetIdpPrincipal().PrincipalEmail)
|
|
})
|
|
|
|
t.Run("user token", func(t *testing.T) {
|
|
headerValue := "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"
|
|
headers := make(map[string][]string)
|
|
headers["Authorization"] = []string{headerValue}
|
|
request := ApiRequest{Header: headers}
|
|
|
|
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
|
|
AuditAttributesFromAuthorizationHeader(&request)
|
|
|
|
auditClaimsMap := auditClaims.AsMap()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "https://accounts.dev.stackit.cloud", auditClaimsMap["iss"])
|
|
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", auditClaimsMap["sub"])
|
|
assert.Equal(t, []interface{}{"stackit-portal-login-dev-client-id"}, auditClaimsMap["aud"].([]interface{}))
|
|
assert.Equal(t, "d73a67ac-d1ec-4b55-99d4-e953275f022a", auditClaimsMap["jti"])
|
|
|
|
principal := fmt.Sprintf("%s/%s",
|
|
url.QueryEscape("cd94f01a-df2e-4456-902e-48f5e57f0b63"),
|
|
url.QueryEscape("https://accounts.dev.stackit.cloud"))
|
|
assert.Equal(t, principal, authenticationPrincipal)
|
|
|
|
assert.Equal(t, []string{"stackit-portal-login-dev-client-id"}, audiences)
|
|
|
|
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
|
|
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail)
|
|
|
|
assert.Nil(t, authenticationInfo.ServiceAccountName)
|
|
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
|
|
})
|
|
}
|
|
|
|
func Test_NewAuditLogEntry(t *testing.T) {
|
|
|
|
t.Run("minimum attributes set", func(t *testing.T) {
|
|
userAgent := "userAgent"
|
|
requestHeaders := make(map[string][]string)
|
|
requestHeaders["Authorization"] = []string{"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}
|
|
requestHeaders["User-Agent"] = []string{userAgent}
|
|
requestHeaders["Custom"] = []string{"customHeader"}
|
|
|
|
request := ApiRequest{
|
|
Method: "GET",
|
|
URL: RequestUrl{Path: "/audit/new"},
|
|
Host: "localhost:8080",
|
|
Proto: "HTTP/1.1",
|
|
Scheme: "http",
|
|
Header: requestHeaders,
|
|
}
|
|
|
|
clientIp := "127.0.0.1"
|
|
correlationId := uuid.NewString()
|
|
auditRequest := AuditRequest{
|
|
Request: &request,
|
|
RequestClientIP: clientIp,
|
|
RequestCorrelationId: &correlationId,
|
|
RequestId: nil,
|
|
RequestTime: nil,
|
|
}
|
|
|
|
statusCode := 200
|
|
auditResponse := AuditResponse{
|
|
ResponseBodyBytes: nil,
|
|
ResponseStatusCode: statusCode,
|
|
ResponseHeaders: nil,
|
|
ResponseNumItems: nil,
|
|
ResponseTime: nil,
|
|
}
|
|
|
|
objectId := uuid.NewString()
|
|
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity)
|
|
serviceName := "resource-manager"
|
|
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
|
|
resourceName := fmt.Sprintf("projects/%s", objectId)
|
|
auditTime := time.Now().UTC()
|
|
insertId := fmt.Sprintf("%d/%s/%s/%d", auditTime.UnixNano(), "eu01", "1", 1)
|
|
|
|
severity := auditV1.LogSeverity_LOG_SEVERITY_DEFAULT
|
|
auditMetadata := AuditMetadata{
|
|
AuditInsertId: insertId,
|
|
AuditLabels: nil,
|
|
AuditLogName: logName,
|
|
AuditLogSeverity: severity,
|
|
AuditOperationName: operationName,
|
|
AuditPermission: nil,
|
|
AuditPermissionGranted: nil,
|
|
AuditResourceName: resourceName,
|
|
AuditServiceName: serviceName,
|
|
AuditTime: nil,
|
|
}
|
|
|
|
logEntry, _ := NewAuditLogEntry(
|
|
auditRequest,
|
|
auditResponse,
|
|
nil,
|
|
auditMetadata,
|
|
nil,
|
|
nil)
|
|
|
|
assert.Equal(t, logName, logEntry.LogName)
|
|
assert.Equal(t, insertId, logEntry.InsertId)
|
|
assert.Equal(t, &correlationId, logEntry.CorrelationId)
|
|
assert.Equal(t, severity, logEntry.Severity)
|
|
assert.NoError(t, logEntry.Timestamp.CheckValid())
|
|
assert.Nil(t, logEntry.Labels)
|
|
assert.Nil(t, logEntry.TraceParent)
|
|
assert.Nil(t, logEntry.TraceState)
|
|
|
|
payload := logEntry.ProtoPayload
|
|
assert.NotNil(t, payload)
|
|
assert.Equal(t, serviceName, payload.ServiceName)
|
|
assert.Equal(t, operationName, payload.OperationName)
|
|
assert.Equal(t, resourceName, payload.ResourceName)
|
|
assert.Equal(t, int32(statusCode), payload.ResponseMetadata.StatusCode.Value)
|
|
assert.Nil(t, payload.ResponseMetadata.ErrorMessage)
|
|
assert.Nil(t, payload.ResponseMetadata.ErrorDetails)
|
|
assert.Nil(t, payload.ResponseMetadata.ResponseAttributes.Size)
|
|
assert.NotNil(t, payload.ResponseMetadata.ResponseAttributes.Time)
|
|
assert.Nil(t, payload.ResponseMetadata.ResponseAttributes.NumResponseItems)
|
|
assert.Nil(t, payload.ResponseMetadata.ResponseAttributes.Headers)
|
|
|
|
assert.Nil(t, payload.Request)
|
|
assert.Nil(t, payload.Response)
|
|
assert.Nil(t, payload.Metadata)
|
|
|
|
authenticationInfo := payload.AuthenticationInfo
|
|
assert.NotNil(t, authenticationInfo)
|
|
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
|
|
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail)
|
|
assert.Nil(t, authenticationInfo.ServiceAccountName)
|
|
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
|
|
|
|
assert.Nil(t, payload.AuthorizationInfo)
|
|
|
|
requestMetadata := payload.RequestMetadata
|
|
assert.NotNil(t, requestMetadata)
|
|
assert.Equal(t, clientIp, requestMetadata.CallerIp)
|
|
assert.Equal(t, userAgent, requestMetadata.CallerSuppliedUserAgent)
|
|
|
|
// Don't verify explicitly - trust on other unit test
|
|
assert.NotNil(t, userAgent, requestMetadata.RequestAttributes)
|
|
})
|
|
|
|
t.Run("all attributes set", func(t *testing.T) {
|
|
userAgent := "userAgent"
|
|
requestHeaders := make(map[string][]string)
|
|
requestHeaders["Authorization"] = []string{"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"}
|
|
requestHeaders["User-Agent"] = []string{userAgent}
|
|
requestHeaders["Custom"] = []string{"customHeader"}
|
|
|
|
requestBody := make(map[string]interface{})
|
|
requestBody["key"] = "request"
|
|
requestBodyBytes, _ := json.Marshal(requestBody)
|
|
query := "topic=project"
|
|
request := ApiRequest{
|
|
Method: "GET",
|
|
URL: RequestUrl{Path: "/audit/new", RawQuery: &query},
|
|
Host: "localhost:8080",
|
|
Proto: "HTTP/1.1",
|
|
Scheme: "http",
|
|
Header: requestHeaders,
|
|
Body: &requestBodyBytes,
|
|
}
|
|
|
|
clientIp := "127.0.0.1"
|
|
correlationId := uuid.NewString()
|
|
requestId := uuid.NewString()
|
|
requestTime := time.Now().UTC()
|
|
auditRequest := AuditRequest{
|
|
Request: &request,
|
|
RequestClientIP: clientIp,
|
|
RequestCorrelationId: &correlationId,
|
|
RequestId: &requestId,
|
|
RequestTime: &requestTime,
|
|
}
|
|
|
|
response := make(map[string]interface{})
|
|
response["key"] = "value"
|
|
responseBody, _ := json.Marshal(response)
|
|
responseHeader := http.Header{}
|
|
responseHeader.Set("Content-Type", "application/json")
|
|
responseHeaderMap := make(map[string]string)
|
|
responseHeaderMap["Content-Type"] = "application/json"
|
|
responseNumItems := int64(1)
|
|
responseStatusCode := 400
|
|
responseTime := time.Now().UTC()
|
|
|
|
auditResponse := AuditResponse{
|
|
ResponseBodyBytes: &responseBody,
|
|
ResponseStatusCode: responseStatusCode,
|
|
ResponseHeaders: responseHeader,
|
|
ResponseNumItems: &responseNumItems,
|
|
ResponseTime: &responseTime,
|
|
}
|
|
|
|
auditTime := time.Now().UTC()
|
|
|
|
objectId := uuid.NewString()
|
|
logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity)
|
|
serviceName := "resource-manager"
|
|
operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName)
|
|
resourceName := fmt.Sprintf("projects/%s", objectId)
|
|
insertId := fmt.Sprintf("%d/%s/%s/%d", auditTime.UnixNano(), "eu01", "1", 1)
|
|
permission := "resource-manager.project.edit"
|
|
permissionGranted := true
|
|
labels := make(map[string]string)
|
|
labels["label"] = "value"
|
|
|
|
severity := auditV1.LogSeverity_LOG_SEVERITY_DEFAULT
|
|
|
|
auditMetadata := AuditMetadata{
|
|
AuditInsertId: insertId,
|
|
AuditLabels: &labels,
|
|
AuditLogName: logName,
|
|
AuditLogSeverity: severity,
|
|
AuditOperationName: operationName,
|
|
AuditPermission: &permission,
|
|
AuditPermissionGranted: &permissionGranted,
|
|
AuditResourceName: resourceName,
|
|
AuditServiceName: serviceName,
|
|
AuditTime: &auditTime,
|
|
}
|
|
|
|
eventMetadata := map[string]interface{}{"key": "value"}
|
|
|
|
traceParent := "traceParent"
|
|
traceState := "traceState"
|
|
logEntry, _ := NewAuditLogEntry(
|
|
auditRequest,
|
|
auditResponse,
|
|
&eventMetadata,
|
|
auditMetadata,
|
|
&traceParent,
|
|
&traceState)
|
|
|
|
assert.Equal(t, logName, logEntry.LogName)
|
|
assert.Equal(t, insertId, logEntry.InsertId)
|
|
assert.Equal(t, labels, logEntry.Labels)
|
|
assert.Equal(t, correlationId, *logEntry.CorrelationId)
|
|
assert.Equal(t, timestamppb.New(auditTime), logEntry.Timestamp)
|
|
assert.Equal(t, severity, logEntry.Severity)
|
|
assert.Equal(t, &traceParent, logEntry.TraceParent)
|
|
assert.Equal(t, &traceState, logEntry.TraceState)
|
|
assert.NotNil(t, logEntry.ProtoPayload)
|
|
|
|
payload := logEntry.ProtoPayload
|
|
assert.NotNil(t, payload)
|
|
assert.Equal(t, serviceName, payload.ServiceName)
|
|
assert.Equal(t, operationName, payload.OperationName)
|
|
assert.Equal(t, resourceName, payload.ResourceName)
|
|
assert.Equal(t, int32(responseStatusCode), payload.ResponseMetadata.StatusCode.Value)
|
|
assert.Equal(t, "Client error", *payload.ResponseMetadata.ErrorMessage)
|
|
assert.Nil(t, payload.ResponseMetadata.ErrorDetails)
|
|
assert.Equal(t, wrapperspb.Int64(int64(len(responseBody))), payload.ResponseMetadata.ResponseAttributes.Size)
|
|
assert.Equal(t, timestamppb.New(responseTime), payload.ResponseMetadata.ResponseAttributes.Time)
|
|
assert.Equal(t, wrapperspb.Int64(responseNumItems), payload.ResponseMetadata.ResponseAttributes.NumResponseItems)
|
|
assert.Equal(t, responseHeaderMap, payload.ResponseMetadata.ResponseAttributes.Headers)
|
|
|
|
expectedRequestBody, _ := structpb.NewStruct(requestBody)
|
|
assert.Equal(t, expectedRequestBody, payload.Request)
|
|
expectedResponseBody, _ := structpb.NewStruct(response)
|
|
assert.Equal(t, expectedResponseBody, payload.Response)
|
|
expectedEventMetadata, _ := structpb.NewStruct(eventMetadata)
|
|
assert.Equal(t, expectedEventMetadata, payload.Metadata)
|
|
|
|
authenticationInfo := payload.AuthenticationInfo
|
|
assert.NotNil(t, authenticationInfo)
|
|
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId)
|
|
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail)
|
|
assert.Nil(t, authenticationInfo.ServiceAccountName)
|
|
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
|
|
|
|
authorizationInfo := payload.AuthorizationInfo
|
|
assert.NotNil(t, authorizationInfo)
|
|
assert.Equal(t, 1, len(authorizationInfo))
|
|
assert.Equal(t, permission, *authorizationInfo[0].Permission)
|
|
assert.Equal(t, permissionGranted, *authorizationInfo[0].Granted)
|
|
assert.Equal(t, resourceName, authorizationInfo[0].Resource)
|
|
|
|
requestMetadata := payload.RequestMetadata
|
|
assert.NotNil(t, requestMetadata)
|
|
assert.Equal(t, clientIp, requestMetadata.CallerIp)
|
|
assert.Equal(t, userAgent, requestMetadata.CallerSuppliedUserAgent)
|
|
|
|
// Don't verify explicitly - trust on other unit test
|
|
assert.NotNil(t, userAgent, requestMetadata.RequestAttributes)
|
|
})
|
|
}
|
|
|
|
func Test_NewInsertId(t *testing.T) {
|
|
insertTime := time.Now().UTC()
|
|
location := "eu01"
|
|
workerId := uuid.NewString()
|
|
var eventSequenceNumber uint64 = 1
|
|
|
|
insertId := NewInsertId(insertTime, location, workerId, eventSequenceNumber)
|
|
expectedId := fmt.Sprintf("%d/%s/%s/%d", insertTime.UnixNano(), location, workerId, eventSequenceNumber)
|
|
assert.Equal(t, expectedId, insertId)
|
|
}
|
|
|
|
func Test_NewNewAuditRoutingIdentifier(t *testing.T) {
|
|
objectId := uuid.NewString()
|
|
singularType := SingularTypeProject
|
|
|
|
routingIdentifier := NewAuditRoutingIdentifier(objectId, singularType)
|
|
assert.Equal(t, objectId, routingIdentifier.Identifier)
|
|
assert.Equal(t, singularType, routingIdentifier.Type)
|
|
}
|
|
|
|
func Test_OperationNameFromUrlPath(t *testing.T) {
|
|
|
|
t.Run("empty path", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("", "GET")
|
|
assert.Equal(t, "", operationName)
|
|
})
|
|
|
|
t.Run("root path", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/", "GET")
|
|
assert.Equal(t, "", operationName)
|
|
})
|
|
|
|
t.Run("path without version", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/projects", "GET")
|
|
assert.Equal(t, "projects.read", operationName)
|
|
})
|
|
|
|
t.Run("path with uuid without version", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET")
|
|
assert.Equal(t, "projects.read", operationName)
|
|
})
|
|
|
|
t.Run("path with uuid and version", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/v2/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET")
|
|
assert.Equal(t, "v2.projects.read", operationName)
|
|
})
|
|
|
|
t.Run("concatenated path", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/folders/167fc176-9d8e-477b-a56c-b50d7b26adcf/projects/0a2a4f9b-4e67-4562-ad02-c2d200e05aa6/audit/policy", "GET")
|
|
assert.Equal(t, "v2.organizations.folders.projects.audit.policy.read", operationName)
|
|
})
|
|
|
|
t.Run("path with query params", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/audit/policy?since=2024-08-27", "GET")
|
|
assert.Equal(t, "v2.organizations.audit.policy.read", operationName)
|
|
})
|
|
|
|
t.Run("path trailing slash", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/", "GET")
|
|
assert.Equal(t, "projects.read", operationName)
|
|
})
|
|
|
|
t.Run("path trailing slash and query params", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/?changeDate=2024-10-13", "GET")
|
|
assert.Equal(t, "projects.read", operationName)
|
|
})
|
|
|
|
t.Run("http method post", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/projects", "POST")
|
|
assert.Equal(t, "projects.create", operationName)
|
|
})
|
|
|
|
t.Run("http method put", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/projects", "PUT")
|
|
assert.Equal(t, "projects.update", operationName)
|
|
})
|
|
|
|
t.Run("http method patch", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/projects", "PATCH")
|
|
assert.Equal(t, "projects.update", operationName)
|
|
})
|
|
|
|
t.Run("http method delete", func(t *testing.T) {
|
|
operationName := OperationNameFromUrlPath("/projects", "DELETE")
|
|
assert.Equal(t, "projects.delete", operationName)
|
|
})
|
|
}
|
|
|
|
func Test_OperationNameFromGrpcMethod(t *testing.T) {
|
|
|
|
t.Run("empty path", func(t *testing.T) {
|
|
operationName := OperationNameFromGrpcMethod("")
|
|
assert.Equal(t, "", operationName)
|
|
})
|
|
|
|
t.Run("root path", func(t *testing.T) {
|
|
operationName := OperationNameFromGrpcMethod("/")
|
|
assert.Equal(t, "", operationName)
|
|
})
|
|
|
|
t.Run("path without version", func(t *testing.T) {
|
|
operationName := OperationNameFromGrpcMethod("/example.ExampleService/ManualAuditEvent")
|
|
assert.Equal(t, "example.exampleservice.manualauditevent", operationName)
|
|
})
|
|
|
|
t.Run("path with version", func(t *testing.T) {
|
|
operationName := OperationNameFromGrpcMethod("/example.v1.ExampleService/ManualAuditEvent")
|
|
assert.Equal(t, "example.v1.exampleservice.manualauditevent", operationName)
|
|
})
|
|
|
|
t.Run("path trailing slash", func(t *testing.T) {
|
|
operationName := OperationNameFromGrpcMethod("/example.v1.ExampleService/ManualAuditEvent/")
|
|
assert.Equal(t, "example.v1.exampleservice.manualauditevent", operationName)
|
|
})
|
|
}
|
|
|
|
func Test_GetObjectIdAndTypeFromUrlPath(t *testing.T) {
|
|
|
|
t.Run("object id and type not in url", func(t *testing.T) {
|
|
objectId, singularType, pluralType, err := GetObjectIdAndTypeFromUrlPath("/v2/projects/audit")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "", objectId)
|
|
assert.Nil(t, singularType)
|
|
assert.Nil(t, pluralType)
|
|
})
|
|
|
|
t.Run("object id and type in url", func(t *testing.T) {
|
|
objectId, singularType, pluralType, 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, SingularTypeProject, *singularType)
|
|
assert.Equal(t, PluralTypeProject, *pluralType)
|
|
})
|
|
|
|
t.Run("multiple object ids and types in url", func(t *testing.T) {
|
|
objectId, singularType, pluralType, 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, SingularTypeProject, *singularType)
|
|
assert.Equal(t, PluralTypeProject, *pluralType)
|
|
})
|
|
}
|
|
|
|
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(SingularTypeProject)}
|
|
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)
|
|
},
|
|
)
|
|
}
|