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 © 2016, timeless@gmail.com</li> |
|
71 | <li>Copyright © 2016, timeless@gmail.com</li> | |
72 | <li>Copyright © 2016, YFdyh000</li> |
|
72 | <li>Copyright © 2016, YFdyh000</li> | |
73 | <li>Copyright © 2012–2013, 2015, Aras Pranckevičius</li> |
|
73 | <li>Copyright © 2012–2013, 2015, Aras Pranckevičius</li> | |
|
74 | <li>Copyright © 2014–2015, Bradley M. Kuhn</li> | |||
74 | <li>Copyright © 2014–2015, Christian Oyarzun</li> |
|
75 | <li>Copyright © 2014–2015, Christian Oyarzun</li> | |
75 | <li>Copyright © 2014–2015, Joseph Rivera</li> |
|
76 | <li>Copyright © 2014–2015, Joseph Rivera</li> | |
76 | <li>Copyright © 2014–2015, Sean Farley</li> |
|
77 | <li>Copyright © 2014–2015, Sean Farley</li> | |
@@ -101,7 +102,6 b'' | |||||
101 | <li>Copyright © 2015, Tuux</li> |
|
102 | <li>Copyright © 2015, Tuux</li> | |
102 | <li>Copyright © 2015, Viktar Palstsiuk</li> |
|
103 | <li>Copyright © 2015, Viktar Palstsiuk</li> | |
103 | <li>Copyright © 2014, Ante Ilic</li> |
|
104 | <li>Copyright © 2014, Ante Ilic</li> | |
104 | <li>Copyright © 2014, Bradley M. Kuhn</li> |
|
|||
105 | <li>Copyright © 2014, Calinou</li> |
|
105 | <li>Copyright © 2014, Calinou</li> | |
106 | <li>Copyright © 2014, Daniel Anderson</li> |
|
106 | <li>Copyright © 2014, Daniel Anderson</li> | |
107 | <li>Copyright © 2014, Henrik Stuart</li> |
|
107 | <li>Copyright © 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