summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/gitlab-sshd/acceptance_test.go177
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])
+}