1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
|
package commandargs
import (
"fmt"
"regexp"
"strings"
"github.com/mattn/go-shellwords"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
)
const (
Discover CommandType = "discover"
TwoFactorRecover CommandType = "2fa_recovery_codes"
TwoFactorVerify CommandType = "2fa_verify"
LfsAuthenticate CommandType = "git-lfs-authenticate"
LfsTransfer CommandType = "git-lfs-transfer"
ReceivePack CommandType = "git-receive-pack"
UploadPack CommandType = "git-upload-pack"
UploadArchive CommandType = "git-upload-archive"
PersonalAccessToken CommandType = "personal_access_token"
)
var (
whoKeyRegex = regexp.MustCompile(`\Akey-(?P<keyid>\d+)\z`)
whoUsernameRegex = regexp.MustCompile(`\Ausername-(?P<username>\S+)\z`)
GitCommands = []CommandType{LfsAuthenticate, UploadPack, ReceivePack, UploadArchive}
)
type Shell struct {
Arguments []string
GitlabUsername string
GitlabKeyId string
GitlabKrb5Principal string
SshArgs []string
CommandType CommandType
Env sshenv.Env
}
func (s *Shell) Parse() error {
if err := s.validate(); err != nil {
return err
}
s.parseWho()
return nil
}
func (s *Shell) GetArguments() []string {
return s.Arguments
}
func (s *Shell) validate() error {
if !s.Env.IsSSHConnection {
return fmt.Errorf("Only SSH allowed")
}
if err := s.ParseCommand(s.Env.OriginalCommand); err != nil {
return fmt.Errorf("Invalid SSH command: %w", err)
}
return nil
}
func (s *Shell) parseWho() {
for _, argument := range s.Arguments {
if keyId := tryParseKeyId(argument); keyId != "" {
s.GitlabKeyId = keyId
break
}
if username := tryParseUsername(argument); username != "" {
s.GitlabUsername = username
break
}
}
}
func tryParse(r *regexp.Regexp, argument string) string {
// sshd may execute the session for AuthorizedKeysCommand in multiple ways:
// 1. key-id
// 2. /path/to/shell -c key-id
args := strings.Split(argument, " ")
lastArg := args[len(args)-1]
matchInfo := r.FindStringSubmatch(lastArg)
if len(matchInfo) == 2 {
// The first element is the full matched string
// The second element is the named `keyid` or `username`
return matchInfo[1]
}
return ""
}
func tryParseKeyId(argument string) string {
return tryParse(whoKeyRegex, argument)
}
func tryParseUsername(argument string) string {
return tryParse(whoUsernameRegex, argument)
}
func (s *Shell) ParseCommand(commandString string) error {
args, err := shellwords.Parse(commandString)
if err != nil {
return err
}
// Handle Git for Windows 2.14 using "git upload-pack" instead of git-upload-pack
if len(args) > 1 && args[0] == "git" {
command := args[0] + "-" + args[1]
commandArgs := args[2:]
args = append([]string{command}, commandArgs...)
}
s.SshArgs = args
s.defineCommandType()
return nil
}
func (s *Shell) defineCommandType() {
if len(s.SshArgs) == 0 {
s.CommandType = Discover
} else {
s.CommandType = CommandType(s.SshArgs[0])
}
}
|