From 96fda4cd565a061636e2b31fadafa10cbdba5039 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Fri, 19 Jun 2026 15:02:43 +0200 Subject: [PATCH] Reject ssh-signing-reuse early in github and gitea MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `bootstrap github` and `bootstrap gitea` generate the SSH transport key in-process, so they have no operator-supplied key to reuse for commit signing. Both subcommands already reject `--ssh-signing-reuse-private-key` with a provider-specific "not supported" error, but the check sat after `bootstrapValidate`, which fails first with the generic "--ssh-signing-reuse-private-key requires --private-key-file" message. A user invoking e.g. `flux bootstrap github --ssh-signing-reuse-private-key` is told to set a flag that the subcommand cannot honour anyway, masking the real problem. Move the unsupported-flag rejection to the top of each `RunE` — before the interactive PAT prompt and before `bootstrapValidate` — so the provider-specific error wins. The deeper, now-redundant check is dropped. `TestBootstrapProviderRejectsReuseBeforeValidate` exercises both subcommands with the reuse flag set and no `--private-key-file` to lock in the precedence. Assisted-by: claude/opus-4.7 Signed-off-by: Hidde Beydals --- cmd/flux/bootstrap_gitea.go | 10 ++++---- cmd/flux/bootstrap_github.go | 10 ++++---- cmd/flux/bootstrap_test.go | 47 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/cmd/flux/bootstrap_gitea.go b/cmd/flux/bootstrap_gitea.go index ad30e8bb..767f61a2 100644 --- a/cmd/flux/bootstrap_gitea.go +++ b/cmd/flux/bootstrap_gitea.go @@ -107,6 +107,11 @@ func init() { } func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error { + if bootstrapArgs.sshSigningReusePrivateKey { + return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap gitea'; " + + "that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse") + } + gtToken := os.Getenv(gtTokenEnvVar) if gtToken == "" { var err error @@ -254,11 +259,6 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error { bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), } - if bootstrapArgs.sshSigningReusePrivateKey { - return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap gitea'; " + - "that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse") - } - if bootstrapArgs.sshHostname != "" { bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) } diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go index 6ae1cb9a..2867cd40 100644 --- a/cmd/flux/bootstrap_github.go +++ b/cmd/flux/bootstrap_github.go @@ -107,6 +107,11 @@ func init() { } func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { + if bootstrapArgs.sshSigningReusePrivateKey { + return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap github'; " + + "that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse") + } + ghToken := os.Getenv(ghTokenEnvVar) if ghToken == "" { var err error @@ -261,11 +266,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), } - if bootstrapArgs.sshSigningReusePrivateKey { - return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap github'; " + - "that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse") - } - if bootstrapArgs.sshHostname != "" { bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) } diff --git a/cmd/flux/bootstrap_test.go b/cmd/flux/bootstrap_test.go index 95b4e172..4ec9b84c 100644 --- a/cmd/flux/bootstrap_test.go +++ b/cmd/flux/bootstrap_test.go @@ -159,3 +159,50 @@ func TestBootstrapValidate_signingFlags(t *testing.T) { }) } } + +// Providers that generate the SSH transport key in-process (github, gitea) +// must reject --ssh-signing-reuse-private-key with their own, provider- +// specific error before bootstrapValidate runs — otherwise the generic +// "--ssh-signing-reuse-private-key requires --private-key-file" error +// shadows the fact that the flag is fundamentally unsupported there. +func TestBootstrapProviderRejectsReuseBeforeValidate(t *testing.T) { + tests := []struct { + name string + runE func() error + wantErr string + }{ + { + name: "github rejects reuse with provider-specific error", + runE: func() error { return bootstrapGitHubCmdRun(nil, nil) }, + wantErr: "not supported by 'bootstrap github'", + }, + { + name: "gitea rejects reuse with provider-specific error", + runE: func() error { return bootstrapGiteaCmdRun(nil, nil) }, + wantErr: "not supported by 'bootstrap gitea'", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + savedReuse := bootstrapArgs.sshSigningReusePrivateKey + savedPrivKey := bootstrapArgs.privateKeyFile + defer func() { + bootstrapArgs.sshSigningReusePrivateKey = savedReuse + bootstrapArgs.privateKeyFile = savedPrivKey + }() + + // Reuse flag set, no --private-key-file: bootstrapValidate + // would otherwise return "requires --private-key-file". + bootstrapArgs.sshSigningReusePrivateKey = true + bootstrapArgs.privateKeyFile = "" + + err := tt.runE() + if err == nil { + t.Fatalf("expected error containing %q, got nil", tt.wantErr) + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("expected error containing %q, got: %v", tt.wantErr, err) + } + }) + } +}