diff options
author | feistel <6742251-feistel@users.noreply.gitlab.com> | 2021-09-08 14:40:35 +0000 |
---|---|---|
committer | feistel <6742251-feistel@users.noreply.gitlab.com> | 2021-09-08 14:41:57 +0000 |
commit | 67415dc4f6f293460517d4281b5e4e80e66ffb91 (patch) | |
tree | f3c3e9162a39ddc8fcfcf6f659ab5cdf362871d6 | |
parent | 7884a4420ac8ffd3ee34589c0f8e0d25ca0fd076 (diff) | |
download | gitlab-shell-67415dc4f6f293460517d4281b5e4e80e66ffb91.tar.gz |
refactor: rearchitect command and executable Go modules
19 files changed, 739 insertions, 483 deletions
diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go new file mode 100644 index 0000000..e72f792 --- /dev/null +++ b/cmd/check/command/command.go @@ -0,0 +1,22 @@ +package command + +import ( + "gitlab.com/gitlab-org/gitlab-shell/internal/command" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand" + "gitlab.com/gitlab-org/gitlab-shell/internal/config" +) + + +func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) { + if cmd := build(config, readWriter); cmd != nil { + return cmd, nil + } + + return nil, disallowedcommand.Error +} + +func build(config *config.Config, readWriter *readwriter.ReadWriter) command.Command { + return &healthcheck.Command{Config: config, ReadWriter: readWriter} +} diff --git a/cmd/check/command/command_test.go b/cmd/check/command/command_test.go new file mode 100644 index 0000000..cd06456 --- /dev/null +++ b/cmd/check/command/command_test.go @@ -0,0 +1,42 @@ +package command_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck" + "gitlab.com/gitlab-org/gitlab-shell/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/internal/executable" + "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" +) + +var ( + basicConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket"} +) + +func TestNew(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + config *config.Config + expectedType interface{} + }{ + { + desc: "it returns a Healthcheck command", + config: basicConfig, + expectedType: &healthcheck.Command{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + command, err := command.New(tc.config, nil) + + require.NoError(t, err) + require.IsType(t, tc.expectedType, command) + }) + } +} diff --git a/cmd/check/main.go b/cmd/check/main.go index 44d8175..e4bcdf2 100644 --- a/cmd/check/main.go +++ b/cmd/check/main.go @@ -4,12 +4,12 @@ import ( "fmt" "os" + checkCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command" "gitlab.com/gitlab-org/gitlab-shell/internal/command" "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" "gitlab.com/gitlab-org/gitlab-shell/internal/config" "gitlab.com/gitlab-org/gitlab-shell/internal/executable" "gitlab.com/gitlab-org/gitlab-shell/internal/logger" - "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" ) func main() { @@ -19,7 +19,7 @@ func main() { ErrOut: os.Stderr, } - executable, err := executable.New(executable.Healthcheck, false) + executable, err := executable.New(executable.Healthcheck) if err != nil { fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting") os.Exit(1) @@ -34,7 +34,7 @@ func main() { logCloser := logger.Configure(config) defer logCloser.Close() - cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter) + cmd, err := checkCmd.New(config, readWriter) if err != nil { fmt.Fprintf(readWriter.ErrOut, "%v\n", err) os.Exit(1) diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go new file mode 100644 index 0000000..3db8605 --- /dev/null +++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go @@ -0,0 +1,39 @@ +package command + +import ( + "gitlab.com/gitlab-org/gitlab-shell/internal/command" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand" + "gitlab.com/gitlab-org/gitlab-shell/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/internal/executable" + "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" +) + +func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) { + args, err := Parse(e, arguments, env) + if err != nil { + return nil, err + } + + if cmd := build(args, config, readWriter); cmd != nil { + return cmd, nil + } + + return nil, disallowedcommand.Error +} + +func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (*commandargs.AuthorizedKeys, error) { + args := &commandargs.AuthorizedKeys{Arguments: arguments} + + if err := args.Parse(); err != nil { + return nil, err + } + + return args, nil +} + +func build(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) command.Command { + return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter} +} diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go new file mode 100644 index 0000000..5c5419e --- /dev/null +++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go @@ -0,0 +1,120 @@ +package command_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/internal/executable" + "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" +) + +var ( + authorizedKeysExec = &executable.Executable{Name: executable.AuthorizedKeysCheck} + basicConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket"} +) + +func TestNew(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + config *config.Config + expectedType interface{} + }{ + { + desc: "it returns a AuthorizedKeys command", + executable: authorizedKeysExec, + arguments: []string{"git", "git", "key"}, + config: basicConfig, + expectedType: &authorizedkeys.Command{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + command, err := command.New(tc.executable, tc.arguments, tc.env, tc.config, nil) + + require.NoError(t, err) + require.IsType(t, tc.expectedType, command) + }) + } +} + +func TestParseSuccess(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + expectedArgs commandargs.CommandArgs + expectError bool + }{ + { + desc: "It parses authorized-keys command", + executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, + arguments: []string{"git", "git", "key"}, + expectedArgs: &commandargs.AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + result, err := command.Parse(tc.executable, tc.arguments, tc.env) + + if !tc.expectError { + require.NoError(t, err) + require.Equal(t, tc.expectedArgs, result) + } else { + require.Error(t, err) + } + }) + } +} + +func TestParseFailure(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + expectedError string + }{ + { + desc: "With not enough arguments for the AuthorizedKeysCheck", + executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, + arguments: []string{"user"}, + expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>", + }, + { + desc: "With too many arguments for the AuthorizedKeysCheck", + executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, + arguments: []string{"user", "user", "key", "something-else"}, + expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>", + }, + { + desc: "With missing username for the AuthorizedKeysCheck", + executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, + arguments: []string{"user", "", "key"}, + expectedError: "# No username provided", + }, + { + desc: "With missing key for the AuthorizedKeysCheck", + executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, + arguments: []string{"user", "user", ""}, + expectedError: "# No key provided", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + _, err := command.Parse(tc.executable, tc.arguments, tc.env) + + require.EqualError(t, err, tc.expectedError) + }) + } +} diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go index cda3e0b..bfaeca7 100644 --- a/cmd/gitlab-shell-authorized-keys-check/main.go +++ b/cmd/gitlab-shell-authorized-keys-check/main.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command" "gitlab.com/gitlab-org/gitlab-shell/internal/command" "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" "gitlab.com/gitlab-org/gitlab-shell/internal/config" @@ -20,7 +21,7 @@ func main() { ErrOut: os.Stderr, } - executable, err := executable.New(executable.AuthorizedKeysCheck, true) + executable, err := executable.New(executable.AuthorizedKeysCheck) if err != nil { fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting") os.Exit(1) @@ -35,7 +36,7 @@ func main() { logCloser := logger.Configure(config) defer logCloser.Close() - cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter) + cmd, err := cmd.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter) if err != nil { // For now this could happen if `SSH_CONNECTION` is not set on // the environment diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go new file mode 100644 index 0000000..b5ded54 --- /dev/null +++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go @@ -0,0 +1,39 @@ +package command + +import ( + "gitlab.com/gitlab-org/gitlab-shell/internal/command" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand" + "gitlab.com/gitlab-org/gitlab-shell/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/internal/executable" + "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" +) + +func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) { + args, err := Parse(e, arguments, env) + if err != nil { + return nil, err + } + + if cmd := build(args, config, readWriter); cmd != nil { + return cmd, nil + } + + return nil, disallowedcommand.Error +} + +func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (*commandargs.AuthorizedPrincipals, error) { + args := &commandargs.AuthorizedPrincipals{Arguments: arguments} + + if err := args.Parse(); err != nil { + return nil, err + } + + return args, nil +} + +func build(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) command.Command { + return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter} +} diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go new file mode 100644 index 0000000..b6a89f5 --- /dev/null +++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go @@ -0,0 +1,114 @@ +package command_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/internal/executable" + "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" +) + +var ( + authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck} + basicConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket"} +) + +func TestNew(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + config *config.Config + expectedType interface{} + }{ + { + desc: "it returns a AuthorizedPrincipals command", + executable: authorizedPrincipalsExec, + arguments: []string{"key", "principal"}, + config: basicConfig, + expectedType: &authorizedprincipals.Command{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + command, err := cmd.New(tc.executable, tc.arguments, tc.env, tc.config, nil) + + require.NoError(t, err) + require.IsType(t, tc.expectedType, command) + }) + } +} + +func TestParseSuccess(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + expectedArgs commandargs.CommandArgs + expectError bool + }{ + { + desc: "It parses authorized-principals command", + executable: &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}, + arguments: []string{"key", "principal-1", "principal-2"}, + expectedArgs: &commandargs.AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + result, err := cmd.Parse(tc.executable, tc.arguments, tc.env) + + if !tc.expectError { + require.NoError(t, err) + require.Equal(t, tc.expectedArgs, result) + } else { + require.Error(t, err) + } + }) + } +} + +func TestParseFailure(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + expectedError string + }{ + { + desc: "With not enough arguments for the AuthorizedPrincipalsCheck", + executable: &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}, + arguments: []string{"key"}, + expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]", + }, + { + desc: "With missing key_id for the AuthorizedPrincipalsCheck", + executable: &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}, + arguments: []string{"", "principal"}, + expectedError: "# No key_id provided", + }, + { + desc: "With blank principal for the AuthorizedPrincipalsCheck", + executable: &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}, + arguments: []string{"key", "principal", ""}, + expectedError: "# An invalid principal was provided", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + _, err := cmd.Parse(tc.executable, tc.arguments, tc.env) + + require.EqualError(t, err, tc.expectedError) + }) + } +} diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go index 87f7fa3..d0e709c 100644 --- a/cmd/gitlab-shell-authorized-principals-check/main.go +++ b/cmd/gitlab-shell-authorized-principals-check/main.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command" "gitlab.com/gitlab-org/gitlab-shell/internal/command" "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" "gitlab.com/gitlab-org/gitlab-shell/internal/config" @@ -20,7 +21,7 @@ func main() { ErrOut: os.Stderr, } - executable, err := executable.New(executable.AuthorizedPrincipalsCheck, true) + executable, err := executable.New(executable.AuthorizedPrincipalsCheck) if err != nil { fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting") os.Exit(1) @@ -35,7 +36,7 @@ func main() { logCloser := logger.Configure(config) defer logCloser.Close() - cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter) + cmd, err := cmd.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter) if err != nil { // For now this could happen if `SSH_CONNECTION` is not set on // the environment diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go new file mode 100644 index 0000000..98bfdff --- /dev/null +++ b/cmd/gitlab-shell/command/command.go @@ -0,0 +1,65 @@ +package command + +import ( + "gitlab.com/gitlab-org/gitlab-shell/internal/command" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/discover" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack" + "gitlab.com/gitlab-org/gitlab-shell/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/internal/executable" + "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" +) + +func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) { + args, err := Parse(e, arguments, env) + if err != nil { + return nil, err + } + + if cmd := Build(args, config, readWriter); cmd != nil { + return cmd, nil + } + + return nil, disallowedcommand.Error +} + +func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (*commandargs.Shell, error) { + args := &commandargs.Shell{Arguments: arguments, Env: env} + + if err := args.Parse(); err != nil { + return nil, err + } + + return args, nil +} + +func Build(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) command.Command { + switch args.CommandType { + case commandargs.Discover: + return &discover.Command{Config: config, Args: args, ReadWriter: readWriter} + case commandargs.TwoFactorRecover: + return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter} + case commandargs.TwoFactorVerify: + return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter} + case commandargs.LfsAuthenticate: + return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter} + case commandargs.ReceivePack: + return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter} + case commandargs.UploadPack: + return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter} + case commandargs.UploadArchive: + return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter} + case commandargs.PersonalAccessToken: + return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter} + } + + return nil +} diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go new file mode 100644 index 0000000..5dacb67 --- /dev/null +++ b/cmd/gitlab-shell/command/command_test.go @@ -0,0 +1,281 @@ +package command_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/discover" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive" + "gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack" + "gitlab.com/gitlab-org/gitlab-shell/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/internal/executable" + "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" +) + +var ( + gitlabShellExec = &executable.Executable{Name: executable.GitlabShell} + basicConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket"} +) + +func TestNew(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + config *config.Config + expectedType interface{} + }{ + { + desc: "it returns a Discover command", + executable: gitlabShellExec, + env: buildEnv(""), + config: basicConfig, + expectedType: &discover.Command{}, + }, + { + desc: "it returns a TwoFactorRecover command", + executable: gitlabShellExec, + env: buildEnv("2fa_recovery_codes"), + config: basicConfig, + expectedType: &twofactorrecover.Command{}, + }, + { + desc: "it returns a TwoFactorVerify command", + executable: gitlabShellExec, + env: buildEnv("2fa_verify"), + config: basicConfig, + expectedType: &twofactorverify.Command{}, + }, + { + desc: "it returns an LfsAuthenticate command", + executable: gitlabShellExec, + env: buildEnv("git-lfs-authenticate"), + config: basicConfig, + expectedType: &lfsauthenticate.Command{}, + }, + { + desc: "it returns a ReceivePack command", + executable: gitlabShellExec, + env: buildEnv("git-receive-pack"), + config: basicConfig, + expectedType: &receivepack.Command{}, + }, + { + desc: "it returns an UploadPack command", + executable: gitlabShellExec, + env: buildEnv("git-upload-pack"), + config: basicConfig, + expectedType: &uploadpack.Command{}, + }, + { + desc: "it returns an UploadArchive command", + executable: gitlabShellExec, + env: buildEnv("git-upload-archive"), + config: basicConfig, + expectedType: &uploadarchive.Command{}, + }, + { + desc: "it returns a PersonalAccessToken command", + executable: gitlabShellExec, + env: buildEnv("personal_access_token"), + config: basicConfig, + expectedType: &personalaccesstoken.Command{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + command, err := cmd.New(tc.executable, tc.arguments, tc.env, tc.config, nil) + + require.NoError(t, err) + require.IsType(t, tc.expectedType, command) + }) + } +} + +func TestFailingNew(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + expectedError error + }{ + { + desc: "Parsing environment failed", + executable: gitlabShellExec, + expectedError: errors.New("Only SSH allowed"), + }, + { + desc: "Unknown command given", + executable: gitlabShellExec, + env: buildEnv("unknown"), + expectedError: disallowedcommand.Error, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + command, err := cmd.New(tc.executable, []string{}, tc.env, basicConfig, nil) + require.Nil(t, command) + require.Equal(t, tc.expectedError, err) + }) + } +} + +func buildEnv(command string) sshenv.Env { + return sshenv.Env{ + IsSSHConnection: true, + OriginalCommand: command, + } +} + +func TestParseSuccess(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + expectedArgs commandargs.CommandArgs + expectError bool + }{ + { + desc: "It sets discover as the command when the command string was empty", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}, + arguments: []string{}, + expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: commandargs.Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}}, + }, + { + desc: "It finds the key id in any passed arguments", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}, + arguments: []string{"hello", "key-123"}, + expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}}, + }, + { + desc: "It finds the key id only if the argument is of <key-id> format", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}, + arguments: []string{"hello", "username-key-123"}, + expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}}, + }, + { + desc: "It finds the username in any passed arguments", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}, + arguments: []string{"hello", "username-jane-doe"}, + expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}}, + }, + { + desc: "It parses 2fa_recovery_codes command", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}, + arguments: []string{}, + expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: commandargs.TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}}, + }, + { + desc: "It parses git-receive-pack command", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}, + arguments: []string{}, + expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}}, + }, + { + desc: "It parses git-receive-pack command and a project with single quotes", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}, + arguments: []string{}, + expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}}, + }, + { + desc: `It parses "git receive-pack" command`, + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}, + arguments: []string{}, + expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}}, + }, + { + desc: `It parses a command followed by control characters`, + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}, + arguments: []string{}, + expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}}, + }, + { + desc: "It parses git-upload-pack command", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}, + arguments: []string{}, + expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: commandargs.UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}}, + }, + { + desc: "It parses git-upload-archive command", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}, + arguments: []string{}, + expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: commandargs.UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}}, + }, + { + desc: "It parses git-lfs-authenticate command", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}, + arguments: []string{}, + expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: commandargs.LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + result, err := cmd.Parse(tc.executable, tc.arguments, tc.env) + + if !tc.expectError { + require.NoError(t, err) + require.Equal(t, tc.expectedArgs, result) + } else { + require.Error(t, err) + } + }) + } +} + +func TestParseFailure(t *testing.T) { + testCases := []struct { + desc string + executable *executable.Executable + env sshenv.Env + arguments []string + expectedError string + }{ + { + desc: "It fails if SSH connection is not set", + executable: &executable.Executable{Name: executable.GitlabShell}, + arguments: []string{}, + expectedError: "Only SSH allowed", + }, + { + desc: "It fails if SSH command is invalid", + executable: &executable.Executable{Name: executable.GitlabShell}, + env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`}, + arguments: []string{}, + expectedError: "Invalid SSH command", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + _, err := cmd.Parse(tc.executable, tc.arguments, tc.env) + + require.EqualError(t, err, tc.expectedError) + }) + } +} diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go index fe52bfc..14bd457 100644 --- a/cmd/gitlab-shell/main.go +++ b/cmd/gitlab-shell/main.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command" "gitlab.com/gitlab-org/gitlab-shell/internal/command" "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" "gitlab.com/gitlab-org/gitlab-shell/internal/config" @@ -34,7 +35,7 @@ func main() { ErrOut: os.Stderr, } - executable, err := executable.New(executable.GitlabShell, true) + executable, err := executable.New(executable.GitlabShell) if err != nil { fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting") os.Exit(1) @@ -50,7 +51,7 @@ func main() { defer logCloser.Close() env := sshenv.NewFromEnv() - cmd, err := command.New(executable, os.Args[1:], env, config, readWriter) + cmd, err := shellCmd.New(executable, os.Args[1:], env, config, readWriter) if err != nil { // For now this could happen if `SSH_CONNECTION` is not set on // the environment diff --git a/internal/command/command.go b/internal/command/command.go index dadf41a..4ee568e 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -3,23 +3,7 @@ package command import ( "context" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/discover" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack" "gitlab.com/gitlab-org/gitlab-shell/internal/config" - "gitlab.com/gitlab-org/gitlab-shell/internal/executable" - "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" "gitlab.com/gitlab-org/labkit/correlation" "gitlab.com/gitlab-org/labkit/tracing" ) @@ -28,23 +12,6 @@ type Command interface { Execute(ctx context.Context) error } -func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (Command, error) { - var args commandargs.CommandArgs - if e.AcceptArgs { - var err error - args, err = commandargs.Parse(e, arguments, env) - if err != nil { - return nil, err - } - } - - if cmd := buildCommand(e, args, config, readWriter); cmd != nil { - return cmd, nil - } - - return nil, disallowedcommand.Error -} - // Setup() initializes tracing from the configuration file and generates a // background context from which all other contexts in the process should derive // from, as it has a service name and initial correlation ID set. @@ -80,53 +47,3 @@ func Setup(serviceName string, config *config.Config) (context.Context, func()) closer.Close() } } - -func buildCommand(e *executable.Executable, args commandargs.CommandArgs, config *config.Config, readWriter *readwriter.ReadWriter) Command { - switch e.Name { - case executable.GitlabShell: - return BuildShellCommand(args.(*commandargs.Shell), config, readWriter) - case executable.AuthorizedKeysCheck: - return buildAuthorizedKeysCommand(args.(*commandargs.AuthorizedKeys), config, readWriter) - case executable.AuthorizedPrincipalsCheck: - return buildAuthorizedPrincipalsCommand(args.(*commandargs.AuthorizedPrincipals), config, readWriter) - case executable.Healthcheck: - return buildHealthcheckCommand(config, readWriter) - } - - return nil -} - -func BuildShellCommand(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) Command { - switch args.CommandType { - case commandargs.Discover: - return &discover.Command{Config: config, Args: args, ReadWriter: readWriter} - case commandargs.TwoFactorRecover: - return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter} - case commandargs.TwoFactorVerify: - return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter} - case commandargs.LfsAuthenticate: - return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter} - case commandargs.ReceivePack: - return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter} - case commandargs.UploadPack: - return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter} - case commandargs.UploadArchive: - return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter} - case commandargs.PersonalAccessToken: - return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter} - } - - return nil -} - -func buildAuthorizedKeysCommand(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) Command { - return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter} -} - -func buildAuthorizedPrincipalsCommand(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) Command { - return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter} -} - -func buildHealthcheckCommand(config *config.Config, readWriter *readwriter.ReadWriter) Command { - return &healthcheck.Command{Config: config, ReadWriter: readWriter} -} diff --git a/internal/command/command_test.go b/internal/command/command_test.go index a538745..2fc6655 100644 --- a/internal/command/command_test.go +++ b/internal/command/command_test.go @@ -1,173 +1,15 @@ package command import ( - "errors" "os" "testing" "github.com/stretchr/testify/require" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/discover" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive" - "gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack" "gitlab.com/gitlab-org/gitlab-shell/internal/config" - "gitlab.com/gitlab-org/gitlab-shell/internal/executable" - "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" "gitlab.com/gitlab-org/labkit/correlation" ) -var ( - authorizedKeysExec = &executable.Executable{Name: executable.AuthorizedKeysCheck, AcceptArgs: true} - authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck, AcceptArgs: true} - checkExec = &executable.Executable{Name: executable.Healthcheck, AcceptArgs: false} - gitlabShellExec = &executable.Executable{Name: executable.GitlabShell, AcceptArgs: true} - - basicConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket"} - advancedConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket", SslCertDir: "/tmp/certs"} -) - -func buildEnv(command string) sshenv.Env { - return sshenv.Env{ - IsSSHConnection: true, - OriginalCommand: command, - } -} - -func TestNew(t *testing.T) { - testCases := []struct { - desc string - executable *executable.Executable - env sshenv.Env - arguments []string - config *config.Config - expectedType interface{} - }{ - { - desc: "it returns a Discover command", - executable: gitlabShellExec, - env: buildEnv(""), - config: basicConfig, - expectedType: &discover.Command{}, - }, - { - desc: "it returns a TwoFactorRecover command", - executable: gitlabShellExec, - env: buildEnv("2fa_recovery_codes"), - config: basicConfig, - expectedType: &twofactorrecover.Command{}, - }, - { - desc: "it returns a TwoFactorVerify command", - executable: gitlabShellExec, - env: buildEnv("2fa_verify"), - config: basicConfig, - expectedType: &twofactorverify.Command{}, - }, - { - desc: "it returns an LfsAuthenticate command", - executable: gitlabShellExec, - env: buildEnv("git-lfs-authenticate"), - config: basicConfig, - expectedType: &lfsauthenticate.Command{}, - }, - { - desc: "it returns a ReceivePack command", - executable: gitlabShellExec, - env: buildEnv("git-receive-pack"), - config: basicConfig, - expectedType: &receivepack.Command{}, - }, - { - desc: "it returns an UploadPack command", - executable: gitlabShellExec, - env: buildEnv("git-upload-pack"), - config: basicConfig, - expectedType: &uploadpack.Command{}, - }, - { - desc: "it returns an UploadArchive command", - executable: gitlabShellExec, - env: buildEnv("git-upload-archive"), - config: basicConfig, - expectedType: &uploadarchive.Command{}, - }, - { - desc: "it returns a Healthcheck command", - executable: checkExec, - config: basicConfig, - expectedType: &healthcheck.Command{}, - }, - { - desc: "it returns a AuthorizedKeys command", - executable: authorizedKeysExec, - arguments: []string{"git", "git", "key"}, - config: basicConfig, - expectedType: &authorizedkeys.Command{}, - }, - { - desc: "it returns a AuthorizedPrincipals command", - executable: authorizedPrincipalsExec, - arguments: []string{"key", "principal"}, - config: basicConfig, - expectedType: &authorizedprincipals.Command{}, - }, - { - desc: "it returns a PersonalAccessToken command", - executable: gitlabShellExec, - env: buildEnv("personal_access_token"), - config: basicConfig, - expectedType: &personalaccesstoken.Command{}, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - command, err := New(tc.executable, tc.arguments, tc.env, tc.config, nil) - - require.NoError(t, err) - require.IsType(t, tc.expectedType, command) - }) - } -} - -func TestFailingNew(t *testing.T) { - testCases := []struct { - desc string - executable *executable.Executable - env sshenv.Env - expectedError error - }{ - { - desc: "Parsing environment failed", - executable: gitlabShellExec, - expectedError: errors.New("Only SSH allowed"), - }, - { - desc: "Unknown command given", - executable: gitlabShellExec, - env: buildEnv("unknown"), - expectedError: disallowedcommand.Error, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - command, err := New(tc.executable, []string{}, tc.env, basicConfig, nil) - require.Nil(t, command) - require.Equal(t, tc.expectedError, err) - }) - } -} - func TestSetup(t *testing.T) { testCases := []struct { name string diff --git a/internal/command/commandargs/command_args.go b/internal/command/commandargs/command_args.go index a01b8b2..f23ba18 100644 --- a/internal/command/commandargs/command_args.go +++ b/internal/command/commandargs/command_args.go @@ -1,37 +1,8 @@ package commandargs -import ( - "errors" - "fmt" - - "gitlab.com/gitlab-org/gitlab-shell/internal/executable" - "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" -) - type CommandType string type CommandArgs interface { Parse() error GetArguments() []string } - -func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (CommandArgs, error) { - var args CommandArgs - - switch e.Name { - case executable.GitlabShell: - args = &Shell{Arguments: arguments, Env: env} - case executable.AuthorizedKeysCheck: - args = &AuthorizedKeys{Arguments: arguments} - case executable.AuthorizedPrincipalsCheck: - args = &AuthorizedPrincipals{Arguments: arguments} - default: - return nil, errors.New(fmt.Sprintf("unknown executable: %s", e.Name)) - } - - if err := args.Parse(); err != nil { - return nil, err - } - - return args, nil -} diff --git a/internal/command/commandargs/command_args_test.go b/internal/command/commandargs/command_args_test.go deleted file mode 100644 index 119ecd4..0000000 --- a/internal/command/commandargs/command_args_test.go +++ /dev/null @@ -1,197 +0,0 @@ -package commandargs - -import ( - "testing" - - "gitlab.com/gitlab-org/gitlab-shell/internal/executable" - "gitlab.com/gitlab-org/gitlab-shell/internal/sshenv" - - "github.com/stretchr/testify/require" -) - -func TestParseSuccess(t *testing.T) { - testCases := []struct { - desc string - executable *executable.Executable - env sshenv.Env - arguments []string - expectedArgs CommandArgs - expectError bool - }{ - { - desc: "It sets discover as the command when the command string was empty", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}, - arguments: []string{}, - expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}}, - }, { - desc: "It finds the key id in any passed arguments", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}, - arguments: []string{"hello", "key-123"}, - expectedArgs: &Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}}, - }, { - desc: "It finds the key id only if the argument is of <key-id> format", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}, - arguments: []string{"hello", "username-key-123"}, - expectedArgs: &Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}}, - }, { - desc: "It finds the username in any passed arguments", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}, - arguments: []string{"hello", "username-jane-doe"}, - expectedArgs: &Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}}, - }, { - desc: "It parses 2fa_recovery_codes command", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}, - arguments: []string{}, - expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}}, - }, { - desc: "It parses git-receive-pack command", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}, - arguments: []string{}, - expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}}, - }, { - desc: "It parses git-receive-pack command and a project with single quotes", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}, - arguments: []string{}, - expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}}, - }, { - desc: `It parses "git receive-pack" command`, - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}, - arguments: []string{}, - expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}}, - }, { - desc: `It parses a command followed by control characters`, - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}, - arguments: []string{}, - expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}}, - }, { - desc: "It parses git-upload-pack command", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}, - arguments: []string{}, - expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}}, - }, { - desc: "It parses git-upload-archive command", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}, - arguments: []string{}, - expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}}, - }, { - desc: "It parses git-lfs-authenticate command", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}, - arguments: []string{}, - expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}}, - }, { - desc: "It parses authorized-keys command", - executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, - arguments: []string{"git", "git", "key"}, - expectedArgs: &AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"}, - }, { - desc: "It parses authorized-principals command", - executable: &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}, - arguments: []string{"key", "principal-1", "principal-2"}, - expectedArgs: &AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}}, - }, { - desc: "Unknown executable", - executable: &executable.Executable{Name: "unknown"}, - arguments: []string{}, - expectError: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - result, err := Parse(tc.executable, tc.arguments, tc.env) - - if !tc.expectError { - require.NoError(t, err) - require.Equal(t, tc.expectedArgs, result) - } else { - require.Error(t, err) - } - }) - } -} - -func TestParseFailure(t *testing.T) { - testCases := []struct { - desc string - executable *executable.Executable - env sshenv.Env - arguments []string - expectedError string - }{ - { - desc: "It fails if SSH connection is not set", - executable: &executable.Executable{Name: executable.GitlabShell}, - arguments: []string{}, - expectedError: "Only SSH allowed", - }, - { - desc: "It fails if SSH command is invalid", - executable: &executable.Executable{Name: executable.GitlabShell}, - env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`}, - arguments: []string{}, - expectedError: "Invalid SSH command", - }, - { - desc: "With not enough arguments for the AuthorizedKeysCheck", - executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, - arguments: []string{"user"}, - expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>", - }, - { - desc: "With too many arguments for the AuthorizedKeysCheck", - executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, - arguments: []string{"user", "user", "key", "something-else"}, - expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>", - }, - { - desc: "With missing username for the AuthorizedKeysCheck", - executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, - arguments: []string{"user", "", "key"}, - expectedError: "# No username provided", - }, - { - desc: "With missing key for the AuthorizedKeysCheck", - executable: &executable.Executable{Name: executable.AuthorizedKeysCheck}, - arguments: []string{"user", "user", ""}, - expectedError: "# No key provided", - }, - { - desc: "With not enough arguments for the AuthorizedPrincipalsCheck", - executable: &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}, - arguments: []string{"key"}, - expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]", - }, - { - desc: "With missing key_id for the AuthorizedPrincipalsCheck", - executable: &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}, - arguments: []string{"", "principal"}, - expectedError: "# No key_id provided", - }, - { - desc: "With blank principal for the AuthorizedPrincipalsCheck", - executable: &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}, - arguments: []string{"key", "principal", ""}, - expectedError: "# An invalid principal was provided", - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - _, err := Parse(tc.executable, tc.arguments, tc.env) - - require.EqualError(t, err, tc.expectedError) - }) - } -} diff --git a/internal/executable/executable.go b/internal/executable/executable.go index 8b6b586..108c3ea 100644 --- a/internal/executable/executable.go +++ b/internal/executable/executable.go @@ -16,7 +16,6 @@ const ( type Executable struct { Name string RootDir string - AcceptArgs bool } var ( @@ -24,7 +23,7 @@ var ( osExecutable = os.Executable ) -func New(name string, acceptArgs bool) (*Executable, error) { +func New(name string) (*Executable, error) { path, err := osExecutable() if err != nil { return nil, err @@ -38,7 +37,6 @@ func New(name string, acceptArgs bool) (*Executable, error) { executable := &Executable{ Name: name, RootDir: rootDir, - AcceptArgs: acceptArgs, } return executable, nil diff --git a/internal/executable/executable_test.go b/internal/executable/executable_test.go index 71984c3..3915f1a 100644 --- a/internal/executable/executable_test.go +++ b/internal/executable/executable_test.go @@ -59,7 +59,7 @@ func TestNewSuccess(t *testing.T) { fake.Setup() defer fake.Cleanup() - result, err := New("gitlab-shell", true) + result, err := New("gitlab-shell") require.NoError(t, err) require.Equal(t, result.Name, "gitlab-shell") @@ -96,7 +96,7 @@ func TestNewFailure(t *testing.T) { fake.Setup() defer fake.Cleanup() - _, err := New("gitlab-shell", true) + _, err := New("gitlab-shell") require.Error(t, err) }) diff --git a/internal/sshd/session.go b/internal/sshd/session.go index 22cb715..b58598e 100644 --- a/internal/sshd/session.go +++ b/internal/sshd/session.go @@ -6,7 +6,7 @@ import ( "golang.org/x/crypto/ssh" - "gitlab.com/gitlab-org/gitlab-shell/internal/command" + shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command" "gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs" "gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter" "gitlab.com/gitlab-org/gitlab-shell/internal/config" @@ -125,7 +125,7 @@ func (s *session) handleShell(ctx context.Context, req *ssh.Request) uint32 { ErrOut: s.channel.Stderr(), } - cmd := command.BuildShellCommand(args, s.cfg, rw) + cmd := shellCmd.Build(args, s.cfg, rw) if cmd == nil { s.toStderr("Unknown command: %v\n", args.CommandType) return 128 |