##// END OF EJS Templates
ssh(sec): fix newline problem on key saving that would allow bypassing command sandbox.
marcink -
r2748:2362c2ca stable
parent child Browse files
Show More
@@ -1,125 +1,131 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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 stat
23 23 import logging
24 24 import tempfile
25 25 import datetime
26 26
27 27 from . import config_keys
28 28 from rhodecode.model.db import true, joinedload, User, UserSshKeys
29 29
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33 HEADER = \
34 34 "# This file is managed by RhodeCode, please do not edit it manually. # \n" \
35 35 "# Current entries: {}, create date: UTC:{}.\n"
36 36
37 37 # Default SSH options for authorized_keys file, can be override via .ini
38 38 SSH_OPTS = 'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding'
39 39
40 40
41 41 def get_all_active_keys():
42 42 result = UserSshKeys.query() \
43 43 .options(joinedload(UserSshKeys.user)) \
44 44 .filter(UserSshKeys.user != User.get_default_user()) \
45 45 .filter(User.active == true()) \
46 46 .all()
47 47 return result
48 48
49 49
50 50 def _generate_ssh_authorized_keys_file(
51 51 authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts, debug):
52 52
53 53 authorized_keys_file_path = os.path.abspath(
54 54 os.path.expanduser(authorized_keys_file_path))
55 55
56 56 import rhodecode
57 57 all_active_keys = get_all_active_keys()
58 58
59 59 if allow_shell:
60 60 ssh_wrapper_cmd = ssh_wrapper_cmd + ' --shell'
61 61 if debug:
62 62 ssh_wrapper_cmd = ssh_wrapper_cmd + ' --debug'
63 63
64 64 if not os.path.isfile(authorized_keys_file_path):
65 65 log.debug('Creating file at %s', authorized_keys_file_path)
66 66 with open(authorized_keys_file_path, 'w'):
67 67 pass
68 68
69 69 if not os.access(authorized_keys_file_path, os.R_OK):
70 70 raise OSError('Access to file {} is without read access'.format(
71 71 authorized_keys_file_path))
72 72
73 73 line_tmpl = '{ssh_opts},command="{wrapper_command} {ini_path} --user-id={user_id} --user={user} --key-id={user_key_id}" {key}\n'
74 74
75 75 fd, tmp_authorized_keys = tempfile.mkstemp(
76 76 '.authorized_keys_write',
77 77 dir=os.path.dirname(authorized_keys_file_path))
78 78
79 79 now = datetime.datetime.utcnow().isoformat()
80 80 keys_file = os.fdopen(fd, 'wb')
81 81 keys_file.write(HEADER.format(len(all_active_keys), now))
82 82 ini_path = rhodecode.CONFIG['__file__']
83 83
84 84 for user_key in all_active_keys:
85 85 username = user_key.user.username
86 86 user_id = user_key.user.user_id
87 # replace all newline from ends and inside
88 safe_key_data = user_key.ssh_key_data\
89 .strip()\
90 .replace('\n', ' ')\
91 .replace('\r', ' ')
87 92
88 keys_file.write(
89 line_tmpl.format(
93 line = line_tmpl.format(
90 94 ssh_opts=ssh_opts or SSH_OPTS,
91 95 wrapper_command=ssh_wrapper_cmd,
92 96 ini_path=ini_path,
93 97 user_id=user_id,
94 98 user=username,
95 99 user_key_id=user_key.ssh_key_id,
96 key=user_key.ssh_key_data))
100 key=safe_key_data)
101
102 keys_file.write(line)
97 103 log.debug('addkey: Key added for user: `%s`', username)
98 104 keys_file.close()
99 105
100 106 # Explicitly setting read-only permissions to authorized_keys
101 107 os.chmod(tmp_authorized_keys, stat.S_IRUSR | stat.S_IWUSR)
102 108 # Rename is atomic operation
103 109 os.rename(tmp_authorized_keys, authorized_keys_file_path)
104 110
105 111
106 112 def generate_ssh_authorized_keys_file(registry):
107 113 log.info('Generating new authorized key file')
108 114
109 115 authorized_keys_file_path = registry.settings.get(
110 116 config_keys.authorized_keys_file_path)
111 117
112 118 ssh_wrapper_cmd = registry.settings.get(
113 119 config_keys.wrapper_cmd)
114 120 allow_shell = registry.settings.get(
115 121 config_keys.wrapper_allow_shell)
116 122 ssh_opts = registry.settings.get(
117 123 config_keys.authorized_keys_line_ssh_opts)
118 124 debug = registry.settings.get(
119 125 config_keys.enable_debug_logging)
120 126
121 127 _generate_ssh_authorized_keys_file(
122 128 authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts,
123 129 debug)
124 130
125 131 return 0
@@ -1,51 +1,51 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('New SSH Key generated')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <p>
7 7 ${_('Below is a 2048 bit generated SSH RSA key. You can use it to access RhodeCode via the SSH wrapper.')}
8 8 </p>
9 9 <h4>${_('Private key')}</h4>
10 10 <pre>
11 # Save the content as
12 # Windows: /Users/<username>/.ssh/id_rsa_rhodecode_access_priv.key
13 # macOS: /Users/<yourname>/.ssh/id_rsa_rhodecode_access_priv.key
14 # Linux: /home/<username>/.ssh/id_rsa_rhodecode_access_priv.key
11 # Save the below content as
12 # Windows: /Users/{username}/.ssh/id_rsa_rhodecode_access_priv.key
13 # macOS: /Users/{yourname}/.ssh/id_rsa_rhodecode_access_priv.key
14 # Linux: /home/{username}/.ssh/id_rsa_rhodecode_access_priv.key
15 15
16 16 # Change permissions to 0600 to make it secure, and usable.
17 e.g chmod 0600 /home/<username>/.ssh/id_rsa_rhodecode_access_priv.key
17 e.g chmod 0600 /home/{username}/.ssh/id_rsa_rhodecode_access_priv.key
18 18 </pre>
19 19
20 20 <div>
21 21 <textarea style="height: 300px">${c.private}</textarea>
22 22 </div>
23 23 <br/>
24 24
25 25 <h4>${_('Public key')}</h4>
26 26 <pre>
27 # Save the content as
28 # Windows: /Users/<username>/.ssh/id_rsa_rhodecode_access_pub.key
29 # macOS: /Users/<yourname>/.ssh/id_rsa_rhodecode_access_pub.key
30 # Linux: /home/<username>/.ssh/id_rsa_rhodecode_access_pub.key
27 # Save the below content as
28 # Windows: /Users/{username}/.ssh/id_rsa_rhodecode_access_pub.key
29 # macOS: /Users/{yourname}/.ssh/id_rsa_rhodecode_access_pub.key
30 # Linux: /home/{username}/.ssh/id_rsa_rhodecode_access_pub.key
31 31 </pre>
32 32
33 33 <input type="text" value="${c.public}" class="large text" size="100"/>
34 34 <p>
35 35 % if hasattr(c, 'target_form_url'):
36 36 <a href="${c.target_form_url}">${_('Use this generated key')}.</a>
37 37 % else:
38 38 <a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id, _query=dict(default_key=c.public))}">${_('Use this generated key')}.</a>
39 39 % endif
40 40 ${_('Confirmation required on the next screen')}.
41 41 </p>
42 42 </div>
43 43 </div>
44 44
45 45 <script>
46 46
47 47 $(document).ready(function(){
48 48
49 49
50 50 });
51 51 </script>
General Comments 0
You need to be logged in to leave comments. Login now