##// END OF EJS Templates
ssh: added support for auto generating authorized_keys from stored ssh keys.
marcink -
r1994:801a60b3 default
parent child Browse files
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 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
587 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
588 #svn.proxy.reload_timeout = 10
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 ## Dummy marker to add new entries after.
611 ## Dummy marker to add new entries after.
591 ## Add any custom entries below. Please don't remove.
612 ## Add any custom entries below. Please don't remove.
592 custom.conf = 1
613 custom.conf = 1
@@ -556,6 +556,27 b' svn.proxy.location_root = /'
556 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
556 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
557 #svn.proxy.reload_timeout = 10
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 ## Dummy marker to add new entries after.
580 ## Dummy marker to add new entries after.
560 ## Add any custom entries below. Please don't remove.
581 ## Add any custom entries below. Please don't remove.
561 custom.conf = 1
582 custom.conf = 1
@@ -28,6 +28,8 b' from sqlalchemy.sql.functions import coa'
28 from sqlalchemy.exc import IntegrityError
28 from sqlalchemy.exc import IntegrityError
29
29
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
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 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
33 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
@@ -327,6 +329,9 b' class AdminUsersView(BaseAppView, DataGr'
327 user=self._rhodecode_user, )
329 user=self._rhodecode_user, )
328 Session().commit()
330 Session().commit()
329
331
332 # Trigger an event on change of keys.
333 trigger(SshKeyFileChangeEvent(), self.request.registry)
334
330 h.flash(_("Ssh Key successfully created"), category='success')
335 h.flash(_("Ssh Key successfully created"), category='success')
331
336
332 except IntegrityError:
337 except IntegrityError:
@@ -368,6 +373,8 b' class AdminUsersView(BaseAppView, DataGr'
368 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
373 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
369 user=self._rhodecode_user,)
374 user=self._rhodecode_user,)
370 Session().commit()
375 Session().commit()
376 # Trigger an event on change of keys.
377 trigger(SshKeyFileChangeEvent(), self.request.registry)
371 h.flash(_("Ssh key successfully deleted"), category='success')
378 h.flash(_("Ssh key successfully deleted"), category='success')
372
379
373 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
380 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
@@ -300,6 +300,7 b' def includeme(config):'
300 config.include('rhodecode.apps.user_profile')
300 config.include('rhodecode.apps.user_profile')
301 config.include('rhodecode.apps.my_account')
301 config.include('rhodecode.apps.my_account')
302 config.include('rhodecode.apps.svn_support')
302 config.include('rhodecode.apps.svn_support')
303 config.include('rhodecode.apps.ssh_support')
303 config.include('rhodecode.apps.gist')
304 config.include('rhodecode.apps.gist')
304
305
305 config.include('rhodecode.apps.debug_style')
306 config.include('rhodecode.apps.debug_style')
General Comments 0
You need to be logged in to leave comments. Login now