Merged PR 684951: fix: Replace manual jwt parsing with jwx library

Related work items: #701160
This commit is contained in:
Christian Schaible 2024-11-26 06:27:46 +00:00
parent bc3de128d4
commit 5fd40d1415
6 changed files with 242 additions and 117 deletions

View file

@ -331,7 +331,7 @@ func Test_AuditLogEntryBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo)
assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail)
assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -869,7 +869,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo)
assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail)
assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)
@ -978,7 +978,7 @@ func Test_AuditEventBuilder(t *testing.T) {
authenticationInfo := logEntry.ProtoPayload.AuthenticationInfo
assert.NotNil(t, authenticationInfo)
assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail)
assert.Equal(t, EmailAddressDoNotReplyAtStackItDotCloud, authenticationInfo.PrincipalEmail)
assert.Equal(t, "none", authenticationInfo.PrincipalId)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
assert.Nil(t, authenticationInfo.ServiceAccountName)

View file

@ -1,12 +1,13 @@
package api
import (
"context"
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"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"
@ -20,6 +21,10 @@ import (
"time"
)
const EmailAddressDoNotReplyAtStackItDotCloud = "do-not-reply@stackit.cloud"
const TokenClaimStackitProjectId = "stackit/project/project.id"
const TokenClaimStackitServiceAccountId = "stackit/serviceaccount/service-account.uid"
var ErrInvalidRequestBody = errors.New("invalid request body")
var ErrInvalidResponse = errors.New("invalid response")
var ErrInvalidAuthorizationHeaderValue = errors.New("invalid authorization header value")
@ -632,7 +637,7 @@ func AuditAttributesFromAuthorizationHeader(request *ApiRequest) (
) {
var principalId = "none"
var principalEmail = "do-not-reply@stackit.cloud"
var principalEmail = EmailAddressDoNotReplyAtStackItDotCloud
emptyClaims, _ := structpb.NewStruct(make(map[string]interface{}))
var auditClaims = emptyClaims
var authenticationPrincipal = "none/none"
@ -650,7 +655,12 @@ func AuditAttributesFromAuthorizationHeader(request *ApiRequest) (
if len(trimmedAuthorizationHeader) > 0 {
// Parse claims
parsedClaims, filteredClaims, err := parseClaimsFromAuthorizationHeader(trimmedAuthorizationHeader)
token, err := parseToken(trimmedAuthorizationHeader)
if err != nil {
return nil, authenticationPrincipal, nil, nil, err
}
filteredClaims, err := parseClaimsFromAuthorizationHeader(token)
if err != nil {
return nil, authenticationPrincipal, nil, nil, err
}
@ -663,23 +673,24 @@ func AuditAttributesFromAuthorizationHeader(request *ApiRequest) (
auditClaims = auditClaimsStruct
// Extract principal data
authenticationPrincipal = extractAuthenticationPrincipal(parsedClaims)
principalId, principalEmail = extractSubjectAndEmail(parsedClaims)
authenticationPrincipal = extractAuthenticationPrincipal(token)
principalId, principalEmail = extractSubjectAndEmail(token)
// Extract service account delegation info data
delegationInfo = extractServiceAccountDelegationInfo(parsedClaims)
// Extract audiences data
audiences, err = extractAudiences(parsedClaims)
if err != nil {
return nil, authenticationPrincipal, nil, nil, err
actClaim, hasActClaim := token.Get("act")
if hasActClaim {
actMap := map[string]interface{}{"act": actClaim}
delegationInfo = extractServiceAccountDelegationInfo(actMap)
}
// Extract project id and service account id
projectId := extractServiceAccountProjectId(parsedClaims)
serviceAccountId := extractServiceAccountId(parsedClaims)
// Extract audiences data
audiences = token.Audience()
// Calculate service account name if project id and service account id are available
// Extract project id and service account id
projectId := getTokenClaim(token, TokenClaimStackitProjectId)
serviceAccountId := getTokenClaim(token, TokenClaimStackitServiceAccountId)
// Calculate service account name if project and service account ids are available
if projectId != nil && serviceAccountId != nil {
accountName := fmt.Sprintf("projects/%s/service-accounts/%s", *projectId, *serviceAccountId)
serviceAccountName = &accountName
@ -696,94 +707,80 @@ func AuditAttributesFromAuthorizationHeader(request *ApiRequest) (
return auditClaims, authenticationPrincipal, audiences, &authenticationInfo, nil
}
func extractServiceAccountId(parsedClaims map[string]interface{}) *string {
projectId, projectIdExists := parsedClaims["stackit/serviceaccount/service-account.uid"]
if projectIdExists {
projectIdString := fmt.Sprintf("%s", projectId)
return &projectIdString
func getTokenClaim(token jwt.Token, claimName string) *string {
claim, claimExists := token.Get(claimName)
if claimExists {
claimString := fmt.Sprintf("%s", claim)
return &claimString
} else {
return nil
}
}
func extractServiceAccountProjectId(parsedClaims map[string]interface{}) *string {
projectId, projectIdExists := parsedClaims["stackit/project/project.id"]
if projectIdExists {
projectIdString := fmt.Sprintf("%s", projectId)
return &projectIdString
} else {
return nil
}
}
func extractAudiences(parsedClaims map[string]interface{}) ([]string, error) {
audClaim, _ := json.Marshal(parsedClaims["aud"])
var audiences []string
if err := json.Unmarshal(audClaim, &audiences); err != nil {
return nil, err
}
return audiences, nil
}
func extractAuthenticationPrincipal(parsedClaims map[string]interface{}) string {
subClaim, subExists := parsedClaims["sub"]
issuerClaim, issuerExists := parsedClaims["iss"]
func extractAuthenticationPrincipal(token jwt.Token) string {
subject := token.Subject()
issuer := token.Issuer()
var principal = "none/none"
if subExists && issuerExists {
principal = fmt.Sprintf("%s/%s", url.QueryEscape(subClaim.(string)), url.QueryEscape(issuerClaim.(string)))
if subject != "" && issuer != "" {
principal = fmt.Sprintf("%s/%s", url.QueryEscape(subject), url.QueryEscape(issuer))
}
return principal
}
func parseClaimsFromAuthorizationHeader(authorizationHeader string) (map[string]interface{}, map[string]interface{}, error) {
func parseToken(authorizationHeader string) (jwt.Token, error) {
parts := strings.Split(authorizationHeader, " ")
if len(parts) != 2 {
return nil, nil, ErrInvalidAuthorizationHeaderValue
return nil, ErrInvalidAuthorizationHeaderValue
}
if !strings.EqualFold(parts[0], "Bearer") {
return nil, nil, ErrTokenIsNotBearerToken
return nil, ErrTokenIsNotBearerToken
}
jwt := parts[1]
authorizationHeaderParts := strings.Split(jwt, ".")
jwtString := parts[1]
authorizationHeaderParts := strings.Split(jwtString, ".")
parsedClaims := make(map[string]interface{})
if len(authorizationHeaderParts) == 3 {
// base64 decoding
decodedString, err := base64.RawURLEncoding.DecodeString(authorizationHeaderParts[1])
token, err := jwt.ParseInsecure([]byte(parts[1]))
if err != nil {
return parsedClaims, nil, errors.Join(err, ErrInvalidBearerToken)
return nil, ErrInvalidBearerToken
}
return token, nil
}
return nil, ErrInvalidBearerToken
}
// unmarshall claim part of token
err = json.Unmarshal(decodedString, &parsedClaims)
func parseClaimsFromAuthorizationHeader(token jwt.Token) (map[string]interface{}, error) {
claimsMap, err := token.AsMap(context.Background())
if err != nil {
return parsedClaims, nil, err
return nil, err
}
// Collect user-friendly filtered subset of claims
filteredClaims := make(map[string]interface{})
err = json.Unmarshal(decodedString, &filteredClaims)
if err != nil {
return parsedClaims, nil, err
claims := map[string]interface{}{}
if len(token.Audience()) > 0 {
var audiences []any
for _, audience := range token.Audience() {
audiences = append(audiences, audience)
}
keysToDelete := make([]string, 0)
for key := range filteredClaims {
if key != "aud" && key != "email" && key != "iss" && key != "jti" && key != "sub" {
keysToDelete = append(keysToDelete, key)
}
}
for _, key := range keysToDelete {
delete(filteredClaims, key)
}
return parsedClaims, filteredClaims, nil
}
return parsedClaims, nil, ErrInvalidBearerToken
claims["aud"] = audiences
}
func extractServiceAccountDelegationInfoDetails(token map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo {
principalId, principalEmail := extractSubjectAndEmail(token)
for key := range claimsMap {
if key == "aud" {
continue
}
value := claimsMap[key]
t, isTime := value.(time.Time)
if isTime {
claims[key] = t.String()
} else if value != nil && value != "" {
claims[key] = value
}
}
return claims, nil
}
func extractServiceAccountDelegationInfoDetails(actClaims map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo {
principalId, principalEmail := extractSubjectAndEmailFromActClaims(actClaims)
delegation := auditV1.ServiceAccountDelegationInfo{Authority: &auditV1.ServiceAccountDelegationInfo_IdpPrincipal_{IdpPrincipal: &auditV1.ServiceAccountDelegationInfo_IdpPrincipal{
PrincipalId: principalId,
@ -792,7 +789,7 @@ func extractServiceAccountDelegationInfoDetails(token map[string]interface{}) []
}}}
delegations := []*auditV1.ServiceAccountDelegationInfo{&delegation}
nestedDelegations := extractServiceAccountDelegationInfo(token)
nestedDelegations := extractServiceAccountDelegationInfo(actClaims)
if len(nestedDelegations) > 0 {
return append(delegations, nestedDelegations...)
} else {
@ -800,9 +797,9 @@ func extractServiceAccountDelegationInfoDetails(token map[string]interface{}) []
}
}
func extractServiceAccountDelegationInfo(token map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo {
actor := token["act"]
if actor != nil {
func extractServiceAccountDelegationInfo(claims map[string]interface{}) []*auditV1.ServiceAccountDelegationInfo {
actor, hasActor := claims["act"]
if hasActor {
actorMap, hasActorClaim := actor.(map[string]interface{})
if hasActorClaim {
return extractServiceAccountDelegationInfoDetails(actorMap)
@ -811,18 +808,30 @@ func extractServiceAccountDelegationInfo(token map[string]interface{}) []*auditV
return nil
}
func extractSubjectAndEmail(token map[string]interface{}) (string, string) {
func extractSubjectAndEmailFromActClaims(actClaim map[string]interface{}) (string, string) {
var principalEmail string
principalId := fmt.Sprintf("%s", token["sub"])
principalEmailRaw := token["email"]
principalId := fmt.Sprintf("%s", actClaim["sub"])
principalEmailRaw := actClaim["email"]
if principalEmailRaw == nil {
principalEmail = "do-not-reply@stackit.cloud"
principalEmail = EmailAddressDoNotReplyAtStackItDotCloud
} else {
principalEmail = fmt.Sprintf("%s", principalEmailRaw)
}
return principalId, principalEmail
}
func extractSubjectAndEmail(token jwt.Token) (string, string) {
var principalEmail string
principalId := token.Subject()
emailClaim, hasEmail := token.Get("email")
if !hasEmail {
principalEmail = EmailAddressDoNotReplyAtStackItDotCloud
} else {
principalEmail = fmt.Sprintf("%s", emailClaim)
}
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:

View file

@ -419,13 +419,19 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Nil(t, err)
assert.Len(t, auditClaimsMap, 9)
assert.Equal(t, []interface{}{"stackit-resource-manager-dev"}, auditClaimsMap["aud"])
assert.Equal(t, "stackit-resource-manager-dev", auditClaimsMap["client_id"])
assert.Equal(t, "2024-08-23 09:28:46 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-23 09:13:46 +0000 UTC", auditClaimsMap["iat"])
assert.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"])
assert.Equal(t, "2024-08-23 09:13:46 +0000 UTC", auditClaimsMap["nbf"])
assert.Equal(t, "uaa.none", auditClaimsMap["scope"])
assert.Equal(t, "stackit-resource-manager-dev", auditClaimsMap["sub"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("stackit-resource-manager-dev"),
@ -448,13 +454,22 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Nil(t, err)
assert.Len(t, auditClaimsMap, 12)
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"])
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", auditClaimsMap["azp"])
assert.Equal(t, "my-service-yifc9e1@sa.stackit.cloud", auditClaimsMap["email"])
assert.Equal(t, "2024-08-03 07:15:43 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-02 07:15:43 +0000 UTC", auditClaimsMap["iat"])
assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"])
assert.Equal(t, "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"])
assert.Equal(t, "api", auditClaimsMap["stackit/serviceaccount/namespace"])
assert.Equal(t, "10f38b01-534b-47bb-a03a-e294ca2be4de", auditClaimsMap[TokenClaimStackitServiceAccountId])
assert.Equal(t, "legacy", auditClaimsMap["stackit/serviceaccount/token.source"])
assert.Equal(t, "dacc7830-843e-4c5e-86ff-aa0fb51d636f", auditClaimsMap[TokenClaimStackitProjectId])
assert.Equal(t, "10f38b01-534b-47bb-a03a-e294ca2be4de", auditClaimsMap["sub"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("10f38b01-534b-47bb-a03a-e294ca2be4de"),
@ -479,13 +494,26 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Nil(t, err)
assert.Len(t, auditClaimsMap, 13)
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"])
assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", auditClaimsMap["azp"])
assert.Equal(t, "service-account-2-tj9srt1@sa.stackit.cloud", auditClaimsMap["email"])
assert.Equal(t, "2024-08-19 10:21:47 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-19 09:21:47 +0000 UTC", auditClaimsMap["iat"])
assert.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"])
assert.Equal(t, "api", auditClaimsMap["stackit/serviceaccount/namespace"])
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", auditClaimsMap[TokenClaimStackitServiceAccountId])
assert.Equal(t, "oauth2", auditClaimsMap["stackit/serviceaccount/token.source"])
assert.Equal(t, "dacc7830-843e-4c5e-86ff-aa0fb51d636f", auditClaimsMap[TokenClaimStackitProjectId])
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", auditClaimsMap["sub"])
assert.NotNil(t, auditClaimsMap["act"])
act := auditClaimsMap["act"].(map[string]interface{})
assert.NotNil(t, act)
assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", act["sub"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("f45009b2-6433-43c1-b6c7-618c44359e71"),
@ -514,13 +542,29 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Nil(t, err)
assert.Len(t, auditClaimsMap, 13)
assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"])
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", auditClaimsMap["azp"])
assert.Equal(t, "service-account-3-fghsxw1@sa.stackit.cloud", auditClaimsMap["email"])
assert.Equal(t, "2024-08-19 10:22:43 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-19 09:22:43 +0000 UTC", auditClaimsMap["iat"])
assert.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"])
assert.Equal(t, "api", auditClaimsMap["stackit/serviceaccount/namespace"])
assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", auditClaimsMap[TokenClaimStackitServiceAccountId])
assert.Equal(t, "oauth2", auditClaimsMap["stackit/serviceaccount/token.source"])
assert.Equal(t, "dacc7830-843e-4c5e-86ff-aa0fb51d636f", auditClaimsMap[TokenClaimStackitProjectId])
assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", auditClaimsMap["sub"])
assert.NotNil(t, auditClaimsMap["act"])
act := auditClaimsMap["act"].(map[string]interface{})
assert.NotNil(t, act)
assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", act["sub"])
nestedAct := act["act"].(map[string]interface{})
assert.NotNil(t, nestedAct)
assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", nestedAct["sub"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("1734b4b6-1d5e-4819-9b50-29917a1b9ad5"),
@ -551,13 +595,21 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.Nil(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Nil(t, err)
assert.Len(t, auditClaimsMap, 11)
assert.Equal(t, []interface{}{"stackit-portal-login-dev-client-id"}, auditClaimsMap["aud"])
assert.Equal(t, "stackit-portal-login-dev-client-id", auditClaimsMap["client_id"])
assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", auditClaimsMap["email"])
assert.True(t, auditClaimsMap["email_verified"].(bool))
assert.Equal(t, "2024-08-02 09:19:27 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-08-02 08:19:27 +0000 UTC", auditClaimsMap["iat"])
assert.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"])
assert.Equal(t, "2024-08-02 08:19:27 +0000 UTC", auditClaimsMap["nbf"])
assert.Equal(t, "openid email", auditClaimsMap["scope"])
assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", auditClaimsMap["sub"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("cd94f01a-df2e-4456-902e-48f5e57f0b63"),
@ -572,6 +624,41 @@ func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) {
assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
})
t.Run("user token with simple aud claim", func(t *testing.T) {
headers := make(map[string][]string)
headers["Authorization"] = []string{userTokenWithSimpleAudience}
request := ApiRequest{Header: headers}
auditClaims, authenticationPrincipal, audiences, authenticationInfo, err :=
AuditAttributesFromAuthorizationHeader(&request)
assert.NoError(t, err)
auditClaimsMap := auditClaims.AsMap()
assert.Len(t, auditClaimsMap, 9)
assert.Equal(t, []interface{}{"https://stackit-service-account-dev.apps.01.cf.eu01.stackit.cloud"}, auditClaimsMap["aud"])
assert.Equal(t, "Lukas.Schmitt@stackit.cloud", auditClaimsMap["email"])
assert.Equal(t, "2024-11-21 09:40:35 +0000 UTC", auditClaimsMap["exp"])
assert.Equal(t, "2024-11-21 08:40:35 +0000 UTC", auditClaimsMap["iat"])
assert.Equal(t, "https://api.dev.stackit.cloud", auditClaimsMap["iss"])
assert.Equal(t, "c2be1651-1e54-4e6e-bac3-ef072b3f0149", auditClaimsMap["jti"])
assert.Equal(t, "2024-11-21 08:40:18 +0000 UTC", auditClaimsMap["nbf"])
assert.Equal(t, "openid email portal-bff", auditClaimsMap["scope"])
assert.Equal(t, "5e426aed-c487-4c48-af25-87f69cf9cdd4", auditClaimsMap["sub"])
principal := fmt.Sprintf("%s/%s",
url.QueryEscape("5e426aed-c487-4c48-af25-87f69cf9cdd4"),
url.QueryEscape("https://api.dev.stackit.cloud"))
assert.Equal(t, principal, authenticationPrincipal)
assert.Equal(t, []string{"https://stackit-service-account-dev.apps.01.cf.eu01.stackit.cloud"}, audiences)
assert.Equal(t, "5e426aed-c487-4c48-af25-87f69cf9cdd4", authenticationInfo.PrincipalId)
assert.Equal(t, "Lukas.Schmitt@stackit.cloud", authenticationInfo.PrincipalEmail)
assert.Nil(t, authenticationInfo.ServiceAccountName)
assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo)
})
}
func Test_NewAuditLogEntry(t *testing.T) {

View file

@ -18,6 +18,7 @@ const serviceAccountTokenRepeatedlyImpersonated = "Bearer eyJraWQiOiJaVFJqWlRNek
const serviceAccountTokenImpersonated = "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiMDJhZWY1MTYtMzE3Zi00ZWMxLWExZGYtMWFjYmQ0ZDQ5ZmUzIn0sInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiIwMmFlZjUxNi0zMTdmLTRlYzEtYTFkZi0xYWNiZDRkNDlmZTMiLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJleHAiOjE3MjQwNjI5MDcsImlhdCI6MTcyNDA1OTMwNywiZW1haWwiOiJzZXJ2aWNlLWFjY291bnQtMi10ajlzcnQxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiIzNzU1NTE4My0wMWI5LTQyNzAtYmRjMS02OWI0ZmNmZDVlZTkifQ.auBvvsIesFMAlWOCPCPC77DrrHF7gSKZwKs_Zry5KFvu2bpZZC1BcSXOc8b9eh0SzANI9M9aGJBhOzOm39-ZZ5XOQ-6_y1aWuEenYQ6kT5D3GzCUTMDzSi1lcZ4IG5nFMa_AAlVEN_7AMv7LHGtz49bWLJnAgeTo1cvof-OgP4mCQ5O6E0iyAq-5u8V8NJL7HIZy7BDe4J1mjfYhwKagrN7QFWu4fhN4TNS7d922X_6V489BhjRFRYjLW_qDnv912JorbGRz_XwNy_dPA81EkdMyKE0BJUezguJUEKEG2_JEi9O64Flcoi6x8cFHYhaDuMMSLipzePaHdyk2lQtH7Q"
const serviceAccountToken = "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxMGYzOGIwMS01MzRiLTQ3YmItYTAzYS1lMjk0Y2EyYmU0ZGUiLCJhdWQiOlsic3RhY2tpdCIsImFwaSJdLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3Rva2VuLnNvdXJjZSI6ImxlZ2FjeSIsInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiJjZDk0ZjAxYS1kZjJlLTQ0NTYtOTAyZS00OGY1ZTU3ZjBiNjMiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50Iiwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMTBmMzhiMDEtNTM0Yi00N2JiLWEwM2EtZTI5NGNhMmJlNGRlIiwiZXhwIjoxNzIyNjY5MzQzLCJpYXQiOjE3MjI1ODI5NDMsImVtYWlsIjoibXktc2VydmljZS15aWZjOWUxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiI4NGMzMGE0Ni0xMDAxLTQzNmYtODU5Zi04OWMwYmExOWJlMWUifQ.hb8X9VKc9xViHgNMyFHT9ePj_lyEwTV1D2es8E278WtoCJ9-4GPPQGjhcLGGrigjnvpRYV2LKzNqpQslerT5lFT_pHACsryaAE0ImYjmoe-nutA7BBpYuM_JN6pk5VIjVFLTqRKeIvFexPacqS2Vo3YoK1GvxPB8WPWBbGIsBtMl-PTm8OTwwzooBOoCRhhMR-E1lFbAymLsc1JI4yDQKLLomvhEopgmocCnQ-P1QkiKMqdkNxiD_YYLLYTOApg6d62BhqpH66ziqx493AStdZ8d5Kjvf3e1knDhaxVwNCghQj7lSo2kNAqZe__g2tiXpiZNTXBFJ_5HgQMLh67wng"
const userToken = "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"
const userTokenWithSimpleAudience = "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6InNlcnZpY2UtYWNjb3VudC1mMDdiZjZhOC02MjA3LTRmOGItYjNlOS03M2VkMGJlYjg4ZjUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3N0YWNraXQtc2VydmljZS1hY2NvdW50LWRldi5hcHBzLjAxLmNmLmV1MDEuc3RhY2tpdC5jbG91ZCIsImVtYWlsIjoiTHVrYXMuU2NobWl0dEBzdGFja2l0LmNsb3VkIiwiZXhwIjoxNzMyMTgyMDM1LCJpYXQiOjE3MzIxNzg0MzUsImlzcyI6Imh0dHBzOi8vYXBpLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiYzJiZTE2NTEtMWU1NC00ZTZlLWJhYzMtZWYwNzJiM2YwMTQ5IiwibmJmIjoxNzMyMTc4NDE4LCJyb2xlcyI6bnVsbCwic2NvcGUiOiJvcGVuaWQgZW1haWwgcG9ydGFsLWJmZiIsInN1YiI6IjVlNDI2YWVkLWM0ODctNGM0OC1hZjI1LTg3ZjY5Y2Y5Y2RkNCIsInVzZXJfaWQiOiIiLCJ4X2NsaWVudF9pZCI6IiIsInppZCI6IiJ9.notavailable"
var TestHeaders = map[string][]string{"user-agent": {"custom"}, "authorization": {userToken}}

15
go.mod
View file

@ -7,6 +7,7 @@ require (
github.com/Azure/go-amqp v1.2.0
github.com/bufbuild/protovalidate-go v0.7.2
github.com/google/uuid v1.6.0
github.com/lestrrat-go/jwx/v2 v2.1.2
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.0
github.com/testcontainers/testcontainers-go v0.34.0
@ -26,6 +27,7 @@ require (
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v27.1.1+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
@ -34,9 +36,15 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/cel-go v0.21.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
@ -52,6 +60,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
@ -62,11 +71,11 @@ require (
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

35
go.sum
View file

@ -30,6 +30,8 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
@ -51,6 +53,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@ -72,6 +76,18 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.1.2 h1:6poete4MPsO8+LAEVhpdrNI4Xp2xdiafgl2RD89moBc=
github.com/lestrrat-go/jwx/v2 v2.1.2/go.mod h1:pO+Gz9whn7MPdbsqSJzG8TlEpMZCwQDXnFJ+zsUVh8Y=
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-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
@ -108,6 +124,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
@ -123,6 +141,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -159,8 +178,8 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -187,14 +206,14 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=