From 0738287dc4096d5209f94d12e57552c2e155c26d Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Sep 30 2016 13:22:38 +0000 Subject: [PATCH 1/3] Check SSH keys before writing them out This is needed because Gitolite will abort all ACL and keyfile regeneration if there is a single invalid key in its keydir. Signed-off-by: Patrick Uiterwijk --- diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 8473bd1..0364bc5 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -25,6 +25,7 @@ import markdown import os import shutil import tempfile +import subprocess import urlparse import uuid @@ -181,6 +182,18 @@ def search_user(session, username=None, email=None, token=None, pattern=None): return output +def is_valid_ssh_key(key): + key = key.strip() + if not key: + return None + proc = subprocess.Popen(['/usr/bin/ssh-keygen', '-l', '-f', '-'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc.communicate(key) + return proc.returncode == 0 + + def create_user_ssh_keys_on_disk(user, gitolite_keydir): ''' Create the ssh keys for the user on the specific folder. @@ -211,6 +224,8 @@ def create_user_ssh_keys_on_disk(user, gitolite_keydir): for i in range(len(keys)): if not keys[i]: continue + if not is_valid_ssh_key(keys[i]): + continue keyline_dir = os.path.join(gitolite_keydir, 'keys_%i' % i) if not os.path.exists(keyline_dir): os.mkdir(keyline_dir) From f8d59053aaa1576773b94235d4c09c3e0339a181 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Sep 30 2016 13:22:38 +0000 Subject: [PATCH 2/3] Add a validator to check ssh keys on edit Signed-off-by: Patrick Uiterwijk --- diff --git a/pagure/forms.py b/pagure/forms.py index 219e425..344d74c 100644 --- a/pagure/forms.py +++ b/pagure/forms.py @@ -20,6 +20,7 @@ import wtforms import tempfile import pagure +import pagure.lib STRICT_REGEX = '^[a-zA-Z0-9-_]+$' @@ -68,6 +69,11 @@ def file_virus_validator(form, field): raise wtforms.ValidationError('Error scanning uploaded file') +def ssh_key_validator(form, field): + if not pagure.lib.are_valid_ssh_keys(field.data): + raise wtforms.ValidationError('Invalid SSH keys') + + class ProjectFormSimplified(wtf.Form): ''' Form to edit the description of a project. ''' description = wtforms.TextField( @@ -336,7 +342,8 @@ class UserSettingsForm(wtf.Form): ''' Form to create or edit project. ''' ssh_key = wtforms.TextAreaField( 'Public SSH key *', - [wtforms.validators.Required()] + [wtforms.validators.Required(), + ssh_key_validator] ) diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 0364bc5..57df7a1 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -194,6 +194,11 @@ def is_valid_ssh_key(key): return proc.returncode == 0 +def are_valid_ssh_keys(keys): + return all([is_valid_ssh_key(key) is not False + for key in keys.split('\n')]) + + def create_user_ssh_keys_on_disk(user, gitolite_keydir): ''' Create the ssh keys for the user on the specific folder. From f888fd9de0103687c30910f9ffdf42f55f8470ce Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Sep 30 2016 13:22:38 +0000 Subject: [PATCH 3/3] Add test case for SSH key checker Signed-off-by: Patrick Uiterwijk --- diff --git a/tests/test_pagure_flask_ui_app.py b/tests/test_pagure_flask_ui_app.py index eb08845..3293c99 100644 --- a/tests/test_pagure_flask_ui_app.py +++ b/tests/test_pagure_flask_ui_app.py @@ -395,7 +395,7 @@ class PagureFlaskApptests(tests.Modeltests): 'name="csrf_token" type="hidden" value="')[1].split('">')[0] data = { - 'ssh_key': 'this is my ssh key', + 'ssh_key': 'blah' } output = self.app.post('/settings/', data=data) @@ -403,24 +403,40 @@ class PagureFlaskApptests(tests.Modeltests): self.assertIn( '
\n Basic Information\n' '
', output.data) - self.assertIn( - '', output.data) data['csrf_token'] = csrf_token output = self.app.post( '/settings/', data=data, follow_redirects=True) self.assertEqual(output.status_code, 200) - self.assertTrue( - '\n Public ssh key updated' - in output.data) + self.assertIn('Invalid SSH keys', output.data) + self.assertIn( + '
\n Basic Information\n' + '
', output.data) + self.assertIn('>blah', output.data) + + csrf_token = output.data.split( + 'name="csrf_token" type="hidden" value="')[1].split('">')[0] + + data = { + 'ssh_key': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDUkub32fZnNI' + '1zJYs43vhhx3c6IcYo4yzhw1gQ37BLhrrNeS6x8l5PKX4J8ZP5' + '1XhViPaLbeOpl94Vm5VSCbLy0xtY9KwLhMkbKj7g6vvfxLm2sT' + 'Osb15j4jzIkUYYgIE7cHhZMCLWR6UA1c1HEzo6mewMDsvpQ9wk' + 'cDnAuXjK3Q==', + 'csrf_token': csrf_token + } + + output = self.app.post( + '/settings/', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn('Public ssh key updated', output.data) self.assertIn( '
\n Basic Information\n' '
', output.data) self.assertIn( '', output.data) + 'ssh-rsa AAAA', output.data) ast.return_value = True output = self.app.get('/settings/')