summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Drozdov <idrozdov@gitlab.com>2021-03-17 14:03:22 +0000
committerIgor Drozdov <idrozdov@gitlab.com>2021-03-17 14:03:22 +0000
commit4b40a2cb8c71a5b490cad4c8e1ad2dc0e9b39548 (patch)
tree165423c495296126cca02c4073afc51e164847ba
parentdc2bddcc325f224ba074cb4d67ecba44ed15daae (diff)
parent92f9a45e15976e6224c1159f39fba7d2e98fc560 (diff)
downloadgitlab-shell-4b40a2cb8c71a5b490cad4c8e1ad2dc0e9b39548.tar.gz
Merge branch '500-gitlab-sshd-acceptance-tests' into 'main'
gitlab-sshd: Acceptance test for the discover command See merge request gitlab-org/gitlab-shell!457
-rw-r--r--cmd/gitlab-sshd/acceptance_test.go192
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--internal/sshd/sshd.go2
4 files changed, 197 insertions, 0 deletions
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
new file mode 100644
index 0000000..1b6931b
--- /dev/null
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -0,0 +1,192 @@
+package main_test
+
+import (
+ "bufio"
+ "context"
+ "crypto/ed25519"
+ "encoding/pem"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "testing"
+
+ "github.com/mikesmitty/edkey"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/crypto/ssh"
+)
+
+var (
+ sshdPath = ""
+)
+
+func init() {
+ rootDir := rootDir()
+ sshdPath = filepath.Join(rootDir, "bin", "gitlab-sshd")
+
+ if _, err := os.Stat(sshdPath); os.IsNotExist(err) {
+ panic(fmt.Errorf("cannot find executable %s. Please run 'make compile'", sshdPath))
+ }
+}
+
+func rootDir() string {
+ _, currentFile, _, ok := runtime.Caller(0)
+ if !ok {
+ panic(fmt.Errorf("rootDir: calling runtime.Caller failed"))
+ }
+
+ return filepath.Join(filepath.Dir(currentFile), "..", "..")
+}
+
+func successAPI(t *testing.T) http.Handler {
+ t.Helper()
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Logf("gitlab-api-mock: received request: %s %s", r.Method, r.RequestURI)
+ w.Header().Set("Content-Type", "application/json")
+
+ switch r.URL.EscapedPath() {
+ case "/api/v4/internal/authorized_keys":
+ fmt.Fprintf(w, `{"id":1, "key":"%s"}`, r.FormValue("key"))
+ case "/api/v4/internal/discover":
+ fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`)
+ default:
+ t.Log("Unexpected request to successAPI!")
+ t.FailNow()
+ }
+ })
+}
+
+func genServerConfig(gitlabUrl, hostKeyPath string) []byte {
+ return []byte(`---
+user: "git"
+log_file: ""
+log_format: json
+secret: "0123456789abcdef"
+gitlab_url: "` + gitlabUrl + `"
+sshd:
+ listen: "127.0.0.1:0"
+ web_listen: ""
+ host_key_files:
+ - "` + hostKeyPath + `"`)
+}
+
+func buildClient(t *testing.T, addr string, hostKey ed25519.PublicKey) *ssh.Client {
+ t.Helper()
+
+ pubKey, err := ssh.NewPublicKey(hostKey)
+ require.NoError(t, err)
+
+ _, clientPrivKey, err := ed25519.GenerateKey(nil)
+ require.NoError(t, err)
+
+ clientSigner, err := ssh.NewSignerFromKey(clientPrivKey)
+ require.NoError(t, err)
+
+ client, err := ssh.Dial("tcp", addr, &ssh.ClientConfig{
+ User: "git",
+ Auth: []ssh.AuthMethod{ssh.PublicKeys(clientSigner)},
+ HostKeyCallback: ssh.FixedHostKey(pubKey),
+ })
+ require.NoError(t, err)
+
+ t.Cleanup(func() { client.Close() })
+
+ return client
+}
+
+func configureSSHD(t *testing.T, apiServer string) (string, ed25519.PublicKey) {
+ t.Helper()
+
+ dir, err := ioutil.TempDir("", "gitlab-sshd-acceptance-test-")
+ require.NoError(t, err)
+ t.Cleanup(func() { os.RemoveAll(dir) })
+
+ configFile := filepath.Join(dir, "config.yml")
+ hostKeyFile := filepath.Join(dir, "hostkey")
+
+ pub, priv, err := ed25519.GenerateKey(nil)
+ require.NoError(t, err)
+
+ configFileData := genServerConfig(apiServer, hostKeyFile)
+ require.NoError(t, ioutil.WriteFile(configFile, configFileData, 0644))
+
+ block := &pem.Block{Type: "OPENSSH PRIVATE KEY", Bytes: edkey.MarshalED25519PrivateKey(priv)}
+ hostKeyData := pem.EncodeToMemory(block)
+ require.NoError(t, ioutil.WriteFile(hostKeyFile, hostKeyData, 0400))
+
+ return dir, pub
+}
+
+func startSSHD(t *testing.T, dir string) string {
+ t.Helper()
+
+ // We need to scan the first few lines of stderr to get the listen address.
+ // Once we've learned it, we'll start a goroutine to copy everything to
+ // the real stderr
+ pr, pw := io.Pipe()
+ t.Cleanup(func() { pr.Close() })
+ t.Cleanup(func() { pw.Close() })
+
+ scanner := bufio.NewScanner(pr)
+ extractor := regexp.MustCompile(`msg="Listening on ([0-9a-f\[\]\.:]+)"`)
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cmd := exec.CommandContext(ctx, sshdPath, "-config-dir", dir)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = pw
+ require.NoError(t, cmd.Start())
+ t.Logf("gitlab-sshd: Start(): success")
+ t.Cleanup(func() { t.Logf("gitlab-sshd: Wait(): %v", cmd.Wait()) })
+ t.Cleanup(cancel)
+
+ var listenAddr string
+ for scanner.Scan() {
+ if matches := extractor.FindSubmatch(scanner.Bytes()); len(matches) == 2 {
+ listenAddr = string(matches[1])
+ break
+ }
+ }
+ require.NotEmpty(t, listenAddr, "Couldn't extract listen address from gitlab-sshd")
+
+ go io.Copy(os.Stderr, pr)
+
+ return listenAddr
+}
+
+// Starts an instance of gitlab-sshd with the given arguments, returning an SSH
+// client already connected to it
+func runSSHD(t *testing.T, apiHandler http.Handler) *ssh.Client {
+ t.Helper()
+
+ // Set up a stub gitlab server
+ apiServer := httptest.NewServer(apiHandler)
+ t.Logf("gitlab-api-mock: started: url=%q", apiServer.URL)
+ t.Cleanup(func() {
+ apiServer.Close()
+ t.Logf("gitlab-api-mock: closed")
+ })
+
+ dir, hostKey := configureSSHD(t, apiServer.URL)
+ listenAddr := startSSHD(t, dir)
+
+ return buildClient(t, listenAddr, hostKey)
+}
+
+func TestDiscoverSuccess(t *testing.T) {
+ client := runSSHD(t, successAPI(t))
+
+ session, err := client.NewSession()
+ require.NoError(t, err)
+ defer session.Close()
+
+ output, err := session.Output("discover")
+ require.NoError(t, err)
+ require.Equal(t, "Welcome to GitLab, @test-user!\n", string(output))
+}
diff --git a/go.mod b/go.mod
index f871738..8ae423e 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.13
require (
github.com/mattn/go-shellwords v1.0.11
+ github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
github.com/otiai10/copy v1.4.2
github.com/prometheus/client_golang v1.9.0
github.com/sirupsen/logrus v1.7.0
diff --git a/go.sum b/go.sum
index e7f9124..df15888 100644
--- a/go.sum
+++ b/go.sum
@@ -319,6 +319,8 @@ github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
+github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
index 496e503..74029b0 100644
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -74,6 +74,8 @@ func Run(cfg *config.Config) error {
return fmt.Errorf("failed to listen for connection: %w", err)
}
+ log.Infof("Listening on %v", sshListener.Addr().String())
+
config := &ssh.ServerConfig{
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
if conn.User() != cfg.User {