ssh: added support for auto generating authorized_keys from stored ssh keys.
This commit is contained in:
parent
35cefa9b2d
commit
945777cf9c
11 changed files with 391 additions and 0 deletions
|
|
@ -587,6 +587,27 @@ svn.proxy.location_root = /
|
|||
## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
|
||||
#svn.proxy.reload_timeout = 10
|
||||
|
||||
############################################################
|
||||
### SSH Support Settings ###
|
||||
############################################################
|
||||
|
||||
## Defines if the authorized_keys file should be written on any change of
|
||||
## user ssh keys
|
||||
ssh.generate_authorized_keyfile = false
|
||||
|
||||
## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
|
||||
# ssh.authorized_keys_ssh_opts =
|
||||
|
||||
## File to generate the authorized keys together with options
|
||||
ssh.authorized_keys_file_path = /home/USER/.ssh/authorized_keys
|
||||
|
||||
## Command to execute as an SSH wrapper, available from
|
||||
## https://code.rhodecode.com/rhodecode-ssh
|
||||
ssh.wrapper_cmd = /home/USER/rhodecode-ssh/sshwrapper.py
|
||||
|
||||
## Allow shell when executing the command
|
||||
ssh.wrapper_cmd_allow_shell = false
|
||||
|
||||
## Dummy marker to add new entries after.
|
||||
## Add any custom entries below. Please don't remove.
|
||||
custom.conf = 1
|
||||
|
|
|
|||
|
|
@ -556,6 +556,27 @@ svn.proxy.location_root = /
|
|||
## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
|
||||
#svn.proxy.reload_timeout = 10
|
||||
|
||||
############################################################
|
||||
### SSH Support Settings ###
|
||||
############################################################
|
||||
|
||||
## Defines if the authorized_keys file should be written on any change of
|
||||
## user ssh keys
|
||||
ssh.generate_authorized_keyfile = false
|
||||
|
||||
## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
|
||||
# ssh.authorized_keys_ssh_opts =
|
||||
|
||||
## File to generate the authorized keys together with options
|
||||
ssh.authorized_keys_file_path = /home/USER/.ssh/authorized_keys
|
||||
|
||||
## Command to execute as an SSH wrapper, available from
|
||||
## https://code.rhodecode.com/rhodecode-ssh
|
||||
ssh.wrapper_cmd = /home/USER/rhodecode-ssh/sshwrapper.py
|
||||
|
||||
## Allow shell when executing the command
|
||||
ssh.wrapper_cmd_allow_shell = false
|
||||
|
||||
## Dummy marker to add new entries after.
|
||||
## Add any custom entries below. Please don't remove.
|
||||
custom.conf = 1
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ from sqlalchemy.sql.functions import coalesce
|
|||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from rhodecode.apps._base import BaseAppView, DataGridAppView
|
||||
from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
|
||||
from rhodecode.events import trigger
|
||||
|
||||
from rhodecode.lib import audit_logger
|
||||
from rhodecode.lib.ext_json import json
|
||||
|
|
@ -327,6 +329,9 @@ class AdminUsersView(BaseAppView, DataGridAppView):
|
|||
user=self._rhodecode_user, )
|
||||
Session().commit()
|
||||
|
||||
# Trigger an event on change of keys.
|
||||
trigger(SshKeyFileChangeEvent(), self.request.registry)
|
||||
|
||||
h.flash(_("Ssh Key successfully created"), category='success')
|
||||
|
||||
except IntegrityError:
|
||||
|
|
@ -368,6 +373,8 @@ class AdminUsersView(BaseAppView, DataGridAppView):
|
|||
'data': {'ssh_key': ssh_key_data, 'user': user_data}},
|
||||
user=self._rhodecode_user,)
|
||||
Session().commit()
|
||||
# Trigger an event on change of keys.
|
||||
trigger(SshKeyFileChangeEvent(), self.request.registry)
|
||||
h.flash(_("Ssh key successfully deleted"), category='success')
|
||||
|
||||
return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
|
||||
|
|
|
|||
54
rhodecode/apps/ssh_support/__init__.py
Normal file
54
rhodecode/apps/ssh_support/__init__.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2016-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
import logging
|
||||
|
||||
from . import config_keys
|
||||
from .events import SshKeyFileChangeEvent
|
||||
from .subscribers import generate_ssh_authorized_keys_file_subscriber
|
||||
|
||||
from rhodecode.config.middleware import _bool_setting, _string_setting
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _sanitize_settings_and_apply_defaults(settings):
|
||||
"""
|
||||
Set defaults, convert to python types and validate settings.
|
||||
"""
|
||||
_bool_setting(settings, config_keys.generate_authorized_keyfile, 'false')
|
||||
_bool_setting(settings, config_keys.wrapper_allow_shell, 'false')
|
||||
|
||||
_string_setting(settings, config_keys.authorized_keys_file_path, '',
|
||||
lower=False)
|
||||
_string_setting(settings, config_keys.wrapper_cmd, '',
|
||||
lower=False)
|
||||
_string_setting(settings, config_keys.authorized_keys_line_ssh_opts, '',
|
||||
lower=False)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
settings = config.registry.settings
|
||||
_sanitize_settings_and_apply_defaults(settings)
|
||||
|
||||
# if we have enable generation of file, subscribe to event
|
||||
if settings[config_keys.generate_authorized_keyfile]:
|
||||
config.add_subscriber(
|
||||
generate_ssh_authorized_keys_file_subscriber, SshKeyFileChangeEvent)
|
||||
28
rhodecode/apps/ssh_support/config_keys.py
Normal file
28
rhodecode/apps/ssh_support/config_keys.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2016-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
|
||||
# Definition of setting keys used to configure this module. Defined here to
|
||||
# avoid repetition of keys throughout the module.
|
||||
generate_authorized_keyfile = 'ssh.generate_authorized_keyfile'
|
||||
authorized_keys_file_path = 'ssh.authorized_keys_file_path'
|
||||
authorized_keys_line_ssh_opts = 'ssh.authorized_keys_ssh_opts'
|
||||
wrapper_cmd = 'ssh.wrapper_cmd'
|
||||
wrapper_allow_shell = 'ssh.wrapper_cmd_allow_shell'
|
||||
29
rhodecode/apps/ssh_support/events.py
Normal file
29
rhodecode/apps/ssh_support/events.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Copyright (C) 2016-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
|
||||
from rhodecode.events.base import RhodecodeEvent
|
||||
from rhodecode.translation import _
|
||||
|
||||
|
||||
class SshKeyFileChangeEvent(RhodecodeEvent):
|
||||
"""
|
||||
This event will be triggered on every modification of the stored SSH keys
|
||||
"""
|
||||
name = 'rhodecode-ssh-key-file-change'
|
||||
display_name = _('RhodeCode SSH Key files changed.')
|
||||
36
rhodecode/apps/ssh_support/subscribers.py
Normal file
36
rhodecode/apps/ssh_support/subscribers.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2016-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
from .utils import generate_ssh_authorized_keys_file
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def generate_ssh_authorized_keys_file_subscriber(event):
|
||||
"""
|
||||
Subscriber to the `SshKeyFileChangeEvent`. This triggers the
|
||||
automatic generation of authorized_keys file on any change in
|
||||
ssh keys management
|
||||
"""
|
||||
generate_ssh_authorized_keys_file(event.request.registry)
|
||||
19
rhodecode/apps/ssh_support/tests/__init__.py
Normal file
19
rhodecode/apps/ssh_support/tests/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2016-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2016-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import mock
|
||||
|
||||
from rhodecode.apps.ssh_support import utils
|
||||
from rhodecode.lib.utils2 import AttributeDict
|
||||
|
||||
|
||||
class TestSshKeyFileGeneration(object):
|
||||
@pytest.mark.parametrize('ssh_wrapper_cmd', ['/tmp/sshwrapper.py'])
|
||||
@pytest.mark.parametrize('allow_shell', [True, False])
|
||||
@pytest.mark.parametrize('ssh_opts', [None, 'mycustom,option'])
|
||||
def test_write_keyfile(self, tmpdir, ssh_wrapper_cmd, allow_shell, ssh_opts):
|
||||
|
||||
authorized_keys_file_path = os.path.join(str(tmpdir), 'authorized_keys')
|
||||
|
||||
def keys():
|
||||
return [
|
||||
AttributeDict({'user': AttributeDict(username='admin'),
|
||||
'ssh_key_data': 'ssh-rsa ADMIN_KEY'}),
|
||||
AttributeDict({'user': AttributeDict(username='user'),
|
||||
'ssh_key_data': 'ssh-rsa USER_KEY'}),
|
||||
]
|
||||
|
||||
with mock.patch('rhodecode.apps.ssh_support.utils.get_all_active_keys',
|
||||
return_value=keys()):
|
||||
utils._generate_ssh_authorized_keys_file(
|
||||
authorized_keys_file_path, ssh_wrapper_cmd,
|
||||
allow_shell, ssh_opts
|
||||
)
|
||||
|
||||
assert os.path.isfile(authorized_keys_file_path)
|
||||
with open(authorized_keys_file_path) as f:
|
||||
content = f.read()
|
||||
|
||||
assert 'command="/tmp/sshwrapper.py' in content
|
||||
assert 'This file is managed by RhodeCode, ' \
|
||||
'please do not edit it manually.' in content
|
||||
|
||||
if allow_shell:
|
||||
assert '--shell --user' in content
|
||||
else:
|
||||
assert '--user' in content
|
||||
|
||||
if ssh_opts:
|
||||
assert ssh_opts in content
|
||||
107
rhodecode/apps/ssh_support/utils.py
Normal file
107
rhodecode/apps/ssh_support/utils.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2016-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
import os
|
||||
import stat
|
||||
import logging
|
||||
import tempfile
|
||||
import datetime
|
||||
|
||||
from . import config_keys
|
||||
from rhodecode.model.db import true, joinedload, User, UserSshKeys
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
HEADER = \
|
||||
"# This file is managed by RhodeCode, please do not edit it manually. # \n" \
|
||||
"# Current entries: {}, create date: UTC:{}.\n"
|
||||
|
||||
# Default SSH options for authorized_keys file, can be override via .ini
|
||||
SSH_OPTS = 'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding'
|
||||
|
||||
|
||||
def get_all_active_keys():
|
||||
result = UserSshKeys.query() \
|
||||
.options(joinedload(UserSshKeys.user)) \
|
||||
.filter(UserSshKeys.user != User.get_default_user()) \
|
||||
.filter(User.active == true()) \
|
||||
.all()
|
||||
return result
|
||||
|
||||
|
||||
def _generate_ssh_authorized_keys_file(
|
||||
authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts):
|
||||
all_active_keys = get_all_active_keys()
|
||||
|
||||
if allow_shell:
|
||||
ssh_wrapper_cmd = ssh_wrapper_cmd + ' --shell'
|
||||
|
||||
if not os.path.isfile(authorized_keys_file_path):
|
||||
with open(authorized_keys_file_path, 'w'):
|
||||
pass
|
||||
|
||||
if not os.access(authorized_keys_file_path, os.R_OK):
|
||||
raise OSError('Access to file {} is without read access'.format(
|
||||
authorized_keys_file_path))
|
||||
|
||||
line_tmpl = '{ssh_opts},command="{wrapper_command} --user {user}" {key}\n'
|
||||
|
||||
fd, tmp_authorized_keys = tempfile.mkstemp(
|
||||
'.authorized_keys_write',
|
||||
dir=os.path.dirname(authorized_keys_file_path))
|
||||
|
||||
now = datetime.datetime.utcnow().isoformat()
|
||||
keys_file = os.fdopen(fd, 'wb')
|
||||
keys_file.write(HEADER.format(len(all_active_keys), now))
|
||||
|
||||
for user_key in all_active_keys:
|
||||
username = user_key.user.username
|
||||
keys_file.write(
|
||||
line_tmpl.format(
|
||||
ssh_opts=ssh_opts or SSH_OPTS,
|
||||
wrapper_command=ssh_wrapper_cmd,
|
||||
user=username, key=user_key.ssh_key_data))
|
||||
log.debug('addkey: Key added for user: `%s`', username)
|
||||
keys_file.close()
|
||||
|
||||
# Explicitly setting read-only permissions to authorized_keys
|
||||
os.chmod(tmp_authorized_keys, stat.S_IRUSR | stat.S_IWUSR)
|
||||
# Rename is atomic operation
|
||||
os.rename(tmp_authorized_keys, authorized_keys_file_path)
|
||||
|
||||
|
||||
def generate_ssh_authorized_keys_file(registry):
|
||||
log.info('Generating new authorized key file')
|
||||
|
||||
authorized_keys_file_path = registry.settings.get(
|
||||
config_keys.authorized_keys_file_path)
|
||||
|
||||
ssh_wrapper_cmd = registry.settings.get(
|
||||
config_keys.wrapper_cmd)
|
||||
allow_shell = registry.settings.get(
|
||||
config_keys.wrapper_allow_shell)
|
||||
ssh_opts = registry.settings.get(
|
||||
config_keys.authorized_keys_line_ssh_opts)
|
||||
|
||||
_generate_ssh_authorized_keys_file(
|
||||
authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts)
|
||||
|
||||
return 0
|
||||
|
|
@ -300,6 +300,7 @@ def includeme(config):
|
|||
config.include('rhodecode.apps.user_profile')
|
||||
config.include('rhodecode.apps.my_account')
|
||||
config.include('rhodecode.apps.svn_support')
|
||||
config.include('rhodecode.apps.ssh_support')
|
||||
config.include('rhodecode.apps.gist')
|
||||
|
||||
config.include('rhodecode.apps.debug_style')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue