From f7c0d55801a7a11e2d5e494d5bce9643e2bbe6a1 Mon Sep 17 00:00:00 2001 From: phm07 <22707808+phm07@users.noreply.github.com> Date: Thu, 2 Jan 2025 16:17:52 +0100 Subject: [PATCH] test(e2e): add ssh key e2e tests --- test/e2e/sshkey_test.go | 166 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 test/e2e/sshkey_test.go diff --git a/test/e2e/sshkey_test.go b/test/e2e/sshkey_test.go new file mode 100644 index 00000000..308b5e26 --- /dev/null +++ b/test/e2e/sshkey_test.go @@ -0,0 +1,166 @@ +//go:build e2e + +package e2e + +import ( + "context" + "crypto/ed25519" + "crypto/rand" + "fmt" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/swaggest/assertjson" + "golang.org/x/crypto/ssh" + + "github.com/hetznercloud/hcloud-go/v2/hcloud" +) + +func TestSSHKey(t *testing.T) { + t.Parallel() + + pubKey, fingerprint, err := generateSSHKey() + require.NoError(t, err) + + sshKeyName := withSuffix("test-ssh-key") + sshKeyID, err := createSSHKey(t, sshKeyName, "--public-key", pubKey) + require.NoError(t, err) + + t.Run("add-label", func(t *testing.T) { + t.Run("non-existing", func(t *testing.T) { + out, err := runCommand(t, "ssh-key", "add-label", "non-existing-ssh-key", "foo=bar") + require.EqualError(t, err, "ssh key not found: non-existing-ssh-key") + assert.Empty(t, out) + }) + + t.Run("1", func(t *testing.T) { + out, err := runCommand(t, "ssh-key", "add-label", strconv.FormatInt(sshKeyID, 10), "foo=bar") + require.NoError(t, err) + assert.Equal(t, fmt.Sprintf("Label(s) foo added to SSH Key %d\n", sshKeyID), out) + }) + + t.Run("2", func(t *testing.T) { + out, err := runCommand(t, "ssh-key", "add-label", strconv.FormatInt(sshKeyID, 10), "baz=qux") + require.NoError(t, err) + assert.Equal(t, fmt.Sprintf("Label(s) baz added to SSH Key %d\n", sshKeyID), out) + }) + }) + + t.Run("list", func(t *testing.T) { + t.Run("table", func(t *testing.T) { + out, err := runCommand(t, "ssh-key", "list", "-o=columns=id,name,fingerprint,public_key,labels,created,age") + require.NoError(t, err) + assert.Regexp(t, + NewRegex().Start(). + SeparatedByWhitespace("ID", "NAME", "FINGERPRINT", "PUBLIC KEY", "LABELS", "CREATED", "AGE").Newline(). + Lit(strconv.FormatInt(sshKeyID, 10)).Whitespace(). + Lit(sshKeyName).Whitespace(). + Lit(fingerprint).Whitespace(). + Lit(pubKey).Whitespace(). + Lit("baz=qux, foo=bar").Whitespace(). + UnixDate().Whitespace(). + Age().Newline(). + End(), + out, + ) + }) + + t.Run("json", func(t *testing.T) { + out, err := runCommand(t, "ssh-key", "list", "-o=json") + require.NoError(t, err) + assertjson.Equal(t, []byte(fmt.Sprintf(` +[ + { + "id": %d, + "name": %q, + "fingerprint": %q, + "public_key": %q, + "labels": { + "baz": "qux", + "foo": "bar" + }, + "created": "" + } +]`, sshKeyID, sshKeyName, fingerprint, pubKey)), []byte(out)) + }) + }) + + t.Run("remove-label", func(t *testing.T) { + out, err := runCommand(t, "ssh-key", "remove-label", strconv.FormatInt(sshKeyID, 10), "baz") + require.NoError(t, err) + assert.Equal(t, fmt.Sprintf("Label(s) baz removed from SSH Key %d\n", sshKeyID), out) + }) + + t.Run("update-name", func(t *testing.T) { + sshKeyName = withSuffix("new-test-ssh-key") + out, err := runCommand(t, "ssh-key", "update", strconv.FormatInt(sshKeyID, 10), "--name", sshKeyName) + require.NoError(t, err) + assert.Equal(t, fmt.Sprintf("SSHKey %d updated\n", sshKeyID), out) + }) + + t.Run("describe", func(t *testing.T) { + out, err := runCommand(t, "ssh-key", "describe", strconv.FormatInt(sshKeyID, 10)) + require.NoError(t, err) + assert.Regexp(t, NewRegex().Start(). + Lit("ID:").Whitespace().Lit(strconv.FormatInt(sshKeyID, 10)).Newline(). + Lit("Name:").Whitespace().Lit(sshKeyName).Newline(). + Lit("Created:").Whitespace().UnixDate().Lit(" (").HumanizeTime().Lit(")").Newline(). + Lit("Fingerprint:").Whitespace().Lit(fingerprint).Newline(). + Lit("Public Key:").Newline().Lit(pubKey). + Lit("Labels:").Newline(). + Lit(" foo:").Whitespace().Lit("bar").Newline(). + End(), + out, + ) + }) + + t.Run("delete", func(t *testing.T) { + out, err := runCommand(t, "ssh-key", "delete", strconv.FormatInt(sshKeyID, 10)) + require.NoError(t, err) + assert.Equal(t, fmt.Sprintf("SSH Key %d deleted\n", sshKeyID), out) + }) +} + +func createSSHKey(t *testing.T, name string, args ...string) (int64, error) { + t.Helper() + t.Cleanup(func() { + _, _ = client.SSHKey.Delete(context.Background(), &hcloud.SSHKey{Name: name}) + }) + + out, err := runCommand(t, append([]string{"ssh-key", "create", "--name", name}, args...)...) + if err != nil { + return 0, err + } + + if !assert.Regexp(t, `^SSH key [0-9]+ created\n$`, out) { + return 0, fmt.Errorf("invalid response: %s", out) + } + + id, err := strconv.ParseInt(out[8:len(out)-9], 10, 64) + if err != nil { + return 0, err + } + + t.Cleanup(func() { + _, _ = client.SSHKey.Delete(context.Background(), &hcloud.SSHKey{ID: id}) + }) + return id, nil +} + +func generateSSHKey() (string, string, error) { + pub, _, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return "", "", err + } + + pubKey, err := ssh.NewPublicKey(pub) + if err != nil { + return "", "", err + } + + fingerprint := ssh.FingerprintLegacyMD5(pubKey) + pubKeyBytes := ssh.MarshalAuthorizedKey(pubKey) + return string(pubKeyBytes), fingerprint, nil +}