##// END OF EJS Templates
ssh-wrapper: perf optimizations...
super-admin -
r4947:4d7cf945 default
parent child Browse files
Show More
@@ -1,238 +1,264 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 import configparser
26 from sqlalchemy import Table
26 27
27 from rhodecode.model.db import Session, User, UserSshKeys
28 from rhodecode.lib.utils2 import AttributeDict
28 29 from rhodecode.model.scm import ScmModel
29 30
30 31 from .hg import MercurialServer
31 32 from .git import GitServer
32 33 from .svn import SubversionServer
33 34 log = logging.getLogger(__name__)
34 35
35 36
36 37 class SshWrapper(object):
37 38 hg_cmd_pat = re.compile(r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$')
38 39 git_cmd_pat = re.compile(r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
39 40 svn_cmd_pat = re.compile(r'^svnserve -t')
40 41
41 42 def __init__(self, command, connection_info, mode,
42 user, user_id, key_id, shell, ini_path, env):
43 user, user_id, key_id: int, shell, ini_path: str, env):
43 44 self.command = command
44 45 self.connection_info = connection_info
45 46 self.mode = mode
46 self.user = user
47 self.username = user
47 48 self.user_id = user_id
48 49 self.key_id = key_id
49 50 self.shell = shell
50 51 self.ini_path = ini_path
51 52 self.env = env
52 53
53 54 self.config = self.parse_config(ini_path)
54 55 self.server_impl = None
55 56
56 57 def parse_config(self, config_path):
57 58 parser = configparser.ConfigParser()
58 59 parser.read(config_path)
59 60 return parser
60 61
61 62 def update_key_access_time(self, key_id):
62 key = UserSshKeys().query().filter(
63 UserSshKeys.ssh_key_id == key_id).scalar()
64 if key:
65 key.accessed_on = datetime.datetime.utcnow()
66 Session().add(key)
67 Session().commit()
63 from rhodecode.model.meta import raw_query_executor, Base
64
65 table = Table('user_ssh_keys', Base.metadata, autoload=False)
66 stmt = (
67 table.update()
68 .where(table.c.ssh_key_id == key_id)
69 .values(accessed_on=datetime.datetime.utcnow())
70 .returning(table.c.accessed_on, table.c.ssh_key_fingerprint)
71 )
72
73 scalar_res = None
74 with raw_query_executor() as session:
75 result = session.execute(stmt)
76 if result.rowcount:
77 scalar_res = result.first()
78
79 if scalar_res:
80 atime, ssh_key_fingerprint = scalar_res
68 81 log.debug('Update key id:`%s` fingerprint:`%s` access time',
69 key_id, key.ssh_key_fingerprint)
82 key_id, ssh_key_fingerprint)
83
84 def get_user(self, user_id):
85 user = AttributeDict()
86 # lazy load db imports
87 from rhodecode.model.db import User
88 dbuser = User.get(user_id)
89 if not dbuser:
90 return None
91 user.user_id = dbuser.user_id
92 user.username = dbuser.username
93 user.auth_user = dbuser.AuthUser()
94 return user
70 95
71 96 def get_connection_info(self):
72 97 """
73 98 connection_info
74 99
75 100 Identifies the client and server ends of the connection.
76 101 The variable contains four space-separated values: client IP address,
77 102 client port number, server IP address, and server port number.
78 103 """
79 104 conn = dict(
80 105 client_ip=None,
81 106 client_port=None,
82 107 server_ip=None,
83 108 server_port=None,
84 109 )
85 110
86 111 info = self.connection_info.split(' ')
87 112 if len(info) == 4:
88 113 conn['client_ip'] = info[0]
89 114 conn['client_port'] = info[1]
90 115 conn['server_ip'] = info[2]
91 116 conn['server_port'] = info[3]
92 117
93 118 return conn
94 119
95 120 def maybe_translate_repo_uid(self, repo_name):
96 121 _org_name = repo_name
97 122 if _org_name.startswith('_'):
98 123 # remove format of _ID/subrepo
99 124 _org_name = _org_name.split('/', 1)[0]
100 125
101 126 if repo_name.startswith('_'):
102 127 from rhodecode.model.repo import RepoModel
103 128 org_repo_name = repo_name
104 129 log.debug('translating UID repo %s', org_repo_name)
105 130 by_id_match = RepoModel().get_repo_by_id(repo_name)
106 131 if by_id_match:
107 132 repo_name = by_id_match.repo_name
108 133 log.debug('translation of UID repo %s got `%s`', org_repo_name, repo_name)
109 134
110 135 return repo_name, _org_name
111 136
112 137 def get_repo_details(self, mode):
113 138 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
114 139 repo_name = None
115 140
116 141 hg_match = self.hg_cmd_pat.match(self.command)
117 142 if hg_match is not None:
118 143 vcs_type = 'hg'
119 144 repo_id = hg_match.group(1).strip('/')
120 145 repo_name, org_name = self.maybe_translate_repo_uid(repo_id)
121 146 return vcs_type, repo_name, mode
122 147
123 148 git_match = self.git_cmd_pat.match(self.command)
124 149 if git_match is not None:
125 150 mode = git_match.group(1)
126 151 vcs_type = 'git'
127 152 repo_id = git_match.group(2).strip('/')
128 153 repo_name, org_name = self.maybe_translate_repo_uid(repo_id)
129 154 return vcs_type, repo_name, mode
130 155
131 156 svn_match = self.svn_cmd_pat.match(self.command)
132 157 if svn_match is not None:
133 158 vcs_type = 'svn'
134 159 # Repo name should be extracted from the input stream, we're unable to
135 160 # extract it at this point in execution
136 161 return vcs_type, repo_name, mode
137 162
138 163 return vcs_type, repo_name, mode
139 164
140 165 def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
141 166 store = ScmModel().repos_path
142 167
143 168 check_branch_perms = False
144 169 detect_force_push = False
145 170
146 171 if branch_permissions:
147 172 check_branch_perms = True
148 173 detect_force_push = True
149 174
150 175 log.debug(
151 176 'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
152 177 vcs, mode, repo, check_branch_perms)
153 178
154 179 # detect if we have to check branch permissions
155 180 extras = {
156 181 'detect_force_push': detect_force_push,
157 182 'check_branch_perms': check_branch_perms,
158 183 }
159 184
160 185 if vcs == 'hg':
161 186 server = MercurialServer(
162 187 store=store, ini_path=self.ini_path,
163 188 repo_name=repo, user=user,
164 189 user_permissions=permissions, config=self.config, env=self.env)
165 190 self.server_impl = server
166 191 return server.run(tunnel_extras=extras)
167 192
168 193 elif vcs == 'git':
169 194 server = GitServer(
170 195 store=store, ini_path=self.ini_path,
171 196 repo_name=repo, repo_mode=mode, user=user,
172 197 user_permissions=permissions, config=self.config, env=self.env)
173 198 self.server_impl = server
174 199 return server.run(tunnel_extras=extras)
175 200
176 201 elif vcs == 'svn':
177 202 server = SubversionServer(
178 203 store=store, ini_path=self.ini_path,
179 204 repo_name=None, user=user,
180 205 user_permissions=permissions, config=self.config, env=self.env)
181 206 self.server_impl = server
182 207 return server.run(tunnel_extras=extras)
183 208
184 209 else:
185 210 raise Exception('Unrecognised VCS: {}'.format(vcs))
186 211
187 212 def wrap(self):
188 213 mode = self.mode
189 user = self.user
214 username = self.username
190 215 user_id = self.user_id
191 216 key_id = self.key_id
192 217 shell = self.shell
193 218
194 219 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
195 220
196 221 log.debug(
197 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
222 'Mode: `%s` User: `name:%s : id:%s` Shell: `%s` SSH Command: `\"%s\"` '
198 223 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
199 mode, user, user_id, shell, self.command,
224 mode, username, user_id, shell, self.command,
200 225 scm_detected, scm_mode, scm_repo)
201 226
227 log.debug('SSH Connection info %s', self.get_connection_info())
228
202 229 # update last access time for this key
203 self.update_key_access_time(key_id)
204
205 log.debug('SSH Connection info %s', self.get_connection_info())
230 if key_id:
231 self.update_key_access_time(key_id)
206 232
207 233 if shell and self.command is None:
208 234 log.info('Dropping to shell, no command given and shell is allowed')
209 235 os.execl('/bin/bash', '-l')
210 236 exit_code = 1
211 237
212 238 elif scm_detected:
213 user = User.get(user_id)
239 user = self.get_user(user_id)
214 240 if not user:
215 241 log.warning('User with id %s not found', user_id)
216 242 exit_code = -1
217 243 return exit_code
218 244
219 auth_user = user.AuthUser()
245 auth_user = user.auth_user
220 246 permissions = auth_user.permissions['repositories']
221 247 repo_branch_permissions = auth_user.get_branch_permissions(scm_repo)
222 248 try:
223 249 exit_code, is_updated = self.serve(
224 250 scm_detected, scm_repo, scm_mode, user, permissions,
225 251 repo_branch_permissions)
226 252 except Exception:
227 253 log.exception('Error occurred during execution of SshWrapper')
228 254 exit_code = -1
229 255
230 256 elif self.command is None and shell is False:
231 257 log.error('No Command given.')
232 258 exit_code = -1
233 259
234 260 else:
235 261 log.error('Unhandled Command: "%s" Aborting.', self.command)
236 262 exit_code = -1
237 263
238 264 return exit_code
@@ -1,45 +1,51 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-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 """
22 22 SQLAlchemy Metadata and Session object
23 23 """
24 24
25 25 from sqlalchemy.orm import declarative_base
26 26 from sqlalchemy.orm import scoped_session, sessionmaker
27
27 from sqlalchemy.orm import Session as SASession
28 28 from rhodecode.lib import caching_query
29 29
30 __all__ = ['Base', 'Session']
30 __all__ = ['Base', 'Session', 'raw_query_executor']
31 31
32 32 # scoped_session. Apply our custom CachingQuery class to it,
33 33 # using a callable that will associate the dictionary
34 34 # of regions with the Query.
35 35 # to use cache use this in query
36 36 # .options(FromCache("sqlalchemy_cache_type", "cachekey"))
37 37 Session = scoped_session(
38 38 sessionmaker(
39 39 query_cls=caching_query.query_callable(),
40 40 expire_on_commit=True,
41 41 )
42 42 )
43 43
44 44 # The declarative Base
45 45 Base = declarative_base()
46
47
48 def raw_query_executor():
49 engine = Base.metadata.bind
50 session = SASession(engine)
51 return session
General Comments 0
You need to be logged in to leave comments. Login now