import redis 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 from data.database import validate_database_url, User from storage import get_storage_driver 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): parameters = config.get('DISTRIBUTED_STORAGE_CONFIG', {}).get('local', ['LocalStorage', {}]) try: return get_storage_driver(parameters) except TypeError: raise Exception('Missing required storage configuration parameter(s)') def validate_service_for_config(service, config): """ Attempts to validate the configuration for the given service. """ if not service in _VALIDATORS: return { 'status': False } try: _VALIDATORS[service](config) return { 'status': True } except Exception as ex: logger.exception('Validation exception') return { 'status': False, 'reason': str(ex) } def _validate_database(config): """ Validates connecting to the database. """ try: validate_database_url(config['DB_URI']) except peewee.OperationalError as ex: if ex.args and len(ex.args) > 1: raise Exception(ex.args[1]) else: raise ex def _validate_redis(config): """ Validates connecting to redis. """ redis_config = config.get('BUILDLOGS_REDIS', {}) if not 'host' in redis_config: raise Exception('Missing redis hostname') client = redis.StrictRedis(socket_connect_timeout=5, **redis_config) client.ping() def _validate_registry_storage(config): """ Validates registry storage. """ driver = get_storage_provider(config) # Put and remove a temporary file. driver.put_content('_verify', 'testing 123') driver.remove('_verify') # Run setup on the driver if the read/write succeeded. try: driver.setup() except Exception as ex: raise Exception('Could not prepare storage: %s' % str(ex)) def _validate_mailing(config): """ Validates sending email. """ test_app = Flask("mail-test-app") test_app.config.update(config) test_app.config.update({ 'MAIL_FAIL_SILENTLY': False, 'TESTING': False }) test_mail = Mail(test_app) test_msg = Message("Test e-mail from %s" % app.config['REGISTRY_TITLE']) test_msg.add_recipient(get_authenticated_user().email) test_mail.send(test_msg) def _validate_github(config_key): return lambda config: _validate_github_with_key(config_key, config) def _validate_github_with_key(config_key, config): """ Validates the OAuth credentials and API endpoint for a Github service. """ github_config = config.get(config_key) if not github_config: raise Exception('Missing Github client id and client secret') endpoint = github_config.get('GITHUB_ENDPOINT') if not endpoint: raise Exception('Missing Github Endpoint') if endpoint.find('http://') != 0 and endpoint.find('https://') != 0: raise Exception('Github Endpoint must start with http:// or https://') if not github_config.get('CLIENT_ID'): raise Exception('Missing Client ID') if not github_config.get('CLIENT_SECRET'): raise Exception('Missing Client Secret') client = app.config['HTTPCLIENT'] oauth = GithubOAuthConfig(config, config_key) result = oauth.validate_client_id_and_secret(client) if not result: raise Exception('Invalid client id or client secret') def _validate_google_login(config): """ Validates the Google Login client ID and secret. """ google_login_config = config.get('GOOGLE_LOGIN_CONFIG') if not google_login_config: raise Exception('Missing client ID and client secret') if not google_login_config.get('CLIENT_ID'): raise Exception('Missing Client ID') if not google_login_config.get('CLIENT_SECRET'): raise Exception('Missing Client Secret') client = app.config['HTTPCLIENT'] oauth = GoogleOAuthConfig(config, 'GOOGLE_LOGIN_CONFIG') result = oauth.validate_client_id_and_secret(client) if not result: raise Exception('Invalid client id or client secret') def _validate_ssl(config): """ Validates the SSL configuration (if enabled). """ if config.get('PREFERRED_URL_SCHEME', 'http') != 'https': return for filename in SSL_FILENAMES: 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. """ if config.get('AUTHENTICATION_TYPE', 'Database') != 'LDAP': return # Note: raises ldap.INVALID_CREDENTIALS on failure admin_dn = config.get('LDAP_ADMIN_DN') admin_passwd = config.get('LDAP_ADMIN_PASSWD') if not admin_dn: raise Exception('Missing Admin DN for LDAP configuration') if not admin_passwd: raise Exception('Missing Admin Password for LDAP configuration') ldap_uri = config.get('LDAP_URI', 'ldap://localhost') try: with LDAPConnection(ldap_uri, admin_dn, admin_passwd): pass except ldap.LDAPError as ex: values = ex.args[0] if ex.args else {} raise Exception(values.get('desc', 'Unknown error')) _VALIDATORS = { 'database': _validate_database, 'redis': _validate_redis, 'registry-storage': _validate_registry_storage, 'mail': _validate_mailing, 'github-login': _validate_github('GITHUB_LOGIN_CONFIG'), 'github-trigger': _validate_github('GITHUB_TRIGGER_CONFIG'), 'google-login': _validate_google_login, 'ssl': _validate_ssl, 'ldap': _validate_ldap, }