From 6a70301b44104e8c77bbc51c3d097a812168f514 Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Mon, 14 Oct 2024 15:50:53 +0200 Subject: [PATCH] Add config to chained validator (#692) * Add config to chained validator * Add tests with mock oidc server * simplify config, remove cors middleware * go mod tidy * remove IssuerURL * better log statements * better flow --- .vscode/launch.json | 9 ++- api/middleware/cors/cors.go | 56 ------------------- api/utils/token/azure_principal.go | 19 +++++-- api/utils/token/chained_validator.go | 34 ++++++++++++ api/utils/token/unchecked_principal.go | 52 ------------------ api/utils/token/unchecked_validator.go | 36 ------------ api/utils/token/validator.go | 11 ++-- api/utils/token/validator_test.go | 76 ++++++++++++++++++++++++-- go.mod | 6 +- go.sum | 11 ++-- internal/config/config.go | 21 ++++--- main.go | 15 +++-- radixconfig.yaml | 6 +- 13 files changed, 161 insertions(+), 191 deletions(-) delete mode 100644 api/middleware/cors/cors.go create mode 100644 api/utils/token/chained_validator.go delete mode 100644 api/utils/token/unchecked_principal.go delete mode 100644 api/utils/token/unchecked_validator.go diff --git a/.vscode/launch.json b/.vscode/launch.json index 40125ec4..2b546f31 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,10 +14,7 @@ "RADIX_CONTAINER_REGISTRY":"radixdev.azurecr.io", "PIPELINE_IMG_TAG": "master-latest", "TEKTON_IMG_TAG": "main-latest", - "RADIX_CLUSTER_TYPE": "development", "RADIX_DNS_ZONE": "dev.radix.equinor.com", - "RADIX_CLUSTERNAME": "weekly-24", - "RADIX_ACTIVE_CLUSTER_EGRESS_IPS": "104.45.84.1", "REQUIRE_APP_CONFIGURATION_ITEM": "true", "REQUIRE_APP_AD_GROUPS": "true", "RADIX_ENVIRONMENT":"qa", @@ -25,8 +22,10 @@ "RADIX_APP":"radix-api", "LOG_LEVEL":"info", "LOG_PRETTY":"true", - "OIDC_AUDIENCE": "6dae42f8-4368-4678-94ff-3960e28e3630", - "OIDC_ISSUER": "https://sts.windows.net/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/" + "OIDC_AZURE_ISSUER": "https://sts.windows.net/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/", + "OIDC_AZURE_AUDIENCE": "6dae42f8-4368-4678-94ff-3960e28e3630", + "OIDC_KUBERNETES_ISSUER": "https://northeurope.oic.prod-aks.azure.com/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/79c72762-ce8d-4874-90cd-a119a5050503/", + "OIDC_KUBERNETES_AUDIENCE": "https://northeurope.oic.prod-aks.azure.com/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/79c72762-ce8d-4874-90cd-a119a5050503/" } } ] diff --git a/api/middleware/cors/cors.go b/api/middleware/cors/cors.go deleted file mode 100644 index 215b7c52..00000000 --- a/api/middleware/cors/cors.go +++ /dev/null @@ -1,56 +0,0 @@ -package cors - -import ( - "fmt" - - "github.com/rs/cors" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" -) - -func NewMiddleware(clusterName, radixDNSZone string) *cors.Cors { - - corsOptions := cors.Options{ - AllowedOrigins: []string{ - "http://localhost:3000", - "http://localhost:3001", - "http://127.0.0.1:3000", - "http://localhost:8000", - "http://localhost:8086", // For swaggerui testing - // TODO: We should consider: - // 1. "https://*.radix.equinor.com" - // 2. Keep cors rules in ingresses - fmt.Sprintf("https://console.%s", radixDNSZone), - getHostName("web", "radix-web-console-qa", clusterName, radixDNSZone), - getHostName("web", "radix-web-console-prod", clusterName, radixDNSZone), - getHostName("web", "radix-web-console-dev", clusterName, radixDNSZone), - // Due to active-cluster - getActiveClusterHostName("web", "radix-web-console-qa", radixDNSZone), - getActiveClusterHostName("web", "radix-web-console-prod", radixDNSZone), - getActiveClusterHostName("web", "radix-web-console-dev", radixDNSZone), - }, - AllowCredentials: true, - MaxAge: 600, - AllowedHeaders: []string{"Accept", "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization"}, - AllowedMethods: []string{"GET", "PUT", "POST", "OPTIONS", "DELETE", "PATCH"}, - } - - if zerolog.GlobalLevel() <= zerolog.TraceLevel { - // debugging mode - corsOptions.Debug = true - corsLogger := log.Logger.With().Str("pkg", "cors-middleware").Logger() - corsOptions.Logger = &corsLogger - } - - c := cors.New(corsOptions) - - return c -} - -func getActiveClusterHostName(componentName, namespace, radixDNSZone string) string { - return fmt.Sprintf("https://%s-%s.%s", componentName, namespace, radixDNSZone) -} - -func getHostName(componentName, namespace, clustername, radixDNSZone string) string { - return fmt.Sprintf("https://%s-%s.%s.%s", componentName, namespace, clustername, radixDNSZone) -} diff --git a/api/utils/token/azure_principal.go b/api/utils/token/azure_principal.go index 2cce1d28..abec28bd 100644 --- a/api/utils/token/azure_principal.go +++ b/api/utils/token/azure_principal.go @@ -2,6 +2,7 @@ package token import ( "context" + "fmt" "github.com/auth0/go-jwt-middleware/v2/validator" ) @@ -19,8 +20,8 @@ func (c *azureClaims) Validate(_ context.Context) error { type azurePrincipal struct { token string - claims *validator.ValidatedClaims - azureClaims *azureClaims + claims validator.RegisteredClaims + azureClaims azureClaims } func (p *azurePrincipal) Token() string { @@ -29,7 +30,13 @@ func (p *azurePrincipal) Token() string { func (p *azurePrincipal) IsAuthenticated() bool { return true } -func (p *azurePrincipal) Id() string { return p.azureClaims.ObjectId } +func (p *azurePrincipal) Id() string { + if p.azureClaims.ObjectId != "" { + return fmt.Sprintf("oid:%s", p.azureClaims.ObjectId) + } + + return fmt.Sprintf("sub:%s", p.claims.Subject) +} func (p *azurePrincipal) Name() string { if p.azureClaims.Upn != "" { @@ -44,5 +51,9 @@ func (p *azurePrincipal) Name() string { return p.azureClaims.AppId } - return p.azureClaims.ObjectId + if p.azureClaims.ObjectId != "" { + return p.azureClaims.ObjectId + } + + return p.claims.Subject } diff --git a/api/utils/token/chained_validator.go b/api/utils/token/chained_validator.go new file mode 100644 index 00000000..4f211ea1 --- /dev/null +++ b/api/utils/token/chained_validator.go @@ -0,0 +1,34 @@ +package token + +import ( + "context" + "errors" + "fmt" +) + +type ChainedValidator struct{ validators []ValidatorInterface } + +var _ ValidatorInterface = &ChainedValidator{} +var errNoValidatorsFound = errors.New("no validators found") + +func NewChainedValidator(validators ...ValidatorInterface) *ChainedValidator { + return &ChainedValidator{validators} +} + +func (v *ChainedValidator) ValidateToken(ctx context.Context, token string) (TokenPrincipal, error) { + var errs []error + + for _, validator := range v.validators { + principal, err := validator.ValidateToken(ctx, token) + if principal != nil { + return principal, nil + } + errs = append(errs, err) + } + + if len(errs) > 0 { + return nil, fmt.Errorf("no issuers could validate token: %w", errors.Join(errs...)) + } + + return nil, errNoValidatorsFound +} diff --git a/api/utils/token/unchecked_principal.go b/api/utils/token/unchecked_principal.go deleted file mode 100644 index 8073d857..00000000 --- a/api/utils/token/unchecked_principal.go +++ /dev/null @@ -1,52 +0,0 @@ -package token - -import ( - "context" - "fmt" - - "github.com/go-jose/go-jose/v4/jwt" -) - -func (c *unchechedClaimsPrincipal) Validate(_ context.Context) error { - return nil -} - -type unchechedClaimsPrincipal struct { - token string - claims *jwt.Claims - azureClaims *azureClaims -} - -func (p *unchechedClaimsPrincipal) Token() string { - return p.token -} -func (p *unchechedClaimsPrincipal) IsAuthenticated() bool { - return true -} -func (p *unchechedClaimsPrincipal) Id() string { - if p.azureClaims.ObjectId != "" { - return fmt.Sprintf("oid:%s", p.azureClaims.ObjectId) - } - - return fmt.Sprintf("sub:%s", p.claims.Subject) -} - -func (p *unchechedClaimsPrincipal) Name() string { - if p.azureClaims.Upn != "" { - return p.azureClaims.Upn - } - - if p.azureClaims.AppDisplayName != "" { - return p.azureClaims.AppDisplayName - } - - if p.azureClaims.AppId != "" { - return p.azureClaims.AppId - } - - if p.azureClaims.ObjectId != "" { - return p.azureClaims.ObjectId - } - - return p.claims.Subject -} diff --git a/api/utils/token/unchecked_validator.go b/api/utils/token/unchecked_validator.go deleted file mode 100644 index 4446be22..00000000 --- a/api/utils/token/unchecked_validator.go +++ /dev/null @@ -1,36 +0,0 @@ -package token - -import ( - "context" - "fmt" - "net/url" - - "github.com/equinor/radix-common/net/http" - "github.com/go-jose/go-jose/v4" - josejwt "github.com/go-jose/go-jose/v4/jwt" -) - -type UncheckedValidator struct{} - -var _ ValidatorInterface = &UncheckedValidator{} - -func NewUncheckedValidator(_ *url.URL, _ string) (*UncheckedValidator, error) { - return &UncheckedValidator{}, nil -} - -func (v *UncheckedValidator) ValidateToken(_ context.Context, token string) (TokenPrincipal, error) { - var registeredClaims josejwt.Claims - var azureClaims azureClaims - - jwt, err := josejwt.ParseSigned(token, []jose.SignatureAlgorithm{jose.HS256, jose.RS256}) - if err != nil { - return nil, http.ForbiddenError("invalid token") - } - err = jwt.UnsafeClaimsWithoutVerification(®isteredClaims, &azureClaims) - if err != nil { - return nil, http.ForbiddenError(fmt.Sprintf("failed to extract JWT unsafeClaims: %s", err.Error())) - } - - principal := &unchechedClaimsPrincipal{token: token, claims: ®isteredClaims, azureClaims: &azureClaims} - return principal, nil -} diff --git a/api/utils/token/validator.go b/api/utils/token/validator.go index f72dd461..f00d5898 100644 --- a/api/utils/token/validator.go +++ b/api/utils/token/validator.go @@ -18,6 +18,7 @@ type TokenPrincipal interface { } type ValidatorInterface interface { + // ValidateToken will return a TokenPrincipal object if token payload and signature is validated agains issuer. It will return nil principal and a error if it fails. ValidateToken(context.Context, string) (TokenPrincipal, error) } @@ -29,8 +30,8 @@ var _ ValidatorInterface = &Validator{} type KeyFunc func(context.Context) (interface{}, error) -func NewValidator(issuerUrl *url.URL, audience string) (*Validator, error) { - provider := jwks.NewCachingProvider(issuerUrl, 5*time.Hour) +func NewValidator(issuerUrl url.URL, audience string) (*Validator, error) { + provider := jwks.NewCachingProvider(&issuerUrl, 5*time.Hour) validator, err := validator.New( provider.KeyFunc, @@ -59,11 +60,11 @@ func (v *Validator) ValidateToken(ctx context.Context, token string) (TokenPrinc return nil, http.ForbiddenError("invalid token") } - azureClaims, ok := claims.CustomClaims.(*azureClaims) - if !ok { + azClaims, ok := claims.CustomClaims.(*azureClaims) + if !ok || azClaims == nil { return nil, http.ForbiddenError("invalid azure token") } - principal := &azurePrincipal{token: token, claims: claims, azureClaims: azureClaims} + principal := &azurePrincipal{token: token, claims: claims.RegisteredClaims, azureClaims: *azClaims} return principal, nil } diff --git a/api/utils/token/validator_test.go b/api/utils/token/validator_test.go index 8df2854f..6e04bc90 100644 --- a/api/utils/token/validator_test.go +++ b/api/utils/token/validator_test.go @@ -5,28 +5,94 @@ import ( "net/url" "testing" + "github.com/golang-jwt/jwt/v5" + "github.com/oauth2-proxy/mockoidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const ( - test_oidc_issuer = "https://radix.equinor.com" test_oidc_audience = "testaudience" test_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MjQ1YTJlYzEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xMjM0NTY3OC03NTY1LTIzNDItMjM0Mi0xMjM0MDViNDU5YjAvIiwiaWF0IjoxNTc1MzU1NTA4LCJuYmYiOjE1NzUzNTU1MDgsImV4cCI6MTU3NTM1OTQwOCwiYWNyIjoiMSIsImFpbyI6IjQyYXNkYXMiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDc5MDM5YTkwIiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJKb2huIiwiZ2l2ZW5fbmFtZSI6IkRvZSIsImhhc2dyb3VwcyI6InRydWUiLCJpcGFkZHIiOiIxMC4xMC4xMC4xMCIsIm5hbWUiOiJKb2huIERvZSIsIm9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzRmYzhmYTBlYSIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0xMjM0NTY3ODktMTIzNDU2OTc4MC0xMjM0NTY3ODktMTIzNDU2NyIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IjBoa2JpbEo3MTIzNHpSU3h6eHZiSW1hc2RmZ3N4amI2YXNkZmVOR2FzZGYiLCJ0aWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MDViNDU5YjAiLCJ1bmlxdWVfbmFtZSI6Im5vdC1leGlzdGluZy1yYWRpeC1lbWFpbEBlcXVpbm9yLmNvbSIsInVwbiI6Im5vdC1leGlzdGluZy10ZXN0LXJhZGl4LWVtYWlsQGVxdWlub3IuY29tIiwidXRpIjoiQlMxMmFzR2R1RXlyZUVjRGN2aDJBRyIsInZlciI6IjEuMCJ9.EB5z7Mk34NkFPCP8MqaNMo4UeWgNyO4-qEmzOVPxfoBqbgA16Ar4xeONXODwjZn9iD-CwJccusW6GP0xZ_PJHBFpfaJO_tLaP1k0KhT-eaANt112TvDBt0yjHtJg6He6CEDqagREIsH3w1mSm40zWLKGZeRLdnGxnQyKsTmNJ1rFRdY3AyoEgf6-pnJweUt0LaFMKmIJ2HornStm2hjUstBaji_5cSS946zqp4tgrc-RzzDuaQXzqlVL2J22SR2S_Oux_3yw88KmlhEFFP9axNcbjZrzW3L9XWnPT6UzVIaVRaNRSWfqDATg-jeHg4Gm1bp8w0aIqLdDxc9CfFMjuQ" ) -func TestNewValidator(t *testing.T) { - issuer, _ := url.Parse(test_oidc_issuer) +func TestValidUser(t *testing.T) { + issuer := createServer(t) v, err := NewValidator(issuer, test_oidc_audience) assert.NoError(t, err) assert.NotNil(t, v) + + principal, err := v.ValidateToken(context.Background(), createUser(t, issuer, test_oidc_audience, "user1")) + require.NoError(t, err) + assert.NotNil(t, principal) + assert.Equal(t, "sub:user1", principal.Id()) + assert.Equal(t, "user1", principal.Name()) } -func TestValidateToken(t *testing.T) { - issuer, _ := url.Parse(test_oidc_issuer) +func TestInvalidToken(t *testing.T) { + issuer := createServer(t) v, err := NewValidator(issuer, test_oidc_audience) + assert.NoError(t, err) + assert.NotNil(t, v) + + _, err = v.ValidateToken(context.Background(), createUser(t, issuer, "invalid audience", "user1")) + assert.Error(t, err) +} +func TestValidUserMultipleIssuers(t *testing.T) { + issuer1 := createServer(t) + issuer2 := createServer(t) + v1, err := NewValidator(issuer1, test_oidc_audience) + assert.NoError(t, err) + v2, err := NewValidator(issuer2, test_oidc_audience) + assert.NoError(t, err) + v := NewChainedValidator(v1, v2) + + principal, err := v.ValidateToken(context.Background(), createUser(t, issuer2, test_oidc_audience, "user1")) + require.NoError(t, err) + assert.NotNil(t, principal) + assert.Equal(t, "sub:user1", principal.Id()) + assert.Equal(t, "user1", principal.Name()) + + _, err = v.ValidateToken(context.Background(), createUser(t, issuer1, "invalid audience", "user1")) + assert.Error(t, err) + + fakeIssuer, err := url.Parse("http://fakeissuer/") require.NoError(t, err) + _, err = v.ValidateToken(context.Background(), createUser(t, *fakeIssuer, test_oidc_audience, "user1")) + assert.Error(t, err) _, err = v.ValidateToken(context.Background(), test_jwt) assert.Error(t, err) } + +func createServer(t *testing.T) url.URL { + m, err := mockoidc.Run() + require.NoError(t, err) + t.Cleanup(func() { + _ = m.Shutdown() + }) + + issuer, err := url.Parse(m.Issuer()) + require.NoError(t, err) + + return *issuer +} + +func createUser(t *testing.T, issuer url.URL, audience, subject string) string { + user := mockoidc.DefaultUser() + key, err := mockoidc.DefaultKeypair() + require.NoError(t, err) + + claims, err := user.Claims([]string{"profile", "email"}, &mockoidc.IDTokenClaims{ + RegisteredClaims: &jwt.RegisteredClaims{ + Issuer: issuer.String(), + Subject: subject, + Audience: []string{audience}, + }, + }) + require.NoError(t, err) + + signJWT, err := key.SignJWT(claims) + require.NoError(t, err) + return signJWT +} diff --git a/go.mod b/go.mod index 23fb4970..dc8ecf28 100644 --- a/go.mod +++ b/go.mod @@ -12,17 +12,17 @@ require ( github.com/equinor/radix-operator v1.62.0 github.com/evanphx/json-patch/v5 v5.9.0 github.com/felixge/httpsnoop v1.0.4 - github.com/go-jose/go-jose/v4 v4.0.2 + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang/mock v1.6.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/kedacore/keda/v2 v2.15.1 github.com/kelseyhightower/envconfig v1.4.0 github.com/marstr/guid v1.1.0 + github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 github.com/prometheus-operator/prometheus-operator/pkg/client v0.76.0 github.com/prometheus/client_golang v1.20.3 github.com/prometheus/common v0.55.0 - github.com/rs/cors v1.11.0 github.com/rs/xid v1.5.0 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.9.0 @@ -51,6 +51,7 @@ require ( github.com/expr-lang/expr v1.16.9 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -58,7 +59,6 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/cel-go v0.20.1 // indirect diff --git a/go.sum b/go.sum index d3fb7c3f..9ed82c22 100644 --- a/go.sum +++ b/go.sum @@ -89,8 +89,6 @@ github.com/equinor/radix-common v1.9.5 h1:p1xldkYUoavwIMguoxxOyVkOXLPA6K8qMsgzez github.com/equinor/radix-common v1.9.5/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY= github.com/equinor/radix-job-scheduler v1.11.0 h1:8wCmXOVl/1cto8q2WJQEE06Cw68/QmfoifYVR49vzkY= github.com/equinor/radix-job-scheduler v1.11.0/go.mod h1:yPXn3kDcMY0Z3kBkosjuefsdY1x2g0NlBeybMmHz5hc= -github.com/equinor/radix-operator v1.61.0 h1:kHWHn5p9S+wKqOTSKtju2URW5FKgAFng1p6RLRnaTmE= -github.com/equinor/radix-operator v1.61.0/go.mod h1:uRW9SgVZ94hkpq87npVv2YVviRuXNJ1zgCleya1uvr8= github.com/equinor/radix-operator v1.62.0 h1:lurDVymrDhlyopd46KMV28eUltrVUPCk3bnBRFuyCsU= github.com/equinor/radix-operator v1.62.0/go.mod h1:uRW9SgVZ94hkpq87npVv2YVviRuXNJ1zgCleya1uvr8= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= @@ -109,8 +107,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= -github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -289,6 +287,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk= +github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= @@ -346,8 +346,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= -github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= @@ -405,6 +403,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= diff --git a/internal/config/config.go b/internal/config/config.go index 7b233c65..7c1498e4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,6 +1,8 @@ package config import ( + "net/url" + "github.com/kelseyhightower/envconfig" "github.com/rs/zerolog/log" ) @@ -16,13 +18,18 @@ type Config struct { RequireAppADGroups bool `envconfig:"REQUIRE_APP_AD_GROUPS" default:"true"` LogLevel string `envconfig:"LOG_LEVEL" default:"info"` LogPrettyPrint bool `envconfig:"LOG_PRETTY" default:"false"` - ClusterName string `envconfig:"RADIX_CLUSTERNAME" required:"true"` - DNSZone string `envconfig:"RADIX_DNS_ZONE" required:"true"` - OidcIssuer string `envconfig:"OIDC_ISSUER" required:"true"` - OidcAudience string `envconfig:"OIDC_AUDIENCE" required:"true"` - AppName string `envconfig:"RADIX_APP" required:"true"` - EnvironmentName string `envconfig:"RADIX_ENVIRONMENT" required:"true"` - PrometheusUrl string `envconfig:"PROMETHEUS_URL" required:"true"` + + AppName string `envconfig:"RADIX_APP" required:"true" desc:"Should be radix-api"` + EnvironmentName string `envconfig:"RADIX_ENVIRONMENT" required:"true" desc:"Should be qa or prod"` + DNSZone string `envconfig:"RADIX_DNS_ZONE" required:"true" desc:"should be .radix.equinor.com"` + AzureOidc Oidc `envconfig:"OIDC_AZURE" required:"true"` + KubernetesOidc Oidc `envconfig:"OIDC_KUBERNETES" required:"true"` + PrometheusUrl string `envconfig:"PROMETHEUS_URL" required:"true"` +} + +type Oidc struct { + Issuer url.URL `envconfig:"ISSUER" required:"true"` + Audience string `envconfig:"Audience" required:"true"` } func MustParse() Config { diff --git a/main.go b/main.go index c6848f57..7cceb178 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,6 @@ import ( "io" "net/http" _ "net/http/pprof" - "net/url" "os" "os/signal" "sync" @@ -77,18 +76,18 @@ func initializeServer(c config.Config) *http.Server { } func initializeTokenValidator(c config.Config) token.ValidatorInterface { - issuerUrl, err := url.Parse(c.OidcIssuer) + azureValidator, err := token.NewValidator(c.AzureOidc.Issuer, c.AzureOidc.Audience) if err != nil { - log.Fatal().Err(err).Msg("Error parsing issuer url") + log.Fatal().Err(err).Msg("Error creating JWT Azure OIDC validator") } - // Set up the validator. - // jwtValidator, err := token.NewValidator(issuerUrl, c.OidcAudience) - jwtValidator, err := token.NewUncheckedValidator(issuerUrl, c.OidcAudience) + kubernetesValidator, err := token.NewValidator(c.KubernetesOidc.Issuer, c.KubernetesOidc.Audience) if err != nil { - log.Fatal().Err(err).Msg("Error creating JWT validator") + log.Fatal().Err(err).Msg("Error creating JWT Kubernetes OIDC validator") } - return jwtValidator + + chainedValidator := token.NewChainedValidator(azureValidator, kubernetesValidator) + return chainedValidator } func initializeMetricsServer(c config.Config) *http.Server { diff --git a/radixconfig.yaml b/radixconfig.yaml index 27fc3277..cc371895 100644 --- a/radixconfig.yaml +++ b/radixconfig.yaml @@ -35,10 +35,8 @@ spec: LOG_LEVEL: info LOG_PRETTY: "false" PROMETHEUS_URL: http://prometheus-operator-prometheus.monitor.svc.cluster.local:9090 - OIDC_AUDIENCE: "6dae42f8-4368-4678-94ff-3960e28e3630" - OIDC_ISSUER: "https://sts.windows.net/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/" - OIDC_AZURE_AUDIENCE: "" - OIDC_AZURE_ISSUER: "" + OIDC_AZURE_AUDIENCE: 6dae42f8-4368-4678-94ff-3960e28e3630 + OIDC_AZURE_ISSUER: https://sts.windows.net/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/ OIDC_KUBERNETES_ISSUER: "" OIDC_KUBERNETES_AUDIENCE: "" environmentConfig: