diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/gitlab-sshd/acceptance_test.go | 177 |
1 files changed, 176 insertions, 1 deletions
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go index 02d8200..80a0838 100644 --- a/cmd/gitlab-sshd/acceptance_test.go +++ b/cmd/gitlab-sshd/acceptance_test.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "crypto/ed25519" + "encoding/json" "encoding/pem" "fmt" "io" @@ -16,18 +17,34 @@ import ( "path/filepath" "regexp" "runtime" + "strings" "testing" "github.com/mikesmitty/edkey" "github.com/pires/go-proxyproto" "github.com/stretchr/testify/require" + gitalyClient "gitlab.com/gitlab-org/gitaly/client" + pb "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" + "gitlab.com/gitlab-org/gitlab-shell/internal/testhelper" "golang.org/x/crypto/ssh" ) var ( - sshdPath = "" + sshdPath = "" + gitalyConnInfo *gitalyConnectionInfo ) +const ( + testRepo = "test-gitlab-shell/gitlab-test.git" + testRepoNamespace = "test-gitlab-shell" + testRepoImportUrl = "https://gitlab.com/gitlab-org/gitlab-test.git" +) + +type gitalyConnectionInfo struct { + Address string `json:"address"` + Storage string `json:"storage"` +} + func init() { rootDir := rootDir() sshdPath = filepath.Join(rootDir, "bin", "gitlab-sshd") @@ -35,6 +52,11 @@ func init() { if _, err := os.Stat(sshdPath); os.IsNotExist(err) { panic(fmt.Errorf("cannot find executable %s. Please run 'make compile'", sshdPath)) } + + gci, exists := os.LookupEnv("GITALY_CONNECTION_INFO") + if exists { + json.Unmarshal([]byte(gci), &gitalyConnInfo) + } } func rootDir() string { @@ -46,10 +68,37 @@ func rootDir() string { return filepath.Join(filepath.Dir(currentFile), "..", "..") } +func ensureGitalyRepository(t *testing.T) { + if os.Getenv("GITALY_CONNECTION_INFO") == "" { + t.Skip("GITALY_CONNECTION_INFO is not set") + } + + conn, err := gitalyClient.Dial(gitalyConnInfo.Address, gitalyClient.DefaultDialOpts) + require.NoError(t, err) + + namespace := pb.NewNamespaceServiceClient(conn) + repository := pb.NewRepositoryServiceClient(conn) + + // Remove the repository if it already exists, for consistency + rmNsReq := &pb.RemoveNamespaceRequest{StorageName: gitalyConnInfo.Storage, Name: testRepoNamespace} + _, err = namespace.RemoveNamespace(context.Background(), rmNsReq) + require.NoError(t, err) + + gl_repository := &pb.Repository{StorageName: gitalyConnInfo.Storage, RelativePath: testRepo} + createReq := &pb.CreateRepositoryFromURLRequest{Repository: gl_repository, Url: testRepoImportUrl} + + _, err = repository.CreateRepositoryFromURL(context.Background(), createReq) + require.NoError(t, err) +} + func successAPI(t *testing.T) http.Handler { t.Helper() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + testDirCleanup, err := testhelper.PrepareTestRootDir() + require.NoError(t, err) + defer testDirCleanup() + t.Logf("gitlab-api-mock: received request: %s %s", r.Method, r.RequestURI) w.Header().Set("Content-Type", "application/json") @@ -60,6 +109,24 @@ func successAPI(t *testing.T) http.Handler { fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`) case "/api/v4/internal/personal_access_token": fmt.Fprint(w, `{"success": true, "token": "testtoken", "scopes": ["api"], "expires_at": ""}`) + case "/api/v4/internal/two_factor_recovery_codes": + fmt.Fprint(w, `{"success": true, "recovery_codes": ["code1", "code2"]}`) + case "/api/v4/internal/two_factor_otp_check": + fmt.Fprint(w, `{"success": true}`) + case "/api/v4/internal/allowed": + body, err := ioutil.ReadFile(filepath.Join(testhelper.TestRoot, "responses/allowed_without_console_messages.json")) + require.NoError(t, err) + + response := strings.Replace(string(body), "GITALY_REPOSITORY", testRepo, 1) + + if gitalyConnInfo != nil { + response = strings.Replace(response, "GITALY_ADDRESS", gitalyConnInfo.Address, 1) + } + + fmt.Fprint(w, response) + require.NoError(t, err) + case "/api/v4/internal/lfs_authenticate": + fmt.Fprint(w, `{"username": "test-user", "lfs_token": "testlfstoken", "repo_path": "foo", "expires_in": 7200}`) default: t.Logf("Unexpected request to successAPI: %s", r.URL.EscapedPath()) t.FailNow() @@ -231,3 +298,111 @@ func TestPersonalAccessTokenSuccess(t *testing.T) { require.NoError(t, err) require.Equal(t, "Token: testtoken\nScopes: api\nExpires: never\n", string(output)) } + +func TestTwoFactorAuthRecoveryCodesSuccess(t *testing.T) { + client := runSSHD(t, successAPI(t)) + + session, err := client.NewSession() + require.NoError(t, err) + defer session.Close() + + stdin, err := session.StdinPipe() + require.NoError(t, err) + + stdout, err := session.StdoutPipe() + require.NoError(t, err) + + reader := bufio.NewReader(stdout) + + err = session.Start("2fa_recovery_codes") + require.NoError(t, err) + + line, err := reader.ReadString('\n') + require.NoError(t, err) + require.Equal(t, "Are you sure you want to generate new two-factor recovery codes?\n", line) + + line, err = reader.ReadString('\n') + require.NoError(t, err) + require.Equal(t, "Any existing recovery codes you saved will be invalidated. (yes/no)\n", line) + + _, err = fmt.Fprintln(stdin, "yes") + require.NoError(t, err) + + output, err := ioutil.ReadAll(stdout) + require.NoError(t, err) + require.Equal(t, ` +Your two-factor authentication recovery codes are: + +code1 +code2 + +During sign in, use one of the codes above when prompted for +your two-factor code. Then, visit your Profile Settings and add +a new device so you do not lose access to your account again. +`, string(output)) +} + +func TwoFactorAuthVerifySuccess(t *testing.T) { + client := runSSHD(t, successAPI(t)) + + session, err := client.NewSession() + require.NoError(t, err) + defer session.Close() + + stdin, err := session.StdinPipe() + require.NoError(t, err) + + stdout, err := session.StdoutPipe() + require.NoError(t, err) + + reader := bufio.NewReader(stdout) + + err = session.Start("2fa_verify") + require.NoError(t, err) + + line, err := reader.ReadString('\n') + require.NoError(t, err) + require.Equal(t, "OTP: ", line) + + _, err = fmt.Fprintln(stdin, "otp123") + require.NoError(t, err) + + output, err := ioutil.ReadAll(stdout) + require.NoError(t, err) + require.Equal(t, "OTP validation successful. Git operations are now allowed.\n", string(output)) +} + +func TestGitLfsAuthenticateSuccess(t *testing.T) { + client := runSSHD(t, successAPI(t)) + + session, err := client.NewSession() + require.NoError(t, err) + defer session.Close() + + output, err := session.Output("git-lfs-authenticate test-user/repo.git download") + + require.NoError(t, err) + require.Equal(t, `{"header":{"Authorization":"Basic dGVzdC11c2VyOnRlc3RsZnN0b2tlbg=="},"href":"/info/lfs","expires_in":7200} +`, string(output)) +} + +func TestGitReceivePackSuccess(t *testing.T) { + ensureGitalyRepository(t) + + client := runSSHD(t, successAPI(t)) + + session, err := client.NewSession() + require.NoError(t, err) + defer session.Close() + + output, err := session.Output(fmt.Sprintf("git-receive-pack %s", testRepo)) + require.NoError(t, err) + + outputLines := strings.Split(string(output), "\n") + + for i := 0; i < (len(outputLines) - 1); i++ { + require.Regexp(t, "^[0-9a-f]{44} refs/(heads|tags)/[^ ]+", outputLines[i]) + } + + require.Equal(t, "0000", outputLines[len(outputLines)-1]) +} |