Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kargo-cli for openSUSE:Factory checked in at 2026-01-27 16:14:34 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kargo-cli (Old) and /work/SRC/openSUSE:Factory/.kargo-cli.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kargo-cli" Tue Jan 27 16:14:34 2026 rev:40 rq:1329435 version:1.8.7 Changes: -------- --- /work/SRC/openSUSE:Factory/kargo-cli/kargo-cli.changes 2026-01-19 18:41:25.530301648 +0100 +++ /work/SRC/openSUSE:Factory/.kargo-cli.new.1928/kargo-cli.changes 2026-01-27 16:15:11.297231740 +0100 @@ -1,0 +2,6 @@ +Tue Jan 27 06:06:56 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.8.7: + no CLI-related changes or dependency updates + +------------------------------------------------------------------- Old: ---- kargo-cli-1.8.6.obscpio New: ---- kargo-cli-1.8.7.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kargo-cli.spec ++++++ --- /var/tmp/diff_new_pack.iNQmBV/_old 2026-01-27 16:15:13.257313477 +0100 +++ /var/tmp/diff_new_pack.iNQmBV/_new 2026-01-27 16:15:13.257313477 +0100 @@ -19,7 +19,7 @@ %define executable_name kargo Name: kargo-cli -Version: 1.8.6 +Version: 1.8.7 Release: 0 Summary: CLI for the Kubernetes Application lifecycle orchestration License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.iNQmBV/_old 2026-01-27 16:15:13.305315454 +0100 +++ /var/tmp/diff_new_pack.iNQmBV/_new 2026-01-27 16:15:13.309315618 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/akuity/kargo</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v1.8.6</param> + <param name="revision">v1.8.7</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.iNQmBV/_old 2026-01-27 16:15:13.337316772 +0100 +++ /var/tmp/diff_new_pack.iNQmBV/_new 2026-01-27 16:15:13.341316936 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/akuity/kargo</param> - <param name="changesrevision">d9a709fa1adfc2e74e4b5811ebd072f5d60d5bbf</param></service></servicedata> + <param name="changesrevision">b3297ace0d3b9e7f7128858c5c4288d77f072b8c</param></service></servicedata> (No newline at EOF) ++++++ kargo-cli-1.8.6.obscpio -> kargo-cli-1.8.7.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.6/cmd/controlplane/api.go new/kargo-cli-1.8.7/cmd/controlplane/api.go --- old/kargo-cli-1.8.6/cmd/controlplane/api.go 2026-01-17 01:21:59.000000000 +0100 +++ new/kargo-cli-1.8.7/cmd/controlplane/api.go 2026-01-26 23:54:49.000000000 +0100 @@ -83,6 +83,7 @@ return fmt.Errorf("error getting Kubernetes client REST config: %w", err) } kubernetes.ConfigureQPSBurst(ctx, restCfg, o.QPS, o.Burst) + serverCfg.RestConfig = restCfg kubeClientOptions := kubernetes.ClientOptions{} if serverCfg.OIDCConfig != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.6/pkg/cli/cmd/server/server.go new/kargo-cli-1.8.7/pkg/cli/cmd/server/server.go --- old/kargo-cli-1.8.6/pkg/cli/cmd/server/server.go 2026-01-17 01:21:59.000000000 +0100 +++ new/kargo-cli-1.8.7/pkg/cli/cmd/server/server.go 2026-01-26 23:54:49.000000000 +0100 @@ -92,7 +92,8 @@ srv := server.NewServer( apiconfig.ServerConfig{ - LocalMode: true, + RestConfig: restCfg, + LocalMode: true, }, client, rbac.NewKubernetesRolesDatabase(client), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.6/pkg/controller/git/work_tree.go new/kargo-cli-1.8.7/pkg/controller/git/work_tree.go --- old/kargo-cli-1.8.6/pkg/controller/git/work_tree.go 2026-01-17 01:21:59.000000000 +0100 +++ new/kargo-cli-1.8.7/pkg/controller/git/work_tree.go 2026-01-26 23:54:49.000000000 +0100 @@ -584,10 +584,10 @@ PullRebase bool } -// https://regex101.com/r/aNYjHP/1 +// https://regex101.com/r/f7kTjs/1 // // nolint: lll -var nonFastForwardRegex = regexp.MustCompile(`(?m)^\s*!\s+\[(?:remote )?rejected].+\((?:non-fast-forward|fetch first|cannot lock ref.*)\)\s*$`) +var nonFastForwardRegex = regexp.MustCompile(`(?m)^\s*!\s+\[(?:remote )?rejected].+\((?:non-fast-forward|fetch first|cannot lock ref.*|incorrect old value provided)\)\s*$`) func (w *workTree) Push(opts *PushOptions) error { if opts == nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.6/pkg/controller/git/work_tree_test.go new/kargo-cli-1.8.7/pkg/controller/git/work_tree_test.go --- old/kargo-cli-1.8.6/pkg/controller/git/work_tree_test.go 2026-01-17 01:21:59.000000000 +0100 +++ new/kargo-cli-1.8.7/pkg/controller/git/work_tree_test.go 2026-01-26 23:54:49.000000000 +0100 @@ -23,7 +23,8 @@ " ! [rejected] main -> main (fetch first)": true, " ! [remote rejected] HEAD -> experiment (cannot lock ref 'refs/heads/experiment': is at " + "7dc98ee9c0b75be429e300bb59b3cf6d091ca9ed but expected 1bdf96c8c868981a0e24c43c98aef09a8970a1b8)": true, - " ! [rejected] HEAD -> experiment (fetch first)": true, + " ! [rejected] HEAD -> experiment (fetch first)": true, + " ! [remote rejected] HEAD -> main (incorrect old value provided)": true, } testingPkg.ValidateRegularExpression(t, nonFastForwardRegex, testCases) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.6/pkg/credentials/kubernetes/github/app.go new/kargo-cli-1.8.7/pkg/credentials/kubernetes/github/app.go --- old/kargo-cli-1.8.6/pkg/credentials/kubernetes/github/app.go 2026-01-17 01:21:59.000000000 +0100 +++ new/kargo-cli-1.8.7/pkg/credentials/kubernetes/github/app.go 2026-01-26 23:54:49.000000000 +0100 @@ -280,7 +280,7 @@ if len(parts) < 5 { return "" } - return parts[len(parts)-1] + return strings.TrimSuffix(parts[len(parts)-1], ".git") } // extractBaseURL extracts the base URL from a full repository URL. The base diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.6/pkg/credentials/kubernetes/github/app_test.go new/kargo-cli-1.8.7/pkg/credentials/kubernetes/github/app_test.go --- old/kargo-cli-1.8.6/pkg/credentials/kubernetes/github/app_test.go 2026-01-17 01:21:59.000000000 +0100 +++ new/kargo-cli-1.8.7/pkg/credentials/kubernetes/github/app_test.go 2026-01-26 23:54:49.000000000 +0100 @@ -173,6 +173,17 @@ }, expected: true, }, + { + name: "valid with .git suffix", + credType: credentials.TypeGit, + repoURL: testRepoURL + ".git", + getDataMap: func() map[string][]byte { + dm := maps.Clone(supportedDataMap) + delete(dm, clientIDKey) + return dm + }, + expected: true, + }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -525,6 +536,11 @@ expected: "repo", }, { + name: "GitHub URL with .git suffix", + repoURL: "https://github.com/example/repo.git", + expected: "repo", + }, + { name: "GitHub Enterprise URL", repoURL: "https://github.example.com/example/repo", expected: "repo", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.6/pkg/server/config/config.go new/kargo-cli-1.8.7/pkg/server/config/config.go --- old/kargo-cli-1.8.6/pkg/server/config/config.go 2026-01-17 01:21:59.000000000 +0100 +++ new/kargo-cli-1.8.7/pkg/server/config/config.go 2026-01-26 23:54:49.000000000 +0100 @@ -6,6 +6,7 @@ "time" "github.com/kelseyhightower/envconfig" + "k8s.io/client-go/rest" "github.com/akuity/kargo/pkg/os" "github.com/akuity/kargo/pkg/server/dex" @@ -32,6 +33,7 @@ AnalysisRunLogToken string AnalysisRunLogHTTPHeaders map[string]string ClusterSecretNamespace string + RestConfig *rest.Config } func ServerConfigFromEnv() ServerConfig { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.6/pkg/server/option/auth.go new/kargo-cli-1.8.7/pkg/server/option/auth.go --- old/kargo-cli-1.8.6/pkg/server/option/auth.go 2026-01-17 01:21:59.000000000 +0100 +++ new/kargo-cli-1.8.7/pkg/server/option/auth.go 2026-01-26 23:54:49.000000000 +0100 @@ -20,6 +20,7 @@ "github.com/hashicorp/go-cleanhttp" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" libClient "sigs.k8s.io/controller-runtime/pkg/client" kargoapi "github.com/akuity/kargo/api/v1alpha1" @@ -50,16 +51,11 @@ claims jwt.Claims, ) (*jwt.Token, []string, error) verifyKargoIssuedTokenFn func(rawToken string) bool - verifyIDPIssuedTokenFn func( - ctx context.Context, - rawToken string, - ) (claims, error) - oidcTokenVerifyFn goOIDCIDTokenVerifyFn - oidcExtractClaimsFn func(*oidc.IDToken) (claims, error) - listServiceAccountsFn func( - ctx context.Context, - c claims, - ) (map[string]map[types.NamespacedName]struct{}, error) + verifyIDPIssuedTokenFn func(ctx context.Context, rawToken string) (claims, error) + verifyKubernetesTokenFn func(ctx context.Context, rawToken string) error + oidcTokenVerifyFn goOIDCIDTokenVerifyFn + oidcExtractClaimsFn func(*oidc.IDToken) (claims, error) + listServiceAccountsFn func(ctx context.Context, c claims) (map[string]map[types.NamespacedName]struct{}, error) } // goOIDCIDTokenVerifyFn is a github.com/coreos/go-oidc/v3/oidc/IDTokenVerifier.Verify() function @@ -81,6 +77,7 @@ a.parseUnverifiedJWTFn = jwt.NewParser(jwt.WithoutClaimsValidation()).ParseUnverified a.verifyKargoIssuedTokenFn = a.verifyKargoIssuedToken a.verifyIDPIssuedTokenFn = a.verifyIDPIssuedToken + a.verifyKubernetesTokenFn = a.verifyKubernetesToken a.oidcExtractClaimsFn = oidcExtractClaims a.listServiceAccountsFn = a.listServiceAccounts return a @@ -368,22 +365,17 @@ // Are we dealing with a JWT? // - // Note: If this is a JWT, we cannot trust these claims yet because we're not - // verifying the token yet. We use untrustedClaims.Issuer only as a hint as to - // HOW we might be able to verify the token further. + // If not, we no longer assume this is potentially some other form of token + // that the Kubernetes API server might recognize, as that is an increasingly + // unlikely scenario. + // + // If this IS a JWT, we cannot trust these claims yet because we're not + // verifying the token just yet. We use untrustedClaims.Issuer only as a hint + // as to HOW we might be able to verify the token further. untrustedClaims := jwt.RegisteredClaims{} if _, _, err := a.parseUnverifiedJWTFn(rawToken, &untrustedClaims); err != nil { - // This token isn't a JWT, so it's probably an opaque bearer token for the - // Kubernetes API server. Just run with it. If we're wrong, Kubernetes API - // calls will simply have auth errors that will bubble back to the client. - return user.ContextWithInfo( - ctx, - user.Info{ - BearerToken: rawToken, - }, - ), nil + return ctx, errors.New("invalid token") } - logger.Debug("found untrusted claims in token", "claims", untrustedClaims) // If we get to here, we're dealing with a JWT. It could have been issued: @@ -451,15 +443,16 @@ } - // Case 3 or 4: We don't know how to verify this token. It's probably a token - // issued by the Kubernetes cluster's identity provider. Just run with it. If - // we're wrong, Kubernetes API calls will simply have auth errors that will - // bubble back to the client. - - logger.Debug( - "could not verify token; assuming it might have been issued by " + - "Kubernetes cluster identity provider", - ) + // Case 3 or 4: We don't know how to verify this token. It's possibly a token + // issued by the Kubernetes cluster's identity provider. + + // Test whether Kubernetes recognizes this token by making a request to /api + logger.Debug("could not verify token; checking if Kubernetes recognizes it") + if err := a.verifyKubernetesTokenFn(ctx, rawToken); err != nil { + logger.Debug("token not recognized by Kubernetes", "error", err) + return ctx, errors.New("invalid token") + } + logger.Debug("token recognized by Kubernetes") return user.ContextWithInfo( ctx, @@ -529,3 +522,42 @@ err := token.Claims(&c) return c, err } + +// verifyKubernetesToken tests whether the Kubernetes API server recognizes the +// provided token by making a GET request to the /api endpoint. This is a +// lightweight check that doesn't require any specific permissions. +func (a *authInterceptor) verifyKubernetesToken( + ctx context.Context, + rawToken string, +) error { + if a.cfg.RestConfig == nil { // This shouldn't happen, but just in case... + return errors.New("Kubernetes REST config is not available") // nolint: staticcheck + } + + transport, err := rest.TransportFor(a.cfg.RestConfig) + if err != nil { + return fmt.Errorf("create transport: %w", err) + } + + apiURL := strings.TrimSuffix(a.cfg.RestConfig.Host, "/") + "/api" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) + if err != nil { + return fmt.Errorf("create request: %w", err) + } + req.Header.Set("Authorization", "Bearer "+rawToken) + + resp, err := (&http.Client{Transport: transport}).Do(req) + if err != nil { + return fmt.Errorf("execute request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf( + "unexpected response from Kubernetes API server: %d", + resp.StatusCode, + ) + } + + return nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.6/pkg/server/option/auth_test.go new/kargo-cli-1.8.7/pkg/server/option/auth_test.go --- old/kargo-cli-1.8.6/pkg/server/option/auth_test.go 2026-01-17 01:21:59.000000000 +0100 +++ new/kargo-cli-1.8.7/pkg/server/option/auth_test.go 2026-01-26 23:54:49.000000000 +0100 @@ -14,6 +14,7 @@ "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" "github.com/akuity/kargo/pkg/server/config" "github.com/akuity/kargo/pkg/server/dex" @@ -52,6 +53,7 @@ require.NotNil(t, a.parseUnverifiedJWTFn) require.NotNil(t, a.verifyKargoIssuedTokenFn) require.NotNil(t, a.verifyIDPIssuedTokenFn) + require.NotNil(t, a.verifyKubernetesTokenFn) require.NotNil(t, a.oidcExtractClaimsFn) require.NotNil(t, a.listServiceAccountsFn) } @@ -199,14 +201,10 @@ }, }, token: testToken, - // We can't parse the token as a JWT, so we assume it could be an opaque - // bearer token for the k8s API server. We expect user info containing the - // raw token to be bound to the context. assertions: func(ctx context.Context, err error) { - require.NoError(t, err) - u, ok := user.InfoFromContext(ctx) - require.True(t, ok) - require.Equal(t, testToken, u.BearerToken) + require.Equal(t, "invalid token", err.Error()) + _, ok := user.InfoFromContext(ctx) + require.False(t, ok) }, }, "failure verifying Kargo-issued token": { @@ -354,7 +352,7 @@ require.Equal(t, testToken, u.BearerToken) }, }, - "unrecognized JWT": { + "unrecognized JWT recognized by Kubernetes": { procedure: testProcedure, authInterceptor: &authInterceptor{ parseUnverifiedJWTFn: func(_ string, claims jwt.Claims) (*jwt.Token, []string, error) { @@ -363,11 +361,14 @@ rc.Issuer = "unrecognized-issuer" return nil, nil, nil }, + verifyKubernetesTokenFn: func(context.Context, string) error { + return nil // Token is recognized by Kubernetes + }, }, token: testToken, - // We can't verify this token, so we assume it could be an an identity - // token from the k8s API server's identity provider. We expect user info - // containing the raw token to be bound to the context. + // We can't verify this token, so we check if Kubernetes recognizes it. + // In this case it does, so we expect user info containing the raw token + // to be bound to the context. assertions: func(ctx context.Context, err error) { require.NoError(t, err) u, ok := user.InfoFromContext(ctx) @@ -375,6 +376,29 @@ require.Equal(t, testToken, u.BearerToken) }, }, + "unrecognized JWT not recognized by Kubernetes": { + procedure: testProcedure, + authInterceptor: &authInterceptor{ + parseUnverifiedJWTFn: func(_ string, claims jwt.Claims) (*jwt.Token, []string, error) { + rc, ok := claims.(*jwt.RegisteredClaims) + require.True(t, ok) + rc.Issuer = "unrecognized-issuer" + return nil, nil, nil + }, + verifyKubernetesTokenFn: func(context.Context, string) error { + return errors.New("token not recognized") + }, + }, + token: testToken, + // We can't verify this token and Kubernetes doesn't recognize it either. + // This should result in an authentication error. + assertions: func(ctx context.Context, err error) { + require.Error(t, err) + require.Equal(t, "invalid token", err.Error()) + _, ok := user.InfoFromContext(ctx) + require.False(t, ok) + }, + }, } for name, ts := range testSets { t.Run(name, func(t *testing.T) { @@ -594,3 +618,46 @@ }) } } + +func TestVerifyKubernetesToken(t *testing.T) { + const testToken = "test-bearer-token" + + testCases := []struct { + name string + mockK8sAPIHandler http.HandlerFunc + assertions func(t *testing.T, err error) + }{ + { + name: "Kubernetes API returns 200", + mockK8sAPIHandler: func(w http.ResponseWriter, r *http.Request) { + // Verify the token was passed correctly + require.Equal(t, "Bearer "+testToken, r.Header.Get("Authorization")) + require.Equal(t, "/api", r.URL.Path) + w.WriteHeader(http.StatusOK) + }, + assertions: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + { + name: "Kubernetes API returns non-200", + mockK8sAPIHandler: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + }, + assertions: func(t *testing.T, err error) { + require.ErrorContains(t, err, "unexpected response from Kubernetes API server: 401") + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + srv := httptest.NewServer(testCase.mockK8sAPIHandler) + t.Cleanup(srv.Close) + authenticator := &authInterceptor{ + cfg: config.ServerConfig{RestConfig: &rest.Config{Host: srv.URL}}, + } + err := authenticator.verifyKubernetesToken(t.Context(), testToken) + testCase.assertions(t, err) + }) + } +} ++++++ kargo-cli.obsinfo ++++++ --- /var/tmp/diff_new_pack.iNQmBV/_old 2026-01-27 16:15:20.117596015 +0100 +++ /var/tmp/diff_new_pack.iNQmBV/_new 2026-01-27 16:15:20.165597992 +0100 @@ -1,5 +1,5 @@ name: kargo-cli -version: 1.8.6 -mtime: 1768609319 -commit: d9a709fa1adfc2e74e4b5811ebd072f5d60d5bbf +version: 1.8.7 +mtime: 1769468089 +commit: b3297ace0d3b9e7f7128858c5c4288d77f072b8c ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/kargo-cli/vendor.tar.gz /work/SRC/openSUSE:Factory/.kargo-cli.new.1928/vendor.tar.gz differ: char 134, line 2
