132 lines
3.9 KiB
Python
132 lines
3.9 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
from datetime import datetime, timedelta
|
||
|
from urlparse import urlunparse
|
||
|
|
||
|
from jinja2 import Template
|
||
|
from cachetools.func import lru_cache
|
||
|
|
||
|
import logging
|
||
|
import release
|
||
|
import os.path
|
||
|
|
||
|
from app import app
|
||
|
from data.model import ServiceKeyDoesNotExist
|
||
|
from data.model.release import set_region_release
|
||
|
from data.model.service_keys import get_service_key
|
||
|
from util.config.database import sync_database_with_config
|
||
|
from util.generatepresharedkey import generate_key
|
||
|
from _init import CONF_DIR
|
||
|
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
@lru_cache(maxsize=1)
|
||
|
def get_audience():
|
||
|
audience = app.config.get('JWTPROXY_AUDIENCE')
|
||
|
|
||
|
if audience:
|
||
|
return audience
|
||
|
|
||
|
scheme = app.config.get('PREFERRED_URL_SCHEME')
|
||
|
hostname = app.config.get('SERVER_HOSTNAME')
|
||
|
|
||
|
# hostname includes port, use that
|
||
|
if ':' in hostname:
|
||
|
return urlunparse((scheme, hostname, '', '', '', ''))
|
||
|
|
||
|
# no port, guess based on scheme
|
||
|
if scheme == 'https':
|
||
|
port = '443'
|
||
|
else:
|
||
|
port = '80'
|
||
|
|
||
|
return urlunparse((scheme, hostname + ':' + port, '', '', '', ''))
|
||
|
|
||
|
|
||
|
def _verify_service_key():
|
||
|
try:
|
||
|
with open(app.config['INSTANCE_SERVICE_KEY_KID_LOCATION']) as f:
|
||
|
quay_key_id = f.read()
|
||
|
|
||
|
try:
|
||
|
get_service_key(quay_key_id, approved_only=False)
|
||
|
assert os.path.exists(app.config['INSTANCE_SERVICE_KEY_LOCATION'])
|
||
|
return quay_key_id
|
||
|
except ServiceKeyDoesNotExist:
|
||
|
logger.exception('Could not find non-expired existing service key %s; creating a new one',
|
||
|
quay_key_id)
|
||
|
return None
|
||
|
|
||
|
# Found a valid service key, so exiting.
|
||
|
except IOError:
|
||
|
logger.exception('Could not load existing service key; creating a new one')
|
||
|
return None
|
||
|
|
||
|
|
||
|
def setup_jwt_proxy():
|
||
|
"""
|
||
|
Creates a service key for quay to use in the jwtproxy and generates the JWT proxy configuration.
|
||
|
"""
|
||
|
if os.path.exists(os.path.join(CONF_DIR, 'jwtproxy_conf.yaml')):
|
||
|
# Proxy is already setup. Make sure the service key is still valid.
|
||
|
quay_key_id = _verify_service_key()
|
||
|
if quay_key_id is not None:
|
||
|
return
|
||
|
|
||
|
# Ensure we have an existing key if in read-only mode.
|
||
|
if app.config.get('REGISTRY_STATE', 'normal') == 'readonly':
|
||
|
quay_key_id = _verify_service_key()
|
||
|
if quay_key_id is None:
|
||
|
raise Exception('No valid service key found for read-only registry.')
|
||
|
else:
|
||
|
# Generate the key for this Quay instance to use.
|
||
|
minutes_until_expiration = app.config.get('INSTANCE_SERVICE_KEY_EXPIRATION', 120)
|
||
|
expiration = datetime.now() + timedelta(minutes=minutes_until_expiration)
|
||
|
quay_key, quay_key_id = generate_key(app.config['INSTANCE_SERVICE_KEY_SERVICE'],
|
||
|
get_audience(), expiration_date=expiration)
|
||
|
|
||
|
with open(app.config['INSTANCE_SERVICE_KEY_KID_LOCATION'], mode='w') as f:
|
||
|
f.truncate(0)
|
||
|
f.write(quay_key_id)
|
||
|
|
||
|
with open(app.config['INSTANCE_SERVICE_KEY_LOCATION'], mode='w') as f:
|
||
|
f.truncate(0)
|
||
|
f.write(quay_key.exportKey())
|
||
|
|
||
|
# Generate the JWT proxy configuration.
|
||
|
audience = get_audience()
|
||
|
registry = audience + '/keys'
|
||
|
security_issuer = app.config.get('SECURITY_SCANNER_ISSUER_NAME', 'security_scanner')
|
||
|
|
||
|
with open(os.path.join(CONF_DIR, 'jwtproxy_conf.yaml.jnj')) as f:
|
||
|
template = Template(f.read())
|
||
|
rendered = template.render(
|
||
|
conf_dir=CONF_DIR,
|
||
|
audience=audience,
|
||
|
registry=registry,
|
||
|
key_id=quay_key_id,
|
||
|
security_issuer=security_issuer,
|
||
|
service_key_location=app.config['INSTANCE_SERVICE_KEY_LOCATION'],
|
||
|
)
|
||
|
|
||
|
with open(os.path.join(CONF_DIR, 'jwtproxy_conf.yaml'), 'w') as f:
|
||
|
f.write(rendered)
|
||
|
|
||
|
|
||
|
def main():
|
||
|
if not app.config.get('SETUP_COMPLETE', False):
|
||
|
raise Exception('Your configuration bundle is either not mounted or setup has not been completed')
|
||
|
|
||
|
sync_database_with_config(app.config)
|
||
|
setup_jwt_proxy()
|
||
|
|
||
|
# Record deploy
|
||
|
if release.REGION and release.GIT_HEAD:
|
||
|
set_region_release(release.SERVICE, release.REGION, release.GIT_HEAD)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|