Add SSL cert and key validation

This commit is contained in:
Joseph Schorr 2015-02-05 13:06:56 -05:00
parent 1255cb94ea
commit 400ffa73e6
5 changed files with 67 additions and 0 deletions

View file

@ -41,3 +41,4 @@ git+https://github.com/DevTable/anunidecode.git
git+https://github.com/DevTable/avatar-generator.git git+https://github.com/DevTable/avatar-generator.git
git+https://github.com/DevTable/pygithub.git git+https://github.com/DevTable/pygithub.git
gipc gipc
pyOpenSSL

View file

@ -44,6 +44,7 @@ python-dateutil==2.4.0
python-ldap==2.4.19 python-ldap==2.4.19
python-magic==0.4.6 python-magic==0.4.6
pytz==2014.10 pytz==2014.10
pyOpenSSL==0.14
raven==5.1.1 raven==5.1.1
redis==2.10.3 redis==2.10.3
reportlab==2.7 reportlab==2.7

View file

@ -601,6 +601,10 @@
text-align: left; text-align: left;
} }
.co-dialog .modal-footer.working .btn {
float: right;
}
.co-dialog .modal-footer.working .cor-loader-inline { .co-dialog .modal-footer.working .cor-loader-inline {
margin-right: 10px; margin-right: 10px;
} }

View file

@ -2,6 +2,7 @@ import os
import yaml import yaml
import logging import logging
import json import json
from StringIO import StringIO
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -55,6 +56,10 @@ class BaseProvider(object):
""" Returns whether the file with the given name exists under the config override volume. """ """ Returns whether the file with the given name exists under the config override volume. """
raise NotImplementedError raise NotImplementedError
def get_volume_file(self, filename, mode='r'):
""" Returns a Python file referring to the given name under the config override volumne. """
raise NotImplementedError
def save_volume_file(self, filename, flask_file): def save_volume_file(self, filename, flask_file):
""" Saves the given flask file to the config override volume, with the given """ Saves the given flask file to the config override volume, with the given
filename. filename.
@ -107,6 +112,9 @@ class FileConfigProvider(BaseProvider):
def volume_file_exists(self, filename): def volume_file_exists(self, filename):
return os.path.exists(os.path.join(self.config_volume, filename)) return os.path.exists(os.path.join(self.config_volume, filename))
def get_volume_file(self, filename, mode='r'):
return open(os.path.join(self.config_volume, filename), mode)
def save_volume_file(self, filename, flask_file): def save_volume_file(self, filename, flask_file):
flask_file.save(os.path.join(self.config_volume, filename)) flask_file.save(os.path.join(self.config_volume, filename))
@ -152,6 +160,9 @@ class TestConfigProvider(BaseProvider):
def save_volume_file(self, filename, flask_file): def save_volume_file(self, filename, flask_file):
self.files[filename] = '' self.files[filename] = ''
def get_volume_file(self, filename, mode='r'):
return StringIO(self.files[filename])
def requires_restart(self, app_config): def requires_restart(self, app_config):
return False return False

View file

@ -3,7 +3,10 @@ import os
import json import json
import ldap import ldap
import peewee import peewee
import OpenSSL
import logging
from fnmatch import fnmatch
from data.users import LDAPConnection from data.users import LDAPConnection
from flask import Flask from flask import Flask
from flask.ext.mail import Mail, Message from flask.ext.mail import Mail, Message
@ -13,6 +16,8 @@ from app import app, CONFIG_PROVIDER
from auth.auth_context import get_authenticated_user from auth.auth_context import get_authenticated_user
from util.oauth import GoogleOAuthConfig, GithubOAuthConfig from util.oauth import GoogleOAuthConfig, GithubOAuthConfig
logger = logging.getLogger(__name__)
SSL_FILENAMES = ['ssl.cert', 'ssl.key'] SSL_FILENAMES = ['ssl.cert', 'ssl.key']
def get_storage_provider(config): def get_storage_provider(config):
@ -35,6 +40,7 @@ def validate_service_for_config(service, config):
'status': True 'status': True
} }
except Exception as ex: except Exception as ex:
logger.exception('Validation exception')
return { return {
'status': False, 'status': False,
'reason': str(ex) 'reason': str(ex)
@ -150,6 +156,50 @@ def _validate_ssl(config):
if not CONFIG_PROVIDER.volume_file_exists(filename): if not CONFIG_PROVIDER.volume_file_exists(filename):
raise Exception('Missing required SSL file: %s' % filename) raise Exception('Missing required SSL file: %s' % filename)
with CONFIG_PROVIDER.get_volume_file(SSL_FILENAMES[0]) as f:
cert_contents = f.read()
# Validate the certificate.
try:
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_contents)
except:
raise Exception('Could not parse certificate file. Is it a valid PEM certificate?')
if cert.has_expired():
raise Exception('The specified SSL certificate has expired.')
private_key_path = None
with CONFIG_PROVIDER.get_volume_file(SSL_FILENAMES[1]) as f:
private_key_path = f.name
if not private_key_path:
# Only in testing.
return
# Validate the private key with the certificate.
context = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
context.use_certificate(cert)
try:
context.use_privatekey_file(private_key_path)
except:
raise Exception('Could not parse key file. Is it a valid PEM private key?')
try:
context.check_privatekey()
except OpenSSL.SSL.Error as e:
raise Exception('SSL key failed to validate: %s' % str(e))
# Verify the hostname matches the name in the certificate.
common_name = cert.get_subject().commonName
if common_name is None:
raise Exception('Missing CommonName (CN) from SSL certificate')
if not fnmatch(config['SERVER_HOSTNAME'], common_name):
raise Exception('CommonName (CN) "%s" in SSL cert does not match server hostname "%s"' %
(common_name, config['SERVER_HOSTNAME']))
def _validate_ldap(config): def _validate_ldap(config):
""" Validates the LDAP connection. """ """ Validates the LDAP connection. """