From 7c077114a1fb294d67de4c4954b5c0cc799aaee4 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Fri, 27 Sep 2024 11:13:25 +0200 Subject: [PATCH 01/29] naming --- api/middleware/auth/authentication.go | 111 +++++++++++++ api/middleware/cors/cors.go | 60 +++++++ .../logger}/middleware.go | 45 +++--- api/middleware/recovery/recovery.go | 13 ++ api/router/api.go | 146 ++++++------------ api/router/metrics.go | 10 +- api/utils/authn/anon_principal.go | 11 ++ api/utils/authn/azure_principal.go | 45 ++++++ api/utils/authn/validator.go | 64 ++++++++ api/utils/radix_middleware.go | 52 ++----- go.mod | 3 + go.sum | 6 + main.go | 7 +- 13 files changed, 402 insertions(+), 171 deletions(-) create mode 100644 api/middleware/auth/authentication.go create mode 100644 api/middleware/cors/cors.go rename api/{router => middleware/logger}/middleware.go (57%) create mode 100644 api/middleware/recovery/recovery.go create mode 100644 api/utils/authn/anon_principal.go create mode 100644 api/utils/authn/azure_principal.go create mode 100644 api/utils/authn/validator.go diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go new file mode 100644 index 00000000..8dfba5f5 --- /dev/null +++ b/api/middleware/auth/authentication.go @@ -0,0 +1,111 @@ +package auth + +import ( + "context" + "net/http" + "net/url" + + token "github.com/equinor/radix-api/api/utils/authn" + "github.com/equinor/radix-common/models" + radixhttp "github.com/equinor/radix-common/net/http" + "github.com/rs/zerolog/log" + "github.com/urfave/negroni/v3" +) + +type ctxUserKey struct{} +type ctxImpersonationKey struct{} + +func CreateAuthenticationMiddleware(issuer, audience string) negroni.HandlerFunc { + issuerUrl, err := url.Parse(issuer) + if err != nil { + log.Fatal().Err(err).Msg("Error parsing issuer url") + } + + // Set up the validator. + jwtValidator, err := token.NewValidator(issuerUrl, audience) + if err != nil { + log.Fatal().Err(err).Msg("Error creating JWT validator") + } + + return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + ctx := r.Context() + logger := log.Ctx(ctx) + if r.Header.Get("authorization") == "" { + next(w, r) + return + } + + token, err := radixhttp.GetBearerTokenFromHeader(r) + if err != nil { + logger.Warn().Err(err).Msg("authentication error") + if err = radixhttp.ErrorResponse(w, r, err); err != nil { + logger.Err(err).Msg("failed to write response") + } + return + } + principal, err := jwtValidator.ValidateToken(ctx, token) + if err != nil { + logger.Warn().Err(err).Msg("authentication error") + if err = radixhttp.ErrorResponse(w, r, err); err != nil { + logger.Err(err).Msg("failed to write response") + } + return + } + logContext := log.Ctx(ctx).With().Str("user", principal.Subject()) + + impersonation, err := radixhttp.GetImpersonationFromHeader(r) + if err != nil { + logger.Warn().Err(err).Msg("authorization error") + if err = radixhttp.ErrorResponse(w, r, radixhttp.UnexpectedError("Problems impersonating", err)); err != nil { + logger.Err(err).Msg("failed to write response") + } + return + } + if impersonation.PerformImpersonation() { + logContext = logContext.Str("impersonate_user", impersonation.User).Strs("impersonate_groups", impersonation.Groups) + } + + ctx = context.WithValue(ctx, ctxUserKey{}, principal) + ctx = context.WithValue(ctx, ctxImpersonationKey{}, impersonation) + ctx = logContext.Logger().WithContext(ctx) + r = r.WithContext(ctx) + + next(w, r) + } +} + +func GetToken(ctx context.Context) token.TokenPrincipal { + val, ok := ctx.Value(ctxUserKey{}).(token.TokenPrincipal) + + if !ok { + return token.NewAnonymousPrincipal() + } + + return val +} + +func GetImpersonation(ctx context.Context) models.Impersonation { + if val, ok := ctx.Value(ctxImpersonationKey{}).(models.Impersonation); ok { + return val + } + + return models.Impersonation{} +} + +func CreateAuthorizeRequiredMiddleware() negroni.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + logger := log.Ctx(r.Context()) + user := GetToken(r.Context()) + + if !user.IsAuthenticated() { + logger.Warn().Msg("authorization error") + if err := radixhttp.ErrorResponse(w, r, radixhttp.ForbiddenError("Authorization is required")); err != nil { + logger.Err(err).Msg("failed to write response") + } + return + } + + next(w, r) + return + } +} diff --git a/api/middleware/cors/cors.go b/api/middleware/cors/cors.go new file mode 100644 index 00000000..c125e37d --- /dev/null +++ b/api/middleware/cors/cors.go @@ -0,0 +1,60 @@ +package cors + +import ( + "fmt" + "os" + + "github.com/equinor/radix-api/api/defaults" + "github.com/rs/cors" + "github.com/rs/zerolog/log" +) + +func CreateMiddleware(clusterName string, debugMode bool) *cors.Cors { + radixDNSZone := os.Getenv(defaults.RadixDNSZoneEnvironmentVariable) + + 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 debugMode { + // debugging mode + corsOptions.Debug = true + corsLogger := log.Logger.With().Str("pkg", "cors-middleware").Logger() + corsOptions.Logger = &corsLogger + // necessary header to allow ajax requests directly from radix-web-console app in browser + corsOptions.AllowedHeaders = append(corsOptions.AllowedHeaders, "X-Requested-With") + } + + 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/router/middleware.go b/api/middleware/logger/middleware.go similarity index 57% rename from api/router/middleware.go rename to api/middleware/logger/middleware.go index 71b510bc..e0ff33a1 100644 --- a/api/router/middleware.go +++ b/api/middleware/logger/middleware.go @@ -1,17 +1,17 @@ -package router +package logger import ( - "context" "net" "net/http" "github.com/felixge/httpsnoop" "github.com/rs/xid" "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/urfave/negroni/v3" ) -func zerologRequestLogger() negroni.HandlerFunc { +func CreateZerologRequestLoggerMiddleware() negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { m := httpsnoop.CaptureMetrics(next, w, r) @@ -27,36 +27,35 @@ func zerologRequestLogger() negroni.HandlerFunc { ev = logger.Info() //nolint:zerologlint // Msg for ev is called later } - remoteIp, _, _ := net.SplitHostPort(r.RemoteAddr) ev. - Str("remote_addr", remoteIp). - Str("referer", r.Referer()). - Str("method", r.Method). - Str("path", r.URL.Path). - Str("query", r.URL.RawQuery). Int("status", m.Code). Int64("body_size", m.Written). Int64("elapsed_ms", m.Duration.Milliseconds()). - Str("user_agent", r.UserAgent()). Msg(http.StatusText(m.Code)) } } -type setZerologLoggerFn func(context.Context) zerolog.Logger +func CreateZerologRequestIdMiddleware() negroni.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + logger := log.Ctx(r.Context()).With().Str("request_id", xid.New().String()).Logger() + r = r.WithContext(logger.WithContext(r.Context())) -// zerologLoggerWithRequestId returns a zerolog logger with a request_id field with a new GUID -func zerologLoggerWithRequestId(ctx context.Context) zerolog.Logger { - return zerolog.Ctx(ctx).With().Str("request_id", xid.New().String()).Logger() + next(w, r) + } } - -// setZerologLogger attaches the zerolog logger returned from each loggerFns function to a shallow copy of the request context -// The logger can then be accessed in a controller method by calling zerolog.Ctx(ctx) -func setZerologLogger(loggerFns ...setZerologLoggerFn) negroni.HandlerFunc { +func CreateZerologRequestDetailsMiddleware() negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - for _, loggerFn := range loggerFns { - logger := loggerFn(r.Context()) - r = r.WithContext(logger.WithContext(r.Context())) - } - next.ServeHTTP(w, r) + remoteIp, _, _ := net.SplitHostPort(r.RemoteAddr) + logger := log.Ctx(r.Context()).With(). + Str("method", r.Method). + Str("path", r.URL.Path). + Str("remote_addr", remoteIp). + Str("referer", r.Referer()). + Str("query", r.URL.RawQuery). + Str("user_agent", r.UserAgent()). + Logger() + r = r.WithContext(logger.WithContext(r.Context())) + + next(w, r) } } diff --git a/api/middleware/recovery/recovery.go b/api/middleware/recovery/recovery.go new file mode 100644 index 00000000..e7e44515 --- /dev/null +++ b/api/middleware/recovery/recovery.go @@ -0,0 +1,13 @@ +package recovery + +import ( + "github.com/rs/zerolog/log" + "github.com/urfave/negroni/v3" +) + +func CreateMiddleware() *negroni.Recovery { + rec := negroni.NewRecovery() + rec.PrintStack = false + rec.Logger = &log.Logger + return rec +} diff --git a/api/router/api.go b/api/router/api.go index 4fa47268..65155c70 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -1,137 +1,79 @@ package router import ( - "fmt" "net/http" - "os" - "github.com/equinor/radix-api/api/defaults" + "github.com/equinor/radix-api/api/middleware/auth" + "github.com/equinor/radix-api/api/middleware/cors" + "github.com/equinor/radix-api/api/middleware/logger" + "github.com/equinor/radix-api/api/middleware/recovery" "github.com/equinor/radix-api/api/utils" "github.com/equinor/radix-api/models" "github.com/equinor/radix-api/swaggerui" "github.com/gorilla/mux" - "github.com/rs/cors" - "github.com/rs/zerolog/log" "github.com/urfave/negroni/v3" ) const ( - apiVersionRoute = "/api/v1" - admissionControllerRootPath = "/admissioncontrollers" - buildstatusControllerRootPath = "/buildstatus" - healthControllerPath = "/health/" - radixDNSZoneEnvironmentVariable = "RADIX_DNS_ZONE" - swaggerUIPath = "/swaggerui" + apiVersionRoute = "/api/v1" ) // NewAPIHandler Constructor function -func NewAPIHandler(clusterName string, kubeUtil utils.KubeUtil, controllers ...models.Controller) http.Handler { - router := mux.NewRouter().StrictSlash(true) - - initializeSwaggerUI(router) - initializeAPIServer(kubeUtil, router, controllers) - initializeHealthEndpoint(router) +func NewAPIHandler(clusterName, oidcIssuer, oidcAudience string, kubeUtil utils.KubeUtil, controllers ...models.Controller) http.Handler { + useOutClusterClient := kubeUtil.IsUseOutClusterClient() serveMux := http.NewServeMux() - serveMux.Handle(healthControllerPath, negroni.New( - negroni.Wrap(router), - )) - - serveMux.Handle("/api/", negroni.New( - negroni.Wrap(router), - )) - - // TODO: We should maybe have oauth to stop any non-radix user from being able to see the API - serveMux.Handle("/swaggerui/", negroni.New( - negroni.Wrap(router), - )) - - rec := negroni.NewRecovery() - rec.PrintStack = false + serveMux.Handle("/health/", createHealthHandler()) + serveMux.Handle("/swaggerui/", createSwaggerHandler()) + serveMux.Handle("/api/", createApiRouter(kubeUtil, controllers)) n := negroni.New( - rec, - setZerologLogger(zerologLoggerWithRequestId), - zerologRequestLogger(), + recovery.CreateMiddleware(), + cors.CreateMiddleware(clusterName, !useOutClusterClient), + logger.CreateZerologRequestIdMiddleware(), + logger.CreateZerologRequestDetailsMiddleware(), + auth.CreateAuthenticationMiddleware(oidcIssuer, oidcAudience), + logger.CreateZerologRequestLoggerMiddleware(), ) n.UseHandler(serveMux) - useOutClusterClient := kubeUtil.IsUseOutClusterClient() - return getCORSHandler(clusterName, n, useOutClusterClient) -} - -func getCORSHandler(clusterName string, handler http.Handler, useOutClusterClient bool) http.Handler { - radixDNSZone := os.Getenv(defaults.RadixDNSZoneEnvironmentVariable) - - 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 !useOutClusterClient { - // debugging mode - corsOptions.Debug = true - corsLogger := log.Logger.With().Str("pkg", "cors-middleware").Logger() - corsOptions.Logger = &corsLogger - // necessary header to allow ajax requests directly from radix-web-console app in browser - corsOptions.AllowedHeaders = append(corsOptions.AllowedHeaders, "X-Requested-With") - } - - c := cors.New(corsOptions) - - return c.Handler(handler) -} - -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) + return n } - -func initializeAPIServer(kubeUtil utils.KubeUtil, router *mux.Router, controllers []models.Controller) { +func createApiRouter(kubeUtil utils.KubeUtil, controllers []models.Controller) *mux.Router { + router := mux.NewRouter().StrictSlash(true) for _, controller := range controllers { for _, route := range controller.GetRoutes() { - addHandlerRoute(kubeUtil, router, route) + path := apiVersionRoute + route.Path + handler := utils.NewRadixMiddleware( + kubeUtil, + path, + route.Method, + route.AllowUnauthenticatedUsers, + route.KubeApiConfig.QPS, + route.KubeApiConfig.Burst, + route.HandlerFunc, + ) + + n := negroni.New() + if !route.AllowUnauthenticatedUsers { + n.Use(auth.CreateAuthorizeRequiredMiddleware()) + } + n.UseHandler(handler) + router.Handle(path, n).Methods(route.Method) } } + return router } -func initializeSwaggerUI(router *mux.Router) { +func createSwaggerHandler() http.Handler { swaggerFsHandler := http.FileServer(http.FS(swaggerui.FS())) - swaggerui := http.StripPrefix(swaggerUIPath, swaggerFsHandler) - router.PathPrefix(swaggerUIPath).Handler(swaggerui) -} + swaggerui := http.StripPrefix("swaggerui", swaggerFsHandler) -func initializeHealthEndpoint(router *mux.Router) { - router.HandleFunc(healthControllerPath, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }).Methods("GET") + return swaggerui } -func addHandlerRoute(kubeUtil utils.KubeUtil, router *mux.Router, route models.Route) { - path := apiVersionRoute + route.Path - router.HandleFunc(path, - utils.NewRadixMiddleware(kubeUtil, path, route.Method, route.AllowUnauthenticatedUsers, route.KubeApiConfig.QPS, route.KubeApiConfig.Burst, route.HandlerFunc).Handle).Methods(route.Method) +func createHealthHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusOK) + }) } diff --git a/api/router/metrics.go b/api/router/metrics.go index 0e36a9dd..def1f216 100644 --- a/api/router/metrics.go +++ b/api/router/metrics.go @@ -3,6 +3,7 @@ package router import ( "net/http" + "github.com/equinor/radix-api/api/middleware/logger" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/urfave/negroni/v3" ) @@ -12,12 +13,11 @@ func NewMetricsHandler() http.Handler { serveMux := http.NewServeMux() serveMux.Handle("GET /metrics", promhttp.Handler()) - rec := negroni.NewRecovery() - rec.PrintStack = false n := negroni.New( - rec, - setZerologLogger(zerologLoggerWithRequestId), - zerologRequestLogger(), + recovery.CreateMiddleware(), + logger.CreateZerologRequestIdMiddleware(), + logger.CreateZerologRequestDetailsMiddleware(), + logger.CreateZerologRequestLoggerMiddleware(), ) n.UseHandler(serveMux) diff --git a/api/utils/authn/anon_principal.go b/api/utils/authn/anon_principal.go new file mode 100644 index 00000000..ec9b6702 --- /dev/null +++ b/api/utils/authn/anon_principal.go @@ -0,0 +1,11 @@ +package token + +type AnonPrincipal struct{} + +func (p *AnonPrincipal) Token() string { return "" } +func (p *AnonPrincipal) Subject() string { return "anonymous" } +func (p *AnonPrincipal) IsAuthenticated() bool { return false } + +func NewAnonymousPrincipal() *AnonPrincipal { + return &AnonPrincipal{} +} diff --git a/api/utils/authn/azure_principal.go b/api/utils/authn/azure_principal.go new file mode 100644 index 00000000..74d3a121 --- /dev/null +++ b/api/utils/authn/azure_principal.go @@ -0,0 +1,45 @@ +package token + +import ( + "context" + "errors" + + "github.com/auth0/go-jwt-middleware/v2/validator" +) + +type azureClaims struct { + AppId string `json:"appid,omitempty"` + Upn string `json:"upn,omitempty"` +} + +func (c *azureClaims) Validate(_ context.Context) error { + if c == nil { + return errors.New("invalid claim") + } + + if c.Upn == "" && c.AppId == "" { + return errors.New("missing one of the required field: upn,appid") + } + + return nil +} + +type azurePrincipal struct { + token string + claims *validator.ValidatedClaims + azureClaims *azureClaims +} + +func (p *azurePrincipal) Token() string { + return p.token +} +func (p *azurePrincipal) IsAuthenticated() bool { + return true +} +func (p *azurePrincipal) Subject() string { + if p.azureClaims.Upn != "" { + return p.azureClaims.Upn + } + + return p.azureClaims.AppId +} diff --git a/api/utils/authn/validator.go b/api/utils/authn/validator.go new file mode 100644 index 00000000..097b154a --- /dev/null +++ b/api/utils/authn/validator.go @@ -0,0 +1,64 @@ +package token + +import ( + "context" + "errors" + "net/url" + "time" + + "github.com/auth0/go-jwt-middleware/v2/jwks" + "github.com/auth0/go-jwt-middleware/v2/validator" +) + +type TokenPrincipal interface { + IsAuthenticated() bool + Token() string + Subject() string +} + +type ValidatorInterface interface { + ValidateToken(ctx context.Context, token string) (*validator.RegisteredClaims, error) +} + +type Validator struct { + validator *validator.Validator +} + +func NewValidator(issuerUrl *url.URL, audience string) (*Validator, error) { + provider := jwks.NewCachingProvider(issuerUrl, 5*time.Minute) + + validator, err := validator.New( + provider.KeyFunc, + validator.RS256, + issuerUrl.String(), + []string{audience}, + validator.WithCustomClaims(func() validator.CustomClaims { + return &azureClaims{} + }), + ) + if err != nil { + return nil, err + } + + return &Validator{validator: validator}, nil +} + +func (v *Validator) ValidateToken(ctx context.Context, token string) (TokenPrincipal, error) { + validateToken, err := v.validator.ValidateToken(ctx, token) + if err != nil { + return nil, err + } + + claims, ok := validateToken.(*validator.ValidatedClaims) + if !ok { + return nil, errors.New("invalid token") + } + + azureClaims, ok := claims.CustomClaims.(*azureClaims) + if !ok { + return nil, errors.New("invalid azure token") + } + + principal := &azurePrincipal{token: token, claims: claims, azureClaims: azureClaims} + return principal, nil +} diff --git a/api/utils/radix_middleware.go b/api/utils/radix_middleware.go index b8796d8b..8665b744 100644 --- a/api/utils/radix_middleware.go +++ b/api/utils/radix_middleware.go @@ -5,6 +5,7 @@ import ( "time" "github.com/equinor/radix-api/api/metrics" + "github.com/equinor/radix-api/api/middleware/auth" "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" "github.com/gorilla/mux" @@ -39,7 +40,7 @@ func NewRadixMiddleware(kubeUtil KubeUtil, path, method string, allowUnauthentic } // Handle Wraps radix handler methods -func (handler *RadixMiddleware) Handle(w http.ResponseWriter, r *http.Request) { +func (handler *RadixMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { start := time.Now() defer func() { @@ -57,25 +58,8 @@ func (handler *RadixMiddleware) Handle(w http.ResponseWriter, r *http.Request) { func (handler *RadixMiddleware) handleAuthorization(w http.ResponseWriter, r *http.Request) { logger := log.Ctx(r.Context()) - useOutClusterClient := handler.kubeUtil.IsUseOutClusterClient() - token, err := getBearerTokenFromHeader(r, useOutClusterClient) - - if err != nil { - logger.Warn().Err(err).Msg("authorization error") - if err = radixhttp.ErrorResponse(w, r, err); err != nil { - logger.Err(err).Msg("failed to write response") - } - return - } - - impersonation, err := radixhttp.GetImpersonationFromHeader(r) - if err != nil { - logger.Warn().Err(err).Msg("authorization error") - if err = radixhttp.ErrorResponse(w, r, radixhttp.UnexpectedError("Problems impersonating", err)); err != nil { - logger.Err(err).Msg("failed to write response") - } - return - } + token := auth.GetToken(r.Context()).Token() + impersonation := auth.GetImpersonation(r.Context()) restOptions := handler.getRestClientOptions() inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient := handler.kubeUtil.GetInClusterKubernetesClient(restOptions...) @@ -111,6 +95,16 @@ func (handler *RadixMiddleware) handleAuthorization(w http.ResponseWriter, r *ht handler.next(accounts, w, r) } +func (handler *RadixMiddleware) handleAnonymous(w http.ResponseWriter, r *http.Request) { + restOptions := handler.getRestClientOptions() + inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient := handler.kubeUtil.GetInClusterKubernetesClient(restOptions...) + + sa := models.NewServiceAccount(inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient) + accounts := models.Accounts{ServiceAccount: sa} + + handler.next(accounts, w, r) +} + func (handler *RadixMiddleware) getRestClientOptions() []RestClientConfigOption { var options []RestClientConfigOption @@ -124,21 +118,3 @@ func (handler *RadixMiddleware) getRestClientOptions() []RestClientConfigOption return options } - -func (handler *RadixMiddleware) handleAnonymous(w http.ResponseWriter, r *http.Request) { - restOptions := handler.getRestClientOptions() - inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient := handler.kubeUtil.GetInClusterKubernetesClient(restOptions...) - - sa := models.NewServiceAccount(inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient) - accounts := models.Accounts{ServiceAccount: sa} - - handler.next(accounts, w, r) -} - -func getBearerTokenFromHeader(r *http.Request, useOutClusterClient bool) (string, error) { - if useOutClusterClient { - return radixhttp.GetBearerTokenFromHeader(r) - } - // if we're in debug mode, arbitrary bearer token is injected - return "some_arbitrary_token", nil -} diff --git a/go.mod b/go.mod index cf914047..cd396fb1 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,14 @@ go 1.22.0 toolchain go1.22.5 require ( + github.com/auth0/go-jwt-middleware/v2 v2.2.2 github.com/cert-manager/cert-manager v1.15.0 github.com/equinor/radix-common v1.9.5 github.com/equinor/radix-job-scheduler v1.11.0 github.com/equinor/radix-operator v1.61.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 @@ -29,6 +31,7 @@ require ( github.com/tektoncd/pipeline v0.55.0 github.com/urfave/negroni/v3 v3.1.0 golang.org/x/sync v0.8.0 + gopkg.in/go-jose/go-jose.v2 v2.6.3 k8s.io/api v0.31.0 k8s.io/apimachinery v0.31.0 k8s.io/client-go v0.31.0 diff --git a/go.sum b/go.sum index 008033ba..c6d0a863 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8V github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/auth0/go-jwt-middleware/v2 v2.2.2 h1:vrvkFZf72r3Qbt45KLjBG3/6Xq2r3NTixWKu2e8de9I= +github.com/auth0/go-jwt-middleware/v2 v2.2.2/go.mod h1:4vwxpVtu/Kl4c4HskT+gFLjq0dra8F1joxzamrje6J0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -107,6 +109,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-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= @@ -739,6 +743,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= +gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= diff --git a/main.go b/main.go index acca65de..3253f52a 100644 --- a/main.go +++ b/main.go @@ -66,7 +66,7 @@ func main() { var servers []*http.Server - srv, err := initializeServer(*port, clusterName, *useOutClusterClient) + srv, err := initializeServer(*port, clusterName, "https://sts.windows.net/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/", "6dae42f8-4368-4678-94ff-3960e28e3630", *useOutClusterClient) if err != nil { log.Fatal().Err(err).Msg("Failed to initialize API server") } @@ -82,12 +82,13 @@ func main() { shutdownServersGracefulOnSignal(servers...) } -func initializeServer(port, clusterName string, useOutClusterClient bool) (*http.Server, error) { +func initializeServer(port, clusterName, oidcIssuer, oidcAudience string, useOutClusterClient bool) (*http.Server, error) { controllers, err := getControllers() if err != nil { return nil, fmt.Errorf("failed to initialize controllers: %w", err) } - handler := router.NewAPIHandler(clusterName, utils.NewKubeUtil(useOutClusterClient), controllers...) + + handler := router.NewAPIHandler(clusterName, oidcIssuer, oidcAudience, utils.NewKubeUtil(useOutClusterClient), controllers...) srv := &http.Server{ Addr: fmt.Sprintf(":%s", port), Handler: handler, From 2538465319fbc09bc31900133e066f343ed5c9c4 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Fri, 27 Sep 2024 14:03:26 +0200 Subject: [PATCH 02/29] fic test build failures --- .../applications_controller_test.go | 23 ++-- api/applications/applications_handler.go | 9 +- .../applications_handler_config.go | 46 ------- .../applications_handler_factory.go | 5 +- api/jobs/job_controller.go | 26 ++-- api/jobs/job_controller_test.go | 2 +- api/jobs/job_handler.go | 22 +-- api/jobs/job_handler_test.go | 10 +- api/jobs/start_job_handler.go | 27 ++-- api/middleware/cors/cors.go | 8 +- api/router/api.go | 6 +- api/router/metrics.go | 1 + api/test/utils.go | 4 +- api/utils/kubernetes.go | 18 +-- go.mod | 4 +- go.sum | 2 - internal/config/config.go | 38 ++++++ main.go | 126 ++++++------------ 18 files changed, 158 insertions(+), 219 deletions(-) delete mode 100644 api/applications/applications_handler_config.go create mode 100644 internal/config/config.go diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 0e9f5e20..8ffa2956 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -18,6 +18,7 @@ import ( jobModels "github.com/equinor/radix-api/api/jobs/models" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/utils" + "github.com/equinor/radix-api/internal/config" "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" @@ -52,7 +53,7 @@ const ( func setupTest(t *testing.T, requireAppConfigurationItem, requireAppADGroups bool) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *radixfake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { return setupTestWithFactory(t, newTestApplicationHandlerFactory( - ApplicationHandlerConfig{RequireAppConfigurationItem: requireAppConfigurationItem, RequireAppADGroups: requireAppADGroups}, + config.Config{RequireAppConfigurationItem: requireAppConfigurationItem, RequireAppADGroups: requireAppADGroups}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil }, @@ -112,7 +113,7 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return false, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil }))) @@ -129,7 +130,7 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, rr v1.RadixRegistration) (bool, error) { return rr.GetName() == "my-second-app", nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil }))) @@ -146,7 +147,7 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil }))) @@ -227,7 +228,7 @@ func TestSearchApplicationsPost(t *testing.T) { controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil }))) @@ -309,7 +310,7 @@ func TestSearchApplicationsPost(t *testing.T) { controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return false, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil }))) @@ -405,7 +406,7 @@ func TestSearchApplicationsGet(t *testing.T) { controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil }))) @@ -477,7 +478,7 @@ func TestSearchApplicationsGet(t *testing.T) { controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return false, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil }))) @@ -1480,7 +1481,7 @@ func TestModifyApplication_UpdateADGroupValidation(t *testing.T) { for _, ts := range scenarios { t.Run(ts.name, func(t *testing.T) { _, controllerTestUtils, _, radixClient, _, _, _, _ := setupTestWithFactory(t, newTestApplicationHandlerFactory( - ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: ts.requireAppADGroups}, + config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: ts.requireAppADGroups}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return ts.hasAccessToAdGroups, nil }, @@ -1970,11 +1971,11 @@ func buildApplicationRegistrationRequest(applicationRegistration applicationMode } type testApplicationHandlerFactory struct { - config ApplicationHandlerConfig + config config.Config hasAccessToGetConfigMap hasAccessToGetConfigMapFunc } -func newTestApplicationHandlerFactory(config ApplicationHandlerConfig, hasAccessToGetConfigMap hasAccessToGetConfigMapFunc) *testApplicationHandlerFactory { +func newTestApplicationHandlerFactory(config config.Config, hasAccessToGetConfigMap hasAccessToGetConfigMapFunc) *testApplicationHandlerFactory { return &testApplicationHandlerFactory{ config: config, hasAccessToGetConfigMap: hasAccessToGetConfigMap, diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index a11a439b..c39fd44a 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -17,6 +17,7 @@ import ( jobModels "github.com/equinor/radix-api/api/jobs/models" "github.com/equinor/radix-api/api/kubequery" apimodels "github.com/equinor/radix-api/api/models" + "github.com/equinor/radix-api/internal/config" "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" @@ -54,16 +55,16 @@ type ApplicationHandler struct { jobHandler job.JobHandler environmentHandler environments.EnvironmentHandler accounts models.Accounts - config ApplicationHandlerConfig + config config.Config namespace string hasAccessToGetConfigMap func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) } // NewApplicationHandler Constructor -func NewApplicationHandler(accounts models.Accounts, config ApplicationHandlerConfig, hasAccessToGetConfigMap hasAccessToGetConfigMapFunc) ApplicationHandler { +func NewApplicationHandler(accounts models.Accounts, config config.Config, hasAccessToGetConfigMap hasAccessToGetConfigMapFunc) ApplicationHandler { return ApplicationHandler{ accounts: accounts, - jobHandler: job.Init(accounts, deployments.Init(accounts)), + jobHandler: job.Init(accounts, deployments.Init(accounts), config.PipelineImageTag, config.TektonImageTag), environmentHandler: environments.Init(environments.WithAccounts(accounts)), config: config, namespace: getApiNamespace(config), @@ -71,7 +72,7 @@ func NewApplicationHandler(accounts models.Accounts, config ApplicationHandlerCo } } -func getApiNamespace(config ApplicationHandlerConfig) string { +func getApiNamespace(config config.Config) string { if namespace := operatorUtils.GetEnvironmentNamespace(config.AppName, config.EnvironmentName); len(namespace) > 0 { return namespace } diff --git a/api/applications/applications_handler_config.go b/api/applications/applications_handler_config.go deleted file mode 100644 index 87c749a2..00000000 --- a/api/applications/applications_handler_config.go +++ /dev/null @@ -1,46 +0,0 @@ -package applications - -import ( - "github.com/equinor/radix-api/internal/flags" - "github.com/mitchellh/mapstructure" - "github.com/spf13/pflag" - "github.com/spf13/viper" -) - -func LoadApplicationHandlerConfig(args []string) (ApplicationHandlerConfig, error) { - var cfg ApplicationHandlerConfig - - flagset := ApplicationHandlerConfigFlagSet() - if err := flagset.Parse(args); err != nil { - return cfg, err - } - - v := viper.New() - v.AutomaticEnv() - - if err := flags.Register(v, "", flagset, &cfg); err != nil { - return cfg, err - } - - err := v.UnmarshalExact(&cfg, func(dc *mapstructure.DecoderConfig) { dc.TagName = "cfg" }) - return cfg, err -} - -type ApplicationHandlerConfig struct { - RequireAppConfigurationItem bool `cfg:"require_app_configuration_item" flag:"require-app-configuration-item"` - RequireAppADGroups bool `cfg:"require_app_ad_groups" flag:"require-app-ad-groups"` - AppName string `cfg:"radix_app" flag:"radix-app"` - EnvironmentName string `cfg:"radix_environment" flag:"radix-environment"` - DNSZone string `cfg:"radix_dns_zone" flag:"radix-dns-zone"` -} - -func ApplicationHandlerConfigFlagSet() *pflag.FlagSet { - flagset := pflag.NewFlagSet("config", pflag.ExitOnError) - flagset.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{UnknownFlags: true} - flagset.Bool("require-app-configuration-item", true, "Require configuration item for application") - flagset.Bool("require-app-ad-groups", true, "Require AD groups for application") - flagset.String("radix-app", "", "Application name") - flagset.String("radix-environment", "", "Environment name") - flagset.String("radix-dns-zone", "", "Radix DNS zone") - return flagset -} diff --git a/api/applications/applications_handler_factory.go b/api/applications/applications_handler_factory.go index 711e61f9..74f398b0 100644 --- a/api/applications/applications_handler_factory.go +++ b/api/applications/applications_handler_factory.go @@ -4,6 +4,7 @@ import ( "context" "github.com/equinor/radix-api/api/utils/access" + "github.com/equinor/radix-api/internal/config" "github.com/equinor/radix-api/models" authorizationapi "k8s.io/api/authorization/v1" "k8s.io/client-go/kubernetes" @@ -15,11 +16,11 @@ type ApplicationHandlerFactory interface { } type applicationHandlerFactory struct { - config ApplicationHandlerConfig + config config.Config } // NewApplicationHandlerFactory creates a new ApplicationHandlerFactory -func NewApplicationHandlerFactory(config ApplicationHandlerConfig) ApplicationHandlerFactory { +func NewApplicationHandlerFactory(config config.Config) ApplicationHandlerFactory { return &applicationHandlerFactory{ config: config, } diff --git a/api/jobs/job_controller.go b/api/jobs/job_controller.go index 48c1cabd..d52d1f50 100644 --- a/api/jobs/job_controller.go +++ b/api/jobs/job_controller.go @@ -119,7 +119,7 @@ func (jc *jobController) GetApplicationJobs(accounts models.Accounts, w http.Res // description: "Not found" appName := mux.Vars(r)["appName"] - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") jobSummaries, err := handler.GetApplicationJobs(r.Context(), appName) if err != nil { @@ -168,7 +168,7 @@ func (jc *jobController) GetApplicationJob(accounts models.Accounts, w http.Resp appName := mux.Vars(r)["appName"] jobName := mux.Vars(r)["jobName"] - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") jobDetail, err := handler.GetApplicationJob(r.Context(), appName, jobName) if err != nil { @@ -215,7 +215,7 @@ func (jc *jobController) StopApplicationJob(accounts models.Accounts, w http.Res appName := mux.Vars(r)["appName"] jobName := mux.Vars(r)["jobName"] - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") err := handler.StopJob(r.Context(), appName, jobName) if err != nil { @@ -261,7 +261,7 @@ func (jc *jobController) RerunApplicationJob(accounts models.Accounts, w http.Re // description: "Not found" appName := mux.Vars(r)["appName"] jobName := mux.Vars(r)["jobName"] - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") err := handler.RerunJob(r.Context(), appName, jobName) if err != nil { @@ -312,7 +312,7 @@ func (jc *jobController) GetTektonPipelineRuns(accounts models.Accounts, w http. appName := mux.Vars(r)["appName"] jobName := mux.Vars(r)["jobName"] - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") tektonPipelineRuns, err := handler.GetTektonPipelineRuns(r.Context(), appName, jobName) if err != nil { @@ -367,7 +367,7 @@ func (jc *jobController) GetTektonPipelineRun(accounts models.Accounts, w http.R jobName := mux.Vars(r)["jobName"] pipelineRunName := mux.Vars(r)["pipelineRunName"] - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") tektonPipelineRun, err := handler.GetTektonPipelineRun(r.Context(), appName, jobName, pipelineRunName) if err != nil { @@ -424,7 +424,7 @@ func (jc *jobController) GetTektonPipelineRunTasks(accounts models.Accounts, w h jobName := mux.Vars(r)["jobName"] pipelineRunName := mux.Vars(r)["pipelineRunName"] - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") tektonTasks, err := handler.GetTektonPipelineRunTasks(r.Context(), appName, jobName, pipelineRunName) if err != nil { @@ -485,7 +485,7 @@ func (jc *jobController) GetTektonPipelineRunTask(accounts models.Accounts, w ht pipelineRunName := mux.Vars(r)["pipelineRunName"] taskName := mux.Vars(r)["taskName"] - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") tektonTasks, err := handler.GetTektonPipelineRunTask(r.Context(), appName, jobName, pipelineRunName, taskName) if err != nil { @@ -548,7 +548,7 @@ func (jc *jobController) GetTektonPipelineRunTaskSteps(accounts models.Accounts, pipelineRunName := mux.Vars(r)["pipelineRunName"] taskName := mux.Vars(r)["taskName"] - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") tektonTaskSteps, err := handler.GetTektonPipelineRunTaskSteps(r.Context(), appName, jobName, pipelineRunName, taskName) if err != nil { @@ -638,13 +638,13 @@ func (jc *jobController) GetTektonPipelineRunTaskStepLogs(accounts models.Accoun return } - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") log, err := handler.GetTektonPipelineRunTaskStepLogs(r.Context(), appName, jobName, pipelineRunName, taskName, stepName, &since, logLines) if err != nil { jc.ErrorResponse(w, r, err) return } - defer func() {_ = log.Close()}() + defer func() { _ = log.Close() }() if asFile { fileName := fmt.Sprintf("%s.log", time.Now().Format("20060102150405")) @@ -721,13 +721,13 @@ func (jc *jobController) GetPipelineJobStepLogs(accounts models.Accounts, w http return } - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") log, err := handler.GetPipelineJobStepLogs(r.Context(), appName, jobName, stepName, &since, logLines) if err != nil { jc.ErrorResponse(w, r, err) return } - defer func() {_ = log.Close()}() + defer func() { _ = log.Close() }() if asFile { fileName := fmt.Sprintf("%s.log", time.Now().Format("20060102150405")) diff --git a/api/jobs/job_controller_test.go b/api/jobs/job_controller_test.go index 401d2d7e..6bff4fe4 100644 --- a/api/jobs/job_controller_test.go +++ b/api/jobs/job_controller_test.go @@ -79,7 +79,7 @@ func TestGetApplicationJob(t *testing.T) { } accounts := models.NewAccounts(client, radixclient, kedaClient, secretproviderclient, nil, certClient, client, radixclient, kedaClient, secretproviderclient, nil, certClient, "", radixmodels.Impersonation{}) - handler := Init(accounts, deployments.Init(accounts)) + handler := Init(accounts, deployments.Init(accounts), "", "") anyPipeline, _ := pipeline.GetPipelineFromName(anyPipelineName) jobSummary, _ := handler.HandleStartPipelineJob(context.Background(), anyAppName, anyPipeline, jobParameters) diff --git a/api/jobs/job_handler.go b/api/jobs/job_handler.go index d1d87614..e1e9e289 100644 --- a/api/jobs/job_handler.go +++ b/api/jobs/job_handler.go @@ -30,19 +30,23 @@ const ( // JobHandler Instance variables type JobHandler struct { - accounts models.Accounts - userAccount models.Account - serviceAccount models.Account - deploy deployments.DeployHandler + accounts models.Accounts + userAccount models.Account + serviceAccount models.Account + deploy deployments.DeployHandler + pipelineImageTag string + tektonImageTag string } // Init Constructor -func Init(accounts models.Accounts, deployHandler deployments.DeployHandler) JobHandler { +func Init(accounts models.Accounts, deployHandler deployments.DeployHandler, pipelineImageTag, tektonImageTag string) JobHandler { return JobHandler{ - accounts: accounts, - userAccount: accounts.UserAccount, - serviceAccount: accounts.ServiceAccount, - deploy: deployHandler, + accounts: accounts, + userAccount: accounts.UserAccount, + serviceAccount: accounts.ServiceAccount, + deploy: deployHandler, + pipelineImageTag: pipelineImageTag, + tektonImageTag: tektonImageTag, } } diff --git a/api/jobs/job_handler_test.go b/api/jobs/job_handler_test.go index 25045e95..8d71a84a 100644 --- a/api/jobs/job_handler_test.go +++ b/api/jobs/job_handler_test.go @@ -140,7 +140,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob() { ctrl := gomock.NewController(s.T()) defer ctrl.Finish() dh := deployMock.NewMockDeployHandler(ctrl) - h := Init(s.accounts, dh) + h := Init(s.accounts, dh, "", "") actualJob, err := h.GetApplicationJob(context.Background(), appName, "missing_job") s.True(k8serrors.IsNotFound(err)) s.Nil(actualJob) @@ -152,7 +152,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob() { dh := deployMock.NewMockDeployHandler(ctrl) dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), appName, jobName).Return(nil, assert.AnError).Times(1) - h := Init(s.accounts, dh) + h := Init(s.accounts, dh, "", "") actualJob, actualErr := h.GetApplicationJob(context.Background(), appName, jobName) s.Equal(assert.AnError, actualErr) @@ -166,7 +166,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob() { deployList := []*deploymentModels.DeploymentSummary{&deploySummary} dh := deployMock.NewMockDeployHandler(ctrl) dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), appName, jobName).Return(deployList, nil).Times(1) - h := Init(s.accounts, dh) + h := Init(s.accounts, dh, "", "") actualJob, actualErr := h.GetApplicationJob(context.Background(), appName, jobName) s.NoError(actualErr) @@ -210,7 +210,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob_Created() { dh := deployMock.NewMockDeployHandler(ctrl) dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - h := Init(s.accounts, dh) + h := Init(s.accounts, dh, "", "") rj := radixv1.RadixJob{ObjectMeta: metav1.ObjectMeta{Name: scenario.jobName, Namespace: utils.GetAppNamespace(appName), CreationTimestamp: scenario.creationTimestamp}} if scenario.jobStatusCreated != emptyTime { rj.Status.Created = &scenario.jobStatusCreated @@ -241,7 +241,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob_Status() { dh := deployMock.NewMockDeployHandler(ctrl) dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - h := Init(s.accounts, dh) + h := Init(s.accounts, dh, "", "") rj := radixv1.RadixJob{ ObjectMeta: metav1.ObjectMeta{Name: scenario.jobName, Namespace: utils.GetAppNamespace(appName)}, Spec: radixv1.RadixJobSpec{Stop: scenario.stop}, diff --git a/api/jobs/start_job_handler.go b/api/jobs/start_job_handler.go index a6ffa857..85c28475 100644 --- a/api/jobs/start_job_handler.go +++ b/api/jobs/start_job_handler.go @@ -3,7 +3,6 @@ package jobs import ( "context" "fmt" - "os" "strings" "time" @@ -117,8 +116,8 @@ func (jh JobHandler) buildPipelineJob(ctx context.Context, appName, cloneURL, ra AppName: appName, CloneURL: cloneURL, PipeLineType: pipeline.Type, - PipelineImage: getPipelineTag(ctx), - TektonImage: getTektonTag(ctx), + PipelineImage: getPipelineTag(ctx, jh.pipelineImageTag), + TektonImage: getTektonTag(ctx, jh.tektonImageTag), Build: buildSpec, Promote: promoteSpec, Deploy: deploySpec, @@ -141,26 +140,24 @@ func (jh JobHandler) getTriggeredBy(triggeredBy string) (string, error) { return triggeredBy, nil } -func getPipelineTag(ctx context.Context) string { - pipelineTag := os.Getenv(pipelineTagEnvironmentVariable) - if pipelineTag == "" { +func getPipelineTag(ctx context.Context, pipelineImageTag string) string { + if pipelineImageTag == "" { log.Ctx(ctx).Warn().Msg("No pipeline image tag defined. Using latest") - pipelineTag = "latest" + pipelineImageTag = "latest" } else { - log.Ctx(ctx).Info().Msgf("Using %s pipeline image tag", pipelineTag) + log.Ctx(ctx).Info().Msgf("Using %s pipeline image tag", pipelineImageTag) } - return pipelineTag + return pipelineImageTag } -func getTektonTag(ctx context.Context) string { - tektonTag := os.Getenv(tektonTagEnvironmentVariable) - if tektonTag == "" { +func getTektonTag(ctx context.Context, tektonImageTag string) string { + if tektonImageTag == "" { log.Ctx(ctx).Warn().Msg("No tekton image tag defined. Using release-latest") - tektonTag = "release-latest" + tektonImageTag = "release-latest" } else { - log.Ctx(ctx).Info().Msgf("Using %s as tekton image tag", tektonTag) + log.Ctx(ctx).Info().Msgf("Using %s as tekton image tag", tektonImageTag) } - return tektonTag + return tektonImageTag } func getUniqueJobName(image string) (string, string) { diff --git a/api/middleware/cors/cors.go b/api/middleware/cors/cors.go index c125e37d..04f65fe9 100644 --- a/api/middleware/cors/cors.go +++ b/api/middleware/cors/cors.go @@ -2,15 +2,13 @@ package cors import ( "fmt" - "os" - "github.com/equinor/radix-api/api/defaults" "github.com/rs/cors" + "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) -func CreateMiddleware(clusterName string, debugMode bool) *cors.Cors { - radixDNSZone := os.Getenv(defaults.RadixDNSZoneEnvironmentVariable) +func CreateMiddleware(clusterName, radixDNSZone string) *cors.Cors { corsOptions := cors.Options{ AllowedOrigins: []string{ @@ -37,7 +35,7 @@ func CreateMiddleware(clusterName string, debugMode bool) *cors.Cors { AllowedMethods: []string{"GET", "PUT", "POST", "OPTIONS", "DELETE", "PATCH"}, } - if debugMode { + if zerolog.GlobalLevel() <= zerolog.DebugLevel { // debugging mode corsOptions.Debug = true corsLogger := log.Logger.With().Str("pkg", "cors-middleware").Logger() diff --git a/api/router/api.go b/api/router/api.go index 65155c70..feb1ff34 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -19,9 +19,7 @@ const ( ) // NewAPIHandler Constructor function -func NewAPIHandler(clusterName, oidcIssuer, oidcAudience string, kubeUtil utils.KubeUtil, controllers ...models.Controller) http.Handler { - useOutClusterClient := kubeUtil.IsUseOutClusterClient() - +func NewAPIHandler(clusterName, oidcIssuer, oidcAudience, radixDNSZone string, kubeUtil utils.KubeUtil, controllers ...models.Controller) http.Handler { serveMux := http.NewServeMux() serveMux.Handle("/health/", createHealthHandler()) serveMux.Handle("/swaggerui/", createSwaggerHandler()) @@ -29,7 +27,7 @@ func NewAPIHandler(clusterName, oidcIssuer, oidcAudience string, kubeUtil utils. n := negroni.New( recovery.CreateMiddleware(), - cors.CreateMiddleware(clusterName, !useOutClusterClient), + cors.CreateMiddleware(clusterName, radixDNSZone), logger.CreateZerologRequestIdMiddleware(), logger.CreateZerologRequestDetailsMiddleware(), auth.CreateAuthenticationMiddleware(oidcIssuer, oidcAudience), diff --git a/api/router/metrics.go b/api/router/metrics.go index def1f216..3f2ccf15 100644 --- a/api/router/metrics.go +++ b/api/router/metrics.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/equinor/radix-api/api/middleware/logger" + "github.com/equinor/radix-api/api/middleware/recovery" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/urfave/negroni/v3" ) diff --git a/api/test/utils.go b/api/test/utils.go index beb80900..40a027f1 100644 --- a/api/test/utils.go +++ b/api/test/utils.go @@ -63,7 +63,7 @@ func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string) <-chan *htt go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler("anyClusterName", "", "", "", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) response <- rr }() @@ -87,7 +87,7 @@ func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameter go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler("anyClusterName", "", "", "", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) response <- rr }() diff --git a/api/utils/kubernetes.go b/api/utils/kubernetes.go index c4083cae..d586a107 100644 --- a/api/utils/kubernetes.go +++ b/api/utils/kubernetes.go @@ -45,6 +45,7 @@ type KubeUtil interface { } type kubeUtil struct { + kubeApiServer string useOutClusterClient bool } @@ -61,10 +62,8 @@ var ( ) // NewKubeUtil Constructor -func NewKubeUtil(useOutClusterClient bool) KubeUtil { - return &kubeUtil{ - useOutClusterClient, - } +func NewKubeUtil(useOutClusterClient bool, kubeApiServer string) KubeUtil { + return &kubeUtil{kubeApiServer, useOutClusterClient} } // GetOutClusterKubernetesClient Gets a kubernetes client using the bearer token from the radix api client @@ -75,7 +74,7 @@ func (ku *kubeUtil) GetOutClusterKubernetesClient(token string, options ...RestC // GetOutClusterKubernetesClientWithImpersonation Gets a kubernetes client using the bearer token from the radix api client func (ku *kubeUtil) GetOutClusterKubernetesClientWithImpersonation(token string, impersonation radixmodels.Impersonation, options ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) { if ku.useOutClusterClient { - config := getOutClusterClientConfig(token, impersonation, options) + config := getOutClusterClientConfig(token, impersonation, ku.kubeApiServer, options) return getKubernetesClientFromConfig(config) } @@ -88,14 +87,9 @@ func (ku *kubeUtil) GetInClusterKubernetesClient(options ...RestClientConfigOpti return getKubernetesClientFromConfig(config) } -func getOutClusterClientConfig(token string, impersonation radixmodels.Impersonation, options []RestClientConfigOption) *restclient.Config { - host := os.Getenv("K8S_API_HOST") - if host == "" { - host = "https://kubernetes.default.svc" - } - +func getOutClusterClientConfig(token string, impersonation radixmodels.Impersonation, kubeApiServer string, options []RestClientConfigOption) *restclient.Config { kubeConfig := &restclient.Config{ - Host: host, + Host: kubeApiServer, BearerToken: token, TLSClientConfig: restclient.TLSClientConfig{ Insecure: true, diff --git a/go.mod b/go.mod index cd396fb1..276e6854 100644 --- a/go.mod +++ b/go.mod @@ -12,12 +12,12 @@ require ( github.com/equinor/radix-operator v1.61.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/mitchellh/mapstructure v1.5.0 github.com/prometheus-operator/prometheus-operator/pkg/client v0.76.0 @@ -31,7 +31,6 @@ require ( github.com/tektoncd/pipeline v0.55.0 github.com/urfave/negroni/v3 v3.1.0 golang.org/x/sync v0.8.0 - gopkg.in/go-jose/go-jose.v2 v2.6.3 k8s.io/api v0.31.0 k8s.io/apimachinery v0.31.0 k8s.io/client-go v0.31.0 @@ -119,6 +118,7 @@ require ( google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index c6d0a863..aca53023 100644 --- a/go.sum +++ b/go.sum @@ -109,8 +109,6 @@ 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-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= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 00000000..d59cb46b --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,38 @@ +package config + +import ( + "github.com/kelseyhightower/envconfig" + "github.com/rs/zerolog/log" +) + +type Config struct { + Port int `envconfig:"PORT" default:"3002" desc:"Port where API will be served"` + MetricsPort int `envconfig:"METRICS_PORT" default:"9090" desc:"Port where Metrics will be served"` + ProfilePort int `envconfig:"PROFILE_PORT" default:"7070" desc:"Port where Profiler will be served"` + UseProfiler bool `envconfig:"USE_PROFILER" default:"false" desc:"Enable Profiler"` + UseOutClusterClient bool `envconfig:"USE_OUT_CLUSTER_CLIENT" default:"true" desc:"In case of testing on local machine you may want to set this to 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"` + KubernetesApiServer string `envconfig:"K8S_API_HOST" default:"https://kubernetes.default.svc"` + LogLevel string `envconfig:"LOG_LEVEL" default:"info"` + LogPrettyPrint bool `envconfig:"LOG_PRETTY" default:"false"` + PipelineImageTag string `envconfig:"PIPELINE_IMG_TAG"` + TektonImageTag string `envconfig:"TEKTON_IMG_TAG"` + RequireAppConfigurationItem bool `envconfig:"REQUIRE_APP_CONFIGURATION_ITEM" default:"true"` + RequireAppADGroups bool `envconfig:"REQUIRE_APP_AD_GROUPS" default:"true"` + AppName string `envconfig:"RADIX_APP" required:"true"` + EnvironmentName string `envconfig:"RADIX_ENVIRONMENT" required:"true"` +} + +func MustParse() Config { + var s Config + err := envconfig.Process("", &s) + if err != nil { + _ = envconfig.Usage("", &s) + log.Fatal().Msg(err.Error()) + } + + return s +} diff --git a/main.go b/main.go index 3253f52a..c16f841e 100644 --- a/main.go +++ b/main.go @@ -9,14 +9,13 @@ import ( _ "net/http/pprof" "os" "os/signal" - "strconv" "sync" "syscall" "time" "github.com/equinor/radix-api/api/secrets" "github.com/equinor/radix-api/api/utils/tlsvalidation" - "github.com/equinor/radix-operator/pkg/apis/defaults" + "github.com/equinor/radix-api/internal/config" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -24,8 +23,6 @@ import ( "github.com/equinor/radix-api/api/buildstatus" - "github.com/spf13/pflag" - // Controllers "github.com/equinor/radix-api/api/alerting" "github.com/equinor/radix-api/api/applications" @@ -41,66 +38,62 @@ import ( "github.com/equinor/radix-api/models" ) -const ( - logLevelEnvironmentVariable = "LOG_LEVEL" - logPrettyEnvironmentVariable = "LOG_PRETTY" - useProfilerEnvironmentVariable = "USE_PROFILER" - defaultPort = "3002" - defaultMetricsPort = "9090" - defaultProfilePort = "7070" -) +// K8S_API_HOST=https://weekly-39-clusters-dev-16ede4-ttgs23zb.hcp.northeurope.azmk8s.io:443; +// LOG_LEVEL=trace; +// LOG_PRETTY=true; +// PIPELINE_IMG_TAG=master-latest; +// PROMETHEUS_URL=http://localhost:9092; +// RADIX_ACTIVE_CLUSTER_EGRESS_IPS=104.45.84.1; +// RADIX_APP=radix-api; +// RADIX_CLUSTER_TYPE=development; +// RADIX_CLUSTERNAME=weekly-38; +// RADIX_CONTAINER_REGISTRY=radixdev.azurecr.io; +// RADIX_DNS_ZONE=dev.radix.equinor.com; +// RADIX_ENVIRONMENT=qa; +// REQUIRE_APP_AD_GROUPS=true; +// REQUIRE_APP_CONFIGURATION_ITEM=true; +// TEKTON_IMG_TAG=main-latest + +// K8S_API_HOST=https://weekly-39-clusters-dev-16ede4-ttgs23zb.hcp.northeurope.azmk8s.io:443;LOG_LEVEL=trace;LOG_PRETTY=true;PIPELINE_IMG_TAG=master-latest;PROMETHEUS_URL=http://localhost:9092;RADIX_ACTIVE_CLUSTER_EGRESS_IPS=104.45.84.1;RADIX_APP=radix-api;RADIX_CLUSTER_TYPE=development;RADIX_CLUSTERNAME=weekly-38;RADIX_CONTAINER_REGISTRY=radixdev.azurecr.io;RADIX_DNS_ZONE=dev.radix.equinor.com;RADIX_ENVIRONMENT=qa;REQUIRE_APP_AD_GROUPS=true;REQUIRE_APP_CONFIGURATION_ITEM=true;TEKTON_IMG_TAG=main-latest //go:generate swagger generate spec func main() { - setupLogger() - fs := initializeFlagSet() - - var ( - port = fs.StringP("port", "p", defaultPort, "Port where API will be served") - metricsPort = fs.String("metrics-port", defaultMetricsPort, "The metrics API server port") - useOutClusterClient = fs.Bool("useOutClusterClient", true, "In case of testing on local machine you may want to set this to false") - clusterName = os.Getenv(defaults.ClusternameEnvironmentVariable) - ) - - parseFlagsFromArgs(fs) - - var servers []*http.Server + c := config.MustParse() + setupLogger(c.LogLevel, c.LogPrettyPrint) - srv, err := initializeServer(*port, clusterName, "https://sts.windows.net/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/", "6dae42f8-4368-4678-94ff-3960e28e3630", *useOutClusterClient) - if err != nil { - log.Fatal().Err(err).Msg("Failed to initialize API server") + servers := []*http.Server{ + initializeServer(c), + initializeMetricsServer(c), } - servers = append(servers, srv, initializeMetricsServer(*metricsPort)) - - if useProfiler, _ := strconv.ParseBool(os.Getenv(useProfilerEnvironmentVariable)); useProfiler { - log.Info().Msgf("Initializing profile server on port %s", defaultProfilePort) - servers = append(servers, &http.Server{Addr: fmt.Sprintf("localhost:%s", defaultProfilePort)}) + if c.UseProfiler { + log.Info().Msgf("Initializing profile server on port %d", c.ProfilePort) + servers = append(servers, &http.Server{Addr: fmt.Sprintf("localhost:%d", c.ProfilePort)}) } startServers(servers...) shutdownServersGracefulOnSignal(servers...) } -func initializeServer(port, clusterName, oidcIssuer, oidcAudience string, useOutClusterClient bool) (*http.Server, error) { - controllers, err := getControllers() +func initializeServer(c config.Config) *http.Server { + controllers, err := getControllers(c) if err != nil { - return nil, fmt.Errorf("failed to initialize controllers: %w", err) + log.Fatal().Err(err).Msgf("failed to initialize controllers: %v", err) } - handler := router.NewAPIHandler(clusterName, oidcIssuer, oidcAudience, utils.NewKubeUtil(useOutClusterClient), controllers...) + handler := router.NewAPIHandler(c.ClusterName, c.OidcIssuer, c.OidcAudience, c.DNSZone, utils.NewKubeUtil(c.UseOutClusterClient, c.KubernetesApiServer), controllers...) srv := &http.Server{ - Addr: fmt.Sprintf(":%s", port), + Addr: fmt.Sprintf(":%d", c.Port), Handler: handler, } - return srv, nil + return srv } -func initializeMetricsServer(port string) *http.Server { - log.Info().Msgf("Initializing metrics server on port %s", port) +func initializeMetricsServer(c config.Config) *http.Server { + log.Info().Msgf("Initializing metrics server on port %d", c.Port) return &http.Server{ - Addr: fmt.Sprintf(":%s", port), + Addr: fmt.Sprintf(":%d", c.Port), Handler: router.NewMetricsHandler(), } } @@ -142,8 +135,7 @@ func shutdownServersGracefulOnSignal(servers ...*http.Server) { wg.Wait() } -func setupLogger() { - logLevelStr := os.Getenv(logLevelEnvironmentVariable) +func setupLogger(logLevelStr string, prettyPrint bool) { if len(logLevelStr) == 0 { logLevelStr = zerolog.LevelInfoValue } @@ -154,10 +146,8 @@ func setupLogger() { log.Warn().Msgf("Invalid log level '%s', fallback to '%s'", logLevelStr, logLevel.String()) } - logPretty, _ := strconv.ParseBool(os.Getenv(logPrettyEnvironmentVariable)) - var logWriter io.Writer = os.Stderr - if logPretty { + if prettyPrint { logWriter = &zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.TimeOnly} } @@ -167,15 +157,12 @@ func setupLogger() { zerolog.DefaultContextLogger = &logger } -func getControllers() ([]models.Controller, error) { +func getControllers(config config.Config) ([]models.Controller, error) { buildStatus := build_models.NewPipelineBadge() - applicationHandlerFactory, err := getApplicationHandlerFactory() - if err != nil { - return nil, err - } + applicatinoFactory := applications.NewApplicationHandlerFactory(config) return []models.Controller{ - applications.NewApplicationController(nil, applicationHandlerFactory), + applications.NewApplicationController(nil, applicatinoFactory), deployments.NewDeploymentController(), jobs.NewJobController(), environments.NewEnvironmentController(environments.NewEnvironmentHandlerFactory()), @@ -187,36 +174,3 @@ func getControllers() ([]models.Controller, error) { secrets.NewSecretController(tlsvalidation.DefaultValidator()), }, nil } - -func getApplicationHandlerFactory() (applications.ApplicationHandlerFactory, error) { - cfg, err := applications.LoadApplicationHandlerConfig(os.Args[1:]) - if err != nil { - return nil, err - } - return applications.NewApplicationHandlerFactory(cfg), nil -} - -func initializeFlagSet() *pflag.FlagSet { - // Flag domain. - fs := pflag.NewFlagSet("default", pflag.ContinueOnError) - fs.Usage = func() { - fmt.Fprintf(os.Stderr, "DESCRIPTION\n") - fmt.Fprintf(os.Stderr, " radix api-server.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, "FLAGS\n") - fs.PrintDefaults() - } - return fs -} - -func parseFlagsFromArgs(fs *pflag.FlagSet) { - err := fs.Parse(os.Args[1:]) - switch { - case err == pflag.ErrHelp: - os.Exit(0) - case err != nil: - fmt.Fprintf(os.Stderr, "Error: %s\n\n", err.Error()) - fs.Usage() - os.Exit(2) - } -} From ebe79beb4b70111ce3a34e5a53715adc96c76735 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Fri, 27 Sep 2024 15:09:16 +0200 Subject: [PATCH 03/29] Add mock, refactor validator to make testing possible --- Makefile | 1 + api/middleware/auth/authentication.go | 16 +--- api/router/api.go | 5 +- api/utils/authn/azure_principal.go | 8 +- api/utils/authn/mock/validator_mock.go | 116 +++++++++++++++++++++++++ api/utils/authn/validator.go | 6 +- go.mod | 2 +- main.go | 19 +++- 8 files changed, 149 insertions(+), 24 deletions(-) create mode 100644 api/utils/authn/mock/validator_mock.go diff --git a/Makefile b/Makefile index 573f4abe..6e74f912 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ mocks: bootstrap mockgen -source ./api/events/event_handler.go -destination ./api/events/mock/event_handler_mock.go -package mock mockgen -source ./api/environmentvariables/env_vars_handler.go -destination ./api/environmentvariables/env_vars_handler_mock.go -package environmentvariables mockgen -source ./api/environmentvariables/env_vars_handler_factory.go -destination ./api/environmentvariables/env_vars_handler_factory_mock.go -package environmentvariables + mockgen -source ./api/utils/authn/validator.go -destination ./api/utils/authn/mock/validator_mock.go -package mock .PHONY: test test: diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index 8dfba5f5..3af7b314 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -3,7 +3,6 @@ package auth import ( "context" "net/http" - "net/url" token "github.com/equinor/radix-api/api/utils/authn" "github.com/equinor/radix-common/models" @@ -15,18 +14,7 @@ import ( type ctxUserKey struct{} type ctxImpersonationKey struct{} -func CreateAuthenticationMiddleware(issuer, audience string) negroni.HandlerFunc { - issuerUrl, err := url.Parse(issuer) - if err != nil { - log.Fatal().Err(err).Msg("Error parsing issuer url") - } - - // Set up the validator. - jwtValidator, err := token.NewValidator(issuerUrl, audience) - if err != nil { - log.Fatal().Err(err).Msg("Error creating JWT validator") - } - +func CreateAuthenticationMiddleware(validator token.ValidatorInterface) negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { ctx := r.Context() logger := log.Ctx(ctx) @@ -43,7 +31,7 @@ func CreateAuthenticationMiddleware(issuer, audience string) negroni.HandlerFunc } return } - principal, err := jwtValidator.ValidateToken(ctx, token) + principal, err := validator.ValidateToken(ctx, token) if err != nil { logger.Warn().Err(err).Msg("authentication error") if err = radixhttp.ErrorResponse(w, r, err); err != nil { diff --git a/api/router/api.go b/api/router/api.go index feb1ff34..c7126b90 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -8,6 +8,7 @@ import ( "github.com/equinor/radix-api/api/middleware/logger" "github.com/equinor/radix-api/api/middleware/recovery" "github.com/equinor/radix-api/api/utils" + token "github.com/equinor/radix-api/api/utils/authn" "github.com/equinor/radix-api/models" "github.com/equinor/radix-api/swaggerui" "github.com/gorilla/mux" @@ -19,7 +20,7 @@ const ( ) // NewAPIHandler Constructor function -func NewAPIHandler(clusterName, oidcIssuer, oidcAudience, radixDNSZone string, kubeUtil utils.KubeUtil, controllers ...models.Controller) http.Handler { +func NewAPIHandler(clusterName string, validator token.ValidatorInterface, radixDNSZone string, kubeUtil utils.KubeUtil, controllers ...models.Controller) http.Handler { serveMux := http.NewServeMux() serveMux.Handle("/health/", createHealthHandler()) serveMux.Handle("/swaggerui/", createSwaggerHandler()) @@ -30,7 +31,7 @@ func NewAPIHandler(clusterName, oidcIssuer, oidcAudience, radixDNSZone string, k cors.CreateMiddleware(clusterName, radixDNSZone), logger.CreateZerologRequestIdMiddleware(), logger.CreateZerologRequestDetailsMiddleware(), - auth.CreateAuthenticationMiddleware(oidcIssuer, oidcAudience), + auth.CreateAuthenticationMiddleware(validator), logger.CreateZerologRequestLoggerMiddleware(), ) n.UseHandler(serveMux) diff --git a/api/utils/authn/azure_principal.go b/api/utils/authn/azure_principal.go index 74d3a121..b469c53a 100644 --- a/api/utils/authn/azure_principal.go +++ b/api/utils/authn/azure_principal.go @@ -24,19 +24,19 @@ func (c *azureClaims) Validate(_ context.Context) error { return nil } -type azurePrincipal struct { +type AzurePrincipal struct { token string claims *validator.ValidatedClaims azureClaims *azureClaims } -func (p *azurePrincipal) Token() string { +func (p *AzurePrincipal) Token() string { return p.token } -func (p *azurePrincipal) IsAuthenticated() bool { +func (p *AzurePrincipal) IsAuthenticated() bool { return true } -func (p *azurePrincipal) Subject() string { +func (p *AzurePrincipal) Subject() string { if p.azureClaims.Upn != "" { return p.azureClaims.Upn } diff --git a/api/utils/authn/mock/validator_mock.go b/api/utils/authn/mock/validator_mock.go new file mode 100644 index 00000000..4c99f918 --- /dev/null +++ b/api/utils/authn/mock/validator_mock.go @@ -0,0 +1,116 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./api/utils/authn/validator.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + token "github.com/equinor/radix-api/api/utils/authn" + gomock "github.com/golang/mock/gomock" +) + +// MockTokenPrincipal is a mock of TokenPrincipal interface. +type MockTokenPrincipal struct { + ctrl *gomock.Controller + recorder *MockTokenPrincipalMockRecorder +} + +// MockTokenPrincipalMockRecorder is the mock recorder for MockTokenPrincipal. +type MockTokenPrincipalMockRecorder struct { + mock *MockTokenPrincipal +} + +// NewMockTokenPrincipal creates a new mock instance. +func NewMockTokenPrincipal(ctrl *gomock.Controller) *MockTokenPrincipal { + mock := &MockTokenPrincipal{ctrl: ctrl} + mock.recorder = &MockTokenPrincipalMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTokenPrincipal) EXPECT() *MockTokenPrincipalMockRecorder { + return m.recorder +} + +// IsAuthenticated mocks base method. +func (m *MockTokenPrincipal) IsAuthenticated() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAuthenticated") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsAuthenticated indicates an expected call of IsAuthenticated. +func (mr *MockTokenPrincipalMockRecorder) IsAuthenticated() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAuthenticated", reflect.TypeOf((*MockTokenPrincipal)(nil).IsAuthenticated)) +} + +// Subject mocks base method. +func (m *MockTokenPrincipal) Subject() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Subject") + ret0, _ := ret[0].(string) + return ret0 +} + +// Subject indicates an expected call of Subject. +func (mr *MockTokenPrincipalMockRecorder) Subject() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subject", reflect.TypeOf((*MockTokenPrincipal)(nil).Subject)) +} + +// Token mocks base method. +func (m *MockTokenPrincipal) Token() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Token") + ret0, _ := ret[0].(string) + return ret0 +} + +// Token indicates an expected call of Token. +func (mr *MockTokenPrincipalMockRecorder) Token() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Token", reflect.TypeOf((*MockTokenPrincipal)(nil).Token)) +} + +// MockValidatorInterface is a mock of ValidatorInterface interface. +type MockValidatorInterface struct { + ctrl *gomock.Controller + recorder *MockValidatorInterfaceMockRecorder +} + +// MockValidatorInterfaceMockRecorder is the mock recorder for MockValidatorInterface. +type MockValidatorInterfaceMockRecorder struct { + mock *MockValidatorInterface +} + +// NewMockValidatorInterface creates a new mock instance. +func NewMockValidatorInterface(ctrl *gomock.Controller) *MockValidatorInterface { + mock := &MockValidatorInterface{ctrl: ctrl} + mock.recorder = &MockValidatorInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockValidatorInterface) EXPECT() *MockValidatorInterfaceMockRecorder { + return m.recorder +} + +// ValidateToken mocks base method. +func (m *MockValidatorInterface) ValidateToken(arg0 context.Context, arg1 string) (token.TokenPrincipal, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateToken", arg0, arg1) + ret0, _ := ret[0].(token.TokenPrincipal) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ValidateToken indicates an expected call of ValidateToken. +func (mr *MockValidatorInterfaceMockRecorder) ValidateToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateToken", reflect.TypeOf((*MockValidatorInterface)(nil).ValidateToken), arg0, arg1) +} diff --git a/api/utils/authn/validator.go b/api/utils/authn/validator.go index 097b154a..b11e2bae 100644 --- a/api/utils/authn/validator.go +++ b/api/utils/authn/validator.go @@ -17,13 +17,15 @@ type TokenPrincipal interface { } type ValidatorInterface interface { - ValidateToken(ctx context.Context, token string) (*validator.RegisteredClaims, error) + ValidateToken(context.Context, string) (TokenPrincipal, error) } type Validator struct { validator *validator.Validator } +var _ ValidatorInterface = &Validator{} + func NewValidator(issuerUrl *url.URL, audience string) (*Validator, error) { provider := jwks.NewCachingProvider(issuerUrl, 5*time.Minute) @@ -59,6 +61,6 @@ func (v *Validator) ValidateToken(ctx context.Context, token string) (TokenPrinc return nil, errors.New("invalid azure token") } - principal := &azurePrincipal{token: token, claims: claims, azureClaims: azureClaims} + principal := &AzurePrincipal{token: token, claims: claims, azureClaims: azureClaims} return principal, nil } diff --git a/go.mod b/go.mod index 276e6854..ae98b843 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( github.com/kedacore/keda/v2 v2.15.1 github.com/kelseyhightower/envconfig v1.4.0 github.com/marstr/guid v1.1.0 - github.com/mitchellh/mapstructure v1.5.0 github.com/prometheus-operator/prometheus-operator/pkg/client v0.76.0 github.com/prometheus/client_golang v1.20.2 github.com/rs/cors v1.11.0 @@ -79,6 +78,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/main.go b/main.go index c16f841e..8b42790d 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "io" "net/http" _ "net/http/pprof" + "net/url" "os" "os/signal" "sync" @@ -14,6 +15,7 @@ import ( "time" "github.com/equinor/radix-api/api/secrets" + token "github.com/equinor/radix-api/api/utils/authn" "github.com/equinor/radix-api/api/utils/tlsvalidation" "github.com/equinor/radix-api/internal/config" "github.com/rs/zerolog" @@ -76,12 +78,13 @@ func main() { } func initializeServer(c config.Config) *http.Server { + jwtValidator := initializeTokenValidator(c) controllers, err := getControllers(c) if err != nil { log.Fatal().Err(err).Msgf("failed to initialize controllers: %v", err) } - handler := router.NewAPIHandler(c.ClusterName, c.OidcIssuer, c.OidcAudience, c.DNSZone, utils.NewKubeUtil(c.UseOutClusterClient, c.KubernetesApiServer), controllers...) + handler := router.NewAPIHandler(c.ClusterName, jwtValidator, c.DNSZone, utils.NewKubeUtil(c.UseOutClusterClient, c.KubernetesApiServer), controllers...) srv := &http.Server{ Addr: fmt.Sprintf(":%d", c.Port), Handler: handler, @@ -90,6 +93,20 @@ func initializeServer(c config.Config) *http.Server { return srv } +func initializeTokenValidator(c config.Config) *token.Validator { + issuerUrl, err := url.Parse(c.OidcIssuer) + if err != nil { + log.Fatal().Err(err).Msg("Error parsing issuer url") + } + + // Set up the validator. + jwtValidator, err := token.NewValidator(issuerUrl, c.OidcAudience) + if err != nil { + log.Fatal().Err(err).Msg("Error creating JWT validator") + } + return jwtValidator +} + func initializeMetricsServer(c config.Config) *http.Server { log.Info().Msgf("Initializing metrics server on port %d", c.Port) return &http.Server{ From 43d406c8b2b521759a2d786f8be779cc9b761bcd Mon Sep 17 00:00:00 2001 From: Richard87 Date: Fri, 27 Sep 2024 15:19:17 +0200 Subject: [PATCH 04/29] remove env --- main.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/main.go b/main.go index 8b42790d..c9686e47 100644 --- a/main.go +++ b/main.go @@ -40,24 +40,6 @@ import ( "github.com/equinor/radix-api/models" ) -// K8S_API_HOST=https://weekly-39-clusters-dev-16ede4-ttgs23zb.hcp.northeurope.azmk8s.io:443; -// LOG_LEVEL=trace; -// LOG_PRETTY=true; -// PIPELINE_IMG_TAG=master-latest; -// PROMETHEUS_URL=http://localhost:9092; -// RADIX_ACTIVE_CLUSTER_EGRESS_IPS=104.45.84.1; -// RADIX_APP=radix-api; -// RADIX_CLUSTER_TYPE=development; -// RADIX_CLUSTERNAME=weekly-38; -// RADIX_CONTAINER_REGISTRY=radixdev.azurecr.io; -// RADIX_DNS_ZONE=dev.radix.equinor.com; -// RADIX_ENVIRONMENT=qa; -// REQUIRE_APP_AD_GROUPS=true; -// REQUIRE_APP_CONFIGURATION_ITEM=true; -// TEKTON_IMG_TAG=main-latest - -// K8S_API_HOST=https://weekly-39-clusters-dev-16ede4-ttgs23zb.hcp.northeurope.azmk8s.io:443;LOG_LEVEL=trace;LOG_PRETTY=true;PIPELINE_IMG_TAG=master-latest;PROMETHEUS_URL=http://localhost:9092;RADIX_ACTIVE_CLUSTER_EGRESS_IPS=104.45.84.1;RADIX_APP=radix-api;RADIX_CLUSTER_TYPE=development;RADIX_CLUSTERNAME=weekly-38;RADIX_CONTAINER_REGISTRY=radixdev.azurecr.io;RADIX_DNS_ZONE=dev.radix.equinor.com;RADIX_ENVIRONMENT=qa;REQUIRE_APP_AD_GROUPS=true;REQUIRE_APP_CONFIGURATION_ITEM=true;TEKTON_IMG_TAG=main-latest - //go:generate swagger generate spec func main() { c := config.MustParse() From aff59bb328adbeb089ceea02ea5b8298b09cc2bf Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 1 Oct 2024 12:36:13 +0200 Subject: [PATCH 05/29] Initialize Metrics Port --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index c9686e47..75ada058 100644 --- a/main.go +++ b/main.go @@ -90,9 +90,9 @@ func initializeTokenValidator(c config.Config) *token.Validator { } func initializeMetricsServer(c config.Config) *http.Server { - log.Info().Msgf("Initializing metrics server on port %d", c.Port) + log.Info().Msgf("Initializing metrics server on port %d", c.MetricsPort) return &http.Server{ - Addr: fmt.Sprintf(":%d", c.Port), + Addr: fmt.Sprintf(":%d", c.MetricsPort), Handler: router.NewMetricsHandler(), } } From 3238ce2120a0bb6dd2f8ccef74e3f90dc4592637 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 1 Oct 2024 14:18:12 +0200 Subject: [PATCH 06/29] Fix Tests --- .vscode/launch.json | 5 +-- README.md | 6 --- .../applications_controller_test.go | 35 ++++++++++++--- api/buildsecrets/buildsecrets_test.go | 6 ++- .../build_status_controller_test.go | 22 +++++++--- api/deployments/deployment_controller_test.go | 6 ++- .../environment_controller_test.go | 7 ++- .../env_vars_controller_test.go | 5 ++- api/jobs/job_controller_test.go | 14 +++--- api/middleware/auth/authentication.go | 8 ++-- api/middleware/cors/cors.go | 4 +- api/router/api.go | 2 +- api/secrets/secret_controller_test.go | 5 ++- api/test/test_principal.go | 13 ++++++ api/test/utils.go | 32 +++++++------- api/utils/authn/anon_principal.go | 2 +- api/utils/authn/azure_principal.go | 17 +++---- api/utils/authn/mock/validator_mock.go | 12 ++--- api/utils/authn/validator.go | 2 +- api/utils/kubernetes.go | 44 ++++++------------- api/utils/radix_middleware.go | 10 ++--- internal/config/config.go | 1 - main.go | 2 +- 23 files changed, 145 insertions(+), 115 deletions(-) create mode 100644 api/test/test_principal.go diff --git a/.vscode/launch.json b/.vscode/launch.json index 63fdd4e0..3ac9d5df 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,10 +25,7 @@ "RADIX_APP":"radix-api", "LOG_LEVEL":"info", "LOG_PRETTY":"true" - }, - "args": [ - "--useOutClusterClient=false" - ] + } } ] } diff --git a/README.md b/README.md index 210e279a..f008d46b 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,6 @@ The following env vars are needed. Useful default values in brackets. - `PIPELINE_IMG_TAG` - (`master-latest`) - `TEKTON_IMG_TAG` - (`release-latest`) -You also probably want to start with the argument `--useOutClusterClient=false`. When `useOutClusterClient` is `false`, several debugging settings are enabled: -* a service principal with superpowers is used to authorize the requests, and the client's `Authorization` bearer token is ignored. -* the Radix API will connect to the currently-configured `kubectl` context and ignore `K8S_API_HOST`. -* the server CORS settings are modified to accept the `X-Requested-With` header in incoming requests. This is necessary to allow direct requests from web browser while e.g. debugging [radix-web-console](https://github.com/equinor/radix-web-console). -* verbose debugging output from CORS rule evaluation is logged to console. - If you are using VSCode, there is a convenient launch configuration in `.vscode`. #### Validate code diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 8ffa2956..ce0636fa 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -18,6 +18,7 @@ import ( jobModels "github.com/equinor/radix-api/api/jobs/models" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/utils" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" "github.com/equinor/radix-api/internal/config" "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" @@ -33,6 +34,7 @@ import ( commontest "github.com/equinor/radix-operator/pkg/apis/test" builders "github.com/equinor/radix-operator/pkg/apis/utils" radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + "github.com/golang/mock/gomock" "github.com/google/uuid" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" @@ -76,12 +78,15 @@ func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory _ = os.Setenv(defaults.ActiveClusternameEnvironmentVariable, clusterName) // controllerTestUtils is used for issuing HTTP request and processing responses + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) controllerTestUtils := controllertest.NewTestUtils( kubeclient, radixclient, kedaClient, secretproviderclient, certClient, + mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil @@ -104,12 +109,15 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { require.NoError(t, err) t.Run("no access", func(t *testing.T) { + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) controllerTestUtils := controllertest.NewTestUtils( kubeclient, radixclient, kedaClient, secretproviderclient, certClient, + mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return false, nil @@ -127,13 +135,16 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { }) t.Run("access to single app", func(t *testing.T) { - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, rr v1.RadixRegistration) (bool, error) { return rr.GetName() == "my-second-app", nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil - }))) + })), + ) responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications") response := <-responseChannel @@ -144,7 +155,9 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { }) t.Run("access to all app", func(t *testing.T) { - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, @@ -225,7 +238,9 @@ func TestSearchApplicationsPost(t *testing.T) { ) require.NoError(t, err) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, @@ -307,7 +322,9 @@ func TestSearchApplicationsPost(t *testing.T) { }) t.Run("search for "+appNames[0]+" - no access", func(t *testing.T) { - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return false, nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, @@ -403,7 +420,9 @@ func TestSearchApplicationsGet(t *testing.T) { ) require.NoError(t, err) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, @@ -475,7 +494,9 @@ func TestSearchApplicationsGet(t *testing.T) { }) t.Run("search for "+appNames[0]+" - no access", func(t *testing.T) { - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewApplicationController( + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return false, nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, diff --git a/api/buildsecrets/buildsecrets_test.go b/api/buildsecrets/buildsecrets_test.go index 3d5f2bba..aec5e965 100644 --- a/api/buildsecrets/buildsecrets_test.go +++ b/api/buildsecrets/buildsecrets_test.go @@ -6,6 +6,8 @@ import ( "testing" environmentModels "github.com/equinor/radix-api/api/secrets/models" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + "github.com/golang/mock/gomock" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" "github.com/stretchr/testify/require" secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake" @@ -41,7 +43,9 @@ func setupTest(t *testing.T) (*commontest.Utils, *controllertest.Utils, *kubefak err := commonTestUtils.CreateClusterPrerequisites(clusterName, egressIps, subscriptionId) require.NoError(t, err) // controllerTestUtils is used for issuing HTTP request and processing responses - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewBuildSecretsController()) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildSecretsController()) return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient } diff --git a/api/buildstatus/build_status_controller_test.go b/api/buildstatus/build_status_controller_test.go index e588fd28..ebf6426f 100644 --- a/api/buildstatus/build_status_controller_test.go +++ b/api/buildstatus/build_status_controller_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" "github.com/stretchr/testify/require" secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake" @@ -105,8 +106,9 @@ func TestGetBuildStatus(t *testing.T) { Return(expected, nil). Times(1) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewBuildStatusController(fakeBuildStatus)) - + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus") response := <-responseChannel @@ -134,7 +136,9 @@ func TestGetBuildStatus(t *testing.T) { return nil, nil }) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewBuildStatusController(fakeBuildStatus)) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus") response := <-responseChannel @@ -162,7 +166,9 @@ func TestGetBuildStatus(t *testing.T) { return nil, nil }) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewBuildStatusController(fakeBuildStatus)) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus?pipeline=deploy") response := <-responseChannel @@ -190,7 +196,9 @@ func TestGetBuildStatus(t *testing.T) { return nil, nil }) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewBuildStatusController(fakeBuildStatus)) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus?pipeline=promote") response := <-responseChannel @@ -212,7 +220,9 @@ func TestGetBuildStatus(t *testing.T) { Return(nil, errors.New("error")). Times(1) - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewBuildStatusController(fakeBuildStatus)) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus") response := <-responseChannel diff --git a/api/deployments/deployment_controller_test.go b/api/deployments/deployment_controller_test.go index 2e8db687..7b42e4d5 100644 --- a/api/deployments/deployment_controller_test.go +++ b/api/deployments/deployment_controller_test.go @@ -8,7 +8,9 @@ import ( "time" certfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" "github.com/equinor/radix-operator/pkg/apis/kube" + "github.com/golang/mock/gomock" kedav2 "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" "github.com/stretchr/testify/require" "k8s.io/client-go/kubernetes" @@ -40,7 +42,9 @@ func createGetLogEndpoint(appName, podName string) string { func setupTest(t *testing.T) (*commontest.Utils, *controllertest.Utils, kubernetes.Interface, radixclient.Interface, kedav2.Interface, prometheusclient.Interface, secretsstorevclient.Interface, *certfake.Clientset) { commonTestUtils, kubeclient, radixClient, kedaClient, prometheusClient, secretproviderclient, certClient := apiUtils.SetupTest(t) // controllerTestUtils is used for issuing HTTP request and processing responses - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixClient, kedaClient, secretproviderclient, certClient, NewDeploymentController()) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixClient, kedaClient, secretproviderclient, certClient, mockValidator, NewDeploymentController()) return commonTestUtils, &controllerTestUtils, kubeclient, radixClient, kedaClient, prometheusClient, secretproviderclient, certClient } func TestGetPodLog_no_radixconfig(t *testing.T) { diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index 4f520e45..67b67479 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -20,6 +20,7 @@ import ( "github.com/equinor/radix-api/api/secrets/suffix" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/utils" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" "github.com/equinor/radix-api/models" radixmodels "github.com/equinor/radix-common/models" radixhttp "github.com/equinor/radix-common/net/http" @@ -81,10 +82,12 @@ func setupTest(t *testing.T, envHandlerOpts []EnvironmentHandlerOptions) (*commo err := commonTestUtils.CreateClusterPrerequisites(clusterName, egressIps, subscriptionId) require.NoError(t, err) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) // secretControllerTestUtils is used for issuing HTTP request and processing responses - secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixClient, kedaClient, secretproviderclient, certClient, secrets.NewSecretController(nil)) + secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixClient, kedaClient, secretproviderclient, certClient, mockValidator, secrets.NewSecretController(nil)) // controllerTestUtils is used for issuing HTTP request and processing responses - environmentControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixClient, kedaClient, secretproviderclient, certClient, NewEnvironmentController(NewEnvironmentHandlerFactory(envHandlerOpts...))) + environmentControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixClient, kedaClient, secretproviderclient, certClient, mockValidator, NewEnvironmentController(NewEnvironmentHandlerFactory(envHandlerOpts...))) return &commonTestUtils, &environmentControllerTestUtils, &secretControllerTestUtils, kubeclient, radixClient, kedaClient, prometheusclient, secretproviderclient, certClient } diff --git a/api/environmentvariables/env_vars_controller_test.go b/api/environmentvariables/env_vars_controller_test.go index 428b2ce3..bd01c6f6 100644 --- a/api/environmentvariables/env_vars_controller_test.go +++ b/api/environmentvariables/env_vars_controller_test.go @@ -9,6 +9,7 @@ import ( certclientfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" envvarsmodels "github.com/equinor/radix-api/api/environmentvariables/models" controllertest "github.com/equinor/radix-api/api/test" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" "github.com/equinor/radix-operator/pkg/apis/config" "github.com/equinor/radix-operator/pkg/apis/deployment" "github.com/equinor/radix-operator/pkg/apis/kube" @@ -44,8 +45,10 @@ func setupTestWithMockHandler(t *testing.T, mockCtrl *gomock.Controller) (*commo handlerFactory := NewMockenvVarsHandlerFactory(mockCtrl) handlerFactory.EXPECT().createHandler(gomock.Any()).Return(handler) controller := (&envVarsController{}).withHandlerFactory(handlerFactory) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) // controllerTestUtils is used for issuing HTTP request and processing responses - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, controller) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, controller) return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, prometheusclient, certClient, handler } diff --git a/api/jobs/job_controller_test.go b/api/jobs/job_controller_test.go index 6bff4fe4..b95ae029 100644 --- a/api/jobs/job_controller_test.go +++ b/api/jobs/job_controller_test.go @@ -5,7 +5,9 @@ import ( "fmt" "testing" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" "github.com/equinor/radix-common/utils/pointers" + "github.com/golang/mock/gomock" kedav2 "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" "github.com/stretchr/testify/require" @@ -44,7 +46,7 @@ const ( anyUser = "a_user@equinor.com" ) -func setupTest() (*commontest.Utils, *controllertest.Utils, kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretsstorevclient.Interface, *certclientfake.Clientset) { +func setupTest(t *testing.T) (*commontest.Utils, *controllertest.Utils, kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretsstorevclient.Interface, *certclientfake.Clientset) { // Setup kubeclient := kubefake.NewSimpleClientset() radixclient := fake.NewSimpleClientset() @@ -56,14 +58,16 @@ func setupTest() (*commontest.Utils, *controllertest.Utils, kubernetes.Interface commonTestUtils := commontest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient) // controllerTestUtils is used for issuing HTTP request and processing responses - controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewJobController()) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewJobController()) return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, secretproviderclient, certClient } func TestGetApplicationJob(t *testing.T) { // Setup - commonTestUtils, controllerTestUtils, client, radixclient, kedaClient, secretproviderclient, certClient := setupTest() + commonTestUtils, controllerTestUtils, client, radixclient, kedaClient, secretproviderclient, certClient := setupTest(t) _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration(). WithName(anyAppName). @@ -130,7 +134,7 @@ func TestGetApplicationJob_RadixJobSpecExists(t *testing.T) { anyJobName := "any-job" // Setup - commonTestUtils, controllerTestUtils, _, _, _, _, _ := setupTest() + commonTestUtils, controllerTestUtils, _, _, _, _, _ := setupTest(t) job, _ := commonTestUtils.ApplyJob(builders.AStartedBuildDeployJob().WithAppName(anyAppName).WithJobName(anyJobName)) // Test @@ -148,7 +152,7 @@ func TestGetApplicationJob_RadixJobSpecExists(t *testing.T) { } func TestGetPipelineJobLogsError(t *testing.T) { - commonTestUtils, controllerTestUtils, _, _, _, _, _ := setupTest() + commonTestUtils, controllerTestUtils, _, _, _, _, _ := setupTest(t) t.Run("job doesn't exist", func(t *testing.T) { aJobName := "aJobName" diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index 3af7b314..b53b06b6 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -39,7 +39,7 @@ func CreateAuthenticationMiddleware(validator token.ValidatorInterface) negroni. } return } - logContext := log.Ctx(ctx).With().Str("user", principal.Subject()) + logContext := log.Ctx(ctx).With().Str("user", principal.Name()) impersonation, err := radixhttp.GetImpersonationFromHeader(r) if err != nil { @@ -62,7 +62,7 @@ func CreateAuthenticationMiddleware(validator token.ValidatorInterface) negroni. } } -func GetToken(ctx context.Context) token.TokenPrincipal { +func CtxTokenPrincipal(ctx context.Context) token.TokenPrincipal { val, ok := ctx.Value(ctxUserKey{}).(token.TokenPrincipal) if !ok { @@ -72,7 +72,7 @@ func GetToken(ctx context.Context) token.TokenPrincipal { return val } -func GetImpersonation(ctx context.Context) models.Impersonation { +func CtxImpersonation(ctx context.Context) models.Impersonation { if val, ok := ctx.Value(ctxImpersonationKey{}).(models.Impersonation); ok { return val } @@ -83,7 +83,7 @@ func GetImpersonation(ctx context.Context) models.Impersonation { func CreateAuthorizeRequiredMiddleware() negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { logger := log.Ctx(r.Context()) - user := GetToken(r.Context()) + user := CtxTokenPrincipal(r.Context()) if !user.IsAuthenticated() { logger.Warn().Msg("authorization error") diff --git a/api/middleware/cors/cors.go b/api/middleware/cors/cors.go index 04f65fe9..e1b7d596 100644 --- a/api/middleware/cors/cors.go +++ b/api/middleware/cors/cors.go @@ -35,13 +35,11 @@ func CreateMiddleware(clusterName, radixDNSZone string) *cors.Cors { AllowedMethods: []string{"GET", "PUT", "POST", "OPTIONS", "DELETE", "PATCH"}, } - if zerolog.GlobalLevel() <= zerolog.DebugLevel { + if zerolog.GlobalLevel() <= zerolog.TraceLevel { // debugging mode corsOptions.Debug = true corsLogger := log.Logger.With().Str("pkg", "cors-middleware").Logger() corsOptions.Logger = &corsLogger - // necessary header to allow ajax requests directly from radix-web-console app in browser - corsOptions.AllowedHeaders = append(corsOptions.AllowedHeaders, "X-Requested-With") } c := cors.New(corsOptions) diff --git a/api/router/api.go b/api/router/api.go index c7126b90..347231d4 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -28,8 +28,8 @@ func NewAPIHandler(clusterName string, validator token.ValidatorInterface, radix n := negroni.New( recovery.CreateMiddleware(), - cors.CreateMiddleware(clusterName, radixDNSZone), logger.CreateZerologRequestIdMiddleware(), + cors.CreateMiddleware(clusterName, radixDNSZone), logger.CreateZerologRequestDetailsMiddleware(), auth.CreateAuthenticationMiddleware(validator), logger.CreateZerologRequestLoggerMiddleware(), diff --git a/api/secrets/secret_controller_test.go b/api/secrets/secret_controller_test.go index 022cd8b6..7b9f2259 100644 --- a/api/secrets/secret_controller_test.go +++ b/api/secrets/secret_controller_test.go @@ -10,6 +10,7 @@ import ( certclientfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" secretModels "github.com/equinor/radix-api/api/secrets/models" controllertest "github.com/equinor/radix-api/api/test" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" "github.com/equinor/radix-api/api/utils/tlsvalidation" tlsvalidationmock "github.com/equinor/radix-api/api/utils/tlsvalidation/mock" radixhttp "github.com/equinor/radix-common/net/http" @@ -58,7 +59,9 @@ func setupTest(t *testing.T, tlsValidator tlsvalidation.Validator) (*commontest. require.NoError(t, err) // secretControllerTestUtils is used for issuing HTTP request and processing responses - secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, NewSecretController(tlsValidator)) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewSecretController(tlsValidator)) return &commonTestUtils, &secretControllerTestUtils, kubeclient, radixclient, prometheusclient, secretproviderclient } diff --git a/api/test/test_principal.go b/api/test/test_principal.go new file mode 100644 index 00000000..5f4650a3 --- /dev/null +++ b/api/test/test_principal.go @@ -0,0 +1,13 @@ +package test + +type TestPrincipal struct{} + +func (p *TestPrincipal) Token() string { + return "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MjQ1YTJlYzEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xMjM0NTY3OC03NTY1LTIzNDItMjM0Mi0xMjM0MDViNDU5YjAvIiwiaWF0IjoxNTc1MzU1NTA4LCJuYmYiOjE1NzUzNTU1MDgsImV4cCI6MTU3NTM1OTQwOCwiYWNyIjoiMSIsImFpbyI6IjQyYXNkYXMiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDc5MDM5YTkwIiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJKb2huIiwiZ2l2ZW5fbmFtZSI6IkRvZSIsImhhc2dyb3VwcyI6InRydWUiLCJpcGFkZHIiOiIxMC4xMC4xMC4xMCIsIm5hbWUiOiJKb2huIERvZSIsIm9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzRmYzhmYTBlYSIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0xMjM0NTY3ODktMTIzNDU2OTc4MC0xMjM0NTY3ODktMTIzNDU2NyIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IjBoa2JpbEo3MTIzNHpSU3h6eHZiSW1hc2RmZ3N4amI2YXNkZmVOR2FzZGYiLCJ0aWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MDViNDU5YjAiLCJ1bmlxdWVfbmFtZSI6Im5vdC1leGlzdGluZy1yYWRpeC1lbWFpbEBlcXVpbm9yLmNvbSIsInVwbiI6Im5vdC1leGlzdGluZy10ZXN0LXJhZGl4LWVtYWlsQGVxdWlub3IuY29tIiwidXRpIjoiQlMxMmFzR2R1RXlyZUVjRGN2aDJBRyIsInZlciI6IjEuMCJ9.EB5z7Mk34NkFPCP8MqaNMo4UeWgNyO4-qEmzOVPxfoBqbgA16Ar4xeONXODwjZn9iD-CwJccusW6GP0xZ_PJHBFpfaJO_tLaP1k0KhT-eaANt112TvDBt0yjHtJg6He6CEDqagREIsH3w1mSm40zWLKGZeRLdnGxnQyKsTmNJ1rFRdY3AyoEgf6-pnJweUt0LaFMKmIJ2HornStm2hjUstBaji_5cSS946zqp4tgrc-RzzDuaQXzqlVL2J22SR2S_Oux_3yw88KmlhEFFP9axNcbjZrzW3L9XWnPT6UzVIaVRaNRSWfqDATg-jeHg4Gm1bp8w0aIqLdDxc9CfFMjuQ" +} +func (p *TestPrincipal) Name() string { return "test" } +func (p *TestPrincipal) IsAuthenticated() bool { return true } + +func NewTestPrincipal() *TestPrincipal { + return &TestPrincipal{} +} diff --git a/api/test/utils.go b/api/test/utils.go index 40a027f1..f69c6347 100644 --- a/api/test/utils.go +++ b/api/test/utils.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" kedav2 "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" "github.com/rs/zerolog/log" @@ -35,10 +36,13 @@ type Utils struct { secretProviderClient *secretsstorevclientfake.Clientset certClient *certclientfake.Clientset controllers []models.Controller + validatorMock *authnmock.MockValidatorInterface } +const radixDNSZone = "fakezone.radix.com" + // NewTestUtils Constructor -func NewTestUtils(kubeClient *kubernetesfake.Clientset, radixClient *radixclientfake.Clientset, kedaClient *kedafake.Clientset, secretProviderClient *secretsstorevclientfake.Clientset, certClient *certclientfake.Clientset, controllers ...models.Controller) Utils { +func NewTestUtils(kubeClient *kubernetesfake.Clientset, radixClient *radixclientfake.Clientset, kedaClient *kedafake.Clientset, secretProviderClient *secretsstorevclientfake.Clientset, certClient *certclientfake.Clientset, validator *authnmock.MockValidatorInterface, controllers ...models.Controller) Utils { return Utils{ kubeClient: kubeClient, radixClient: radixClient, @@ -46,6 +50,7 @@ func NewTestUtils(kubeClient *kubernetesfake.Clientset, radixClient *radixclient secretProviderClient: secretProviderClient, certClient: certClient, controllers: controllers, + validatorMock: validator, } } @@ -59,11 +64,13 @@ func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string) <-chan *htt req, _ := http.NewRequest(method, endpoint, reader) + // TODO: Implement options to override validation + response := make(chan *httptest.ResponseRecorder) go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", "", "", "", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler("anyClusterName", tu.validatorMock, "", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) response <- rr }() @@ -87,7 +94,7 @@ func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameter go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", "", "", "", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler("anyClusterName", tu.validatorMock, radixDNSZone, NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) response <- rr }() @@ -97,7 +104,7 @@ func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameter // Generates fake token. Use it to reduce noise of security scanner fail alerts func getFakeToken() string { - return "bea" + "rer " + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MjQ1YTJlYzEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xMjM0NTY3OC03NTY1LTIzNDItMjM0Mi0xMjM0MDViNDU5YjAvIiwiaWF0IjoxNTc1MzU1NTA4LCJuYmYiOjE1NzUzNTU1MDgsImV4cCI6MTU3NTM1OTQwOCwiYWNyIjoiMSIsImFpbyI6IjQyYXNkYXMiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDc5MDM5YTkwIiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJKb2huIiwiZ2l2ZW5fbmFtZSI6IkRvZSIsImhhc2dyb3VwcyI6InRydWUiLCJpcGFkZHIiOiIxMC4xMC4xMC4xMCIsIm5hbWUiOiJKb2huIERvZSIsIm9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzRmYzhmYTBlYSIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0xMjM0NTY3ODktMTIzNDU2OTc4MC0xMjM0NTY3ODktMTIzNDU2NyIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IjBoa2JpbEo3MTIzNHpSU3h6eHZiSW1hc2RmZ3N4amI2YXNkZmVOR2FzZGYiLCJ0aWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MDViNDU5YjAiLCJ1bmlxdWVfbmFtZSI6Im5vdC1leGlzdGluZy1yYWRpeC1lbWFpbEBlcXVpbm9yLmNvbSIsInVwbiI6Im5vdC1leGlzdGluZy10ZXN0LXJhZGl4LWVtYWlsQGVxdWlub3IuY29tIiwidXRpIjoiQlMxMmFzR2R1RXlyZUVjRGN2aDJBRyIsInZlciI6IjEuMCJ9.EB5z7Mk34NkFPCP8MqaNMo4UeWgNyO4-qEmzOVPxfoBqbgA16Ar4xeONXODwjZn9iD-CwJccusW6GP0xZ_PJHBFpfaJO_tLaP1k0KhT-eaANt112TvDBt0yjHtJg6He6CEDqagREIsH3w1mSm40zWLKGZeRLdnGxnQyKsTmNJ1rFRdY3AyoEgf6-pnJweUt0LaFMKmIJ2HornStm2hjUstBaji_5cSS946zqp4tgrc-RzzDuaQXzqlVL2J22SR2S_Oux_3yw88KmlhEFFP9axNcbjZrzW3L9XWnPT6UzVIaVRaNRSWfqDATg-jeHg4Gm1bp8w0aIqLdDxc9CfFMjuQ" + return "bearer " + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MjQ1YTJlYzEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xMjM0NTY3OC03NTY1LTIzNDItMjM0Mi0xMjM0MDViNDU5YjAvIiwiaWF0IjoxNTc1MzU1NTA4LCJuYmYiOjE1NzUzNTU1MDgsImV4cCI6MTU3NTM1OTQwOCwiYWNyIjoiMSIsImFpbyI6IjQyYXNkYXMiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDc5MDM5YTkwIiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJKb2huIiwiZ2l2ZW5fbmFtZSI6IkRvZSIsImhhc2dyb3VwcyI6InRydWUiLCJpcGFkZHIiOiIxMC4xMC4xMC4xMCIsIm5hbWUiOiJKb2huIERvZSIsIm9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzRmYzhmYTBlYSIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0xMjM0NTY3ODktMTIzNDU2OTc4MC0xMjM0NTY3ODktMTIzNDU2NyIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IjBoa2JpbEo3MTIzNHpSU3h6eHZiSW1hc2RmZ3N4amI2YXNkZmVOR2FzZGYiLCJ0aWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MDViNDU5YjAiLCJ1bmlxdWVfbmFtZSI6Im5vdC1leGlzdGluZy1yYWRpeC1lbWFpbEBlcXVpbm9yLmNvbSIsInVwbiI6Im5vdC1leGlzdGluZy10ZXN0LXJhZGl4LWVtYWlsQGVxdWlub3IuY29tIiwidXRpIjoiQlMxMmFzR2R1RXlyZUVjRGN2aDJBRyIsInZlciI6IjEuMCJ9.EB5z7Mk34NkFPCP8MqaNMo4UeWgNyO4-qEmzOVPxfoBqbgA16Ar4xeONXODwjZn9iD-CwJccusW6GP0xZ_PJHBFpfaJO_tLaP1k0KhT-eaANt112TvDBt0yjHtJg6He6CEDqagREIsH3w1mSm40zWLKGZeRLdnGxnQyKsTmNJ1rFRdY3AyoEgf6-pnJweUt0LaFMKmIJ2HornStm2hjUstBaji_5cSS946zqp4tgrc-RzzDuaQXzqlVL2J22SR2S_Oux_3yw88KmlhEFFP9axNcbjZrzW3L9XWnPT6UzVIaVRaNRSWfqDATg-jeHg4Gm1bp8w0aIqLdDxc9CfFMjuQ" } // GetErrorResponse Gets error repsonse @@ -127,10 +134,6 @@ type kubeUtilMock struct { kedaClient *kedafake.Clientset } -func (ku *kubeUtilMock) IsUseOutClusterClient() bool { - return true -} - // NewKubeUtilMock Constructor func NewKubeUtilMock(kubeClient *kubernetesfake.Clientset, radixClient *radixclientfake.Clientset, kedaClient *kedafake.Clientset, secretProviderClient *secretsstorevclientfake.Clientset, certClient *certclientfake.Clientset) utils.KubeUtil { return &kubeUtilMock{ @@ -142,17 +145,12 @@ func NewKubeUtilMock(kubeClient *kubernetesfake.Clientset, radixClient *radixcli } } -// GetOutClusterKubernetesClient Gets a kubefake client using the bearer token from the radix api client -func (ku *kubeUtilMock) GetOutClusterKubernetesClient(_ string, _ ...utils.RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretsstorevclient.Interface, tektonclient.Interface, certclient.Interface) { - return ku.kubeClient, ku.radixClient, ku.kedaClient, ku.secretProviderClient, nil, ku.certClient -} - -// GetOutClusterKubernetesClientWithImpersonation Gets a kubefake client -func (ku *kubeUtilMock) GetOutClusterKubernetesClientWithImpersonation(_ string, impersonation radixmodels.Impersonation, _ ...utils.RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretsstorevclient.Interface, tektonclient.Interface, certclient.Interface) { +// GetUserKubernetesClient Gets a kubefake client +func (ku *kubeUtilMock) GetUserKubernetesClient(_ string, impersonation radixmodels.Impersonation, _ ...utils.RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretsstorevclient.Interface, tektonclient.Interface, certclient.Interface) { return ku.kubeClient, ku.radixClient, ku.kedaClient, ku.secretProviderClient, nil, ku.certClient } -// GetInClusterKubernetesClient Gets a kubefake client using the config of the running pod -func (ku *kubeUtilMock) GetInClusterKubernetesClient(_ ...utils.RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretsstorevclient.Interface, tektonclient.Interface, certclient.Interface) { +// GetServerKubernetesClient Gets a kubefake client using the config of the running pod +func (ku *kubeUtilMock) GetServerKubernetesClient(_ ...utils.RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretsstorevclient.Interface, tektonclient.Interface, certclient.Interface) { return ku.kubeClient, ku.radixClient, ku.kedaClient, ku.secretProviderClient, nil, ku.certClient } diff --git a/api/utils/authn/anon_principal.go b/api/utils/authn/anon_principal.go index ec9b6702..7356a6f9 100644 --- a/api/utils/authn/anon_principal.go +++ b/api/utils/authn/anon_principal.go @@ -3,7 +3,7 @@ package token type AnonPrincipal struct{} func (p *AnonPrincipal) Token() string { return "" } -func (p *AnonPrincipal) Subject() string { return "anonymous" } +func (p *AnonPrincipal) Name() string { return "anonymous" } func (p *AnonPrincipal) IsAuthenticated() bool { return false } func NewAnonymousPrincipal() *AnonPrincipal { diff --git a/api/utils/authn/azure_principal.go b/api/utils/authn/azure_principal.go index b469c53a..e453e13a 100644 --- a/api/utils/authn/azure_principal.go +++ b/api/utils/authn/azure_principal.go @@ -2,7 +2,6 @@ package token import ( "context" - "errors" "github.com/auth0/go-jwt-middleware/v2/validator" ) @@ -13,14 +12,6 @@ type azureClaims struct { } func (c *azureClaims) Validate(_ context.Context) error { - if c == nil { - return errors.New("invalid claim") - } - - if c.Upn == "" && c.AppId == "" { - return errors.New("missing one of the required field: upn,appid") - } - return nil } @@ -36,10 +27,14 @@ func (p *AzurePrincipal) Token() string { func (p *AzurePrincipal) IsAuthenticated() bool { return true } -func (p *AzurePrincipal) Subject() string { +func (p *AzurePrincipal) Name() string { if p.azureClaims.Upn != "" { return p.azureClaims.Upn } - return p.azureClaims.AppId + if p.azureClaims.AppId != "" { + return p.azureClaims.AppId + } + + return p.claims.RegisteredClaims.Subject } diff --git a/api/utils/authn/mock/validator_mock.go b/api/utils/authn/mock/validator_mock.go index 4c99f918..ef25e140 100644 --- a/api/utils/authn/mock/validator_mock.go +++ b/api/utils/authn/mock/validator_mock.go @@ -49,18 +49,18 @@ func (mr *MockTokenPrincipalMockRecorder) IsAuthenticated() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAuthenticated", reflect.TypeOf((*MockTokenPrincipal)(nil).IsAuthenticated)) } -// Subject mocks base method. -func (m *MockTokenPrincipal) Subject() string { +// Name mocks base method. +func (m *MockTokenPrincipal) Name() string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Subject") + ret := m.ctrl.Call(m, "Name") ret0, _ := ret[0].(string) return ret0 } -// Subject indicates an expected call of Subject. -func (mr *MockTokenPrincipalMockRecorder) Subject() *gomock.Call { +// Name indicates an expected call of Name. +func (mr *MockTokenPrincipalMockRecorder) Name() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subject", reflect.TypeOf((*MockTokenPrincipal)(nil).Subject)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockTokenPrincipal)(nil).Name)) } // Token mocks base method. diff --git a/api/utils/authn/validator.go b/api/utils/authn/validator.go index b11e2bae..c417a294 100644 --- a/api/utils/authn/validator.go +++ b/api/utils/authn/validator.go @@ -13,7 +13,7 @@ import ( type TokenPrincipal interface { IsAuthenticated() bool Token() string - Subject() string + Name() string } type ValidatorInterface interface { diff --git a/api/utils/kubernetes.go b/api/utils/kubernetes.go index d586a107..7b288a90 100644 --- a/api/utils/kubernetes.go +++ b/api/utils/kubernetes.go @@ -38,19 +38,12 @@ func WithBurst(burst int) RestClientConfigOption { // KubeUtil Interface to be mocked in tests type KubeUtil interface { - GetOutClusterKubernetesClient(string, ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) - GetOutClusterKubernetesClientWithImpersonation(string, radixmodels.Impersonation, ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) - GetInClusterKubernetesClient(...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) - IsUseOutClusterClient() bool + GetUserKubernetesClient(string, radixmodels.Impersonation, ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) + GetServerKubernetesClient(...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) } type kubeUtil struct { - kubeApiServer string - useOutClusterClient bool -} - -func (ku *kubeUtil) IsUseOutClusterClient() bool { - return ku.useOutClusterClient + kubeApiServer string } var ( @@ -62,32 +55,23 @@ var ( ) // NewKubeUtil Constructor -func NewKubeUtil(useOutClusterClient bool, kubeApiServer string) KubeUtil { - return &kubeUtil{kubeApiServer, useOutClusterClient} -} - -// GetOutClusterKubernetesClient Gets a kubernetes client using the bearer token from the radix api client -func (ku *kubeUtil) GetOutClusterKubernetesClient(token string, options ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) { - return ku.GetOutClusterKubernetesClientWithImpersonation(token, radixmodels.Impersonation{}, options...) +func NewKubeUtil(kubeApiServer string) KubeUtil { + return &kubeUtil{kubeApiServer} } -// GetOutClusterKubernetesClientWithImpersonation Gets a kubernetes client using the bearer token from the radix api client -func (ku *kubeUtil) GetOutClusterKubernetesClientWithImpersonation(token string, impersonation radixmodels.Impersonation, options ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) { - if ku.useOutClusterClient { - config := getOutClusterClientConfig(token, impersonation, ku.kubeApiServer, options) - return getKubernetesClientFromConfig(config) - } - - return ku.GetInClusterKubernetesClient(options...) +// GetUserKubernetesClient Gets a kubernetes client using the bearer token from the radix api client +func (ku *kubeUtil) GetUserKubernetesClient(token string, impersonation radixmodels.Impersonation, options ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) { + config := getUserClientConfig(token, impersonation, ku.kubeApiServer, options) + return getKubernetesClientFromConfig(config) } -// GetInClusterKubernetesClient Gets a kubernetes client using the config of the running pod -func (ku *kubeUtil) GetInClusterKubernetesClient(options ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) { - config := getInClusterClientConfig(options) +// GetServerKubernetesClient Gets a kubernetes client using the config of host or pod +func (ku *kubeUtil) GetServerKubernetesClient(options ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) { + config := getServerClientConfig(options) return getKubernetesClientFromConfig(config) } -func getOutClusterClientConfig(token string, impersonation radixmodels.Impersonation, kubeApiServer string, options []RestClientConfigOption) *restclient.Config { +func getUserClientConfig(token string, impersonation radixmodels.Impersonation, kubeApiServer string, options []RestClientConfigOption) *restclient.Config { kubeConfig := &restclient.Config{ Host: kubeApiServer, BearerToken: token, @@ -111,7 +95,7 @@ func getOutClusterClientConfig(token string, impersonation radixmodels.Impersona return addCommonConfigs(kubeConfig, options) } -func getInClusterClientConfig(options []RestClientConfigOption) *restclient.Config { +func getServerClientConfig(options []RestClientConfigOption) *restclient.Config { kubeConfigPath := os.Getenv("HOME") + "/.kube/config" config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath) if err != nil { diff --git a/api/utils/radix_middleware.go b/api/utils/radix_middleware.go index 8665b744..5a78005b 100644 --- a/api/utils/radix_middleware.go +++ b/api/utils/radix_middleware.go @@ -58,12 +58,12 @@ func (handler *RadixMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request func (handler *RadixMiddleware) handleAuthorization(w http.ResponseWriter, r *http.Request) { logger := log.Ctx(r.Context()) - token := auth.GetToken(r.Context()).Token() - impersonation := auth.GetImpersonation(r.Context()) + token := auth.CtxTokenPrincipal(r.Context()).Token() + impersonation := auth.CtxImpersonation(r.Context()) restOptions := handler.getRestClientOptions() - inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient := handler.kubeUtil.GetInClusterKubernetesClient(restOptions...) - outClusterClient, outClusterRadixClient, outClusterKedaClient, outClusterSecretProviderClient, outClusterTektonClient, outClusterCertManagerClient := handler.kubeUtil.GetOutClusterKubernetesClientWithImpersonation(token, impersonation, restOptions...) + inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient := handler.kubeUtil.GetServerKubernetesClient(restOptions...) + outClusterClient, outClusterRadixClient, outClusterKedaClient, outClusterSecretProviderClient, outClusterTektonClient, outClusterCertManagerClient := handler.kubeUtil.GetUserKubernetesClient(token, impersonation, restOptions...) accounts := models.NewAccounts( inClusterClient, @@ -97,7 +97,7 @@ func (handler *RadixMiddleware) handleAuthorization(w http.ResponseWriter, r *ht func (handler *RadixMiddleware) handleAnonymous(w http.ResponseWriter, r *http.Request) { restOptions := handler.getRestClientOptions() - inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient := handler.kubeUtil.GetInClusterKubernetesClient(restOptions...) + inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient := handler.kubeUtil.GetServerKubernetesClient(restOptions...) sa := models.NewServiceAccount(inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient) accounts := models.Accounts{ServiceAccount: sa} diff --git a/internal/config/config.go b/internal/config/config.go index d59cb46b..27141f9a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,7 +10,6 @@ type Config struct { MetricsPort int `envconfig:"METRICS_PORT" default:"9090" desc:"Port where Metrics will be served"` ProfilePort int `envconfig:"PROFILE_PORT" default:"7070" desc:"Port where Profiler will be served"` UseProfiler bool `envconfig:"USE_PROFILER" default:"false" desc:"Enable Profiler"` - UseOutClusterClient bool `envconfig:"USE_OUT_CLUSTER_CLIENT" default:"true" desc:"In case of testing on local machine you may want to set this to false"` ClusterName string `envconfig:"RADIX_CLUSTERNAME" required:"true"` DNSZone string `envconfig:"RADIX_DNS_ZONE" required:"true"` OidcIssuer string `envconfig:"OIDC_ISSUER" required:"true"` diff --git a/main.go b/main.go index 75ada058..faec4668 100644 --- a/main.go +++ b/main.go @@ -66,7 +66,7 @@ func initializeServer(c config.Config) *http.Server { log.Fatal().Err(err).Msgf("failed to initialize controllers: %v", err) } - handler := router.NewAPIHandler(c.ClusterName, jwtValidator, c.DNSZone, utils.NewKubeUtil(c.UseOutClusterClient, c.KubernetesApiServer), controllers...) + handler := router.NewAPIHandler(c.ClusterName, jwtValidator, c.DNSZone, utils.NewKubeUtil(c.KubernetesApiServer), controllers...) srv := &http.Server{ Addr: fmt.Sprintf(":%d", c.Port), Handler: handler, From cb912279889007a5a5e87e7ee3c0a52f97550412 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 1 Oct 2024 15:19:35 +0200 Subject: [PATCH 07/29] Replace username with azure object id --- .../applications_controller_test.go | 4 +- api/applications/applications_handler.go | 6 +- api/middleware/auth/authentication.go | 2 +- api/middleware/auth/authentication_test.go | 154 ++++++++++++++++++ api/test/test_principal.go | 2 +- api/test/utils.go | 36 +++- api/utils/authn/anon_principal.go | 2 +- api/utils/authn/azure_principal.go | 15 +- api/utils/authn/mock/validator_mock.go | 28 ++-- api/utils/authn/validator.go | 10 +- 10 files changed, 210 insertions(+), 49 deletions(-) create mode 100644 api/middleware/auth/authentication_test.go diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index ce0636fa..12bed4fd 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -1993,10 +1993,10 @@ func buildApplicationRegistrationRequest(applicationRegistration applicationMode type testApplicationHandlerFactory struct { config config.Config - hasAccessToGetConfigMap hasAccessToGetConfigMapFunc + hasAccessToGetConfigMap HasAccessToGetConfigMapFunc } -func newTestApplicationHandlerFactory(config config.Config, hasAccessToGetConfigMap hasAccessToGetConfigMapFunc) *testApplicationHandlerFactory { +func newTestApplicationHandlerFactory(config config.Config, hasAccessToGetConfigMap HasAccessToGetConfigMapFunc) *testApplicationHandlerFactory { return &testApplicationHandlerFactory{ config: config, hasAccessToGetConfigMap: hasAccessToGetConfigMap, diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index c39fd44a..d6532116 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -48,7 +48,7 @@ type patch struct { Value interface{} `json:"value"` } -type hasAccessToGetConfigMapFunc func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) +type HasAccessToGetConfigMapFunc func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) // ApplicationHandler Instance variables type ApplicationHandler struct { @@ -57,11 +57,11 @@ type ApplicationHandler struct { accounts models.Accounts config config.Config namespace string - hasAccessToGetConfigMap func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) + hasAccessToGetConfigMap HasAccessToGetConfigMapFunc } // NewApplicationHandler Constructor -func NewApplicationHandler(accounts models.Accounts, config config.Config, hasAccessToGetConfigMap hasAccessToGetConfigMapFunc) ApplicationHandler { +func NewApplicationHandler(accounts models.Accounts, config config.Config, hasAccessToGetConfigMap HasAccessToGetConfigMapFunc) ApplicationHandler { return ApplicationHandler{ accounts: accounts, jobHandler: job.Init(accounts, deployments.Init(accounts), config.PipelineImageTag, config.TektonImageTag), diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index b53b06b6..a7cc24c1 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -39,7 +39,7 @@ func CreateAuthenticationMiddleware(validator token.ValidatorInterface) negroni. } return } - logContext := log.Ctx(ctx).With().Str("user", principal.Name()) + logContext := log.Ctx(ctx).With().Str("azure_oid", principal.Id()) impersonation, err := radixhttp.GetImpersonationFromHeader(r) if err != nil { diff --git a/api/middleware/auth/authentication_test.go b/api/middleware/auth/authentication_test.go new file mode 100644 index 00000000..5df407e1 --- /dev/null +++ b/api/middleware/auth/authentication_test.go @@ -0,0 +1,154 @@ +package auth_test + +import ( + "context" + "fmt" + "net/http" + "os" + "testing" + + certfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" + "github.com/equinor/radix-api/api/applications" + applicationModels "github.com/equinor/radix-api/api/applications/models" + controllertest "github.com/equinor/radix-api/api/test" + token "github.com/equinor/radix-api/api/utils/authn" + authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + "github.com/equinor/radix-api/internal/config" + "github.com/equinor/radix-api/models" + radixhttp "github.com/equinor/radix-common/net/http" + "github.com/equinor/radix-operator/pkg/apis/defaults" + v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + commontest "github.com/equinor/radix-operator/pkg/apis/test" + builders "github.com/equinor/radix-operator/pkg/apis/utils" + radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + "github.com/golang/mock/gomock" + kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" + prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/client-go/kubernetes" + kubefake "k8s.io/client-go/kubernetes/fake" + secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake" +) + +const ( + clusterName = "AnyClusterName" + appAliasDNSZone = "app.dev.radix.equinor.com" + egressIps = "0.0.0.0" + subscriptionId = "12347718-c8f8-4995-bfbb-02655ff1f89c" +) + +func setupTest(t *testing.T, requireAppConfigurationItem, requireAppADGroups bool) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *radixfake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { + return setupTestWithFactory(t, newTestApplicationHandlerFactory( + config.Config{RequireAppConfigurationItem: requireAppConfigurationItem, RequireAppADGroups: requireAppADGroups}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }, + )) +} +func setupTestWithFactory(t *testing.T, handlerFactory applications.ApplicationHandlerFactory) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *radixfake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { + // Setup + kubeclient := kubefake.NewSimpleClientset() + radixclient := radixfake.NewSimpleClientset() + kedaClient := kedafake.NewSimpleClientset() + prometheusclient := prometheusfake.NewSimpleClientset() + secretproviderclient := secretproviderfake.NewSimpleClientset() + certClient := certfake.NewSimpleClientset() + + // commonTestUtils is used for creating CRDs + commonTestUtils := commontest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient) + err := commonTestUtils.CreateClusterPrerequisites(clusterName, egressIps, subscriptionId) + require.NoError(t, err) + _ = os.Setenv(defaults.ActiveClusternameEnvironmentVariable, clusterName) + + // controllerTestUtils is used for issuing HTTP request and processing responses + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + controllerTestUtils := controllertest.NewTestUtils( + kubeclient, + radixclient, + kedaClient, + secretproviderclient, + certClient, + mockValidator, + applications.NewApplicationController( + func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return true, nil + }, + handlerFactory, + ), + ) + + return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, prometheusclient, secretproviderclient, certClient +} + +type testApplicationHandlerFactory struct { + config config.Config + hasAccessToGetConfigMap applications.HasAccessToGetConfigMapFunc +} + +func newTestApplicationHandlerFactory(config config.Config, hasAccessToGetConfigMap applications.HasAccessToGetConfigMapFunc) *testApplicationHandlerFactory { + return &testApplicationHandlerFactory{ + config, + hasAccessToGetConfigMap, + } +} + +// Create creates a new ApplicationHandler +func (f *testApplicationHandlerFactory) Create(accounts models.Accounts) applications.ApplicationHandler { + return applications.NewApplicationHandler(accounts, f.config, f.hasAccessToGetConfigMap) +} + +func TestGetApplications_Authenticated(t *testing.T) { + // Setup + commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, true, true) + _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) + require.NoError(t, err) + + // Test + + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(controllertest.NewTestPrincipal(), nil) + + responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications"), controllertest.WithValidatorOverride(mockValidator)) + response := <-responseChannel + + applications := make([]applicationModels.ApplicationSummary, 0) + err = controllertest.GetResponseBody(response, &applications) + require.NoError(t, err) + assert.Equal(t, 1, len(applications)) +} + +func TestGetApplications_Unauthenticated(t *testing.T) { + // Setup + commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, true, true) + _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) + require.NoError(t, err) + + // Test + + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(token.NewAnonymousPrincipal(), nil) + + responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications"), controllertest.WithValidatorOverride(mockValidator)) + response := <-responseChannel + + assert.Equal(t, http.StatusForbidden, response.Code) +} + +func TestGetApplications_InvalidToken(t *testing.T) { + // Setup + commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, true, true) + _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) + require.NoError(t, err) + + // Test + + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(token.NewAnonymousPrincipal(), radixhttp.ForbiddenError("invalid token")) + + responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications"), controllertest.WithValidatorOverride(mockValidator)) + response := <-responseChannel + + assert.Equal(t, http.StatusForbidden, response.Code) +} diff --git a/api/test/test_principal.go b/api/test/test_principal.go index 5f4650a3..378511e0 100644 --- a/api/test/test_principal.go +++ b/api/test/test_principal.go @@ -5,7 +5,7 @@ type TestPrincipal struct{} func (p *TestPrincipal) Token() string { return "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MjQ1YTJlYzEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xMjM0NTY3OC03NTY1LTIzNDItMjM0Mi0xMjM0MDViNDU5YjAvIiwiaWF0IjoxNTc1MzU1NTA4LCJuYmYiOjE1NzUzNTU1MDgsImV4cCI6MTU3NTM1OTQwOCwiYWNyIjoiMSIsImFpbyI6IjQyYXNkYXMiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDc5MDM5YTkwIiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJKb2huIiwiZ2l2ZW5fbmFtZSI6IkRvZSIsImhhc2dyb3VwcyI6InRydWUiLCJpcGFkZHIiOiIxMC4xMC4xMC4xMCIsIm5hbWUiOiJKb2huIERvZSIsIm9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzRmYzhmYTBlYSIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0xMjM0NTY3ODktMTIzNDU2OTc4MC0xMjM0NTY3ODktMTIzNDU2NyIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IjBoa2JpbEo3MTIzNHpSU3h6eHZiSW1hc2RmZ3N4amI2YXNkZmVOR2FzZGYiLCJ0aWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MDViNDU5YjAiLCJ1bmlxdWVfbmFtZSI6Im5vdC1leGlzdGluZy1yYWRpeC1lbWFpbEBlcXVpbm9yLmNvbSIsInVwbiI6Im5vdC1leGlzdGluZy10ZXN0LXJhZGl4LWVtYWlsQGVxdWlub3IuY29tIiwidXRpIjoiQlMxMmFzR2R1RXlyZUVjRGN2aDJBRyIsInZlciI6IjEuMCJ9.EB5z7Mk34NkFPCP8MqaNMo4UeWgNyO4-qEmzOVPxfoBqbgA16Ar4xeONXODwjZn9iD-CwJccusW6GP0xZ_PJHBFpfaJO_tLaP1k0KhT-eaANt112TvDBt0yjHtJg6He6CEDqagREIsH3w1mSm40zWLKGZeRLdnGxnQyKsTmNJ1rFRdY3AyoEgf6-pnJweUt0LaFMKmIJ2HornStm2hjUstBaji_5cSS946zqp4tgrc-RzzDuaQXzqlVL2J22SR2S_Oux_3yw88KmlhEFFP9axNcbjZrzW3L9XWnPT6UzVIaVRaNRSWfqDATg-jeHg4Gm1bp8w0aIqLdDxc9CfFMjuQ" } -func (p *TestPrincipal) Name() string { return "test" } +func (p *TestPrincipal) Id() string { return "anon." } func (p *TestPrincipal) IsAuthenticated() bool { return true } func NewTestPrincipal() *TestPrincipal { diff --git a/api/test/utils.go b/api/test/utils.go index f69c6347..78626817 100644 --- a/api/test/utils.go +++ b/api/test/utils.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" + token "github.com/equinor/radix-api/api/utils/authn" authnmock "github.com/equinor/radix-api/api/utils/authn/mock" kedav2 "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" @@ -36,7 +37,7 @@ type Utils struct { secretProviderClient *secretsstorevclientfake.Clientset certClient *certclientfake.Clientset controllers []models.Controller - validatorMock *authnmock.MockValidatorInterface + validator token.ValidatorInterface } const radixDNSZone = "fakezone.radix.com" @@ -50,27 +51,39 @@ func NewTestUtils(kubeClient *kubernetesfake.Clientset, radixClient *radixclient secretProviderClient: secretProviderClient, certClient: certClient, controllers: controllers, - validatorMock: validator, + validator: validator, } } // ExecuteRequest Helper method to issue a http request -func (tu *Utils) ExecuteRequest(method, endpoint string) <-chan *httptest.ResponseRecorder { - return tu.ExecuteRequestWithParameters(method, endpoint, nil) +func (tu *Utils) ExecuteRequest(method, endpoint string, overrideOtions ...OverrideOption) <-chan *httptest.ResponseRecorder { + return tu.ExecuteRequestWithParameters(method, endpoint, nil, overrideOtions...) } -func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string) <-chan *httptest.ResponseRecorder { +type OverrideOption func(Utils) Utils + +func WithValidatorOverride(validator token.ValidatorInterface) OverrideOption { + return func(utils Utils) Utils { + utils.validator = validator + return utils + } +} + +func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string, overrideOtions ...OverrideOption) <-chan *httptest.ResponseRecorder { var reader io.Reader req, _ := http.NewRequest(method, endpoint, reader) - // TODO: Implement options to override validation + u := *tu + for _, overrideFn := range overrideOtions { + u = overrideFn(u) + } response := make(chan *httptest.ResponseRecorder) go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", tu.validatorMock, "", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler("anyClusterName", u.validator, "", NewKubeUtilMock(u.kubeClient, u.radixClient, u.kedaClient, u.secretProviderClient, u.certClient), u.controllers...).ServeHTTP(rr, req) response <- rr }() @@ -78,7 +91,7 @@ func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string) <-chan *htt } // ExecuteRequestWithParameters Helper method to issue a http request with payload -func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameters interface{}) <-chan *httptest.ResponseRecorder { +func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameters interface{}, overrideOtions ...OverrideOption) <-chan *httptest.ResponseRecorder { var reader io.Reader if parameters != nil { @@ -90,11 +103,16 @@ func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameter req.Header.Add("Authorization", getFakeToken()) req.Header.Add("Accept", "application/json") + u := *tu + for _, overrideFn := range overrideOtions { + u = overrideFn(u) + } + response := make(chan *httptest.ResponseRecorder) go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", tu.validatorMock, radixDNSZone, NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler("anyClusterName", u.validator, radixDNSZone, NewKubeUtilMock(u.kubeClient, u.radixClient, u.kedaClient, u.secretProviderClient, u.certClient), u.controllers...).ServeHTTP(rr, req) response <- rr }() diff --git a/api/utils/authn/anon_principal.go b/api/utils/authn/anon_principal.go index 7356a6f9..85a04ad7 100644 --- a/api/utils/authn/anon_principal.go +++ b/api/utils/authn/anon_principal.go @@ -3,7 +3,7 @@ package token type AnonPrincipal struct{} func (p *AnonPrincipal) Token() string { return "" } -func (p *AnonPrincipal) Name() string { return "anonymous" } +func (p *AnonPrincipal) Id() string { return "anonymous" } func (p *AnonPrincipal) IsAuthenticated() bool { return false } func NewAnonymousPrincipal() *AnonPrincipal { diff --git a/api/utils/authn/azure_principal.go b/api/utils/authn/azure_principal.go index e453e13a..8d99c693 100644 --- a/api/utils/authn/azure_principal.go +++ b/api/utils/authn/azure_principal.go @@ -7,8 +7,7 @@ import ( ) type azureClaims struct { - AppId string `json:"appid,omitempty"` - Upn string `json:"upn,omitempty"` + ObjectId string `json:"oid,omitempty"` } func (c *azureClaims) Validate(_ context.Context) error { @@ -27,14 +26,4 @@ func (p *AzurePrincipal) Token() string { func (p *AzurePrincipal) IsAuthenticated() bool { return true } -func (p *AzurePrincipal) Name() string { - if p.azureClaims.Upn != "" { - return p.azureClaims.Upn - } - - if p.azureClaims.AppId != "" { - return p.azureClaims.AppId - } - - return p.claims.RegisteredClaims.Subject -} +func (p *AzurePrincipal) Id() string { return p.azureClaims.ObjectId } diff --git a/api/utils/authn/mock/validator_mock.go b/api/utils/authn/mock/validator_mock.go index ef25e140..593eb3dc 100644 --- a/api/utils/authn/mock/validator_mock.go +++ b/api/utils/authn/mock/validator_mock.go @@ -35,32 +35,32 @@ func (m *MockTokenPrincipal) EXPECT() *MockTokenPrincipalMockRecorder { return m.recorder } -// IsAuthenticated mocks base method. -func (m *MockTokenPrincipal) IsAuthenticated() bool { +// Id mocks base method. +func (m *MockTokenPrincipal) Id() string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsAuthenticated") - ret0, _ := ret[0].(bool) + ret := m.ctrl.Call(m, "Id") + ret0, _ := ret[0].(string) return ret0 } -// IsAuthenticated indicates an expected call of IsAuthenticated. -func (mr *MockTokenPrincipalMockRecorder) IsAuthenticated() *gomock.Call { +// Id indicates an expected call of Id. +func (mr *MockTokenPrincipalMockRecorder) Id() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAuthenticated", reflect.TypeOf((*MockTokenPrincipal)(nil).IsAuthenticated)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockTokenPrincipal)(nil).Id)) } -// Name mocks base method. -func (m *MockTokenPrincipal) Name() string { +// IsAuthenticated mocks base method. +func (m *MockTokenPrincipal) IsAuthenticated() bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Name") - ret0, _ := ret[0].(string) + ret := m.ctrl.Call(m, "IsAuthenticated") + ret0, _ := ret[0].(bool) return ret0 } -// Name indicates an expected call of Name. -func (mr *MockTokenPrincipalMockRecorder) Name() *gomock.Call { +// IsAuthenticated indicates an expected call of IsAuthenticated. +func (mr *MockTokenPrincipalMockRecorder) IsAuthenticated() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockTokenPrincipal)(nil).Name)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAuthenticated", reflect.TypeOf((*MockTokenPrincipal)(nil).IsAuthenticated)) } // Token mocks base method. diff --git a/api/utils/authn/validator.go b/api/utils/authn/validator.go index c417a294..280677f4 100644 --- a/api/utils/authn/validator.go +++ b/api/utils/authn/validator.go @@ -2,18 +2,18 @@ package token import ( "context" - "errors" "net/url" "time" "github.com/auth0/go-jwt-middleware/v2/jwks" "github.com/auth0/go-jwt-middleware/v2/validator" + "github.com/equinor/radix-common/net/http" ) type TokenPrincipal interface { IsAuthenticated() bool Token() string - Name() string + Id() string } type ValidatorInterface interface { @@ -27,7 +27,7 @@ type Validator struct { var _ ValidatorInterface = &Validator{} func NewValidator(issuerUrl *url.URL, audience string) (*Validator, error) { - provider := jwks.NewCachingProvider(issuerUrl, 5*time.Minute) + provider := jwks.NewCachingProvider(issuerUrl, 5*time.Hour) validator, err := validator.New( provider.KeyFunc, @@ -53,12 +53,12 @@ func (v *Validator) ValidateToken(ctx context.Context, token string) (TokenPrinc claims, ok := validateToken.(*validator.ValidatedClaims) if !ok { - return nil, errors.New("invalid token") + return nil, http.ForbiddenError("invalid token") } azureClaims, ok := claims.CustomClaims.(*azureClaims) if !ok { - return nil, errors.New("invalid azure token") + return nil, http.ForbiddenError("invalid azure token") } principal := &AzurePrincipal{token: token, claims: claims, azureClaims: azureClaims} From 367eb38faddf689080b76252c6ebaa12a4964400 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 2 Oct 2024 08:24:48 +0200 Subject: [PATCH 08/29] Add tests --- Makefile | 2 +- .../applications_controller_test.go | 2 +- api/buildsecrets/buildsecrets_test.go | 2 +- .../build_status_controller_test.go | 2 +- api/deployments/deployment_controller_test.go | 2 +- .../environment_controller_test.go | 2 +- .../env_vars_controller_test.go | 2 +- api/jobs/job_controller_test.go | 2 +- api/middleware/auth/authentication.go | 2 +- api/middleware/auth/authentication_test.go | 88 ++++++++++++------- api/router/api.go | 2 +- api/secrets/secret_controller_test.go | 2 +- api/test/utils.go | 35 ++------ api/utils/{authn => token}/anon_principal.go | 0 api/utils/{authn => token}/azure_principal.go | 0 .../{authn => token}/mock/validator_mock.go | 4 +- api/utils/{authn => token}/validator.go | 2 + api/utils/token/validator_test.go | 32 +++++++ main.go | 2 +- 19 files changed, 112 insertions(+), 73 deletions(-) rename api/utils/{authn => token}/anon_principal.go (100%) rename api/utils/{authn => token}/azure_principal.go (100%) rename api/utils/{authn => token}/mock/validator_mock.go (97%) rename api/utils/{authn => token}/validator.go (96%) create mode 100644 api/utils/token/validator_test.go diff --git a/Makefile b/Makefile index 6e74f912..f744a3da 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ mocks: bootstrap mockgen -source ./api/events/event_handler.go -destination ./api/events/mock/event_handler_mock.go -package mock mockgen -source ./api/environmentvariables/env_vars_handler.go -destination ./api/environmentvariables/env_vars_handler_mock.go -package environmentvariables mockgen -source ./api/environmentvariables/env_vars_handler_factory.go -destination ./api/environmentvariables/env_vars_handler_factory_mock.go -package environmentvariables - mockgen -source ./api/utils/authn/validator.go -destination ./api/utils/authn/mock/validator_mock.go -package mock + mockgen -source ./api/utils/token/validator.go -destination ./api/utils/token/mock/validator_mock.go -package mock .PHONY: test test: diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 12bed4fd..94d7a00c 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -18,7 +18,7 @@ import ( jobModels "github.com/equinor/radix-api/api/jobs/models" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/utils" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/equinor/radix-api/internal/config" "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" diff --git a/api/buildsecrets/buildsecrets_test.go b/api/buildsecrets/buildsecrets_test.go index aec5e965..9a17aff2 100644 --- a/api/buildsecrets/buildsecrets_test.go +++ b/api/buildsecrets/buildsecrets_test.go @@ -6,7 +6,7 @@ import ( "testing" environmentModels "github.com/equinor/radix-api/api/secrets/models" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/golang/mock/gomock" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" "github.com/stretchr/testify/require" diff --git a/api/buildstatus/build_status_controller_test.go b/api/buildstatus/build_status_controller_test.go index ebf6426f..91bed53f 100644 --- a/api/buildstatus/build_status_controller_test.go +++ b/api/buildstatus/build_status_controller_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" "github.com/stretchr/testify/require" secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake" diff --git a/api/deployments/deployment_controller_test.go b/api/deployments/deployment_controller_test.go index 7b42e4d5..3a4c266d 100644 --- a/api/deployments/deployment_controller_test.go +++ b/api/deployments/deployment_controller_test.go @@ -8,7 +8,7 @@ import ( "time" certfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/equinor/radix-operator/pkg/apis/kube" "github.com/golang/mock/gomock" kedav2 "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index 67b67479..453ee939 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -20,7 +20,7 @@ import ( "github.com/equinor/radix-api/api/secrets/suffix" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/utils" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/equinor/radix-api/models" radixmodels "github.com/equinor/radix-common/models" radixhttp "github.com/equinor/radix-common/net/http" diff --git a/api/environmentvariables/env_vars_controller_test.go b/api/environmentvariables/env_vars_controller_test.go index bd01c6f6..930211df 100644 --- a/api/environmentvariables/env_vars_controller_test.go +++ b/api/environmentvariables/env_vars_controller_test.go @@ -9,7 +9,7 @@ import ( certclientfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" envvarsmodels "github.com/equinor/radix-api/api/environmentvariables/models" controllertest "github.com/equinor/radix-api/api/test" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/equinor/radix-operator/pkg/apis/config" "github.com/equinor/radix-operator/pkg/apis/deployment" "github.com/equinor/radix-operator/pkg/apis/kube" diff --git a/api/jobs/job_controller_test.go b/api/jobs/job_controller_test.go index b95ae029..7f1b4b4c 100644 --- a/api/jobs/job_controller_test.go +++ b/api/jobs/job_controller_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/equinor/radix-common/utils/pointers" "github.com/golang/mock/gomock" kedav2 "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index a7cc24c1..0c933434 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - token "github.com/equinor/radix-api/api/utils/authn" + token "github.com/equinor/radix-api/api/utils/token" "github.com/equinor/radix-common/models" radixhttp "github.com/equinor/radix-common/net/http" "github.com/rs/zerolog/log" diff --git a/api/middleware/auth/authentication_test.go b/api/middleware/auth/authentication_test.go index 5df407e1..197dc4ff 100644 --- a/api/middleware/auth/authentication_test.go +++ b/api/middleware/auth/authentication_test.go @@ -2,6 +2,7 @@ package auth_test import ( "context" + "errors" "fmt" "net/http" "os" @@ -10,9 +11,11 @@ import ( certfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" "github.com/equinor/radix-api/api/applications" applicationModels "github.com/equinor/radix-api/api/applications/models" + "github.com/equinor/radix-api/api/buildstatus" controllertest "github.com/equinor/radix-api/api/test" - token "github.com/equinor/radix-api/api/utils/authn" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + "github.com/equinor/radix-api/api/test/mock" + token "github.com/equinor/radix-api/api/utils/token" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/equinor/radix-api/internal/config" "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" @@ -38,16 +41,9 @@ const ( subscriptionId = "12347718-c8f8-4995-bfbb-02655ff1f89c" ) -func setupTest(t *testing.T, requireAppConfigurationItem, requireAppADGroups bool) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *radixfake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { - return setupTestWithFactory(t, newTestApplicationHandlerFactory( - config.Config{RequireAppConfigurationItem: requireAppConfigurationItem, RequireAppADGroups: requireAppADGroups}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }, - )) -} -func setupTestWithFactory(t *testing.T, handlerFactory applications.ApplicationHandlerFactory) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *radixfake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { +func setupTest(t *testing.T, validator *authnmock.MockValidatorInterface, buildStatusMock *mock.MockPipelineBadge) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *radixfake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { // Setup + ctrl := gomock.NewController(t) kubeclient := kubefake.NewSimpleClientset() radixclient := radixfake.NewSimpleClientset() kedaClient := kedafake.NewSimpleClientset() @@ -62,21 +58,36 @@ func setupTestWithFactory(t *testing.T, handlerFactory applications.ApplicationH _ = os.Setenv(defaults.ActiveClusternameEnvironmentVariable, clusterName) // controllerTestUtils is used for issuing HTTP request and processing responses - mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + + if buildStatusMock == nil { + buildStatusMock = mock.NewMockPipelineBadge(ctrl) + buildStatusMock.EXPECT().GetBadge(gomock.Any(), gomock.Any()).Return([]byte("hello world"), errors.New("error")).AnyTimes() + } + + if validator == nil { + validator = authnmock.NewMockValidatorInterface(gomock.NewController(t)) + validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(controllertest.NewTestPrincipal(), nil) + } + controllerTestUtils := controllertest.NewTestUtils( kubeclient, radixclient, kedaClient, secretproviderclient, certClient, - mockValidator, + validator, applications.NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil }, - handlerFactory, + newTestApplicationHandlerFactory( + config.Config{}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }, + ), ), + buildstatus.NewBuildStatusController(buildStatusMock), ) return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, prometheusclient, secretproviderclient, certClient @@ -99,18 +110,15 @@ func (f *testApplicationHandlerFactory) Create(accounts models.Accounts) applica return applications.NewApplicationHandler(accounts, f.config, f.hasAccessToGetConfigMap) } -func TestGetApplications_Authenticated(t *testing.T) { +func TestGetApplications_AuthenticatedRequestIsOk(t *testing.T) { // Setup - commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, true, true) + commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, nil, nil) _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) require.NoError(t, err) // Test - mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(controllertest.NewTestPrincipal(), nil) - - responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications"), controllertest.WithValidatorOverride(mockValidator)) + responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications")) response := <-responseChannel applications := make([]applicationModels.ApplicationSummary, 0) @@ -119,35 +127,51 @@ func TestGetApplications_Authenticated(t *testing.T) { assert.Equal(t, 1, len(applications)) } -func TestGetApplications_Unauthenticated(t *testing.T) { +func TestGetBuildStatus_AnonymousRequestIsOk(t *testing.T) { // Setup - commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, true, true) - _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) + ctrl := gomock.NewController(t) + mockValidator := authnmock.NewMockValidatorInterface(ctrl) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Return(token.NewAnonymousPrincipal(), nil).Times(0) + buildStatusMock := mock.NewMockPipelineBadge(ctrl) + buildStatusMock.EXPECT().GetBadge(gomock.Any(), gomock.Any()).Return([]byte("hello world"), errors.New("error")).Times(1) + commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, mockValidator, buildStatusMock) + _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration().WithName("anyapp")) require.NoError(t, err) // Test + responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", fmt.Sprintf("/api/v1/applications/anyapp/environments/qa/buildstatus")) + <-responseChannel + ctrl.Finish() // We expect buildStatusMock to be called 1 time, without auth middleware getting in the way +} + +func TestGetApplications_UnauthenticatedIsForbidden(t *testing.T) { + // Setup mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(token.NewAnonymousPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(token.NewAnonymousPrincipal(), radixhttp.ForbiddenError("invalid token")) + commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, mockValidator, nil) + _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) + require.NoError(t, err) - responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications"), controllertest.WithValidatorOverride(mockValidator)) + // Test + + responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", fmt.Sprintf("/api/v1/applications")) response := <-responseChannel assert.Equal(t, http.StatusForbidden, response.Code) } -func TestGetApplications_InvalidToken(t *testing.T) { +func TestGetApplications_InvalidTokenIsForbidden(t *testing.T) { // Setup - commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, true, true) + mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(token.NewAnonymousPrincipal(), radixhttp.ForbiddenError("invalid token")) + commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, mockValidator, nil) _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) require.NoError(t, err) // Test - mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(token.NewAnonymousPrincipal(), radixhttp.ForbiddenError("invalid token")) - - responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications"), controllertest.WithValidatorOverride(mockValidator)) + responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", fmt.Sprintf("/api/v1/applications")) response := <-responseChannel assert.Equal(t, http.StatusForbidden, response.Code) diff --git a/api/router/api.go b/api/router/api.go index 347231d4..dbac7183 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -8,7 +8,7 @@ import ( "github.com/equinor/radix-api/api/middleware/logger" "github.com/equinor/radix-api/api/middleware/recovery" "github.com/equinor/radix-api/api/utils" - token "github.com/equinor/radix-api/api/utils/authn" + "github.com/equinor/radix-api/api/utils/token" "github.com/equinor/radix-api/models" "github.com/equinor/radix-api/swaggerui" "github.com/gorilla/mux" diff --git a/api/secrets/secret_controller_test.go b/api/secrets/secret_controller_test.go index 7b9f2259..8db342a0 100644 --- a/api/secrets/secret_controller_test.go +++ b/api/secrets/secret_controller_test.go @@ -10,9 +10,9 @@ import ( certclientfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" secretModels "github.com/equinor/radix-api/api/secrets/models" controllertest "github.com/equinor/radix-api/api/test" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" "github.com/equinor/radix-api/api/utils/tlsvalidation" tlsvalidationmock "github.com/equinor/radix-api/api/utils/tlsvalidation/mock" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" radixhttp "github.com/equinor/radix-common/net/http" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" commontest "github.com/equinor/radix-operator/pkg/apis/test" diff --git a/api/test/utils.go b/api/test/utils.go index 78626817..bebc4a1b 100644 --- a/api/test/utils.go +++ b/api/test/utils.go @@ -7,8 +7,8 @@ import ( "net/http" "net/http/httptest" - token "github.com/equinor/radix-api/api/utils/authn" - authnmock "github.com/equinor/radix-api/api/utils/authn/mock" + token "github.com/equinor/radix-api/api/utils/token" + authnmock "github.com/equinor/radix-api/api/utils/token/mock" kedav2 "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" "github.com/rs/zerolog/log" @@ -56,34 +56,20 @@ func NewTestUtils(kubeClient *kubernetesfake.Clientset, radixClient *radixclient } // ExecuteRequest Helper method to issue a http request -func (tu *Utils) ExecuteRequest(method, endpoint string, overrideOtions ...OverrideOption) <-chan *httptest.ResponseRecorder { - return tu.ExecuteRequestWithParameters(method, endpoint, nil, overrideOtions...) +func (tu *Utils) ExecuteRequest(method, endpoint string) <-chan *httptest.ResponseRecorder { + return tu.ExecuteRequestWithParameters(method, endpoint, nil) } -type OverrideOption func(Utils) Utils - -func WithValidatorOverride(validator token.ValidatorInterface) OverrideOption { - return func(utils Utils) Utils { - utils.validator = validator - return utils - } -} - -func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string, overrideOtions ...OverrideOption) <-chan *httptest.ResponseRecorder { +func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string) <-chan *httptest.ResponseRecorder { var reader io.Reader req, _ := http.NewRequest(method, endpoint, reader) - u := *tu - for _, overrideFn := range overrideOtions { - u = overrideFn(u) - } - response := make(chan *httptest.ResponseRecorder) go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", u.validator, "", NewKubeUtilMock(u.kubeClient, u.radixClient, u.kedaClient, u.secretProviderClient, u.certClient), u.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler("anyClusterName", tu.validator, "", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) response <- rr }() @@ -91,7 +77,7 @@ func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string, overrideOti } // ExecuteRequestWithParameters Helper method to issue a http request with payload -func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameters interface{}, overrideOtions ...OverrideOption) <-chan *httptest.ResponseRecorder { +func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameters interface{}) <-chan *httptest.ResponseRecorder { var reader io.Reader if parameters != nil { @@ -103,16 +89,11 @@ func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameter req.Header.Add("Authorization", getFakeToken()) req.Header.Add("Accept", "application/json") - u := *tu - for _, overrideFn := range overrideOtions { - u = overrideFn(u) - } - response := make(chan *httptest.ResponseRecorder) go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", u.validator, radixDNSZone, NewKubeUtilMock(u.kubeClient, u.radixClient, u.kedaClient, u.secretProviderClient, u.certClient), u.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler("anyClusterName", tu.validator, radixDNSZone, NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) response <- rr }() diff --git a/api/utils/authn/anon_principal.go b/api/utils/token/anon_principal.go similarity index 100% rename from api/utils/authn/anon_principal.go rename to api/utils/token/anon_principal.go diff --git a/api/utils/authn/azure_principal.go b/api/utils/token/azure_principal.go similarity index 100% rename from api/utils/authn/azure_principal.go rename to api/utils/token/azure_principal.go diff --git a/api/utils/authn/mock/validator_mock.go b/api/utils/token/mock/validator_mock.go similarity index 97% rename from api/utils/authn/mock/validator_mock.go rename to api/utils/token/mock/validator_mock.go index 593eb3dc..e4f3ba1b 100644 --- a/api/utils/authn/mock/validator_mock.go +++ b/api/utils/token/mock/validator_mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: ./api/utils/authn/validator.go +// Source: ./api/utils/token/validator.go // Package mock is a generated GoMock package. package mock @@ -8,7 +8,7 @@ import ( context "context" reflect "reflect" - token "github.com/equinor/radix-api/api/utils/authn" + token "github.com/equinor/radix-api/api/utils/token" gomock "github.com/golang/mock/gomock" ) diff --git a/api/utils/authn/validator.go b/api/utils/token/validator.go similarity index 96% rename from api/utils/authn/validator.go rename to api/utils/token/validator.go index 280677f4..41ba1de9 100644 --- a/api/utils/authn/validator.go +++ b/api/utils/token/validator.go @@ -26,6 +26,8 @@ type Validator struct { 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) diff --git a/api/utils/token/validator_test.go b/api/utils/token/validator_test.go new file mode 100644 index 00000000..8df2854f --- /dev/null +++ b/api/utils/token/validator_test.go @@ -0,0 +1,32 @@ +package token + +import ( + "context" + "net/url" + "testing" + + "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) + v, err := NewValidator(issuer, test_oidc_audience) + assert.NoError(t, err) + assert.NotNil(t, v) +} + +func TestValidateToken(t *testing.T) { + issuer, _ := url.Parse(test_oidc_issuer) + v, err := NewValidator(issuer, test_oidc_audience) + require.NoError(t, err) + + _, err = v.ValidateToken(context.Background(), test_jwt) + assert.Error(t, err) +} diff --git a/main.go b/main.go index faec4668..da0b4f38 100644 --- a/main.go +++ b/main.go @@ -15,8 +15,8 @@ import ( "time" "github.com/equinor/radix-api/api/secrets" - token "github.com/equinor/radix-api/api/utils/authn" "github.com/equinor/radix-api/api/utils/tlsvalidation" + token "github.com/equinor/radix-api/api/utils/token" "github.com/equinor/radix-api/internal/config" "github.com/rs/zerolog" "github.com/rs/zerolog/log" From 43ec098e5577807bb5e652b9f819e3ea8fa9f639 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 2 Oct 2024 08:49:10 +0200 Subject: [PATCH 09/29] Fix merge conflicts --- .../applications_controller_test.go | 80 +++++++++++-------- api/middleware/auth/authentication_test.go | 4 + go.mod | 2 +- internal/config/config.go | 1 + main.go | 1 - 5 files changed, 51 insertions(+), 37 deletions(-) diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 1f57f611..f890c7e7 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -92,8 +92,8 @@ func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return true, nil - }, handlerFactory, prometheusHandlerMock), + return true, nil + }, handlerFactory, prometheusHandlerMock), ) return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, prometheusclient, secretproviderclient, certClient @@ -133,11 +133,11 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return false, nil + return false, nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }), prometheusHandlerMock)) + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications") response := <-responseChannel @@ -153,10 +153,10 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, rr v1.RadixRegistration) (bool, error) { - return rr.GetName() == "my-second-app", nil + return rr.GetName() == "my-second-app", nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil }), prometheusHandlerMock)) responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications") response := <-responseChannel @@ -173,11 +173,11 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return true, nil - }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil - }), prometheusHandlerMock)) + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications") response := <-responseChannel @@ -257,11 +257,11 @@ func TestSearchApplicationsPost(t *testing.T) { mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return true, nil - }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil - }), prometheusHandlerMock)) + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) // Tests t.Run("search for "+appNames[0], func(t *testing.T) { @@ -342,11 +342,11 @@ func TestSearchApplicationsPost(t *testing.T) { mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return false, nil + return false, nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }), prometheusHandlerMock)) + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) params := applicationModels.ApplicationsSearchRequest{Names: []string{appNames[0]}} responseChannel := controllerTestUtils.ExecuteRequestWithParameters("POST", "/api/v1/applications/_search", ¶ms) response := <-responseChannel @@ -441,11 +441,11 @@ func TestSearchApplicationsGet(t *testing.T) { mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return true, nil - }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { return true, nil - }), prometheusHandlerMock)) + }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) // Tests t.Run("search for "+appNames[0], func(t *testing.T) { @@ -516,11 +516,11 @@ func TestSearchApplicationsGet(t *testing.T) { mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return false, nil + return false, nil }, newTestApplicationHandlerFactory(config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }), prometheusHandlerMock)) + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }), prometheusHandlerMock)) params := "apps=" + appNames[0] responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications/_search?"+params) response := <-responseChannel @@ -2021,13 +2021,23 @@ func Test_GetUsedResources(t *testing.T) { Times(1). Return(ts.expectedUsedResources, ts.expectedUsedResourcesError) } + validator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(controllertest.NewTestPrincipal(), nil) prometheusHandlerMock := createPrometheusHandlerMock(t, radixClient, &mockHandlerModifier) - controllerTestUtils := controllertest.NewTestUtils(kubeClient, radixClient, kedaClient, secretProviderClient, certClient, NewApplicationController(func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return true, nil - }, newTestApplicationHandlerFactory(ApplicationHandlerConfig{RequireAppConfigurationItem: true, RequireAppADGroups: true}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }), prometheusHandlerMock)) + controllerTestUtils := controllertest.NewTestUtils(kubeClient, radixClient, kedaClient, secretProviderClient, certClient, validator, + NewApplicationController( + func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { + return true, nil + }, + newTestApplicationHandlerFactory( + config.Config{RequireAppConfigurationItem: true, RequireAppADGroups: true}, + func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { + return true, nil + }, + ), + prometheusHandlerMock, + ), + ) responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/resources%s", appName1, ts.queryString)) response := <-responseChannel diff --git a/api/middleware/auth/authentication_test.go b/api/middleware/auth/authentication_test.go index 197dc4ff..d82999d4 100644 --- a/api/middleware/auth/authentication_test.go +++ b/api/middleware/auth/authentication_test.go @@ -12,6 +12,7 @@ import ( "github.com/equinor/radix-api/api/applications" applicationModels "github.com/equinor/radix-api/api/applications/models" "github.com/equinor/radix-api/api/buildstatus" + metricsMock "github.com/equinor/radix-api/api/metrics/mock" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/test/mock" token "github.com/equinor/radix-api/api/utils/token" @@ -58,6 +59,8 @@ func setupTest(t *testing.T, validator *authnmock.MockValidatorInterface, buildS _ = os.Setenv(defaults.ActiveClusternameEnvironmentVariable, clusterName) // controllerTestUtils is used for issuing HTTP request and processing responses + mockPrometheusHandler := metricsMock.NewMockPrometheusHandler(ctrl) + mockPrometheusHandler.EXPECT().GetUsedResources(gomock.Any(), radixclient, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&applicationModels.UsedResources{}, nil) if buildStatusMock == nil { buildStatusMock = mock.NewMockPipelineBadge(ctrl) @@ -86,6 +89,7 @@ func setupTest(t *testing.T, validator *authnmock.MockValidatorInterface, buildS return true, nil }, ), + mockPrometheusHandler, ), buildstatus.NewBuildStatusController(buildStatusMock), ) diff --git a/go.mod b/go.mod index afcea631..2657bd5c 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 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 @@ -66,7 +67,6 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-containerregistry v0.16.1 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect diff --git a/internal/config/config.go b/internal/config/config.go index 27141f9a..377315b8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,7 @@ type Config struct { RequireAppADGroups bool `envconfig:"REQUIRE_APP_AD_GROUPS" default:"true"` AppName string `envconfig:"RADIX_APP" required:"true"` EnvironmentName string `envconfig:"RADIX_ENVIRONMENT" required:"true"` + PrometheusUrl string `envconfig:"PROMETHEUS_URL" required:"true"` } func MustParse() Config { diff --git a/main.go b/main.go index 6a337046..a8b1cff0 100644 --- a/main.go +++ b/main.go @@ -160,7 +160,6 @@ func setupLogger(logLevelStr string, prettyPrint bool) { func getControllers(config config.Config) ([]models.Controller, error) { buildStatus := build_models.NewPipelineBadge() applicatinoFactory := applications.NewApplicationHandlerFactory(config) - // PrometheusUrl string `cfg:"prometheus_url" flag:"prometheus-url"` prometheusClient, err := metrics.NewPrometheusClient(config.PrometheusUrl) if err != nil { return nil, err From de031b2ce07b27d3985d4a7763e4f83c561f0dcb Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 2 Oct 2024 08:53:32 +0200 Subject: [PATCH 10/29] cleanup linting --- api/jobs/start_job_handler.go | 5 ----- api/middleware/auth/authentication.go | 1 - api/middleware/auth/authentication_test.go | 9 ++++----- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/api/jobs/start_job_handler.go b/api/jobs/start_job_handler.go index 85c28475..f8ae8c6f 100644 --- a/api/jobs/start_job_handler.go +++ b/api/jobs/start_job_handler.go @@ -20,11 +20,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - pipelineTagEnvironmentVariable = "PIPELINE_IMG_TAG" - tektonTagEnvironmentVariable = "TEKTON_IMG_TAG" -) - // HandleStartPipelineJob Handles the creation of a pipeline job for an application func (jh JobHandler) HandleStartPipelineJob(ctx context.Context, appName string, pipeline *pipelineJob.Definition, jobParameters *jobModels.JobParameters) (*jobModels.JobSummary, error) { radixRegistration, _ := jh.userAccount.RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index 0c933434..511f80d3 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -94,6 +94,5 @@ func CreateAuthorizeRequiredMiddleware() negroni.HandlerFunc { } next(w, r) - return } } diff --git a/api/middleware/auth/authentication_test.go b/api/middleware/auth/authentication_test.go index d82999d4..81c4b0a6 100644 --- a/api/middleware/auth/authentication_test.go +++ b/api/middleware/auth/authentication_test.go @@ -3,7 +3,6 @@ package auth_test import ( "context" "errors" - "fmt" "net/http" "os" "testing" @@ -122,7 +121,7 @@ func TestGetApplications_AuthenticatedRequestIsOk(t *testing.T) { // Test - responseChannel := controllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications")) + responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications") response := <-responseChannel applications := make([]applicationModels.ApplicationSummary, 0) @@ -144,7 +143,7 @@ func TestGetBuildStatus_AnonymousRequestIsOk(t *testing.T) { // Test - responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", fmt.Sprintf("/api/v1/applications/anyapp/environments/qa/buildstatus")) + responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/anyapp/environments/qa/buildstatus") <-responseChannel ctrl.Finish() // We expect buildStatusMock to be called 1 time, without auth middleware getting in the way } @@ -159,7 +158,7 @@ func TestGetApplications_UnauthenticatedIsForbidden(t *testing.T) { // Test - responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", fmt.Sprintf("/api/v1/applications")) + responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications") response := <-responseChannel assert.Equal(t, http.StatusForbidden, response.Code) @@ -175,7 +174,7 @@ func TestGetApplications_InvalidTokenIsForbidden(t *testing.T) { // Test - responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", fmt.Sprintf("/api/v1/applications")) + responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications") response := <-responseChannel assert.Equal(t, http.StatusForbidden, response.Code) From d9e84b807197409a1b45c517ec99a4761d28115e Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 7 Oct 2024 09:59:04 +0200 Subject: [PATCH 11/29] migrate start job handler from jobs controller to application controller --- api/applications/applications_handler.go | 35 +++---- .../start_job_handler.go | 92 +++++++------------ api/jobs/job_controller_test.go | 13 +-- api/jobs/job_handler.go | 19 ++-- api/jobs/manage_job_handler.go | 33 +++++-- api/middleware/auth/authentication.go | 11 +++ api/test/test_principal.go | 1 + api/utils/token/anon_principal.go | 1 + api/utils/token/azure_principal.go | 21 ++++- api/utils/token/validator.go | 1 + internal/config/config.go | 14 +-- internal/flags/register.go | 71 -------------- models/accounts.go | 50 ---------- 13 files changed, 129 insertions(+), 233 deletions(-) rename api/{jobs => applications}/start_job_handler.go (53%) delete mode 100644 internal/flags/register.go diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index d6532116..f2e348d1 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -13,9 +13,10 @@ import ( applicationModels "github.com/equinor/radix-api/api/applications/models" "github.com/equinor/radix-api/api/deployments" "github.com/equinor/radix-api/api/environments" - job "github.com/equinor/radix-api/api/jobs" + jobController "github.com/equinor/radix-api/api/jobs" jobModels "github.com/equinor/radix-api/api/jobs/models" "github.com/equinor/radix-api/api/kubequery" + "github.com/equinor/radix-api/api/middleware/auth" apimodels "github.com/equinor/radix-api/api/models" "github.com/equinor/radix-api/internal/config" "github.com/equinor/radix-api/models" @@ -52,23 +53,27 @@ type HasAccessToGetConfigMapFunc func(ctx context.Context, kubeClient kubernetes // ApplicationHandler Instance variables type ApplicationHandler struct { - jobHandler job.JobHandler + jobHandler jobController.JobHandler environmentHandler environments.EnvironmentHandler accounts models.Accounts config config.Config namespace string hasAccessToGetConfigMap HasAccessToGetConfigMapFunc + tektonImageTag string + pipelineImageTag string } // NewApplicationHandler Constructor func NewApplicationHandler(accounts models.Accounts, config config.Config, hasAccessToGetConfigMap HasAccessToGetConfigMapFunc) ApplicationHandler { return ApplicationHandler{ - accounts: accounts, - jobHandler: job.Init(accounts, deployments.Init(accounts), config.PipelineImageTag, config.TektonImageTag), + jobHandler: jobController.Init(accounts, deployments.Init(accounts), config.PipelineImageTag, config.TektonImageTag), environmentHandler: environments.Init(environments.WithAccounts(accounts)), + accounts: accounts, config: config, namespace: getApiNamespace(config), hasAccessToGetConfigMap: hasAccessToGetConfigMap, + tektonImageTag: config.TektonImageTag, + pipelineImageTag: config.PipelineImageTag, } } @@ -130,11 +135,7 @@ func (ah *ApplicationHandler) RegisterApplication(ctx context.Context, applicati var err error application := applicationRegistrationRequest.ApplicationRegistration - - creator, err := ah.accounts.GetOriginator() - if err != nil { - return nil, err - } + creator := auth.GetOriginator(ctx) application.RadixConfigFullName = cleanFileFullName(application.RadixConfigFullName) if len(application.RadixConfigFullName) > 0 { @@ -443,7 +444,7 @@ func (ah *ApplicationHandler) TriggerPipelinePromote(ctx context.Context, appNam return nil, radixhttp.ValidationError("Radix Application Pipeline", "Deployment name, from environment and to environment are required for \"promote\" pipeline") } - log.Ctx(ctx).Info().Msgf("Creating promote pipeline job for %s using deployment %s from environment %s into environment %s", appName, deploymentName, fromEnvironment, toEnvironment) + log.Ctx(ctx).Info().Msgf("Creating promote pipeline jobController for %s using deployment %s from environment %s into environment %s", appName, deploymentName, fromEnvironment, toEnvironment) pipeline, err := jobPipeline.GetPipelineFromName("promote") if err != nil { @@ -458,7 +459,7 @@ func (ah *ApplicationHandler) TriggerPipelinePromote(ctx context.Context, appNam jobParameters := pipelineParameters.MapPipelineParametersPromoteToJobParameter() jobParameters.CommitID = radixDeployment.GetLabels()[kube.RadixCommitLabel] - jobSummary, err := ah.jobHandler.HandleStartPipelineJob(ctx, appName, pipeline, jobParameters) + jobSummary, err := HandleStartPipelineJob(ctx, ah.accounts.UserAccount.RadixClient, appName, ah.pipelineImageTag, ah.tektonImageTag, pipeline, jobParameters) if err != nil { return nil, err } @@ -498,7 +499,7 @@ func (ah *ApplicationHandler) TriggerPipelineDeploy(ctx context.Context, appName return nil, radixhttp.ValidationError("Radix Application Pipeline", "To environment is required for \"deploy\" pipeline") } - log.Ctx(ctx).Info().Msgf("Creating deploy pipeline job for %s into environment %s", appName, toEnvironment) + log.Ctx(ctx).Info().Msgf("Creating deploy pipeline jobController for %s into environment %s", appName, toEnvironment) pipeline, err := jobPipeline.GetPipelineFromName("deploy") if err != nil { @@ -507,7 +508,7 @@ func (ah *ApplicationHandler) TriggerPipelineDeploy(ctx context.Context, appName jobParameters := pipelineParameters.MapPipelineParametersDeployToJobParameter() - jobSummary, err := ah.jobHandler.HandleStartPipelineJob(ctx, appName, pipeline, jobParameters) + jobSummary, err := HandleStartPipelineJob(ctx, ah.accounts.UserAccount.RadixClient, appName, ah.pipelineImageTag, ah.tektonImageTag, pipeline, jobParameters) if err != nil { return nil, err } @@ -522,7 +523,7 @@ func (ah *ApplicationHandler) TriggerPipelineApplyConfig(ctx context.Context, ap return nil, err } - log.Ctx(ctx).Info().Msgf("Creating apply config pipeline job for %s", appName) + log.Ctx(ctx).Info().Msgf("Creating apply config pipeline jobController for %s", appName) pipeline, err := jobPipeline.GetPipelineFromName("apply-config") if err != nil { @@ -531,7 +532,7 @@ func (ah *ApplicationHandler) TriggerPipelineApplyConfig(ctx context.Context, ap jobParameters := pipelineParameters.MapPipelineParametersApplyConfigToJobParameter() - jobSummary, err := ah.jobHandler.HandleStartPipelineJob(ctx, appName, pipeline, jobParameters) + jobSummary, err := HandleStartPipelineJob(ctx, ah.accounts.UserAccount.RadixClient, appName, ah.pipelineImageTag, ah.tektonImageTag, pipeline, jobParameters) if err != nil { return nil, err } @@ -554,7 +555,7 @@ func (ah *ApplicationHandler) triggerPipelineBuildOrBuildDeploy(ctx context.Cont return nil, applicationModels.AppNameAndBranchAreRequiredForStartingPipeline() } - log.Ctx(ctx).Info().Msgf("Creating build pipeline job for %s on branch %s for commit %s", appName, branch, commitID) + log.Ctx(ctx).Info().Msgf("Creating build pipeline jobController for %s on branch %s for commit %s", appName, branch, commitID) radixRegistration, err := ah.getUserAccount().RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) if err != nil { @@ -582,7 +583,7 @@ func (ah *ApplicationHandler) triggerPipelineBuildOrBuildDeploy(ctx context.Cont log.Ctx(ctx).Info().Msgf("Creating build pipeline job for %s on branch %s for commit %s", appName, branch, commitID) - jobSummary, err := ah.jobHandler.HandleStartPipelineJob(ctx, appName, pipeline, jobParameters) + jobSummary, err := HandleStartPipelineJob(ctx, ah.accounts.UserAccount.RadixClient, appName, ah.pipelineImageTag, ah.tektonImageTag, pipeline, jobParameters) if err != nil { return nil, err } diff --git a/api/jobs/start_job_handler.go b/api/applications/start_job_handler.go similarity index 53% rename from api/jobs/start_job_handler.go rename to api/applications/start_job_handler.go index f8ae8c6f..897c32cf 100644 --- a/api/jobs/start_job_handler.go +++ b/api/applications/start_job_handler.go @@ -1,4 +1,4 @@ -package jobs +package applications import ( "context" @@ -6,44 +6,43 @@ import ( "strings" "time" - "github.com/equinor/radix-operator/pkg/apis/defaults" - "github.com/equinor/radix-operator/pkg/apis/radixvalidators" - "github.com/rs/zerolog/log" - + jobController "github.com/equinor/radix-api/api/jobs" jobModels "github.com/equinor/radix-api/api/jobs/models" - "github.com/equinor/radix-api/api/metrics" + "github.com/equinor/radix-api/api/middleware/auth" radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" pipelineJob "github.com/equinor/radix-operator/pkg/apis/pipeline" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + "github.com/equinor/radix-operator/pkg/apis/radixvalidators" k8sObjectUtils "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/equinor/radix-operator/pkg/client/clientset/versioned" + "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// HandleStartPipelineJob Handles the creation of a pipeline job for an application -func (jh JobHandler) HandleStartPipelineJob(ctx context.Context, appName string, pipeline *pipelineJob.Definition, jobParameters *jobModels.JobParameters) (*jobModels.JobSummary, error) { - radixRegistration, _ := jh.userAccount.RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) +// HandleStartPipelineJob Handles the creation of a pipeline jobController for an application +func HandleStartPipelineJob(ctx context.Context, radixClient versioned.Interface, appName, pipelineImageTag, tektonImageTag string, pipeline *pipelineJob.Definition, jobParameters *jobModels.JobParameters) (*jobModels.JobSummary, error) { + radixRegistration, _ := radixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) radixConfigFullName, err := getRadixConfigFullName(radixRegistration) if err != nil { return nil, err } - job := jh.buildPipelineJob(ctx, appName, radixRegistration.Spec.CloneURL, radixConfigFullName, pipeline, jobParameters) - return jh.createPipelineJob(ctx, appName, job) + job := buildPipelineJob(ctx, appName, radixRegistration.Spec.CloneURL, radixConfigFullName, pipelineImageTag, tektonImageTag, pipeline, jobParameters) + return createPipelineJob(ctx, radixClient, appName, job) } -func (jh JobHandler) createPipelineJob(ctx context.Context, appName string, job *v1.RadixJob) (*jobModels.JobSummary, error) { - log.Ctx(ctx).Info().Msgf("Starting job: %s, %s", job.GetName(), workerImage) +func createPipelineJob(ctx context.Context, radixClient versioned.Interface, appName string, job *v1.RadixJob) (*jobModels.JobSummary, error) { + log.Ctx(ctx).Info().Msgf("Starting jobController: %s, %s", job.GetName(), jobController.WorkerImage) appNamespace := k8sObjectUtils.GetAppNamespace(appName) - job, err := jh.userAccount.RadixClient.RadixV1().RadixJobs(appNamespace).Create(ctx, job, metav1.CreateOptions{}) + job, err := radixClient.RadixV1().RadixJobs(appNamespace).Create(ctx, job, metav1.CreateOptions{}) if err != nil { return nil, err } - metrics.AddJobTriggered(appName, string(job.Spec.PipeLineType)) - - log.Ctx(ctx).Info().Msgf("Started job: %s, %s", job.GetName(), workerImage) + log.Ctx(ctx).Info().Msgf("Started jobController: %s, %s", job.GetName(), jobController.WorkerImage) return jobModels.GetSummaryFromRadixJob(job), nil } @@ -57,8 +56,8 @@ func getRadixConfigFullName(radixRegistration *v1.RadixRegistration) (string, er return radixRegistration.Spec.RadixConfigFullName, nil } -func (jh JobHandler) buildPipelineJob(ctx context.Context, appName, cloneURL, radixConfigFullName string, pipeline *pipelineJob.Definition, jobSpec *jobModels.JobParameters) *v1.RadixJob { - jobName, imageTag := getUniqueJobName(workerImage) +func buildPipelineJob(ctx context.Context, appName, cloneURL, radixConfigFullName, pipelineImageTag, tektonImageTag string, pipeline *pipelineJob.Definition, jobSpec *jobModels.JobParameters) *v1.RadixJob { + jobName, imageTag := jobController.GetUniqueJobName() if len(jobSpec.ImageTag) > 0 { imageTag = jobSpec.ImageTag } @@ -67,10 +66,8 @@ func (jh JobHandler) buildPipelineJob(ctx context.Context, appName, cloneURL, ra var promoteSpec v1.RadixPromoteSpec var deploySpec v1.RadixDeploySpec - triggeredBy, err := jh.getTriggeredBy(jobSpec.TriggeredBy) - if err != nil { - log.Ctx(ctx).Warn().Msgf("failed to get triggeredBy: %v", err) - } + log.Ctx(ctx).Info().Msgf("Using %s pipeline image tag", pipelineImageTag) + log.Ctx(ctx).Info().Msgf("Using %s as tekton image tag", tektonImageTag) switch pipeline.Type { case v1.BuildDeploy, v1.Build: @@ -111,12 +108,12 @@ func (jh JobHandler) buildPipelineJob(ctx context.Context, appName, cloneURL, ra AppName: appName, CloneURL: cloneURL, PipeLineType: pipeline.Type, - PipelineImage: getPipelineTag(ctx, jh.pipelineImageTag), - TektonImage: getTektonTag(ctx, jh.tektonImageTag), + PipelineImage: pipelineImageTag, + TektonImage: tektonImageTag, Build: buildSpec, Promote: promoteSpec, Deploy: deploySpec, - TriggeredBy: triggeredBy, + TriggeredBy: getTriggeredBy(ctx, jobSpec.TriggeredBy), RadixConfigFullName: fmt.Sprintf("/workspace/%s", radixConfigFullName), }, } @@ -124,46 +121,19 @@ func (jh JobHandler) buildPipelineJob(ctx context.Context, appName, cloneURL, ra return &job } -func (jh JobHandler) getTriggeredBy(triggeredBy string) (string, error) { - if triggeredBy != "" && triggeredBy != "" { - return triggeredBy, nil - } - triggeredBy, err := jh.accounts.GetOriginator() - if err != nil { - return "", fmt.Errorf("failed to get originator: %w", err) - } - return triggeredBy, nil -} - -func getPipelineTag(ctx context.Context, pipelineImageTag string) string { - if pipelineImageTag == "" { - log.Ctx(ctx).Warn().Msg("No pipeline image tag defined. Using latest") - pipelineImageTag = "latest" - } else { - log.Ctx(ctx).Info().Msgf("Using %s pipeline image tag", pipelineImageTag) - } - return pipelineImageTag -} - -func getTektonTag(ctx context.Context, tektonImageTag string) string { - if tektonImageTag == "" { - log.Ctx(ctx).Warn().Msg("No tekton image tag defined. Using release-latest") - tektonImageTag = "release-latest" - } else { - log.Ctx(ctx).Info().Msgf("Using %s as tekton image tag", tektonImageTag) - } - return tektonImageTag -} - func getUniqueJobName(image string) (string, string) { var jobName []string + timestamp := time.Now().Format("20060102150405") randomStr := strings.ToLower(radixutils.RandString(5)) - jobName = append(jobName, image, "-", getCurrentTimestamp(), "-", randomStr) + jobName = append(jobName, image, "-", timestamp, "-", randomStr) return strings.Join(jobName, ""), randomStr } -func getCurrentTimestamp() string { - t := time.Now() - return t.Format("20060102150405") // YYYYMMDDHHMISS in Go +func getTriggeredBy(ctx context.Context, triggeredBy string) string { + if triggeredBy != "" && triggeredBy != "" { + return triggeredBy + } + + return auth.GetOriginator(ctx) } diff --git a/api/jobs/job_controller_test.go b/api/jobs/job_controller_test.go index 7f1b4b4c..3732cd06 100644 --- a/api/jobs/job_controller_test.go +++ b/api/jobs/job_controller_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/equinor/radix-api/api/applications" authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/equinor/radix-common/utils/pointers" "github.com/golang/mock/gomock" @@ -21,12 +22,9 @@ import ( "github.com/stretchr/testify/assert" certclientfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" - "github.com/equinor/radix-api/api/deployments" . "github.com/equinor/radix-api/api/jobs" jobmodels "github.com/equinor/radix-api/api/jobs/models" controllertest "github.com/equinor/radix-api/api/test" - "github.com/equinor/radix-api/models" - radixmodels "github.com/equinor/radix-common/models" commontest "github.com/equinor/radix-operator/pkg/apis/test" builders "github.com/equinor/radix-operator/pkg/apis/utils" radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" @@ -67,7 +65,7 @@ func setupTest(t *testing.T) (*commontest.Utils, *controllertest.Utils, kubernet func TestGetApplicationJob(t *testing.T) { // Setup - commonTestUtils, controllerTestUtils, client, radixclient, kedaClient, secretproviderclient, certClient := setupTest(t) + commonTestUtils, controllerTestUtils, client, radixclient, _, _, _ := setupTest(t) _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration(). WithName(anyAppName). @@ -82,11 +80,10 @@ func TestGetApplicationJob(t *testing.T) { OverrideUseBuildCache: pointers.Ptr(true), } - accounts := models.NewAccounts(client, radixclient, kedaClient, secretproviderclient, nil, certClient, client, radixclient, kedaClient, secretproviderclient, nil, certClient, "", radixmodels.Impersonation{}) - handler := Init(accounts, deployments.Init(accounts), "", "") - anyPipeline, _ := pipeline.GetPipelineFromName(anyPipelineName) - jobSummary, _ := handler.HandleStartPipelineJob(context.Background(), anyAppName, anyPipeline, jobParameters) + anyPipelineTagName := "latestPipelineImageTag" + anyTektonTagName := "latestTektonImageTag" + jobSummary, _ := applications.HandleStartPipelineJob(context.Background(), radixclient, anyAppName, anyPipelineTagName, anyTektonTagName, anyPipeline, jobParameters) _, err = createPipelinePod(client, builders.GetAppNamespace(anyAppName), jobSummary.Name) require.NoError(t, err) diff --git a/api/jobs/job_handler.go b/api/jobs/job_handler.go index e1e9e289..14c09776 100644 --- a/api/jobs/job_handler.go +++ b/api/jobs/job_handler.go @@ -8,12 +8,14 @@ import ( "github.com/equinor/radix-api/api/deployments" jobModels "github.com/equinor/radix-api/api/jobs/models" + "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-api/api/utils" "github.com/equinor/radix-api/api/utils/tekton" "github.com/equinor/radix-api/models" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/kube" + v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" crdUtils "github.com/equinor/radix-operator/pkg/apis/utils" pipelinev1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "golang.org/x/sync/errgroup" @@ -24,7 +26,7 @@ import ( ) const ( - workerImage = "radix-pipeline" + WorkerImage = "radix-pipeline" tektonRealNameAnnotation = "radix.equinor.com/tekton-pipeline-name" ) @@ -336,21 +338,14 @@ func (jh JobHandler) getDefinedJobs(ctx context.Context, appNames []string) ([]* } func (jh JobHandler) getJobs(ctx context.Context, appName string) ([]*jobModels.JobSummary, error) { - return jh.getJobsInNamespace(ctx, crdUtils.GetAppNamespace(appName)) -} - -func (jh JobHandler) getJobsInNamespace(ctx context.Context, namespace string) ([]*jobModels.JobSummary, error) { - jobList, err := jh.userAccount.RadixClient.RadixV1().RadixJobs(namespace).List(ctx, metav1.ListOptions{}) + jobs, err := kubequery.GetRadixJobs(ctx, jh.accounts.UserAccount.RadixClient, appName) if err != nil { return nil, err } - jobs := make([]*jobModels.JobSummary, len(jobList.Items)) - for i, job := range jobList.Items { - jobs[i] = jobModels.GetSummaryFromRadixJob(&job) - } - - return jobs, nil + return slice.Map(jobs, func(j v1.RadixJob) *jobModels.JobSummary { + return jobModels.GetSummaryFromRadixJob(&j) + }), nil } func (jh JobHandler) getLatestJobPerApplication(ctx context.Context, forApplications map[string]bool) (map[string]*jobModels.JobSummary, error) { diff --git a/api/jobs/manage_job_handler.go b/api/jobs/manage_job_handler.go index 3c0c6a9e..1586d059 100644 --- a/api/jobs/manage_job_handler.go +++ b/api/jobs/manage_job_handler.go @@ -3,11 +3,16 @@ package jobs import ( "context" "fmt" + "strings" + "time" jobModels "github.com/equinor/radix-api/api/jobs/models" "github.com/equinor/radix-api/api/kubequery" + "github.com/equinor/radix-api/api/middleware/auth" + radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/slice" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + k8sObjectUtils "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -60,9 +65,20 @@ func (jh JobHandler) RerunJob(ctx context.Context, appName, jobName string) erro return nil } +func (jh JobHandler) createPipelineJob(ctx context.Context, appName string, job *radixv1.RadixJob) (*jobModels.JobSummary, error) { + log.Ctx(ctx).Info().Msgf("Starting job: %s, %s", job.GetName(), WorkerImage) + appNamespace := k8sObjectUtils.GetAppNamespace(appName) + job, err := jh.userAccount.RadixClient.RadixV1().RadixJobs(appNamespace).Create(ctx, job, metav1.CreateOptions{}) + if err != nil { + return nil, err + } + + log.Ctx(ctx).Info().Msgf("Started job: %s, %s", job.GetName(), WorkerImage) + return jobModels.GetSummaryFromRadixJob(job), nil +} func (jh JobHandler) buildPipelineJobToRerunFrom(ctx context.Context, radixJob *radixv1.RadixJob) *radixv1.RadixJob { - rerunJobName, imageTag := getUniqueJobName(workerImage) + rerunJobName, imageTag := GetUniqueJobName() rerunRadixJob := radixv1.RadixJob{ ObjectMeta: metav1.ObjectMeta{ Name: rerunJobName, @@ -79,11 +95,7 @@ func (jh JobHandler) buildPipelineJobToRerunFrom(ctx context.Context, radixJob * rerunRadixJob.Spec.Build.ImageTag = imageTag } rerunRadixJob.Spec.Stop = false - triggeredBy, err := jh.getTriggeredBy("") - if err != nil { - log.Ctx(ctx).Warn().Msgf("failed to get triggeredBy: %v", err) - } - rerunRadixJob.Spec.TriggeredBy = triggeredBy + rerunRadixJob.Spec.TriggeredBy = auth.GetOriginator(ctx) return &rerunRadixJob } @@ -97,3 +109,12 @@ func (jh JobHandler) getPipelineJobByName(ctx context.Context, appName string, j } return radixJob, nil } + +func GetUniqueJobName() (string, string) { + var jobName []string + randomStr := strings.ToLower(radixutils.RandString(5)) + timestamp := time.Now().Format("20060102150405") + jobName = append(jobName, WorkerImage, "-", timestamp, "-", randomStr) + + return strings.Join(jobName, ""), randomStr +} diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index 511f80d3..4557fc92 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -80,6 +80,17 @@ func CtxImpersonation(ctx context.Context) models.Impersonation { return models.Impersonation{} } +func GetOriginator(ctx context.Context) string { + impersonation := CtxImpersonation(ctx) + principal := CtxTokenPrincipal(ctx) + + if impersonation.PerformImpersonation() { + return impersonation.User + } + + return principal.Name() +} + func CreateAuthorizeRequiredMiddleware() negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { logger := log.Ctx(r.Context()) diff --git a/api/test/test_principal.go b/api/test/test_principal.go index 378511e0..c276ed15 100644 --- a/api/test/test_principal.go +++ b/api/test/test_principal.go @@ -6,6 +6,7 @@ func (p *TestPrincipal) Token() string { return "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MjQ1YTJlYzEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xMjM0NTY3OC03NTY1LTIzNDItMjM0Mi0xMjM0MDViNDU5YjAvIiwiaWF0IjoxNTc1MzU1NTA4LCJuYmYiOjE1NzUzNTU1MDgsImV4cCI6MTU3NTM1OTQwOCwiYWNyIjoiMSIsImFpbyI6IjQyYXNkYXMiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDc5MDM5YTkwIiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJKb2huIiwiZ2l2ZW5fbmFtZSI6IkRvZSIsImhhc2dyb3VwcyI6InRydWUiLCJpcGFkZHIiOiIxMC4xMC4xMC4xMCIsIm5hbWUiOiJKb2huIERvZSIsIm9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzRmYzhmYTBlYSIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0xMjM0NTY3ODktMTIzNDU2OTc4MC0xMjM0NTY3ODktMTIzNDU2NyIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IjBoa2JpbEo3MTIzNHpSU3h6eHZiSW1hc2RmZ3N4amI2YXNkZmVOR2FzZGYiLCJ0aWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MDViNDU5YjAiLCJ1bmlxdWVfbmFtZSI6Im5vdC1leGlzdGluZy1yYWRpeC1lbWFpbEBlcXVpbm9yLmNvbSIsInVwbiI6Im5vdC1leGlzdGluZy10ZXN0LXJhZGl4LWVtYWlsQGVxdWlub3IuY29tIiwidXRpIjoiQlMxMmFzR2R1RXlyZUVjRGN2aDJBRyIsInZlciI6IjEuMCJ9.EB5z7Mk34NkFPCP8MqaNMo4UeWgNyO4-qEmzOVPxfoBqbgA16Ar4xeONXODwjZn9iD-CwJccusW6GP0xZ_PJHBFpfaJO_tLaP1k0KhT-eaANt112TvDBt0yjHtJg6He6CEDqagREIsH3w1mSm40zWLKGZeRLdnGxnQyKsTmNJ1rFRdY3AyoEgf6-pnJweUt0LaFMKmIJ2HornStm2hjUstBaji_5cSS946zqp4tgrc-RzzDuaQXzqlVL2J22SR2S_Oux_3yw88KmlhEFFP9axNcbjZrzW3L9XWnPT6UzVIaVRaNRSWfqDATg-jeHg4Gm1bp8w0aIqLdDxc9CfFMjuQ" } func (p *TestPrincipal) Id() string { return "anon." } +func (p *TestPrincipal) Name() string { return "anonymous" } func (p *TestPrincipal) IsAuthenticated() bool { return true } func NewTestPrincipal() *TestPrincipal { diff --git a/api/utils/token/anon_principal.go b/api/utils/token/anon_principal.go index 85a04ad7..441f7c80 100644 --- a/api/utils/token/anon_principal.go +++ b/api/utils/token/anon_principal.go @@ -4,6 +4,7 @@ type AnonPrincipal struct{} func (p *AnonPrincipal) Token() string { return "" } func (p *AnonPrincipal) Id() string { return "anonymous" } +func (p *AnonPrincipal) Name() string { return "anonymous" } func (p *AnonPrincipal) IsAuthenticated() bool { return false } func NewAnonymousPrincipal() *AnonPrincipal { diff --git a/api/utils/token/azure_principal.go b/api/utils/token/azure_principal.go index 8d99c693..f3df7e95 100644 --- a/api/utils/token/azure_principal.go +++ b/api/utils/token/azure_principal.go @@ -7,7 +7,10 @@ import ( ) type azureClaims struct { - ObjectId string `json:"oid,omitempty"` + ObjectId string `json:"oid,omitempty"` + Upn string `json:"upn,omitempty"` + AppDisplayName string `json:"app_displayname,omitempty"` + AppId string `json:"appid,omitempty"` } func (c *azureClaims) Validate(_ context.Context) error { @@ -27,3 +30,19 @@ func (p *AzurePrincipal) IsAuthenticated() bool { return true } func (p *AzurePrincipal) Id() string { return p.azureClaims.ObjectId } + +func (p *AzurePrincipal) 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 + } + + return p.azureClaims.ObjectId +} diff --git a/api/utils/token/validator.go b/api/utils/token/validator.go index 41ba1de9..b9276d89 100644 --- a/api/utils/token/validator.go +++ b/api/utils/token/validator.go @@ -14,6 +14,7 @@ type TokenPrincipal interface { IsAuthenticated() bool Token() string Id() string + Name() string } type ValidatorInterface interface { diff --git a/internal/config/config.go b/internal/config/config.go index 377315b8..5a17b9a9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,17 +10,17 @@ type Config struct { MetricsPort int `envconfig:"METRICS_PORT" default:"9090" desc:"Port where Metrics will be served"` ProfilePort int `envconfig:"PROFILE_PORT" default:"7070" desc:"Port where Profiler will be served"` UseProfiler bool `envconfig:"USE_PROFILER" default:"false" desc:"Enable Profiler"` + KubernetesApiServer string `envconfig:"K8S_API_HOST" default:"https://kubernetes.default.svc"` + PipelineImageTag string `envconfig:"PIPELINE_IMG_TAG" default:"latest"` + TektonImageTag string `envconfig:"TEKTON_IMG_TAG" default:"release-latest"` + RequireAppConfigurationItem bool `envconfig:"REQUIRE_APP_CONFIGURATION_ITEM" default:"true"` + 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"` - KubernetesApiServer string `envconfig:"K8S_API_HOST" default:"https://kubernetes.default.svc"` - LogLevel string `envconfig:"LOG_LEVEL" default:"info"` - LogPrettyPrint bool `envconfig:"LOG_PRETTY" default:"false"` - PipelineImageTag string `envconfig:"PIPELINE_IMG_TAG"` - TektonImageTag string `envconfig:"TEKTON_IMG_TAG"` - RequireAppConfigurationItem bool `envconfig:"REQUIRE_APP_CONFIGURATION_ITEM" default:"true"` - RequireAppADGroups bool `envconfig:"REQUIRE_APP_AD_GROUPS" default:"true"` AppName string `envconfig:"RADIX_APP" required:"true"` EnvironmentName string `envconfig:"RADIX_ENVIRONMENT" required:"true"` PrometheusUrl string `envconfig:"PROMETHEUS_URL" required:"true"` diff --git a/internal/flags/register.go b/internal/flags/register.go deleted file mode 100644 index d0582c15..00000000 --- a/internal/flags/register.go +++ /dev/null @@ -1,71 +0,0 @@ -package flags - -import ( - "fmt" - "reflect" - "strings" - - "github.com/spf13/pflag" - "github.com/spf13/viper" -) - -func Register(v *viper.Viper, prefix string, flagSet *pflag.FlagSet, config interface{}) error { - val := reflect.ValueOf(config) - var typ reflect.Type - if val.Kind() == reflect.Ptr { - typ = val.Elem().Type() - } else { - typ = val.Type() - } - - for i := 0; i < typ.NumField(); i++ { - // pull out the struct tags: - // flag - the name of the command line flag - // cfg - the name of the config file option - field := typ.Field(i) - fieldV := reflect.Indirect(val).Field(i) - fieldName := strings.Join([]string{prefix, field.Name}, ".") - - cfgName := field.Tag.Get("cfg") - if cfgName == ",internal" { - // Public but internal types that should not be exposed to users, skip them - continue - } - - if field.Name == strings.ToLower(field.Name) { - // Unexported fields cannot be set by a user, so won't have tags or flags, skip them - continue - } - - if field.Type.Kind() == reflect.Struct { - if cfgName != ",squash" { - return fmt.Errorf("field %q does not have required cfg tag: `,squash`", fieldName) - } - err := Register(v, fieldName, flagSet, fieldV.Interface()) - if err != nil { - return err - } - continue - } - - flagName := field.Tag.Get("flag") - if flagName == "" || cfgName == "" { - return fmt.Errorf("field %q does not have required tags (cfg, flag)", fieldName) - } - - if flagSet == nil { - return fmt.Errorf("flagset cannot be nil") - } - - f := flagSet.Lookup(flagName) - if f == nil { - return fmt.Errorf("field %q does not have a registered flag", flagName) - } - err := v.BindPFlag(cfgName, f) - if err != nil { - return fmt.Errorf("error binding flag for field %q: %w", fieldName, err) - } - } - - return nil -} diff --git a/models/accounts.go b/models/accounts.go index 60ceee10..02564307 100644 --- a/models/accounts.go +++ b/models/accounts.go @@ -1,15 +1,12 @@ package models import ( - "fmt" - kedav2 "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned" tektonclient "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" certclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" radixmodels "github.com/equinor/radix-common/models" radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" - "github.com/golang-jwt/jwt/v5" "k8s.io/client-go/kubernetes" secretProviderClient "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned" ) @@ -60,50 +57,3 @@ type Accounts struct { token string impersonation radixmodels.Impersonation } - -// GetOriginator get the request originator name or id -func (accounts Accounts) GetOriginator() (string, error) { - if accounts.impersonation.PerformImpersonation() { - return accounts.impersonation.User, nil - } - if originator, err, done := accounts.getOriginator("upn", ""); done { - return originator, err - } - if originator, err, done := accounts.getOriginator("app_displayname", ""); done { - return originator, err - } - if originator, err, done := accounts.getOriginator("appid", "%s (appid)"); done { - return originator, err - } - if originator, err, done := accounts.getOriginator("sub", "%s (sub)"); done { - return originator, err - } - return "", nil -} - -func (accounts Accounts) getOriginator(claim, format string) (string, error, bool) { - originator, err := getTokenClaim(accounts.token, claim) - if err != nil { - return "", err, true - } - if originator == "" { - return "", nil, false - } - if format != "" { - return fmt.Sprintf(format, originator), nil, true - } - return originator, nil, true -} - -func getTokenClaim(token string, claim string) (string, error) { - claims := jwt.MapClaims{} - parser := jwt.Parser{} - _, _, err := parser.ParseUnverified(token, claims) - if err != nil { - return "", fmt.Errorf("could not parse token (%v)", err) - } - if val, ok := claims[claim]; ok { - return fmt.Sprintf("%v", val), nil - } - return "", nil -} From cb34e87ec8e7e90d58c7ea10a54831efeec601a1 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 7 Oct 2024 10:00:11 +0200 Subject: [PATCH 12/29] remove unused image tag names --- api/applications/applications_handler.go | 2 +- api/jobs/job_controller.go | 22 +++++++++++----------- api/jobs/job_handler.go | 22 +++++++++------------- api/jobs/job_handler_test.go | 10 +++++----- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index f2e348d1..b3082ccc 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -66,7 +66,7 @@ type ApplicationHandler struct { // NewApplicationHandler Constructor func NewApplicationHandler(accounts models.Accounts, config config.Config, hasAccessToGetConfigMap HasAccessToGetConfigMapFunc) ApplicationHandler { return ApplicationHandler{ - jobHandler: jobController.Init(accounts, deployments.Init(accounts), config.PipelineImageTag, config.TektonImageTag), + jobHandler: jobController.Init(accounts, deployments.Init(accounts)), environmentHandler: environments.Init(environments.WithAccounts(accounts)), accounts: accounts, config: config, diff --git a/api/jobs/job_controller.go b/api/jobs/job_controller.go index d52d1f50..2ceeebad 100644 --- a/api/jobs/job_controller.go +++ b/api/jobs/job_controller.go @@ -119,7 +119,7 @@ func (jc *jobController) GetApplicationJobs(accounts models.Accounts, w http.Res // description: "Not found" appName := mux.Vars(r)["appName"] - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) jobSummaries, err := handler.GetApplicationJobs(r.Context(), appName) if err != nil { @@ -168,7 +168,7 @@ func (jc *jobController) GetApplicationJob(accounts models.Accounts, w http.Resp appName := mux.Vars(r)["appName"] jobName := mux.Vars(r)["jobName"] - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) jobDetail, err := handler.GetApplicationJob(r.Context(), appName, jobName) if err != nil { @@ -215,7 +215,7 @@ func (jc *jobController) StopApplicationJob(accounts models.Accounts, w http.Res appName := mux.Vars(r)["appName"] jobName := mux.Vars(r)["jobName"] - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) err := handler.StopJob(r.Context(), appName, jobName) if err != nil { @@ -261,7 +261,7 @@ func (jc *jobController) RerunApplicationJob(accounts models.Accounts, w http.Re // description: "Not found" appName := mux.Vars(r)["appName"] jobName := mux.Vars(r)["jobName"] - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) err := handler.RerunJob(r.Context(), appName, jobName) if err != nil { @@ -312,7 +312,7 @@ func (jc *jobController) GetTektonPipelineRuns(accounts models.Accounts, w http. appName := mux.Vars(r)["appName"] jobName := mux.Vars(r)["jobName"] - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) tektonPipelineRuns, err := handler.GetTektonPipelineRuns(r.Context(), appName, jobName) if err != nil { @@ -367,7 +367,7 @@ func (jc *jobController) GetTektonPipelineRun(accounts models.Accounts, w http.R jobName := mux.Vars(r)["jobName"] pipelineRunName := mux.Vars(r)["pipelineRunName"] - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) tektonPipelineRun, err := handler.GetTektonPipelineRun(r.Context(), appName, jobName, pipelineRunName) if err != nil { @@ -424,7 +424,7 @@ func (jc *jobController) GetTektonPipelineRunTasks(accounts models.Accounts, w h jobName := mux.Vars(r)["jobName"] pipelineRunName := mux.Vars(r)["pipelineRunName"] - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) tektonTasks, err := handler.GetTektonPipelineRunTasks(r.Context(), appName, jobName, pipelineRunName) if err != nil { @@ -485,7 +485,7 @@ func (jc *jobController) GetTektonPipelineRunTask(accounts models.Accounts, w ht pipelineRunName := mux.Vars(r)["pipelineRunName"] taskName := mux.Vars(r)["taskName"] - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) tektonTasks, err := handler.GetTektonPipelineRunTask(r.Context(), appName, jobName, pipelineRunName, taskName) if err != nil { @@ -548,7 +548,7 @@ func (jc *jobController) GetTektonPipelineRunTaskSteps(accounts models.Accounts, pipelineRunName := mux.Vars(r)["pipelineRunName"] taskName := mux.Vars(r)["taskName"] - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) tektonTaskSteps, err := handler.GetTektonPipelineRunTaskSteps(r.Context(), appName, jobName, pipelineRunName, taskName) if err != nil { @@ -638,7 +638,7 @@ func (jc *jobController) GetTektonPipelineRunTaskStepLogs(accounts models.Accoun return } - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) log, err := handler.GetTektonPipelineRunTaskStepLogs(r.Context(), appName, jobName, pipelineRunName, taskName, stepName, &since, logLines) if err != nil { jc.ErrorResponse(w, r, err) @@ -721,7 +721,7 @@ func (jc *jobController) GetPipelineJobStepLogs(accounts models.Accounts, w http return } - handler := Init(accounts, deployments.Init(accounts), "", "") + handler := Init(accounts, deployments.Init(accounts)) log, err := handler.GetPipelineJobStepLogs(r.Context(), appName, jobName, stepName, &since, logLines) if err != nil { jc.ErrorResponse(w, r, err) diff --git a/api/jobs/job_handler.go b/api/jobs/job_handler.go index 14c09776..ca63b478 100644 --- a/api/jobs/job_handler.go +++ b/api/jobs/job_handler.go @@ -32,23 +32,19 @@ const ( // JobHandler Instance variables type JobHandler struct { - accounts models.Accounts - userAccount models.Account - serviceAccount models.Account - deploy deployments.DeployHandler - pipelineImageTag string - tektonImageTag string + accounts models.Accounts + userAccount models.Account + serviceAccount models.Account + deploy deployments.DeployHandler } // Init Constructor -func Init(accounts models.Accounts, deployHandler deployments.DeployHandler, pipelineImageTag, tektonImageTag string) JobHandler { +func Init(accounts models.Accounts, deployHandler deployments.DeployHandler) JobHandler { return JobHandler{ - accounts: accounts, - userAccount: accounts.UserAccount, - serviceAccount: accounts.ServiceAccount, - deploy: deployHandler, - pipelineImageTag: pipelineImageTag, - tektonImageTag: tektonImageTag, + accounts: accounts, + userAccount: accounts.UserAccount, + serviceAccount: accounts.ServiceAccount, + deploy: deployHandler, } } diff --git a/api/jobs/job_handler_test.go b/api/jobs/job_handler_test.go index 8d71a84a..25045e95 100644 --- a/api/jobs/job_handler_test.go +++ b/api/jobs/job_handler_test.go @@ -140,7 +140,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob() { ctrl := gomock.NewController(s.T()) defer ctrl.Finish() dh := deployMock.NewMockDeployHandler(ctrl) - h := Init(s.accounts, dh, "", "") + h := Init(s.accounts, dh) actualJob, err := h.GetApplicationJob(context.Background(), appName, "missing_job") s.True(k8serrors.IsNotFound(err)) s.Nil(actualJob) @@ -152,7 +152,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob() { dh := deployMock.NewMockDeployHandler(ctrl) dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), appName, jobName).Return(nil, assert.AnError).Times(1) - h := Init(s.accounts, dh, "", "") + h := Init(s.accounts, dh) actualJob, actualErr := h.GetApplicationJob(context.Background(), appName, jobName) s.Equal(assert.AnError, actualErr) @@ -166,7 +166,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob() { deployList := []*deploymentModels.DeploymentSummary{&deploySummary} dh := deployMock.NewMockDeployHandler(ctrl) dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), appName, jobName).Return(deployList, nil).Times(1) - h := Init(s.accounts, dh, "", "") + h := Init(s.accounts, dh) actualJob, actualErr := h.GetApplicationJob(context.Background(), appName, jobName) s.NoError(actualErr) @@ -210,7 +210,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob_Created() { dh := deployMock.NewMockDeployHandler(ctrl) dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - h := Init(s.accounts, dh, "", "") + h := Init(s.accounts, dh) rj := radixv1.RadixJob{ObjectMeta: metav1.ObjectMeta{Name: scenario.jobName, Namespace: utils.GetAppNamespace(appName), CreationTimestamp: scenario.creationTimestamp}} if scenario.jobStatusCreated != emptyTime { rj.Status.Created = &scenario.jobStatusCreated @@ -241,7 +241,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob_Status() { dh := deployMock.NewMockDeployHandler(ctrl) dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - h := Init(s.accounts, dh, "", "") + h := Init(s.accounts, dh) rj := radixv1.RadixJob{ ObjectMeta: metav1.ObjectMeta{Name: scenario.jobName, Namespace: utils.GetAppNamespace(appName)}, Spec: radixv1.RadixJobSpec{Stop: scenario.stop}, From 71b2154064a6492c0fd32148b15f97d0bcdedb29 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 7 Oct 2024 12:52:35 +0200 Subject: [PATCH 13/29] cleanup job handler, remove connection to applications --- api/applications/applications_handler.go | 4 - api/applications/get_applications_handler.go | 58 ++++++--- api/jobs/job_handler.go | 117 +------------------ 3 files changed, 50 insertions(+), 129 deletions(-) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index b3082ccc..f42c4b66 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -11,9 +11,7 @@ import ( "time" applicationModels "github.com/equinor/radix-api/api/applications/models" - "github.com/equinor/radix-api/api/deployments" "github.com/equinor/radix-api/api/environments" - jobController "github.com/equinor/radix-api/api/jobs" jobModels "github.com/equinor/radix-api/api/jobs/models" "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-api/api/middleware/auth" @@ -53,7 +51,6 @@ type HasAccessToGetConfigMapFunc func(ctx context.Context, kubeClient kubernetes // ApplicationHandler Instance variables type ApplicationHandler struct { - jobHandler jobController.JobHandler environmentHandler environments.EnvironmentHandler accounts models.Accounts config config.Config @@ -66,7 +63,6 @@ type ApplicationHandler struct { // NewApplicationHandler Constructor func NewApplicationHandler(accounts models.Accounts, config config.Config, hasAccessToGetConfigMap HasAccessToGetConfigMapFunc) ApplicationHandler { return ApplicationHandler{ - jobHandler: jobController.Init(accounts, deployments.Init(accounts)), environmentHandler: environments.Init(environments.WithAccounts(accounts)), accounts: accounts, config: config, diff --git a/api/applications/get_applications_handler.go b/api/applications/get_applications_handler.go index 04afd6ce..ed0d92dc 100644 --- a/api/applications/get_applications_handler.go +++ b/api/applications/get_applications_handler.go @@ -4,13 +4,16 @@ import ( "context" "sort" "strings" + "sync" applicationModels "github.com/equinor/radix-api/api/applications/models" deployment "github.com/equinor/radix-api/api/deployments" deploymentModels "github.com/equinor/radix-api/api/deployments/models" environmentModels "github.com/equinor/radix-api/api/environments/models" jobModels "github.com/equinor/radix-api/api/jobs/models" + "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-api/api/utils/access" + "github.com/equinor/radix-operator/pkg/client/clientset/versioned" "golang.org/x/sync/errgroup" authorizationapi "k8s.io/api/authorization/v1" @@ -48,7 +51,7 @@ func (ah *ApplicationHandler) GetApplications(ctx context.Context, matcher appli var latestApplicationJobs map[string]*jobModels.JobSummary if options.IncludeLatestJobSummary { - if latestApplicationJobs, err = ah.getJobsForApplication(ctx, radixRegistrations); err != nil { + if latestApplicationJobs, err = getLatestJobPerApplication(ctx, ah.accounts.UserAccount.RadixClient, radixRegistrations); err != nil { return nil, err } } @@ -154,19 +157,6 @@ func getComponentsForActiveDeploymentsInEnvironments(ctx context.Context, deploy return components, nil } -func (ah *ApplicationHandler) getJobsForApplication(ctx context.Context, radixRegistations []v1.RadixRegistration) (map[string]*jobModels.JobSummary, error) { - forApplications := map[string]bool{} - for _, app := range radixRegistations { - forApplications[app.GetName()] = true - } - - applicationJobs, err := ah.jobHandler.GetLatestJobPerApplication(ctx, forApplications) - if err != nil { - return nil, err - } - return applicationJobs, nil -} - func (ah *ApplicationHandler) filterRadixRegByAccess(ctx context.Context, radixregs []v1.RadixRegistration, hasAccess hasAccessToRR) ([]v1.RadixRegistration, error) { result := []v1.RadixRegistration{} limit := 25 @@ -221,3 +211,43 @@ func hasAccess(ctx context.Context, client kubernetes.Interface, rr v1.RadixRegi Name: rr.GetName(), }) } + +func getLatestJobPerApplication(ctx context.Context, radixClient versioned.Interface, radixRegistations []v1.RadixRegistration) (map[string]*jobModels.JobSummary, error) { + g, ctx := errgroup.WithContext(ctx) + g.SetLimit(25) + jobSummaries := sync.Map{} + + for _, rr := range radixRegistations { + g.Go(func() error { + jobs, err := kubequery.GetRadixJobs(ctx, radixClient, rr.GetName()) + if err != nil { + return err + } + + var latestJob *v1.RadixJob + for _, job := range jobs { + if latestJob == nil || job.Status.Started.After(latestJob.Status.Started.Time) { + latestJob = &job + } + } + + if latestJob != nil { + jobSummaries.Store(rr.GetName(), jobModels.GetSummaryFromRadixJob(latestJob)) + } + return nil + }) + } + + err := g.Wait() + if err != nil { + return nil, err + } + + applicationJob := make(map[string]*jobModels.JobSummary, len(radixRegistations)) + for _, rr := range radixRegistations { + job, _ := jobSummaries.Load(rr.GetName()) + applicationJob[rr.GetName()] = job.(*jobModels.JobSummary) + } + + return applicationJob, nil +} diff --git a/api/jobs/job_handler.go b/api/jobs/job_handler.go index ca63b478..f5ffdb20 100644 --- a/api/jobs/job_handler.go +++ b/api/jobs/job_handler.go @@ -18,7 +18,6 @@ import ( v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" crdUtils "github.com/equinor/radix-operator/pkg/apis/utils" pipelinev1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" - "golang.org/x/sync/errgroup" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/apis" @@ -48,28 +47,19 @@ func Init(accounts models.Accounts, deployHandler deployments.DeployHandler) Job } } -// GetLatestJobPerApplication Handler for GetApplicationJobs - NOTE: does not get latestJob.Environments -func (jh JobHandler) GetLatestJobPerApplication(ctx context.Context, forApplications map[string]bool) (map[string]*jobModels.JobSummary, error) { - return jh.getLatestJobPerApplication(ctx, forApplications) -} - // GetApplicationJobs Handler for GetApplicationJobs func (jh JobHandler) GetApplicationJobs(ctx context.Context, appName string) ([]*jobModels.JobSummary, error) { - return jh.getApplicationJobs(ctx, appName) -} - -// GetLatestApplicationJob Get last run application job -func (jh JobHandler) GetLatestApplicationJob(ctx context.Context, appName string) (*jobModels.JobSummary, error) { - jobs, err := jh.getApplicationJobs(ctx, appName) + jobs, err := jh.getJobs(ctx, appName) if err != nil { return nil, err } - if len(jobs) == 0 { - return nil, nil - } + // Sort jobs descending + sort.Slice(jobs, func(i, j int) bool { + return utils.IsBefore(jobs[j], jobs[i]) + }) - return jobs[0], nil + return jobs, nil } // GetApplicationJob Handler for GetApplicationJob @@ -290,49 +280,6 @@ func sortPipelineTasks(tasks []jobModels.PipelineRunTask) []jobModels.PipelineRu return tasks } -func (jh JobHandler) getApplicationJobs(ctx context.Context, appName string) ([]*jobModels.JobSummary, error) { - jobs, err := jh.getJobs(ctx, appName) - if err != nil { - return nil, err - } - - // Sort jobs descending - sort.Slice(jobs, func(i, j int) bool { - return utils.IsBefore(jobs[j], jobs[i]) - }) - - return jobs, nil -} - -func (jh JobHandler) getDefinedJobs(ctx context.Context, appNames []string) ([]*jobModels.JobSummary, error) { - var g errgroup.Group - g.SetLimit(25) - - jobsCh := make(chan []*jobModels.JobSummary, len(appNames)) - for _, appName := range appNames { - name := appName // locally scope appName to avoid race condition in go routines - g.Go(func() error { - jobs, err := jh.getJobs(ctx, name) - if err == nil { - jobsCh <- jobs - } - return err - }) - } - - err := g.Wait() - close(jobsCh) - if err != nil { - return nil, err - } - - var jobSummaries []*jobModels.JobSummary - for jobs := range jobsCh { - jobSummaries = append(jobSummaries, jobs...) - } - return jobSummaries, nil -} - func (jh JobHandler) getJobs(ctx context.Context, appName string) ([]*jobModels.JobSummary, error) { jobs, err := kubequery.GetRadixJobs(ctx, jh.accounts.UserAccount.RadixClient, appName) if err != nil { @@ -343,55 +290,3 @@ func (jh JobHandler) getJobs(ctx context.Context, appName string) ([]*jobModels. return jobModels.GetSummaryFromRadixJob(&j) }), nil } - -func (jh JobHandler) getLatestJobPerApplication(ctx context.Context, forApplications map[string]bool) (map[string]*jobModels.JobSummary, error) { - // Primarily use Radix Jobs - var apps []string - for name, shouldAdd := range forApplications { - if shouldAdd { - apps = append(apps, name) - } - } - - someJobs, err := jh.getDefinedJobs(ctx, apps) - if err != nil { - return nil, err - } - - sort.Slice(someJobs, func(i, j int) bool { - switch strings.Compare(someJobs[i].AppName, someJobs[j].AppName) { - case -1: - return true - case 1: - return false - } - - return utils.IsBefore(someJobs[j], someJobs[i]) - }) - - applicationJob := make(map[string]*jobModels.JobSummary) - for _, job := range someJobs { - if applicationJob[job.AppName] != nil { - continue - } - if !forApplications[job.AppName] { - continue - } - - if job.Started == "" { - // Job may still be queued or waiting to be scheduled by the operator - continue - } - - applicationJob[job.AppName] = job - } - - forApplicationsWithNoRadixJob := make(map[string]bool) - for applicationName := range forApplications { - if applicationJob[applicationName] == nil { - forApplicationsWithNoRadixJob[applicationName] = true - } - } - - return applicationJob, nil -} From 31c786ff33acd865cf065282386c216b4f9927b5 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 7 Oct 2024 13:07:09 +0200 Subject: [PATCH 14/29] fix tests --- api/applications/applications_controller_test.go | 2 +- api/applications/get_applications_handler.go | 4 +++- api/test/test_principal.go | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index f890c7e7..07f1ec31 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -885,7 +885,7 @@ func TestGetApplication_AllFieldsAreSet(t *testing.T) { assert.Equal(t, adUsers, application.Registration.AdUsers) assert.Equal(t, readerAdGroups, application.Registration.ReaderAdGroups) assert.Equal(t, readerAdUsers, application.Registration.ReaderAdUsers) - assert.Equal(t, "not-existing-test-radix-email@equinor.com", application.Registration.Creator) + assert.Equal(t, "test-principal", application.Registration.Creator) assert.Equal(t, "abranch", application.Registration.ConfigBranch) assert.Equal(t, "a/custom-radixconfig.yaml", application.Registration.RadixConfigFullName) assert.Equal(t, "ci", application.Registration.ConfigurationItem) diff --git a/api/applications/get_applications_handler.go b/api/applications/get_applications_handler.go index ed0d92dc..7f2aca21 100644 --- a/api/applications/get_applications_handler.go +++ b/api/applications/get_applications_handler.go @@ -246,7 +246,9 @@ func getLatestJobPerApplication(ctx context.Context, radixClient versioned.Inter applicationJob := make(map[string]*jobModels.JobSummary, len(radixRegistations)) for _, rr := range radixRegistations { job, _ := jobSummaries.Load(rr.GetName()) - applicationJob[rr.GetName()] = job.(*jobModels.JobSummary) + if job != nil { + applicationJob[rr.GetName()] = job.(*jobModels.JobSummary) + } } return applicationJob, nil diff --git a/api/test/test_principal.go b/api/test/test_principal.go index c276ed15..09231fb3 100644 --- a/api/test/test_principal.go +++ b/api/test/test_principal.go @@ -5,8 +5,8 @@ type TestPrincipal struct{} func (p *TestPrincipal) Token() string { return "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MjQ1YTJlYzEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xMjM0NTY3OC03NTY1LTIzNDItMjM0Mi0xMjM0MDViNDU5YjAvIiwiaWF0IjoxNTc1MzU1NTA4LCJuYmYiOjE1NzUzNTU1MDgsImV4cCI6MTU3NTM1OTQwOCwiYWNyIjoiMSIsImFpbyI6IjQyYXNkYXMiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDc5MDM5YTkwIiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJKb2huIiwiZ2l2ZW5fbmFtZSI6IkRvZSIsImhhc2dyb3VwcyI6InRydWUiLCJpcGFkZHIiOiIxMC4xMC4xMC4xMCIsIm5hbWUiOiJKb2huIERvZSIsIm9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzRmYzhmYTBlYSIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0xMjM0NTY3ODktMTIzNDU2OTc4MC0xMjM0NTY3ODktMTIzNDU2NyIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IjBoa2JpbEo3MTIzNHpSU3h6eHZiSW1hc2RmZ3N4amI2YXNkZmVOR2FzZGYiLCJ0aWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MDViNDU5YjAiLCJ1bmlxdWVfbmFtZSI6Im5vdC1leGlzdGluZy1yYWRpeC1lbWFpbEBlcXVpbm9yLmNvbSIsInVwbiI6Im5vdC1leGlzdGluZy10ZXN0LXJhZGl4LWVtYWlsQGVxdWlub3IuY29tIiwidXRpIjoiQlMxMmFzR2R1RXlyZUVjRGN2aDJBRyIsInZlciI6IjEuMCJ9.EB5z7Mk34NkFPCP8MqaNMo4UeWgNyO4-qEmzOVPxfoBqbgA16Ar4xeONXODwjZn9iD-CwJccusW6GP0xZ_PJHBFpfaJO_tLaP1k0KhT-eaANt112TvDBt0yjHtJg6He6CEDqagREIsH3w1mSm40zWLKGZeRLdnGxnQyKsTmNJ1rFRdY3AyoEgf6-pnJweUt0LaFMKmIJ2HornStm2hjUstBaji_5cSS946zqp4tgrc-RzzDuaQXzqlVL2J22SR2S_Oux_3yw88KmlhEFFP9axNcbjZrzW3L9XWnPT6UzVIaVRaNRSWfqDATg-jeHg4Gm1bp8w0aIqLdDxc9CfFMjuQ" } -func (p *TestPrincipal) Id() string { return "anon." } -func (p *TestPrincipal) Name() string { return "anonymous" } +func (p *TestPrincipal) Id() string { return "test-id" } +func (p *TestPrincipal) Name() string { return "test-principal" } func (p *TestPrincipal) IsAuthenticated() bool { return true } func NewTestPrincipal() *TestPrincipal { From c1a09b7a4c9591bc7717271d18f4e1578ddf2c72 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 7 Oct 2024 13:12:33 +0200 Subject: [PATCH 15/29] fix linting/cip --- api/applications/start_job_handler.go | 12 ------------ api/utils/token/mock/validator_mock.go | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/api/applications/start_job_handler.go b/api/applications/start_job_handler.go index 897c32cf..634747db 100644 --- a/api/applications/start_job_handler.go +++ b/api/applications/start_job_handler.go @@ -3,13 +3,10 @@ package applications import ( "context" "fmt" - "strings" - "time" jobController "github.com/equinor/radix-api/api/jobs" jobModels "github.com/equinor/radix-api/api/jobs/models" "github.com/equinor/radix-api/api/middleware/auth" - radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" pipelineJob "github.com/equinor/radix-operator/pkg/apis/pipeline" @@ -121,15 +118,6 @@ func buildPipelineJob(ctx context.Context, appName, cloneURL, radixConfigFullNam return &job } -func getUniqueJobName(image string) (string, string) { - var jobName []string - timestamp := time.Now().Format("20060102150405") - randomStr := strings.ToLower(radixutils.RandString(5)) - jobName = append(jobName, image, "-", timestamp, "-", randomStr) - - return strings.Join(jobName, ""), randomStr -} - func getTriggeredBy(ctx context.Context, triggeredBy string) string { if triggeredBy != "" && triggeredBy != "" { return triggeredBy diff --git a/api/utils/token/mock/validator_mock.go b/api/utils/token/mock/validator_mock.go index e4f3ba1b..029f8d4d 100644 --- a/api/utils/token/mock/validator_mock.go +++ b/api/utils/token/mock/validator_mock.go @@ -63,6 +63,20 @@ func (mr *MockTokenPrincipalMockRecorder) IsAuthenticated() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAuthenticated", reflect.TypeOf((*MockTokenPrincipal)(nil).IsAuthenticated)) } +// Name mocks base method. +func (m *MockTokenPrincipal) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockTokenPrincipalMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockTokenPrincipal)(nil).Name)) +} + // Token mocks base method. func (m *MockTokenPrincipal) Token() string { m.ctrl.T.Helper() From 177a83c58f7b92bc41a58fd5e8e89c77b57da63d Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 09:37:50 +0200 Subject: [PATCH 16/29] Remove unused token/impersonation from accounts --- api/alerting/handler_test.go | 3 +-- api/environments/environment_controller_test.go | 3 +-- api/jobs/job_handler_test.go | 3 +-- api/middleware/auth/authentication.go | 2 +- api/middleware/cors/cors.go | 2 +- api/middleware/logger/middleware.go | 6 +++--- api/middleware/recovery/recovery.go | 2 +- api/router/api.go | 12 ++++++------ api/router/metrics.go | 8 ++++---- api/utils/radix_middleware.go | 16 +--------------- models/accounts.go | 10 +--------- 11 files changed, 21 insertions(+), 46 deletions(-) diff --git a/api/alerting/handler_test.go b/api/alerting/handler_test.go index 2d6c5cea..032d7720 100644 --- a/api/alerting/handler_test.go +++ b/api/alerting/handler_test.go @@ -8,7 +8,6 @@ import ( certclientfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" alertModels "github.com/equinor/radix-api/api/alerting/models" "github.com/equinor/radix-api/models" - radixmodels "github.com/equinor/radix-common/models" operatoralert "github.com/equinor/radix-operator/pkg/apis/alert" "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" @@ -34,7 +33,7 @@ func (s *HandlerTestSuite) SetupTest() { kedaClient := kedafake.NewSimpleClientset() secretProviderClient := secretproviderfake.NewSimpleClientset() certClient := certclientfake.NewSimpleClientset() - s.accounts = models.NewAccounts(kubeClient, radixClient, kedaClient, secretProviderClient, nil, certClient, kubeClient, radixClient, kedaClient, secretProviderClient, nil, certClient, "", radixmodels.Impersonation{}) + s.accounts = models.NewAccounts(kubeClient, radixClient, kedaClient, secretProviderClient, nil, certClient, kubeClient, radixClient, kedaClient, secretProviderClient, nil, certClient) } func TestHandlerTestSuite(t *testing.T) { diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index 453ee939..ec156e8c 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -22,7 +22,6 @@ import ( "github.com/equinor/radix-api/api/utils" authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/equinor/radix-api/models" - radixmodels "github.com/equinor/radix-common/models" radixhttp "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/numbers" @@ -2709,7 +2708,7 @@ func initHandler(client kubernetes.Interface, secretproviderclient secretsstorevclient.Interface, certClient certclient.Interface, handlerConfig ...EnvironmentHandlerOptions) EnvironmentHandler { - accounts := models.NewAccounts(client, radixclient, kedaClient, secretproviderclient, nil, certClient, client, radixclient, kedaClient, secretproviderclient, nil, certClient, "", radixmodels.Impersonation{}) + accounts := models.NewAccounts(client, radixclient, kedaClient, secretproviderclient, nil, certClient, client, radixclient, kedaClient, secretproviderclient, nil, certClient) options := []EnvironmentHandlerOptions{WithAccounts(accounts)} options = append(options, handlerConfig...) return Init(options...) diff --git a/api/jobs/job_handler_test.go b/api/jobs/job_handler_test.go index 25045e95..1acb13cd 100644 --- a/api/jobs/job_handler_test.go +++ b/api/jobs/job_handler_test.go @@ -10,7 +10,6 @@ import ( deploymentModels "github.com/equinor/radix-api/api/deployments/models" jobModels "github.com/equinor/radix-api/api/jobs/models" "github.com/equinor/radix-api/models" - radixmodels "github.com/equinor/radix-common/models" radixutils "github.com/equinor/radix-common/utils" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" @@ -75,7 +74,7 @@ func (s *JobHandlerTestSuite) SetupTest() { func (s *JobHandlerTestSuite) setupTest() { s.kubeClient, s.radixClient, s.kedaClient, s.secretProviderClient, s.certClient = s.getUtils() - accounts := models.NewAccounts(s.kubeClient, s.radixClient, s.kedaClient, s.secretProviderClient, nil, s.certClient, s.kubeClient, s.radixClient, s.kedaClient, s.secretProviderClient, nil, s.certClient, "", radixmodels.Impersonation{}) + accounts := models.NewAccounts(s.kubeClient, s.radixClient, s.kedaClient, s.secretProviderClient, nil, s.certClient, s.kubeClient, s.radixClient, s.kedaClient, s.secretProviderClient, nil, s.certClient) s.accounts = accounts } diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index 4557fc92..05d0d212 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -14,7 +14,7 @@ import ( type ctxUserKey struct{} type ctxImpersonationKey struct{} -func CreateAuthenticationMiddleware(validator token.ValidatorInterface) negroni.HandlerFunc { +func NewAuthenticationMiddleware(validator token.ValidatorInterface) negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { ctx := r.Context() logger := log.Ctx(ctx) diff --git a/api/middleware/cors/cors.go b/api/middleware/cors/cors.go index e1b7d596..215b7c52 100644 --- a/api/middleware/cors/cors.go +++ b/api/middleware/cors/cors.go @@ -8,7 +8,7 @@ import ( "github.com/rs/zerolog/log" ) -func CreateMiddleware(clusterName, radixDNSZone string) *cors.Cors { +func NewMiddleware(clusterName, radixDNSZone string) *cors.Cors { corsOptions := cors.Options{ AllowedOrigins: []string{ diff --git a/api/middleware/logger/middleware.go b/api/middleware/logger/middleware.go index e0ff33a1..c8be393b 100644 --- a/api/middleware/logger/middleware.go +++ b/api/middleware/logger/middleware.go @@ -11,7 +11,7 @@ import ( "github.com/urfave/negroni/v3" ) -func CreateZerologRequestLoggerMiddleware() negroni.HandlerFunc { +func NewZerologResponseLoggerMiddleware() negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { m := httpsnoop.CaptureMetrics(next, w, r) @@ -35,7 +35,7 @@ func CreateZerologRequestLoggerMiddleware() negroni.HandlerFunc { } } -func CreateZerologRequestIdMiddleware() negroni.HandlerFunc { +func NewZerologRequestIdMiddleware() negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { logger := log.Ctx(r.Context()).With().Str("request_id", xid.New().String()).Logger() r = r.WithContext(logger.WithContext(r.Context())) @@ -43,7 +43,7 @@ func CreateZerologRequestIdMiddleware() negroni.HandlerFunc { next(w, r) } } -func CreateZerologRequestDetailsMiddleware() negroni.HandlerFunc { +func NewZerologRequestDetailsMiddleware() negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { remoteIp, _, _ := net.SplitHostPort(r.RemoteAddr) logger := log.Ctx(r.Context()).With(). diff --git a/api/middleware/recovery/recovery.go b/api/middleware/recovery/recovery.go index e7e44515..ba778a5d 100644 --- a/api/middleware/recovery/recovery.go +++ b/api/middleware/recovery/recovery.go @@ -5,7 +5,7 @@ import ( "github.com/urfave/negroni/v3" ) -func CreateMiddleware() *negroni.Recovery { +func NewMiddleware() *negroni.Recovery { rec := negroni.NewRecovery() rec.PrintStack = false rec.Logger = &log.Logger diff --git a/api/router/api.go b/api/router/api.go index dbac7183..b6d9e48d 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -27,12 +27,12 @@ func NewAPIHandler(clusterName string, validator token.ValidatorInterface, radix serveMux.Handle("/api/", createApiRouter(kubeUtil, controllers)) n := negroni.New( - recovery.CreateMiddleware(), - logger.CreateZerologRequestIdMiddleware(), - cors.CreateMiddleware(clusterName, radixDNSZone), - logger.CreateZerologRequestDetailsMiddleware(), - auth.CreateAuthenticationMiddleware(validator), - logger.CreateZerologRequestLoggerMiddleware(), + recovery.NewMiddleware(), + logger.NewZerologRequestIdMiddleware(), + cors.NewMiddleware(clusterName, radixDNSZone), + logger.NewZerologRequestDetailsMiddleware(), + auth.NewAuthenticationMiddleware(validator), + logger.NewZerologResponseLoggerMiddleware(), ) n.UseHandler(serveMux) diff --git a/api/router/metrics.go b/api/router/metrics.go index 3f2ccf15..c303f2d7 100644 --- a/api/router/metrics.go +++ b/api/router/metrics.go @@ -15,10 +15,10 @@ func NewMetricsHandler() http.Handler { serveMux.Handle("GET /metrics", promhttp.Handler()) n := negroni.New( - recovery.CreateMiddleware(), - logger.CreateZerologRequestIdMiddleware(), - logger.CreateZerologRequestDetailsMiddleware(), - logger.CreateZerologRequestLoggerMiddleware(), + recovery.NewMiddleware(), + logger.NewZerologRequestIdMiddleware(), + logger.NewZerologRequestDetailsMiddleware(), + logger.NewZerologResponseLoggerMiddleware(), ) n.UseHandler(serveMux) diff --git a/api/utils/radix_middleware.go b/api/utils/radix_middleware.go index 5a78005b..906be29d 100644 --- a/api/utils/radix_middleware.go +++ b/api/utils/radix_middleware.go @@ -65,21 +65,7 @@ func (handler *RadixMiddleware) handleAuthorization(w http.ResponseWriter, r *ht inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient := handler.kubeUtil.GetServerKubernetesClient(restOptions...) outClusterClient, outClusterRadixClient, outClusterKedaClient, outClusterSecretProviderClient, outClusterTektonClient, outClusterCertManagerClient := handler.kubeUtil.GetUserKubernetesClient(token, impersonation, restOptions...) - accounts := models.NewAccounts( - inClusterClient, - inClusterRadixClient, - inClusterKedaClient, - inClusterSecretProviderClient, - inClusterTektonClient, - inClusterCertManagerClient, - outClusterClient, - outClusterRadixClient, - outClusterKedaClient, - outClusterSecretProviderClient, - outClusterTektonClient, - outClusterCertManagerClient, - token, - impersonation) + accounts := models.NewAccounts(inClusterClient, inClusterRadixClient, inClusterKedaClient, inClusterSecretProviderClient, inClusterTektonClient, inClusterCertManagerClient, outClusterClient, outClusterRadixClient, outClusterKedaClient, outClusterSecretProviderClient, outClusterTektonClient, outClusterCertManagerClient) // Check if registration of application exists for application-specific requests if appName, exists := mux.Vars(r)["appName"]; exists { diff --git a/models/accounts.go b/models/accounts.go index 02564307..cb308041 100644 --- a/models/accounts.go +++ b/models/accounts.go @@ -5,17 +5,13 @@ import ( tektonclient "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" certclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - radixmodels "github.com/equinor/radix-common/models" radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" "k8s.io/client-go/kubernetes" secretProviderClient "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned" ) // NewAccounts creates a new Accounts struct -func NewAccounts( - inClusterClient kubernetes.Interface, inClusterRadixClient radixclient.Interface, inClusterKedaClient kedav2.Interface, inClusterSecretProviderClient secretProviderClient.Interface, inClusterTektonClient tektonclient.Interface, inClusterCertManagerClient certclient.Interface, - outClusterClient kubernetes.Interface, outClusterRadixClient radixclient.Interface, outClusterKedaClient kedav2.Interface, outClusterSecretProviderClient secretProviderClient.Interface, outClusterTektonClient tektonclient.Interface, outClusterCertManagerClient certclient.Interface, - token string, impersonation radixmodels.Impersonation) Accounts { +func NewAccounts(inClusterClient kubernetes.Interface, inClusterRadixClient radixclient.Interface, inClusterKedaClient kedav2.Interface, inClusterSecretProviderClient secretProviderClient.Interface, inClusterTektonClient tektonclient.Interface, inClusterCertManagerClient certclient.Interface, outClusterClient kubernetes.Interface, outClusterRadixClient radixclient.Interface, outClusterKedaClient kedav2.Interface, outClusterSecretProviderClient secretProviderClient.Interface, outClusterTektonClient tektonclient.Interface, outClusterCertManagerClient certclient.Interface) Accounts { return Accounts{ UserAccount: Account{ @@ -34,8 +30,6 @@ func NewAccounts( TektonClient: inClusterTektonClient, CertManagerClient: inClusterCertManagerClient, }, - token: token, - impersonation: impersonation, } } @@ -54,6 +48,4 @@ func NewServiceAccount(inClusterClient kubernetes.Interface, inClusterRadixClien type Accounts struct { UserAccount Account ServiceAccount Account - token string - impersonation radixmodels.Impersonation } From e0bb20174b2795731ce0dd58221edd50d5bc2b57 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 10:02:05 +0200 Subject: [PATCH 17/29] Move anonPrincipals and Azure Princiapls to internal structures to enforce proper creation --- .../applications_controller_test.go | 18 ++++++------ api/buildsecrets/buildsecrets_test.go | 2 +- .../build_status_controller_test.go | 10 +++---- api/deployments/deployment_controller_test.go | 2 +- .../environment_controller_test.go | 2 +- .../env_vars_controller_test.go | 2 +- api/jobs/job_controller_test.go | 2 +- api/middleware/auth/anon_principal.go | 8 ++++++ api/middleware/auth/authentication.go | 2 +- api/middleware/auth/authentication_test.go | 9 +++--- api/secrets/secret_controller_test.go | 2 +- api/test/test_principal.go | 13 +++++---- api/utils/token/anon_principal.go | 12 -------- api/utils/token/azure_principal.go | 10 +++---- api/utils/token/validator.go | 2 +- go.mod | 16 ++--------- go.sum | 28 ------------------- 17 files changed, 49 insertions(+), 91 deletions(-) create mode 100644 api/middleware/auth/anon_principal.go delete mode 100644 api/utils/token/anon_principal.go diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 07f1ec31..aab669b9 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -82,7 +82,7 @@ func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory // controllerTestUtils is used for issuing HTTP request and processing responses mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils( kubeclient, radixclient, @@ -123,7 +123,7 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { t.Run("no access", func(t *testing.T) { prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils( kubeclient, radixclient, @@ -150,7 +150,7 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { t.Run("access to single app", func(t *testing.T) { prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, rr v1.RadixRegistration) (bool, error) { return rr.GetName() == "my-second-app", nil @@ -170,7 +170,7 @@ func TestGetApplications_HasAccessToSomeRR(t *testing.T) { t.Run("access to all app", func(t *testing.T) { prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil @@ -254,7 +254,7 @@ func TestSearchApplicationsPost(t *testing.T) { prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil @@ -339,7 +339,7 @@ func TestSearchApplicationsPost(t *testing.T) { t.Run("search for "+appNames[0]+" - no access", func(t *testing.T) { prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return false, nil @@ -438,7 +438,7 @@ func TestSearchApplicationsGet(t *testing.T) { prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return true, nil @@ -513,7 +513,7 @@ func TestSearchApplicationsGet(t *testing.T) { t.Run("search for "+appNames[0]+" - no access", func(t *testing.T) { prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewApplicationController( func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { return false, nil @@ -2022,7 +2022,7 @@ func Test_GetUsedResources(t *testing.T) { Return(ts.expectedUsedResources, ts.expectedUsedResourcesError) } validator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(controllertest.NewTestPrincipal(), nil) + validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(controllertest.NewTestPrincipal(true), nil) prometheusHandlerMock := createPrometheusHandlerMock(t, radixClient, &mockHandlerModifier) controllerTestUtils := controllertest.NewTestUtils(kubeClient, radixClient, kedaClient, secretProviderClient, certClient, validator, NewApplicationController( diff --git a/api/buildsecrets/buildsecrets_test.go b/api/buildsecrets/buildsecrets_test.go index 9a17aff2..c5069f45 100644 --- a/api/buildsecrets/buildsecrets_test.go +++ b/api/buildsecrets/buildsecrets_test.go @@ -44,7 +44,7 @@ func setupTest(t *testing.T) (*commontest.Utils, *controllertest.Utils, *kubefak require.NoError(t, err) // controllerTestUtils is used for issuing HTTP request and processing responses mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildSecretsController()) return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient diff --git a/api/buildstatus/build_status_controller_test.go b/api/buildstatus/build_status_controller_test.go index 91bed53f..62c30eb8 100644 --- a/api/buildstatus/build_status_controller_test.go +++ b/api/buildstatus/build_status_controller_test.go @@ -107,7 +107,7 @@ func TestGetBuildStatus(t *testing.T) { Times(1) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus") response := <-responseChannel @@ -137,7 +137,7 @@ func TestGetBuildStatus(t *testing.T) { }) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus") @@ -167,7 +167,7 @@ func TestGetBuildStatus(t *testing.T) { }) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus?pipeline=deploy") @@ -197,7 +197,7 @@ func TestGetBuildStatus(t *testing.T) { }) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus?pipeline=promote") @@ -221,7 +221,7 @@ func TestGetBuildStatus(t *testing.T) { Times(1) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewBuildStatusController(fakeBuildStatus)) responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/my-app/environments/test/buildstatus") diff --git a/api/deployments/deployment_controller_test.go b/api/deployments/deployment_controller_test.go index 3a4c266d..b9204dc7 100644 --- a/api/deployments/deployment_controller_test.go +++ b/api/deployments/deployment_controller_test.go @@ -43,7 +43,7 @@ func setupTest(t *testing.T) (*commontest.Utils, *controllertest.Utils, kubernet commonTestUtils, kubeclient, radixClient, kedaClient, prometheusClient, secretproviderclient, certClient := apiUtils.SetupTest(t) // controllerTestUtils is used for issuing HTTP request and processing responses mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixClient, kedaClient, secretproviderclient, certClient, mockValidator, NewDeploymentController()) return commonTestUtils, &controllerTestUtils, kubeclient, radixClient, kedaClient, prometheusClient, secretproviderclient, certClient } diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index ec156e8c..87ff7c8a 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -82,7 +82,7 @@ func setupTest(t *testing.T, envHandlerOpts []EnvironmentHandlerOptions) (*commo require.NoError(t, err) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) // secretControllerTestUtils is used for issuing HTTP request and processing responses secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixClient, kedaClient, secretproviderclient, certClient, mockValidator, secrets.NewSecretController(nil)) // controllerTestUtils is used for issuing HTTP request and processing responses diff --git a/api/environmentvariables/env_vars_controller_test.go b/api/environmentvariables/env_vars_controller_test.go index 930211df..550fe632 100644 --- a/api/environmentvariables/env_vars_controller_test.go +++ b/api/environmentvariables/env_vars_controller_test.go @@ -46,7 +46,7 @@ func setupTestWithMockHandler(t *testing.T, mockCtrl *gomock.Controller) (*commo handlerFactory.EXPECT().createHandler(gomock.Any()).Return(handler) controller := (&envVarsController{}).withHandlerFactory(handlerFactory) mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) // controllerTestUtils is used for issuing HTTP request and processing responses controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, controller) diff --git a/api/jobs/job_controller_test.go b/api/jobs/job_controller_test.go index 3732cd06..9f8afc1a 100644 --- a/api/jobs/job_controller_test.go +++ b/api/jobs/job_controller_test.go @@ -57,7 +57,7 @@ func setupTest(t *testing.T) (*commontest.Utils, *controllertest.Utils, kubernet // controllerTestUtils is used for issuing HTTP request and processing responses mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) controllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewJobController()) return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, secretproviderclient, certClient diff --git a/api/middleware/auth/anon_principal.go b/api/middleware/auth/anon_principal.go new file mode 100644 index 00000000..f76e64ed --- /dev/null +++ b/api/middleware/auth/anon_principal.go @@ -0,0 +1,8 @@ +package auth + +type anonPrincipal struct{} + +func (p *anonPrincipal) Token() string { return "" } +func (p *anonPrincipal) Id() string { return "anonymous" } +func (p *anonPrincipal) Name() string { return "anonymous" } +func (p *anonPrincipal) IsAuthenticated() bool { return false } diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index 05d0d212..0a6b57dc 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -66,7 +66,7 @@ func CtxTokenPrincipal(ctx context.Context) token.TokenPrincipal { val, ok := ctx.Value(ctxUserKey{}).(token.TokenPrincipal) if !ok { - return token.NewAnonymousPrincipal() + return &anonPrincipal{} } return val diff --git a/api/middleware/auth/authentication_test.go b/api/middleware/auth/authentication_test.go index 81c4b0a6..f57686b4 100644 --- a/api/middleware/auth/authentication_test.go +++ b/api/middleware/auth/authentication_test.go @@ -14,7 +14,6 @@ import ( metricsMock "github.com/equinor/radix-api/api/metrics/mock" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/test/mock" - token "github.com/equinor/radix-api/api/utils/token" authnmock "github.com/equinor/radix-api/api/utils/token/mock" "github.com/equinor/radix-api/internal/config" "github.com/equinor/radix-api/models" @@ -68,7 +67,7 @@ func setupTest(t *testing.T, validator *authnmock.MockValidatorInterface, buildS if validator == nil { validator = authnmock.NewMockValidatorInterface(gomock.NewController(t)) - validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(controllertest.NewTestPrincipal(), nil) + validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(controllertest.NewTestPrincipal(true), nil) } controllerTestUtils := controllertest.NewTestUtils( @@ -134,7 +133,7 @@ func TestGetBuildStatus_AnonymousRequestIsOk(t *testing.T) { // Setup ctrl := gomock.NewController(t) mockValidator := authnmock.NewMockValidatorInterface(ctrl) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Return(token.NewAnonymousPrincipal(), nil).Times(0) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Return(controllertest.NewTestPrincipal(false), nil).Times(0) buildStatusMock := mock.NewMockPipelineBadge(ctrl) buildStatusMock.EXPECT().GetBadge(gomock.Any(), gomock.Any()).Return([]byte("hello world"), errors.New("error")).Times(1) commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, mockValidator, buildStatusMock) @@ -151,7 +150,7 @@ func TestGetBuildStatus_AnonymousRequestIsOk(t *testing.T) { func TestGetApplications_UnauthenticatedIsForbidden(t *testing.T) { // Setup mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(token.NewAnonymousPrincipal(), radixhttp.ForbiddenError("invalid token")) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(controllertest.NewTestPrincipal(false), radixhttp.ForbiddenError("invalid token")) commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, mockValidator, nil) _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) require.NoError(t, err) @@ -167,7 +166,7 @@ func TestGetApplications_UnauthenticatedIsForbidden(t *testing.T) { func TestGetApplications_InvalidTokenIsForbidden(t *testing.T) { // Setup mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(token.NewAnonymousPrincipal(), radixhttp.ForbiddenError("invalid token")) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(controllertest.NewTestPrincipal(false), radixhttp.ForbiddenError("invalid token")) commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, mockValidator, nil) _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) require.NoError(t, err) diff --git a/api/secrets/secret_controller_test.go b/api/secrets/secret_controller_test.go index 8db342a0..6215eca4 100644 --- a/api/secrets/secret_controller_test.go +++ b/api/secrets/secret_controller_test.go @@ -60,7 +60,7 @@ func setupTest(t *testing.T, tlsValidator tlsvalidation.Validator) (*commontest. // secretControllerTestUtils is used for issuing HTTP request and processing responses mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(), nil) + mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).AnyTimes().Return(controllertest.NewTestPrincipal(true), nil) secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, mockValidator, NewSecretController(tlsValidator)) return &commonTestUtils, &secretControllerTestUtils, kubeclient, radixclient, prometheusclient, secretproviderclient diff --git a/api/test/test_principal.go b/api/test/test_principal.go index 09231fb3..f1e250c5 100644 --- a/api/test/test_principal.go +++ b/api/test/test_principal.go @@ -1,14 +1,17 @@ package test -type TestPrincipal struct{} +type TestPrincipal struct{ authenticated bool } func (p *TestPrincipal) Token() string { - return "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MjQ1YTJlYzEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xMjM0NTY3OC03NTY1LTIzNDItMjM0Mi0xMjM0MDViNDU5YjAvIiwiaWF0IjoxNTc1MzU1NTA4LCJuYmYiOjE1NzUzNTU1MDgsImV4cCI6MTU3NTM1OTQwOCwiYWNyIjoiMSIsImFpbyI6IjQyYXNkYXMiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDc5MDM5YTkwIiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJKb2huIiwiZ2l2ZW5fbmFtZSI6IkRvZSIsImhhc2dyb3VwcyI6InRydWUiLCJpcGFkZHIiOiIxMC4xMC4xMC4xMCIsIm5hbWUiOiJKb2huIERvZSIsIm9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzRmYzhmYTBlYSIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0xMjM0NTY3ODktMTIzNDU2OTc4MC0xMjM0NTY3ODktMTIzNDU2NyIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IjBoa2JpbEo3MTIzNHpSU3h6eHZiSW1hc2RmZ3N4amI2YXNkZmVOR2FzZGYiLCJ0aWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MDViNDU5YjAiLCJ1bmlxdWVfbmFtZSI6Im5vdC1leGlzdGluZy1yYWRpeC1lbWFpbEBlcXVpbm9yLmNvbSIsInVwbiI6Im5vdC1leGlzdGluZy10ZXN0LXJhZGl4LWVtYWlsQGVxdWlub3IuY29tIiwidXRpIjoiQlMxMmFzR2R1RXlyZUVjRGN2aDJBRyIsInZlciI6IjEuMCJ9.EB5z7Mk34NkFPCP8MqaNMo4UeWgNyO4-qEmzOVPxfoBqbgA16Ar4xeONXODwjZn9iD-CwJccusW6GP0xZ_PJHBFpfaJO_tLaP1k0KhT-eaANt112TvDBt0yjHtJg6He6CEDqagREIsH3w1mSm40zWLKGZeRLdnGxnQyKsTmNJ1rFRdY3AyoEgf6-pnJweUt0LaFMKmIJ2HornStm2hjUstBaji_5cSS946zqp4tgrc-RzzDuaQXzqlVL2J22SR2S_Oux_3yw88KmlhEFFP9axNcbjZrzW3L9XWnPT6UzVIaVRaNRSWfqDATg-jeHg4Gm1bp8w0aIqLdDxc9CfFMjuQ" + if p.authenticated { + return "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MjQ1YTJlYzEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8xMjM0NTY3OC03NTY1LTIzNDItMjM0Mi0xMjM0MDViNDU5YjAvIiwiaWF0IjoxNTc1MzU1NTA4LCJuYmYiOjE1NzUzNTU1MDgsImV4cCI6MTU3NTM1OTQwOCwiYWNyIjoiMSIsImFpbyI6IjQyYXNkYXMiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDc5MDM5YTkwIiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJKb2huIiwiZ2l2ZW5fbmFtZSI6IkRvZSIsImhhc2dyb3VwcyI6InRydWUiLCJpcGFkZHIiOiIxMC4xMC4xMC4xMCIsIm5hbWUiOiJKb2huIERvZSIsIm9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzRmYzhmYTBlYSIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0xMjM0NTY3ODktMTIzNDU2OTc4MC0xMjM0NTY3ODktMTIzNDU2NyIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IjBoa2JpbEo3MTIzNHpSU3h6eHZiSW1hc2RmZ3N4amI2YXNkZmVOR2FzZGYiLCJ0aWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0MDViNDU5YjAiLCJ1bmlxdWVfbmFtZSI6Im5vdC1leGlzdGluZy1yYWRpeC1lbWFpbEBlcXVpbm9yLmNvbSIsInVwbiI6Im5vdC1leGlzdGluZy10ZXN0LXJhZGl4LWVtYWlsQGVxdWlub3IuY29tIiwidXRpIjoiQlMxMmFzR2R1RXlyZUVjRGN2aDJBRyIsInZlciI6IjEuMCJ9.EB5z7Mk34NkFPCP8MqaNMo4UeWgNyO4-qEmzOVPxfoBqbgA16Ar4xeONXODwjZn9iD-CwJccusW6GP0xZ_PJHBFpfaJO_tLaP1k0KhT-eaANt112TvDBt0yjHtJg6He6CEDqagREIsH3w1mSm40zWLKGZeRLdnGxnQyKsTmNJ1rFRdY3AyoEgf6-pnJweUt0LaFMKmIJ2HornStm2hjUstBaji_5cSS946zqp4tgrc-RzzDuaQXzqlVL2J22SR2S_Oux_3yw88KmlhEFFP9axNcbjZrzW3L9XWnPT6UzVIaVRaNRSWfqDATg-jeHg4Gm1bp8w0aIqLdDxc9CfFMjuQ" + } + return "" } func (p *TestPrincipal) Id() string { return "test-id" } func (p *TestPrincipal) Name() string { return "test-principal" } -func (p *TestPrincipal) IsAuthenticated() bool { return true } +func (p *TestPrincipal) IsAuthenticated() bool { return p.authenticated } -func NewTestPrincipal() *TestPrincipal { - return &TestPrincipal{} +func NewTestPrincipal(authenticated bool) *TestPrincipal { + return &TestPrincipal{authenticated} } diff --git a/api/utils/token/anon_principal.go b/api/utils/token/anon_principal.go deleted file mode 100644 index 441f7c80..00000000 --- a/api/utils/token/anon_principal.go +++ /dev/null @@ -1,12 +0,0 @@ -package token - -type AnonPrincipal struct{} - -func (p *AnonPrincipal) Token() string { return "" } -func (p *AnonPrincipal) Id() string { return "anonymous" } -func (p *AnonPrincipal) Name() string { return "anonymous" } -func (p *AnonPrincipal) IsAuthenticated() bool { return false } - -func NewAnonymousPrincipal() *AnonPrincipal { - return &AnonPrincipal{} -} diff --git a/api/utils/token/azure_principal.go b/api/utils/token/azure_principal.go index f3df7e95..2cce1d28 100644 --- a/api/utils/token/azure_principal.go +++ b/api/utils/token/azure_principal.go @@ -17,21 +17,21 @@ func (c *azureClaims) Validate(_ context.Context) error { return nil } -type AzurePrincipal struct { +type azurePrincipal struct { token string claims *validator.ValidatedClaims azureClaims *azureClaims } -func (p *AzurePrincipal) Token() string { +func (p *azurePrincipal) Token() string { return p.token } -func (p *AzurePrincipal) IsAuthenticated() bool { +func (p *azurePrincipal) IsAuthenticated() bool { return true } -func (p *AzurePrincipal) Id() string { return p.azureClaims.ObjectId } +func (p *azurePrincipal) Id() string { return p.azureClaims.ObjectId } -func (p *AzurePrincipal) Name() string { +func (p *azurePrincipal) Name() string { if p.azureClaims.Upn != "" { return p.azureClaims.Upn } diff --git a/api/utils/token/validator.go b/api/utils/token/validator.go index b9276d89..f72dd461 100644 --- a/api/utils/token/validator.go +++ b/api/utils/token/validator.go @@ -64,6 +64,6 @@ func (v *Validator) ValidateToken(ctx context.Context, token string) (TokenPrinc return nil, http.ForbiddenError("invalid azure token") } - principal := &AzurePrincipal{token: token, claims: claims, azureClaims: azureClaims} + principal := &azurePrincipal{token: token, claims: claims, azureClaims: azureClaims} return principal, nil } diff --git a/go.mod b/go.mod index 2657bd5c..4dac852e 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/equinor/radix-operator v1.61.0 github.com/evanphx/json-patch/v5 v5.9.0 github.com/felixge/httpsnoop v1.0.4 - 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 @@ -25,8 +24,6 @@ require ( github.com/rs/cors v1.11.0 github.com/rs/xid v1.5.0 github.com/rs/zerolog v1.33.0 - github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/tektoncd/pipeline v0.55.0 github.com/urfave/negroni/v3 v3.1.0 @@ -60,6 +57,7 @@ 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 @@ -70,21 +68,17 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect - github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.76.0 // indirect @@ -92,13 +86,8 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -120,7 +109,6 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.31.0 // indirect diff --git a/go.sum b/go.sum index 6bc44f7e..a21e0507 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,6 @@ github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -228,8 +226,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= -github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= @@ -266,8 +262,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0 h1:/M4H/1G4avsieL6BbUwCOBzulmoeKVP5ux/3mQNnbyI= @@ -279,8 +273,6 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -300,8 +292,6 @@ github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -358,30 +348,17 @@ 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= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -389,12 +366,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tektoncd/pipeline v0.55.0 h1:RUfqSC/J1dMrdfu1ThJreHojwGXcWc8P131el/c+c1c= github.com/tektoncd/pipeline v0.55.0/go.mod h1:fFbFAhyNwsPQpitrwhi+Wp0Xse2EkIE1LtGKC08rVqo= github.com/urfave/negroni/v3 v3.1.0 h1:lzmuxGSpnJCT/ujgIAjkU3+LW3NX8alCglO/L6KjIGQ= @@ -747,8 +721,6 @@ gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKK gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From ac4ce10c721adff25d002f2f0dba6bf249fcdb4c Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 10:12:49 +0200 Subject: [PATCH 18/29] rename create authorize middleware --- api/middleware/auth/authentication.go | 2 +- api/router/api.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index 0a6b57dc..5b2274f5 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -91,7 +91,7 @@ func GetOriginator(ctx context.Context) string { return principal.Name() } -func CreateAuthorizeRequiredMiddleware() negroni.HandlerFunc { +func NewAuthorizeRequiredMiddleware() negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { logger := log.Ctx(r.Context()) user := CtxTokenPrincipal(r.Context()) diff --git a/api/router/api.go b/api/router/api.go index b6d9e48d..bc1426b6 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -55,7 +55,7 @@ func createApiRouter(kubeUtil utils.KubeUtil, controllers []models.Controller) * n := negroni.New() if !route.AllowUnauthenticatedUsers { - n.Use(auth.CreateAuthorizeRequiredMiddleware()) + n.Use(auth.NewAuthorizeRequiredMiddleware()) } n.UseHandler(handler) router.Handle(path, n).Methods(route.Method) From 5b3e32e39db8e0cfb959212034db8240fae80ec0 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 10:21:39 +0200 Subject: [PATCH 19/29] create custom middleware for logging authenticated user --- api/middleware/auth/authentication.go | 27 ++++++++++++++++++++++----- api/router/api.go | 1 + 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index 5b2274f5..3d4a9e48 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -39,7 +39,6 @@ func NewAuthenticationMiddleware(validator token.ValidatorInterface) negroni.Han } return } - logContext := log.Ctx(ctx).With().Str("azure_oid", principal.Id()) impersonation, err := radixhttp.GetImpersonationFromHeader(r) if err != nil { @@ -49,13 +48,9 @@ func NewAuthenticationMiddleware(validator token.ValidatorInterface) negroni.Han } return } - if impersonation.PerformImpersonation() { - logContext = logContext.Str("impersonate_user", impersonation.User).Strs("impersonate_groups", impersonation.Groups) - } ctx = context.WithValue(ctx, ctxUserKey{}, principal) ctx = context.WithValue(ctx, ctxImpersonationKey{}, impersonation) - ctx = logContext.Logger().WithContext(ctx) r = r.WithContext(ctx) next(w, r) @@ -91,6 +86,28 @@ func GetOriginator(ctx context.Context) string { return principal.Name() } +func NewZerologAuthenticationDetailsMiddleware() negroni.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + ctx := r.Context() + user := CtxTokenPrincipal(ctx) + impersonation := CtxImpersonation(ctx) + + logContext := log.Ctx(ctx).With() + if user.IsAuthenticated() { + logContext = logContext.Str("azure_oid", user.Id()) + } else { + logContext = logContext.Bool("anonymous", true) + } + if impersonation.PerformImpersonation() { + logContext = logContext.Str("impersonate_user", impersonation.User).Strs("impersonate_groups", impersonation.Groups) + } + ctx = logContext.Logger().WithContext(ctx) + + r = r.WithContext(ctx) + next(w, r) + } +} + func NewAuthorizeRequiredMiddleware() negroni.HandlerFunc { return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { logger := log.Ctx(r.Context()) diff --git a/api/router/api.go b/api/router/api.go index bc1426b6..b15e2068 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -32,6 +32,7 @@ func NewAPIHandler(clusterName string, validator token.ValidatorInterface, radix cors.NewMiddleware(clusterName, radixDNSZone), logger.NewZerologRequestDetailsMiddleware(), auth.NewAuthenticationMiddleware(validator), + auth.NewZerologAuthenticationDetailsMiddleware(), logger.NewZerologResponseLoggerMiddleware(), ) n.UseHandler(serveMux) From 3f6ac7eddb2e8b4b7e281ad067bed271a3f2b794 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 10:24:01 +0200 Subject: [PATCH 20/29] correct logging kube client user/server --- api/utils/kubernetes.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/utils/kubernetes.go b/api/utils/kubernetes.go index 7b288a90..9f34fe6d 100644 --- a/api/utils/kubernetes.go +++ b/api/utils/kubernetes.go @@ -89,7 +89,7 @@ func getUserClientConfig(token string, impersonation radixmodels.Impersonation, kubeConfig.Impersonate = impersonationConfig } kubeConfig.Wrap(logs.Logger(func(e *zerolog.Event) { - e.Str("client", "out-cluster") + e.Str("client", "user") })) return addCommonConfigs(kubeConfig, options) @@ -101,11 +101,11 @@ func getServerClientConfig(options []RestClientConfigOption) *restclient.Config if err != nil { config, err = restclient.InClusterConfig() if err != nil { - log.Fatal().Err(err).Msg("getClusterConfig InClusterConfig") + log.Fatal().Err(err).Msg("failed to create in cluster config") } } config.Wrap(logs.Logger(func(e *zerolog.Event) { - e.Str("client", "in-cluster") + e.Str("client", "server") })) return addCommonConfigs(config, options) From 9a23ed11f79a9c8c08f76805accbf266a1206d8f Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 12:10:54 +0200 Subject: [PATCH 21/29] rewrite authentication middleware test --- .../applications_controller_test.go | 4 +- api/applications/applications_handler.go | 6 +- api/middleware/auth/authentication_test.go | 209 +++++------------- 3 files changed, 65 insertions(+), 154 deletions(-) diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index aab669b9..80ed311c 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -2124,10 +2124,10 @@ func buildApplicationRegistrationRequest(applicationRegistration applicationMode type testApplicationHandlerFactory struct { config config.Config - hasAccessToGetConfigMap HasAccessToGetConfigMapFunc + hasAccessToGetConfigMap hasAccessToGetConfigMapFunc } -func newTestApplicationHandlerFactory(config config.Config, hasAccessToGetConfigMap HasAccessToGetConfigMapFunc) *testApplicationHandlerFactory { +func newTestApplicationHandlerFactory(config config.Config, hasAccessToGetConfigMap hasAccessToGetConfigMapFunc) *testApplicationHandlerFactory { return &testApplicationHandlerFactory{ config: config, hasAccessToGetConfigMap: hasAccessToGetConfigMap, diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index f42c4b66..a98bd761 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -47,7 +47,7 @@ type patch struct { Value interface{} `json:"value"` } -type HasAccessToGetConfigMapFunc func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) +type hasAccessToGetConfigMapFunc func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) // ApplicationHandler Instance variables type ApplicationHandler struct { @@ -55,13 +55,13 @@ type ApplicationHandler struct { accounts models.Accounts config config.Config namespace string - hasAccessToGetConfigMap HasAccessToGetConfigMapFunc + hasAccessToGetConfigMap hasAccessToGetConfigMapFunc tektonImageTag string pipelineImageTag string } // NewApplicationHandler Constructor -func NewApplicationHandler(accounts models.Accounts, config config.Config, hasAccessToGetConfigMap HasAccessToGetConfigMapFunc) ApplicationHandler { +func NewApplicationHandler(accounts models.Accounts, config config.Config, hasAccessToGetConfigMap hasAccessToGetConfigMapFunc) ApplicationHandler { return ApplicationHandler{ environmentHandler: environments.Init(environments.WithAccounts(accounts)), accounts: accounts, diff --git a/api/middleware/auth/authentication_test.go b/api/middleware/auth/authentication_test.go index f57686b4..5fe015a7 100644 --- a/api/middleware/auth/authentication_test.go +++ b/api/middleware/auth/authentication_test.go @@ -4,177 +4,88 @@ import ( "context" "errors" "net/http" - "os" + "net/http/httptest" "testing" - certfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" - "github.com/equinor/radix-api/api/applications" - applicationModels "github.com/equinor/radix-api/api/applications/models" - "github.com/equinor/radix-api/api/buildstatus" - metricsMock "github.com/equinor/radix-api/api/metrics/mock" + "github.com/equinor/radix-api/api/middleware/auth" controllertest "github.com/equinor/radix-api/api/test" - "github.com/equinor/radix-api/api/test/mock" authnmock "github.com/equinor/radix-api/api/utils/token/mock" - "github.com/equinor/radix-api/internal/config" - "github.com/equinor/radix-api/models" - radixhttp "github.com/equinor/radix-common/net/http" - "github.com/equinor/radix-operator/pkg/apis/defaults" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - commontest "github.com/equinor/radix-operator/pkg/apis/test" - builders "github.com/equinor/radix-operator/pkg/apis/utils" - radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" "github.com/golang/mock/gomock" - kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" - prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "k8s.io/client-go/kubernetes" - kubefake "k8s.io/client-go/kubernetes/fake" - secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake" ) -const ( - clusterName = "AnyClusterName" - appAliasDNSZone = "app.dev.radix.equinor.com" - egressIps = "0.0.0.0" - subscriptionId = "12347718-c8f8-4995-bfbb-02655ff1f89c" -) - -func setupTest(t *testing.T, validator *authnmock.MockValidatorInterface, buildStatusMock *mock.MockPipelineBadge) (*commontest.Utils, *controllertest.Utils, *kubefake.Clientset, *radixfake.Clientset, *kedafake.Clientset, *prometheusfake.Clientset, *secretproviderfake.Clientset, *certfake.Clientset) { - // Setup - ctrl := gomock.NewController(t) - kubeclient := kubefake.NewSimpleClientset() - radixclient := radixfake.NewSimpleClientset() - kedaClient := kedafake.NewSimpleClientset() - prometheusclient := prometheusfake.NewSimpleClientset() - secretproviderclient := secretproviderfake.NewSimpleClientset() - certClient := certfake.NewSimpleClientset() - - // commonTestUtils is used for creating CRDs - commonTestUtils := commontest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient) - err := commonTestUtils.CreateClusterPrerequisites(clusterName, egressIps, subscriptionId) - require.NoError(t, err) - _ = os.Setenv(defaults.ActiveClusternameEnvironmentVariable, clusterName) - - // controllerTestUtils is used for issuing HTTP request and processing responses - mockPrometheusHandler := metricsMock.NewMockPrometheusHandler(ctrl) - mockPrometheusHandler.EXPECT().GetUsedResources(gomock.Any(), radixclient, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&applicationModels.UsedResources{}, nil) - - if buildStatusMock == nil { - buildStatusMock = mock.NewMockPipelineBadge(ctrl) - buildStatusMock.EXPECT().GetBadge(gomock.Any(), gomock.Any()).Return([]byte("hello world"), errors.New("error")).AnyTimes() - } - - if validator == nil { - validator = authnmock.NewMockValidatorInterface(gomock.NewController(t)) - validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(controllertest.NewTestPrincipal(true), nil) - } - - controllerTestUtils := controllertest.NewTestUtils( - kubeclient, - radixclient, - kedaClient, - secretproviderclient, - certClient, - validator, - applications.NewApplicationController( - func(_ context.Context, _ kubernetes.Interface, _ v1.RadixRegistration) (bool, error) { - return true, nil - }, - newTestApplicationHandlerFactory( - config.Config{}, - func(ctx context.Context, kubeClient kubernetes.Interface, namespace string, configMapName string) (bool, error) { - return true, nil - }, - ), - mockPrometheusHandler, - ), - buildstatus.NewBuildStatusController(buildStatusMock), - ) - - return &commonTestUtils, &controllerTestUtils, kubeclient, radixclient, kedaClient, prometheusclient, secretproviderclient, certClient +func TestAuthenticatedRequest(t *testing.T) { + validator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + testPrincipal := controllertest.NewTestPrincipal(true) + validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(testPrincipal, nil) + + handler := auth.NewAuthenticationMiddleware(validator) + rw := httptest.NewRecorder() + + req, _ := http.NewRequest("POST", "/api/anyendpoint", nil) + req.Header.Add("Authorization", "Bearer "+testPrincipal.Token()) + var reqCtx context.Context + handler.ServeHTTP(rw, req, func(w http.ResponseWriter, r *http.Request) { + reqCtx = r.Context() + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("hello world")) + }) + reqPrincipal := auth.CtxTokenPrincipal(reqCtx) + assert.Same(t, testPrincipal, reqPrincipal) + assert.Equal(t, http.StatusOK, rw.Code) + assert.Equal(t, "hello world", rw.Body.String()) } -type testApplicationHandlerFactory struct { - config config.Config - hasAccessToGetConfigMap applications.HasAccessToGetConfigMapFunc -} +func TestAnonumousRequest(t *testing.T) { + validator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + testPrincipal := controllertest.NewTestPrincipal(true) + validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(testPrincipal, nil) -func newTestApplicationHandlerFactory(config config.Config, hasAccessToGetConfigMap applications.HasAccessToGetConfigMapFunc) *testApplicationHandlerFactory { - return &testApplicationHandlerFactory{ - config, - hasAccessToGetConfigMap, - } -} + handler := auth.NewAuthenticationMiddleware(validator) + rw := httptest.NewRecorder() -// Create creates a new ApplicationHandler -func (f *testApplicationHandlerFactory) Create(accounts models.Accounts) applications.ApplicationHandler { - return applications.NewApplicationHandler(accounts, f.config, f.hasAccessToGetConfigMap) -} + req, _ := http.NewRequest("POST", "/api/anyendpoint", nil) -func TestGetApplications_AuthenticatedRequestIsOk(t *testing.T) { - // Setup - commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, nil, nil) - _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) - require.NoError(t, err) + handler.ServeHTTP(rw, req, newNullMiddleware()) + assert.Equal(t, http.StatusOK, rw.Code) + assert.Equal(t, "hello world", rw.Body.String()) +} - // Test +func TestInvalidHeader(t *testing.T) { + validator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + testPrincipal := controllertest.NewTestPrincipal(true) + validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(testPrincipal, nil) - responseChannel := controllerTestUtils.ExecuteRequest("GET", "/api/v1/applications") - response := <-responseChannel + handler := auth.NewAuthenticationMiddleware(validator) + rw := httptest.NewRecorder() - applications := make([]applicationModels.ApplicationSummary, 0) - err = controllertest.GetResponseBody(response, &applications) - require.NoError(t, err) - assert.Equal(t, 1, len(applications)) -} + req, _ := http.NewRequest("POST", "/api/anyendpoint", nil) + req.Header.Add("Authorization", "Bearerhello-world") -func TestGetBuildStatus_AnonymousRequestIsOk(t *testing.T) { - // Setup - ctrl := gomock.NewController(t) - mockValidator := authnmock.NewMockValidatorInterface(ctrl) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Return(controllertest.NewTestPrincipal(false), nil).Times(0) - buildStatusMock := mock.NewMockPipelineBadge(ctrl) - buildStatusMock.EXPECT().GetBadge(gomock.Any(), gomock.Any()).Return([]byte("hello world"), errors.New("error")).Times(1) - commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, mockValidator, buildStatusMock) - _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration().WithName("anyapp")) - require.NoError(t, err) - - // Test - - responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications/anyapp/environments/qa/buildstatus") - <-responseChannel - ctrl.Finish() // We expect buildStatusMock to be called 1 time, without auth middleware getting in the way + handler.ServeHTTP(rw, req, newNullMiddleware()) + assert.Equal(t, http.StatusBadRequest, rw.Code) + assert.Contains(t, rw.Body.String(), "Authentication header is invalid") } -func TestGetApplications_UnauthenticatedIsForbidden(t *testing.T) { - // Setup - mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(controllertest.NewTestPrincipal(false), radixhttp.ForbiddenError("invalid token")) - commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, mockValidator, nil) - _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) - require.NoError(t, err) +func TestInvalidToken(t *testing.T) { + validator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) + testPrincipal := controllertest.NewTestPrincipal(true) + validator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(1).Return(testPrincipal, errors.New("some error")) - // Test + handler := auth.NewAuthenticationMiddleware(validator) + rw := httptest.NewRecorder() - responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications") - response := <-responseChannel + req, _ := http.NewRequest("POST", "/api/anyendpoint", nil) + req.Header.Add("Authorization", "Bearer hello-world") - assert.Equal(t, http.StatusForbidden, response.Code) + handler.ServeHTTP(rw, req, newNullMiddleware()) + assert.Equal(t, http.StatusBadRequest, rw.Code) + assert.Contains(t, rw.Body.String(), "some error") } -func TestGetApplications_InvalidTokenIsForbidden(t *testing.T) { - // Setup - mockValidator := authnmock.NewMockValidatorInterface(gomock.NewController(t)) - mockValidator.EXPECT().ValidateToken(gomock.Any(), gomock.Any()).Times(0).Return(controllertest.NewTestPrincipal(false), radixhttp.ForbiddenError("invalid token")) - commonTestUtils, controllerTestUtils, _, _, _, _, _, _ := setupTest(t, mockValidator, nil) - _, err := commonTestUtils.ApplyRegistration(builders.ARadixRegistration()) - require.NoError(t, err) - - // Test - - responseChannel := controllerTestUtils.ExecuteUnAuthorizedRequest("GET", "/api/v1/applications") - response := <-responseChannel - - assert.Equal(t, http.StatusForbidden, response.Code) +func newNullMiddleware() func(writer http.ResponseWriter, request *http.Request) { + return func(writer http.ResponseWriter, request *http.Request) { + writer.WriteHeader(http.StatusOK) + _, _ = writer.Write([]byte("hello world")) + } } From 5d312f711b5d9dd786b9b566f58e06c700ec4f3f Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 12:13:32 +0200 Subject: [PATCH 22/29] rewrite authentication middleware test --- api/middleware/auth/authentication_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/api/middleware/auth/authentication_test.go b/api/middleware/auth/authentication_test.go index 5fe015a7..7aa39c67 100644 --- a/api/middleware/auth/authentication_test.go +++ b/api/middleware/auth/authentication_test.go @@ -22,14 +22,17 @@ func TestAuthenticatedRequest(t *testing.T) { handler := auth.NewAuthenticationMiddleware(validator) rw := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/api/anyendpoint", nil) - req.Header.Add("Authorization", "Bearer "+testPrincipal.Token()) var reqCtx context.Context - handler.ServeHTTP(rw, req, func(w http.ResponseWriter, r *http.Request) { + nextFn := func(w http.ResponseWriter, r *http.Request) { reqCtx = r.Context() w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("hello world")) - }) + } + + req, _ := http.NewRequest("POST", "/api/anyendpoint", nil) + req.Header.Add("Authorization", "Bearer "+testPrincipal.Token()) + handler.ServeHTTP(rw, req, nextFn) + reqPrincipal := auth.CtxTokenPrincipal(reqCtx) assert.Same(t, testPrincipal, reqPrincipal) assert.Equal(t, http.StatusOK, rw.Code) @@ -45,8 +48,8 @@ func TestAnonumousRequest(t *testing.T) { rw := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/anyendpoint", nil) - handler.ServeHTTP(rw, req, newNullMiddleware()) + assert.Equal(t, http.StatusOK, rw.Code) assert.Equal(t, "hello world", rw.Body.String()) } From 3f42521598a3b165fb03fe3bc81bbbfddf756d88 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 14:09:01 +0200 Subject: [PATCH 23/29] rename appNamespace --- api/applications/applications_handler.go | 20 ++++++-------------- api/middleware/auth/authentication.go | 2 +- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/api/applications/applications_handler.go b/api/applications/applications_handler.go index a98bd761..eeab8f2b 100644 --- a/api/applications/applications_handler.go +++ b/api/applications/applications_handler.go @@ -54,7 +54,6 @@ type ApplicationHandler struct { environmentHandler environments.EnvironmentHandler accounts models.Accounts config config.Config - namespace string hasAccessToGetConfigMap hasAccessToGetConfigMapFunc tektonImageTag string pipelineImageTag string @@ -66,20 +65,12 @@ func NewApplicationHandler(accounts models.Accounts, config config.Config, hasAc environmentHandler: environments.Init(environments.WithAccounts(accounts)), accounts: accounts, config: config, - namespace: getApiNamespace(config), hasAccessToGetConfigMap: hasAccessToGetConfigMap, tektonImageTag: config.TektonImageTag, pipelineImageTag: config.PipelineImageTag, } } -func getApiNamespace(config config.Config) string { - if namespace := operatorUtils.GetEnvironmentNamespace(config.AppName, config.EnvironmentName); len(namespace) > 0 { - return namespace - } - panic("missing RADIX_APP or RADIX_ENVIRONMENT environment variables") -} - func (ah *ApplicationHandler) getUserAccount() models.Account { return ah.accounts.UserAccount } @@ -747,31 +738,32 @@ func (ah *ApplicationHandler) validateUserIsMemberOfAdGroups(ctx context.Context } return nil } + radixApiAppNamespace := operatorUtils.GetEnvironmentNamespace(ah.config.AppName, ah.config.EnvironmentName) name := fmt.Sprintf("access-validation-%s", appName) labels := map[string]string{"radix-access-validation": "true"} configMapName := fmt.Sprintf("%s-%s", name, strings.ToLower(operatorUtils.RandString(6))) - role, err := createRoleToGetConfigMap(ctx, ah.accounts.ServiceAccount.Client, ah.namespace, name, labels, configMapName) + role, err := createRoleToGetConfigMap(ctx, ah.accounts.ServiceAccount.Client, radixApiAppNamespace, name, labels, configMapName) if err != nil { return err } defer func() { - err = deleteRole(context.Background(), ah.accounts.ServiceAccount.Client, ah.namespace, role.GetName()) + err = deleteRole(context.Background(), ah.accounts.ServiceAccount.Client, radixApiAppNamespace, role.GetName()) if err != nil { log.Ctx(ctx).Warn().Msgf("Failed to delete role %s: %v", role.GetName(), err) } }() - roleBinding, err := createRoleBindingForRole(ctx, ah.accounts.ServiceAccount.Client, ah.namespace, role, name, adGroups, labels) + roleBinding, err := createRoleBindingForRole(ctx, ah.accounts.ServiceAccount.Client, radixApiAppNamespace, role, name, adGroups, labels) if err != nil { return err } defer func() { - err = deleteRoleBinding(context.Background(), ah.accounts.ServiceAccount.Client, ah.namespace, roleBinding.GetName()) + err = deleteRoleBinding(context.Background(), ah.accounts.ServiceAccount.Client, radixApiAppNamespace, roleBinding.GetName()) if err != nil { log.Ctx(ctx).Warn().Msgf("Failed to delete role binding %s: %v", roleBinding.GetName(), err) } }() - valid, err := ah.hasAccessToGetConfigMap(ctx, ah.accounts.UserAccount.Client, ah.namespace, configMapName) + valid, err := ah.hasAccessToGetConfigMap(ctx, ah.accounts.UserAccount.Client, radixApiAppNamespace, configMapName) if err != nil { return err } diff --git a/api/middleware/auth/authentication.go b/api/middleware/auth/authentication.go index 3d4a9e48..ac2ce272 100644 --- a/api/middleware/auth/authentication.go +++ b/api/middleware/auth/authentication.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - token "github.com/equinor/radix-api/api/utils/token" + "github.com/equinor/radix-api/api/utils/token" "github.com/equinor/radix-common/models" radixhttp "github.com/equinor/radix-common/net/http" "github.com/rs/zerolog/log" From 5590d1cf1835620c0f1a454058aa0ceb3936a3de Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 14:43:09 +0200 Subject: [PATCH 24/29] Add config --- .vscode/launch.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 9fe68eba..190667ee 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,7 +25,9 @@ "PROMETHEUS_URL":"http://localhost:9091", "RADIX_APP":"radix-api", "LOG_LEVEL":"info", - "LOG_PRETTY":"true" + "LOG_PRETTY":"true", + "OIDC_AUDIENCE": "6dae42f8-4368-4678-94ff-3960e28e3630", + "OIDC_ISSUER": "https://sts.windows.net/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/" } } ] From 2c3f3ddf2c6fac878bf0752dda26fd84b8db8138 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 14:44:08 +0200 Subject: [PATCH 25/29] Add config --- radixconfig.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/radixconfig.yaml b/radixconfig.yaml index 91269a0a..bb333a1a 100644 --- a/radixconfig.yaml +++ b/radixconfig.yaml @@ -35,6 +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/" environmentConfig: - environment: qa horizontalScaling: From 23ea68b4f7bbad35f75c8ac879c1261d61aa749c Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 15:11:05 +0200 Subject: [PATCH 26/29] Use k8s host from kube config file --- api/utils/kubernetes.go | 16 ++++++++-------- internal/config/config.go | 1 - main.go | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/api/utils/kubernetes.go b/api/utils/kubernetes.go index 9f34fe6d..5031ab82 100644 --- a/api/utils/kubernetes.go +++ b/api/utils/kubernetes.go @@ -42,9 +42,7 @@ type KubeUtil interface { GetServerKubernetesClient(...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) } -type kubeUtil struct { - kubeApiServer string -} +type kubeUtil struct{} var ( nrRequests = promauto.NewHistogramVec(prometheus.HistogramOpts{ @@ -55,13 +53,13 @@ var ( ) // NewKubeUtil Constructor -func NewKubeUtil(kubeApiServer string) KubeUtil { - return &kubeUtil{kubeApiServer} +func NewKubeUtil() KubeUtil { + return &kubeUtil{} } // GetUserKubernetesClient Gets a kubernetes client using the bearer token from the radix api client func (ku *kubeUtil) GetUserKubernetesClient(token string, impersonation radixmodels.Impersonation, options ...RestClientConfigOption) (kubernetes.Interface, radixclient.Interface, kedav2.Interface, secretproviderclient.Interface, tektonclient.Interface, certclient.Interface) { - config := getUserClientConfig(token, impersonation, ku.kubeApiServer, options) + config := getUserClientConfig(token, impersonation, options) return getKubernetesClientFromConfig(config) } @@ -71,9 +69,11 @@ func (ku *kubeUtil) GetServerKubernetesClient(options ...RestClientConfigOption) return getKubernetesClientFromConfig(config) } -func getUserClientConfig(token string, impersonation radixmodels.Impersonation, kubeApiServer string, options []RestClientConfigOption) *restclient.Config { +func getUserClientConfig(token string, impersonation radixmodels.Impersonation, options []RestClientConfigOption) *restclient.Config { + cfg := getServerClientConfig(options) + kubeConfig := &restclient.Config{ - Host: kubeApiServer, + Host: cfg.Host, BearerToken: token, TLSClientConfig: restclient.TLSClientConfig{ Insecure: true, diff --git a/internal/config/config.go b/internal/config/config.go index 5a17b9a9..7b233c65 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,7 +10,6 @@ type Config struct { MetricsPort int `envconfig:"METRICS_PORT" default:"9090" desc:"Port where Metrics will be served"` ProfilePort int `envconfig:"PROFILE_PORT" default:"7070" desc:"Port where Profiler will be served"` UseProfiler bool `envconfig:"USE_PROFILER" default:"false" desc:"Enable Profiler"` - KubernetesApiServer string `envconfig:"K8S_API_HOST" default:"https://kubernetes.default.svc"` PipelineImageTag string `envconfig:"PIPELINE_IMG_TAG" default:"latest"` TektonImageTag string `envconfig:"TEKTON_IMG_TAG" default:"release-latest"` RequireAppConfigurationItem bool `envconfig:"REQUIRE_APP_CONFIGURATION_ITEM" default:"true"` diff --git a/main.go b/main.go index a8b1cff0..f0388e58 100644 --- a/main.go +++ b/main.go @@ -67,7 +67,7 @@ func initializeServer(c config.Config) *http.Server { log.Fatal().Err(err).Msgf("failed to initialize controllers: %v", err) } - handler := router.NewAPIHandler(c.ClusterName, jwtValidator, c.DNSZone, utils.NewKubeUtil(c.KubernetesApiServer), controllers...) + handler := router.NewAPIHandler(c.ClusterName, jwtValidator, c.DNSZone, utils.NewKubeUtil(), controllers...) srv := &http.Server{ Addr: fmt.Sprintf(":%d", c.Port), Handler: handler, From 3391293ddb604a6a5060f6661a7ffee041083869 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 15:11:54 +0200 Subject: [PATCH 27/29] Use k8s host from kube config file --- .vscode/launch.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 190667ee..40125ec4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,6 @@ "RADIX_CONTAINER_REGISTRY":"radixdev.azurecr.io", "PIPELINE_IMG_TAG": "master-latest", "TEKTON_IMG_TAG": "main-latest", - "K8S_API_HOST": "https://weekly-24-clusters-dev-16ede4-uk527vqt.hcp.northeurope.azmk8s.io:443", "RADIX_CLUSTER_TYPE": "development", "RADIX_DNS_ZONE": "dev.radix.equinor.com", "RADIX_CLUSTERNAME": "weekly-24", From fec8777012d33a1513eb969150a363e3f5d68fc9 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 15:33:47 +0200 Subject: [PATCH 28/29] disable CORS middlewawre --- api/router/api.go | 7 +++---- api/test/utils.go | 4 ++-- main.go | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/api/router/api.go b/api/router/api.go index b15e2068..495ef556 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/equinor/radix-api/api/middleware/auth" - "github.com/equinor/radix-api/api/middleware/cors" "github.com/equinor/radix-api/api/middleware/logger" "github.com/equinor/radix-api/api/middleware/recovery" "github.com/equinor/radix-api/api/utils" @@ -20,8 +19,9 @@ const ( ) // NewAPIHandler Constructor function -func NewAPIHandler(clusterName string, validator token.ValidatorInterface, radixDNSZone string, kubeUtil utils.KubeUtil, controllers ...models.Controller) http.Handler { +func NewAPIHandler(validator token.ValidatorInterface, kubeUtil utils.KubeUtil, controllers ...models.Controller) http.Handler { serveMux := http.NewServeMux() + serveMux.Handle("/health/", createHealthHandler()) serveMux.Handle("/swaggerui/", createSwaggerHandler()) serveMux.Handle("/api/", createApiRouter(kubeUtil, controllers)) @@ -29,7 +29,6 @@ func NewAPIHandler(clusterName string, validator token.ValidatorInterface, radix n := negroni.New( recovery.NewMiddleware(), logger.NewZerologRequestIdMiddleware(), - cors.NewMiddleware(clusterName, radixDNSZone), logger.NewZerologRequestDetailsMiddleware(), auth.NewAuthenticationMiddleware(validator), auth.NewZerologAuthenticationDetailsMiddleware(), @@ -67,7 +66,7 @@ func createApiRouter(kubeUtil utils.KubeUtil, controllers []models.Controller) * func createSwaggerHandler() http.Handler { swaggerFsHandler := http.FileServer(http.FS(swaggerui.FS())) - swaggerui := http.StripPrefix("swaggerui", swaggerFsHandler) + swaggerui := http.StripPrefix("/swaggerui", swaggerFsHandler) return swaggerui } diff --git a/api/test/utils.go b/api/test/utils.go index bebc4a1b..3317d47d 100644 --- a/api/test/utils.go +++ b/api/test/utils.go @@ -69,7 +69,7 @@ func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string) <-chan *htt go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", tu.validator, "", NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler(tu.validator, NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) response <- rr }() @@ -93,7 +93,7 @@ func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameter go func() { rr := httptest.NewRecorder() defer close(response) - router.NewAPIHandler("anyClusterName", tu.validator, radixDNSZone, NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) + router.NewAPIHandler(tu.validator, NewKubeUtilMock(tu.kubeClient, tu.radixClient, tu.kedaClient, tu.secretProviderClient, tu.certClient), tu.controllers...).ServeHTTP(rr, req) response <- rr }() diff --git a/main.go b/main.go index f0388e58..65a4f041 100644 --- a/main.go +++ b/main.go @@ -67,7 +67,7 @@ func initializeServer(c config.Config) *http.Server { log.Fatal().Err(err).Msgf("failed to initialize controllers: %v", err) } - handler := router.NewAPIHandler(c.ClusterName, jwtValidator, c.DNSZone, utils.NewKubeUtil(), controllers...) + handler := router.NewAPIHandler(jwtValidator, utils.NewKubeUtil(), controllers...) srv := &http.Server{ Addr: fmt.Sprintf(":%d", c.Port), Handler: handler, From 8378ebe56236222147a8097a4b5b237b6a8cc84a Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 8 Oct 2024 15:38:49 +0200 Subject: [PATCH 29/29] remove unused constant --- api/test/utils.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/test/utils.go b/api/test/utils.go index 3317d47d..42c3714a 100644 --- a/api/test/utils.go +++ b/api/test/utils.go @@ -40,8 +40,6 @@ type Utils struct { validator token.ValidatorInterface } -const radixDNSZone = "fakezone.radix.com" - // NewTestUtils Constructor func NewTestUtils(kubeClient *kubernetesfake.Clientset, radixClient *radixclientfake.Clientset, kedaClient *kedafake.Clientset, secretProviderClient *secretsstorevclientfake.Clientset, certClient *certclientfake.Clientset, validator *authnmock.MockValidatorInterface, controllers ...models.Controller) Utils { return Utils{