1
0
mirror of synced 2026-06-20 20:10:49 +00:00

Reject ssh-signing-reuse early in github and gitea

`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 <hidde@hhh.computer>
This commit is contained in:
Hidde Beydals
2026-06-19 15:02:43 +02:00
parent 2ca3468423
commit 96fda4cd56
3 changed files with 57 additions and 10 deletions
+5 -5
View File
@@ -107,6 +107,11 @@ func init() {
} }
func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error { 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) gtToken := os.Getenv(gtTokenEnvVar)
if gtToken == "" { if gtToken == "" {
var err error var err error
@@ -254,11 +259,6 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), 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 != "" { if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
} }
+5 -5
View File
@@ -107,6 +107,11 @@ func init() {
} }
func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { 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) ghToken := os.Getenv(ghTokenEnvVar)
if ghToken == "" { if ghToken == "" {
var err error var err error
@@ -261,11 +266,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), 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 != "" { if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
} }
+47
View File
@@ -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)
}
})
}
}