Add support for TLS init container 29/65529/1
authorJack Lucas <jflucas@research.att.com>
Mon, 10 Sep 2018 12:14:29 +0000 (12:14 +0000)
committerJack Lucas <jflucas@research.att.com>
Mon, 10 Sep 2018 12:50:51 +0000 (12:50 +0000)
Change-Id: I118af2c8a0294ffc89e045f8cdae24dfb7e57ab6
Issue-ID: DCAEGEN2-591
Signed-off-by: Jack Lucas <jflucas@research.att.com>
k8s/README.md
k8s/configure/configure.py
k8s/k8s-node-type.yaml
k8s/k8sclient/k8sclient.py
k8s/k8splugin/tasks.py
k8s/pom.xml
k8s/setup.py

index 5b2d0da..2f4b3a6 100644 (file)
@@ -16,6 +16,9 @@ creates the following Kubernetes entities:
   - If the blueprint specifies a logging directory via the `log_info` property, the `Deployment` includes a second container,
   running the `filebeat` logging sidecar that ships logging information to the ONAP ELK stack.  The `Deployment` will include
   some additional volumes needed by filebeat.
+  - If the blueprint specifies that the component uses TLS (HTTPS) via the `tls_info` property, the `Deployment` includes an init container,
+    a volume that holds TLS certificate artifacts, and volume mounts on the init container and the component's container.  The init container
+    populates the TLS certificate artifacts volume with certificates, keys, keystores, etc.
 - If the blueprint indicates that the component exposes any ports, the plugin will create a Kubernetes `Service` that allocates an address
   in the Kubernetes network address space that will route traffic to a container that's running the component.  This `Service` provides a
   fixed "virtual IP" for the component.
@@ -40,16 +43,20 @@ address=10.12.5.115:30270
 Additional configuration information is stored in the Consul KV store under the key `k8s-plugin`.
 The configuration is provided as JSON object with the following properties:
 
-    - namespace:  k8s namespace to use for DCAE
-    - consul_dns_name: k8s internal DNS name for Consul (passed to containers)
-    - image_pull_secrets: list of names of k8s secrets for accessing Docker registries, with the following properties:
-    - filebeat:  object containing onfiguration for setting up filebeat container
-            - log_path: mount point for log volume in filebeat container
-            - data_path: mount point for data volume in filebeat container
-            - config_path: mount point for config volume in filebeat container
-            - config_subpath: subpath for config data in filebeat container
-            - config_map: name of a ConfigMap holding the filebeat configuration file
-            - image: Docker image to use for filebeat
+    - `namespace`:  k8s namespace to use for DCAE
+    - `consul_dns_name`: k8s internal DNS name for Consul (passed to containers)
+    - `image_pull_secrets`: list of names of k8s secrets for accessing Docker registries, with the following properties:
+    - `filebeat`:  object containing onfiguration for setting up filebeat container
+            - `log_path`: mount point for log volume in filebeat container
+            - `data_path`: mount point for data volume in filebeat container
+            - `config_path`: mount point for config volume in filebeat container
+            - `config_subpath`: subpath for config data in filebeat container
+            - `config_map`: name of a ConfigMap holding the filebeat configuration file
+            - `image`: Docker image to use for filebeat
+    - `tls`: object containing configuration for setting up TLS init container
+            - `cert_path`: mount point for the TLS certificate artifact volume in the init container
+            - `image`: Docker image to use for the TLS init container
+
 
 #### Kubernetes access information
 The plugin accesses a Kubernetes cluster.  The information and credentials for accessing a cluster are stored in a "kubeconfig"
index fcf4044..03077d2 100644 (file)
@@ -32,6 +32,9 @@ FB_CONFIG_SUBPATH = "filebeat.yml"
 FB_CONFIG_MAP = "filebeat-conf"
 FB_IMAGE = "docker.elastic.co/beats/filebeat:5.5.0"
 
+TLS_CERT_PATH = "/opt/tls/shared"
+TLS_IMAGE = "tls-init:latest"
+
 def _set_defaults():
     """ Set default configuration parameters """
     return {
@@ -45,6 +48,10 @@ def _set_defaults():
             "config_subpath" : FB_CONFIG_SUBPATH,   # subpath for config data in filebeat container
             "config_map" : FB_CONFIG_MAP,           # ConfigMap holding the filebeat configuration
             "image": FB_IMAGE                       # Docker image to use for filebeat
+        },
+        "tls": {                                    # Configuration for setting up TLS init container
+            "cert_path" : TLS_CERT_PATH,            # mount point for certificate volume in TLS init container
+            "image": TLS_IMAGE                      # Docker image to use for TLS init container
         }
     }
 
index d43d76a..5dae20a 100644 (file)
@@ -25,7 +25,7 @@ plugins:
   k8s:
     executor: 'central_deployment_agent'
     package_name: k8splugin
-    package_version: 1.4.2
+    package_version: 1.4.3
 
 data_types:
 
