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
|
require 'shellwords'
require 'pathname'
require_relative 'gitlab_net'
require_relative 'gitlab_metrics'
require_relative 'current_user_helper'
require_relative 'api_command_helper'
require_relative 'log_helper'
class GitlabShell # rubocop:disable Metrics/ClassLength
include CurrentUserHelper
include APICommandHelper
include LogHelper
class AccessDeniedError < StandardError; end
class DisallowedCommandError < StandardError; end
class InvalidRepositoryPathError < StandardError; end
GIT_COMMANDS = %w(git-upload-pack git-receive-pack git-upload-archive git-lfs-authenticate).freeze
API_COMMANDS = %w(2fa_recovery_codes).freeze
def initialize(key_id)
@key_id = key_id
@config = GitlabConfig.new
end
# The origin_cmd variable contains UNTRUSTED input. If the user ran
# ssh git@gitlab.example.com 'evil command', then origin_cmd contains
# 'evil command'.
def exec(origin_cmd)
unless origin_cmd
puts "Welcome to GitLab, #{username}!"
return true
end
args = Shellwords.shellwords(origin_cmd)
args = parse_cmd(args)
return send("api_#{command}") if API_COMMANDS.include?(command)
action = GitlabMetrics.measure('verify-access') { verify_access }
process_action(action, args)
rescue GitlabNet::ApiUnreachableError
$stderr.puts "GitLab: Failed to authorize your Git request: internal API unreachable"
false
rescue AccessDeniedError => ex
$logger.warn('Access denied', command: origin_cmd, user: log_username)
$stderr.puts "GitLab: #{ex.message}"
false
rescue DisallowedCommandError
$logger.warn('Denied disallowed command', command: origin_cmd, user: log_username)
$stderr.puts "GitLab: Disallowed command"
false
rescue InvalidRepositoryPathError
$stderr.puts "GitLab: Invalid repository path"
false
rescue Action::Custom::UnsuccessfulError
$stderr.puts "GitLab: A custom action error has occurred"
false
end
private
attr_accessor :repo_name, :command, :git_access
attr_reader :config, :key_id, :repo_path
def parse_cmd(args)
# Handle Git for Windows 2.14 using "git upload-pack" instead of git-upload-pack
if args.length == 3 && args.first == 'git'
@command = "git-#{args[1]}"
args = [command, args.last]
else
@command = args.first
end
@git_access = command
return args if API_COMMANDS.include?(command)
raise DisallowedCommandError unless GIT_COMMANDS.include?(command)
case command
when 'git-lfs-authenticate'
raise DisallowedCommandError unless args.count >= 2
@repo_name = args[1]
case args[2]
when 'download'
@git_access = 'git-upload-pack'
when 'upload'
@git_access = 'git-receive-pack'
else
raise DisallowedCommandError
end
else
raise DisallowedCommandError unless args.count == 2
@repo_name = args.last
end
args
end
def verify_access
api.check_access(git_access, nil, repo_name, key_id, '_any')
end
def process_action(action, args)
if command == 'git-lfs-authenticate'
GitlabMetrics.measure('lfs-authenticate') do
$logger.info('Processing LFS authentication', user: log_username)
lfs_authenticate
end
return true
end
action.execute(command, args)
end
def api
GitlabNet.new
end
def lfs_authenticate
lfs_access = api.lfs_authenticate(key_id, repo_name)
return unless lfs_access
puts lfs_access.authentication_payload
end
end
|