diff --git a/.golangci.yml b/.golangci.yml index cce4ac23..d89f1da2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,56 +1,79 @@ -linters-settings: - dupl: - threshold: 100 - goconst: - min-len: 2 - min-occurrences: 2 - cyclop: - max-complexity: 27 - gocognit: - min-complexity: 50 - gci: - sections: - - standard - - default - - prefix(github.com/projectcapsule/capsule-proxy) - gofumpt: - module-path: github.com/projectcapsule/capsule-proxy - extra-rules: false - inamedparam: - # Skips check for interface methods with only a single parameter. - # Default: false - skip-single-param: true - nakedret: - # Make an issue if func has more lines of code than this setting, and it has naked returns. - max-func-lines: 50 +version: "2" +run: + tests: false + allow-parallel-runners: true linters: - enable-all: true + default: all disable: - - err113 - depguard - - perfsprint + - err113 + - exhaustruct - funlen + - gochecknoglobals - gochecknoinits + - ireturn - lll - - gochecknoglobals - mnd - nilnil - - recvcheck - - unparam + - nonamedreturns - paralleltest - - ireturn + - perfsprint + - recvcheck - testpackage + - unparam - varnamelen - wrapcheck - - exhaustruct - - nonamedreturns -issues: - exclude-files: - - "zz_.*\\.go$" - - ".+\\.generated.go" - - ".+_test.go" - - ".+_test_.+.go" -run: - timeout: 3m - allow-parallel-runners: true - tests: false + settings: + cyclop: + max-complexity: 27 + dupl: + threshold: 100 + gocognit: + min-complexity: 50 + goconst: + min-len: 2 + min-occurrences: 2 + inamedparam: + skip-single-param: true + nakedret: + max-func-lines: 50 + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - zz_.*\.go$ + - .+\.generated.go + - .+_test.go + - .+_test_.+.go + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + settings: + gci: + sections: + - standard + - default + - prefix(github.com/projectcapsule/capsule-proxy) + gofumpt: + module-path: github.com/projectcapsule/capsule-proxy + extra-rules: false + exclusions: + generated: lax + paths: + - zz_.*\.go$ + - .+\.generated.go + - .+_test.go + - .+_test_.+.go + - third_party$ + - builtin$ + - examples$ diff --git a/.goreleaser.yml b/.goreleaser.yml index bf49ad5b..9b5e3750 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -44,9 +44,9 @@ release: - `ghcr.io/projectcapsule/charts/{{ .ProjectName }}:{{ .Version }}` - **Kubernetes compatibility** - > [!IMPORTANT] + > **Kubernetes compatibility** + > > Note that the Capsule project offers support only for the latest minor version of Kubernetes. > Backwards compatibility with older versions of Kubernetes and OpenShift is [offered by supporters](https://projectcapsule.dev/support/). > @@ -63,7 +63,6 @@ changelog: filters: exclude: - '^test:' - - '^chore' - '^rebase:' - 'merge conflict' - Merge pull request @@ -71,26 +70,27 @@ changelog: - Merge branch groups: # https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional - - title: '🛠 Dependency updates' - regexp: '^.*?(feat|fix)\(deps\)!?:.+$' - order: 300 - - title: '✨ New Features' - regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' - order: 100 - - title: '🐛 Bug fixes' - regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$' - order: 200 - - title: '📖 Documentation updates' - regexp: ^.*?docs(\([[:word:]]+\))??!?:.+$ - order: 400 - - title: '🛡️ Security updates' - regexp: ^.*?(sec)(\([[:word:]]+\))??!?:.+$ - order: 500 - - title: '🚀 Build process updates' - regexp: ^.*?(build|ci)(\([[:word:]]+\))??!?:.+$ - order: 600 - - title: '📦 Other work' - order: 9999 + - title: '🛠 Dependency updates' + regexp: '^fix\(deps\):|^feat\(deps\):' + order: 300 + - title: '✨ New Features' + regexp: '^feat(\([^)]*\))?:' + order: 100 + - title: '🐛 Bug fixes' + regexp: '^fix(\([^)]*\))?:' + order: 200 + - title: '📖 Documentation updates' + regexp: '^docs(\([^)]*\))?:' + order: 400 + - title: '🛡️ Security updates' + regexp: '^sec(\([^)]*\))?:' + order: 500 + - title: '🚀 Build process updates' + regexp: '^(build|ci)(\([^)]*\))?:' + order: 600 + - title: '📦 Other work' + regexp: '^chore(\([^)]*\))?:|^chore:' + order: 9999 sboms: - artifacts: archive signs: diff --git a/Makefile b/Makefile index 6fb89bbf..6c0b0481 100644 --- a/Makefile +++ b/Makefile @@ -341,11 +341,11 @@ ko: $(call go-install-tool,$(KO),github.com/$(KO_LOOKUP)@$(KO_VERSION)) GOLANGCI_LINT := $(LOCALBIN)/golangci-lint -GOLANGCI_LINT_VERSION := v1.64.8 +GOLANGCI_LINT_VERSION := v2.1.6 GOLANGCI_LINT_LOOKUP := golangci/golangci-lint golangci-lint: ## Download golangci-lint locally if necessary. @test -s $(GOLANGCI_LINT) && $(GOLANGCI_LINT) -h | grep -q $(GOLANGCI_LINT_VERSION) || \ - $(call go-install-tool,$(GOLANGCI_LINT),github.com/$(GOLANGCI_LINT_LOOKUP)/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/$(GOLANGCI_LINT_LOOKUP)/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)) # go-install-tool will 'go install' any package $2 and install it to $1. PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) diff --git a/internal/controllers/watchdog/crds_watcher.go b/internal/controllers/watchdog/crds_watcher.go index 301d3794..c7c2e9d1 100644 --- a/internal/controllers/watchdog/crds_watcher.go +++ b/internal/controllers/watchdog/crds_watcher.go @@ -35,50 +35,6 @@ type CRDWatcher struct { requeue chan event.GenericEvent } -func (c *CRDWatcher) keyFunction(group, kind string) string { - return fmt.Sprintf("%s-%s", group, kind) -} - -func (c *CRDWatcher) register(ctx context.Context, group string, versions []string, kind string) error { - mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: c.Client.Scheme(), - Metrics: metricsserver.Options{ - BindAddress: "0", - }, - }) - - watchedVersions := sets.New[string]() - - for _, v := range versions { - watchedVersions.Insert(v) - - gvk := metav1.GroupVersionKind{ - Group: group, - Version: v, - Kind: kind, - } - //nolint:contextcheck - if err := (&NamespacedWatcher{Client: c.Client}).SetupWithManager(mgr, gvk); err != nil { - return err - } - } - - scopedCtx, scopedCancelFn := context.WithCancel(ctx) - - go func() { - if err := mgr.Start(scopedCtx); err != nil { - scopedCancelFn() - } - }() - - c.watchMap[c.keyFunction(group, kind)] = resourceManager{ - cancelFn: scopedCancelFn, - watchedVersions: watchedVersions, - } - - return nil -} - func (c *CRDWatcher) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { crd := apiextensionsv1.CustomResourceDefinition{} if err := c.Client.Get(ctx, request.NamespacedName, &crd); err != nil { @@ -156,7 +112,7 @@ func (c *CRDWatcher) SetupWithManager(ctx context.Context, mgr manager.Manager) apiGroup, apiKind := parts[0], parts[1] if registerErr := c.register(ctx, apiGroup, versions.UnsortedList(), apiKind); registerErr != nil { - return errors.Wrap(err, "cannot register watcher prior to start-up") + return errors.Wrap(registerErr, "cannot register watcher prior to start-up") } } @@ -170,3 +126,47 @@ func (c *CRDWatcher) SetupWithManager(ctx context.Context, mgr manager.Manager) }))). Complete(c) } + +func (c *CRDWatcher) keyFunction(group, kind string) string { + return fmt.Sprintf("%s-%s", group, kind) +} + +func (c *CRDWatcher) register(ctx context.Context, group string, versions []string, kind string) error { + mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: c.Client.Scheme(), + Metrics: metricsserver.Options{ + BindAddress: "0", + }, + }) + + watchedVersions := sets.New[string]() + + for _, v := range versions { + watchedVersions.Insert(v) + + gvk := metav1.GroupVersionKind{ + Group: group, + Version: v, + Kind: kind, + } + //nolint:contextcheck + if err := (&NamespacedWatcher{Client: c.Client}).SetupWithManager(mgr, gvk); err != nil { + return err + } + } + + scopedCtx, scopedCancelFn := context.WithCancel(ctx) + + go func() { + if err := mgr.Start(scopedCtx); err != nil { + scopedCancelFn() + } + }() + + c.watchMap[c.keyFunction(group, kind)] = resourceManager{ + cancelFn: scopedCancelFn, + watchedVersions: watchedVersions, + } + + return nil +} diff --git a/internal/modules/ingressclass/utils.go b/internal/modules/ingressclass/utils.go index 90d44afb..3f9bc857 100644 --- a/internal/modules/ingressclass/utils.go +++ b/internal/modules/ingressclass/utils.go @@ -37,19 +37,19 @@ func getIngressClasses(request *http.Request, proxyTenants []*tenant.ProxyTenant continue } - if len(ic.SelectorAllowedListSpec.Exact) > 0 { - exact = append(exact, ic.SelectorAllowedListSpec.Exact...) + if len(ic.Exact) > 0 { + exact = append(exact, ic.Exact...) } if len(ic.Default) > 0 { exact = append(exact, ic.Default) } - if r := ic.SelectorAllowedListSpec.Regex; len(r) > 0 { + if r := ic.Regex; len(r) > 0 { regex = append(regex, regexp.MustCompile(r)) } - selector, err := metav1.LabelSelectorAsSelector(&ic.SelectorAllowedListSpec.LabelSelector) + selector, err := metav1.LabelSelectorAsSelector(&ic.LabelSelector) if err != nil { continue } diff --git a/internal/modules/priorityclass/utils.go b/internal/modules/priorityclass/utils.go index b5faa62d..35297ef5 100644 --- a/internal/modules/priorityclass/utils.go +++ b/internal/modules/priorityclass/utils.go @@ -34,19 +34,19 @@ func getPriorityClass(req *http.Request, proxyTenants []*tenant.ProxyTenant) (al continue } - if len(pc.SelectorAllowedListSpec.Exact) > 0 { - exact = append(exact, pc.SelectorAllowedListSpec.Exact...) + if len(pc.Exact) > 0 { + exact = append(exact, pc.Exact...) } if len(pc.Default) > 0 { exact = append(exact, pc.Default) } - if r := pc.SelectorAllowedListSpec.Regex; len(r) > 0 { + if r := pc.Regex; len(r) > 0 { regex = append(regex, regexp.MustCompile(r)) } - selector, err := metav1.LabelSelectorAsSelector(&pc.SelectorAllowedListSpec.LabelSelector) + selector, err := metav1.LabelSelectorAsSelector(&pc.LabelSelector) if err != nil { continue } diff --git a/internal/modules/storageclass/utils.go b/internal/modules/storageclass/utils.go index 09102abf..f3973785 100644 --- a/internal/modules/storageclass/utils.go +++ b/internal/modules/storageclass/utils.go @@ -34,19 +34,19 @@ func getStorageClasses(req *http.Request, proxyTenants []*tenant.ProxyTenant) (a continue } - if len(sc.SelectorAllowedListSpec.Exact) > 0 { - exact = append(exact, sc.SelectorAllowedListSpec.Exact...) + if len(sc.Exact) > 0 { + exact = append(exact, sc.Exact...) } if len(sc.Default) > 0 { exact = append(exact, sc.Default) } - if r := sc.SelectorAllowedListSpec.Regex; len(r) > 0 { + if r := sc.Regex; len(r) > 0 { regex = append(regex, regexp.MustCompile(r)) } - selector, err := metav1.LabelSelectorAsSelector(&sc.SelectorAllowedListSpec.LabelSelector) + selector, err := metav1.LabelSelectorAsSelector(&sc.LabelSelector) if err != nil { continue } diff --git a/internal/request/http.go b/internal/request/http.go index 9a354313..4995bd8a 100644 --- a/internal/request/http.go +++ b/internal/request/http.go @@ -66,7 +66,7 @@ func (h http) GetUserAndGroups() (username string, groups []string, err error) { Groups: groups, }, } - if err = h.client.Create(h.Request.Context(), ac); err != nil { + if err = h.client.Create(h.Context(), ac); err != nil { return "", nil, err } @@ -95,7 +95,7 @@ func (h http) GetUserAndGroups() (username string, groups []string, err error) { Groups: groups, }, } - if err = h.client.Create(h.Request.Context(), ac); err != nil { + if err = h.client.Create(h.Context(), ac); err != nil { return "", nil, err } @@ -139,7 +139,7 @@ func (h http) processBearerToken() (username string, groups []string, err error) }, } - if err = h.client.Create(h.Request.Context(), tr); err != nil { + if err = h.client.Create(h.Context(), tr); err != nil { return "", nil, fmt.Errorf("cannot create TokenReview") } diff --git a/internal/webserver/webserver.go b/internal/webserver/webserver.go index 07e62dfa..4318310a 100644 --- a/internal/webserver/webserver.go +++ b/internal/webserver/webserver.go @@ -113,6 +113,74 @@ type kubeFilter struct { writer client.Writer } +//nolint:funlen +func (n *kubeFilter) Start(ctx context.Context) error { + r := mux.NewRouter() + r.Use(handlers.RecoveryHandler()) + + r.Path("/_healthz").Subrouter().HandleFunc("", func(writer http.ResponseWriter, _ *http.Request) { + writer.WriteHeader(http.StatusOK) + _, _ = writer.Write([]byte("ok")) + }) + + root := r.PathPrefix("").Subrouter() + n.registerModules(ctx, root) + root.Use( + n.reverseProxyMiddleware, + middleware.CheckPaths(n.log, n.allowedPaths, n.impersonateHandler), + middleware.CheckJWTMiddleware(n.writer), + ) + root.PathPrefix("/").HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + n.impersonateHandler(writer, request) + }) + + var srv *http.Server + + go func() { + var err error + + addr := fmt.Sprintf("0.0.0.0:%d", n.serverOptions.ListeningPort()) + + if n.serverOptions.IsListeningTLS() { + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + ClientCAs: n.serverOptions.GetCertificateAuthorityPool(), + } + + for _, authType := range n.authTypes { + if authType == req.TLSCertificate { + tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven + + break + } + } + + srv = &http.Server{ + Handler: r, + Addr: addr, + TLSConfig: tlsConfig, + ReadHeaderTimeout: 5 * time.Second, + } + err = srv.ListenAndServeTLS(n.serverOptions.TLSCertificatePath(), n.serverOptions.TLSCertificateKeyPath()) + } else { + srv = &http.Server{ + Handler: r, + Addr: addr, + ReadHeaderTimeout: 5 * time.Second, + } + err = srv.ListenAndServe() + } + + if err != nil { + panic(err) + } + }() + + <-ctx.Done() + + return srv.Shutdown(ctx) +} + func (n *kubeFilter) LivenessProbe(*http.Request) error { return nil } @@ -158,6 +226,17 @@ func (n *kubeFilter) ReadinessProbe(req *http.Request) (err error) { return nil } +func (n *kubeFilter) BearerToken() string { + if time.Now().After(n.bearerTokenExpirationTime) { + n.log.V(5).Info("Token expired. Reading new token from file", "token", n.bearerToken, "token file", n.bearerTokenFile) + token, _ := os.ReadFile(n.bearerTokenFile) + n.bearerToken = string(token) + n.bearerTokenExpirationTime = bearerExpirationTime(string(token)) + } + + return n.bearerToken +} + func (n *kubeFilter) reverseProxyMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { next.ServeHTTP(writer, request) @@ -346,74 +425,6 @@ func (n *kubeFilter) registerModules(ctx context.Context, root *mux.Router) { } } -//nolint:funlen -func (n *kubeFilter) Start(ctx context.Context) error { - r := mux.NewRouter() - r.Use(handlers.RecoveryHandler()) - - r.Path("/_healthz").Subrouter().HandleFunc("", func(writer http.ResponseWriter, _ *http.Request) { - writer.WriteHeader(http.StatusOK) - _, _ = writer.Write([]byte("ok")) - }) - - root := r.PathPrefix("").Subrouter() - n.registerModules(ctx, root) - root.Use( - n.reverseProxyMiddleware, - middleware.CheckPaths(n.log, n.allowedPaths, n.impersonateHandler), - middleware.CheckJWTMiddleware(n.writer), - ) - root.PathPrefix("/").HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - n.impersonateHandler(writer, request) - }) - - var srv *http.Server - - go func() { - var err error - - addr := fmt.Sprintf("0.0.0.0:%d", n.serverOptions.ListeningPort()) - - if n.serverOptions.IsListeningTLS() { - tlsConfig := &tls.Config{ - MinVersion: tls.VersionTLS12, - ClientCAs: n.serverOptions.GetCertificateAuthorityPool(), - } - - for _, authType := range n.authTypes { - if authType == req.TLSCertificate { - tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven - - break - } - } - - srv = &http.Server{ - Handler: r, - Addr: addr, - TLSConfig: tlsConfig, - ReadHeaderTimeout: 5 * time.Second, - } - err = srv.ListenAndServeTLS(n.serverOptions.TLSCertificatePath(), n.serverOptions.TLSCertificateKeyPath()) - } else { - srv = &http.Server{ - Handler: r, - Addr: addr, - ReadHeaderTimeout: 5 * time.Second, - } - err = srv.ListenAndServe() - } - - if err != nil { - panic(err) - } - }() - - <-ctx.Done() - - return srv.Shutdown(ctx) -} - func (n *kubeFilter) getTenantsForOwner(ctx context.Context, username string, groups []string) (proxyTenants []*tenant.ProxyTenant, err error) { if strings.HasPrefix(username, serviceaccount.ServiceAccountUsernamePrefix) { proxyTenants, err = n.getProxyTenantsForOwnerKind(ctx, capsulev1beta2.ServiceAccountOwner, username) @@ -547,17 +558,6 @@ func (n *kubeFilter) removingHopByHopHeaders(request *http.Request) { request.Header.Del(connectionHeaderName) } -func (n *kubeFilter) BearerToken() string { - if time.Now().After(n.bearerTokenExpirationTime) { - n.log.V(5).Info("Token expired. Reading new token from file", "token", n.bearerToken, "token file", n.bearerTokenFile) - token, _ := os.ReadFile(n.bearerTokenFile) - n.bearerToken = string(token) - n.bearerTokenExpirationTime = bearerExpirationTime(string(token)) - } - - return n.bearerToken -} - func bearerExpirationTime(tokenString string) time.Time { token, _, _ := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) claims, _ := token.Claims.(jwt.MapClaims)