def validate(cls, validator_context): """ Validates the OAuth credentials and API endpoint for a GitLab service. """ config = validator_context.config url_scheme_and_hostname = validator_context.url_scheme_and_hostname client = validator_context.http_client github_config = config.get("GITLAB_TRIGGER_CONFIG") if not github_config: raise ConfigValidationException( "Missing GitLab client id and client secret") endpoint = github_config.get("GITLAB_ENDPOINT") if endpoint: if endpoint.find("http://") != 0 and endpoint.find( "https://") != 0: raise ConfigValidationException( "GitLab Endpoint must start with http:// or https://") if not github_config.get("CLIENT_ID"): raise ConfigValidationException("Missing Client ID") if not github_config.get("CLIENT_SECRET"): raise ConfigValidationException("Missing Client Secret") oauth = GitLabOAuthService(config, "GITLAB_TRIGGER_CONFIG") result = oauth.validate_client_id_and_secret(client, url_scheme_and_hostname) if not result: raise ConfigValidationException( "Invalid client id or client secret")
def validate(cls, validator_context): """ Validates the config for BitBucket. """ config = validator_context.config trigger_config = config.get("BITBUCKET_TRIGGER_CONFIG") if not trigger_config: raise ConfigValidationException( "Missing client ID and client secret") if not trigger_config.get("CONSUMER_KEY"): raise ConfigValidationException("Missing Consumer Key") if not trigger_config.get("CONSUMER_SECRET"): raise ConfigValidationException("Missing Consumer Secret") key = trigger_config["CONSUMER_KEY"] secret = trigger_config["CONSUMER_SECRET"] callback_url = "%s/oauth1/bitbucket/callback/trigger/" % ( validator_context.url_scheme_and_hostname.get_url()) bitbucket_client = BitBucket(key, secret, callback_url) (result, _, _) = bitbucket_client.get_authorization_url() if not result: raise ConfigValidationException("Invalid consumer key or secret")
def validate(cls, validator_context): config = validator_context.config if not "DEFAULT_TAG_EXPIRATION" in config: # Old style config return try: convert_to_timedelta( config["DEFAULT_TAG_EXPIRATION"]).total_seconds() except ValueError as ve: raise ConfigValidationException("Invalid default expiration: %s" % ve.message) if not config["DEFAULT_TAG_EXPIRATION"] in config.get( "TAG_EXPIRATION_OPTIONS", []): raise ConfigValidationException( "Default expiration must be in expiration options set") for ts in config.get("TAG_EXPIRATION_OPTIONS", []): try: convert_to_timedelta(ts) except ValueError as ve: raise ConfigValidationException( "Invalid tag expiration option: %s" % ts)
def validate(cls, validator_context): config = validator_context.config logs_model = config.get("LOGS_MODEL", "database") if logs_model != "elasticsearch": raise ConfigValidationException("LOGS_MODEL not set to Elasticsearch") logs_model_config = config.get("LOGS_MODEL_CONFIG", {}) elasticsearch_config = logs_model_config.get("elasticsearch_config", {}) if not elasticsearch_config: raise ConfigValidationException("Missing Elasticsearch config") if not "host" in elasticsearch_config: raise ConfigValidationException("Missing Elasticsearch hostname") host = elasticsearch_config["host"] port = str(elasticsearch_config["port"]) index_prefix = elasticsearch_config.get("index_prefix") or INDEX_NAME_PREFIX auth = (elasticsearch_config.get("access_key"), elasticsearch_config.get("secret_key")) resp = requests.get("https://" + host + ":" + port + "/" + index_prefix + "*", auth=auth) if resp.status_code != 200: raise ConfigValidationException( "Unable to connect to Elasticsearch with config: %s", resp.status_code )
def validate(cls, validator_context): config = validator_context.config client = validator_context.http_client login_manager = OAuthLoginManager(config, client=client) for service in login_manager.services: if not isinstance(service, OIDCLoginService): continue if service.config.get('OIDC_SERVER') is None: msg = 'Missing OIDC_SERVER on OIDC service %s' % service.service_id( ) raise ConfigValidationException(msg) if service.config.get('CLIENT_ID') is None: msg = 'Missing CLIENT_ID on OIDC service %s' % service.service_id( ) raise ConfigValidationException(msg) if service.config.get('CLIENT_SECRET') is None: msg = 'Missing CLIENT_SECRET on OIDC service %s' % service.service_id( ) raise ConfigValidationException(msg) try: if not service.validate(): msg = 'Could not validate OIDC service %s' % service.service_id( ) raise ConfigValidationException(msg) except DiscoveryFailureException as dfe: msg = 'Could not validate OIDC service %s: %s' % ( service.service_id(), dfe.message) raise ConfigValidationException(msg)
def validate(cls, validator_context): """ Validates registry storage. """ config = validator_context.config client = validator_context.http_client ip_resolver = validator_context.ip_resolver config_provider = validator_context.config_provider replication_enabled = config.get('FEATURE_STORAGE_REPLICATION', False) providers = _get_storage_providers(config, ip_resolver, config_provider).items() if not providers: raise ConfigValidationException('Storage configuration required') for name, (storage_type, driver) in providers: # We can skip localstorage validation, since we can't guarantee that # this will be the same machine Q.E. will run under if storage_type == TYPE_LOCAL_STORAGE: continue try: if replication_enabled and storage_type == 'LocalStorage': raise ConfigValidationException( 'Locally mounted directory not supported ' + 'with storage replication') # Run validation on the driver. driver.validate(client) # Run setup on the driver if the read/write succeeded. driver.setup() except Exception as ex: msg = str(ex).strip().split("\n")[0] raise ConfigValidationException( 'Invalid storage configuration: %s: %s' % (name, msg))
def validate(cls, validator_context): """ Validates the SSL configuration (if enabled). """ config = validator_context.config config_provider = validator_context.config_provider # Skip if non-SSL. if config.get("PREFERRED_URL_SCHEME", "http") != "https": return # Skip if externally terminated. if config.get("EXTERNAL_TLS_TERMINATION", False) is True: return # Verify that we have all the required SSL files. for filename in SSL_FILENAMES: if not config_provider.volume_file_exists(filename): raise ConfigValidationException( "Missing required SSL file: %s" % filename) # Read the contents of the SSL certificate. with config_provider.get_volume_file(SSL_FILENAMES[0]) as f: cert_contents = f.read() # Validate the certificate. try: certificate = load_certificate(cert_contents) except CertInvalidException as cie: raise ConfigValidationException( "Could not load SSL certificate: %s" % cie) # Verify the certificate has not expired. if certificate.expired: raise ConfigValidationException( "The specified SSL certificate has expired.") # Verify the hostname matches the name in the certificate. if not certificate.matches_name(_ssl_cn(config["SERVER_HOSTNAME"])): msg = 'Supported names "%s" in SSL cert do not match server hostname "%s"' % ( ", ".join(list(certificate.names)), _ssl_cn(config["SERVER_HOSTNAME"]), ) raise ConfigValidationException(msg) # Verify the private key against the certificate. 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 try: certificate.validate_private_key(private_key_path) except KeyInvalidException as kie: raise ConfigValidationException( "SSL private key failed to validate: %s" % kie)
def validate(cls, validator_context): """ Validates the configuration for talking to a Quay Security Scanner. """ config = validator_context.config client = validator_context.http_client feature_sec_scanner = validator_context.feature_sec_scanner is_testing = validator_context.is_testing server_hostname = validator_context.url_scheme_and_hostname.hostname uri_creator = validator_context.uri_creator if not feature_sec_scanner: return api = SecurityScannerAPI( config, None, server_hostname, client=client, skip_validation=True, uri_creator=uri_creator, ) # if not is_testing: # Generate a temporary Quay key to use for signing the outgoing requests. # setup_jwt_proxy() # We have to wait for JWT proxy to restart with the newly generated key. max_tries = 5 response = None last_exception = None while max_tries > 0: try: response = api.ping() last_exception = None if response.status_code == 200: return except Exception as ex: last_exception = ex time.sleep(1) max_tries = max_tries - 1 if last_exception is not None: message = str(last_exception) raise ConfigValidationException( "Could not ping security scanner: %s" % message) else: message = "Expected 200 status code, got %s: %s" % ( response.status_code, response.text) raise ConfigValidationException( "Could not ping security scanner: %s" % message)
def validate(cls, validator_context): config = validator_context.config if config.get('AUTHENTICATION_TYPE', 'Database') != 'AppToken': return # Ensure that app tokens are enabled, as they are required. if not config.get('FEATURE_APP_SPECIFIC_TOKENS', False): msg = 'Application token support must be enabled to use External Application Token auth' raise ConfigValidationException(msg) # Ensure that direct login is disabled. if config.get('FEATURE_DIRECT_LOGIN', True): msg = 'Direct login must be disabled to use External Application Token auth' raise ConfigValidationException(msg)
def validate(cls, validator_context, public_key_path=None): """ Validates the JWT authentication system. """ config = validator_context.config http_client = validator_context.http_client jwt_auth_max = validator_context.jwt_auth_max config_provider = validator_context.config_provider if config.get("AUTHENTICATION_TYPE", "Database") != "JWT": return verify_endpoint = config.get("JWT_VERIFY_ENDPOINT") query_endpoint = config.get("JWT_QUERY_ENDPOINT", None) getuser_endpoint = config.get("JWT_GETUSER_ENDPOINT", None) issuer = config.get("JWT_AUTH_ISSUER") if not verify_endpoint: raise ConfigValidationException("Missing JWT Verification endpoint") if not issuer: raise ConfigValidationException("Missing JWT Issuer ID") override_config_directory = config_provider.get_config_dir_path() # Try to instatiate the JWT authentication mechanism. This will raise an exception if # the key cannot be found. users = ExternalJWTAuthN( verify_endpoint, query_endpoint, getuser_endpoint, issuer, override_config_directory, http_client, jwt_auth_max, public_key_path=public_key_path, requires_email=config.get("FEATURE_MAILING", True), ) # Verify that we can reach the jwt server (result, err_msg) = users.ping() if not result: msg = ( "Verification of JWT failed: %s. \n\nWe cannot reach the JWT server" + "OR JWT auth is misconfigured" ) % err_msg raise ConfigValidationException(msg)
def validate(cls, validator_context): config = validator_context.config """ Validates the action log archiving configuration. """ if not config.get('FEATURE_ACTION_LOG_ROTATION', False): return if not config.get('ACTION_LOG_ARCHIVE_PATH'): raise ConfigValidationException('Missing action log archive path') if not config.get('ACTION_LOG_ARCHIVE_LOCATION'): raise ConfigValidationException('Missing action log archive storage location') location = config['ACTION_LOG_ARCHIVE_LOCATION'] storage_config = config.get('DISTRIBUTED_STORAGE_CONFIG') or {} if location not in storage_config: msg = 'Action log archive storage location `%s` not found in storage config' % location raise ConfigValidationException(msg)
def validate(cls, validator_context): config = validator_context.config """ Validates the action log archiving configuration. """ if not config.get("FEATURE_ACTION_LOG_ROTATION", False): return if not config.get("ACTION_LOG_ARCHIVE_PATH"): raise ConfigValidationException("Missing action log archive path") if not config.get("ACTION_LOG_ARCHIVE_LOCATION"): raise ConfigValidationException("Missing action log archive storage location") location = config["ACTION_LOG_ARCHIVE_LOCATION"] storage_config = config.get("DISTRIBUTED_STORAGE_CONFIG") or {} if location not in storage_config: msg = "Action log archive storage location `%s` not found in storage config" % location raise ConfigValidationException(msg)
def validate(cls, validator_context): config = validator_context.config logs_model = config.get("LOGS_MODEL", "database") if logs_model != "elasticsearch": raise ConfigValidationException( "LOGS_MODEL not set to Elasticsearch") logs_model_config = config.get("LOGS_MODEL_CONFIG", {}) producer = logs_model_config.get("producer", {}) if not producer or producer != "kinesis_stream": raise ConfigValidationException( "Producer not set to 'kinesis_stream'") kinesis_stream_config = logs_model_config.get("kinesis_stream_config", {}) if not kinesis_stream_config: raise ConfigValidationException( "No kinesis_stream_config defined'") stream_name = kinesis_stream_config.get("stream_name") aws_access_key = kinesis_stream_config.get("aws_access_key") aws_secret_key = kinesis_stream_config.get("aws_secret_key") aws_region = kinesis_stream_config.get("aws_region") producer = boto3.client( "kinesis", use_ssl=True, region_name=aws_region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key, ) try: producer.describe_stream(StreamName=stream_name) except ( producer.exceptions.ClientError, producer.exceptions.ResourceNotFoundException, ) as e: raise ConfigValidationException( "Unable to connect to Kinesis with config: %s", e) except Exception: raise ConfigValidationException("Unable to connect to Kinesis")
def validate(cls, validator_context): """ Validates connecting to redis. """ config = validator_context.config redis_config = config.get("BUILDLOGS_REDIS", {}) if not "host" in redis_config: raise ConfigValidationException("Missing redis hostname") client = redis.StrictRedis(socket_connect_timeout=5, **redis_config) client.ping()
def validate(cls, validator_context): """ Validates the Keystone authentication system. """ config = validator_context.config if config.get("AUTHENTICATION_TYPE", "Database") != "Keystone": return auth_url = config.get("KEYSTONE_AUTH_URL") auth_version = int(config.get("KEYSTONE_AUTH_VERSION", 2)) 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: raise ConfigValidationException("Missing authentication URL") if not admin_username: raise ConfigValidationException("Missing admin username") if not admin_password: raise ConfigValidationException("Missing admin password") if not admin_tenant: raise ConfigValidationException("Missing admin tenant") requires_email = config.get("FEATURE_MAILING", True) users = get_keystone_users(auth_version, auth_url, admin_username, admin_password, admin_tenant, requires_email) # Verify that the superuser exists. If not, raise an exception. (result, err_msg) = users.at_least_one_user_exists() if not result: msg = ( "Verification that users exist failed: %s. \n\nNo users exist " + "in the admin tenant/project " + "in the remote authentication system " + "OR Keystone auth is misconfigured.") % err_msg raise ConfigValidationException(msg)
def validate(cls, validator_context): """ Validates connecting to the database. """ config = validator_context.config try: validate_database_precondition( config["DB_URI"], config.get("DB_CONNECTION_ARGS", {})) except OperationalError as ex: if ex.args and len(ex.args) > 1: raise ConfigValidationException(ex.args[1]) else: raise ex
def _get_storage_providers(config, ip_resolver, config_provider): storage_config = config.get("DISTRIBUTED_STORAGE_CONFIG", {}) drivers = {} try: for name, parameters in storage_config.items(): driver = get_storage_driver(None, None, config_provider, ip_resolver, parameters) drivers[name] = (parameters[0], driver) except TypeError: raise ConfigValidationException("Missing required parameter(s) for storage %s" % name) return drivers
def validate(cls, validator_context): """ Validates the Google Login client ID and secret. """ config = validator_context.config client = validator_context.http_client url_scheme_and_hostname = validator_context.url_scheme_and_hostname google_login_config = config.get("GOOGLE_LOGIN_CONFIG") if not google_login_config: raise ConfigValidationException("Missing client ID and client secret") if not google_login_config.get("CLIENT_ID"): raise ConfigValidationException("Missing Client ID") if not google_login_config.get("CLIENT_SECRET"): raise ConfigValidationException("Missing Client Secret") oauth = GoogleOAuthService(config, "GOOGLE_LOGIN_CONFIG") result = oauth.validate_client_id_and_secret(client, url_scheme_and_hostname) if not result: raise ConfigValidationException("Invalid client id or client secret")
def validate(cls, validator_context): """ Validates the Keystone authentication system. """ config = validator_context.config if config.get('AUTHENTICATION_TYPE', 'Database') != 'Keystone': return auth_url = config.get('KEYSTONE_AUTH_URL') auth_version = int(config.get('KEYSTONE_AUTH_VERSION', 2)) 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: raise ConfigValidationException('Missing authentication URL') if not admin_username: raise ConfigValidationException('Missing admin username') if not admin_password: raise ConfigValidationException('Missing admin password') if not admin_tenant: raise ConfigValidationException('Missing admin tenant') requires_email = config.get('FEATURE_MAILING', True) users = get_keystone_users(auth_version, auth_url, admin_username, admin_password, admin_tenant, requires_email) # Verify that the superuser exists. If not, raise an exception. (result, err_msg) = users.at_least_one_user_exists() if not result: msg = ( 'Verification that users exist failed: %s. \n\nNo users exist ' + 'in the admin tenant/project ' + 'in the remote authentication system ' + 'OR Keystone auth is misconfigured.') % err_msg raise ConfigValidationException(msg)
def validate(cls, validator_context): config = validator_context.config client = validator_context.http_client if not config.get("FEATURE_DIRECT_LOGIN", True): # Make sure we have at least one OIDC enabled. github_login = config.get("FEATURE_GITHUB_LOGIN", False) google_login = config.get("FEATURE_GOOGLE_LOGIN", False) login_manager = OAuthLoginManager(config, client=client) custom_oidc = [ s for s in login_manager.services if isinstance(s, OIDCLoginService) ] if not github_login and not google_login and not custom_oidc: msg = "Cannot disable credentials login to UI without configured OIDC service" raise ConfigValidationException(msg) if not config.get("FEATURE_USER_CREATION", True) and config.get( "FEATURE_INVITE_ONLY_USER_CREATION", False): msg = "Invite only user creation requires user creation to be enabled" raise ConfigValidationException(msg)
def validate(cls, validator_context): """ Validates the GPG public+private key pair used for signing converted ACIs. """ config = validator_context.config config_provider = validator_context.config_provider if config.get("SIGNING_ENGINE") is None: return if config["SIGNING_ENGINE"] not in SIGNING_ENGINES: raise ConfigValidationException("Unknown signing engine: %s" % config["SIGNING_ENGINE"]) engine = SIGNING_ENGINES[config["SIGNING_ENGINE"]](config, config_provider) engine.detached_sign(StringIO("test string"))
def validate(cls, validator_context): """ Validates the configuration for using BitTorrent for downloads. """ config = validator_context.config client = validator_context.http_client announce_url = config.get("BITTORRENT_ANNOUNCE_URL") if not announce_url: raise ConfigValidationException("Missing announce URL") # Ensure that the tracker is reachable and accepts requests signed with a registry key. params = { "info_hash": sha1("test").digest(), "peer_id": "-QUAY00-6wfG2wk6wWLc", "uploaded": 0, "downloaded": 0, "left": 0, "numwant": 0, "port": 80, } torrent_config = TorrentConfiguration.for_testing( validator_context.instance_keys, announce_url, validator_context.registry_title) encoded_jwt = jwt_from_infohash(torrent_config, params["info_hash"]) params["jwt"] = encoded_jwt resp = client.get(announce_url, timeout=5, params=params) logger.debug("Got tracker response: %s: %s", resp.status_code, resp.text) if resp.status_code == 404: raise ConfigValidationException( "Announce path not found; did you forget `/announce`?") if resp.status_code == 500: raise ConfigValidationException( "Did not get expected response from Tracker; " + "please check your settings") if resp.status_code == 200: if "invalid jwt" in resp.text: raise ConfigValidationException( "Could not authorize to Tracker; is your Tracker " + "properly configured?") if "failure reason" in resp.text: raise ConfigValidationException( "Could not validate signed announce request: " + resp.text) if "go_goroutines" in resp.text: raise ConfigValidationException( "Could not validate signed announce request: " + "provided port is used for Prometheus")
def validate(cls, validator_context): """ Validates the OAuth credentials and API endpoint for a Github service. """ config = validator_context.config client = validator_context.http_client url_scheme_and_hostname = validator_context.url_scheme_and_hostname github_config = config.get(cls.config_key) if not github_config: raise ConfigValidationException( "Missing GitHub client id and client secret") endpoint = github_config.get("GITHUB_ENDPOINT") if not endpoint: raise ConfigValidationException("Missing GitHub Endpoint") if endpoint.find("http://") != 0 and endpoint.find("https://") != 0: raise ConfigValidationException( "Github Endpoint must start with http:// or https://") if not github_config.get("CLIENT_ID"): raise ConfigValidationException("Missing Client ID") if not github_config.get("CLIENT_SECRET"): raise ConfigValidationException("Missing Client Secret") if github_config.get("ORG_RESTRICT") and not github_config.get( "ALLOWED_ORGANIZATIONS"): raise ConfigValidationException( "Organization restriction must have at least one allowed " + "organization") oauth = GithubOAuthService(config, cls.config_key) result = oauth.validate_client_id_and_secret(client, url_scheme_and_hostname) if not result: raise ConfigValidationException( "Invalid client id or client secret") if github_config.get("ALLOWED_ORGANIZATIONS"): for org_id in github_config.get("ALLOWED_ORGANIZATIONS"): if not oauth.validate_organization(org_id, client): raise ConfigValidationException( "Invalid organization: %s" % org_id)
def validate(cls, validator_context): """ Validates the configuration for using BitTorrent for downloads. """ config = validator_context.config client = validator_context.http_client announce_url = config.get('BITTORRENT_ANNOUNCE_URL') if not announce_url: raise ConfigValidationException('Missing announce URL') # Ensure that the tracker is reachable and accepts requests signed with a registry key. params = { 'info_hash': sha1('test').digest(), 'peer_id': '-QUAY00-6wfG2wk6wWLc', 'uploaded': 0, 'downloaded': 0, 'left': 0, 'numwant': 0, 'port': 80, } torrent_config = TorrentConfiguration.for_testing( validator_context.instance_keys, announce_url, validator_context.registry_title) encoded_jwt = jwt_from_infohash(torrent_config, params['info_hash']) params['jwt'] = encoded_jwt resp = client.get(announce_url, timeout=5, params=params) logger.debug('Got tracker response: %s: %s', resp.status_code, resp.text) if resp.status_code == 404: raise ConfigValidationException( 'Announce path not found; did you forget `/announce`?') if resp.status_code == 500: raise ConfigValidationException( 'Did not get expected response from Tracker; ' + 'please check your settings') if resp.status_code == 200: if 'invalid jwt' in resp.text: raise ConfigValidationException( 'Could not authorize to Tracker; is your Tracker ' + 'properly configured?') if 'failure reason' in resp.text: raise ConfigValidationException( 'Could not validate signed announce request: ' + resp.text) if 'go_goroutines' in resp.text: raise ConfigValidationException( 'Could not validate signed announce request: ' + 'provided port is used for Prometheus')
def valid(self): if not self._feature_repo_mirror: raise ConfigValidationException("REPO_MIRROR feature not enabled") return True
def validate(cls, validator_context): """ Validates the LDAP connection. """ config = validator_context.config config_provider = validator_context.config_provider init_scripts_location = validator_context.init_scripts_location if config.get('AUTHENTICATION_TYPE', 'Database') != 'LDAP': return # If there is a custom LDAP certificate, then reinstall the certificates for the container. if config_provider.volume_file_exists(LDAP_CERT_FILENAME): subprocess.check_call( [os.path.join(init_scripts_location, 'certs_install.sh')], env={'QUAYCONFIG': config_provider.get_config_dir_path()}) # 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 ConfigValidationException( 'Missing Admin DN for LDAP configuration') if not admin_passwd: raise ConfigValidationException( 'Missing Admin Password for LDAP configuration') ldap_uri = config.get('LDAP_URI', 'ldap://localhost') if not ldap_uri.startswith('ldap://') and not ldap_uri.startswith( 'ldaps://'): raise ConfigValidationException( 'LDAP URI must start with ldap:// or ldaps://') allow_tls_fallback = config.get('LDAP_ALLOW_INSECURE_FALLBACK', False) try: with LDAPConnection(ldap_uri, admin_dn, admin_passwd, allow_tls_fallback): pass except ldap.LDAPError as ex: values = ex.args[0] if ex.args else {} if not isinstance(values, dict): raise ConfigValidationException(str(ex.args)) raise ConfigValidationException(values.get('desc', 'Unknown error')) 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') requires_email = config.get('FEATURE_MAILING', True) users = LDAPUsers(ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr, email_attr, allow_tls_fallback, requires_email=requires_email) # Ensure at least one user exists to verify the connection is setup properly. (result, err_msg) = users.at_least_one_user_exists() if not result: msg = ( 'Verification that users exist failed: %s. \n\nNo users exist ' + 'in the remote authentication system ' + 'OR LDAP auth is misconfigured.') % err_msg raise ConfigValidationException(msg)
def validate(cls, validator_context): """ Validates the LDAP connection. """ config = validator_context.config config_provider = validator_context.config_provider init_scripts_location = validator_context.init_scripts_location if config.get("AUTHENTICATION_TYPE", "Database") != "LDAP": return # If there is a custom LDAP certificate, then reinstall the certificates for the container. if config_provider.volume_file_exists(LDAP_CERT_FILENAME): subprocess.check_call( [os.path.join(init_scripts_location, "certs_install.sh")], env={"QUAYCONFIG": config_provider.get_config_dir_path()}, ) # 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 ConfigValidationException("Missing Admin DN for LDAP configuration") if not admin_passwd: raise ConfigValidationException("Missing Admin Password for LDAP configuration") ldap_uri = config.get("LDAP_URI", "ldap://localhost") if not ldap_uri.startswith("ldap://") and not ldap_uri.startswith("ldaps://"): raise ConfigValidationException("LDAP URI must start with ldap:// or ldaps://") allow_tls_fallback = config.get("LDAP_ALLOW_INSECURE_FALLBACK", False) try: with LDAPConnection(ldap_uri, admin_dn, admin_passwd, allow_tls_fallback): pass except ldap.LDAPError as ex: values = ex.args[0] if ex.args else {} if not isinstance(values, dict): raise ConfigValidationException(str(ex.args)) raise ConfigValidationException(values.get("desc", "Unknown error")) 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") email_attr = config.get("LDAP_EMAIL_ATTR", "mail") ldap_user_filter = config.get("LDAP_USER_FILTER", None) users = LDAPUsers( ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr, email_attr, allow_tls_fallback, ldap_user_filter=ldap_user_filter, ) # Ensure at least one user exists to verify the connection is setup properly. (result, err_msg) = users.at_least_one_user_exists() if not result: msg = ( "Verification that users exist failed: %s. \n\nNo users exist " + "in the remote authentication system " + "OR LDAP auth is misconfigured." ) % err_msg raise ConfigValidationException(msg)