33package integration
44
55import (
6+ "bytes"
67 "context"
8+ "errors"
79 "fmt"
810 "net"
911 "net/http"
@@ -422,21 +424,37 @@ qe ssh://root@podman.test: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://root@podman.test: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://root@podman.test: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