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:
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user