Skip to content

Commit ea0f53d

Browse files
committed
New system connection add tests
These tests verify that podman successfully adds (or fails to add) a connection to an SSH server based on the entries in the `~/.ssh/known_hosts` file. In particular `system connection add` should succeed if: - there is no `know_hosts` file - `known_hosts` has an entry that matches the first protocol/key returned by the SSH server - `known_hosts` has an entry that matches the first protocol/key returned by the SSH server - `known_hosts` has an entry for another SSH server, not for the target server It should fail if the `known_host` file has an entry for the target server that matches the protocol but not the key. Depends on containers/common#2212 Fixes containers#23575 Signed-off-by: Mario Loriedo <[email protected]>
1 parent bbb08a2 commit ea0f53d

File tree

1 file changed

+137
-6
lines changed

1 file changed

+137
-6
lines changed

test/e2e/system_connection_test.go

Lines changed: 137 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
package integration
44

55
import (
6+
"bytes"
67
"context"
8+
"errors"
79
"fmt"
810
"net"
911
"net/http"
@@ -422,21 +424,37 @@ qe ssh://[email protected]:2222/run/podman/podman.sock ~/.ssh/id_rsa false true
422424
})
423425
})
424426

425-
Context("sshd and API services required", func() {
426-
BeforeEach(func() {
427+
// Using "Ordered" or tests would concurrently access
428+
// the ~/.ssh/know_host file with unexpected results
429+
Context("sshd and API services required", Ordered, func() {
430+
var khCopyPath, khPath string
431+
var u *user.User
432+
433+
BeforeAll(func() {
427434
// These tests are unique in as much as they require podman, podman-remote, systemd and sshd.
428435
// podman-remote commands will be executed by ginkgo directly.
429436
SkipIfContainerized("sshd is not available when running in a container")
430437
SkipIfRemote("connection heuristic requires both podman and podman-remote binaries")
431438
SkipIfNotRootless(fmt.Sprintf("FIXME: set up ssh keys when root. uid(%d) euid(%d)", os.Getuid(), os.Geteuid()))
432439
SkipIfSystemdNotRunning("cannot test connection heuristic if systemd is not running")
433440
SkipIfNotActive("sshd", "cannot test connection heuristic if sshd is not running")
434-
})
435441

436-
It("add ssh:// socket path using connection heuristic", func() {
437-
u, err := user.Current()
442+
// If the file ~/.ssh/known_host exists, temporarily remove it so that the tests are deterministics
443+
var err error
444+
u, err = user.Current()
438445
Expect(err).ShouldNot(HaveOccurred())
446+
khPath = filepath.Join(u.HomeDir, ".ssh", "known_hosts")
447+
khCopyPath = khPath + ".copy"
448+
err = os.Rename(khPath, khCopyPath)
449+
Expect(err == nil || errors.Is(err, os.ErrNotExist)).To(BeTrue(), fmt.Sprintf("failed to rename %s to %s", khPath, khCopyPath))
450+
})
439451

452+
AfterAll(func() { // codespell:ignore afterall
453+
err = os.Rename(khCopyPath, khPath)
454+
Expect(err == nil || errors.Is(err, os.ErrNotExist)).To(BeTrue(), fmt.Sprintf("failed to rename %s to %s", khCopyPath, khPath))
455+
})
456+
457+
It("add ssh:// socket path using connection heuristic", func() {
440458
// Ensure that the remote end uses our built podman
441459
if os.Getenv("PODMAN_BINARY") == "" {
442460
err = os.Setenv("PODMAN_BINARY", podmanTest.PodmanBinary)
@@ -446,7 +464,6 @@ qe ssh://[email protected]:2222/run/podman/podman.sock ~/.ssh/id_rsa false true
446464
os.Unsetenv("PODMAN_BINARY")
447465
}()
448466
}
449-
450467
cmd := exec.Command(podmanTest.RemotePodmanBinary,
451468
"system", "connection", "add",
452469
"--default",
@@ -488,5 +505,119 @@ qe ssh://[email protected]:2222/run/podman/podman.sock ~/.ssh/id_rsa false true
488505
Expect(lsSession).Should(Exit(0))
489506
Expect(string(lsSession.Out.Contents())).To(Equal("QA " + uri.String() + " " + filepath.Join(u.HomeDir, ".ssh", "id_ed25519") + " true true\n"))
490507
})
508+
509+
Describe("add ssh:// with known_hosts", func() {
510+
var (
511+
initialKnownHostFilesLines map[string][]string
512+
currentSSHServerHostname string
513+
)
514+
515+
BeforeAll(func() {
516+
currentSSHServerHostname = "localhost"
517+
518+
// Retrieve current SSH server first two public keys
519+
// with the command `ssh-keyscan localhost`
520+
cmd := exec.Command("ssh-keyscan", currentSSHServerHostname)
521+
session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
522+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("`ssh-keyscan %s` failed to execute", currentSSHServerHostname))
523+
Eventually(session, DefaultWaitTimeout).Should(Exit(0))
524+
Expect(session.Out.Contents()).ShouldNot(BeEmpty(), fmt.Sprintf("`ssh-keyscan %s` output is empty", currentSSHServerHostname))
525+
serverKeys := bytes.Split(session.Out.Contents(), []byte("\n"))
526+
Expect(len(serverKeys)).Should(BeNumerically(">=", 2), fmt.Sprintf("`ssh-keyscan %s` returned less then 2 keys", currentSSHServerHostname))
527+
528+
initialKnownHostFilesLines = map[string][]string{
529+
"serverFirstKey": {string(serverKeys[0])},
530+
"serverSecondKey": {string(serverKeys[1])},
531+
"fakeKey": {currentSSHServerHostname + " ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGnBlHlwqleAtyzT01mLa+DXQFyxX8T0oa8odcEZ2/07"},
532+
"differentHostKey": {"differentserver ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGnBlHlwqleAtyzT01mLa+DXQFyxX8T0oa8odcEZ2/07"},
533+
"empty": {},
534+
}
535+
})
536+
537+
DescribeTable("->",
538+
func(label string, shouldFail bool, shouldAddKey bool) {
539+
initialKhLines, ok := initialKnownHostFilesLines[label]
540+
Expect(ok).To(BeTrue(), fmt.Sprintf("label %q not found in kh", label))
541+
// Create known_hosts file if needed
542+
if len(initialKhLines) > 0 {
543+
khFile, err := os.Create(khPath)
544+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("failed to create %s", khPath))
545+
defer khFile.Close()
546+
err = os.WriteFile(khPath, []byte(strings.Join(initialKhLines, "\n")), 0600)
547+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("failed to write to %s", khPath))
548+
}
549+
// Ensure that the remote end uses our built podman
550+
if os.Getenv("PODMAN_BINARY") == "" {
551+
err = os.Setenv("PODMAN_BINARY", podmanTest.PodmanBinary)
552+
Expect(err).ShouldNot(HaveOccurred())
553+
554+
defer func() {
555+
os.Unsetenv("PODMAN_BINARY")
556+
}()
557+
}
558+
// Run podman system connection add
559+
cmd := exec.Command(podmanTest.RemotePodmanBinary,
560+
"system", "connection", "add",
561+
"--default",
562+
"--identity", filepath.Join(u.HomeDir, ".ssh", "id_ed25519"),
563+
"QA",
564+
fmt.Sprintf("ssh://%s@%s", u.Username, currentSSHServerHostname))
565+
session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
566+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("%q failed to execute", podmanTest.RemotePodmanBinary))
567+
Expect(session.Out.Contents()).Should(BeEmpty())
568+
if shouldFail {
569+
Eventually(session, DefaultWaitTimeout).Should(Exit(125))
570+
Expect(session.Err.Contents()).ShouldNot(BeEmpty())
571+
} else {
572+
Eventually(session, DefaultWaitTimeout).Should(Exit(0))
573+
Expect(session.Err.Contents()).Should(BeEmpty())
574+
}
575+
// If the known_hosts file didn't exist, it should
576+
// have been created
577+
if len(initialKhLines) == 0 {
578+
Expect(khPath).To(BeAnExistingFile())
579+
defer os.Remove(khPath)
580+
}
581+
// If the known_hosts file didn't contain the SSH server
582+
// public key it should have been added
583+
if shouldAddKey {
584+
khFileContent, err := os.ReadFile(khPath)
585+
Expect(err).ToNot(HaveOccurred())
586+
khLines := bytes.Split(khFileContent, []byte("\n"))
587+
Expect(len(khLines)).To(BeNumerically(">", len(initialKhLines)))
588+
}
589+
},
590+
Entry(
591+
"with a public key of the SSH server that matches the SSH server first key",
592+
"serverFirstKey",
593+
false,
594+
false,
595+
),
596+
Entry(
597+
"with a public key of the SSH server that matches SSH server second key",
598+
"serverSecondKey",
599+
false,
600+
false,
601+
),
602+
Entry(
603+
"with a fake public key of the SSH server that doesn't match any of the SSH server keys (should fail)",
604+
"fakeKey",
605+
true,
606+
false,
607+
),
608+
Entry(
609+
"with no public key for the SSH server (new key should be added)",
610+
"differentHostKey",
611+
false,
612+
true,
613+
),
614+
Entry(
615+
"not existing (should be created and a new key should be added)",
616+
"empty",
617+
false,
618+
true,
619+
),
620+
)
621+
})
491622
})
492623
})

0 commit comments

Comments
 (0)