From 96dc3be75ef5dc50e1c58b741d11f99589fa77a2 Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Thu, 7 Jul 2022 06:56:41 +0000 Subject: [PATCH] add working dir e2e test --- .github/workflows/go.yml | 2 +- e2e/docker-compose.yml | 36 ++++- e2e/fixed_test.go | 43 ++++++ e2e/{e2e_test.go => main_test.go} | 83 +++++------- e2e/workingdir_test.go | 213 ++++++++++++++++++++++++++++++ 5 files changed, 324 insertions(+), 53 deletions(-) create mode 100644 e2e/fixed_test.go rename e2e/{e2e_test.go => main_test.go} (68%) create mode 100644 e2e/workingdir_test.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 7aa39b97..c80ee6d7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,4 +1,4 @@ -name: Go +name: Go Unit Test on: push: diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index deea61e3..be086aba 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -9,6 +9,15 @@ services: - USER_NAME=user volumes: - shared:/shared + - sshconfig_password:/config + + host-publickey: + image: lscr.io/linuxserver/openssh-server:latest + environment: + - USER_NAME=user + volumes: + - shared:/shared + - sshconfig_publickey:/config piper-fixed: environment: @@ -22,6 +31,21 @@ services: depends_on: - host-password + piper-workingdir: + environment: + - SSHPIPERD_LOG_LEVEL=trace + - SSHPIPERD_WORKINGDIR_STRICTHOSTKEY=true + volumes: + - shared:/shared + build: ../ + command: + - "/sshpiperd/sshpiperd" + - "/sshpiperd/plugins/workingdir" + - "--root" + - "/shared/workingdir" + depends_on: + - host-password + - host-publickey testrunner: environment: @@ -32,14 +56,20 @@ services: volumes: - ..:/src - shared:/shared - command: ["go", "test", "-v"] + - sshconfig_publickey:/sshconfig_publickey + - sshconfig_password:/sshconfig_password + command: ["bash", "-c", "if [ \"${SSHPIPERD_DEBUG}\" == \"1\" ]; then sleep infinity; else go test -v; fi"] working_dir: /src/e2e depends_on: - - host-password - piper-fixed + - piper-workingdir volumes: shared: driver_opts: type: tmpfs - device: tmpfs \ No newline at end of file + device: tmpfs + + sshconfig_publickey: + + sshconfig_password: \ No newline at end of file diff --git a/e2e/fixed_test.go b/e2e/fixed_test.go new file mode 100644 index 00000000..fd3d6741 --- /dev/null +++ b/e2e/fixed_test.go @@ -0,0 +1,43 @@ +package e2e_test + +import ( + "fmt" + "testing" + "time" + + "github.com/google/uuid" +) + +func TestFixed(t *testing.T) { + waitForEndpointReady("piper-fixed:2222") + + randtext := uuid.New().String() + targetfie := uuid.New().String() + + c, stdin, stdout, err := runCmd( + "ssh", + "-v", + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile=/dev/null", + "-p", + "2222", + "-l", + "user", + "piper-fixed", + fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie), + ) + + if err != nil { + t.Errorf("failed to ssh to piper-fixed, %v", err) + } + + defer killCmd(c) + + enterPassword(stdin, stdout, "pass") + + time.Sleep(time.Second) // wait for file flush + + checkSharedFileContent(t, targetfie, randtext) +} diff --git a/e2e/e2e_test.go b/e2e/main_test.go similarity index 68% rename from e2e/e2e_test.go rename to e2e/main_test.go index e0859435..d1d77b4f 100644 --- a/e2e/e2e_test.go +++ b/e2e/main_test.go @@ -17,7 +17,6 @@ import ( "time" "github.com/creack/pty" - "github.com/google/uuid" ) const waitTimeout = time.Second * 10 @@ -55,15 +54,18 @@ func runCmd(cmd string, args ...string) (*exec.Cmd, io.Writer, io.Reader, error) log.Printf("starting %v", c.Args) - go func() { - if err := c.Wait(); err != nil { - log.Printf("wait %v returns %v", c.Args, err) - } - }() - return c, f, &buf, nil } +func runCmdAndWait(cmd string, args ...string) error { + c, _, _, err := runCmd(cmd, args...) + if err != nil { + return err + } + + return c.Wait() +} + func enterPassword(stdin io.Writer, stdout io.Reader, password string) { st := time.Now() for { @@ -97,10 +99,32 @@ func checkSharedFileContent(t *testing.T, targetfie string, expected string) { } if string(b) != expected { - t.Errorf("shared file content mismathc, expected %v, got %v", expected, string(b)) + t.Errorf("shared file content mismatch, expected %v, got %v", expected, string(b)) } } +func killCmd(c *exec.Cmd) { + if c.Process != nil { + if err := c.Process.Kill(); err != nil { + log.Printf("failed to kill ssh process, %v", err) + } + } +} + +func runAndGetStdout(cmd string, args ...string) ([]byte, error) { + c, _, stdout, err := runCmd(cmd, args...) + + if err != nil { + return nil, err + } + + if err := c.Wait(); err != nil { + return nil, err + } + + return io.ReadAll(stdout) +} + func TestMain(m *testing.M) { if os.Getenv("SSHPIPERD_E2E_TEST") != "1" { @@ -109,53 +133,14 @@ func TestMain(m *testing.M) { return } - _, _, _, _ = runCmd("ssh", "-V") + _ = runCmdAndWait("ssh", "-V") for _, ep := range []string{ "host-password:2222", + "host-publickey:2222", } { waitForEndpointReady(ep) } os.Exit(m.Run()) } - -func TestFixed(t *testing.T) { - waitForEndpointReady("piper-fixed:2222") - - randtext := uuid.New().String() - targetfie := uuid.New().String() - - c, stdin, stdout, err := runCmd( - "ssh", - "-v", - "-o", - "StrictHostKeyChecking=no", - "-o", - "UserKnownHostsFile=/dev/null", - "-p", - "2222", - "-l", - "user", - "piper-fixed", - fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie), - ) - - if err != nil { - t.Errorf("failed to ssh to piper-fixed, %v", err) - } - - defer func() { - if c.Process != nil { - if err = c.Process.Kill(); err != nil { - log.Printf("failed to kill ssh process, %v", err) - } - } - }() - - enterPassword(stdin, stdout, "pass") - - time.Sleep(time.Second) // wait for file flush - - checkSharedFileContent(t, targetfie, randtext) -} diff --git a/e2e/workingdir_test.go b/e2e/workingdir_test.go new file mode 100644 index 00000000..3a291379 --- /dev/null +++ b/e2e/workingdir_test.go @@ -0,0 +1,213 @@ +package e2e_test + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path" + "testing" + "time" + + "github.com/google/uuid" +) + +const workingdir = "/shared/workingdir" + +func ensureWorkingDirectory() { + err := os.MkdirAll(workingdir, 0700) + if err != nil { + log.Panicf("failed to create working directory %s: %v", workingdir, err) + } +} + +func TestWorkingDirectory(t *testing.T) { + ensureWorkingDirectory() + waitForEndpointReady("piper-workingdir:2222") + + t.Run("bypassword", func(t *testing.T) { + userdir := path.Join(workingdir, "bypassword") + + { + if err := os.MkdirAll(userdir, 0700); err != nil { + t.Errorf("failed to create working directory %s: %v", userdir, err) + } + + if err := ioutil.WriteFile(path.Join(userdir, "sshpiper_upstream"), []byte("user@host-password:2222"), 0400); err != nil { + t.Errorf("failed to write upstream file: %v", err) + } + } + + { + b, err := runAndGetStdout( + "ssh-keyscan", + "-p", + "2222", + "host-password", + ) + + if err != nil { + t.Errorf("failed to run ssh-keyscan: %v", err) + } + + if err := ioutil.WriteFile(path.Join(userdir, "known_hosts"), b, 0400); err != nil { + t.Errorf("failed to write known_hosts: %v", err) + } + } + + { + randtext := uuid.New().String() + targetfie := uuid.New().String() + + c, stdin, stdout, err := runCmd( + "ssh", + "-v", + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile=/dev/null", + "-p", + "2222", + "-l", + "bypassword", + "piper-workingdir", + fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie), + ) + + if err != nil { + t.Errorf("failed to ssh to piper-workingdir, %v", err) + } + + defer killCmd(c) + + enterPassword(stdin, stdout, "pass") + + time.Sleep(time.Second) // wait for file flush + + checkSharedFileContent(t, targetfie, randtext) + } + }) + + t.Run("bypublickey", func(t *testing.T) { + userdir := path.Join(workingdir, "bypublickey") + if err := os.MkdirAll(userdir, 0700); err != nil { + t.Errorf("failed to create working directory %s: %v", userdir, err) + } + + if err := ioutil.WriteFile(path.Join(userdir, "sshpiper_upstream"), []byte("user@host-publickey:2222"), 0400); err != nil { + t.Errorf("failed to write upstream file: %v", err) + } + + { + b, err := runAndGetStdout( + "ssh-keyscan", + "-p", + "2222", + "host-publickey", + ) + + if err != nil { + t.Errorf("failed to run ssh-keyscan: %v", err) + } + + if err := ioutil.WriteFile(path.Join(userdir, "known_hosts"), b, 0400); err != nil { + t.Errorf("failed to write known_hosts: %v", err) + } + } + + keydir, err := os.MkdirTemp("", "") + // generate a local key + if err != nil { + t.Errorf("failed to create temp dir: %v", err) + } + + { + + if err := runCmdAndWait("rm", "-f", path.Join(keydir, "id_rsa")); err != nil { + t.Errorf("failed to remove id_rsa: %v", err) + } + + if err := runCmdAndWait( + "ssh-keygen", + "-N", + "", + "-f", + path.Join(keydir, "id_rsa"), + ); err != nil { + t.Errorf("failed to generate private key: %v", err) + } + + if err := runCmdAndWait( + "/bin/cp", + path.Join(keydir, "id_rsa.pub"), + path.Join(userdir, "authorized_keys"), + ); err != nil { + t.Errorf("failed to copy public key: %v", err) + } + + if err := runCmdAndWait( + "chmod", + "0400", + path.Join(userdir, "authorized_keys"), + ); err != nil { + t.Errorf("failed to chmod public key: %v", err) + } + + // set upstream key + if err := runCmdAndWait("rm", "-f", path.Join(userdir, "id_rsa")); err != nil { + t.Errorf("failed to remove id_rsa: %v", err) + } + + if err := runCmdAndWait( + "ssh-keygen", + "-N", + "", + "-f", + path.Join(userdir, "id_rsa"), + ); err != nil { + t.Errorf("failed to generate private key: %v", err) + } + + if err := runCmdAndWait( + "/bin/cp", + path.Join(userdir, "id_rsa.pub"), + "/sshconfig_publickey/.ssh/authorized_keys", + ); err != nil { + t.Errorf("failed to copy public key: %v", err) + } + } + + { + randtext := uuid.New().String() + targetfie := uuid.New().String() + + c, _, _, err := runCmd( + "ssh", + "-v", + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile=/dev/null", + "-p", + "2222", + "-l", + "bypublickey", + "-i", + path.Join(keydir, "id_rsa"), + "piper-workingdir", + fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie), + ) + + if err != nil { + t.Errorf("failed to ssh to piper-workingdir, %v", err) + } + + defer killCmd(c) + + time.Sleep(time.Second) // wait for file flush + + checkSharedFileContent(t, targetfie, randtext) + } + + }) +}