8b20c2efc1
Covers the validation matrix of the new --gpg-* / --ssh-signing-* surface: mutual exclusion (across GPG/SSH groups and within the SSH group between --ssh-signing-key-file and --ssh-signing-reuse-private- key), alias resolution between --ssh-signing-password and --ssh-signing-passphrase, the dependency checks (--ssh-signing- password requires --ssh-signing-key-file; --ssh-signing-reuse- private-key requires --private-key-file), and pre-flight key-parse failures (malformed PEM, encrypted SSH key without passphrase, GPG ring with wrong passphrase). Test keys are checked in so the test does not depend on local ssh-keygen or gpg invocations at run time. Signed-off-by: Hidde Beydals <hidde@hhh.computer>
156 lines
5.0 KiB
Go
156 lines
5.0 KiB
Go
/*
|
|
Copyright 2026 The Flux authors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestBootstrapValidate_signingFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
gpgRing string
|
|
gpgPass string
|
|
sshKey string
|
|
sshPass string
|
|
sshPassp string
|
|
privateKey string
|
|
reuse bool
|
|
wantErr string
|
|
}{
|
|
{name: "no signing flags is valid"},
|
|
{name: "GPG only is valid", gpgRing: "./testdata/bootstrap/gpg.pgp"},
|
|
{name: "SSH only is valid", sshKey: "./testdata/bootstrap/ed25519.private"},
|
|
{
|
|
name: "Reuse-private-key with private-key-file is valid",
|
|
privateKey: "./testdata/bootstrap/ed25519.private",
|
|
reuse: true,
|
|
},
|
|
{
|
|
name: "GPG + SSH errors",
|
|
gpgRing: "./testdata/bootstrap/gpg.pgp",
|
|
sshKey: "./testdata/bootstrap/ed25519.private",
|
|
wantErr: "--gpg-* and --ssh-signing-* are mutually exclusive",
|
|
},
|
|
{
|
|
name: "GPG + reuse errors",
|
|
gpgRing: "./testdata/bootstrap/gpg.pgp",
|
|
privateKey: "./testdata/bootstrap/ed25519.private",
|
|
reuse: true,
|
|
wantErr: "--gpg-* and --ssh-signing-* are mutually exclusive",
|
|
},
|
|
{
|
|
name: "SSH key-file + reuse errors",
|
|
sshKey: "./testdata/bootstrap/ed25519.private",
|
|
privateKey: "./testdata/bootstrap/ed25519.private",
|
|
reuse: true,
|
|
wantErr: "--ssh-signing-key-file and --ssh-signing-reuse-private-key are mutually exclusive",
|
|
},
|
|
{
|
|
name: "Reuse without private-key-file errors",
|
|
reuse: true,
|
|
wantErr: "--ssh-signing-reuse-private-key requires --private-key-file",
|
|
},
|
|
{
|
|
name: "SSH password without key errors",
|
|
sshPass: "secret",
|
|
wantErr: "--ssh-signing-password requires --ssh-signing-key-file",
|
|
},
|
|
{
|
|
name: "SSH passphrase alias alone applies",
|
|
sshKey: "./testdata/bootstrap/ed25519-encrypted.private",
|
|
sshPassp: "abcde12345",
|
|
},
|
|
{
|
|
name: "SSH password and passphrase with same value passes",
|
|
sshKey: "./testdata/bootstrap/ed25519-encrypted.private",
|
|
sshPass: "abcde12345",
|
|
sshPassp: "abcde12345",
|
|
},
|
|
{
|
|
name: "SSH password and passphrase with different values errors",
|
|
sshKey: "./testdata/bootstrap/ed25519-encrypted.private",
|
|
sshPass: "right",
|
|
sshPassp: "wrong",
|
|
wantErr: "are aliases; do not pass both",
|
|
},
|
|
{
|
|
name: "SSH malformed key fails pre-flight",
|
|
sshKey: "./testdata/bootstrap/malformed.private",
|
|
wantErr: "invalid SSH signing key",
|
|
},
|
|
{
|
|
name: "SSH encrypted key without password fails pre-flight",
|
|
sshKey: "./testdata/bootstrap/ed25519-encrypted.private",
|
|
wantErr: "passphrase required",
|
|
},
|
|
// The GPG fixture used here is encrypted (passphrase: "right") so that
|
|
// passing the wrong passphrase exercises the Decrypt error path.
|
|
// An unencrypted key would make Decrypt a no-op regardless of the
|
|
// passphrase supplied.
|
|
{
|
|
name: "GPG with wrong passphrase fails pre-flight",
|
|
gpgRing: "./testdata/bootstrap/gpg-encrypted.pgp",
|
|
gpgPass: "wrong",
|
|
wantErr: "invalid GPG signing key",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
savedGpgRing := bootstrapArgs.gpgKeyRingPath
|
|
savedGpgPass := bootstrapArgs.gpgPassphrase
|
|
savedSshKey := bootstrapArgs.sshSigningKeyFile
|
|
savedSshPass := bootstrapArgs.sshSigningPassword
|
|
savedSshPassp := bootstrapArgs.sshSigningPassphrase
|
|
savedPrivKey := bootstrapArgs.privateKeyFile
|
|
savedReuse := bootstrapArgs.sshSigningReusePrivateKey
|
|
defer func() {
|
|
bootstrapArgs.gpgKeyRingPath = savedGpgRing
|
|
bootstrapArgs.gpgPassphrase = savedGpgPass
|
|
bootstrapArgs.sshSigningKeyFile = savedSshKey
|
|
bootstrapArgs.sshSigningPassword = savedSshPass
|
|
bootstrapArgs.sshSigningPassphrase = savedSshPassp
|
|
bootstrapArgs.privateKeyFile = savedPrivKey
|
|
bootstrapArgs.sshSigningReusePrivateKey = savedReuse
|
|
}()
|
|
|
|
bootstrapArgs.gpgKeyRingPath = tt.gpgRing
|
|
bootstrapArgs.gpgPassphrase = tt.gpgPass
|
|
bootstrapArgs.sshSigningKeyFile = tt.sshKey
|
|
bootstrapArgs.sshSigningPassword = tt.sshPass
|
|
bootstrapArgs.sshSigningPassphrase = tt.sshPassp
|
|
bootstrapArgs.privateKeyFile = tt.privateKey
|
|
bootstrapArgs.sshSigningReusePrivateKey = tt.reuse
|
|
|
|
err := bootstrapValidate()
|
|
if tt.wantErr == "" {
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got: %v", err)
|
|
}
|
|
return
|
|
}
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|