##// END OF EJS Templates
ssh: introduce 'kallithea-cli ssh-update-authorized-keys' command for updating authorized_keys file...
Christian Oyarzun -
r7684:b27e515d default
parent child Browse files
Show More
@@ -49,6 +49,7 b' List of contributors to Kallithea projec'
49 YFdyh000 <yfdyh000@gmail.com> 2016
49 YFdyh000 <yfdyh000@gmail.com> 2016
50 Aras Pranckevičius <aras@unity3d.com> 2012-2013 2015
50 Aras Pranckevičius <aras@unity3d.com> 2012-2013 2015
51 Sean Farley <sean.michael.farley@gmail.com> 2013-2015
51 Sean Farley <sean.michael.farley@gmail.com> 2013-2015
52 Bradley M. Kuhn <bkuhn@sfconservancy.org> 2014-2015
52 Christian Oyarzun <oyarzun@gmail.com> 2014-2015
53 Christian Oyarzun <oyarzun@gmail.com> 2014-2015
53 Joseph Rivera <rivera.d.joseph@gmail.com> 2014-2015
54 Joseph Rivera <rivera.d.joseph@gmail.com> 2014-2015
54 Anatoly Bubenkov <bubenkoff@gmail.com> 2015
55 Anatoly Bubenkov <bubenkoff@gmail.com> 2015
@@ -78,7 +79,6 b' List of contributors to Kallithea projec'
78 Tuux <tuxa@galaxie.eu.org> 2015
79 Tuux <tuxa@galaxie.eu.org> 2015
79 Viktar Palstsiuk <vipals@gmail.com> 2015
80 Viktar Palstsiuk <vipals@gmail.com> 2015
80 Ante Ilic <ante@unity3d.com> 2014
81 Ante Ilic <ante@unity3d.com> 2014
81 Bradley M. Kuhn <bkuhn@sfconservancy.org> 2014
82 Calinou <calinou@opmbx.org> 2014
82 Calinou <calinou@opmbx.org> 2014
83 Daniel Anderson <daniel@dattrix.com> 2014
83 Daniel Anderson <daniel@dattrix.com> 2014
84 Henrik Stuart <hg@hstuart.dk> 2014
84 Henrik Stuart <hg@hstuart.dk> 2014
@@ -232,6 +232,12 b' allow_custom_hooks_settings = True'
232 ## SSH is disabled by default, until an Administrator decides to enable it.
232 ## SSH is disabled by default, until an Administrator decides to enable it.
233 ssh_enabled = false
233 ssh_enabled = false
234
234
235 ## File where users' SSH keys will be stored *if* ssh_enabled is true.
236 #ssh_authorized_keys = /home/kallithea/.ssh/authorized_keys
237
238 ## Path to be used in ssh_authorized_keys file to invoke kallithea-cli with ssh-serve.
239 #kallithea_cli_path = /srv/kallithea/venv/bin/kallithea-cli
240
235 ####################################
241 ####################################
236 ### CELERY CONFIG ####
242 ### CELERY CONFIG ####
237 ####################################
243 ####################################
@@ -25,6 +25,7 b' import kallithea'
25 from kallithea.lib.utils2 import str2bool
25 from kallithea.lib.utils2 import str2bool
26 from kallithea.lib.vcs.backends.git.ssh import GitSshHandler
26 from kallithea.lib.vcs.backends.git.ssh import GitSshHandler
27 from kallithea.lib.vcs.backends.hg.ssh import MercurialSshHandler
27 from kallithea.lib.vcs.backends.hg.ssh import MercurialSshHandler
28 from kallithea.model.ssh_key import SshKeyModel
28
29
29 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
30
31
@@ -69,3 +70,13 b' def ssh_serve(user_id, key_id):'
69
70
70 sys.stderr.write("This account can only be used for repository access. SSH command %r is not supported.\n" % ssh_original_command)
71 sys.stderr.write("This account can only be used for repository access. SSH command %r is not supported.\n" % ssh_original_command)
71 sys.exit(1)
72 sys.exit(1)
73
74
75 @cli_base.register_command(config_file_initialize_app=True)
76 def ssh_update_authorized_keys():
77 """Update .ssh/authorized_keys file.
78
79 The file is usually maintained automatically, but this command will also re-write it.
80 """
81
82 SshKeyModel().write_authorized_keys()
@@ -329,6 +329,12 b' allow_custom_hooks_settings = True'
329 <%text>## SSH is disabled by default, until an Administrator decides to enable it.</%text>
329 <%text>## SSH is disabled by default, until an Administrator decides to enable it.</%text>
330 ssh_enabled = false
330 ssh_enabled = false
331
331
332 <%text>## File where users' SSH keys will be stored *if* ssh_enabled is true.</%text>
333 #ssh_authorized_keys = /home/kallithea/.ssh/authorized_keys
334
335 <%text>## Path to be used in ssh_authorized_keys file to invoke kallithea-cli with ssh-serve.</%text>
336 #kallithea_cli_path = /srv/kallithea/venv/bin/kallithea-cli
337
332 <%text>####################################</%text>
338 <%text>####################################</%text>
333 <%text>### CELERY CONFIG ####</%text>
339 <%text>### CELERY CONFIG ####</%text>
334 <%text>####################################</%text>
340 <%text>####################################</%text>
@@ -89,3 +89,28 b' def parse_pub_key(ssh_key):'
89 raise SshKeyParseError(_("Incorrect SSH key - base64 part is not %r as claimed but %r") % (str(keytype), str(decoded[4:].split('\0', 1)[0])))
89 raise SshKeyParseError(_("Incorrect SSH key - base64 part is not %r as claimed but %r") % (str(keytype), str(decoded[4:].split('\0', 1)[0])))
90
90
91 return keytype, decoded, comment
91 return keytype, decoded, comment
92
93
94 SSH_OPTIONS = 'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding'
95
96
97 def authorized_keys_line(kallithea_cli_path, config_file, key):
98 """
99 Return a line as it would appear in .authorized_keys
100
101 >>> from kallithea.model.db import UserSshKeys, User
102 >>> user = User(user_id=7, username='uu')
103 >>> key = UserSshKeys(user_ssh_key_id=17, user=user, description='test key')
104 >>> key.public_key='''ssh-rsa AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ== and a comment'''
105 >>> authorized_keys_line('/srv/kallithea/venv/bin/kallithea-cli', '/srv/kallithea/my.ini', key)
106 'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command="/srv/kallithea/venv/bin/kallithea-cli ssh-serve -c /srv/kallithea/my.ini 7 17" ssh-rsa AAAAB3NzaC1yc2EAAAALVGhpcyBpcyBmYWtlIQ==\\n'
107 """
108 try:
109 keytype, decoded, comment = parse_pub_key(key.public_key)
110 except SshKeyParseError:
111 return '# Invalid Kallithea SSH key: %s %s\n' % (key.user.user_id, key.user_ssh_key_id)
112 mimekey = decoded.encode('base64').replace('\n', '')
113 return '%s,command="%s ssh-serve -c %s %s %s" %s %s\n' % (
114 SSH_OPTIONS, kallithea_cli_path, config_file,
115 key.user.user_id, key.user_ssh_key_id,
116 keytype, mimekey)
@@ -20,10 +20,15 b' SSH key model for Kallithea'
20 """
20 """
21
21
22 import logging
22 import logging
23 import os
24 import stat
25 import tempfile
26 import errno
23
27
28 from tg import config
24 from tg.i18n import ugettext as _
29 from tg.i18n import ugettext as _
25
30
26 from kallithea.lib.utils2 import safe_str
31 from kallithea.lib.utils2 import safe_str, str2bool
27 from kallithea.model.db import UserSshKeys, User
32 from kallithea.model.db import UserSshKeys, User
28 from kallithea.model.meta import Session
33 from kallithea.model.meta import Session
29 from kallithea.lib import ssh
34 from kallithea.lib import ssh
@@ -88,3 +93,48 b' class SshKeyModel(object):'
88 user_ssh_keys = UserSshKeys.query() \
93 user_ssh_keys = UserSshKeys.query() \
89 .filter(UserSshKeys.user_id == user.user_id).all()
94 .filter(UserSshKeys.user_id == user.user_id).all()
90 return user_ssh_keys
95 return user_ssh_keys
96
97 def write_authorized_keys(self):
98 if not str2bool(config.get('ssh_enabled', False)):
99 log.error("Will not write SSH authorized_keys file - ssh_enabled is not configured")
100 return
101 authorized_keys = config.get('ssh_authorized_keys')
102 kallithea_cli_path = config.get('kallithea_cli_path', 'kallithea-cli')
103 if not authorized_keys:
104 log.error('Cannot write SSH authorized_keys file - ssh_authorized_keys is not configured')
105 return
106 log.info('Writing %s', authorized_keys)
107
108 authorized_keys_dir = os.path.dirname(authorized_keys)
109 try:
110 os.makedirs(authorized_keys_dir)
111 os.chmod(authorized_keys_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) # ~/.ssh/ must be 0700
112 except OSError as exception:
113 if exception.errno != errno.EEXIST:
114 raise
115 # Now, test that the directory is or was created in a readable way by previous.
116 if not (os.path.isdir(authorized_keys_dir) and
117 os.access(authorized_keys_dir, os.W_OK)):
118 raise Exception("Directory of authorized_keys cannot be written to so authorized_keys file %s cannot be written" % (authorized_keys))
119
120 # Make sure we don't overwrite a key file with important content
121 if os.path.exists(authorized_keys):
122 with open(authorized_keys) as f:
123 for l in f:
124 if not l.strip() or l.startswith('#'):
125 pass # accept empty lines and comments
126 elif ssh.SSH_OPTIONS in l and ' ssh-serve ' in l:
127 pass # Kallithea entries are ok to overwrite
128 else:
129 raise Exception("Safety check failed, found %r in %s - please review and remove it" % (l.strip(), authorized_keys))
130
131 fh, tmp_authorized_keys = tempfile.mkstemp('.authorized_keys', dir=os.path.dirname(authorized_keys))
132 with os.fdopen(fh, 'w') as f:
133 for key in UserSshKeys.query().join(UserSshKeys.user).filter(User.active == True):
134 f.write(ssh.authorized_keys_line(kallithea_cli_path, config['__file__'], key))
135 os.chmod(tmp_authorized_keys, stat.S_IRUSR | stat.S_IWUSR)
136 # This preliminary remove is needed for Windows, not for Unix.
137 # TODO In Python 3, the remove+rename sequence below should become os.replace.
138 if os.path.exists(authorized_keys):
139 os.remove(authorized_keys)
140 os.rename(tmp_authorized_keys, authorized_keys)
@@ -71,6 +71,7 b''
71 <li>Copyright &copy; 2016, timeless@gmail.com</li>
71 <li>Copyright &copy; 2016, timeless@gmail.com</li>
72 <li>Copyright &copy; 2016, YFdyh000</li>
72 <li>Copyright &copy; 2016, YFdyh000</li>
73 <li>Copyright &copy; 2012&ndash;2013, 2015, Aras Pranckevičius</li>
73 <li>Copyright &copy; 2012&ndash;2013, 2015, Aras Pranckevičius</li>
74 <li>Copyright &copy; 2014&ndash;2015, Bradley M. Kuhn</li>
74 <li>Copyright &copy; 2014&ndash;2015, Christian Oyarzun</li>
75 <li>Copyright &copy; 2014&ndash;2015, Christian Oyarzun</li>
75 <li>Copyright &copy; 2014&ndash;2015, Joseph Rivera</li>
76 <li>Copyright &copy; 2014&ndash;2015, Joseph Rivera</li>
76 <li>Copyright &copy; 2014&ndash;2015, Sean Farley</li>
77 <li>Copyright &copy; 2014&ndash;2015, Sean Farley</li>
@@ -101,7 +102,6 b''
101 <li>Copyright &copy; 2015, Tuux</li>
102 <li>Copyright &copy; 2015, Tuux</li>
102 <li>Copyright &copy; 2015, Viktar Palstsiuk</li>
103 <li>Copyright &copy; 2015, Viktar Palstsiuk</li>
103 <li>Copyright &copy; 2014, Ante Ilic</li>
104 <li>Copyright &copy; 2014, Ante Ilic</li>
104 <li>Copyright &copy; 2014, Bradley M. Kuhn</li>
105 <li>Copyright &copy; 2014, Calinou</li>
105 <li>Copyright &copy; 2014, Calinou</li>
106 <li>Copyright &copy; 2014, Daniel Anderson</li>
106 <li>Copyright &copy; 2014, Daniel Anderson</li>
107 <li>Copyright &copy; 2014, Henrik Stuart</li>
107 <li>Copyright &copy; 2014, Henrik Stuart</li>
@@ -43,6 +43,9 b' def pytest_configure():'
43 },
43 },
44 '[app:main]': {
44 '[app:main]': {
45 'ssh_enabled': 'true',
45 'ssh_enabled': 'true',
46 # Mainly to safeguard against accidentally overwriting the real one:
47 'ssh_authorized_keys': os.path.join(TESTS_TMP_PATH, 'authorized_keys'),
48 #'ssh_locale': 'C',
46 'app_instance_uuid': 'test',
49 'app_instance_uuid': 'test',
47 'show_revision_number': 'true',
50 'show_revision_number': 'true',
48 'beaker.cache.sql_cache_short.expire': '1',
51 'beaker.cache.sql_cache_short.expire': '1',
@@ -67,6 +67,7 b" no_about.add(('Sean Farley <sean.michael"
67 other = [
67 other = [
68 # Work folded into commits attributed to others:
68 # Work folded into commits attributed to others:
69 ('2013', 'Ilya Beda <ir4y.ix@gmail.com>'),
69 ('2013', 'Ilya Beda <ir4y.ix@gmail.com>'),
70 ('2015', 'Bradley M. Kuhn <bkuhn@sfconservancy.org>'),
70 ]
71 ]
71
72
72 # Preserve contributors listed in about.html but not appearing in repository
73 # Preserve contributors listed in about.html but not appearing in repository
General Comments 0
You need to be logged in to leave comments. Login now