2013-09-23 12:37:40 -04:00
|
|
|
import logging
|
2013-09-30 23:54:12 -04:00
|
|
|
import os
|
2014-06-23 11:24:54 -04:00
|
|
|
import json
|
2013-09-23 12:37:40 -04:00
|
|
|
|
2015-09-01 15:03:46 -04:00
|
|
|
from functools import partial
|
2015-07-15 17:25:41 -04:00
|
|
|
from flask import Flask, request, Request, _request_ctx_stack
|
2013-09-20 18:38:17 -04:00
|
|
|
from flask.ext.principal import Principal
|
2015-01-16 22:41:54 -05:00
|
|
|
from flask.ext.login import LoginManager, UserMixin
|
2013-09-27 19:29:01 -04:00
|
|
|
from flask.ext.mail import Mail
|
2015-06-22 17:37:13 -04:00
|
|
|
from werkzeug.routing import BaseConverter
|
2015-09-28 15:43:20 -04:00
|
|
|
from jwkest.jwk import RSAKey
|
|
|
|
from Crypto.PublicKey import RSA
|
2013-10-03 16:19:01 -04:00
|
|
|
|
2014-04-03 18:47:17 -04:00
|
|
|
import features
|
|
|
|
|
2015-02-05 15:37:14 -05:00
|
|
|
from avatars.avatars import Avatar
|
2014-04-03 17:31:46 -04:00
|
|
|
from storage import Storage
|
2014-05-13 12:17:26 -04:00
|
|
|
from data import model
|
|
|
|
from data import database
|
2014-04-03 17:31:46 -04:00
|
|
|
from data.userfiles import Userfiles
|
2014-05-09 17:39:43 -04:00
|
|
|
from data.users import UserAuthentication
|
2014-04-10 15:20:16 -04:00
|
|
|
from data.billing import Billing
|
2014-05-09 18:45:11 -04:00
|
|
|
from data.buildlogs import BuildLogs
|
2014-09-08 16:43:17 -04:00
|
|
|
from data.archivedlogs import LogArchive
|
2014-05-30 14:25:29 -04:00
|
|
|
from data.userevent import UserEventsBuilderModule
|
2016-06-28 14:36:17 -04:00
|
|
|
from data.queue import WorkQueue, BuildMetricQueueReporter
|
2015-09-01 15:03:46 -04:00
|
|
|
from util import get_app_url
|
2015-08-03 15:49:10 -04:00
|
|
|
from util.saas.analytics import Analytics
|
|
|
|
from util.saas.exceptionlog import Sentry
|
2015-01-09 16:23:31 -05:00
|
|
|
from util.names import urn_generator
|
2015-09-04 16:14:46 -04:00
|
|
|
from util.config.oauth import (GoogleOAuthConfig, GithubOAuthConfig, GitLabOAuthConfig,
|
|
|
|
DexOAuthConfig)
|
|
|
|
|
2015-08-03 15:49:10 -04:00
|
|
|
from util.security.signing import Signer
|
2016-05-31 16:48:19 -04:00
|
|
|
from util.security.instancekeys import InstanceKeys
|
2015-08-12 15:14:09 -04:00
|
|
|
from util.saas.cloudwatch import start_cloudwatch_sender
|
2015-07-24 14:52:19 -04:00
|
|
|
from util.config.provider import get_config_provider
|
2015-01-09 16:23:31 -05:00
|
|
|
from util.config.configutil import generate_secret_key
|
2015-01-20 12:43:11 -05:00
|
|
|
from util.config.superusermanager import SuperUserManager
|
2015-11-10 15:01:33 -05:00
|
|
|
from util.secscan.api import SecurityScannerAPI
|
2016-07-01 14:16:15 -04:00
|
|
|
from util.metrics.metricqueue import MetricQueue
|
|
|
|
from util.metrics.prometheus import PrometheusPlugin
|
2014-04-07 16:59:22 -04:00
|
|
|
|
2015-02-04 15:29:24 -05:00
|
|
|
OVERRIDE_CONFIG_DIRECTORY = 'conf/stack/'
|
2014-08-21 20:36:11 -04:00
|
|
|
OVERRIDE_CONFIG_YAML_FILENAME = 'conf/stack/config.yaml'
|
|
|
|
OVERRIDE_CONFIG_PY_FILENAME = 'conf/stack/config.py'
|
|
|
|
|
2014-06-23 11:24:54 -04:00
|
|
|
OVERRIDE_CONFIG_KEY = 'QUAY_OVERRIDE_CONFIG'
|
2014-04-07 16:59:22 -04:00
|
|
|
|
2015-09-28 15:43:20 -04:00
|
|
|
DOCKER_V2_SIGNINGKEY_FILENAME = 'docker_v2.pem'
|
|
|
|
|
2013-09-20 11:55:44 -04:00
|
|
|
app = Flask(__name__)
|
2013-10-03 16:19:01 -04:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2015-07-27 11:17:44 -04:00
|
|
|
# Instantiate the configuration.
|
2015-07-24 14:52:19 -04:00
|
|
|
is_testing = 'TEST' in os.environ
|
2015-07-27 11:17:44 -04:00
|
|
|
is_kubernetes = 'KUBERNETES_SERVICE_HOST' in os.environ
|
2015-07-24 14:52:19 -04:00
|
|
|
config_provider = get_config_provider(OVERRIDE_CONFIG_DIRECTORY, 'config.yaml', 'config.py',
|
2015-07-27 11:17:44 -04:00
|
|
|
testing=is_testing, kubernetes=is_kubernetes)
|
2015-01-09 16:23:31 -05:00
|
|
|
|
2015-07-24 14:52:19 -04:00
|
|
|
if is_testing:
|
2014-04-07 16:59:22 -04: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 16:23:31 -05:00
|
|
|
app.teardown_request(database.close_db_filter)
|
2014-04-03 17:31:46 -04:00
|
|
|
|
2015-01-09 16:23:31 -05:00
|
|
|
# Load the override config via the provider.
|
2015-07-24 14:52:19 -04:00
|
|
|
config_provider.update_app_config(app.config)
|
2013-09-27 19:29:01 -04:00
|
|
|
|
2015-01-09 16:23:31 -05: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 13:58:08 -04:00
|
|
|
|
2015-11-06 13:34:49 -05: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 13:41:50 -04: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.
|
|
|
|
if app.config['PREFERRED_URL_SCHEME'] == 'https':
|
|
|
|
app.config['SESSION_COOKIE_SECURE'] = True
|
|
|
|
|
|
|
|
# Load features from config.
|
|
|
|
features.import_features(app.config)
|
|
|
|
|
2014-10-14 13:58:08 -04: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():
|
2015-02-11 14:15:18 -05:00
|
|
|
logger.debug('Starting request: %s', request.path)
|
2014-10-14 13:58:08 -04:00
|
|
|
|
|
|
|
|
|
|
|
@app.after_request
|
|
|
|
def _request_end(r):
|
2015-02-11 14:15:18 -05:00
|
|
|
logger.debug('Ending request: %s', request.path)
|
2014-10-14 13:58:08 -04:00
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
|
|
class InjectingFilter(logging.Filter):
|
|
|
|
def filter(self, record):
|
2015-02-11 14:15:18 -05:00
|
|
|
if _request_ctx_stack.top is not None:
|
|
|
|
record.msg = '[%s] %s' % (request.request_id, record.msg)
|
2014-10-14 13:58:08 -04:00
|
|
|
return True
|
|
|
|
|
2016-01-28 11:30:47 -05:00
|
|
|
root_logger = logging.getLogger()
|
|
|
|
|
2015-02-11 14:15:18 -05:00
|
|
|
# Add the request id filter to all handlers of the root logger
|
2016-01-28 11:30:47 -05:00
|
|
|
for handler in root_logger.handlers:
|
2015-02-11 14:15:18 -05:00
|
|
|
handler.addFilter(InjectingFilter())
|
2014-10-14 13:58:08 -04:00
|
|
|
|
|
|
|
app.request_class = RequestWithId
|
|
|
|
|
2016-01-21 15:40:51 -05:00
|
|
|
# Register custom converters.
|
|
|
|
class RegexConverter(BaseConverter):
|
|
|
|
""" Converter for handling custom regular expression patterns in paths. """
|
2016-05-04 17:38:21 -04:00
|
|
|
def __init__(self, url_map, regex_value):
|
2016-01-21 15:40:51 -05:00
|
|
|
super(RegexConverter, self).__init__(url_map)
|
2016-05-04 17:38:21 -04:00
|
|
|
self.regex = regex_value
|
|
|
|
|
2016-01-21 15:40:51 -05:00
|
|
|
|
|
|
|
class RepositoryPathConverter(BaseConverter):
|
|
|
|
""" Converter for handling repository paths. Handles both library and non-library paths (if
|
|
|
|
configured).
|
|
|
|
"""
|
2016-05-04 17:38:21 -04:00
|
|
|
def __init__(self, url_map):
|
2016-01-21 15:40:51 -05:00
|
|
|
super(RepositoryPathConverter, self).__init__(url_map)
|
|
|
|
self.weight = 200
|
|
|
|
|
|
|
|
if features.LIBRARY_SUPPORT:
|
|
|
|
# Allow names without namespaces.
|
|
|
|
self.regex = r'[^/]+(/[^/]+)?'
|
|
|
|
else:
|
|
|
|
self.regex = r'([^/]+/[^/]+)'
|
|
|
|
|
|
|
|
|
|
|
|
class APIRepositoryPathConverter(BaseConverter):
|
|
|
|
""" Converter for handling repository paths. Does not handle library paths.
|
|
|
|
"""
|
2016-05-04 17:38:21 -04:00
|
|
|
def __init__(self, url_map):
|
2016-01-21 15:40:51 -05:00
|
|
|
super(APIRepositoryPathConverter, self).__init__(url_map)
|
|
|
|
self.weight = 200
|
|
|
|
self.regex = r'([^/]+/[^/]+)'
|
|
|
|
|
|
|
|
|
|
|
|
app.url_map.converters['regex'] = RegexConverter
|
|
|
|
app.url_map.converters['repopath'] = RepositoryPathConverter
|
|
|
|
app.url_map.converters['apirepopath'] = APIRepositoryPathConverter
|
|
|
|
|
2014-03-17 12:01:13 -04:00
|
|
|
Principal(app, use_sessions=False)
|
2013-09-20 18:38:17 -04:00
|
|
|
|
2014-11-24 19:25:13 -05:00
|
|
|
avatar = Avatar(app)
|
2014-04-08 23:05:45 -04:00
|
|
|
login_manager = LoginManager(app)
|
|
|
|
mail = Mail(app)
|
2016-02-01 15:07:46 -05:00
|
|
|
prometheus = PrometheusPlugin(app)
|
|
|
|
metric_queue = MetricQueue(prometheus)
|
2016-01-15 11:15:40 -05:00
|
|
|
storage = Storage(app, metric_queue)
|
2014-09-09 15:54:03 -04:00
|
|
|
userfiles = Userfiles(app, storage)
|
2014-09-11 11:18:28 -04:00
|
|
|
log_archive = LogArchive(app, storage)
|
2014-04-08 23:05:45 -04:00
|
|
|
analytics = Analytics(app)
|
2014-04-10 15:20:16 -04:00
|
|
|
billing = Billing(app)
|
2014-04-28 18:59:22 -04:00
|
|
|
sentry = Sentry(app)
|
2014-05-09 18:45:11 -04:00
|
|
|
build_logs = BuildLogs(app)
|
2016-05-03 15:02:39 -04:00
|
|
|
authentication = UserAuthentication(app, config_provider, OVERRIDE_CONFIG_DIRECTORY)
|
2014-05-30 14:25:29 -04:00
|
|
|
userevents = UserEventsBuilderModule(app)
|
2015-01-20 12:43:11 -05:00
|
|
|
superusers = SuperUserManager(app)
|
2016-05-23 17:10:03 -04:00
|
|
|
signer = Signer(app, config_provider)
|
2016-05-31 16:48:19 -04:00
|
|
|
instance_keys = InstanceKeys(app)
|
|
|
|
|
2015-08-12 16:31:01 -04:00
|
|
|
start_cloudwatch_sender(metric_queue, app)
|
2015-01-16 13:44:29 -05:00
|
|
|
|
|
|
|
tf = app.config['DB_TRANSACTION_FACTORY']
|
2014-05-21 19:50:37 -04:00
|
|
|
|
2015-01-07 16:20:51 -05:00
|
|
|
github_login = GithubOAuthConfig(app.config, 'GITHUB_LOGIN_CONFIG')
|
|
|
|
github_trigger = GithubOAuthConfig(app.config, 'GITHUB_TRIGGER_CONFIG')
|
2015-05-02 17:54:48 -04:00
|
|
|
gitlab_trigger = GitLabOAuthConfig(app.config, 'GITLAB_TRIGGER_CONFIG')
|
2015-01-07 16:20:51 -05:00
|
|
|
google_login = GoogleOAuthConfig(app.config, 'GOOGLE_LOGIN_CONFIG')
|
2015-09-04 16:14:46 -04:00
|
|
|
dex_login = DexOAuthConfig(app.config, 'DEX_LOGIN_CONFIG')
|
|
|
|
|
|
|
|
oauth_apps = [github_login, github_trigger, gitlab_trigger, google_login, dex_login]
|
2014-11-05 16:43:37 -05:00
|
|
|
|
2015-10-06 17:07:27 -04:00
|
|
|
image_replication_queue = WorkQueue(app.config['REPLICATION_QUEUE_NAME'], tf)
|
2014-05-22 12:13:41 -04:00
|
|
|
dockerfile_build_queue = WorkQueue(app.config['DOCKERFILE_BUILD_QUEUE_NAME'], tf,
|
2016-06-28 14:36:17 -04:00
|
|
|
reporter=BuildMetricQueueReporter(metric_queue))
|
2015-10-06 17:07:27 -04:00
|
|
|
notification_queue = WorkQueue(app.config['NOTIFICATION_QUEUE_NAME'], tf)
|
2015-11-09 18:30:14 -05:00
|
|
|
secscan_notification_queue = WorkQueue(app.config['SECSCAN_NOTIFICATION_QUEUE_NAME'], tf)
|
2016-05-04 17:40:09 -04:00
|
|
|
secscan_api = SecurityScannerAPI(app, app.config, storage)
|
2014-05-22 12:13:41 -04:00
|
|
|
|
2015-09-28 15:43:20 -04: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 15:32:17 -05:00
|
|
|
|
2014-05-13 12:17:26 -04:00
|
|
|
database.configure(app.config)
|
|
|
|
model.config.app_config = app.config
|
|
|
|
model.config.store = storage
|
2014-07-29 13:47:54 -04:00
|
|
|
|
2015-01-16 22:41:54 -05:00
|
|
|
@login_manager.user_loader
|
|
|
|
def load_user(user_uuid):
|
|
|
|
logger.debug('User loader loading deferred user with uuid: %s' % user_uuid)
|
|
|
|
return LoginWrappedDBUser(user_uuid)
|
|
|
|
|
|
|
|
class LoginWrappedDBUser(UserMixin):
|
|
|
|
def __init__(self, user_uuid, db_user=None):
|
|
|
|
self._uuid = user_uuid
|
|
|
|
self._db_user = db_user
|
|
|
|
|
|
|
|
def db_user(self):
|
|
|
|
if not self._db_user:
|
2015-07-15 17:25:41 -04:00
|
|
|
self._db_user = model.user.get_user_by_uuid(self._uuid)
|
2015-01-16 22:41:54 -05:00
|
|
|
return self._db_user
|
|
|
|
|
2016-01-27 11:36:40 -05:00
|
|
|
@property
|
2015-01-16 22:41:54 -05:00
|
|
|
def is_authenticated(self):
|
|
|
|
return self.db_user() is not None
|
|
|
|
|
2016-01-27 11:36:40 -05:00
|
|
|
@property
|
2015-01-16 22:41:54 -05:00
|
|
|
def is_active(self):
|
|
|
|
return self.db_user().verified
|
|
|
|
|
|
|
|
def get_id(self):
|
|
|
|
return unicode(self._uuid)
|
|
|
|
|
2015-09-01 15:03:46 -04:00
|
|
|
get_app_url = partial(get_app_url, app.config)
|