2017-02-01 23:17:25 +00:00
|
|
|
import hashlib
|
2016-10-10 20:23:42 +00:00
|
|
|
import json
|
2013-09-23 16:37:40 +00:00
|
|
|
import logging
|
2013-10-01 03:54:12 +00:00
|
|
|
import os
|
2013-09-23 16:37:40 +00:00
|
|
|
|
2015-09-01 19:03:46 +00:00
|
|
|
from functools import partial
|
2016-10-10 20:23:42 +00:00
|
|
|
|
|
|
|
from Crypto.PublicKey import RSA
|
2018-01-05 21:27:03 +00:00
|
|
|
from flask import Flask, request, Request
|
|
|
|
from flask_login import LoginManager
|
2016-09-29 00:17:14 +00:00
|
|
|
from flask_mail import Mail
|
2016-10-10 20:23:42 +00:00
|
|
|
from flask_principal import Principal
|
2015-09-28 19:43:20 +00:00
|
|
|
from jwkest.jwk import RSAKey
|
2013-10-03 20:19:01 +00:00
|
|
|
|
2014-04-03 22:47:17 +00:00
|
|
|
import features
|
2017-02-01 23:17:25 +00:00
|
|
|
from _init import CONF_DIR
|
2017-02-01 23:17:25 +00:00
|
|
|
from auth.auth_context import get_authenticated_user
|
2015-02-05 20:37:14 +00:00
|
|
|
from avatars.avatars import Avatar
|
2016-10-27 17:18:02 +00:00
|
|
|
from buildman.manager.buildcanceller import BuildCanceller
|
2014-05-13 16:17:26 +00:00
|
|
|
from data import database
|
2016-10-10 20:23:42 +00:00
|
|
|
from data import model
|
|
|
|
from data.archivedlogs import LogArchive
|
2014-04-10 19:20:16 +00:00
|
|
|
from data.billing import Billing
|
2014-05-09 22:45:11 +00:00
|
|
|
from data.buildlogs import BuildLogs
|
2017-12-19 22:13:37 +00:00
|
|
|
from data.cache import get_model_cache
|
2017-01-27 16:22:40 +00:00
|
|
|
from data.model.user import LoginWrappedDBUser
|
2016-06-28 18:36:17 +00:00
|
|
|
from data.queue import WorkQueue, BuildMetricQueueReporter
|
2016-10-10 20:23:42 +00:00
|
|
|
from data.userevent import UserEventsBuilderModule
|
|
|
|
from data.userfiles import Userfiles
|
|
|
|
from data.users import UserAuthentication
|
2017-01-27 16:22:40 +00:00
|
|
|
from path_converters import RegexConverter, RepositoryPathConverter, APIRepositoryPathConverter
|
2017-01-20 20:21:08 +00:00
|
|
|
from oauth.services.github import GithubOAuthService
|
|
|
|
from oauth.services.gitlab import GitLabOAuthService
|
|
|
|
from oauth.loginmanager import OAuthLoginManager
|
2016-10-10 20:23:42 +00:00
|
|
|
from storage import Storage
|
2017-02-01 23:17:25 +00:00
|
|
|
from util.log import filter_logs
|
2015-09-01 19:03:46 +00:00
|
|
|
from util import get_app_url
|
2017-09-26 20:08:50 +00:00
|
|
|
from util.ipresolver import IPResolver
|
2015-08-03 19:49:10 +00:00
|
|
|
from util.saas.analytics import Analytics
|
2016-10-13 17:48:35 +00:00
|
|
|
from util.saas.useranalytics import UserAnalytics
|
2015-08-03 19:49:10 +00:00
|
|
|
from util.saas.exceptionlog import Sentry
|
2015-01-09 21:23:31 +00:00
|
|
|
from util.names import urn_generator
|
2016-10-10 20:23:42 +00:00
|
|
|
from util.config.configutil import generate_secret_key
|
2015-07-24 18:52:19 +00:00
|
|
|
from util.config.provider import get_config_provider
|
2015-01-20 17:43:11 +00:00
|
|
|
from util.config.superusermanager import SuperUserManager
|
2016-10-10 20:23:42 +00:00
|
|
|
from util.label_validator import LabelValidator
|
2017-01-19 20:23:15 +00:00
|
|
|
from util.license import LicenseValidator
|
2016-07-01 18:16:15 +00:00
|
|
|
from util.metrics.metricqueue import MetricQueue
|
|
|
|
from util.metrics.prometheus import PrometheusPlugin
|
2016-10-10 20:23:42 +00:00
|
|
|
from util.saas.cloudwatch import start_cloudwatch_sender
|
|
|
|
from util.secscan.api import SecurityScannerAPI
|
2017-04-05 14:03:27 +00:00
|
|
|
from util.tufmetadata.api import TUFMetadataAPI
|
2016-10-10 20:23:42 +00:00
|
|
|
from util.security.instancekeys import InstanceKeys
|
|
|
|
from util.security.signing import Signer
|
2014-04-07 20:59:22 +00:00
|
|
|
|
2017-02-01 23:17:25 +00:00
|
|
|
|
2017-02-01 23:17:25 +00:00
|
|
|
OVERRIDE_CONFIG_DIRECTORY = os.path.join(CONF_DIR, 'stack/')
|
|
|
|
OVERRIDE_CONFIG_YAML_FILENAME = os.path.join(CONF_DIR, 'stack/config.yaml')
|
|
|
|
OVERRIDE_CONFIG_PY_FILENAME = os.path.join(CONF_DIR, 'stack/config.py')
|
2014-08-22 00:36:11 +00:00
|
|
|
|
2014-06-23 15:24:54 +00:00
|
|
|
OVERRIDE_CONFIG_KEY = 'QUAY_OVERRIDE_CONFIG'
|
2014-04-07 20:59:22 +00:00
|
|
|
|
2015-09-28 19:43:20 +00:00
|
|
|
DOCKER_V2_SIGNINGKEY_FILENAME = 'docker_v2.pem'
|
|
|
|
|
2013-09-20 15:55:44 +00:00
|
|
|
app = Flask(__name__)
|
2013-10-03 20:19:01 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2015-07-27 15:17:44 +00:00
|
|
|
# Instantiate the configuration.
|
2015-07-24 18:52:19 +00:00
|
|
|
is_testing = 'TEST' in os.environ
|
2015-07-27 15:17:44 +00:00
|
|
|
is_kubernetes = 'KUBERNETES_SERVICE_HOST' in os.environ
|
2015-07-24 18:52:19 +00:00
|
|
|
config_provider = get_config_provider(OVERRIDE_CONFIG_DIRECTORY, 'config.yaml', 'config.py',
|
2015-07-27 15:17:44 +00:00
|
|
|
testing=is_testing, kubernetes=is_kubernetes)
|
2015-01-09 21:23:31 +00:00
|
|
|
|
2015-07-24 18:52:19 +00:00
|
|
|
if is_testing:
|
2014-04-07 20:59:22 +00:00
|
|
|
from test.testconfig import TestConfig
|
|
|
|
logger.debug('Loading test config.')
|
|
|
|
app.config.from_object(TestConfig())
|
|
|
|
else:
|
|
|
|
from config import DefaultConfig
|
|
|
|
logger.debug('Loading default config.')
|
|
|
|
app.config.from_object(DefaultConfig())
|
2015-01-09 21:23:31 +00:00
|
|
|
app.teardown_request(database.close_db_filter)
|
2014-04-03 21:31:46 +00:00
|
|
|
|
2015-01-09 21:23:31 +00:00
|
|
|
# Load the override config via the provider.
|
2015-07-24 18:52:19 +00:00
|
|
|
config_provider.update_app_config(app.config)
|
2013-09-27 23:29:01 +00:00
|
|
|
|
2015-01-09 21:23:31 +00:00
|
|
|
# Update any configuration found in the override environment variable.
|
|
|
|
environ_config = json.loads(os.environ.get(OVERRIDE_CONFIG_KEY, '{}'))
|
|
|
|
app.config.update(environ_config)
|
2014-10-14 17:58:08 +00:00
|
|
|
|
2015-11-06 18:34:49 +00:00
|
|
|
# Allow user to define a custom storage preference for the local instance.
|
|
|
|
_distributed_storage_preference = os.environ.get('QUAY_DISTRIBUTED_STORAGE_PREFERENCE', '').split()
|
|
|
|
if _distributed_storage_preference:
|
|
|
|
app.config['DISTRIBUTED_STORAGE_PREFERENCE'] = _distributed_storage_preference
|
|
|
|
|
2016-04-28 17:41:50 +00:00
|
|
|
# Generate a secret key if none was specified.
|
|
|
|
if app.config['SECRET_KEY'] is None:
|
|
|
|
logger.debug('Generating in-memory secret key')
|
|
|
|
app.config['SECRET_KEY'] = generate_secret_key()
|
|
|
|
|
|
|
|
# If the "preferred" scheme is https, then http is not allowed. Therefore, ensure we have a secure
|
|
|
|
# session cookie.
|
2016-08-11 20:16:15 +00:00
|
|
|
if (app.config['PREFERRED_URL_SCHEME'] == 'https' and
|
|
|
|
not app.config.get('FORCE_NONSECURE_SESSION_COOKIE', False)):
|
2016-04-28 17:41:50 +00:00
|
|
|
app.config['SESSION_COOKIE_SECURE'] = True
|
|
|
|
|
|
|
|
# Load features from config.
|
|
|
|
features.import_features(app.config)
|
|
|
|
|
2017-02-01 23:17:25 +00:00
|
|
|
CONFIG_DIGEST = hashlib.sha256(json.dumps(app.config, default=str)).hexdigest()[0:8]
|
|
|
|
|
|
|
|
logger.debug("Loaded config", extra={"config": app.config})
|
|
|
|
|
2014-10-14 17:58:08 +00:00
|
|
|
|
|
|
|
class RequestWithId(Request):
|
|
|
|
request_gen = staticmethod(urn_generator(['request']))
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super(RequestWithId, self).__init__(*args, **kwargs)
|
|
|
|
self.request_id = self.request_gen()
|
|
|
|
|
|
|
|
|
|
|
|
@app.before_request
|
|
|
|
def _request_start():
|
2018-01-04 17:58:30 +00:00
|
|
|
logger.debug('Starting request: %s (%s)', request.request_id, request.path,
|
|
|
|
extra={"request_id": request.request_id})
|
2017-02-01 23:17:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
DEFAULT_FILTER = lambda x: '[FILTERED]'
|
|
|
|
FILTERED_VALUES = [
|
|
|
|
{'key': ['password'], 'fn': DEFAULT_FILTER},
|
|
|
|
{'key': ['user', 'password'], 'fn': DEFAULT_FILTER},
|
|
|
|
{'key': ['blob'], 'fn': lambda x: x[0:8]}
|
|
|
|
]
|
2014-10-14 17:58:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
@app.after_request
|
2017-02-01 23:17:25 +00:00
|
|
|
def _request_end(resp):
|
|
|
|
jsonbody = request.get_json(force=True, silent=True)
|
|
|
|
values = request.values.to_dict()
|
|
|
|
|
|
|
|
if jsonbody and not isinstance(jsonbody, dict):
|
|
|
|
jsonbody = {'_parsererror': jsonbody}
|
|
|
|
|
|
|
|
if isinstance(values, dict):
|
|
|
|
filter_logs(values, FILTERED_VALUES)
|
|
|
|
|
|
|
|
extra = {
|
|
|
|
"endpoint": request.endpoint,
|
|
|
|
"request_id" : request.request_id,
|
|
|
|
"remote_addr": request.remote_addr,
|
|
|
|
"http_method": request.method,
|
|
|
|
"original_url": request.url,
|
|
|
|
"path": request.path,
|
|
|
|
"parameters": values,
|
|
|
|
"json_body": jsonbody,
|
|
|
|
"confsha": CONFIG_DIGEST,
|
|
|
|
}
|
|
|
|
|
|
|
|
if request.user_agent is not None:
|
|
|
|
extra["user-agent"] = request.user_agent.string
|
|
|
|
|
2018-01-04 17:58:30 +00:00
|
|
|
logger.debug('Ending request: %s (%s)', request.request_id, request.path, extra=extra)
|
2017-02-01 23:17:25 +00:00
|
|
|
return resp
|
2014-10-14 17:58:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2016-01-28 16:30:47 +00:00
|
|
|
root_logger = logging.getLogger()
|
|
|
|
|
2014-10-14 17:58:08 +00:00
|
|
|
app.request_class = RequestWithId
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
# Register custom converters.
|
|
|
|
app.url_map.converters['regex'] = RegexConverter
|
|
|
|
app.url_map.converters['repopath'] = RepositoryPathConverter
|
|
|
|
app.url_map.converters['apirepopath'] = APIRepositoryPathConverter
|
|
|
|
|
2014-03-17 16:01:13 +00:00
|
|
|
Principal(app, use_sessions=False)
|
2013-09-20 22:38:17 +00:00
|
|
|
|
2016-11-10 18:54:04 +00:00
|
|
|
tf = app.config['DB_TRANSACTION_FACTORY']
|
|
|
|
|
2017-12-19 22:13:37 +00:00
|
|
|
model_cache = get_model_cache(app.config)
|
2014-11-25 00:25:13 +00:00
|
|
|
avatar = Avatar(app)
|
2014-04-09 03:05:45 +00:00
|
|
|
login_manager = LoginManager(app)
|
|
|
|
mail = Mail(app)
|
2016-02-01 20:07:46 +00:00
|
|
|
prometheus = PrometheusPlugin(app)
|
|
|
|
metric_queue = MetricQueue(prometheus)
|
2017-01-24 21:31:33 +00:00
|
|
|
chunk_cleanup_queue = WorkQueue(app.config['CHUNK_CLEANUP_QUEUE_NAME'], tf, metric_queue=metric_queue)
|
2016-08-24 16:55:33 +00:00
|
|
|
instance_keys = InstanceKeys(app)
|
2017-09-26 20:08:50 +00:00
|
|
|
ip_resolver = IPResolver(app)
|
|
|
|
storage = Storage(app, metric_queue, chunk_cleanup_queue, instance_keys, config_provider, ip_resolver)
|
2014-09-09 19:54:03 +00:00
|
|
|
userfiles = Userfiles(app, storage)
|
2014-09-11 15:18:28 +00:00
|
|
|
log_archive = LogArchive(app, storage)
|
2014-04-09 03:05:45 +00:00
|
|
|
analytics = Analytics(app)
|
2016-10-13 17:48:35 +00:00
|
|
|
user_analytics = UserAnalytics(app)
|
2014-04-10 19:20:16 +00:00
|
|
|
billing = Billing(app)
|
2014-04-28 22:59:22 +00:00
|
|
|
sentry = Sentry(app)
|
2014-05-09 22:45:11 +00:00
|
|
|
build_logs = BuildLogs(app)
|
2016-05-03 19:02:39 +00:00
|
|
|
authentication = UserAuthentication(app, config_provider, OVERRIDE_CONFIG_DIRECTORY)
|
2014-05-30 18:25:29 +00:00
|
|
|
userevents = UserEventsBuilderModule(app)
|
2015-01-20 17:43:11 +00:00
|
|
|
superusers = SuperUserManager(app)
|
2016-05-23 21:10:03 +00:00
|
|
|
signer = Signer(app, config_provider)
|
2016-05-31 20:48:19 +00:00
|
|
|
instance_keys = InstanceKeys(app)
|
2016-07-18 22:20:00 +00:00
|
|
|
label_validator = LabelValidator(app)
|
2016-10-27 17:18:02 +00:00
|
|
|
build_canceller = BuildCanceller(app)
|
2016-05-31 20:48:19 +00:00
|
|
|
|
2016-10-18 15:44:13 +00:00
|
|
|
license_validator = LicenseValidator(config_provider)
|
2016-10-10 20:23:42 +00:00
|
|
|
license_validator.start()
|
|
|
|
|
2015-08-12 20:31:01 +00:00
|
|
|
start_cloudwatch_sender(metric_queue, app)
|
2015-01-16 18:44:29 +00:00
|
|
|
|
2017-01-19 20:23:15 +00:00
|
|
|
github_trigger = GithubOAuthService(app.config, 'GITHUB_TRIGGER_CONFIG')
|
|
|
|
gitlab_trigger = GitLabOAuthService(app.config, 'GITLAB_TRIGGER_CONFIG')
|
2015-09-04 20:14:46 +00:00
|
|
|
|
2017-01-20 20:21:08 +00:00
|
|
|
oauth_login = OAuthLoginManager(app.config)
|
|
|
|
oauth_apps = [github_trigger, gitlab_trigger]
|
2014-11-05 21:43:37 +00:00
|
|
|
|
2017-01-18 21:20:16 +00:00
|
|
|
image_replication_queue = WorkQueue(app.config['REPLICATION_QUEUE_NAME'], tf,
|
|
|
|
has_namespace=False, metric_queue=metric_queue)
|
2014-05-22 16:13:41 +00:00
|
|
|
dockerfile_build_queue = WorkQueue(app.config['DOCKERFILE_BUILD_QUEUE_NAME'], tf,
|
2017-01-24 21:31:33 +00:00
|
|
|
metric_queue=metric_queue,
|
2016-08-09 21:58:33 +00:00
|
|
|
reporter=BuildMetricQueueReporter(metric_queue),
|
|
|
|
has_namespace=True)
|
2017-01-24 21:31:33 +00:00
|
|
|
notification_queue = WorkQueue(app.config['NOTIFICATION_QUEUE_NAME'], tf, has_namespace=True,
|
|
|
|
metric_queue=metric_queue)
|
2016-08-09 21:58:33 +00:00
|
|
|
secscan_notification_queue = WorkQueue(app.config['SECSCAN_NOTIFICATION_QUEUE_NAME'], tf,
|
2017-01-24 21:31:33 +00:00
|
|
|
has_namespace=False,
|
|
|
|
metric_queue=metric_queue)
|
2016-08-09 21:58:33 +00:00
|
|
|
|
2018-02-23 21:45:16 +00:00
|
|
|
# Note: We set `has_namespace` to `False` here, as we explicitly want this queue to not be emptied
|
|
|
|
# when a namespace is marked for deletion.
|
|
|
|
namespace_gc_queue = WorkQueue(app.config['NAMESPACE_GC_QUEUE_NAME'], tf, has_namespace=False,
|
|
|
|
metric_queue=metric_queue)
|
|
|
|
|
2016-08-09 21:58:33 +00:00
|
|
|
all_queues = [image_replication_queue, dockerfile_build_queue, notification_queue,
|
2018-02-23 21:45:16 +00:00
|
|
|
secscan_notification_queue, chunk_cleanup_queue, namespace_gc_queue]
|
2016-08-09 21:58:33 +00:00
|
|
|
|
2016-05-04 21:40:09 +00:00
|
|
|
secscan_api = SecurityScannerAPI(app, app.config, storage)
|
2017-04-05 14:03:27 +00:00
|
|
|
tuf_metadata_api = TUFMetadataAPI(app, app.config)
|
2014-05-22 16:13:41 +00:00
|
|
|
|
2015-09-28 19:43:20 +00:00
|
|
|
# Check for a key in config. If none found, generate a new signing key for Docker V2 manifests.
|
|
|
|
_v2_key_path = os.path.join(OVERRIDE_CONFIG_DIRECTORY, DOCKER_V2_SIGNINGKEY_FILENAME)
|
|
|
|
if os.path.exists(_v2_key_path):
|
|
|
|
docker_v2_signing_key = RSAKey().load(_v2_key_path)
|
|
|
|
else:
|
|
|
|
docker_v2_signing_key = RSAKey(key=RSA.generate(2048))
|
|
|
|
|
2015-11-20 20:32:17 +00:00
|
|
|
|
2014-05-13 16:17:26 +00:00
|
|
|
database.configure(app.config)
|
|
|
|
model.config.app_config = app.config
|
|
|
|
model.config.store = storage
|
2016-12-22 19:55:26 +00:00
|
|
|
model.config.register_image_cleanup_callback(secscan_api.cleanup_layers)
|
2017-04-12 19:47:24 +00:00
|
|
|
model.config.register_repo_cleanup_callback(tuf_metadata_api.delete_metadata)
|
|
|
|
|
2014-07-29 17:47:54 +00:00
|
|
|
|
2015-01-17 03:41:54 +00:00
|
|
|
@login_manager.user_loader
|
|
|
|
def load_user(user_uuid):
|
2017-01-19 20:23:15 +00:00
|
|
|
logger.debug('User loader loading deferred user with uuid: %s', user_uuid)
|
2015-01-17 03:41:54 +00:00
|
|
|
return LoginWrappedDBUser(user_uuid)
|
|
|
|
|
|
|
|
|
2015-09-01 19:03:46 +00:00
|
|
|
get_app_url = partial(get_app_url, app.config)
|