@@ -79,6 +79,22 @@ data_types:
         type: string
         required: false
 
+  dcae.types.TLSInfo:
+    description: >
+      Information for using TLS (HTTPS).  (The properties all have to be declared as not
+      required, otherwise the tls_info property on the node would also be required.)
+    properties:
+      cert_directory:
+        description: >
+          The path in the container where the component expects to find TLS-related data.
+        type: string
+        required: false
+      use_tls:
+        description: >
+          Flag indicating whether TLS (HTTPS) is to be used
+        type: boolean
+        required: false
+
 node_types:
     dcae.nodes.ContainerizedComponent:
     # Bese type for all containerized components
@@ -109,6 +125,12 @@ node_types:
                 Information for setting up centralized logging via ELK.
               required: false
 
+            tls_info:
+              type: dcae.types.TLSInfo
+              description: >
+                Information for setting up TLS (HTTPS).
+              required: false
+
             replicas:
               type: integer
               description: >
index 1c30534..eff51a3 100644 (file)
@@ -84,7 +84,7 @@ def _parse_interval(t):
         raise ValueError("Bad interval specification: {0}".format(t))
     return time
 
-def _create_probe(hc, port):
+def _create_probe(hc, port, use_tls=False):
     ''' Create a Kubernetes probe based on info in the health check dictionary hc '''
     probe_type = hc['type']
     probe = None
@@ -99,7 +99,7 @@ def _create_probe(hc, port):
           http_get = client.V1HTTPGetAction(
               path = hc['endpoint'],
               port = port,
-              scheme = probe_type.upper()
+              scheme = 'HTTPS' if use_tls else probe_type.upper()
           )
         )
     elif probe_type in ['script', 'docker']:
@@ -114,7 +114,7 @@ def _create_probe(hc, port):
         )
     return probe
 
-def _create_container_object(name, image, always_pull, env={}, container_ports=[], volume_mounts = [], readiness = None):
+def _create_container_object(name, image, always_pull, use_tls=False, env={}, container_ports=[], volume_mounts = [], readiness = None):
     # Set up environment variables
     # Copy any passed in environment variables
     env_vars = [client.V1EnvVar(name=k, value=env[k]) for k in env.keys()]
