blob: 616d7e345487f806bbef30d36b69a728fc92fb9c (
plain)
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
|
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"
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`)
)
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])
}
}
|