From ff294d6c52d004262a739e3fb677d73e47b86fac Mon Sep 17 00:00:00 2001 From: Sam Chow Date: Thu, 16 Aug 2018 15:42:01 -0400 Subject: [PATCH] Add init script to download extra ca certs --- conf/init/02_get_kube_certs.py | 71 +++++++++++++++++++ conf/init/02_get_kube_certs.sh | 12 ++++ conf/init/certs_install.sh | 30 ++++---- config_app/conf/server-base.conf | 3 + config_app/config_endpoints/api/superuser.py | 4 +- .../config/TransientDirectoryProvider.py | 4 ++ .../kube-deploy-modal.component.ts | 5 +- 7 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 conf/init/02_get_kube_certs.py create mode 100755 conf/init/02_get_kube_certs.sh diff --git a/conf/init/02_get_kube_certs.py b/conf/init/02_get_kube_certs.py new file mode 100644 index 000000000..3f88a09ac --- /dev/null +++ b/conf/init/02_get_kube_certs.py @@ -0,0 +1,71 @@ +import json +import os +import base64 + +from requests import Request, Session + +QUAYPATH = os.environ.get('QUAYPATH', '.') +KUBE_EXTRA_CA_CERTDIR = os.environ.get('KUBE_EXTRA_CA_CERTDIR', '%s/conf/kube_extra_certs' % QUAYPATH) + +KUBERNETES_API_HOST = os.environ.get('KUBERNETES_SERVICE_HOST', '') +port = os.environ.get('KUBERNETES_SERVICE_PORT') +if port: + KUBERNETES_API_HOST += ':' + port + +SERVICE_ACCOUNT_TOKEN_PATH = '/var/run/secrets/kubernetes.io/serviceaccount/token' + +QE_NAMESPACE = os.environ.get('QE_K8S_NAMESPACE', 'quay-enterprise') +QE_CONFIG_SECRET = os.environ.get('QE_K8S_CONFIG_SECRET', 'quay-enterprise-config-secret') +EXTRA_CA_DIRECTORY_PREFIX = 'extra_ca_certs_' + + +def _lookup_secret(service_token): + secret_url = 'namespaces/%s/secrets/%s' % (QE_NAMESPACE, QE_CONFIG_SECRET) + response = _execute_k8s_api(service_token, 'GET', secret_url) + if response.status_code != 200: + raise Exception('Cannot get the config secret') + return json.loads(response.text) + +def _execute_k8s_api(service_account_token, method, relative_url, data=None, api_prefix='api/v1', content_type='application/json'): + headers = { + 'Authorization': 'Bearer ' + service_account_token + } + + if data: + headers['Content-Type'] = content_type + + data = json.dumps(data) if data else None + session = Session() + url = 'https://%s/%s/%s' % (KUBERNETES_API_HOST, api_prefix, relative_url) + + request = Request(method, url, data=data, headers=headers) + return session.send(request.prepare(), verify=False, timeout=2) + +def is_extra_cert(key): + return key.find(EXTRA_CA_DIRECTORY_PREFIX) == 0 + +def main(): + # Load the service account token from the local store. + if not os.path.exists(SERVICE_ACCOUNT_TOKEN_PATH): + raise Exception('Cannot load Kubernetes service account token') + + with open(SERVICE_ACCOUNT_TOKEN_PATH, 'r') as f: + service_token = f.read() + + secret_data = _lookup_secret(service_token).get('data', {}) + cert_keys = filter(is_extra_cert, secret_data.keys()) + + for cert_key in cert_keys: + if not os.path.exists(KUBE_EXTRA_CA_CERTDIR): + os.mkdir(KUBE_EXTRA_CA_CERTDIR) + + cert_value = base64.b64decode(secret_data[cert_key]) + cert_filename = cert_key.replace(EXTRA_CA_DIRECTORY_PREFIX, '') + print "Found an extra cert %s in config-secret, copying to kube ca dir" + + with open(os.path.join(KUBE_EXTRA_CA_CERTDIR, cert_filename), 'w') as f: + f.write(cert_value) + + +if __name__ == '__main__': + main() diff --git a/conf/init/02_get_kube_certs.sh b/conf/init/02_get_kube_certs.sh new file mode 100755 index 000000000..997847d17 --- /dev/null +++ b/conf/init/02_get_kube_certs.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +QUAYDIR=${QUAYDIR:-"/"} +QUAYPATH=${QUAYPATH:-"."} +QUAYCONF=${QUAYCONF:-"$QUAYPATH/conf"} + +cd $QUAYDIR + +if [[ "$KUBERNETES_SERVICE_HOST" != "" ]];then + echo "Running on kubernetes, attempting to retrieve extra certs from secret" + venv/bin/python $QUAYCONF/init/02_get_kube_certs.py +fi \ No newline at end of file diff --git a/conf/init/certs_install.sh b/conf/init/certs_install.sh index 29be4e8e5..e58d282bb 100755 --- a/conf/init/certs_install.sh +++ b/conf/init/certs_install.sh @@ -3,6 +3,12 @@ set -e QUAYPATH=${QUAYPATH:-"."} QUAYCONF=${QUAYCONF:-"$QUAYPATH/conf/stack"} QUAYCONFIG=${QUAYCONFIG:-"$QUAYCONF/stack"} +CERTDIR=${QUAYCONFIG/extra_ca_certs} + +# If we're running under kube, the previous script (02_get_kube_certs.sh) will put the certs in a different location +if [[ "$KUBERNETES_SERVICE_HOST" != "" ]];then + CERTDIR=${KUBE_EXTRA_CA_CERTDIR:-"$QUAYPATH/conf/kube_extra_certs"} +fi cd ${QUAYDIR:-"/quay-registry"} @@ -13,25 +19,25 @@ then fi # Add extra trusted certificates (as a directory) -if [ -d $QUAYCONFIG/extra_ca_certs ]; then - if test "$(ls -A "$QUAYCONFIG/extra_ca_certs")"; then - echo "Installing extra certificates found in $QUAYCONFIG/extra_ca_certs directory" - cp $QUAYCONFIG/extra_ca_certs/* /usr/local/share/ca-certificates/ - cat $QUAYCONFIG/extra_ca_certs/* >> venv/lib/python2.7/site-packages/requests/cacert.pem - cat $QUAYCONFIG/extra_ca_certs/* >> venv/lib/python2.7/site-packages/certifi/cacert.pem +if [ -d $CERTDIR ]; then + if test "$(ls -A "$CERTDIR")"; then + echo "Installing extra certificates found in $CERTDIR directory" + cp $CERTDIR/* /usr/local/share/ca-certificates/ + cat $CERTDIR/* >> venv/lib/python2.7/site-packages/requests/cacert.pem + cat $CERTDIR/* >> venv/lib/python2.7/site-packages/certifi/cacert.pem fi fi # Add extra trusted certificates (as a file) -if [ -f $QUAYCONFIG/extra_ca_certs ]; then - echo "Installing extra certificates found in $QUAYCONFIG/extra_ca_certs file" - csplit -z -f /usr/local/share/ca-certificates/extra-ca- $QUAYCONFIG/extra_ca_certs '/-----BEGIN CERTIFICATE-----/' '{*}' - cat $QUAYCONFIG/extra_ca_certs >> venv/lib/python2.7/site-packages/requests/cacert.pem - cat $QUAYCONFIG/extra_ca_certs >> venv/lib/python2.7/site-packages/certifi/cacert.pem +if [ -f $CERTDIR ]; then + echo "Installing extra certificates found in $CERTDIR file" + csplit -z -f /usr/local/share/ca-certificates/extra-ca- $CERTDIR '/-----BEGIN CERTIFICATE-----/' '{*}' + cat $CERTDIR >> venv/lib/python2.7/site-packages/requests/cacert.pem + cat $CERTDIR >> venv/lib/python2.7/site-packages/certifi/cacert.pem fi # Add extra trusted certificates (prefixed) -for f in $(find $QUAYCONFIG/ -maxdepth 1 -type f -name "extra_ca*") +for f in $(find $CERTDIR/ -maxdepth 1 -type f -name "extra_ca*") do echo "Installing extra cert $f" cp "$f" /usr/local/share/ca-certificates/ diff --git a/config_app/conf/server-base.conf b/config_app/conf/server-base.conf index b1732ed20..bb7af7bf9 100644 --- a/config_app/conf/server-base.conf +++ b/config_app/conf/server-base.conf @@ -10,6 +10,9 @@ proxy_redirect off; proxy_set_header Transfer-Encoding $http_transfer_encoding; +# The DB migrations sometimes take a while, so increase timeoutso we don't report an error +proxy_read_timeout 300s; + location / { proxy_pass http://web_app_server; } diff --git a/config_app/config_endpoints/api/superuser.py b/config_app/config_endpoints/api/superuser.py index 7cca94012..a3f1039b3 100644 --- a/config_app/config_endpoints/api/superuser.py +++ b/config_app/config_endpoints/api/superuser.py @@ -54,8 +54,8 @@ class SuperUserCustomCertificate(ApiResource): return '', 204 # Call the update script with config dir location to install the certificate immediately. - if subprocess.call([os.path.join(INIT_SCRIPTS_LOCATION, 'certs_install.sh')], - env={ 'QUAYCONFIG': config_provider.get_config_dir_path() }) != 0: + cert_dir = os.path.join(config_provider.get_config_dir_path(), EXTRA_CA_DIRECTORY) + if subprocess.call([os.path.join(INIT_SCRIPTS_LOCATION, 'certs_install.sh')], env={ 'CERTDIR': cert_dir }) != 0: raise Exception('Could not install certificates') return '', 204 diff --git a/config_app/config_util/config/TransientDirectoryProvider.py b/config_app/config_util/config/TransientDirectoryProvider.py index 33c6f4216..7dab7dcf3 100644 --- a/config_app/config_util/config/TransientDirectoryProvider.py +++ b/config_app/config_util/config/TransientDirectoryProvider.py @@ -44,6 +44,10 @@ class TransientDirectoryProvider(FileConfigProvider): def save_configuration_to_kubernetes(self): data = {} + # Kubernetes secrets don't have sub-directories, so for the extra_ca_certs dir + # we have to put the extra certs in with a prefix, and then one of our init scripts + # (02_get_kube_certs.sh) will expand the prefixed certs into the equivalent directory + # so that they'll be installed correctly on startup by the certs_install script certs_dir = os.path.join(self.config_volume, EXTRA_CA_DIRECTORY) if os.path.exists(certs_dir): for extra_cert in os.listdir(certs_dir): diff --git a/config_app/js/components/kube-deploy-modal/kube-deploy-modal.component.ts b/config_app/js/components/kube-deploy-modal/kube-deploy-modal.component.ts index 47eec013b..d66b442da 100644 --- a/config_app/js/components/kube-deploy-modal/kube-deploy-modal.component.ts +++ b/config_app/js/components/kube-deploy-modal/kube-deploy-modal.component.ts @@ -24,7 +24,7 @@ export class KubeDeployModalComponent { this.state = 'loadingDeployments'; ApiService.scGetNumDeployments().then(resp => { - this.deploymentsStatus = resp.items.map(dep => ({ name: dep.metadata.name, numPods: dep.status.replicas })); + this.deploymentsStatus = resp.items.map(dep => ({ name: dep.metadata.name, numPods: dep.spec.replicas })); this.state = 'readyToDeploy'; }).catch(err => { this.state = 'error'; @@ -37,7 +37,7 @@ export class KubeDeployModalComponent { deployConfiguration(): void { this.ApiService.scDeployConfiguration().then(() => { - const deploymentNames: string[]= this.deploymentsStatus.map(dep => dep.name); + const deploymentNames: string[] = this.deploymentsStatus.map(dep => dep.name); this.ApiService.scCycleQEDeployments({ deploymentNames }).then(() => { this.state = 'deployed' @@ -46,7 +46,6 @@ export class KubeDeployModalComponent { this.errorMessage = `Could cycle the deployments with the new configuration. Error: ${err.toString()}`; }) }).catch(err => { - console.log(err) this.state = 'error'; this.errorMessage = `Could not deploy the configuration. Error: ${err.toString()}`; })