@@ -130,7 +130,7 @@ def _create_container_object(name, image, always_pull, env={}, container_ports=[
         hc_port = None
         if len(container_ports) > 0:
             hc_port = container_ports[0]
-        probe = _create_probe(readiness, hc_port)
+        probe = _create_probe(readiness, hc_port, use_tls)
 
     # Define container for pod
     return client.V1Container(
@@ -145,6 +145,7 @@ def _create_container_object(name, image, always_pull, env={}, container_ports=[
 
 def _create_deployment_object(component_name,
                               containers,
+                              init_containers,
                               replicas,
                               volumes,
                               labels={},
@@ -166,6 +167,7 @@ def _create_deployment_object(component_name,
         metadata=client.V1ObjectMeta(labels=labels),
         spec=client.V1PodSpec(hostname=component_name,
                               containers=containers,
+                              init_containers=init_containers,
                               volumes=volumes,
                               image_pull_secrets=ips)
     )
@@ -333,6 +335,9 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, *
             "config_subpath" :  subpath for config data in filebeat container
             "config_map" : ConfigMap holding the filebeat configuration
             "image": Docker image to use for filebeat
+        - tls: a dictionary of TLS init container parameters:
+            "cert_path": mount point for certificate volume in init container
+            "image": Docker image to use for TLS 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"}
@@ -341,6 +346,8 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, *
         - msb_list: array of msb objects, where an msb object is as described in msb/msb.py.
         - log_info: an object with info for setting up ELK logging, with the form:
             {"log_directory": "/path/to/container/log/directory", "alternate_fb_path" : "/alternate/sidecar/log/path"}
+        - tls_info: an object with info for setting up TLS (HTTPS), with the form:
+            {"use_tls": true, "cert_directory": "/path/to/container/cert/directory" }
         - labels: dict with label-name/label-value pairs, e.g. {"cfydeployment" : "lsdfkladflksdfsjkl", "cfynode":"mycomponent"}
             These label will be set on all the pods deployed as a result of this deploy() invocation.
         - readiness: dict with health check info; if present, used to create a readiness probe for the main container.  Includes:
@@ -375,6 +382,7 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, *
 
         # Initialize the list of containers that will be part of the pod
         containers = []
+        init_containers = []
 
         # Set up the ELK logging sidecar container, if needed
         log_info = kwargs.get("log_info")
@@ -395,7 +403,7 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, *
             sidecar_volume_mounts.append(client.V1VolumeMount(name="filebeat-data", mount_path=fb["data_path"]))
 
             # Create the container for the sidecar
-            containers.append(_create_container_object("filebeat", fb["image"], False, {}, [], sidecar_volume_mounts))
+            containers.append(_create_container_object("filebeat", fb["image"], False, False, {}, [], sidecar_volume_mounts))
 
             # Create the volume for the sidecar configuration data and the volume mount for it
             # The configuration data is in a k8s ConfigMap that should be created when DCAE is installed.
@@ -404,14 +412,30 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, *
             sidecar_volume_mounts.append(
                 client.V1VolumeMount(name="filebeat-conf", mount_path=fb["config_path"], sub_path=fb["config_subpath"]))
 
+        # Set up the TLS init container, if needed
+        tls_info = kwargs.get("tls_info")
+        use_tls = False
+        if tls_info and "use_tls" in tls_info and tls_info["use_tls"]:
+            if "cert_directory" in tls_info and len(tls_info["cert_directory"]) > 0:
+                use_tls = True
+                tls_config = k8sconfig["tls"]
+
+                # Create the certificate volume and volume mounts
+                volumes.append(client.V1Volume(name="tls-info", empty_dir=client.V1EmptyDirVolumeSource()))
+                volume_mounts.append(client.V1VolumeMount(name="tls-info", mount_path=tls_info["cert_directory"]))
+                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, False, {}, [], init_volume_mounts))
+
         # Create the container for the component
         # Make it the first container in the pod
-        containers.insert(0, _create_container_object(component_name, image, always_pull, kwargs.get("env", {}), container_ports, volume_mounts, kwargs["readiness"]))
+        containers.insert(0, _create_container_object(component_name, image, always_pull, use_tls, kwargs.get("env", {}), container_ports, volume_mounts, kwargs["readiness"]))
 
         # Build the k8s Deployment object
         labels = kwargs.get("labels", {})
         labels.update({"app": component_name})
-        dep = _create_deployment_object(component_name, containers, replicas, volumes, labels, pull_secrets=k8sconfig["image_pull_secrets"])
+        dep = _create_deployment_object(component_name, containers, init_containers, replicas, volumes, labels, pull_secrets=k8sconfig["image_pull_secrets"])
 
         # Have k8s deploy it
         ext.create_namespaced_deployment(namespace, dep)
index d32ce30..ba71bd9 100644 (file)
@@ -296,6 +296,7 @@ def _create_and_start_container(container_name, image, **kwargs):
                      volumes=kwargs.get("volumes",[]),
                      ports=kwargs.get("ports",[]),
                      msb_list=kwargs.get("msb_list"),
+                     tls_info=kwargs.get("tls_info"),
                      env = env,
                      labels = kwargs.get("labels", {}),
                      log_info=kwargs.get("log_info"),
@@ -324,6 +325,10 @@ def _parse_cloudify_context(**kwargs):
     if "log_info" in ctx.node.properties and "log_directory" in ctx.node.properties["log_info"]:
         kwargs["log_info"] = ctx.node.properties["log_info"]
 
+    # Pick up TLS info if present
+    if "tls_info" in ctx.node.properties:
+        kwargs["tls_info"] = ctx.node.properties["tls_info"]
+
     # Pick up replica count and always_pull_image flag
     if "replicas" in ctx.node.properties:
         kwargs["replicas"] = ctx.node.properties["replicas"]
@@ -380,6 +385,7 @@ def _create_and_start_component(**kwargs):
         "ports": kwargs.get("ports", None),
         "envs": kwargs.get("envs", {}),
         "log_info": kwargs.get("log_info", {}),
+        "tls_info": kwargs.get("tls_info", {}),
         "labels": kwargs.get("labels", {}),
         "readiness": kwargs.get("readiness",{})}
     _create_and_start_container(service_component_name, image, **sub_kwargs)
@@ -524,6 +530,10 @@ def create_and_start_container_for_platforms(**kwargs):
     if "log_info" in ctx.node.properties and "log_directory" in ctx.node.properties["log_info"]:
         kwargs["log_info"] = ctx.node.properties["log_info"]
 
+    # Pick up TLS info if present
+    if "tls_info" in ctx.node.properties:
+        kwargs["tls_info"] = ctx.node.properties["tls_info"]
+
     # Pick up replica count and always_pull_image flag
     if "replicas" in ctx.node.properties:
         kwargs["replicas"] = ctx.node.properties["replicas"]
index 14baaf7..ea34f78 100644 (file)
@@ -28,7 +28,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property.
   <groupId>org.onap.dcaegen2.platform.plugins</groupId>
   <artifactId>k8s</artifactId>
   <name>k8s-plugin</name>
-  <version>1.4.2-SNAPSHOT</version>
+  <version>1.4.3-SNAPSHOT</version>
   <url>http://maven.apache.org</url>
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
index 0fa7634..5897f61 100644 (file)
@@ -23,7 +23,7 @@ from setuptools import setup
 setup(
     name='k8splugin',
     description='Cloudify plugin for containerized components deployed using Kubernetes',
-    version="1.4.2",
+    version="1.4.3",
     author='J. F. Lucas, Michael Hwang, Tommy Carpenter',
     packages=['k8splugin','k8sclient','msb','configure'],
     zip_safe=False,