Add SSL cert and key validation
This commit is contained in:
parent
1255cb94ea
commit
400ffa73e6
5 changed files with 67 additions and 0 deletions
|
@ -41,3 +41,4 @@ git+https://github.com/DevTable/anunidecode.git
|
|||
git+https://github.com/DevTable/avatar-generator.git
|
||||
git+https://github.com/DevTable/pygithub.git
|
||||
gipc
|
||||
pyOpenSSL
|
||||
|
|
|
@ -44,6 +44,7 @@ python-dateutil==2.4.0
|
|||
python-ldap==2.4.19
|
||||
python-magic==0.4.6
|
||||
pytz==2014.10
|
||||
pyOpenSSL==0.14
|
||||
raven==5.1.1
|
||||
redis==2.10.3
|
||||
reportlab==2.7
|
||||
|
|
|
@ -601,6 +601,10 @@
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
.co-dialog .modal-footer.working .btn {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.co-dialog .modal-footer.working .cor-loader-inline {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import os
|
|||
import yaml
|
||||
import logging
|
||||
import json
|
||||
from StringIO import StringIO
|
||||
|
||||
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. """
|
||||
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):
|
||||
""" Saves the given flask file to the config override volume, with the given
|
||||
filename.
|
||||
|
@ -107,6 +112,9 @@ class FileConfigProvider(BaseProvider):
|
|||
def volume_file_exists(self, 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):
|
||||
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):
|
||||
self.files[filename] = ''
|
||||
|
||||
def get_volume_file(self, filename, mode='r'):
|
||||
return StringIO(self.files[filename])
|
||||
|
||||
def requires_restart(self, app_config):
|
||||
return False
|
||||
|
||||
|
|
|
@ -3,7 +3,10 @@ import os
|
|||
import json
|
||||
import ldap
|
||||
import peewee
|
||||
import OpenSSL
|
||||
import logging
|
||||
|
||||
from fnmatch import fnmatch
|
||||
from data.users import LDAPConnection
|
||||
from flask import Flask
|
||||
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 util.oauth import GoogleOAuthConfig, GithubOAuthConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SSL_FILENAMES = ['ssl.cert', 'ssl.key']
|
||||
|
||||
def get_storage_provider(config):
|
||||
|
@ -35,6 +40,7 @@ def validate_service_for_config(service, config):
|
|||
'status': True
|
||||
}
|
||||
except Exception as ex:
|
||||
logger.exception('Validation exception')
|
||||
return {
|
||||
'status': False,
|
||||
'reason': str(ex)
|
||||
|
@ -150,6 +156,50 @@ def _validate_ssl(config):
|
|||
if not CONFIG_PROVIDER.volume_file_exists(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):
|
||||
""" Validates the LDAP connection. """
|
||||
|
|
Reference in a new issue