2015-02-05 18:06:56 +00:00
|
|
|
import logging
|
2016-06-09 18:47:08 +00:00
|
|
|
import subprocess
|
2016-09-29 00:17:14 +00:00
|
|
|
import time
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2016-02-16 20:31:23 +00:00
|
|
|
from StringIO import StringIO
|
2015-02-05 18:06:56 +00:00
|
|
|
from fnmatch import fnmatch
|
2016-12-09 22:36:57 +00:00
|
|
|
from hashlib import sha1
|
2016-09-29 00:17:14 +00:00
|
|
|
|
|
|
|
import OpenSSL
|
|
|
|
import ldap
|
|
|
|
import peewee
|
|
|
|
import redis
|
2015-07-20 15:39:59 +00:00
|
|
|
|
2015-01-07 21:20:51 +00:00
|
|
|
from flask import Flask
|
2016-09-29 00:17:14 +00:00
|
|
|
from flask_mail import Mail, Message
|
|
|
|
|
|
|
|
from app import app, config_provider, get_app_url, OVERRIDE_CONFIG_DIRECTORY
|
|
|
|
from auth.auth_context import get_authenticated_user
|
|
|
|
from bitbucket import BitBucket
|
|
|
|
from boot import setup_jwt_proxy
|
2016-05-02 19:29:31 +00:00
|
|
|
from data.database import validate_database_url
|
2016-09-29 00:17:14 +00:00
|
|
|
from data.users import LDAP_CERT_FILENAME
|
|
|
|
from data.users.externaljwt import ExternalJWTAuthN
|
|
|
|
from data.users.externalldap import LDAPConnection, LDAPUsers
|
2016-10-27 19:35:52 +00:00
|
|
|
from data.users.keystone import get_keystone_users
|
2015-01-07 21:20:51 +00:00
|
|
|
from storage import get_storage_driver
|
2015-08-03 19:49:10 +00:00
|
|
|
from util.config.oauth import GoogleOAuthConfig, GithubOAuthConfig, GitLabOAuthConfig
|
2016-05-02 19:29:31 +00:00
|
|
|
from util.secscan.api import SecurityScannerAPI
|
2016-09-26 09:13:59 +00:00
|
|
|
from util.registry.torrent import torrent_jwt
|
2016-09-29 00:17:14 +00:00
|
|
|
from util.security.signing import SIGNING_ENGINES
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2015-02-05 18:06:56 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
class ConfigValidationException(Exception):
|
|
|
|
""" Exception raised when the configuration fails to validate for a known reason. """
|
|
|
|
pass
|
|
|
|
|
2016-09-29 00:17:14 +00:00
|
|
|
|
2015-07-15 21:49:07 +00:00
|
|
|
# Note: Only add files required for HTTPS to the SSL_FILESNAMES list.
|
|
|
|
SSL_FILENAMES = ['ssl.cert', 'ssl.key']
|
|
|
|
DB_SSL_FILENAMES = ['database.pem']
|
2015-06-02 22:19:22 +00:00
|
|
|
JWT_FILENAMES = ['jwt-authn.cert']
|
2016-02-16 20:31:23 +00:00
|
|
|
ACI_CERT_FILENAMES = ['signing-public.gpg', 'signing-private.gpg']
|
2016-05-03 19:02:39 +00:00
|
|
|
LDAP_FILENAMES = [LDAP_CERT_FILENAME]
|
|
|
|
CONFIG_FILENAMES = (SSL_FILENAMES + DB_SSL_FILENAMES + JWT_FILENAMES + ACI_CERT_FILENAMES +
|
|
|
|
LDAP_FILENAMES)
|
2015-07-15 21:49:07 +00:00
|
|
|
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2015-10-26 23:06:05 +00:00
|
|
|
def get_storage_providers(config):
|
|
|
|
storage_config = config.get('DISTRIBUTED_STORAGE_CONFIG', {})
|
|
|
|
|
|
|
|
drivers = {}
|
|
|
|
|
2015-01-16 21:10:40 +00:00
|
|
|
try:
|
2015-10-26 23:06:05 +00:00
|
|
|
for name, parameters in storage_config.items():
|
2016-11-29 20:20:46 +00:00
|
|
|
drivers[name] = (parameters[0], get_storage_driver(None, None, None, parameters))
|
2016-12-09 22:36:57 +00:00
|
|
|
except TypeError:
|
2016-01-29 18:01:17 +00:00
|
|
|
logger.exception('Missing required storage configuration provider')
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing required parameter(s) for storage %s' % name)
|
2015-10-26 23:06:05 +00:00
|
|
|
|
|
|
|
return drivers
|
2015-01-16 21:10:40 +00:00
|
|
|
|
2015-06-22 22:17:37 +00:00
|
|
|
def validate_service_for_config(service, config, password=None):
|
2015-01-07 21:20:51 +00:00
|
|
|
""" Attempts to validate the configuration for the given service. """
|
2016-11-29 20:20:46 +00:00
|
|
|
if not service in VALIDATORS:
|
2015-01-07 21:20:51 +00:00
|
|
|
return {
|
|
|
|
'status': False
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
2016-11-29 20:20:46 +00:00
|
|
|
VALIDATORS[service](config, get_authenticated_user(), password)
|
2015-01-07 21:20:51 +00:00
|
|
|
return {
|
|
|
|
'status': True
|
|
|
|
}
|
|
|
|
except Exception as ex:
|
2015-02-05 18:06:56 +00:00
|
|
|
logger.exception('Validation exception')
|
2015-01-07 21:20:51 +00:00
|
|
|
return {
|
|
|
|
'status': False,
|
|
|
|
'reason': str(ex)
|
|
|
|
}
|
|
|
|
|
2015-01-08 18:26:24 +00:00
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_database(config, user_obj, _):
|
2015-01-07 21:20:51 +00:00
|
|
|
""" Validates connecting to the database. """
|
2015-01-08 20:27:49 +00:00
|
|
|
try:
|
2015-06-29 05:08:10 +00:00
|
|
|
validate_database_url(config['DB_URI'], config.get('DB_CONNECTION_ARGS', {}))
|
2015-01-08 20:27:49 +00:00
|
|
|
except peewee.OperationalError as ex:
|
|
|
|
if ex.args and len(ex.args) > 1:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException(ex.args[1])
|
2015-01-08 20:27:49 +00:00
|
|
|
else:
|
|
|
|
raise ex
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2015-01-08 18:26:24 +00:00
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_redis(config, user_obj, _):
|
2015-01-07 21:20:51 +00:00
|
|
|
""" Validates connecting to redis. """
|
2015-01-08 20:27:49 +00:00
|
|
|
redis_config = config.get('BUILDLOGS_REDIS', {})
|
|
|
|
if not 'host' in redis_config:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing redis hostname')
|
2015-01-08 20:27:49 +00:00
|
|
|
|
2015-01-07 21:20:51 +00:00
|
|
|
client = redis.StrictRedis(socket_connect_timeout=5, **redis_config)
|
|
|
|
client.ping()
|
|
|
|
|
2015-01-08 18:26:24 +00:00
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_registry_storage(config, user_obj, _):
|
2015-01-07 21:20:51 +00:00
|
|
|
""" Validates registry storage. """
|
2015-10-26 23:06:05 +00:00
|
|
|
replication_enabled = config.get('FEATURE_STORAGE_REPLICATION', False)
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2015-10-26 23:06:05 +00:00
|
|
|
providers = get_storage_providers(config).items()
|
|
|
|
if not providers:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Storage configuration required')
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2015-10-26 23:06:05 +00:00
|
|
|
for name, (storage_type, driver) in providers:
|
|
|
|
try:
|
|
|
|
if replication_enabled and storage_type == 'LocalStorage':
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Locally mounted directory not supported ' +
|
|
|
|
'with storage replication')
|
2015-10-26 23:06:05 +00:00
|
|
|
|
2016-08-01 21:07:33 +00:00
|
|
|
# Run validation on the driver.
|
2015-10-26 23:06:05 +00:00
|
|
|
driver.validate(app.config['HTTPCLIENT'])
|
|
|
|
|
|
|
|
# Run setup on the driver if the read/write succeeded.
|
|
|
|
driver.setup()
|
|
|
|
except Exception as ex:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Invalid storage configuration: %s: %s' % (name, str(ex)))
|
2015-01-16 21:10:40 +00:00
|
|
|
|
2015-01-08 18:26:24 +00:00
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_mailing(config, user_obj, _):
|
2015-01-07 21:20:51 +00:00
|
|
|
""" 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)
|
2015-03-03 18:56:32 +00:00
|
|
|
test_msg = Message("Test e-mail from %s" % app.config['REGISTRY_TITLE'],
|
|
|
|
sender=config.get('MAIL_DEFAULT_SENDER'))
|
2016-11-29 20:20:46 +00:00
|
|
|
test_msg.add_recipient(user_obj.email)
|
2015-01-07 21:20:51 +00:00
|
|
|
test_mail.send(test_msg)
|
|
|
|
|
2015-01-08 18:26:24 +00:00
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_gitlab(config, user_obj, _):
|
2015-05-03 18:50:26 +00:00
|
|
|
""" Validates the OAuth credentials and API endpoint for a GitLab service. """
|
|
|
|
github_config = config.get('GITLAB_TRIGGER_CONFIG')
|
|
|
|
if not github_config:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing GitLab client id and client secret')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
endpoint = github_config.get('GITLAB_ENDPOINT')
|
|
|
|
if not endpoint:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing GitLab Endpoint')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
if endpoint.find('http://') != 0 and endpoint.find('https://') != 0:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('GitLab Endpoint must start with http:// or https://')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
if not github_config.get('CLIENT_ID'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Client ID')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
if not github_config.get('CLIENT_SECRET'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Client Secret')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
client = app.config['HTTPCLIENT']
|
|
|
|
oauth = GitLabOAuthConfig(config, 'GITLAB_TRIGGER_CONFIG')
|
|
|
|
result = oauth.validate_client_id_and_secret(client, app.config)
|
|
|
|
if not result:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Invalid client id or client secret')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
|
2015-01-08 18:26:24 +00:00
|
|
|
def _validate_github(config_key):
|
2016-11-29 20:20:46 +00:00
|
|
|
return lambda config, user_obj, _: _validate_github_with_key(config_key, config)
|
2015-01-08 18:26:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _validate_github_with_key(config_key, config):
|
|
|
|
""" Validates the OAuth credentials and API endpoint for a Github service. """
|
2015-01-08 18:56:17 +00:00
|
|
|
github_config = config.get(config_key)
|
|
|
|
if not github_config:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing GitHub client id and client secret')
|
2015-01-08 18:56:17 +00:00
|
|
|
|
|
|
|
endpoint = github_config.get('GITHUB_ENDPOINT')
|
2015-01-08 18:26:24 +00:00
|
|
|
if not endpoint:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing GitHub Endpoint')
|
2015-01-08 18:26:24 +00:00
|
|
|
|
|
|
|
if endpoint.find('http://') != 0 and endpoint.find('https://') != 0:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Github Endpoint must start with http:// or https://')
|
2015-01-08 18:26:24 +00:00
|
|
|
|
2015-01-08 18:56:17 +00:00
|
|
|
if not github_config.get('CLIENT_ID'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Client ID')
|
2015-01-08 18:26:24 +00:00
|
|
|
|
2015-01-08 18:56:17 +00:00
|
|
|
if not github_config.get('CLIENT_SECRET'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Client Secret')
|
2015-01-08 18:26:24 +00:00
|
|
|
|
2015-03-04 00:58:42 +00:00
|
|
|
if github_config.get('ORG_RESTRICT') and not github_config.get('ALLOWED_ORGANIZATIONS'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Organization restriction must have at least one allowed ' +
|
|
|
|
'organization')
|
2015-03-04 00:58:42 +00:00
|
|
|
|
2015-01-07 21:20:51 +00:00
|
|
|
client = app.config['HTTPCLIENT']
|
2015-01-08 18:26:24 +00:00
|
|
|
oauth = GithubOAuthConfig(config, config_key)
|
2015-05-03 18:50:26 +00:00
|
|
|
result = oauth.validate_client_id_and_secret(client, app.config)
|
2015-01-08 18:26:24 +00:00
|
|
|
if not result:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Invalid client id or client secret')
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2015-03-04 00:58:42 +00:00
|
|
|
if github_config.get('ALLOWED_ORGANIZATIONS'):
|
|
|
|
for org_id in github_config.get('ALLOWED_ORGANIZATIONS'):
|
|
|
|
if not oauth.validate_organization(org_id, client):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Invalid organization: %s' % org_id)
|
2015-03-04 00:58:42 +00:00
|
|
|
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_bitbucket(config, user_obj, _):
|
2015-05-03 18:50:26 +00:00
|
|
|
""" Validates the config for BitBucket. """
|
|
|
|
trigger_config = config.get('BITBUCKET_TRIGGER_CONFIG')
|
|
|
|
if not trigger_config:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing client ID and client secret')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
if not trigger_config.get('CONSUMER_KEY'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Consumer Key')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
if not trigger_config.get('CONSUMER_SECRET'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Consumer Secret')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
key = trigger_config['CONSUMER_KEY']
|
|
|
|
secret = trigger_config['CONSUMER_SECRET']
|
|
|
|
callback_url = '%s/oauth1/bitbucket/callback/trigger/' % (get_app_url())
|
|
|
|
|
|
|
|
bitbucket_client = BitBucket(key, secret, callback_url)
|
|
|
|
(result, _, _) = bitbucket_client.get_authorization_url()
|
|
|
|
if not result:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Invalid consumer key or secret')
|
2015-05-03 18:50:26 +00:00
|
|
|
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_google_login(config, user_obj, _):
|
2015-01-08 18:56:17 +00:00
|
|
|
""" Validates the Google Login client ID and secret. """
|
|
|
|
google_login_config = config.get('GOOGLE_LOGIN_CONFIG')
|
|
|
|
if not google_login_config:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing client ID and client secret')
|
2015-01-08 18:56:17 +00:00
|
|
|
|
|
|
|
if not google_login_config.get('CLIENT_ID'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Client ID')
|
2015-01-08 18:56:17 +00:00
|
|
|
|
|
|
|
if not google_login_config.get('CLIENT_SECRET'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Client Secret')
|
2015-01-08 18:56:17 +00:00
|
|
|
|
|
|
|
client = app.config['HTTPCLIENT']
|
|
|
|
oauth = GoogleOAuthConfig(config, 'GOOGLE_LOGIN_CONFIG')
|
2015-05-03 18:50:26 +00:00
|
|
|
result = oauth.validate_client_id_and_secret(client, app.config)
|
2015-01-08 18:56:17 +00:00
|
|
|
if not result:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Invalid client id or client secret')
|
2015-01-08 18:56:17 +00:00
|
|
|
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_ssl(config, user_obj, _):
|
2015-01-07 21:20:51 +00:00
|
|
|
""" Validates the SSL configuration (if enabled). """
|
2016-10-13 18:49:29 +00:00
|
|
|
|
|
|
|
# Skip if non-SSL.
|
2015-01-07 21:20:51 +00:00
|
|
|
if config.get('PREFERRED_URL_SCHEME', 'http') != 'https':
|
|
|
|
return
|
|
|
|
|
2016-10-13 18:49:29 +00:00
|
|
|
# Skip if externally terminated.
|
2016-12-09 22:36:57 +00:00
|
|
|
if config.get('EXTERNAL_TLS_TERMINATION', False) is True:
|
2016-10-13 18:49:29 +00:00
|
|
|
return
|
|
|
|
|
2015-01-07 21:20:51 +00:00
|
|
|
for filename in SSL_FILENAMES:
|
2015-07-24 18:52:19 +00:00
|
|
|
if not config_provider.volume_file_exists(filename):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing required SSL file: %s' % filename)
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2015-07-24 18:52:19 +00:00
|
|
|
with config_provider.get_volume_file(SSL_FILENAMES[0]) as f:
|
2015-02-05 18:06:56 +00:00
|
|
|
cert_contents = f.read()
|
|
|
|
|
|
|
|
# Validate the certificate.
|
|
|
|
try:
|
|
|
|
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_contents)
|
|
|
|
except:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Could not parse certificate file. Is it a valid PEM certificate?')
|
2015-02-05 18:06:56 +00:00
|
|
|
|
|
|
|
if cert.has_expired():
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('The specified SSL certificate has expired.')
|
2015-02-05 18:06:56 +00:00
|
|
|
|
|
|
|
private_key_path = None
|
2015-07-24 18:52:19 +00:00
|
|
|
with config_provider.get_volume_file(SSL_FILENAMES[1]) as f:
|
2015-02-05 18:06:56 +00:00
|
|
|
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:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Could not parse key file. Is it a valid PEM private key?')
|
2015-02-05 18:06:56 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
context.check_privatekey()
|
|
|
|
except OpenSSL.SSL.Error as e:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('SSL key failed to validate: %s' % str(e))
|
2015-02-05 18:06:56 +00:00
|
|
|
|
|
|
|
# Verify the hostname matches the name in the certificate.
|
|
|
|
common_name = cert.get_subject().commonName
|
|
|
|
if common_name is None:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing CommonName (CN) from SSL certificate')
|
2015-02-05 18:06:56 +00:00
|
|
|
|
2015-02-12 19:00:26 +00:00
|
|
|
# Build the list of allowed host patterns.
|
|
|
|
hosts = set([common_name])
|
|
|
|
|
|
|
|
# Find the DNS extension, if any.
|
|
|
|
for i in range(0, cert.get_extension_count()):
|
|
|
|
ext = cert.get_extension(i)
|
|
|
|
if ext.get_short_name() == 'subjectAltName':
|
|
|
|
value = str(ext)
|
|
|
|
hosts.update([host.strip()[4:] for host in value.split(',')])
|
|
|
|
|
|
|
|
# Check each host.
|
|
|
|
for host in hosts:
|
|
|
|
if fnmatch(config['SERVER_HOSTNAME'], host):
|
|
|
|
return
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
msg = ('Supported names "%s" in SSL cert do not match server hostname "%s"' %
|
|
|
|
(', '.join(list(hosts)), config['SERVER_HOSTNAME']))
|
|
|
|
raise ConfigValidationException(msg)
|
2015-02-05 18:06:56 +00:00
|
|
|
|
|
|
|
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_ldap(config, user_obj, password):
|
2015-01-07 21:20:51 +00:00
|
|
|
""" Validates the LDAP connection. """
|
|
|
|
if config.get('AUTHENTICATION_TYPE', 'Database') != 'LDAP':
|
|
|
|
return
|
|
|
|
|
2016-06-09 18:47:08 +00:00
|
|
|
# If there is a custom LDAP certificate, then reinstall the certificates for the container.
|
|
|
|
if config_provider.volume_file_exists(LDAP_CERT_FILENAME):
|
2016-09-19 21:55:08 +00:00
|
|
|
subprocess.check_call(['/conf/init/certs_install.sh'])
|
2016-06-09 18:47:08 +00:00
|
|
|
|
2015-01-07 21:20:51 +00:00
|
|
|
# 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:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Admin DN for LDAP configuration')
|
2015-01-07 21:20:51 +00:00
|
|
|
|
|
|
|
if not admin_passwd:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing Admin Password for LDAP configuration')
|
2015-01-07 21:20:51 +00:00
|
|
|
|
|
|
|
ldap_uri = config.get('LDAP_URI', 'ldap://localhost')
|
2015-03-16 18:33:53 +00:00
|
|
|
if not ldap_uri.startswith('ldap://') and not ldap_uri.startswith('ldaps://'):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('LDAP URI must start with ldap:// or ldaps://')
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2016-05-03 19:02:39 +00:00
|
|
|
allow_tls_fallback = config.get('LDAP_ALLOW_INSECURE_FALLBACK', False)
|
|
|
|
|
2015-01-07 21:20:51 +00:00
|
|
|
try:
|
2016-06-09 18:47:08 +00:00
|
|
|
with LDAPConnection(ldap_uri, admin_dn, admin_passwd, allow_tls_fallback):
|
2015-01-07 21:20:51 +00:00
|
|
|
pass
|
|
|
|
except ldap.LDAPError as ex:
|
|
|
|
values = ex.args[0] if ex.args else {}
|
2015-03-16 18:33:53 +00:00
|
|
|
if not isinstance(values, dict):
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException(str(ex.args))
|
2015-03-16 18:33:53 +00:00
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException(values.get('desc', 'Unknown error'))
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2015-06-22 22:17:37 +00:00
|
|
|
# Verify that the superuser exists. If not, raise an exception.
|
|
|
|
base_dn = config.get('LDAP_BASE_DN')
|
|
|
|
user_rdn = config.get('LDAP_USER_RDN', [])
|
|
|
|
uid_attr = config.get('LDAP_UID_ATTR', 'uid')
|
|
|
|
email_attr = config.get('LDAP_EMAIL_ATTR', 'mail')
|
2016-09-08 16:24:47 +00:00
|
|
|
requires_email = config.get('FEATURE_MAILING', True)
|
2015-06-22 22:17:37 +00:00
|
|
|
|
2016-05-03 19:02:39 +00:00
|
|
|
users = LDAPUsers(ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr, email_attr,
|
2016-09-08 16:24:47 +00:00
|
|
|
allow_tls_fallback, requires_email=requires_email)
|
2015-01-07 21:20:51 +00:00
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
username = user_obj.username
|
2015-07-20 15:39:59 +00:00
|
|
|
(result, err_msg) = users.verify_credentials(username, password)
|
2015-06-22 22:17:37 +00:00
|
|
|
if not result:
|
2016-11-29 20:20:46 +00:00
|
|
|
msg = ('Verification of superuser %s failed: %s. \n\nThe user either does not exist ' +
|
|
|
|
'in the remote authentication system ' +
|
|
|
|
'OR LDAP auth is misconfigured.') % (username, err_msg)
|
|
|
|
raise ConfigValidationException(msg)
|
2015-06-22 22:17:37 +00:00
|
|
|
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_jwt(config, user_obj, password):
|
2015-06-02 22:19:22 +00:00
|
|
|
""" Validates the JWT authentication system. """
|
|
|
|
if config.get('AUTHENTICATION_TYPE', 'Database') != 'JWT':
|
|
|
|
return
|
|
|
|
|
|
|
|
verify_endpoint = config.get('JWT_VERIFY_ENDPOINT')
|
2016-10-27 19:34:03 +00:00
|
|
|
query_endpoint = config.get('JWT_QUERY_ENDPOINT', None)
|
|
|
|
getuser_endpoint = config.get('JWT_GETUSER_ENDPOINT', None)
|
|
|
|
|
2015-06-02 22:19:22 +00:00
|
|
|
issuer = config.get('JWT_AUTH_ISSUER')
|
|
|
|
|
|
|
|
if not verify_endpoint:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing JWT Verification endpoint')
|
2015-06-02 22:19:22 +00:00
|
|
|
|
|
|
|
if not issuer:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing JWT Issuer ID')
|
2015-06-02 22:19:22 +00:00
|
|
|
|
|
|
|
# Try to instatiate the JWT authentication mechanism. This will raise an exception if
|
|
|
|
# the key cannot be found.
|
2016-10-27 19:34:03 +00:00
|
|
|
users = ExternalJWTAuthN(verify_endpoint, query_endpoint, getuser_endpoint, issuer,
|
|
|
|
OVERRIDE_CONFIG_DIRECTORY,
|
2015-07-20 17:18:07 +00:00
|
|
|
app.config['HTTPCLIENT'],
|
2016-09-08 16:24:47 +00:00
|
|
|
app.config.get('JWT_AUTH_MAX_FRESH_S', 300),
|
|
|
|
requires_email=config.get('FEATURE_MAILING', True))
|
2015-06-02 22:19:22 +00:00
|
|
|
|
|
|
|
# Verify that the superuser exists. If not, raise an exception.
|
2016-11-29 20:20:46 +00:00
|
|
|
username = user_obj.username
|
2015-07-20 15:39:59 +00:00
|
|
|
(result, err_msg) = users.verify_credentials(username, password)
|
2015-06-02 22:19:22 +00:00
|
|
|
if not result:
|
2016-11-29 20:20:46 +00:00
|
|
|
msg = ('Verification of superuser %s failed: %s. \n\nThe user either does not ' +
|
|
|
|
'exist in the remote authentication system ' +
|
|
|
|
'OR JWT auth is misconfigured') % (username, err_msg)
|
|
|
|
raise ConfigValidationException(msg)
|
2016-10-27 19:34:03 +00:00
|
|
|
|
|
|
|
# If the query endpoint exists, ensure we can query to find the current user and that we can
|
|
|
|
# look up users directly.
|
|
|
|
if query_endpoint:
|
|
|
|
(results, err_msg) = users.query_users(username)
|
|
|
|
if not results:
|
|
|
|
err_msg = err_msg or ('Could not find users matching query: %s' % username)
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Query endpoint is misconfigured or not returning ' +
|
|
|
|
'proper users: %s' % err_msg)
|
2016-10-27 19:34:03 +00:00
|
|
|
|
|
|
|
# Make sure the get user endpoint is also configured.
|
|
|
|
if not getuser_endpoint:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('The lookup user endpoint must be configured if the ' +
|
|
|
|
'query endpoint is set')
|
2016-10-27 19:34:03 +00:00
|
|
|
|
|
|
|
(result, err_msg) = users.get_user(username)
|
|
|
|
if not result:
|
|
|
|
err_msg = err_msg or ('Could not find user %s' % username)
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Lookup endpoint is misconfigured or not returning ' +
|
|
|
|
'properly: %s' % err_msg)
|
2015-06-02 22:19:22 +00:00
|
|
|
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_keystone(config, user_obj, password):
|
2015-07-13 09:34:32 +00:00
|
|
|
""" Validates the Keystone authentication system. """
|
|
|
|
if config.get('AUTHENTICATION_TYPE', 'Database') != 'Keystone':
|
|
|
|
return
|
|
|
|
|
|
|
|
auth_url = config.get('KEYSTONE_AUTH_URL')
|
2016-10-27 19:35:52 +00:00
|
|
|
auth_version = int(config.get('KEYSTONE_AUTH_VERSION', 2))
|
2015-07-13 09:34:32 +00:00
|
|
|
admin_username = config.get('KEYSTONE_ADMIN_USERNAME')
|
|
|
|
admin_password = config.get('KEYSTONE_ADMIN_PASSWORD')
|
|
|
|
admin_tenant = config.get('KEYSTONE_ADMIN_TENANT')
|
|
|
|
|
|
|
|
if not auth_url:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing authentication URL')
|
2015-07-13 09:34:32 +00:00
|
|
|
|
|
|
|
if not admin_username:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing admin username')
|
2015-07-13 09:34:32 +00:00
|
|
|
|
|
|
|
if not admin_password:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing admin password')
|
2015-07-13 09:34:32 +00:00
|
|
|
|
|
|
|
if not admin_tenant:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Missing admin tenant')
|
2015-07-13 09:34:32 +00:00
|
|
|
|
2016-09-08 16:24:47 +00:00
|
|
|
requires_email = config.get('FEATURE_MAILING', True)
|
|
|
|
users = get_keystone_users(auth_version, auth_url, admin_username, admin_password, admin_tenant,
|
|
|
|
requires_email)
|
2015-07-13 09:34:32 +00:00
|
|
|
|
|
|
|
# Verify that the superuser exists. If not, raise an exception.
|
2016-11-29 20:20:46 +00:00
|
|
|
username = user_obj.username
|
2015-07-20 15:39:59 +00:00
|
|
|
(result, err_msg) = users.verify_credentials(username, password)
|
2015-07-13 09:34:32 +00:00
|
|
|
if not result:
|
2016-11-29 20:20:46 +00:00
|
|
|
msg = ('Verification of superuser %s failed: %s \n\nThe user either does not ' +
|
|
|
|
'exist in the remote authentication system ' +
|
|
|
|
'OR Keystone auth is misconfigured.') % (username, err_msg)
|
|
|
|
raise ConfigValidationException(msg)
|
2015-07-13 09:34:32 +00:00
|
|
|
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_signer(config, user_obj, _):
|
2016-02-16 20:31:23 +00:00
|
|
|
""" Validates the GPG public+private key pair used for signing converted ACIs. """
|
|
|
|
if config.get('SIGNING_ENGINE') is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
if config['SIGNING_ENGINE'] not in SIGNING_ENGINES:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Unknown signing engine: %s' % config['SIGNING_ENGINE'])
|
2016-02-16 20:31:23 +00:00
|
|
|
|
2016-06-09 18:47:08 +00:00
|
|
|
engine = SIGNING_ENGINES[config['SIGNING_ENGINE']](config, config_provider)
|
2016-02-16 20:31:23 +00:00
|
|
|
engine.detached_sign(StringIO('test string'))
|
|
|
|
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_security_scanner(config, user_obj, _):
|
2016-05-02 19:29:31 +00:00
|
|
|
""" Validates the configuration for talking to a Quay Security Scanner. """
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
if not config.get('TESTING', False):
|
|
|
|
# Generate a temporary Quay key to use for signing the outgoing requests.
|
|
|
|
setup_jwt_proxy()
|
|
|
|
|
|
|
|
# Wait a few seconds for the JWT proxy to startup.
|
|
|
|
time.sleep(2)
|
2016-05-02 19:29:31 +00:00
|
|
|
|
|
|
|
# Make a ping request to the security service.
|
|
|
|
client = app.config['HTTPCLIENT']
|
2016-05-04 21:40:09 +00:00
|
|
|
api = SecurityScannerAPI(app, config, None, client=client, skip_validation=True)
|
2016-05-02 19:29:31 +00:00
|
|
|
response = api.ping()
|
|
|
|
if response.status_code != 200:
|
|
|
|
message = 'Expected 200 status code, got %s: %s' % (response.status_code, response.text)
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Could not ping security scanner: %s' % message)
|
2016-05-02 19:29:31 +00:00
|
|
|
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
def _validate_bittorrent(config, user_obj, _):
|
2016-09-26 09:13:59 +00:00
|
|
|
""" Validates the configuration for using BitTorrent for downloads. """
|
2016-11-29 20:20:46 +00:00
|
|
|
announce_url = config.get('BITTORRENT_ANNOUNCE_URL')
|
|
|
|
if not announce_url:
|
|
|
|
raise ConfigValidationException('Missing announce URL')
|
2016-09-26 09:13:59 +00:00
|
|
|
|
|
|
|
# Ensure that the tracker is reachable and accepts requests signed with a registry key.
|
|
|
|
client = app.config['HTTPCLIENT']
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'info_hash': sha1('somedata').digest(),
|
|
|
|
'peer_id': '-QUAY00-6wfG2wk6wWLc',
|
|
|
|
'uploaded': 0,
|
|
|
|
'downloaded': 0,
|
|
|
|
'left': 0,
|
|
|
|
'numwant': 0,
|
|
|
|
'port': 80,
|
|
|
|
}
|
|
|
|
|
|
|
|
encoded_jwt = torrent_jwt(params)
|
|
|
|
params['jwt'] = encoded_jwt
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
resp = client.get(announce_url, timeout=5, params=params)
|
2016-09-26 09:13:59 +00:00
|
|
|
logger.debug('Got tracker response: %s: %s', resp.status_code, resp.text)
|
|
|
|
|
|
|
|
if resp.status_code == 404:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Announce path not found; did you forget `/announce`?')
|
2016-09-26 09:13:59 +00:00
|
|
|
|
|
|
|
if resp.status_code == 500:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Did not get expected response from Tracker; ' +
|
|
|
|
'please check your settings')
|
2016-09-26 09:13:59 +00:00
|
|
|
|
|
|
|
if resp.status_code == 200:
|
|
|
|
if 'invalid jwt' in resp.text:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Could not authorize to Tracker; is your Tracker ' +
|
|
|
|
'properly configured?')
|
2016-09-26 09:13:59 +00:00
|
|
|
|
|
|
|
if 'failure reason' in resp.text:
|
2016-11-29 20:20:46 +00:00
|
|
|
raise ConfigValidationException('Could not validate signed announce request: ' + resp.text)
|
2016-09-26 09:13:59 +00:00
|
|
|
|
|
|
|
|
2016-11-29 20:20:46 +00:00
|
|
|
VALIDATORS = {
|
2015-01-07 21:20:51 +00:00
|
|
|
'database': _validate_database,
|
|
|
|
'redis': _validate_redis,
|
|
|
|
'registry-storage': _validate_registry_storage,
|
|
|
|
'mail': _validate_mailing,
|
2015-01-08 18:26:24 +00:00
|
|
|
'github-login': _validate_github('GITHUB_LOGIN_CONFIG'),
|
|
|
|
'github-trigger': _validate_github('GITHUB_TRIGGER_CONFIG'),
|
2015-05-03 18:50:26 +00:00
|
|
|
'gitlab-trigger': _validate_gitlab,
|
|
|
|
'bitbucket-trigger': _validate_bitbucket,
|
2015-01-08 18:56:17 +00:00
|
|
|
'google-login': _validate_google_login,
|
2015-01-07 21:20:51 +00:00
|
|
|
'ssl': _validate_ssl,
|
|
|
|
'ldap': _validate_ldap,
|
2015-06-02 22:19:22 +00:00
|
|
|
'jwt': _validate_jwt,
|
2015-07-13 09:34:32 +00:00
|
|
|
'keystone': _validate_keystone,
|
2016-02-16 20:31:23 +00:00
|
|
|
'signer': _validate_signer,
|
2016-05-02 19:29:31 +00:00
|
|
|
'security-scanner': _validate_security_scanner,
|
2016-09-26 09:13:59 +00:00
|
|
|
'bittorrent': _validate_bittorrent,
|
2015-07-20 17:18:07 +00:00
|
|
|
}
|