##// END OF EJS Templates
ssh-support: don't use API calls to fetch the data....
marcink -
r2186:32d56a2c default
parent child Browse files
Show More
@@ -628,14 +628,6 b' ssh.wrapper_cmd_allow_shell = false'
628 ## operations. Usefull for debugging, shouldn't be used in production.
628 ## operations. Usefull for debugging, shouldn't be used in production.
629 ssh.enable_debug_logging = true
629 ssh.enable_debug_logging = true
630
630
631 ## API KEY for user who has access to fetch other user permission information
632 ## most likely an super-admin account with some IP restrictions.
633 ssh.api_key =
634
635 ## API Host, the server address of RhodeCode instance that the api_key will
636 ## access
637 ssh.api_host = http://localhost
638
639 ## Paths to binary executable, by default they are the names, but we can
631 ## Paths to binary executable, by default they are the names, but we can
640 ## override them if we want to use a custom one
632 ## override them if we want to use a custom one
641 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
633 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
@@ -598,14 +598,6 b' ssh.wrapper_cmd_allow_shell = false'
598 ## operations. Usefull for debugging, shouldn't be used in production.
598 ## operations. Usefull for debugging, shouldn't be used in production.
599 ssh.enable_debug_logging = false
599 ssh.enable_debug_logging = false
600
600
601 ## API KEY for user who has access to fetch other user permission information
602 ## most likely an super-admin account with some IP restrictions.
603 ssh.api_key =
604
605 ## API Host, the server address of RhodeCode instance that the api_key will
606 ## access
607 ssh.api_host = http://localhost
608
609 ## Paths to binary executable, by default they are the names, but we can
601 ## Paths to binary executable, by default they are the names, but we can
610 ## override them if we want to use a custom one
602 ## override them if we want to use a custom one
611 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
603 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
@@ -45,10 +45,6 b' def _sanitize_settings_and_apply_default'
45 _string_setting(settings, config_keys.authorized_keys_line_ssh_opts, '',
45 _string_setting(settings, config_keys.authorized_keys_line_ssh_opts, '',
46 lower=False)
46 lower=False)
47
47
48 _string_setting(settings, config_keys.ssh_api_key, '',
49 lower=False)
50 _string_setting(settings, config_keys.ssh_api_host, '',
51 lower=False)
52 _string_setting(settings, config_keys.ssh_hg_bin,
48 _string_setting(settings, config_keys.ssh_hg_bin,
53 '~/.rccontrol/vcsserver-1/profile/bin/hg',
49 '~/.rccontrol/vcsserver-1/profile/bin/hg',
54 lower=False)
50 lower=False)
@@ -28,9 +28,6 b" wrapper_cmd = 'ssh.wrapper_cmd'"
28 wrapper_allow_shell = 'ssh.wrapper_cmd_allow_shell'
28 wrapper_allow_shell = 'ssh.wrapper_cmd_allow_shell'
29 enable_debug_logging = 'ssh.enable_debug_logging'
29 enable_debug_logging = 'ssh.enable_debug_logging'
30
30
31 ssh_api_key = 'ssh.api_key'
32 ssh_api_host = 'ssh.api_host'
33
34 ssh_hg_bin = 'ssh.executable.hg'
31 ssh_hg_bin = 'ssh.executable.hg'
35 ssh_git_bin = 'ssh.executable.git'
32 ssh_git_bin = 'ssh.executable.git'
36 ssh_svn_bin = 'ssh.executable.svn'
33 ssh_svn_bin = 'ssh.executable.svn'
This diff has been collapsed as it changes many lines, (568 lines changed) Show them Hide them
@@ -19,29 +19,23 b''
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
23 import sys
22 import sys
24 import json
25 import logging
23 import logging
26 import random
27 import signal
28 import tempfile
29 from subprocess import Popen, PIPE, check_output, CalledProcessError
30 import ConfigParser
31 import urllib2
32 import urlparse
33
24
34 import click
25 import click
35 import pyramid.paster
36
26
27 from pyramid.paster import bootstrap, setup_logging
28 from pyramid.request import Request
29
30 from .backends import SshWrapper
37
31
38 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
39
33
40
34
41 def setup_logging(ini_path, debug):
35 def setup_custom_logging(ini_path, debug):
42 if debug:
36 if debug:
43 # enabled rhodecode.ini controlled logging setup
37 # enabled rhodecode.ini controlled logging setup
44 pyramid.paster.setup_logging(ini_path)
38 setup_logging(ini_path)
45 else:
39 else:
46 # configure logging in a mode that doesn't print anything.
40 # configure logging in a mode that doesn't print anything.
47 # in case of regularly configured logging it gets printed out back
41 # in case of regularly configured logging it gets printed out back
@@ -52,532 +46,6 b' def setup_logging(ini_path, debug):'
52 logger.handlers = [null]
46 logger.handlers = [null]
53
47
54
48
55 class SubversionTunnelWrapper(object):
56 process = None
57
58 def __init__(self, timeout, repositories_root=None, svn_path=None):
59 self.timeout = timeout
60 self.stdin = sys.stdin
61 self.repositories_root = repositories_root
62 self.svn_path = svn_path or 'svnserve'
63 self.svn_conf_fd, self.svn_conf_path = tempfile.mkstemp()
64 self.hooks_env_fd, self.hooks_env_path = tempfile.mkstemp()
65 self.read_only = False
66 self.create_svn_config()
67
68 def create_svn_config(self):
69 content = (
70 '[general]\n'
71 'hooks-env = {}\n').format(self.hooks_env_path)
72 with os.fdopen(self.svn_conf_fd, 'w') as config_file:
73 config_file.write(content)
74
75 def create_hooks_env(self):
76 content = (
77 '[default]\n'
78 'LANG = en_US.UTF-8\n')
79 if self.read_only:
80 content += 'SSH_READ_ONLY = 1\n'
81 with os.fdopen(self.hooks_env_fd, 'w') as hooks_env_file:
82 hooks_env_file.write(content)
83
84 def remove_configs(self):
85 os.remove(self.svn_conf_path)
86 os.remove(self.hooks_env_path)
87
88 def start(self):
89 config = ['--config-file', self.svn_conf_path]
90 command = [self.svn_path, '-t'] + config
91 if self.repositories_root:
92 command.extend(['-r', self.repositories_root])
93 self.process = Popen(command, stdin=PIPE)
94
95 def sync(self):
96 while self.process.poll() is None:
97 next_byte = self.stdin.read(1)
98 if not next_byte:
99 break
100 self.process.stdin.write(next_byte)
101 self.remove_configs()
102
103 @property
104 def return_code(self):
105 return self.process.returncode
106
107 def get_first_client_response(self):
108 signal.signal(signal.SIGALRM, self.interrupt)
109 signal.alarm(self.timeout)
110 first_response = self._read_first_client_response()
111 signal.alarm(0)
112 return (
113 self._parse_first_client_response(first_response)
114 if first_response else None)
115
116 def patch_first_client_response(self, response, **kwargs):
117 self.create_hooks_env()
118 data = response.copy()
119 data.update(kwargs)
120 data['url'] = self._svn_string(data['url'])
121 data['ra_client'] = self._svn_string(data['ra_client'])
122 data['client'] = data['client'] or ''
123 buffer_ = (
124 "( {version} ( {capabilities} ) {url}{ra_client}"
125 "( {client}) ) ".format(**data))
126 self.process.stdin.write(buffer_)
127
128 def fail(self, message):
129 print(
130 "( failure ( ( 210005 {message} 0: 0 ) ) )".format(
131 message=self._svn_string(message)))
132 self.remove_configs()
133 self.process.kill()
134
135 def interrupt(self, signum, frame):
136 self.fail("Exited by timeout")
137
138 def _svn_string(self, str_):
139 if not str_:
140 return ''
141 return '{length}:{string} '.format(length=len(str_), string=str_)
142
143 def _read_first_client_response(self):
144 buffer_ = ""
145 brackets_stack = []
146 while True:
147 next_byte = self.stdin.read(1)
148 buffer_ += next_byte
149 if next_byte == "(":
150 brackets_stack.append(next_byte)
151 elif next_byte == ")":
152 brackets_stack.pop()
153 elif next_byte == " " and not brackets_stack:
154 break
155 return buffer_
156
157 def _parse_first_client_response(self, buffer_):
158 """
159 According to the Subversion RA protocol, the first request
160 should look like:
161
162 ( version:number ( cap:word ... ) url:string ? ra-client:string
163 ( ? client:string ) )
164
165 Please check https://svn.apache.org/repos/asf/subversion/trunk/
166 subversion/libsvn_ra_svn/protocol
167 """
168 version_re = r'(?P<version>\d+)'
169 capabilities_re = r'\(\s(?P<capabilities>[\w\d\-\ ]+)\s\)'
170 url_re = r'\d+\:(?P<url>[\W\w]+)'
171 ra_client_re = r'(\d+\:(?P<ra_client>[\W\w]+)\s)'
172 client_re = r'(\d+\:(?P<client>[\W\w]+)\s)*'
173 regex = re.compile(
174 r'^\(\s{version}\s{capabilities}\s{url}\s{ra_client}'
175 r'\(\s{client}\)\s\)\s*$'.format(
176 version=version_re, capabilities=capabilities_re,
177 url=url_re, ra_client=ra_client_re, client=client_re))
178 matcher = regex.match(buffer_)
179 return matcher.groupdict() if matcher else None
180
181
182 class RhodeCodeApiClient(object):
183 def __init__(self, api_key, api_host):
184 self.api_key = api_key
185 self.api_host = api_host
186
187 if not api_host:
188 raise ValueError('api_key:{} not defined'.format(api_key))
189 if not api_host:
190 raise ValueError('api_host:{} not defined '.format(api_host))
191
192 def request(self, method, args):
193 id_ = random.randrange(1, 9999)
194 args = {
195 'id': id_,
196 'api_key': self.api_key,
197 'method': method,
198 'args': args
199 }
200 host = '{host}/_admin/api'.format(host=self.api_host)
201
202 log.debug('Doing API call to %s method:%s', host, method)
203 req = urllib2.Request(
204 host,
205 data=json.dumps(args),
206 headers={'content-type': 'text/plain'})
207 ret = urllib2.urlopen(req)
208 raw_json = ret.read()
209 json_data = json.loads(raw_json)
210 id_ret = json_data['id']
211
212 if id_ret != id_:
213 raise Exception('something went wrong. '
214 'ID mismatch got %s, expected %s | %s'
215 % (id_ret, id_, raw_json))
216
217 result = json_data['result']
218 error = json_data['error']
219 return result, error
220
221 def get_user_permissions(self, user, user_id):
222 result, error = self.request('get_user', {'userid': int(user_id)})
223 if result is None and error:
224 raise Exception(
225 'User "%s" not found or another error happened: %s!' % (
226 user, error))
227 log.debug(
228 'Given User: `%s` Fetched User: `%s`', user, result.get('username'))
229 return result.get('permissions').get('repositories')
230
231 def invalidate_cache(self, repo_name):
232 log.debug('Invalidate cache for repo:%s', repo_name)
233 return self.request('invalidate_cache', {'repoid': repo_name})
234
235 def get_repo_store(self):
236 result, error = self.request('get_repo_store', {})
237 return result
238
239
240 class VcsServer(object):
241
242 def __init__(self, user, user_permissions, config):
243 self.user = user
244 self.user_permissions = user_permissions
245 self.config = config
246 self.repo_name = None
247 self.repo_mode = None
248 self.store = {}
249 self.ini_path = ''
250
251 def run(self):
252 raise NotImplementedError()
253
254 def get_root_store(self):
255 root_store = self.store['path']
256 if not root_store.endswith('/'):
257 # always append trailing slash
258 root_store = root_store + '/'
259 return root_store
260
261
262 class MercurialServer(VcsServer):
263 read_only = False
264
265 def __init__(self, store, ini_path, repo_name,
266 user, user_permissions, config):
267 super(MercurialServer, self).__init__(user, user_permissions, config)
268 self.store = store
269 self.repo_name = repo_name
270 self.ini_path = ini_path
271 self.hg_path = config.get('app:main', 'ssh.executable.hg')
272
273 def run(self):
274 if not self._check_permissions():
275 return 2, False
276
277 tip_before = self.tip()
278 exit_code = os.system(self.command)
279 tip_after = self.tip()
280 return exit_code, tip_before != tip_after
281
282 def tip(self):
283 root = self.get_root_store()
284 command = (
285 'cd {root}; {hg_path} -R {root}{repo_name} tip --template "{{node}}\n"'
286 ''.format(
287 root=root, hg_path=self.hg_path, repo_name=self.repo_name))
288 try:
289 tip = check_output(command, shell=True).strip()
290 except CalledProcessError:
291 tip = None
292 return tip
293
294 @property
295 def command(self):
296 root = self.get_root_store()
297 arguments = (
298 '--config hooks.pretxnchangegroup=\"false\"'
299 if self.read_only else '')
300
301 command = (
302 "cd {root}; {hg_path} -R {root}{repo_name} serve --stdio"
303 " {arguments}".format(
304 root=root, hg_path=self.hg_path, repo_name=self.repo_name,
305 arguments=arguments))
306 log.debug("Final CMD: %s", command)
307 return command
308
309 def _check_permissions(self):
310 permission = self.user_permissions.get(self.repo_name)
311 if permission is None or permission == 'repository.none':
312 log.error('repo not found or no permissions')
313 return False
314
315 elif permission in ['repository.admin', 'repository.write']:
316 log.info(
317 'Write Permissions for User "%s" granted to repo "%s"!' % (
318 self.user, self.repo_name))
319 else:
320 self.read_only = True
321 log.info(
322 'Only Read Only access for User "%s" granted to repo "%s"!',
323 self.user, self.repo_name)
324 return True
325
326
327 class GitServer(VcsServer):
328 def __init__(self, store, ini_path, repo_name, repo_mode,
329 user, user_permissions, config):
330 super(GitServer, self).__init__(user, user_permissions, config)
331 self.store = store
332 self.ini_path = ini_path
333 self.repo_name = repo_name
334 self.repo_mode = repo_mode
335 self.git_path = config.get('app:main', 'ssh.executable.git')
336
337 def run(self):
338 exit_code = self._check_permissions()
339 if exit_code:
340 return exit_code, False
341
342 self._update_environment()
343 exit_code = os.system(self.command)
344 return exit_code, self.repo_mode == "receive-pack"
345
346 @property
347 def command(self):
348 root = self.get_root_store()
349 command = "cd {root}; {git_path}-{mode} '{root}{repo_name}'".format(
350 root=root, git_path=self.git_path, mode=self.repo_mode,
351 repo_name=self.repo_name)
352 log.debug("Final CMD: %s", command)
353 return command
354
355 def _update_environment(self):
356 action = "push" if self.repo_mode == "receive-pack" else "pull",
357 scm_data = {
358 "ip": os.environ["SSH_CLIENT"].split()[0],
359 "username": self.user,
360 "action": action,
361 "repository": self.repo_name,
362 "scm": "git",
363 "config": self.ini_path,
364 "make_lock": None,
365 "locked_by": [None, None]
366 }
367 os.putenv("RC_SCM_DATA", json.dumps(scm_data))
368
369 def _check_permissions(self):
370 permission = self.user_permissions.get(self.repo_name)
371 log.debug(
372 'permission for %s on %s are: %s',
373 self.user, self.repo_name, permission)
374
375 if permission is None or permission == 'repository.none':
376 log.error('repo not found or no permissions')
377 return 2
378 elif permission in ['repository.admin', 'repository.write']:
379 log.info(
380 'Write Permissions for User "%s" granted to repo "%s"!',
381 self.user, self.repo_name)
382 elif (permission == 'repository.read' and
383 self.repo_mode == 'upload-pack'):
384 log.info(
385 'Only Read Only access for User "%s" granted to repo "%s"!',
386 self.user, self.repo_name)
387 elif (permission == 'repository.read'
388 and self.repo_mode == 'receive-pack'):
389 log.error(
390 'Only Read Only access for User "%s" granted to repo "%s"!'
391 ' Failing!', self.user, self.repo_name)
392 return -3
393 else:
394 log.error('Cannot properly fetch user permission. '
395 'Return value is: %s', permission)
396 return -2
397
398
399 class SubversionServer(VcsServer):
400
401 def __init__(self, store, ini_path,
402 user, user_permissions, config):
403 super(SubversionServer, self).__init__(user, user_permissions, config)
404 self.store = store
405 self.ini_path = ini_path
406 # this is set in .run() from input stream
407 self.repo_name = None
408 self.svn_path = config.get('app:main', 'ssh.executable.svn')
409
410 def run(self):
411 root = self.get_root_store()
412 log.debug("Using subversion binaries from '%s'", self.svn_path)
413
414 self.tunnel = SubversionTunnelWrapper(
415 timeout=self.timeout, repositories_root=root, svn_path=self.svn_path)
416 self.tunnel.start()
417 first_response = self.tunnel.get_first_client_response()
418 if not first_response:
419 self.tunnel.fail("Repository name cannot be extracted")
420 return 1, False
421
422 url_parts = urlparse.urlparse(first_response['url'])
423 self.repo_name = url_parts.path.strip('/')
424 if not self._check_permissions():
425 self.tunnel.fail("Not enough permissions")
426 return 1, False
427
428 self.tunnel.patch_first_client_response(first_response)
429 self.tunnel.sync()
430 return self.tunnel.return_code, False
431
432 @property
433 def timeout(self):
434 timeout = 30
435 return timeout
436
437 def _check_permissions(self):
438 permission = self.user_permissions.get(self.repo_name)
439
440 if permission in ['repository.admin', 'repository.write']:
441 self.tunnel.read_only = False
442 return True
443
444 elif permission == 'repository.read':
445 self.tunnel.read_only = True
446 return True
447
448 else:
449 self.tunnel.fail("Not enough permissions for repository {}".format(
450 self.repo_name))
451 return False
452
453
454 class SshWrapper(object):
455
456 def __init__(self, command, mode, user, user_id, shell, ini_path):
457 self.command = command
458 self.mode = mode
459 self.user = user
460 self.user_id = user_id
461 self.shell = shell
462 self.ini_path = ini_path
463
464 self.config = self.parse_config(ini_path)
465 api_key = self.config.get('app:main', 'ssh.api_key')
466 api_host = self.config.get('app:main', 'ssh.api_host')
467 self.api = RhodeCodeApiClient(api_key, api_host)
468
469 def parse_config(self, config):
470 parser = ConfigParser.ConfigParser()
471 parser.read(config)
472 return parser
473
474 def get_repo_details(self, mode):
475 type_ = mode if mode in ['svn', 'hg', 'git'] else None
476 mode = mode
477 name = None
478
479 hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$'
480 hg_match = re.match(hg_pattern, self.command)
481 if hg_match is not None:
482 type_ = 'hg'
483 name = hg_match.group(1).strip('/')
484 return type_, name, mode
485
486 git_pattern = (
487 r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
488 git_match = re.match(git_pattern, self.command)
489 if git_match is not None:
490 type_ = 'git'
491 name = git_match.group(2).strip('/')
492 mode = git_match.group(1)
493 return type_, name, mode
494
495 svn_pattern = r'^svnserve -t'
496 svn_match = re.match(svn_pattern, self.command)
497 if svn_match is not None:
498 type_ = 'svn'
499 # Repo name should be extracted from the input stream
500 return type_, name, mode
501
502 return type_, name, mode
503
504 def serve(self, vcs, repo, mode, user, permissions):
505 store = self.api.get_repo_store()
506
507 log.debug(
508 'VCS detected:`%s` mode: `%s` repo: %s', vcs, mode, repo)
509
510 if vcs == 'hg':
511 server = MercurialServer(
512 store=store, ini_path=self.ini_path,
513 repo_name=repo, user=user,
514 user_permissions=permissions, config=self.config)
515 return server.run()
516
517 elif vcs == 'git':
518 server = GitServer(
519 store=store, ini_path=self.ini_path,
520 repo_name=repo, repo_mode=mode, user=user,
521 user_permissions=permissions, config=self.config)
522 return server.run()
523
524 elif vcs == 'svn':
525 server = SubversionServer(
526 store=store, ini_path=self.ini_path,
527 user=user,
528 user_permissions=permissions, config=self.config)
529 return server.run()
530
531 else:
532 raise Exception('Unrecognised VCS: {}'.format(vcs))
533
534 def wrap(self):
535 mode = self.mode
536 user = self.user
537 user_id = self.user_id
538 shell = self.shell
539
540 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
541 log.debug(
542 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
543 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
544 mode, user, user_id, shell, self.command,
545 scm_detected, scm_mode, scm_repo)
546
547 try:
548 permissions = self.api.get_user_permissions(user, user_id)
549 except Exception as e:
550 log.exception('Failed to fetch user permissions')
551 return 1
552
553 if shell and self.command is None:
554 log.info(
555 'Dropping to shell, no command given and shell is allowed')
556 os.execl('/bin/bash', '-l')
557 exit_code = 1
558
559 elif scm_detected:
560 try:
561 exit_code, is_updated = self.serve(
562 scm_detected, scm_repo, scm_mode, user, permissions)
563 if exit_code == 0 and is_updated:
564 self.api.invalidate_cache(scm_repo)
565 except Exception:
566 log.exception('Error occurred during execution of SshWrapper')
567 exit_code = -1
568
569 elif self.command is None and shell is False:
570 log.error('No Command given.')
571 exit_code = -1
572
573 else:
574 log.error(
575 'Unhandled Command: "%s" Aborting.', self.command)
576 exit_code = -1
577
578 return exit_code
579
580
581 @click.command()
49 @click.command()
582 @click.argument('ini_path', type=click.Path(exists=True))
50 @click.argument('ini_path', type=click.Path(exists=True))
583 @click.option(
51 @click.option(
@@ -586,10 +54,11 b' class SshWrapper(object):'
586 help='mode of operation')
54 help='mode of operation')
587 @click.option('--user', help='Username for which the command will be executed')
55 @click.option('--user', help='Username for which the command will be executed')
588 @click.option('--user-id', help='User ID for which the command will be executed')
56 @click.option('--user-id', help='User ID for which the command will be executed')
57 @click.option('--key-id', help='ID of the key from the database')
589 @click.option('--shell', '-s', is_flag=True, help='Allow Shell')
58 @click.option('--shell', '-s', is_flag=True, help='Allow Shell')
590 @click.option('--debug', is_flag=True, help='Enabled detailed output logging')
59 @click.option('--debug', is_flag=True, help='Enabled detailed output logging')
591 def main(ini_path, mode, user, user_id, shell, debug):
60 def main(ini_path, mode, user, user_id, key_id, shell, debug):
592 setup_logging(ini_path, debug)
61 setup_custom_logging(ini_path, debug)
593
62
594 command = os.environ.get('SSH_ORIGINAL_COMMAND', '')
63 command = os.environ.get('SSH_ORIGINAL_COMMAND', '')
595 if not command and mode not in ['test']:
64 if not command and mode not in ['test']:
@@ -597,11 +66,16 b' def main(ini_path, mode, user, user_id, '
597 'Unable to fetch SSH_ORIGINAL_COMMAND from environment.'
66 'Unable to fetch SSH_ORIGINAL_COMMAND from environment.'
598 'Please make sure this is set and available during execution '
67 'Please make sure this is set and available during execution '
599 'of this script.')
68 'of this script.')
69 connection_info = os.environ.get('SSH_CONNECTION', '')
70 request = Request.blank('/', base_url='http://rhodecode-ssh-wrapper/')
71 with bootstrap(ini_path, request=request) as env:
72 try:
73 ssh_wrapper = SshWrapper(
74 command, connection_info, mode,
75 user, user_id, key_id, shell, ini_path)
76 except Exception:
77 log.exception('Failed to execute SshWrapper')
78 sys.exit(-5)
600
79
601 try:
80 return_code = ssh_wrapper.wrap()
602 ssh_wrapper = SshWrapper(command, mode, user, user_id, shell, ini_path)
81 sys.exit(return_code)
603 except Exception:
604 log.exception('Failed to execute SshWrapper')
605 sys.exit(-5)
606
607 sys.exit(ssh_wrapper.wrap()) No newline at end of file
@@ -21,9 +21,9 b''
21 import json
21 import json
22
22
23 import pytest
23 import pytest
24 from mock import Mock, patch, call
24 from mock import Mock, patch
25
25
26 from rhodecode.apps.ssh_support.lib.ssh_wrapper import GitServer
26 from rhodecode.apps.ssh_support.lib.backends.git import GitServer
27
27
28
28
29 @pytest.fixture
29 @pytest.fixture
@@ -19,9 +19,9 b''
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 pytest
21 import pytest
22 from mock import Mock, patch, call
22 from mock import Mock, patch
23
23
24 from rhodecode.apps.ssh_support.lib.ssh_wrapper import MercurialServer
24 from rhodecode.apps.ssh_support.lib.backends.hg import MercurialServer
25
25
26
26
27 @pytest.fixture
27 @pytest.fixture
@@ -19,9 +19,9 b''
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 pytest
21 import pytest
22 from mock import Mock, patch, call
22 from mock import Mock, patch
23
23
24 from rhodecode.apps.ssh_support.lib.ssh_wrapper import SubversionServer
24 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionServer
25
25
26
26
27 @pytest.fixture
27 @pytest.fixture
@@ -18,8 +18,6 b''
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
22
23 import os
21 import os
24 import pytest
22 import pytest
25 import mock
23 import mock
@@ -33,9 +33,6 b' def dummy_conf(tmpdir):'
33 conf.set('app:main', 'ssh.executable.git', '/usr/bin/git')
33 conf.set('app:main', 'ssh.executable.git', '/usr/bin/git')
34 conf.set('app:main', 'ssh.executable.svn', '/usr/bin/svnserve')
34 conf.set('app:main', 'ssh.executable.svn', '/usr/bin/svnserve')
35
35
36 conf.set('app:main', 'ssh.api_key', 'xxx')
37 conf.set('app:main', 'ssh.api_host', 'http://localhost')
38
39 f_path = os.path.join(str(tmpdir), 'ssh_wrapper_test.ini')
36 f_path = os.path.join(str(tmpdir), 'ssh_wrapper_test.ini')
40 with open(f_path, 'wb') as f:
37 with open(f_path, 'wb') as f:
41 conf.write(f)
38 conf.write(f)
@@ -26,7 +26,7 b' from time import sleep'
26 import pytest
26 import pytest
27 from mock import patch, Mock, MagicMock, call
27 from mock import patch, Mock, MagicMock, call
28
28
29 from rhodecode.apps.ssh_support.lib.ssh_wrapper import SubversionTunnelWrapper
29 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
30 from rhodecode.tests import no_newline_id_generator
30 from rhodecode.tests import no_newline_id_generator
31
31
32
32
@@ -66,7 +66,7 b' def _generate_ssh_authorized_keys_file('
66 raise OSError('Access to file {} is without read access'.format(
66 raise OSError('Access to file {} is without read access'.format(
67 authorized_keys_file_path))
67 authorized_keys_file_path))
68
68
69 line_tmpl = '{ssh_opts},command="{wrapper_command} {ini_path} --user-id={user_id} --user={user}" {key}\n'
69 line_tmpl = '{ssh_opts},command="{wrapper_command} {ini_path} --user-id={user_id} --user={user} --key-id={user_key_id}" {key}\n'
70
70
71 fd, tmp_authorized_keys = tempfile.mkstemp(
71 fd, tmp_authorized_keys = tempfile.mkstemp(
72 '.authorized_keys_write',
72 '.authorized_keys_write',
@@ -87,7 +87,9 b' def _generate_ssh_authorized_keys_file('
87 wrapper_command=ssh_wrapper_cmd,
87 wrapper_command=ssh_wrapper_cmd,
88 ini_path=ini_path,
88 ini_path=ini_path,
89 user_id=user_id,
89 user_id=user_id,
90 user=username, key=user_key.ssh_key_data))
90 user=username,
91 user_key_id=user_key.ssh_key_id,
92 key=user_key.ssh_key_data))
91 log.debug('addkey: Key added for user: `%s`', username)
93 log.debug('addkey: Key added for user: `%s`', username)
92 keys_file.close()
94 keys_file.close()
93
95
@@ -664,14 +664,6 b' ssh.wrapper_cmd_allow_shell = false'
664 ## debugging, shouldn't be used in production.
664 ## debugging, shouldn't be used in production.
665 ssh.enable_debug_logging = false
665 ssh.enable_debug_logging = false
666
666
667 ## API KEY for user who has access to fetch other user permission information
668 ## most likely an super-admin account with some IP restrictions.
669 ssh.api_key =
670
671 ## API Host, the server address of RhodeCode instance that the api_key will
672 ## access
673 ssh.api_host = http://localhost
674
675 ## Paths to binary executrables, by default they are the names, but we can
667 ## Paths to binary executrables, by default they are the names, but we can
676 ## override them if we want to use a custom one
668 ## override them if we want to use a custom one
677 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
669 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
General Comments 0
You need to be logged in to leave comments. Login now