Show More
@@ -0,0 +1,54 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2016-2017 RhodeCode GmbH | |
|
4 | # | |
|
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 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
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/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | import logging | |
|
22 | ||
|
23 | from . import config_keys | |
|
24 | from .events import SshKeyFileChangeEvent | |
|
25 | from .subscribers import generate_ssh_authorized_keys_file_subscriber | |
|
26 | ||
|
27 | from rhodecode.config.middleware import _bool_setting, _string_setting | |
|
28 | ||
|
29 | log = logging.getLogger(__name__) | |
|
30 | ||
|
31 | ||
|
32 | def _sanitize_settings_and_apply_defaults(settings): | |
|
33 | """ | |
|
34 | Set defaults, convert to python types and validate settings. | |
|
35 | """ | |
|
36 | _bool_setting(settings, config_keys.generate_authorized_keyfile, 'false') | |
|
37 | _bool_setting(settings, config_keys.wrapper_allow_shell, 'false') | |
|
38 | ||
|
39 | _string_setting(settings, config_keys.authorized_keys_file_path, '', | |
|
40 | lower=False) | |
|
41 | _string_setting(settings, config_keys.wrapper_cmd, '', | |
|
42 | lower=False) | |
|
43 | _string_setting(settings, config_keys.authorized_keys_line_ssh_opts, '', | |
|
44 | lower=False) | |
|
45 | ||
|
46 | ||
|
47 | def includeme(config): | |
|
48 | settings = config.registry.settings | |
|
49 | _sanitize_settings_and_apply_defaults(settings) | |
|
50 | ||
|
51 | # if we have enable generation of file, subscribe to event | |
|
52 | if settings[config_keys.generate_authorized_keyfile]: | |
|
53 | config.add_subscriber( | |
|
54 | generate_ssh_authorized_keys_file_subscriber, SshKeyFileChangeEvent) |
@@ -0,0 +1,28 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2016-2017 RhodeCode GmbH | |
|
4 | # | |
|
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 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
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/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | ||
|
22 | # Definition of setting keys used to configure this module. Defined here to | |
|
23 | # avoid repetition of keys throughout the module. | |
|
24 | generate_authorized_keyfile = 'ssh.generate_authorized_keyfile' | |
|
25 | authorized_keys_file_path = 'ssh.authorized_keys_file_path' | |
|
26 | authorized_keys_line_ssh_opts = 'ssh.authorized_keys_ssh_opts' | |
|
27 | wrapper_cmd = 'ssh.wrapper_cmd' | |
|
28 | wrapper_allow_shell = 'ssh.wrapper_cmd_allow_shell' |
@@ -0,0 +1,29 b'' | |||
|
1 | # Copyright (C) 2016-2017 RhodeCode GmbH | |
|
2 | # | |
|
3 | # This program is free software: you can redistribute it and/or modify | |
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
5 | # (only), as published by the Free Software Foundation. | |
|
6 | # | |
|
7 | # This program is distributed in the hope that it will be useful, | |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
10 | # GNU General Public License for more details. | |
|
11 | # | |
|
12 | # You should have received a copy of the GNU Affero General Public License | |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
14 | # | |
|
15 | # This program is dual-licensed. If you wish to learn more about the | |
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
18 | ||
|
19 | ||
|
20 | from rhodecode.events.base import RhodecodeEvent | |
|
21 | from rhodecode.translation import _ | |
|
22 | ||
|
23 | ||
|
24 | class SshKeyFileChangeEvent(RhodecodeEvent): | |
|
25 | """ | |
|
26 | This event will be triggered on every modification of the stored SSH keys | |
|
27 | """ | |
|
28 | name = 'rhodecode-ssh-key-file-change' | |
|
29 | display_name = _('RhodeCode SSH Key files changed.') |
@@ -0,0 +1,36 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2016-2017 RhodeCode GmbH | |
|
4 | # | |
|
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 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
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/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | import logging | |
|
22 | ||
|
23 | ||
|
24 | from .utils import generate_ssh_authorized_keys_file | |
|
25 | ||
|
26 | ||
|
27 | log = logging.getLogger(__name__) | |
|
28 | ||
|
29 | ||
|
30 | def generate_ssh_authorized_keys_file_subscriber(event): | |
|
31 | """ | |
|
32 | Subscriber to the `SshKeyFileChangeEvent`. This triggers the | |
|
33 | automatic generation of authorized_keys file on any change in | |
|
34 | ssh keys management | |
|
35 | """ | |
|
36 | generate_ssh_authorized_keys_file(event.request.registry) |
@@ -0,0 +1,19 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2016-2017 RhodeCode GmbH | |
|
4 | # | |
|
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 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
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/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
@@ -0,0 +1,68 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2016-2017 RhodeCode GmbH | |
|
4 | # | |
|
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 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
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/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | ||
|
22 | ||
|
23 | import os | |
|
24 | import pytest | |
|
25 | import mock | |
|
26 | ||
|
27 | from rhodecode.apps.ssh_support import utils | |
|
28 | from rhodecode.lib.utils2 import AttributeDict | |
|
29 | ||
|
30 | ||
|
31 | class TestSshKeyFileGeneration(object): | |
|
32 | @pytest.mark.parametrize('ssh_wrapper_cmd', ['/tmp/sshwrapper.py']) | |
|
33 | @pytest.mark.parametrize('allow_shell', [True, False]) | |
|
34 | @pytest.mark.parametrize('ssh_opts', [None, 'mycustom,option']) | |
|
35 | def test_write_keyfile(self, tmpdir, ssh_wrapper_cmd, allow_shell, ssh_opts): | |
|
36 | ||
|
37 | authorized_keys_file_path = os.path.join(str(tmpdir), 'authorized_keys') | |
|
38 | ||
|
39 | def keys(): | |
|
40 | return [ | |
|
41 | AttributeDict({'user': AttributeDict(username='admin'), | |
|
42 | 'ssh_key_data': 'ssh-rsa ADMIN_KEY'}), | |
|
43 | AttributeDict({'user': AttributeDict(username='user'), | |
|
44 | 'ssh_key_data': 'ssh-rsa USER_KEY'}), | |
|
45 | ] | |
|
46 | ||
|
47 | with mock.patch('rhodecode.apps.ssh_support.utils.get_all_active_keys', | |
|
48 | return_value=keys()): | |
|
49 | utils._generate_ssh_authorized_keys_file( | |
|
50 | authorized_keys_file_path, ssh_wrapper_cmd, | |
|
51 | allow_shell, ssh_opts | |
|
52 | ) | |
|
53 | ||
|
54 | assert os.path.isfile(authorized_keys_file_path) | |
|
55 | with open(authorized_keys_file_path) as f: | |
|
56 | content = f.read() | |
|
57 | ||
|
58 | assert 'command="/tmp/sshwrapper.py' in content | |
|
59 | assert 'This file is managed by RhodeCode, ' \ | |
|
60 | 'please do not edit it manually.' in content | |
|
61 | ||
|
62 | if allow_shell: | |
|
63 | assert '--shell --user' in content | |
|
64 | else: | |
|
65 | assert '--user' in content | |
|
66 | ||
|
67 | if ssh_opts: | |
|
68 | assert ssh_opts in content |
@@ -0,0 +1,107 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2016-2017 RhodeCode GmbH | |
|
4 | # | |
|
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 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
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/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | import os | |
|
22 | import stat | |
|
23 | import logging | |
|
24 | import tempfile | |
|
25 | import datetime | |
|
26 | ||
|
27 | from . import config_keys | |
|
28 | from rhodecode.model.db import true, joinedload, User, UserSshKeys | |
|
29 | ||
|
30 | ||
|
31 | log = logging.getLogger(__name__) | |
|
32 | ||
|
33 | HEADER = \ | |
|
34 | "# This file is managed by RhodeCode, please do not edit it manually. # \n" \ | |
|
35 | "# Current entries: {}, create date: UTC:{}.\n" | |
|
36 | ||
|
37 | # Default SSH options for authorized_keys file, can be override via .ini | |
|
38 | SSH_OPTS = 'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding' | |
|
39 | ||
|
40 | ||
|
41 | def get_all_active_keys(): | |
|
42 | result = UserSshKeys.query() \ | |
|
43 | .options(joinedload(UserSshKeys.user)) \ | |
|
44 | .filter(UserSshKeys.user != User.get_default_user()) \ | |
|
45 | .filter(User.active == true()) \ | |
|
46 | .all() | |
|
47 | return result | |
|
48 | ||
|
49 | ||
|
50 | def _generate_ssh_authorized_keys_file( | |
|
51 | authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts): | |
|
52 | all_active_keys = get_all_active_keys() | |
|
53 | ||
|
54 | if allow_shell: | |
|
55 | ssh_wrapper_cmd = ssh_wrapper_cmd + ' --shell' | |
|
56 | ||
|
57 | if not os.path.isfile(authorized_keys_file_path): | |
|
58 | with open(authorized_keys_file_path, 'w'): | |
|
59 | pass | |
|
60 | ||
|
61 | if not os.access(authorized_keys_file_path, os.R_OK): | |
|
62 | raise OSError('Access to file {} is without read access'.format( | |
|
63 | authorized_keys_file_path)) | |
|
64 | ||
|
65 | line_tmpl = '{ssh_opts},command="{wrapper_command} --user {user}" {key}\n' | |
|
66 | ||
|
67 | fd, tmp_authorized_keys = tempfile.mkstemp( | |
|
68 | '.authorized_keys_write', | |
|
69 | dir=os.path.dirname(authorized_keys_file_path)) | |
|
70 | ||
|
71 | now = datetime.datetime.utcnow().isoformat() | |
|
72 | keys_file = os.fdopen(fd, 'wb') | |
|
73 | keys_file.write(HEADER.format(len(all_active_keys), now)) | |
|
74 | ||
|
75 | for user_key in all_active_keys: | |
|
76 | username = user_key.user.username | |
|
77 | keys_file.write( | |
|
78 | line_tmpl.format( | |
|
79 | ssh_opts=ssh_opts or SSH_OPTS, | |
|
80 | wrapper_command=ssh_wrapper_cmd, | |
|
81 | user=username, key=user_key.ssh_key_data)) | |
|
82 | log.debug('addkey: Key added for user: `%s`', username) | |
|
83 | keys_file.close() | |
|
84 | ||
|
85 | # Explicitly setting read-only permissions to authorized_keys | |
|
86 | os.chmod(tmp_authorized_keys, stat.S_IRUSR | stat.S_IWUSR) | |
|
87 | # Rename is atomic operation | |
|
88 | os.rename(tmp_authorized_keys, authorized_keys_file_path) | |
|
89 | ||
|
90 | ||
|
91 | def generate_ssh_authorized_keys_file(registry): | |
|
92 | log.info('Generating new authorized key file') | |
|
93 | ||
|
94 | authorized_keys_file_path = registry.settings.get( | |
|
95 | config_keys.authorized_keys_file_path) | |
|
96 | ||
|
97 | ssh_wrapper_cmd = registry.settings.get( | |
|
98 | config_keys.wrapper_cmd) | |
|
99 | allow_shell = registry.settings.get( | |
|
100 | config_keys.wrapper_allow_shell) | |
|
101 | ssh_opts = registry.settings.get( | |
|
102 | config_keys.authorized_keys_line_ssh_opts) | |
|
103 | ||
|
104 | _generate_ssh_authorized_keys_file( | |
|
105 | authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts) | |
|
106 | ||
|
107 | return 0 |
@@ -587,6 +587,27 b' svn.proxy.location_root = /' | |||
|
587 | 587 | ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds. |
|
588 | 588 | #svn.proxy.reload_timeout = 10 |
|
589 | 589 | |
|
590 | ############################################################ | |
|
591 | ### SSH Support Settings ### | |
|
592 | ############################################################ | |
|
593 | ||
|
594 | ## Defines if the authorized_keys file should be written on any change of | |
|
595 | ## user ssh keys | |
|
596 | ssh.generate_authorized_keyfile = false | |
|
597 | ||
|
598 | ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding` | |
|
599 | # ssh.authorized_keys_ssh_opts = | |
|
600 | ||
|
601 | ## File to generate the authorized keys together with options | |
|
602 | ssh.authorized_keys_file_path = /home/USER/.ssh/authorized_keys | |
|
603 | ||
|
604 | ## Command to execute as an SSH wrapper, available from | |
|
605 | ## https://code.rhodecode.com/rhodecode-ssh | |
|
606 | ssh.wrapper_cmd = /home/USER/rhodecode-ssh/sshwrapper.py | |
|
607 | ||
|
608 | ## Allow shell when executing the command | |
|
609 | ssh.wrapper_cmd_allow_shell = false | |
|
610 | ||
|
590 | 611 | ## Dummy marker to add new entries after. |
|
591 | 612 | ## Add any custom entries below. Please don't remove. |
|
592 | 613 | custom.conf = 1 |
@@ -556,6 +556,27 b' svn.proxy.location_root = /' | |||
|
556 | 556 | ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds. |
|
557 | 557 | #svn.proxy.reload_timeout = 10 |
|
558 | 558 | |
|
559 | ############################################################ | |
|
560 | ### SSH Support Settings ### | |
|
561 | ############################################################ | |
|
562 | ||
|
563 | ## Defines if the authorized_keys file should be written on any change of | |
|
564 | ## user ssh keys | |
|
565 | ssh.generate_authorized_keyfile = false | |
|
566 | ||
|
567 | ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding` | |
|
568 | # ssh.authorized_keys_ssh_opts = | |
|
569 | ||
|
570 | ## File to generate the authorized keys together with options | |
|
571 | ssh.authorized_keys_file_path = /home/USER/.ssh/authorized_keys | |
|
572 | ||
|
573 | ## Command to execute as an SSH wrapper, available from | |
|
574 | ## https://code.rhodecode.com/rhodecode-ssh | |
|
575 | ssh.wrapper_cmd = /home/USER/rhodecode-ssh/sshwrapper.py | |
|
576 | ||
|
577 | ## Allow shell when executing the command | |
|
578 | ssh.wrapper_cmd_allow_shell = false | |
|
579 | ||
|
559 | 580 | ## Dummy marker to add new entries after. |
|
560 | 581 | ## Add any custom entries below. Please don't remove. |
|
561 | 582 | custom.conf = 1 |
@@ -28,6 +28,8 b' from sqlalchemy.sql.functions import coa' | |||
|
28 | 28 | from sqlalchemy.exc import IntegrityError |
|
29 | 29 | |
|
30 | 30 | from rhodecode.apps._base import BaseAppView, DataGridAppView |
|
31 | from rhodecode.apps.ssh_support import SshKeyFileChangeEvent | |
|
32 | from rhodecode.events import trigger | |
|
31 | 33 | |
|
32 | 34 | from rhodecode.lib import audit_logger |
|
33 | 35 | from rhodecode.lib.ext_json import json |
@@ -327,6 +329,9 b' class AdminUsersView(BaseAppView, DataGr' | |||
|
327 | 329 | user=self._rhodecode_user, ) |
|
328 | 330 | Session().commit() |
|
329 | 331 | |
|
332 | # Trigger an event on change of keys. | |
|
333 | trigger(SshKeyFileChangeEvent(), self.request.registry) | |
|
334 | ||
|
330 | 335 | h.flash(_("Ssh Key successfully created"), category='success') |
|
331 | 336 | |
|
332 | 337 | except IntegrityError: |
@@ -368,6 +373,8 b' class AdminUsersView(BaseAppView, DataGr' | |||
|
368 | 373 | 'data': {'ssh_key': ssh_key_data, 'user': user_data}}, |
|
369 | 374 | user=self._rhodecode_user,) |
|
370 | 375 | Session().commit() |
|
376 | # Trigger an event on change of keys. | |
|
377 | trigger(SshKeyFileChangeEvent(), self.request.registry) | |
|
371 | 378 | h.flash(_("Ssh key successfully deleted"), category='success') |
|
372 | 379 | |
|
373 | 380 | return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id)) |
@@ -300,6 +300,7 b' def includeme(config):' | |||
|
300 | 300 | config.include('rhodecode.apps.user_profile') |
|
301 | 301 | config.include('rhodecode.apps.my_account') |
|
302 | 302 | config.include('rhodecode.apps.svn_support') |
|
303 | config.include('rhodecode.apps.ssh_support') | |
|
303 | 304 | config.include('rhodecode.apps.gist') |
|
304 | 305 | |
|
305 | 306 | config.include('rhodecode.apps.debug_style') |
General Comments 0
You need to be logged in to leave comments.
Login now