##// END OF EJS Templates
ssh: handle subrepos better
super-admin -
r4703:3248f2d8 stable
parent child Browse files
Show More
@@ -1,227 +1,238 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import re
22 import re
23 import logging
23 import logging
24 import datetime
24 import datetime
25 from pyramid.compat import configparser
25 from pyramid.compat import configparser
26
26
27 from rhodecode.model.db import Session, User, UserSshKeys
27 from rhodecode.model.db import Session, User, UserSshKeys
28 from rhodecode.model.scm import ScmModel
28 from rhodecode.model.scm import ScmModel
29
29
30 from .hg import MercurialServer
30 from .hg import MercurialServer
31 from .git import GitServer
31 from .git import GitServer
32 from .svn import SubversionServer
32 from .svn import SubversionServer
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class SshWrapper(object):
36 class SshWrapper(object):
37 hg_cmd_pat = re.compile(r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$')
37 hg_cmd_pat = re.compile(r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$')
38 git_cmd_pat = re.compile(r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
38 git_cmd_pat = re.compile(r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
39 svn_cmd_pat = re.compile(r'^svnserve -t')
39 svn_cmd_pat = re.compile(r'^svnserve -t')
40
40
41 def __init__(self, command, connection_info, mode,
41 def __init__(self, command, connection_info, mode,
42 user, user_id, key_id, shell, ini_path, env):
42 user, user_id, key_id, shell, ini_path, env):
43 self.command = command
43 self.command = command
44 self.connection_info = connection_info
44 self.connection_info = connection_info
45 self.mode = mode
45 self.mode = mode
46 self.user = user
46 self.user = user
47 self.user_id = user_id
47 self.user_id = user_id
48 self.key_id = key_id
48 self.key_id = key_id
49 self.shell = shell
49 self.shell = shell
50 self.ini_path = ini_path
50 self.ini_path = ini_path
51 self.env = env
51 self.env = env
52
52
53 self.config = self.parse_config(ini_path)
53 self.config = self.parse_config(ini_path)
54 self.server_impl = None
54 self.server_impl = None
55
55
56 def parse_config(self, config_path):
56 def parse_config(self, config_path):
57 parser = configparser.ConfigParser()
57 parser = configparser.ConfigParser()
58 parser.read(config_path)
58 parser.read(config_path)
59 return parser
59 return parser
60
60
61 def update_key_access_time(self, key_id):
61 def update_key_access_time(self, key_id):
62 key = UserSshKeys().query().filter(
62 key = UserSshKeys().query().filter(
63 UserSshKeys.ssh_key_id == key_id).scalar()
63 UserSshKeys.ssh_key_id == key_id).scalar()
64 if key:
64 if key:
65 key.accessed_on = datetime.datetime.utcnow()
65 key.accessed_on = datetime.datetime.utcnow()
66 Session().add(key)
66 Session().add(key)
67 Session().commit()
67 Session().commit()
68 log.debug('Update key id:`%s` fingerprint:`%s` access time',
68 log.debug('Update key id:`%s` fingerprint:`%s` access time',
69 key_id, key.ssh_key_fingerprint)
69 key_id, key.ssh_key_fingerprint)
70
70
71 def get_connection_info(self):
71 def get_connection_info(self):
72 """
72 """
73 connection_info
73 connection_info
74
74
75 Identifies the client and server ends of the connection.
75 Identifies the client and server ends of the connection.
76 The variable contains four space-separated values: client IP address,
76 The variable contains four space-separated values: client IP address,
77 client port number, server IP address, and server port number.
77 client port number, server IP address, and server port number.
78 """
78 """
79 conn = dict(
79 conn = dict(
80 client_ip=None,
80 client_ip=None,
81 client_port=None,
81 client_port=None,
82 server_ip=None,
82 server_ip=None,
83 server_port=None,
83 server_port=None,
84 )
84 )
85
85
86 info = self.connection_info.split(' ')
86 info = self.connection_info.split(' ')
87 if len(info) == 4:
87 if len(info) == 4:
88 conn['client_ip'] = info[0]
88 conn['client_ip'] = info[0]
89 conn['client_port'] = info[1]
89 conn['client_port'] = info[1]
90 conn['server_ip'] = info[2]
90 conn['server_ip'] = info[2]
91 conn['server_port'] = info[3]
91 conn['server_port'] = info[3]
92
92
93 return conn
93 return conn
94
94
95 def maybe_translate_repo_uid(self, repo_name):
95 def maybe_translate_repo_uid(self, repo_name):
96 _org_name = repo_name
97 if _org_name.startswith('_'):
98 # remove format of _ID/subrepo
99 _org_name = _org_name.split('/', 1)[0]
100
96 if repo_name.startswith('_'):
101 if repo_name.startswith('_'):
97 from rhodecode.model.repo import RepoModel
102 from rhodecode.model.repo import RepoModel
103 org_repo_name = repo_name
104 log.debug('translating UID repo %s', org_repo_name)
98 by_id_match = RepoModel().get_repo_by_id(repo_name)
105 by_id_match = RepoModel().get_repo_by_id(repo_name)
99 if by_id_match:
106 if by_id_match:
100 repo_name = by_id_match.repo_name
107 repo_name = by_id_match.repo_name
101 return repo_name
108 log.debug('translation of UID repo %s got `%s`', org_repo_name, repo_name)
109
110 return repo_name, _org_name
102
111
103 def get_repo_details(self, mode):
112 def get_repo_details(self, mode):
104 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
113 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
105 repo_name = None
114 repo_name = None
106
115
107 hg_match = self.hg_cmd_pat.match(self.command)
116 hg_match = self.hg_cmd_pat.match(self.command)
108 if hg_match is not None:
117 if hg_match is not None:
109 vcs_type = 'hg'
118 vcs_type = 'hg'
110 repo_name = self.maybe_translate_repo_uid(hg_match.group(1).strip('/'))
119 repo_id = hg_match.group(1).strip('/')
120 repo_name, org_name = self.maybe_translate_repo_uid(repo_id)
111 return vcs_type, repo_name, mode
121 return vcs_type, repo_name, mode
112
122
113 git_match = self.git_cmd_pat.match(self.command)
123 git_match = self.git_cmd_pat.match(self.command)
114 if git_match is not None:
124 if git_match is not None:
125 mode = git_match.group(1)
115 vcs_type = 'git'
126 vcs_type = 'git'
116 repo_name = self.maybe_translate_repo_uid(git_match.group(2).strip('/'))
127 repo_id = git_match.group(2).strip('/')
117 mode = git_match.group(1)
128 repo_name, org_name = self.maybe_translate_repo_uid(repo_id)
118 return vcs_type, repo_name, mode
129 return vcs_type, repo_name, mode
119
130
120 svn_match = self.svn_cmd_pat.match(self.command)
131 svn_match = self.svn_cmd_pat.match(self.command)
121 if svn_match is not None:
132 if svn_match is not None:
122 vcs_type = 'svn'
133 vcs_type = 'svn'
123 # Repo name should be extracted from the input stream, we're unable to
134 # Repo name should be extracted from the input stream, we're unable to
124 # extract it at this point in execution
135 # extract it at this point in execution
125 return vcs_type, repo_name, mode
136 return vcs_type, repo_name, mode
126
137
127 return vcs_type, repo_name, mode
138 return vcs_type, repo_name, mode
128
139
129 def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
140 def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
130 store = ScmModel().repos_path
141 store = ScmModel().repos_path
131
142
132 check_branch_perms = False
143 check_branch_perms = False
133 detect_force_push = False
144 detect_force_push = False
134
145
135 if branch_permissions:
146 if branch_permissions:
136 check_branch_perms = True
147 check_branch_perms = True
137 detect_force_push = True
148 detect_force_push = True
138
149
139 log.debug(
150 log.debug(
140 'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
151 'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
141 vcs, mode, repo, check_branch_perms)
152 vcs, mode, repo, check_branch_perms)
142
153
143 # detect if we have to check branch permissions
154 # detect if we have to check branch permissions
144 extras = {
155 extras = {
145 'detect_force_push': detect_force_push,
156 'detect_force_push': detect_force_push,
146 'check_branch_perms': check_branch_perms,
157 'check_branch_perms': check_branch_perms,
147 }
158 }
148
159
149 if vcs == 'hg':
160 if vcs == 'hg':
150 server = MercurialServer(
161 server = MercurialServer(
151 store=store, ini_path=self.ini_path,
162 store=store, ini_path=self.ini_path,
152 repo_name=repo, user=user,
163 repo_name=repo, user=user,
153 user_permissions=permissions, config=self.config, env=self.env)
164 user_permissions=permissions, config=self.config, env=self.env)
154 self.server_impl = server
165 self.server_impl = server
155 return server.run(tunnel_extras=extras)
166 return server.run(tunnel_extras=extras)
156
167
157 elif vcs == 'git':
168 elif vcs == 'git':
158 server = GitServer(
169 server = GitServer(
159 store=store, ini_path=self.ini_path,
170 store=store, ini_path=self.ini_path,
160 repo_name=repo, repo_mode=mode, user=user,
171 repo_name=repo, repo_mode=mode, user=user,
161 user_permissions=permissions, config=self.config, env=self.env)
172 user_permissions=permissions, config=self.config, env=self.env)
162 self.server_impl = server
173 self.server_impl = server
163 return server.run(tunnel_extras=extras)
174 return server.run(tunnel_extras=extras)
164
175
165 elif vcs == 'svn':
176 elif vcs == 'svn':
166 server = SubversionServer(
177 server = SubversionServer(
167 store=store, ini_path=self.ini_path,
178 store=store, ini_path=self.ini_path,
168 repo_name=None, user=user,
179 repo_name=None, user=user,
169 user_permissions=permissions, config=self.config, env=self.env)
180 user_permissions=permissions, config=self.config, env=self.env)
170 self.server_impl = server
181 self.server_impl = server
171 return server.run(tunnel_extras=extras)
182 return server.run(tunnel_extras=extras)
172
183
173 else:
184 else:
174 raise Exception('Unrecognised VCS: {}'.format(vcs))
185 raise Exception('Unrecognised VCS: {}'.format(vcs))
175
186
176 def wrap(self):
187 def wrap(self):
177 mode = self.mode
188 mode = self.mode
178 user = self.user
189 user = self.user
179 user_id = self.user_id
190 user_id = self.user_id
180 key_id = self.key_id
191 key_id = self.key_id
181 shell = self.shell
192 shell = self.shell
182
193
183 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
194 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
184
195
185 log.debug(
196 log.debug(
186 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
197 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
187 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
198 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
188 mode, user, user_id, shell, self.command,
199 mode, user, user_id, shell, self.command,
189 scm_detected, scm_mode, scm_repo)
200 scm_detected, scm_mode, scm_repo)
190
201
191 # update last access time for this key
202 # update last access time for this key
192 self.update_key_access_time(key_id)
203 self.update_key_access_time(key_id)
193
204
194 log.debug('SSH Connection info %s', self.get_connection_info())
205 log.debug('SSH Connection info %s', self.get_connection_info())
195
206
196 if shell and self.command is None:
207 if shell and self.command is None:
197 log.info('Dropping to shell, no command given and shell is allowed')
208 log.info('Dropping to shell, no command given and shell is allowed')
198 os.execl('/bin/bash', '-l')
209 os.execl('/bin/bash', '-l')
199 exit_code = 1
210 exit_code = 1
200
211
201 elif scm_detected:
212 elif scm_detected:
202 user = User.get(user_id)
213 user = User.get(user_id)
203 if not user:
214 if not user:
204 log.warning('User with id %s not found', user_id)
215 log.warning('User with id %s not found', user_id)
205 exit_code = -1
216 exit_code = -1
206 return exit_code
217 return exit_code
207
218
208 auth_user = user.AuthUser()
219 auth_user = user.AuthUser()
209 permissions = auth_user.permissions['repositories']
220 permissions = auth_user.permissions['repositories']
210 repo_branch_permissions = auth_user.get_branch_permissions(scm_repo)
221 repo_branch_permissions = auth_user.get_branch_permissions(scm_repo)
211 try:
222 try:
212 exit_code, is_updated = self.serve(
223 exit_code, is_updated = self.serve(
213 scm_detected, scm_repo, scm_mode, user, permissions,
224 scm_detected, scm_repo, scm_mode, user, permissions,
214 repo_branch_permissions)
225 repo_branch_permissions)
215 except Exception:
226 except Exception:
216 log.exception('Error occurred during execution of SshWrapper')
227 log.exception('Error occurred during execution of SshWrapper')
217 exit_code = -1
228 exit_code = -1
218
229
219 elif self.command is None and shell is False:
230 elif self.command is None and shell is False:
220 log.error('No Command given.')
231 log.error('No Command given.')
221 exit_code = -1
232 exit_code = -1
222
233
223 else:
234 else:
224 log.error('Unhandled Command: "%s" Aborting.', self.command)
235 log.error('Unhandled Command: "%s" Aborting.', self.command)
225 exit_code = -1
236 exit_code = -1
226
237
227 return exit_code
238 return exit_code
General Comments 0
You need to be logged in to leave comments. Login now