From 92f74ae2a3506cea96635e922c0217dc1ef96bb9 Mon Sep 17 00:00:00 2001 From: Jan Malkiewicz Date: Wed, 19 Aug 2020 15:49:10 +0200 Subject: Add init container support for truststore merger. Issue-ID: DCAEGEN2-2253 Signed-off-by: Jan Malkiewicz Change-Id: I98f27834b36cad333728d41ec079b86a090e77f3 --- .gitignore | 23 +++++++++++++ k8s/ChangeLog.md | 3 ++ k8s/Makefile | 15 +++++++++ k8s/README.md | 3 +- k8s/centos.wagon-builder.dockerfile | 16 +++++++++ k8s/configure/configure.py | 4 +++ k8s/k8sclient/k8sclient.py | 65 +++++++++++++++++++++++++++++++++---- k8s/k8splugin/tasks.py | 4 ++- k8s/k8splugin_types.yaml | 2 +- k8s/pom.xml | 2 +- k8s/setup.py | 4 +-- k8s/tests/common.py | 35 ++++++++++++++++++-- k8s/tests/test_k8sclient_deploy.py | 4 +++ 13 files changed, 165 insertions(+), 15 deletions(-) create mode 100644 k8s/Makefile create mode 100644 k8s/centos.wagon-builder.dockerfile diff --git a/.gitignore b/.gitignore index 5a7dcdf..f99c97a 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,26 @@ target/ # tox output xunit-results* htmlcov + +# Idea IntelliJ project files +/.idea/* +*.iml + +# Output files +/k8splugin-*.tgz +/k8splugin-*.zip +/pgaas-*.tgz +/pgaas-*.zip +/clamppolicyplugin-*.tgz +/clamppolicyplugin-*.zip +/dcaepolicyplugin-*.tgz +/dcaepolicyplugin-*.zip +/dmaap-*.tgz +/dmaap-*.zip +/helm-*.tgz +/helm-*.zip +/relationshipplugin-*.tgz +/relationshipplugin-*.zip +/sshkeyshare-*.tgz +/sshkeyshare-*.zip +/k8s/k8splugin-*.zip diff --git a/k8s/ChangeLog.md b/k8s/ChangeLog.md index d7b1da6..7c014e8 100644 --- a/k8s/ChangeLog.md +++ b/k8s/ChangeLog.md @@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [3.4.0] +* DCAEGEN2-2253 - Add support to truststore merger init container + ## [3.3.0] * DCAEGEN2-2252 - Add support to request certificates from CMPv2 server in DCAE cloudify blueprints - handle incorrect blueprint diff --git a/k8s/Makefile b/k8s/Makefile new file mode 100644 index 0000000..0add666 --- /dev/null +++ b/k8s/Makefile @@ -0,0 +1,15 @@ +build-wagon: + @echo "### Copy generated k8s plugin" + cp ../k8splugin-*.zip . + + @echo "### Create docker image (CentOS)" + docker build -t k8splugin -f centos.wagon-builder.dockerfile . + + @echo "### Run docker container and generate wagon file" + docker run -t --name k8s_container k8splugin:latest + + @echo "### Copy wagon file from docker image to current directory" + docker cp k8s_container:/opt/app-root/src/output . + + @echo "### Remove docker container" + docker rm k8s_container diff --git a/k8s/README.md b/k8s/README.md index 21e7564..e310dee 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -67,7 +67,8 @@ The configuration is provided as JSON object with the following properties: - `state`: State name, for which certificate will be created - `organizational_unit`: Organizational unit name, for which certificate will be created - `location`: Location name, for which certificate will be created - + - `truststore_merger`: object containing configuration for setting up truststore-merger init container + - `image_tag`: truststore-merger image name and version #### Kubernetes access information The plugin accesses a Kubernetes cluster. The information and credentials for accessing a cluster are stored in a "kubeconfig" diff --git a/k8s/centos.wagon-builder.dockerfile b/k8s/centos.wagon-builder.dockerfile new file mode 100644 index 0000000..401c1a5 --- /dev/null +++ b/k8s/centos.wagon-builder.dockerfile @@ -0,0 +1,16 @@ +FROM centos/python-27-centos7:latest as cent + +# Sometimes it's necessary to set a proxy (e.g. in case of local development). +# To do it just uncomment those two env variables and set appriopriate values for them. +#ENV HTTP_PROXY= +#ENV HTTPS_PROXY= + +RUN bash -c "pip install --upgrade pip" +RUN bash -c "pip install wagon" + +COPY / k8s/ + +USER root +RUN bash -c "wagon create -r k8s/requirements.txt k8s/" +RUN bash -c "mkdir output" +RUN bash -c "mv k8splugin*.wgn output/" diff --git a/k8s/configure/configure.py b/k8s/configure/configure.py index c0dcd3f..fbf578c 100644 --- a/k8s/configure/configure.py +++ b/k8s/configure/configure.py @@ -50,6 +50,7 @@ EXT_TLS_LOCATION = "San-Francisco" EXT_TLS_KEYSTORE_PASSWORD = "secret" EXT_TLS_TRUSTSTORE_PASSWORD = "secret" +TRUST_STORE_MERGER_IMAGE = "nexus3.onap.org:10001/onap/org.onap.dcae.truststore-merger:1.2.0" CBS_BASE_URL = "https://config-binding-service:10443/service_component_all" def _set_defaults(): @@ -85,6 +86,9 @@ def _set_defaults(): "keystore_password" : EXT_TLS_KEYSTORE_PASSWORD, # Password to keystore file "truststore_password" : EXT_TLS_TRUSTSTORE_PASSWORD # Password to truststore file }, + "truststore_merger": { + "image_tag": TRUST_STORE_MERGER_IMAGE + }, "cbs": { "base_url" : CBS_BASE_URL # URL prefix for accessing config binding service } diff --git a/k8s/k8sclient/k8sclient.py b/k8s/k8sclient/k8sclient.py index b972ce1..4b58b0e 100644 --- a/k8s/k8sclient/k8sclient.py +++ b/k8s/k8sclient/k8sclient.py @@ -21,6 +21,7 @@ import os import re import uuid + from kubernetes import config, client, stream # Default values for readiness probe @@ -304,7 +305,7 @@ def _add_elk_logging_sidecar(containers, volumes, volume_mounts, component_name, # Finally create the container for the sidecar containers.append(_create_container_object("filebeat", filebeat["image"], False, volume_mounts=sidecar_volume_mounts)) -def _add_tls_init_container(init_containers, volumes, volume_mounts, tls_info, tls_config): +def _add_tls_init_container(ctx, init_containers, volumes, volume_mounts, tls_info, tls_config): # Adds an InitContainer to the pod to set up TLS certificate information. For components that act as a # server(tls_info["use_tls"] is True), the InitContainer will populate a directory with server and CA certificate # materials in various formats. For other components (tls_info["use_tls"] is False, or tls_info is not specified), @@ -312,6 +313,8 @@ def _add_tls_init_container(init_containers, volumes, volume_mounts, tls_info, t # In either case, the certificate directory is mounted onto the component container filesystem at the location # specified by tls_info["component_cert_dir"], if present, otherwise at the configured default mount point # (tls_config["component_cert_dir"]). + docker_image = tls_config["image"] + ctx.logger.info("Creating init container: TLS \n * [" + docker_image + "]") cert_directory = tls_info.get("cert_directory") or tls_config.get("component_cert_dir") env = {} @@ -323,9 +326,13 @@ def _add_tls_init_container(init_containers, volumes, volume_mounts, tls_info, t init_volume_mounts = [client.V1VolumeMount(name="tls-info", mount_path=tls_config["cert_path"])] # Create the init container - init_containers.append(_create_container_object("init-tls", tls_config["image"], False, volume_mounts=init_volume_mounts, env=env)) + init_containers.append(_create_container_object("init-tls", docker_image, False, volume_mounts=init_volume_mounts, env=env)) + +def _add_external_tls_init_container(ctx, init_containers, volumes, external_cert, external_tls_config): + # Adds an InitContainer to the pod which will generate external TLS certificates. + docker_image = external_tls_config["image_tag"] + ctx.logger.info("Creating init container: external TLS \n * [" + docker_image + "]") -def _add_external_tls_init_container(init_containers, volumes, external_cert, external_tls_config): env = {} output_path = external_cert.get("external_cert_directory") if not output_path.endswith('/'): @@ -355,7 +362,48 @@ def _add_external_tls_init_container(init_containers, volumes, external_cert, ex client.V1VolumeMount(name="tls-volume", mount_path=MOUNT_PATH)] # Create the init container - init_containers.append(_create_container_object("cert-service-client", external_tls_config["image_tag"], False, volume_mounts=init_volume_mounts, env=env)) + init_containers.append(_create_container_object("cert-service-client", docker_image, False, volume_mounts=init_volume_mounts, env=env)) + + +def _add_truststore_merger_init_container(ctx, init_containers, tls_info, tls_config, external_cert, truststore_merger_config): + # Adds an InitContainer to the pod to merge TLS and external TLS truststore into single file. + docker_image = truststore_merger_config["image_tag"] + ctx.logger.info("Creating init container: truststore merger \n * [" + docker_image + "]") + + tls_cert_dir = tls_info.get("cert_directory") or tls_config.get("component_cert_dir") + if not tls_cert_dir.endswith('/'): + tls_cert_dir += '/' + + tls_cert_file_path = tls_cert_dir + "trust.jks" + tls_cert_file_pass = tls_cert_dir + "trust.pass" + + ext_cert_dir = tls_cert_dir + "external/" + + output_type = (external_cert.get("cert_type") or 'p12').lower() + ext_truststore_path = ext_cert_dir + "truststore." + _get_file_extension(output_type) + ext_truststore_pass = '' + if output_type != 'pem': + ext_truststore_pass = ext_cert_dir + "truststore.pass" + + env = {} + env["TRUSTSTORES_PATHS"] = tls_cert_file_path + ":" + ext_truststore_path + env["TRUSTSTORES_PASSWORDS_PATHS"] = tls_cert_file_pass + ":" + ext_truststore_pass + + ctx.logger.info("TRUSTSTORES_PATHS: " + env["TRUSTSTORES_PATHS"]) + ctx.logger.info("TRUSTSTORES_PASSWORDS_PATHS: " + env["TRUSTSTORES_PASSWORDS_PATHS"]) + + # Create the volumes and volume mounts + init_volume_mounts = [client.V1VolumeMount(name="tls-info", mount_path=tls_cert_dir)] + + # Create the init container + init_containers.append(_create_container_object("truststore-merger", docker_image, False, volume_mounts=init_volume_mounts, env=env)) + +def _get_file_extension(output_type): + return { + 'p12': 'p12', + 'pem': 'pem', + 'jks': 'jks', + }[output_type] def _process_port_map(port_map): service_ports = [] # Ports exposed internally on the k8s network @@ -448,7 +496,7 @@ def _execute_command_in_pod(location, namespace, pod_name, command): return {"pod" : pod_name, "output" : output} -def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, **kwargs): +def deploy(ctx, namespace, component_name, image, replicas, always_pull, k8sconfig, **kwargs): ''' This will create a k8s Deployment and, if needed, one or two k8s Services. (We are being opinionated in our use of k8s... this code decides what k8s abstractions and features to use. @@ -476,6 +524,8 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, * "cert_path": mount point for certificate volume in init container "image": Docker image to use for TLS init container "component_cert_dir" : default mount point for certs + - truststore-merger: a dictionary of trustore-merger information: + "image_tag": docker image to use for truststore-merger init container kwargs may have: - volumes: array of volume objects, where a volume object is: {"host":{"path": "/path/on/host"}, "container":{"bind":"/path/on/container","mode":"rw_or_ro"} @@ -545,12 +595,13 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, * _add_elk_logging_sidecar(containers, volumes, volume_mounts, component_name, kwargs.get("log_info"), k8sconfig.get("filebeat")) # Set up TLS information - _add_tls_init_container(init_containers, volumes, volume_mounts, kwargs.get("tls_info") or {}, k8sconfig.get("tls")) + _add_tls_init_container(ctx, init_containers, volumes, volume_mounts, kwargs.get("tls_info") or {}, k8sconfig.get("tls")) # Set up external TLS information external_cert = kwargs.get("external_cert") if external_cert and external_cert.get("use_external_tls"): - _add_external_tls_init_container(init_containers, volumes, external_cert, k8sconfig.get("external_cert")) + _add_external_tls_init_container(ctx, init_containers, volumes, external_cert, k8sconfig.get("external_cert")) + _add_truststore_merger_init_container(ctx, init_containers, kwargs.get("tls_info") or {}, k8sconfig.get("tls"), external_cert, k8sconfig.get("truststore_merger")) # Create the container for the component # Make it the first container in the pod diff --git a/k8s/k8splugin/tasks.py b/k8s/k8splugin/tasks.py index e03e4d2..3d24277 100644 --- a/k8s/k8splugin/tasks.py +++ b/k8s/k8splugin/tasks.py @@ -292,7 +292,9 @@ def _create_and_start_container(container_name, image, **kwargs): ctx.logger.info("Passing k8sconfig: {}".format(plugin_conf)) replicas = kwargs.get("replicas", 1) resource_config = _get_resources(**kwargs) - _, dep = k8sclient.deploy(DCAE_NAMESPACE, + _, dep = k8sclient.deploy( + ctx, + DCAE_NAMESPACE, container_name, image, replicas=replicas, diff --git a/k8s/k8splugin_types.yaml b/k8s/k8splugin_types.yaml index 5d354f7..81bd7ff 100644 --- a/k8s/k8splugin_types.yaml +++ b/k8s/k8splugin_types.yaml @@ -23,7 +23,7 @@ plugins: k8s: executor: 'central_deployment_agent' package_name: k8splugin - package_version: 3.3.0 + package_version: 3.4.0 data_types: diff --git a/k8s/pom.xml b/k8s/pom.xml index c91dcbf..31cc737 100644 --- a/k8s/pom.xml +++ b/k8s/pom.xml @@ -28,7 +28,7 @@ limitations under the License. org.onap.dcaegen2.platform.plugins k8s k8s-plugin - 3.3.0-SNAPSHOT + 3.4.0-SNAPSHOT http://maven.apache.org UTF-8 diff --git a/k8s/setup.py b/k8s/setup.py index 7ff694b..fb6efcb 100644 --- a/k8s/setup.py +++ b/k8s/setup.py @@ -23,8 +23,8 @@ from setuptools import setup setup( name='k8splugin', description='Cloudify plugin for containerized components deployed using Kubernetes', - version="3.3.0", - author='J. F. Lucas, Michael Hwang, Tommy Carpenter, Joanna Jeremicz, Sylwia Jakubek', + version="3.4.0", + author='J. F. Lucas, Michael Hwang, Tommy Carpenter, Joanna Jeremicz, Sylwia Jakubek, Jan Malkiewicz', packages=['k8splugin','k8sclient','configure'], zip_safe=False, install_requires=[ diff --git a/k8s/tests/common.py b/k8s/tests/common.py index 9778d1f..35d34ba 100644 --- a/k8s/tests/common.py +++ b/k8s/tests/common.py @@ -48,6 +48,9 @@ def _set_k8s_configuration(): "keystore_password" : "secret1", "truststore_password" : "secret2" }, + "truststore_merger": { + "image_tag": "repo/oom-truststore-merger:1.2.3" + }, "cbs": { "base_url": "https://config-binding-service:10443/service_component_all/test-component" } @@ -160,6 +163,25 @@ def verify_external_cert(dep): for k in expected_envs: assert (k in envs and expected_envs[k] == envs[k]) +def verify_truststore_merger(dep): + cert_container = dep.spec.template.spec.init_containers[2] + print(cert_container) + assert cert_container.image == "repo/oom-truststore-merger:1.2.3" + assert cert_container.name == "truststore-merger" + assert len(cert_container.volume_mounts) == 1 + assert cert_container.volume_mounts[0].name == "tls-info" + assert cert_container.volume_mounts[0].mount_path == "/opt/dcae/cacert/" + + expected_envs = { + "TRUSTSTORES_PATHS": "/opt/dcae/cacert/trust.jks:/opt/dcae/cacert/external/truststore.p12", + "TRUSTSTORES_PASSWORDS_PATHS": "/opt/dcae/cacert/trust.pass:/opt/dcae/cacert/external/truststore.pass", + } + + envs = {k.name: k.value for k in cert_container.env} + for k in expected_envs: + assert (k in envs and expected_envs[k] == envs[k]) + + def do_deploy(tls_info=None): ''' Common deployment operations ''' import k8sclient.k8sclient @@ -172,7 +194,7 @@ def do_deploy(tls_info=None): if tls_info: kwargs["tls_info"] = tls_info - dep, deployment_description = k8sclient.k8sclient.deploy("k8stest", "testcomponent", "example.com/testcomponent:1.4.3", 1, False, k8s_test_config, **kwargs) + dep, deployment_description = k8sclient.k8sclient.deploy(k8s_ctx(), "k8stest", "testcomponent", "example.com/testcomponent:1.4.3", 1, False, k8s_test_config, **kwargs) # Make sure all of the basic k8s parameters are correct verify_common(dep, deployment_description) @@ -190,9 +212,18 @@ def do_deploy_ext(ext_tls_info): kwargs['resources'] = _set_resources() kwargs["external_cert"] = ext_tls_info - dep, deployment_description = k8sclient.k8sclient.deploy("k8stest", "testcomponent", "example.com/testcomponent:1.4.3", 1, False, k8s_test_config, **kwargs) + dep, deployment_description = k8sclient.k8sclient.deploy(k8s_ctx(), "k8stest", "testcomponent", "example.com/testcomponent:1.4.3", 1, False, k8s_test_config, **kwargs) # Make sure all of the basic k8s parameters are correct verify_common(dep, deployment_description) return dep, deployment_description + +class k8s_logger: + def info(self, text): + print(text) + +class k8s_ctx: + logger = k8s_logger() + + diff --git a/k8s/tests/test_k8sclient_deploy.py b/k8s/tests/test_k8sclient_deploy.py index 30490a6..a325b68 100644 --- a/k8s/tests/test_k8sclient_deploy.py +++ b/k8s/tests/test_k8sclient_deploy.py @@ -25,6 +25,7 @@ import pytest from common import do_deploy from common import do_deploy_ext from common import verify_external_cert +from common import verify_truststore_merger def test_deploy_full_tls(mockk8sapi): ''' Deploy component with a full TLS configuration, to act as a server ''' @@ -67,3 +68,6 @@ def test_deploy_external_cert(mockk8sapi): # Make sure all of the external init container parameters are correct verify_external_cert(dep) + verify_truststore_merger(dep) + + -- cgit 1.2.3-korg