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