##// END OF EJS Templates
ssh: allow clone by ID via SSH operations.
milka -
r4644:17476173 default
parent child Browse files
Show More
@@ -1,220 +1,228 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
37
38 def __init__(self, command, connection_info, mode,
38 def __init__(self, command, connection_info, mode,
39 user, user_id, key_id, shell, ini_path, env):
39 user, user_id, key_id, shell, ini_path, env):
40 self.command = command
40 self.command = command
41 self.connection_info = connection_info
41 self.connection_info = connection_info
42 self.mode = mode
42 self.mode = mode
43 self.user = user
43 self.user = user
44 self.user_id = user_id
44 self.user_id = user_id
45 self.key_id = key_id
45 self.key_id = key_id
46 self.shell = shell
46 self.shell = shell
47 self.ini_path = ini_path
47 self.ini_path = ini_path
48 self.env = env
48 self.env = env
49
49
50 self.config = self.parse_config(ini_path)
50 self.config = self.parse_config(ini_path)
51 self.server_impl = None
51 self.server_impl = None
52
52
53 def parse_config(self, config_path):
53 def parse_config(self, config_path):
54 parser = configparser.ConfigParser()
54 parser = configparser.ConfigParser()
55 parser.read(config_path)
55 parser.read(config_path)
56 return parser
56 return parser
57
57
58 def update_key_access_time(self, key_id):
58 def update_key_access_time(self, key_id):
59 key = UserSshKeys().query().filter(
59 key = UserSshKeys().query().filter(
60 UserSshKeys.ssh_key_id == key_id).scalar()
60 UserSshKeys.ssh_key_id == key_id).scalar()
61 if key:
61 if key:
62 key.accessed_on = datetime.datetime.utcnow()
62 key.accessed_on = datetime.datetime.utcnow()
63 Session().add(key)
63 Session().add(key)
64 Session().commit()
64 Session().commit()
65 log.debug('Update key id:`%s` fingerprint:`%s` access time',
65 log.debug('Update key id:`%s` fingerprint:`%s` access time',
66 key_id, key.ssh_key_fingerprint)
66 key_id, key.ssh_key_fingerprint)
67
67
68 def get_connection_info(self):
68 def get_connection_info(self):
69 """
69 """
70 connection_info
70 connection_info
71
71
72 Identifies the client and server ends of the connection.
72 Identifies the client and server ends of the connection.
73 The variable contains four space-separated values: client IP address,
73 The variable contains four space-separated values: client IP address,
74 client port number, server IP address, and server port number.
74 client port number, server IP address, and server port number.
75 """
75 """
76 conn = dict(
76 conn = dict(
77 client_ip=None,
77 client_ip=None,
78 client_port=None,
78 client_port=None,
79 server_ip=None,
79 server_ip=None,
80 server_port=None,
80 server_port=None,
81 )
81 )
82
82
83 info = self.connection_info.split(' ')
83 info = self.connection_info.split(' ')
84 if len(info) == 4:
84 if len(info) == 4:
85 conn['client_ip'] = info[0]
85 conn['client_ip'] = info[0]
86 conn['client_port'] = info[1]
86 conn['client_port'] = info[1]
87 conn['server_ip'] = info[2]
87 conn['server_ip'] = info[2]
88 conn['server_port'] = info[3]
88 conn['server_port'] = info[3]
89
89
90 return conn
90 return conn
91
91
92 def maybe_translate_repo_uid(self, repo_name):
93 if repo_name.startswith('_'):
94 from rhodecode.model.repo import RepoModel
95 by_id_match = RepoModel().get_repo_by_id(repo_name)
96 if by_id_match:
97 repo_name = by_id_match.repo_name
98 return repo_name
99
92 def get_repo_details(self, mode):
100 def get_repo_details(self, mode):
93 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
101 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
94 repo_name = None
102 repo_name = None
95
103
96 hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$'
104 hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$'
97 hg_match = re.match(hg_pattern, self.command)
105 hg_match = re.match(hg_pattern, self.command)
98 if hg_match is not None:
106 if hg_match is not None:
99 vcs_type = 'hg'
107 vcs_type = 'hg'
100 repo_name = hg_match.group(1).strip('/')
108 repo_name = self.maybe_translate_repo_uid(hg_match.group(1).strip('/'))
101 return vcs_type, repo_name, mode
109 return vcs_type, repo_name, mode
102
110
103 git_pattern = r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$'
111 git_pattern = r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$'
104 git_match = re.match(git_pattern, self.command)
112 git_match = re.match(git_pattern, self.command)
105 if git_match is not None:
113 if git_match is not None:
106 vcs_type = 'git'
114 vcs_type = 'git'
107 repo_name = git_match.group(2).strip('/')
115 repo_name = self.maybe_translate_repo_uid(git_match.group(2).strip('/'))
108 mode = git_match.group(1)
116 mode = git_match.group(1)
109 return vcs_type, repo_name, mode
117 return vcs_type, repo_name, mode
110
118
111 svn_pattern = r'^svnserve -t'
119 svn_pattern = r'^svnserve -t'
112 svn_match = re.match(svn_pattern, self.command)
120 svn_match = re.match(svn_pattern, self.command)
113
121
114 if svn_match is not None:
122 if svn_match is not None:
115 vcs_type = 'svn'
123 vcs_type = 'svn'
116 # Repo name should be extracted from the input stream, we're unable to
124 # Repo name should be extracted from the input stream, we're unable to
117 # extract it at this point in execution
125 # extract it at this point in execution
118 return vcs_type, repo_name, mode
126 return vcs_type, repo_name, mode
119
127
120 return vcs_type, repo_name, mode
128 return vcs_type, repo_name, mode
121
129
122 def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
130 def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
123 store = ScmModel().repos_path
131 store = ScmModel().repos_path
124
132
125 check_branch_perms = False
133 check_branch_perms = False
126 detect_force_push = False
134 detect_force_push = False
127
135
128 if branch_permissions:
136 if branch_permissions:
129 check_branch_perms = True
137 check_branch_perms = True
130 detect_force_push = True
138 detect_force_push = True
131
139
132 log.debug(
140 log.debug(
133 'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
141 'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
134 vcs, mode, repo, check_branch_perms)
142 vcs, mode, repo, check_branch_perms)
135
143
136 # detect if we have to check branch permissions
144 # detect if we have to check branch permissions
137 extras = {
145 extras = {
138 'detect_force_push': detect_force_push,
146 'detect_force_push': detect_force_push,
139 'check_branch_perms': check_branch_perms,
147 'check_branch_perms': check_branch_perms,
140 }
148 }
141
149
142 if vcs == 'hg':
150 if vcs == 'hg':
143 server = MercurialServer(
151 server = MercurialServer(
144 store=store, ini_path=self.ini_path,
152 store=store, ini_path=self.ini_path,
145 repo_name=repo, user=user,
153 repo_name=repo, user=user,
146 user_permissions=permissions, config=self.config, env=self.env)
154 user_permissions=permissions, config=self.config, env=self.env)
147 self.server_impl = server
155 self.server_impl = server
148 return server.run(tunnel_extras=extras)
156 return server.run(tunnel_extras=extras)
149
157
150 elif vcs == 'git':
158 elif vcs == 'git':
151 server = GitServer(
159 server = GitServer(
152 store=store, ini_path=self.ini_path,
160 store=store, ini_path=self.ini_path,
153 repo_name=repo, repo_mode=mode, user=user,
161 repo_name=repo, repo_mode=mode, user=user,
154 user_permissions=permissions, config=self.config, env=self.env)
162 user_permissions=permissions, config=self.config, env=self.env)
155 self.server_impl = server
163 self.server_impl = server
156 return server.run(tunnel_extras=extras)
164 return server.run(tunnel_extras=extras)
157
165
158 elif vcs == 'svn':
166 elif vcs == 'svn':
159 server = SubversionServer(
167 server = SubversionServer(
160 store=store, ini_path=self.ini_path,
168 store=store, ini_path=self.ini_path,
161 repo_name=None, user=user,
169 repo_name=None, user=user,
162 user_permissions=permissions, config=self.config, env=self.env)
170 user_permissions=permissions, config=self.config, env=self.env)
163 self.server_impl = server
171 self.server_impl = server
164 return server.run(tunnel_extras=extras)
172 return server.run(tunnel_extras=extras)
165
173
166 else:
174 else:
167 raise Exception('Unrecognised VCS: {}'.format(vcs))
175 raise Exception('Unrecognised VCS: {}'.format(vcs))
168
176
169 def wrap(self):
177 def wrap(self):
170 mode = self.mode
178 mode = self.mode
171 user = self.user
179 user = self.user
172 user_id = self.user_id
180 user_id = self.user_id
173 key_id = self.key_id
181 key_id = self.key_id
174 shell = self.shell
182 shell = self.shell
175
183
176 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
184 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
177
185
178 log.debug(
186 log.debug(
179 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
187 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
180 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
188 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
181 mode, user, user_id, shell, self.command,
189 mode, user, user_id, shell, self.command,
182 scm_detected, scm_mode, scm_repo)
190 scm_detected, scm_mode, scm_repo)
183
191
184 # update last access time for this key
192 # update last access time for this key
185 self.update_key_access_time(key_id)
193 self.update_key_access_time(key_id)
186
194
187 log.debug('SSH Connection info %s', self.get_connection_info())
195 log.debug('SSH Connection info %s', self.get_connection_info())
188
196
189 if shell and self.command is None:
197 if shell and self.command is None:
190 log.info('Dropping to shell, no command given and shell is allowed')
198 log.info('Dropping to shell, no command given and shell is allowed')
191 os.execl('/bin/bash', '-l')
199 os.execl('/bin/bash', '-l')
192 exit_code = 1
200 exit_code = 1
193
201
194 elif scm_detected:
202 elif scm_detected:
195 user = User.get(user_id)
203 user = User.get(user_id)
196 if not user:
204 if not user:
197 log.warning('User with id %s not found', user_id)
205 log.warning('User with id %s not found', user_id)
198 exit_code = -1
206 exit_code = -1
199 return exit_code
207 return exit_code
200
208
201 auth_user = user.AuthUser()
209 auth_user = user.AuthUser()
202 permissions = auth_user.permissions['repositories']
210 permissions = auth_user.permissions['repositories']
203 repo_branch_permissions = auth_user.get_branch_permissions(scm_repo)
211 repo_branch_permissions = auth_user.get_branch_permissions(scm_repo)
204 try:
212 try:
205 exit_code, is_updated = self.serve(
213 exit_code, is_updated = self.serve(
206 scm_detected, scm_repo, scm_mode, user, permissions,
214 scm_detected, scm_repo, scm_mode, user, permissions,
207 repo_branch_permissions)
215 repo_branch_permissions)
208 except Exception:
216 except Exception:
209 log.exception('Error occurred during execution of SshWrapper')
217 log.exception('Error occurred during execution of SshWrapper')
210 exit_code = -1
218 exit_code = -1
211
219
212 elif self.command is None and shell is False:
220 elif self.command is None and shell is False:
213 log.error('No Command given.')
221 log.error('No Command given.')
214 exit_code = -1
222 exit_code = -1
215
223
216 else:
224 else:
217 log.error('Unhandled Command: "%s" Aborting.', self.command)
225 log.error('Unhandled Command: "%s" Aborting.', self.command)
218 exit_code = -1
226 exit_code = -1
219
227
220 return exit_code
228 return exit_code
General Comments 0
You need to be logged in to leave comments. Login now