diff --git a/CHANGELOG.md b/CHANGELOG.md index d58292a..4beba47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog for Vault Sidecar Injector +## Release v6.0.0 - 2020-03-04 + +This is a major release introducing new features and complete code refactoring for clear isolation of modes. + +Highlights: + +- New Static Secrets feature, part of `secrets` mode (now supporting both **dynamic** and **static** secrets) +- Kubernetes Jobs are now handled as a *Vault Sidecar Injector mode*. Annotation `sidecar.vault.talend.org/workload` is **still supported but deprecated**: make use of `sidecar.vault.talend.org/mode` to enable job mode +- HashiCorp Vault image updated to `1.3.2` + +**Added** + +- [VSI #20](https://github.com/Talend/vault-sidecar-injector/pull/20) - Static secrets. Feature announcement [here](https://github.com/Talend/vault-sidecar-injector/blob/master/doc/Static-vs-Dynamic-Secrets.md). + ## Release v5.1.1 - 2019-12-23 **Added** diff --git a/Makefile b/Makefile index e1ea7ae..05abe85 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ -RELEASE_VERSION:=5.1.1 -VSI_VERSION:=5.0.1 +SHELL=/bin/bash + +RELEASE_VERSION:=$(shell cat VERSION_RELEASE) +VSI_VERSION:=$(shell cat VERSION_VSI) +CHART_VERSION:=$(shell cat VERSION_CHART) OWNER:=Talend REPO:=vault-sidecar-injector @@ -10,7 +13,7 @@ SRC:=$(shell find . -type f -name '*.go' -not -path "./vendor/*") LDFLAGS=-ldflags "-X=main.VERSION=$(VSI_VERSION)" .SILENT: ; # No need for @ -.ONESHELL: ; # Single shell for a target (required to properly use all of our local variables) +.ONESHELL: ; # Single shell for a target (required to properly use local variables) .PHONY: all clean fmt test build release image .DEFAULT_GOAL := build @@ -32,22 +35,42 @@ build: clean test cd target && sha512sum vaultinjector-webhook > vaultinjector-webhook.sha512 package: + set -e mkdir -p target && cd target echo "Archive Helm chart ..." mkdir -p vault-sidecar-injector && cp -R ../README.md ../deploy/helm/* ./vault-sidecar-injector + sed -i "s/version: 0.0.0/version: ${CHART_VERSION}/;s/appVersion: 0.0.0/appVersion: ${VSI_VERSION}/" ./vault-sidecar-injector/Chart.yaml + sed -i "s/tag: \"latest\" # VSI image tag/tag: \"${VSI_VERSION}\" # VSI image tag/" ./vault-sidecar-injector/values.yaml + sed -i "s/latest \*(local testing)\*, \[VERSION_VSI\](VERSION_VSI) \*(release)\*/${VSI_VERSION}/" ./vault-sidecar-injector/README.md helm package vault-sidecar-injector rm -R vault-sidecar-injector helm lint ./vault-sidecar-injector-*.tgz --debug image: - echo "Build image from sources ..." + echo "Build image using Go container and multi-stage build ..." docker build -t talend/vault-sidecar-injector:${VSI_VERSION} . + docker tag talend/vault-sidecar-injector:${VSI_VERSION} talend/vault-sidecar-injector image-from-build: build echo "Build image from local build ..." docker build -f Dockerfile.local -t talend/vault-sidecar-injector:${VSI_VERSION} . + docker tag talend/vault-sidecar-injector:${VSI_VERSION} talend/vault-sidecar-injector release: image-from-build package + read -p "Publish image on Docker Hub (y/n)? " answer + case $$answer in \ + y|Y ) \ + docker login; \ + docker push talend/vault-sidecar-injector:${VSI_VERSION}; \ + if [ "$$?" -ne 0 ]; then \ + echo "Unable to publish image"; \ + exit 1; \ + fi; \ + ;; \ + * ) \ + echo "Image not published on Docker Hub"; \ + ;; \ + esac cd target echo "Releasing artifacts ..." read -p "- Github user name to use for release: " username diff --git a/README.md b/README.md index 63bf41c..dafacab 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,11 @@ - [Default template](#default-template) - [Template's Syntax](#templates-syntax) - [Proxy Mode](#proxy-mode) + - [Modes and Injection Config Overview](#modes-and-injection-config-overview) - [Examples](#examples) - [Using Vault Kubernetes Auth Method](#using-vault-kubernetes-auth-method) - [Secrets mode - Usage with a K8S Deployment workload](#secrets-mode---usage-with-a-k8s-deployment-workload) + - [Secrets mode - Static secrets](#secrets-mode---static-secrets) - [Secrets mode - Usage with a K8S Job workload](#secrets-mode---usage-with-a-k8s-job-workload) - [Secrets and Proxy modes - K8S Job workload](#secrets-and-proxy-modes---k8s-job-workload) - [Secrets mode - Custom secrets path and notification command](#secrets-mode---custom-secrets-path-and-notification-command) @@ -26,14 +28,8 @@ - [Using Vault AppRole Auth Method](#using-vault-approle-auth-method) - [How to deploy Vault Sidecar Injector](#how-to-deploy-vault-sidecar-injector) - [Prerequisites](#prerequisites) - - [Helm 2: Tiller installation](#helm-2-tiller-installation) - - [Vault server installation](#vault-server-installation) - [Vault Sidecar Injector image](#vault-sidecar-injector-image) - - [Pulling the image from Docker Hub](#pulling-the-image-from-docker-hub) - - [Building the image](#building-the-image) - [Installing the Chart](#installing-the-chart) - - [Installing the chart in a dev environment](#installing-the-chart-in-a-dev-environment) - - [Restrict injection to specific namespaces](#restrict-injection-to-specific-namespaces) - [Uninstalling the chart](#uninstalling-the-chart) - [Configuration](#configuration) - [Metrics](#metrics) @@ -41,6 +37,7 @@ ## Announcements +- 2020-03: [Static vs Dynamic secrets](https://github.com/Talend/vault-sidecar-injector/blob/master/doc/Static-vs-Dynamic-Secrets.md) - 2019-12: [Discovering Vault Sidecar Injector's Proxy feature](https://github.com/Talend/vault-sidecar-injector/blob/master/doc/Discovering-Vault-Sidecar-Injector-Proxy.md) - 2019-11: [Vault Sidecar Injector now leverages Vault Agent Template feature](https://github.com/Talend/vault-sidecar-injector/blob/master/doc/Leveraging-Vault-Agent-Template.md) - 2019-10: [Open-sourcing Vault Sidecar Injector](https://github.com/Talend/vault-sidecar-injector/blob/master/doc/Open-sourcing-Vault-Sidecar-Injector.md) @@ -55,7 +52,7 @@ To ease deployment, a Helm chart is provided under [deploy/helm](https://github. > ⚠️ **Important note** ⚠️: support for sidecars in Kubernetes **jobs** suffers from limitations and issues exposed here: . > -> A Kubernetes proposal tries to address those points: , . Implementation of the proposal has started and may be released in Kubernetes 1.18 (in Alpha stage). +> A Kubernetes proposal tries to address those points: , . Implementation of the proposal has started and may be released in Kubernetes 1.19 (in Alpha stage). > > In the meantime however, `Vault Sidecar Injector` implements **specific sidecar and signaling mechanism** to properly stop all injected containers on job termination. @@ -65,8 +62,11 @@ To ease deployment, a Helm chart is provided under [deploy/helm](https://github. `Vault Sidecar Injector` supports several high-level features or *modes*: -- [**secrets**](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#secrets-mode), the primary mode allowing to continuously retrieve secrets from Vault server's stores, coping with secrets rotations (ie any change will be propagated and updated values made available to consume by applications). +- [**secrets**](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#secrets-mode), the primary mode allowing to retrieve secrets from Vault server's stores, either once (for **static secrets**) or continuously (for **dynamic secrets**), coping with secrets rotations (ie any change will be propagated and updated values made available to consume by applications). - [**proxy**](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#proxy-mode), to enable the injected Vault Agent as a local, authenticated gateway to the remote Vault server. As an example, with this mode on, applications can easily leverage Vault's Transit Engine to cipher/decipher payloads by just sending data to the local proxy without dealing themselves with Vault authentication and tokens. +- **job**, to use when a Kubernetes Job is submitted. This new mode comes in replacement of the now deprecated `sidecar.vault.talend.org/workload` annotation. + +For details, refer to [Modes and Injection Config Overview](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#modes-and-injection-config-overview). ### Requirements @@ -80,7 +80,7 @@ Invoking `Vault Sidecar Injector` is pretty straightforward. In your application - **Use of `serviceAccountName` attribute**, with role allowing to perform GET on pods (needed to poll for job's pod status) - **Do not make use of annotation `sidecar.vault.talend.org/secrets-hook`** as it will immediately put the job in error state. This hook is meant to be used with regular workloads only (ie Kubernetes Deployments) as it forces a restart of the application container until secrets are available in application's context. With jobs, as we look after status of the job container, our special signaling mechanism will terminate all the sidecars upon job exit thus preventing use of the hook. -Refer to provided [sample files](https://github.com/Talend/vault-sidecar-injector/blob/master/deploy/samples) and [examples](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#examples) section. +Refer to provided [sample files](https://github.com/Talend/vault-sidecar-injector/blob/master/samples) and [examples](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#examples) section. ### Annotations @@ -88,18 +88,19 @@ Following annotations in requesting pods are supported: | Annotation | (M)andatory / (O)ptional | Apply to mode | Default Value | Supported Values | Description | |---------------------------------------|--------------------------|-----------------|----------------------|--------------------------------|-------------| -| `sidecar.vault.talend.org/inject` | M | N/A | | "true" / "on" / "yes" / "y" | Ask for sidecar injection to get secrets from Vault | -| `sidecar.vault.talend.org/auth` | O | N/A | "kubernetes" | "kubernetes" / "approle" | Vault Auth Method to use | -| `sidecar.vault.talend.org/mode` | O | N/A | "secrets" | "secrets" / "proxy" / Comma-separated values (eg "secrets,proxy") | Enable provided mode(s) | -| `sidecar.vault.talend.org/notify` | O | secrets | "" | Comma-separated strings | List of commands to notify application/service of secrets change, one per secrets path | +| `sidecar.vault.talend.org/inject` | M | N/A | | "true" / "on" / "yes" / "y" | Ask for sidecar injection to get secrets from Vault | +| `sidecar.vault.talend.org/auth` | O | N/A | "kubernetes" | "kubernetes" / "approle" | Vault Auth Method to use. **Static secrets only supports "kubernetes" authentication method** | +| `sidecar.vault.talend.org/mode` | O | N/A | "secrets" | "secrets" / "proxy" / "job" / Comma-separated values (eg "secrets,proxy") | Enable provided mode(s). **Note: `secrets` mode will be enabled if you only set `job` mode** | +| `sidecar.vault.talend.org/notify` | O | secrets | "" | Comma-separated strings | List of commands to notify application/service of secrets change, one per secrets path. **Usage context: dynamic secrets only** | | `sidecar.vault.talend.org/proxy-port` | O | proxy | "8200" | Any allowed port value | Port for local Vault proxy | | `sidecar.vault.talend.org/role` | O | N/A | "\<`com.talend.application` label\>" | Any string | **Only used with "kubernetes" Vault Auth Method**. Vault role associated to requesting pod. If annotation not used, role is read from label defined by `mutatingwebhook.annotations.appLabelKey` key (refer to [configuration](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#configuration)) which is `com.talend.application` by default | | `sidecar.vault.talend.org/sa-token` | O | N/A | "/var/run/secrets/kubernetes.io/serviceaccount/token" | Any string | Full path to service account token used for Vault Kubernetes authentication | -| `sidecar.vault.talend.org/secrets-destination` | O | secrets | "secrets.properties" | Comma-separated strings | List of secrets filenames (without path), one per secrets path | -| `sidecar.vault.talend.org/secrets-hook` | O | secrets | | "true" / "on" / "yes" / "y" | If set, lifecycle hooks will be added to pod's container(s) to wait for secrets files | -| `sidecar.vault.talend.org/secrets-path` | O | secrets | "secret/<`com.talend.application` label>/<`com.talend.service` label>" | Comma-separated strings | List of secrets engines and path. If annotation not used, path is set from labels defined by `mutatingwebhook.annotations.appLabelKey` and `mutatingwebhook.annotations.appServiceLabelKey` keys (refer to [configuration](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#configuration)) | -| `sidecar.vault.talend.org/secrets-template` | O | secrets | [Default template](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#default-template) | templates separated with `---` | Allow to override default template. Ignore `sidecar.vault.talend.org/secrets-path` annotation if set | -| `sidecar.vault.talend.org/workload` | O | N/A | | "job" | Type of submitted workload | +| `sidecar.vault.talend.org/secrets-destination` | O | secrets | "secrets.properties" | Comma-separated strings | List of secrets filenames (without path), one per secrets path | +| `sidecar.vault.talend.org/secrets-hook` | O | secrets | *nil* | "true" / "on" / "yes" / "y" | If set, lifecycle hooks will be added to pod's container(s) to wait for secrets files. **Usage context: dynamic secrets only** | +| `sidecar.vault.talend.org/secrets-path` | O | secrets | "secret/<`com.talend.application` label>/<`com.talend.service` label>" | Comma-separated strings | List of secrets engines and path. If annotation not used, path is set from labels defined by `mutatingwebhook.annotations.appLabelKey` and `mutatingwebhook.annotations.appServiceLabelKey` keys (refer to [configuration](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#configuration)) | +| `sidecar.vault.talend.org/secrets-template` | O | secrets | [Default template](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#default-template) | templates separated with `---` | Allow to override default template. Ignore `sidecar.vault.talend.org/secrets-path` annotation if set | +| `sidecar.vault.talend.org/secrets-type` | O | secrets | "dynamic" | "static" / "dynamic" | Type of secrets to handle (see details [here](https://github.com/Talend/vault-sidecar-injector/blob/master/doc/Static-vs-Dynamic-Secrets.md)) | +| `sidecar.vault.talend.org/workload` | O | N/A | *nil* | "job" | Type of submitted workload. **⚠️ Deprecated: use `sidecar.vault.talend.org/mode` instead. Using this annotation will enable `job` mode ⚠️** | Upon successful injection, Vault Sidecar Injector will add annotation(s) to the requesting pods: @@ -117,8 +118,8 @@ Template below is used by default to fetch all secrets and create corresponding ```yaml -{{ with secret "" }}{{ range \$k, \$v := .Data }} -{{ \$k }}={{ \$v }} +{{ with secret "" }}{{ range $k, $v := .Data }} +{{ $k }}={{ $v }} {{ end }}{{ end }} ``` @@ -137,9 +138,64 @@ Details on template syntax can be found in Consul Template's documentation (same This mode opens the gate to virtually any Vault features for requesting applications. A [blog entry](https://github.com/Talend/vault-sidecar-injector/blob/master/doc/Discovering-Vault-Sidecar-Injector-Proxy.md) introduces this mode and examples are provided. +### Modes and Injection Config Overview + +Depending on the modes you decide to enable and whether you opt for static or dynamic secrets (when **secrets** mode is selected), the configuration injected into your pod varies. The following table provides a quick glance at the different configurations. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Enabled Mode(s)Injected Configuration
secrets (static)secrets (dynamic)proxyjobInit ContainerSidecar(s)²
XX +
XX¹X (secrets)X (job)
XX
XX¹X
XX
XXX
XXX (secrets)X (proxy)
XXXX (secrets)X (proxy & job)
XXX
XXXX
+ +> **[1]** *on job mode:* if you only set mode annotation's value to "job", `secrets` mode will be enabled automatically and configured to handle dynamic secrets (unless you set `sidecar.vault.talend.org/secrets-type` to "static" but note that in this situation, there is no need, although we do not prevent it, to enable job mode as no Vault Agent will be injected as sidecar). + +> **[2]** *on number of injected sidecars:* for Kubernetes **Deployment** workloads, **only one sidecar container** is added to your pod to handle dynamic secrets and/or proxy. For Kubernetes **Job** workloads, **two sidecars** are injected to achieve the same tasks. + ### Examples -**Ready to use sample manifests are provided under [deploy/samples](https://github.com/Talend/vault-sidecar-injector/blob/master/deploy/samples) folder**. Just deploy them using `kubectl apply -f `. +**Ready to use sample manifests are provided under [samples](https://github.com/Talend/vault-sidecar-injector/blob/master/samples) folder**. Just deploy them using `kubectl apply -f `. Examples hereafter go further and highlight all the features of `Vault Sidecar Injector` through the supported annotations. @@ -147,6 +203,11 @@ Examples hereafter go further and highlight all the features of `Vault Sidecar I ##### Secrets mode - Usage with a K8S Deployment workload +
+ +Show example + + Only mandatory annotation to ask for Vault Agent injection is `sidecar.vault.talend.org/inject`. It means that, with the provided manifest below: @@ -159,7 +220,55 @@ It means that, with the provided manifest below: apiVersion: apps/v1 kind: Deployment metadata: - name: test-app-1 + name: example-1 +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test-app-1 + com.talend.service: test-app-1-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + labels: + com.talend.application: test-app-1 + com.talend.service: test-app-1-svc + spec: + serviceAccountName: ... + containers: + - name: ... + image: ... + ... + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory +``` +
+ +##### Secrets mode - Static secrets + +
+ +Show example + + +With the provided manifest below: + +- Vault authentication done using role `test-app-1` (value of `com.talend.application` label) +- secrets fetched from Vault's path `secret/test-app-1/test-app-1-svc` using default template +- secrets to be stored into `/opt/talend/secrets/secrets.properties` +- as we specified *static* for `sidecar.vault.talend.org/secrets-type`, the secrets **will not be refreshed** but fetched only once + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: example-1 spec: replicas: 1 selector: @@ -170,6 +279,7 @@ spec: metadata: annotations: sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/secrets-type: "static" # static secrets labels: com.talend.application: test-app-1 com.talend.service: test-app-1-svc @@ -187,9 +297,15 @@ spec: emptyDir: medium: Memory ``` +
##### Secrets mode - Usage with a K8S Job workload +
+ +Show example + + When submitting a job, annotation `sidecar.vault.talend.org/workload` **must be used with value set to `"job"`**. The service account used to run the job should at least have the following permissions: @@ -229,7 +345,7 @@ Last point is to make sure your job is waiting for availability of secrets file( apiVersion: batch/v1 kind: Job metadata: - name: test-app-job + name: example-2 namespace: default spec: backoffLimit: 1 @@ -272,9 +388,15 @@ spec: emptyDir: medium: Memory ``` +
##### Secrets and Proxy modes - K8S Job workload +
+ +Show example + + This sample demonstrates how to enable the proxy mode in addition to the secrets mode used in previous samples. We are here again using a Kubernetes job so we reuse the dedicated service account and the `sidecar.vault.talend.org/workload` annotation. > Note that any mode combination is supported: **secrets** only (default when annotation `sidecar.vault.talend.org/mode` not provided), **proxy** only and both modes. @@ -314,7 +436,7 @@ roleRef: apiVersion: batch/v1 kind: Job metadata: - name: test-app-job-proxy + name: example-3 namespace: default spec: backoffLimit: 1 @@ -364,9 +486,15 @@ spec: emptyDir: medium: Memory ``` +
##### Secrets mode - Custom secrets path and notification command +
+ +Show example + + Several optional annotations to end up with: - Vault authentication using role `test-app-4` (value of `com.talend.application` label) @@ -378,7 +506,7 @@ Several optional annotations to end up with: apiVersion: apps/v1 kind: Deployment metadata: - name: test-app-4 + name: example-4 spec: replicas: 1 selector: @@ -408,9 +536,15 @@ spec: emptyDir: medium: Memory ``` +
##### Secrets mode - Ask for secrets hook injection, custom secrets file and template +
+ +Show example + + Several optional annotations to end up with: - Vault authentication using role `test-app-6` (value of `com.talend.application` label) @@ -423,7 +557,7 @@ Several optional annotations to end up with: apiVersion: apps/v1 kind: Deployment metadata: - name: test-app-6 + name: example-5 spec: replicas: 1 selector: @@ -463,9 +597,15 @@ spec: medium: Memory ``` +
##### Secrets mode - Ask for secrets hook injection, several custom secrets files and templates +
+ +Show example + + Several optional annotations to end up with: - Vault authentication using role `test-app-2` (value of `com.talend.application` label) @@ -478,7 +618,7 @@ Several optional annotations to end up with: apiVersion: apps/v1 kind: Deployment metadata: - name: test-app-2 + name: example-6 spec: replicas: 1 selector: @@ -521,9 +661,17 @@ spec: medium: Memory ``` +
#### Using Vault AppRole Auth Method +> ⚠️ AppRole Auth Method can only be used with **dynamic** secrets. + +
+ +Show example + + - Ask Vault Sidecar Injector to use Vault authentication method `approle` (instead of the default which is `kubernetes`) - Vault AppRole requires info stored into 2 files (containing role id and secret id) that have to be provided to Vault Sidecar Injector: in the sample below this is done via an init container (replace placeholders \ and \ by values generated by Vault server using commands `vault read auth/approle/role/test/role-id` and `vault write -f auth/approle/role/test/secret-id`) @@ -531,7 +679,7 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: test-app-8 + name: example-7 namespace: default spec: replicas: 1 @@ -577,6 +725,7 @@ spec: emptyDir: medium: Memory ``` +
## How to deploy Vault Sidecar Injector @@ -597,7 +746,10 @@ Runtime: - Vault server deployed (either *in cluster* with official chart or *out of cluster*), started and reachable through Kubernetes service & endpoint deployed into cluster -#### Helm 2: Tiller installation +
+ +Helm 2: Tiller installation + > Note: this step does not apply if you are using Helm 3. @@ -628,12 +780,18 @@ EOF $ helm init --service-account tiller ``` +> On Kubernetes `1.16+`: make sure to use Helm **`2.16.0` or higher** as previous Helm 2 versions rely on deprecated APIs no longer served (see ). + For details on using Tiller with RBAC: - - +
-#### Vault server installation +
+ +Vault Server installation + > **Note:** this step is optional if you already have a running Vault server. This section helps you setup a test Vault server with ready to use configuration. @@ -659,12 +817,16 @@ $ kubectl logs vault-0 $ cd vault-sidecar-injector/deploy/vault $ ./init-dev-vault-server.sh ``` +
### Vault Sidecar Injector image > Note: if you don't intend to perform some tests with the image you can skip this section and jump to [Installing the Chart](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#installing-the-chart). -#### Pulling the image from Docker Hub +
+ +Pulling the image from Docker Hub + Official Docker images are published on [Talend's public Docker Hub](https://hub.docker.com/r/talend/vault-sidecar-injector) repository for each `Vault Sidecar Injector` release. Provided Helm chart will pull the image automatically if needed. @@ -673,8 +835,12 @@ For manual pull of a specific tag: ```bash $ docker pull talend/vault-sidecar-injector: ``` +
-#### Building the image +
+ +Building the image + A [Dockerfile](https://github.com/Talend/vault-sidecar-injector/blob/master/Dockerfile) is also provided to both compile `Vault Sidecar Injector` and build the image locally if you prefer. @@ -684,6 +850,10 @@ Just run following command: $ make image ``` +> Note: if you have Go installed on your machine, you can use `make image-from-build` instead. + +
+ ### Installing the Chart > **Note:** as `Vault Sidecar Injector` chart makes use of Helm post-install hooks, **do not** provide Helm `--wait` flag since it will prevent post-install hooks from running and installation will fail. @@ -721,7 +891,7 @@ To see Chart content before installing it, perform a dry run first: ```bash $ cd deploy/helm -# If using Helm 2.x +# If using Helm 2 $ helm install $CHART_LOCATION --name vault-sidecar-injector --namespace --set vault.addr= --debug --dry-run # If using Helm 3 @@ -733,7 +903,7 @@ To install the chart on the cluster: ```bash $ cd deploy/helm -# If using Helm 2.x +# If using Helm 2 $ helm install $CHART_LOCATION --name vault-sidecar-injector --namespace --set vault.addr= # If using Helm 3 @@ -747,7 +917,7 @@ As an example, to install `Vault Sidecar Injector` on our test cluster: ```bash $ cd deploy/helm -# If using Helm 2.x +# If using Helm 2 $ helm install $CHART_LOCATION --name vault-sidecar-injector --namespace kube-system --set vault.addr=http://vault:8200 --set vault.ssl.verify=false # If using Helm 3 @@ -758,14 +928,17 @@ This command deploys the component on the Kubernetes cluster with modified confi The [configuration](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#configuration) section lists all the parameters that can be configured during installation. -#### Installing the chart in a dev environment +
+ +Installing the chart in a dev environment + In a dev environment, you may want to install your own test instance of `Vault Sidecar Injector`, connected to your own Vault server and limiting injection to a given namespace. To do so, use following options: ```bash $ cd deploy/helm -# If using Helm 2.x +# If using Helm 2 $ helm install $CHART_LOCATION --name vault-sidecar-injector --namespace --set vault.addr= --set mutatingwebhook.namespaceSelector.namespaced=true # If using Helm 3 @@ -780,8 +953,12 @@ $ kubectl label namespace vault-injection= -#### Restrict injection to specific namespaces +
+ +Restrict injection to specific namespaces + By default `Vault Sidecar Injector` monitors all namespaces (except `kube-system` and `kube-public`) and looks after annotations in submitted pods. @@ -790,7 +967,7 @@ If you want to strictly control the list of namespaces where injection is allowe ```bash $ cd deploy/helm -# If using Helm 2.x +# If using Helm 2 $ helm install $CHART_LOCATION --name vault-sidecar-injector --namespace --set vault.addr= --set mutatingwebhook.namespaceSelector.boolean=true # If using Helm 3 @@ -805,13 +982,14 @@ $ kubectl label namespace vault-injection=enabled # check label on namespace $ kubectl get namespace -L vault-injection ``` +
### Uninstalling the chart To uninstall/delete the `Vault Sidecar Injector` deployment: ```bash -# If using Helm 2.x +# If using Helm 2 $ helm delete --purge vault-sidecar-injector # If using Helm 3 @@ -828,27 +1006,27 @@ The following table lists the configurable parameters of the `Vault Sidecar Inje | Parameter | Description | Default | |:-------------|:---------------------|:----------------------------------------------------------------| -| hook.image.path | Docker image path | bitnami/kubectl | -| hook.image.pullPolicy | Pull policy for docker image: IfNotPresent or Always | IfNotPresent | -| hook.image.tag | Version/tag of the docker image | latest | +| hook.image.path | Image path | bitnami/kubectl | +| hook.image.pullPolicy | Pull policy for image: IfNotPresent or Always | Always | +| hook.image.tag | Image tag | latest | | image.applicationNameLabel | Application Name. Must match label com.talend.application | talend-vault-sidecar-injector | | image.metricsPort | Port exposed for metrics collection | 9000 | -| image.path | Docker image path | talend/vault-sidecar-injector | +| image.path | Image path | talend/vault-sidecar-injector | | image.port | Service main port | 8443 | -| image.pullPolicy | Pull policy for docker image: IfNotPresent or Always | IfNotPresent | +| image.pullPolicy | Pull policy for image: IfNotPresent or Always | IfNotPresent | | image.serviceNameLabel | Service Name. Must match label com.talend.service | talend-vault-sidecar-injector | -| image.tag | Version/tag of the docker image | 5.0.1 | +| image.tag | Image tag | latest *(local testing)*, [VERSION_VSI](VERSION_VSI) *(release)* | | imageRegistry | Image registry | | -| injectconfig.jobbabysitter.image.path | Docker image path | everpeace/curl-jq | -| injectconfig.jobbabysitter.image.pullPolicy | Pull policy for docker image: IfNotPresent or Always | IfNotPresent | -| injectconfig.jobbabysitter.image.tag | Version/tag of the docker image | latest | +| injectconfig.jobbabysitter.image.path | Image path | everpeace/curl-jq | +| injectconfig.jobbabysitter.image.pullPolicy | Pull policy for image: IfNotPresent or Always | Always | +| injectconfig.jobbabysitter.image.tag | Image tag | latest | | injectconfig.jobbabysitter.resources.limits.cpu | Job babysitter sidecar CPU resource limits | 20m | | injectconfig.jobbabysitter.resources.limits.memory | Job babysitter sidecar memory resource limits | 25Mi | | injectconfig.jobbabysitter.resources.requests.cpu | Job babysitter sidecar CPU resource requests | 15m | | injectconfig.jobbabysitter.resources.requests.memory | Job babysitter sidecar memory resource requests | 20Mi | -| injectconfig.vault.image.path | Docker image path | vault | -| injectconfig.vault.image.pullPolicy | Pull policy for docker image: IfNotPresent or Always | IfNotPresent | -| injectconfig.vault.image.tag | Version/tag of the docker image | 1.3.1 | +| injectconfig.vault.image.path | Image path | vault | +| injectconfig.vault.image.pullPolicy | Pull policy for image: IfNotPresent or Always | Always | +| injectconfig.vault.image.tag | Image tag | 1.3.2 | | injectconfig.vault.loglevel | Vault log level: trace, debug, info, warn, err | info | | injectconfig.vault.resources.limits.cpu | Vault sidecar CPU resource limits | 50m | | injectconfig.vault.resources.limits.memory | Vault sidecar memory resource limits | 50Mi | @@ -890,7 +1068,7 @@ The following table lists the configurable parameters of the `Vault Sidecar Inje You can override these values at runtime using the `--set key=value[,key=value]` argument to `helm install`. For example, ```bash -# If using Helm 2.x +# If using Helm 2 $ helm install \ --name vault-sidecar-injector \ --namespace \ @@ -909,23 +1087,34 @@ Vault Sidecar Injector exposes a Prometheus endpoint at `/metrics` on port `metr Following collectors are available: -- Process Collector - - process_cpu_seconds_total - - process_virtual_memory_bytes - - process_start_time_seconds - - process_open_fds - - process_max_fds -- Go Collector - - go_goroutines - - go_threads - - go_gc_duration_seconds - - go_info - - go_memstats_alloc_bytes - - go_memstats_heap_alloc_bytes - - go_memstats_alloc_bytes_total - - go_memstats_sys_bytes - - go_memstats_lookups_total - - ... +
+ +Process Collector + + +- process_cpu_seconds_total +- process_virtual_memory_bytes +- process_start_time_seconds +- process_open_fds +- process_max_fds +
+ +
+ +Go Collector + + +- go_goroutines +- go_threads +- go_gc_duration_seconds +- go_info +- go_memstats_alloc_bytes +- go_memstats_heap_alloc_bytes +- go_memstats_alloc_bytes_total +- go_memstats_sys_bytes +- go_memstats_lookups_total +- ... [full list here](https://github.com/prometheus/client_golang/blob/master/prometheus/go_collector.go) +
![Grafana dashboard](https://github.com/Talend/vault-sidecar-injector/blob/master/doc/grafana-vault-sidecar-injector.png) diff --git a/VERSION_CHART b/VERSION_CHART new file mode 100644 index 0000000..a4f52a5 --- /dev/null +++ b/VERSION_CHART @@ -0,0 +1 @@ +3.2.0 \ No newline at end of file diff --git a/VERSION_RELEASE b/VERSION_RELEASE new file mode 100644 index 0000000..f4965a3 --- /dev/null +++ b/VERSION_RELEASE @@ -0,0 +1 @@ +6.0.0 \ No newline at end of file diff --git a/VERSION_VSI b/VERSION_VSI new file mode 100644 index 0000000..f4965a3 --- /dev/null +++ b/VERSION_VSI @@ -0,0 +1 @@ +6.0.0 \ No newline at end of file diff --git a/deploy/helm/Chart.yaml b/deploy/helm/Chart.yaml index e2bc0f6..4f09f65 100644 --- a/deploy/helm/Chart.yaml +++ b/deploy/helm/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v1 name: vault-sidecar-injector description: A Helm chart for Talend Vault Sidecar Injector (OSS) -version: 3.1.1 +version: 0.0.0 icon: https://www.talend.com/wp-content/uploads/talend-logo.svg keywords: - Talend @@ -14,4 +14,4 @@ sources: maintainers: - name: Talend email: support@talend.com -appVersion: 5.0.1 \ No newline at end of file +appVersion: 0.0.0 \ No newline at end of file diff --git a/deploy/helm/config/injectionconfig.yaml b/deploy/helm/config/injectionconfig.yaml new file mode 100644 index 0000000..d710e25 --- /dev/null +++ b/deploy/helm/config/injectionconfig.yaml @@ -0,0 +1,229 @@ +initContainers: + - name: tvsi-vault-agent-init + image: {{ include "talend-vault-sidecar-injector.injectconfig.vault.image" .Values }} + imagePullPolicy: {{ .Values.injectconfig.vault.image.pullPolicy }} + env: + - name: SKIP_SETCAP + value: "true" + - name: VAULT_ADDR + value: {{ required "Vault server's address must be specified" .Values.vault.addr | quote }} + # env var set by webhook + - name: VSI_SECRETS_TEMPLATES_PLACEHOLDER + value: "" + # env var set by webhook + - name: VSI_VAULT_ROLE + value: "" + command: + - "sh" + - "-c" + - | + cat < vault-agent-config.hcl + pid_file = "/home/vault/pidfile" + + auto_auth { + method "kubernetes" { + mount_path = "auth/{{ .Values.vault.authMethods.kubernetes.path }}" + config = { + role = "${VSI_VAULT_ROLE}" + token_path = "/var/run/secrets/talend/vault-sidecar-injector/serviceaccount/token" + } + } + + sink "file" { + config = { + path = "/home/vault/.vault-token" + } + } + } + + ${VSI_SECRETS_TEMPLATES_PLACEHOLDER} + EOF + + docker-entrypoint.sh agent -config=vault-agent-config.hcl -exit-after-auth=true {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -log-level={{- .Values.injectconfig.vault.loglevel }} + export VAULT_TOKEN=$(cat /home/vault/.vault-token) + vault token revoke {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -self + volumeMounts: + # Mount path used to share secrets. The associated volume is expected to be defined in application's manifest but in case it is not, + # a default 'secrets' volume will be injected in the requesting pod (see definition below) so that mutation process does not fail. + - name: secrets + mountPath: /opt/talend/secrets + # The name's value will be overridden by the webhook to point to container's service account volume to use for Vault authentication. + - name: TVSI_SA_SECRETS_VOL_NAME + mountPath: /var/run/secrets/talend/vault-sidecar-injector/serviceaccount + readOnly: true + {{- if .Values.injectconfig.vault.resources }} + resources: +{{ toYaml .Values.injectconfig.vault.resources | indent 6 }} + {{- end }} +containers: + # This container is only injected in K8S jobs to monitor app job's container termination and send signal to vault sidecar + - name: tvsi-job-babysitter + image: {{ include "talend-vault-sidecar-injector.injectconfig.jobbabysitter.image" .Values }} + imagePullPolicy: {{ .Values.injectconfig.jobbabysitter.image.pullPolicy }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + # env var set by webhook + - name: VSI_JOB_CNT_NAME + value: "" + command: + - "sh" + - "-c" + - | + jwt_sa_token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + pod_ns=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) + retCode=$(curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq .code) + if [ $retCode = "403" ]; then + curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq .message + exit 1 + fi + + while true; do + cntStatus=$(curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq -c --raw-output --arg cntname "${VSI_JOB_CNT_NAME}" '.status.containerStatuses[] | select(.name == $cntname).state | keys[0]') + if [ "$cntStatus" = "terminated" ]; then + echo "=> job container terminated: send signal" + touch /opt/talend/tvsi/vault-sidecars-signal-terminate + exit 0 + fi + sleep 2 + done + volumeMounts: + # Mount path used by injected sidecars to share data + - name: tvsi-shared + mountPath: /opt/talend/tvsi + # The name's value will be overridden by the webhook + - name: K8S_SA_SECRETS_VOL_NAME + mountPath: /var/run/secrets/kubernetes.io/serviceaccount + readOnly: true + {{- if .Values.injectconfig.jobbabysitter.resources }} + resources: +{{ toYaml .Values.injectconfig.jobbabysitter.resources | indent 6 }} + {{- end }} + - name: tvsi-vault-agent + image: {{ include "talend-vault-sidecar-injector.injectconfig.vault.image" .Values }} + imagePullPolicy: {{ .Values.injectconfig.vault.image.pullPolicy }} + env: + - name: SKIP_SETCAP + value: "true" + - name: VAULT_ADDR + value: {{ required "Vault server's address must be specified" .Values.vault.addr | quote }} + # env var set by webhook + - name: VSI_JOB_WORKLOAD + value: "false" + # env var set by webhook + - name: VSI_PROXY_CONFIG_PLACEHOLDER + value: "" + # env var set by webhook + - name: VSI_SECRETS_TEMPLATES_PLACEHOLDER + value: "" + # env var set by webhook + - name: VSI_VAULT_AUTH_METHOD + value: "kubernetes" + # env var set by webhook + - name: VSI_VAULT_ROLE + value: "" + command: + - "sh" + - "-c" + - | + if [ "${VSI_VAULT_AUTH_METHOD}" = "kubernetes" ]; then + cat < vault-agent-config.hcl + pid_file = "/home/vault/pidfile" + + auto_auth { + method "kubernetes" { + mount_path = "auth/{{ .Values.vault.authMethods.kubernetes.path }}" + config = { + role = "${VSI_VAULT_ROLE}" + token_path = "/var/run/secrets/talend/vault-sidecar-injector/serviceaccount/token" + } + } + + sink "file" { + config = { + path = "/home/vault/.vault-token" + } + } + } + + ${VSI_PROXY_CONFIG_PLACEHOLDER} + + ${VSI_SECRETS_TEMPLATES_PLACEHOLDER} + EOF + elif [ "${VSI_VAULT_AUTH_METHOD}" = "approle" ]; then + cat < vault-agent-config.hcl + pid_file = "/home/vault/pidfile" + + auto_auth { + method "approle" { + mount_path = "auth/{{ .Values.vault.authMethods.approle.path }}" + config = { + role_id_file_path = "/opt/talend/secrets/{{ .Values.vault.authMethods.approle.roleid_filename }}" + secret_id_file_path = "/opt/talend/secrets/{{ .Values.vault.authMethods.approle.secretid_filename }}" + } + } + + sink "file" { + config = { + path = "/home/vault/.vault-token" + } + } + } + + ${VSI_PROXY_CONFIG_PLACEHOLDER} + + ${VSI_SECRETS_TEMPLATES_PLACEHOLDER} + EOF + else + echo "Unsupported Vault Auth Method: ${VSI_VAULT_AUTH_METHOD}" + exit 1 + fi + if [ "${VSI_JOB_WORKLOAD}" = "true" ]; then + docker-entrypoint.sh agent -config=vault-agent-config.hcl {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -log-level={{- .Values.injectconfig.vault.loglevel }} & + while true; do + if [ -f "/opt/talend/tvsi/vault-sidecars-signal-terminate" ]; then + echo "=> exit (signal received)" + export VAULT_TOKEN=$(cat /home/vault/.vault-token); + vault token revoke {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -self; + exit 0 + fi + sleep 2 + done + else + docker-entrypoint.sh agent -config=vault-agent-config.hcl {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -log-level={{- .Values.injectconfig.vault.loglevel }} + fi + lifecycle: + preStop: + exec: + command: + - "sh" + - "-c" + - > + export VAULT_TOKEN=$(cat /home/vault/.vault-token); + vault token revoke {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -self; + volumeMounts: + # Mount path used by injected sidecars to share data + - name: tvsi-shared + mountPath: /opt/talend/tvsi + # Mount path used to share secrets. The associated volume is expected to be defined in application's manifest but in case it is not, + # a default 'secrets' volume will be injected in the requesting pod (see definition below) so that mutation process does not fail. + - name: secrets + mountPath: /opt/talend/secrets + # The name's value will be overridden by the webhook to point to container's service account volume to use for Vault authentication. + - name: TVSI_SA_SECRETS_VOL_NAME + mountPath: /var/run/secrets/talend/vault-sidecar-injector/serviceaccount + readOnly: true + {{- if .Values.injectconfig.vault.resources }} + resources: +{{ toYaml .Values.injectconfig.vault.resources | indent 6 }} + {{- end }} +volumes: + - name: tvsi-shared + emptyDir: + medium: Memory + # Note: if 'secrets' volume is defined in pod's manifest then it will be considered instead of the default definition below + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/deploy/helm/config/podlifecyclehooks.yaml b/deploy/helm/config/podlifecyclehooks.yaml new file mode 100644 index 0000000..3193b2d --- /dev/null +++ b/deploy/helm/config/podlifecyclehooks.yaml @@ -0,0 +1,6 @@ +postStart: + exec: + command: + - "sh" + - "-c" + - cat /* >/dev/null 2>&1 \ No newline at end of file diff --git a/deploy/helm/config/proxyconfig.hcl b/deploy/helm/config/proxyconfig.hcl new file mode 100644 index 0000000..f7163d9 --- /dev/null +++ b/deploy/helm/config/proxyconfig.hcl @@ -0,0 +1,8 @@ +cache { + use_auto_auth_token = true +} + +listener "tcp" { + address = "127.0.0.1:" + tls_disable = true +} \ No newline at end of file diff --git a/deploy/helm/config/templateblock.hcl b/deploy/helm/config/templateblock.hcl new file mode 100644 index 0000000..4002b27 --- /dev/null +++ b/deploy/helm/config/templateblock.hcl @@ -0,0 +1,11 @@ +template { + destination = "/opt/talend/secrets/" + contents = < + EOH + command = "" + wait { + min = "1s" + max = "2s" + } +} \ No newline at end of file diff --git a/deploy/helm/config/templatedefault.tmpl b/deploy/helm/config/templatedefault.tmpl new file mode 100644 index 0000000..bace435 --- /dev/null +++ b/deploy/helm/config/templatedefault.tmpl @@ -0,0 +1,3 @@ +{{`{{ with secret "" }}{{ range $k, $v := .Data }} +{{ $k }}={{ $v }} +{{ end }}{{ end }}`}} \ No newline at end of file diff --git a/deploy/helm/templates/NOTES.txt b/deploy/helm/templates/NOTES.txt new file mode 100644 index 0000000..522ca24 --- /dev/null +++ b/deploy/helm/templates/NOTES.txt @@ -0,0 +1,15 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +===== +To get status: +$ helm status {{ .Release.Name }} + +To uninstall: +# Using Helm 2 +$ helm delete --purge {{ .Release.Name }} + +# Using Helm 3 +$ helm delete {{ .Release.Name }} -n {{ .Release.Namespace }} +===== diff --git a/deploy/helm/templates/configmap.yaml b/deploy/helm/templates/configmap.yaml index 254958e..05ea35b 100644 --- a/deploy/helm/templates/configmap.yaml +++ b/deploy/helm/templates/configmap.yaml @@ -5,198 +5,8 @@ metadata: labels: {{ include "talend-vault-sidecar-injector.labels" . | indent 4 }} data: - podlifecyclehooks.yaml: | - postStart: - exec: - command: - - "sh" - - "-c" - - cat /* >/dev/null 2>&1 - templateblock.hcl: | - template { - destination = "/opt/talend/secrets/" - contents = < - EOH - command = "" - wait { - min = "1s" - max = "2s" - } - } - templatedefault.tmpl: | - {{`{{ with secret "" }}{{ range \$k, \$v := .Data }} - {{ \$k }}={{ \$v }} - {{ end }}{{ end }}`}} - proxyconfig.hcl: | - cache { - use_auto_auth_token = true - } - - listener "tcp" { - address = "127.0.0.1:" - tls_disable = true - } - sidecarconfig.yaml: | - {{- if .Values.injectconfig.securityContext }} - securityContext: - {{- if .Values.injectconfig.securityContext.fsGroup }} - fsGroup: {{ .Values.injectconfig.securityContext.fsGroup }} - {{- end }} - {{- end }} - containers: - # This container is only injected in K8S jobs to monitor app job's container termination and send signal to vault sidecar - - name: tvsi-job-babysitter - image: {{ include "talend-vault-sidecar-injector.injectconfig.jobbabysitter.image" .Values }} - imagePullPolicy: {{ .Values.injectconfig.jobbabysitter.image.pullPolicy }} - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - command: - - "sh" - - "-c" - - | - jwt_sa_token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) - pod_ns=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) - retCode=$(curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq .code) - if [ $retCode = "403" ]; then - curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq .message - exit 1 - fi - - while true; do - cntStatus=$(curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq -c --raw-output '.status.containerStatuses[] | select(.name == "").state | keys[0]') - if [ $cntStatus = "terminated" ]; then - echo "=> job container terminated: send signal" - touch /opt/talend/tvsi/vault-sidecars-signal-terminate - exit 0 - fi - sleep 2 - done - volumeMounts: - # Mount path used by injected sidecars to share data - - name: tvsi-shared - mountPath: /opt/talend/tvsi - # The name's value will be overridden by the webhook - - name: K8S_SA_SECRETS_VOL_NAME - mountPath: /var/run/secrets/kubernetes.io/serviceaccount - readOnly: true - {{- if .Values.injectconfig.jobbabysitter.resources }} - resources: -{{ toYaml .Values.injectconfig.jobbabysitter.resources | indent 10 }} - {{- end }} - - name: tvsi-vault-agent - image: {{ include "talend-vault-sidecar-injector.injectconfig.vault.image" .Values }} - imagePullPolicy: {{ .Values.injectconfig.vault.image.pullPolicy }} - env: - - name: SKIP_SETCAP - value: "true" - - name: VAULT_ADDR - value: {{ required "Vault server's address must be specified" .Values.vault.addr | quote }} - command: - - "sh" - - "-c" - - | - vault_auth_method="" - if [ $vault_auth_method = "kubernetes" ]; then - cat < vault-agent-config.hcl - pid_file = "/home/vault/pidfile" - - auto_auth { - method "kubernetes" { - mount_path = "auth/{{ .Values.vault.authMethods.kubernetes.path }}" - config = { - role = "" - token_path = "/var/run/secrets/talend/vault-sidecar-injector/serviceaccount/token" - } - } - - sink "file" { - config = { - path = "/home/vault/.vault-token" - } - } - } - - - - - EOF - elif [ $vault_auth_method = "approle" ]; then - cat < vault-agent-config.hcl - pid_file = "/home/vault/pidfile" - - auto_auth { - method "approle" { - mount_path = "auth/{{ .Values.vault.authMethods.approle.path }}" - config = { - role_id_file_path = "/opt/talend/secrets/{{ .Values.vault.authMethods.approle.roleid_filename }}" - secret_id_file_path = "/opt/talend/secrets/{{ .Values.vault.authMethods.approle.secretid_filename }}" - } - } - - sink "file" { - config = { - path = "/home/vault/.vault-token" - } - } - } - - - - - EOF - else - echo "Unsupported Vault Auth Method: $vault_auth_method" - exit 1 - fi - workload_is_job="" - if [ $workload_is_job = "true" ]; then - docker-entrypoint.sh agent -config=vault-agent-config.hcl {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -log-level={{- .Values.injectconfig.vault.loglevel }} & - while true; do - if [ -f "/opt/talend/tvsi/vault-sidecars-signal-terminate" ]; then - echo "=> exit (signal received)" - export VAULT_TOKEN=$(cat /home/vault/.vault-token); - vault token revoke {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -self; - exit 0 - fi - sleep 2 - done - else - docker-entrypoint.sh agent -config=vault-agent-config.hcl {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -log-level={{- .Values.injectconfig.vault.loglevel }} - fi - lifecycle: - preStop: - exec: - command: - - "sh" - - "-c" - - > - export VAULT_TOKEN=$(cat /home/vault/.vault-token); - vault token revoke {{ include "talend-vault-sidecar-injector.vault.cert.skip.verify" .Values }} -self; - volumeMounts: - # Mount path used by injected sidecars to share data - - name: tvsi-shared - mountPath: /opt/talend/tvsi - # Mount path used to share secrets. The associated volume is expected to be defined in application's manifest but in case it is not, - # a default 'secrets' volume will be injected in the requesting pod (see definition below) so that mutation process does not fail. - - name: secrets - mountPath: /opt/talend/secrets - # The name's value will be overridden by the webhook to point to container's service account volume to use for Vault authentication. - - name: TVSI_SA_SECRETS_VOL_NAME - mountPath: /var/run/secrets/talend/vault-sidecar-injector/serviceaccount - readOnly: true - {{- if .Values.injectconfig.vault.resources }} - resources: -{{ toYaml .Values.injectconfig.vault.resources | indent 10 }} - {{- end }} - volumes: - - name: tvsi-shared - emptyDir: - medium: Memory - # Note: if 'secrets' volume is defined in pod's manifest then it will be considered instead of the default definition below - - name: secrets - emptyDir: - medium: Memory + injectionconfig.yaml: | +{{ (tpl (.Files.Get "config/injectionconfig.yaml") . ) | indent 4 }} +{{ (tpl (.Files.Glob "config/podlifecyclehooks.yaml").AsConfig . ) | indent 2 }} +{{ (tpl (.Files.Glob "config/*.hcl").AsConfig . ) | indent 2 }} +{{ (tpl (.Files.Glob "config/*.tmpl").AsConfig . ) | indent 2 }} \ No newline at end of file diff --git a/deploy/helm/templates/deployment.yaml b/deploy/helm/templates/deployment.yaml index da68f88..56c8b6c 100644 --- a/deploy/helm/templates/deployment.yaml +++ b/deploy/helm/templates/deployment.yaml @@ -33,10 +33,10 @@ spec: args: - -port={{ .Values.image.port }} - -metricsport={{ .Values.image.metricsPort }} - - -annotationKeyPrefix={{ .Values.mutatingwebhook.annotations.keyPrefix }} - - -appLabelKey={{ .Values.mutatingwebhook.annotations.appLabelKey }} - - -appServiceLabelKey={{ .Values.mutatingwebhook.annotations.appServiceLabelKey }} - - -sidecarcfgfile=/opt/talend/webhook/config/sidecarconfig.yaml + - -annotationkeyprefix={{ .Values.mutatingwebhook.annotations.keyPrefix }} + - -applabelkey={{ .Values.mutatingwebhook.annotations.appLabelKey }} + - -appservicelabelkey={{ .Values.mutatingwebhook.annotations.appServiceLabelKey }} + - -injectioncfgfile=/opt/talend/webhook/config/injectionconfig.yaml - -proxycfgfile=/opt/talend/webhook/config/proxyconfig.hcl - -tmplblockfile=/opt/talend/webhook/config/templateblock.hcl - -tmpldefaultfile=/opt/talend/webhook/config/templatedefault.tmpl diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index 8f7f8ea..7fb1a3e 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -14,11 +14,11 @@ revisionHistoryLimit: 3 # revision history limit in tiller / helm / k8s # ---------------------------------------------------------------------------- image: - path: "talend/vault-sidecar-injector" # Docker image path - tag: "5.0.1" # Version/tag of the docker image - pullPolicy: IfNotPresent # Pull policy for docker images: IfNotPresent or Always - port: 8443 # service main port exposed by the docker image - metricsPort: 9000 # metricsPort defines the port exposed by the docker image for metrics collection + path: "talend/vault-sidecar-injector" # VSI image path + tag: "latest" # VSI image tag + pullPolicy: IfNotPresent # Pull policy for images: IfNotPresent or Always + port: 8443 # service main port exposed by the image + metricsPort: 9000 # metricsPort defines the port exposed by the image for metrics collection applicationNameLabel: talend-vault-sidecar-injector # applicationNameLabel represents the Talend Application Name and it must match the label com.talend.application from the docker image serviceNameLabel: talend-vault-sidecar-injector # serviceNameLabel represents the Talend Service Name and it must match the label com.talend.service from the docker image @@ -53,9 +53,9 @@ mutatingwebhook: injectconfig: jobbabysitter: image: - path: "everpeace/curl-jq" # Docker image path - tag: "latest" # Version/tag of the docker image - pullPolicy: IfNotPresent # Pull policy for docker images: IfNotPresent or Always + path: "everpeace/curl-jq" # image path + tag: "latest" # image tag + pullPolicy: Always # Pull policy for images: IfNotPresent or Always resources: limits: cpu: 20m # Job babysitter sidecar CPU resource limits @@ -65,9 +65,9 @@ injectconfig: memory: 20Mi # Job babysitter sidecar memory resource requests vault: image: - path: "vault" # Docker image path - tag: "1.3.1" # Version/tag of the docker image - pullPolicy: IfNotPresent # Pull policy for docker images: IfNotPresent or Always + path: "vault" # image path + tag: "1.3.2" # image tag + pullPolicy: Always # Pull policy for images: IfNotPresent or Always loglevel: info # Vault log level: trace, debug, info, warn, err resources: limits: @@ -83,9 +83,9 @@ injectconfig: hook: image: - path: "bitnami/kubectl" # Docker image path - tag: "latest" # Version/tag of the docker image - pullPolicy: IfNotPresent # Pull policy for docker images: IfNotPresent or Always + path: "bitnami/kubectl" # image path + tag: "latest" # image tag + pullPolicy: Always # Pull policy for images: IfNotPresent or Always # ---------------------------------------------------------------------------- # Define resources requests and limits diff --git a/doc/Discovering-Vault-Sidecar-Injector-Proxy.md b/doc/Discovering-Vault-Sidecar-Injector-Proxy.md index c9ffc2e..9d6bdd4 100644 --- a/doc/Discovering-Vault-Sidecar-Injector-Proxy.md +++ b/doc/Discovering-Vault-Sidecar-Injector-Proxy.md @@ -12,4 +12,4 @@ It is also possible to disable dynamic secrets retrieval and benefit only from t All of this is achieved using the brand new **modes** mechanism relying on the `sidecar.vault.talend.org/mode` annotation. With **modes** you can enable part or all of the features you need depending on your use case. Details are available in the [documentation](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#modes). -The provided `test-app-job-proxy` and `test-app-job-secrets-proxy` [samples](https://github.com/Talend/vault-sidecar-injector/blob/master/deploy/samples) show some of the possible combinations. +The provided `app-job-2-proxy` and `app-job-3-secrets-proxy` [samples](https://github.com/Talend/vault-sidecar-injector/blob/master/samples) show some of the possible combinations. diff --git a/doc/Open-sourcing-Vault-Sidecar-Injector.md b/doc/Open-sourcing-Vault-Sidecar-Injector.md index a81d698..7314d1a 100644 --- a/doc/Open-sourcing-Vault-Sidecar-Injector.md +++ b/doc/Open-sourcing-Vault-Sidecar-Injector.md @@ -87,7 +87,7 @@ Figure below depicts what's happening when you submit a manifest with custom ann To help you installing and using Vault Sidecar Injector and for detailed description, go look at the [README](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md) on GitHub. -The repository also comes with [samples](https://github.com/Talend/vault-sidecar-injector/tree/master/deploy/samples) of annotated workloads to allow you to quickly test the component. +The repository also comes with [samples](https://github.com/Talend/vault-sidecar-injector/tree/master/samples) of annotated workloads to allow you to quickly test the component. ## Community @@ -98,4 +98,4 @@ Here's how you can contribute to Vault Sidecar Injector: - [Report issues on GitHub](https://github.com/Talend/vault-sidecar-injector/issues) to help us squash any remaining bugs - [Submit PR on GitHub](https://github.com/Talend/vault-sidecar-injector/pulls) to propose new features -Tags: #technology #kubernetes #secrets #security #vault \ No newline at end of file +Tags: #technology #kubernetes #secrets #security #vault diff --git a/doc/Static-vs-Dynamic-Secrets.md b/doc/Static-vs-Dynamic-Secrets.md new file mode 100644 index 0000000..a26c345 --- /dev/null +++ b/doc/Static-vs-Dynamic-Secrets.md @@ -0,0 +1,17 @@ +# Static vs Dynamic Secrets + +*March 2020, [Post by Alain Saint-Sever, Senior Cloud Software Architect (@alstsever)](https://twitter.com/alstsever)* + +Available with `Vault Sidecar Injector` version **`6.0.0`**, *static secrets* submode (part of **secrets** mode) allows to handle simpler needs where you only want to fetch secrets that are not meant to change over your workload's lifetime. Such secrets may be database credentials (depending on your credentials rotation policy of course) or any confidential data static by nature. + +A new annotation, `sidecar.vault.talend.org/secrets-type`, is supported to explicitly define what kind of secrets you intend to fetch, default being *dynamic secrets*. + +When *static secrets* are set, `Vault Sidecar Injector` will only inject an init container in your workload's pod. Fetched secrets will be stored in a file in a shared memory volume, the same way it is already done for *dynamic secrets*. As a result, if you do not enable other modes (e.g. *proxy*, *job*) no sidecar will be added. It also means that you don't have to leverage hooks or wait for the injected Vault Agent to fetch your secrets: your workload can access the values right after its container is started. The drawback of course is that your secrets **will not be automatically refreshed upon changes**, opt for *dynamic secrets* if this behavior is required. + +If you enable several modes, you may end up with both init container and sidecar(s) in your workload. A comprehensive table is provided in the main documention in section [Modes and Injection Config Overview](https://github.com/Talend/vault-sidecar-injector/blob/master/README.md#modes-and-injection-config-overview). + +New [samples](https://github.com/Talend/vault-sidecar-injector/blob/master/samples) are available to quickly demonstrate how to benefit from this feature: + +- Deployment workload with only **secrets** mode on for *static secrets*: [manifest](https://github.com/Talend/vault-sidecar-injector/blob/master/samples/app-dep-4-secrets_static.yaml) +- Deployment workload with both **secrets** and **proxy** modes on to handle *static secrets* and direct use of Vault features (cipher/decipher data here) via the proxy: [manifest](https://github.com/Talend/vault-sidecar-injector/blob/master/samples/app-dep-5-secrets_static-proxy.yaml) +- Job workload with only **secrets** mode on for *static secrets*: [manifest](https://github.com/Talend/vault-sidecar-injector/blob/master/samples/app-job-4-secrets_static.yaml) diff --git a/main.go b/main.go index ab57a01..fcf1b4c 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -43,10 +43,10 @@ func main() { flag.IntVar(¶meters.MetricsPort, "metricsport", 9000, "metrics server port (Prometheus)") flag.StringVar(¶meters.CertFile, "tlscertfile", config.CertsPath+"/cert.pem", "file containing the x509 Certificate for HTTPS") flag.StringVar(¶meters.KeyFile, "tlskeyfile", config.CertsPath+"/key.pem", "file containing the x509 private key to tlscertfile") - flag.StringVar(¶meters.AnnotationKeyPrefix, "annotationKeyPrefix", "sidecar.vault", "annotations key prefix") - flag.StringVar(¶meters.AppLabelKey, "appLabelKey", "application.name", "key for application label") - flag.StringVar(¶meters.AppServiceLabelKey, "appServiceLabelKey", "service.name", "key for application's service label") - flag.StringVar(¶meters.SidecarCfgFile, "sidecarcfgfile", config.ConfigFilesPath+"/sidecarconfig.yaml", "file containing the mutation configuration (initcontainers, sidecars, volumes, ...)") + flag.StringVar(¶meters.AnnotationKeyPrefix, "annotationkeyprefix", "sidecar.vault", "annotations key prefix") + flag.StringVar(¶meters.AppLabelKey, "applabelkey", "application.name", "key for application label") + flag.StringVar(¶meters.AppServiceLabelKey, "appservicelabelkey", "service.name", "key for application's service label") + flag.StringVar(¶meters.InjectionCfgFile, "injectioncfgfile", config.ConfigFilesPath+"/injectionconfig.yaml", "file containing the mutation configuration (initcontainers, sidecars, volumes, ...)") flag.StringVar(¶meters.ProxyCfgFile, "proxycfgfile", config.ConfigFilesPath+"/proxyconfig.hcl", "file containing Vault proxy configuration") flag.StringVar(¶meters.TemplateBlockFile, "tmplblockfile", config.ConfigFilesPath+"/templateblock.hcl", "file containing the template block") flag.StringVar(¶meters.TemplateDefaultFile, "tmpldefaultfile", config.ConfigFilesPath+"/templatedefault.tmpl", "file containing the default template") @@ -74,7 +74,7 @@ func main() { } // Load webhook admission server's config - injectionCfg, err := config.Load(parameters) + vsiCfg, err := config.Load(parameters) if err != nil { os.Exit(1) } @@ -86,7 +86,7 @@ func main() { } vaultInjector := webhook.New( - injectionCfg, + vsiCfg, &http.Server{ Addr: fmt.Sprintf(":%v", parameters.Port), TLSConfig: &tls.Config{Certificates: []tls.Certificate{pair}}, diff --git a/pkg/config/constants.go b/pkg/config/constants.go index 0874fb1..93595f0 100644 --- a/pkg/config/constants.go +++ b/pkg/config/constants.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/config/load.go b/pkg/config/load.go index 72659d5..7ba6bec 100644 --- a/pkg/config/load.go +++ b/pkg/config/load.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,16 +23,16 @@ import ( ) // Load : Load Vault Sidecar Injector's config -func Load(whSvrParams WhSvrParameters) (*InjectionConfig, error) { +func Load(whSvrParams WhSvrParameters) (*VSIConfig, error) { klog.Infof("annotationKeyPrefix=%s", whSvrParams.AnnotationKeyPrefix) klog.Infof("appLabelKey=%s", whSvrParams.AppLabelKey) klog.Infof("appServiceLabelKey=%s", whSvrParams.AppServiceLabelKey) - // Load sidecar config - var sidecarConfig SidecarConfig - err := loadYaml(whSvrParams.SidecarCfgFile, &sidecarConfig) + // Load injection config + var injectionConfig InjectionConfig + err := loadYaml(whSvrParams.InjectionCfgFile, &injectionConfig) if err != nil { - klog.Errorf("Failed to load sidecar configuration: %v", err) + klog.Errorf("Failed to load injection configuration: %v", err) return nil, err } @@ -65,11 +65,11 @@ func Load(whSvrParams WhSvrParameters) (*InjectionConfig, error) { return nil, err } - return &InjectionConfig{ + return &VSIConfig{ VaultInjectorAnnotationKeyPrefix: whSvrParams.AnnotationKeyPrefix, ApplicationLabelKey: whSvrParams.AppLabelKey, ApplicationServiceLabelKey: whSvrParams.AppServiceLabelKey, - SidecarConfig: &sidecarConfig, + InjectionConfig: &injectionConfig, ProxyConfig: proxyConfig, TemplateBlock: templateBlock, TemplateDefaultTmpl: templateDefaultTmpl, diff --git a/pkg/config/load_test.go b/pkg/config/load_test.go index f4c24b7..fb069d1 100644 --- a/pkg/config/load_test.go +++ b/pkg/config/load_test.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,13 +23,13 @@ import ( ) const ( - proxyCfgFileResolved = "cache {\n use_auto_auth_token = true\n}\n\nlistener \"tcp\" {\n address = \"127.0.0.1:\"\n tls_disable = true\n}" - templateBlockResolved = "template {\n destination = \"/opt/talend/secrets/\"\n contents = <\n EOH\n command = \"\"\n wait {\n min = \"1s\"\n max = \"2s\"\n }\n}" - templateDefaultResolved = "{{ with secret \"\" }}{{ range \\$k, \\$v := .Data }}\n{{ \\$k }}={{ \\$v }}\n{{ end }}{{ end }}" + proxyCfgFileResolved = "cache {\n use_auto_auth_token = true\n}\n\nlistener \"tcp\" {\n address = \"127.0.0.1:\"\n tls_disable = true\n}" + templateBlockResolved = "template {\n destination = \"/opt/talend/secrets/\"\n contents = <\n EOH\n command = \"\"\n wait {\n min = \"1s\"\n max = \"2s\"\n }\n}" + templateDefaultResolved = "{{ with secret \"\" }}{{ range $k, $v := .Data }}\n{{ $k }}={{ $v }}\n{{ end }}{{ end }}" ) type inputLoaded struct { - sidecarCfgFile string + injectionCfgFile string proxyCfgFile string templateBlockFile string templateDefaultFile string @@ -37,7 +37,7 @@ type inputLoaded struct { } type expectedLoad struct { - sidecarCfgFileResolved string + injectionCfgFileResolved string proxyCfgFileResolved string templateBlockResolved string templateDefaultResolved string @@ -51,14 +51,14 @@ func TestLoadConfig(t *testing.T) { }{ { inputLoaded{ - "../../test/config/sidecarconfig.yaml", + "../../test/config/injectionconfig.yaml", "../../test/config/proxyconfig.hcl", "../../test/config/tmplblock.hcl", "../../test/config/tmpldefault.tmpl", "../../test/config/podlifecyclehooks.yaml", }, expectedLoad{ - "../../test/config/sidecarconfig.yaml.resolved", + "../../test/config/injectionconfig.yaml.resolved", proxyCfgFileResolved, templateBlockResolved, templateDefaultResolved, @@ -68,11 +68,11 @@ func TestLoadConfig(t *testing.T) { } for _, table := range tables { - injectionCfg, err := Load( + vsiCfg, err := Load( WhSvrParameters{ 0, 0, "", "", "", "", "", - table.sidecarCfgFile, + table.injectionCfgFile, table.proxyCfgFile, table.templateBlockFile, table.templateDefaultFile, @@ -80,17 +80,17 @@ func TestLoadConfig(t *testing.T) { }, ) if err != nil { - t.Errorf("Loading error \"%s\"", err) + t.Fatalf("Loading error \"%s\"", err) } // Verify strings - assert.Equal(t, table.proxyCfgFileResolved, injectionCfg.ProxyConfig) - assert.Equal(t, table.templateBlockResolved, injectionCfg.TemplateBlock) - assert.Equal(t, table.templateDefaultResolved, injectionCfg.TemplateDefaultTmpl) + assert.Equal(t, table.proxyCfgFileResolved, vsiCfg.ProxyConfig) + assert.Equal(t, table.templateBlockResolved, vsiCfg.TemplateBlock) + assert.Equal(t, table.templateDefaultResolved, vsiCfg.TemplateDefaultTmpl) // Verify yaml by marshalling the object into yaml again - assert.Equal(t, stringFromYamlFile(t, table.sidecarCfgFileResolved), stringFromYamlObj(t, injectionCfg.SidecarConfig)) - assert.Equal(t, stringFromYamlFile(t, table.podLifecycleHooksFileResolved), stringFromYamlObj(t, injectionCfg.PodslifecycleHooks)) + assert.Equal(t, stringFromYamlFile(t, table.injectionCfgFileResolved), stringFromYamlObj(t, vsiCfg.InjectionConfig)) + assert.Equal(t, stringFromYamlFile(t, table.podLifecycleHooksFileResolved), stringFromYamlObj(t, vsiCfg.PodslifecycleHooks)) } } diff --git a/pkg/config/types.go b/pkg/config/types.go index 9ec2575..a24c580 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,19 +27,18 @@ type WhSvrParameters struct { AnnotationKeyPrefix string // annotations key prefix AppLabelKey string // key for application label AppServiceLabelKey string // key for application's service label - SidecarCfgFile string // path to sidecar injector configuration file + InjectionCfgFile string // path to injection configuration file ProxyCfgFile string // path to Vault proxy configuration file TemplateBlockFile string // path to template file TemplateDefaultFile string // path to default template content file PodLifecycleHooksFile string // path to pod's lifecycle hooks file } -// SidecarConfig : resources that will be injected (read from config file) -type SidecarConfig struct { - SecurityContext *corev1.PodSecurityContext `yaml:"securityContext" json:"securityContext"` - InitContainers []corev1.Container `yaml:"initContainers" json:"initContainers"` - Containers []corev1.Container `yaml:"containers" json:"containers"` - Volumes []corev1.Volume `yaml:"volumes" json:"volumes"` +// InjectionConfig : resources that will be injected (read from config file) +type InjectionConfig struct { + InitContainers []corev1.Container `yaml:"initContainers" json:"initContainers"` + Containers []corev1.Container `yaml:"containers" json:"containers"` + Volumes []corev1.Volume `yaml:"volumes" json:"volumes"` } // LifecycleHooks : lifecycle hooks to inject in requesting pod @@ -47,13 +46,13 @@ type LifecycleHooks struct { PostStart *corev1.Handler `yaml:"postStart" json:"postStart"` } -// InjectionConfig : Vault Sidecar Injector configuration -type InjectionConfig struct { +// VSIConfig : Vault Sidecar Injector configuration +type VSIConfig struct { VaultInjectorAnnotationKeyPrefix string // annotations prefix VaultInjectorAnnotationsFQ map[string]string // supported annotations (fully-qualified with prefix if any) ApplicationLabelKey string // key for application label ApplicationServiceLabelKey string // key for application's service label - SidecarConfig *SidecarConfig // sidecar injector configuration + InjectionConfig *InjectionConfig // injection configuration ProxyConfig string // Vault proxy configuration TemplateBlock string // template TemplateDefaultTmpl string // default template content diff --git a/pkg/context/constants.go b/pkg/context/constants.go new file mode 100644 index 0000000..744af47 --- /dev/null +++ b/pkg/context/constants.go @@ -0,0 +1,29 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package context + +const ( + //--- JSON Patch operations + JsonPatchOpAdd = "add" + JsonPatchOpReplace = "replace" +) + +const ( + //--- JSON Path + JsonPathAnnotations = "/metadata/annotations" + JsonPathInitContainers = "/spec/initContainers" + JsonPathContainers = "/spec/containers" + JsonPathVolumes = "/spec/volumes" +) diff --git a/pkg/context/types.go b/pkg/context/types.go new file mode 100644 index 0000000..c5a0ea3 --- /dev/null +++ b/pkg/context/types.go @@ -0,0 +1,37 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package context + +// InjectionContext : struct to carry computed placeholders' values and context info for current injection +type InjectionContext struct { + K8sDefaultSATokenVolumeName string + VaultInjectorSATokenVolumeName string + VaultAuthMethod string + VaultRole string + ModesStatus map[string]bool + ModesConfig map[string]ModeConfig +} + +// ModeConfig : interface for mode's config +type ModeConfig interface { + GetTemplate() string +} + +// PatchOperation : this struct represents a JSON Patch operation (see http://jsonpatch.com/) +type PatchOperation struct { + Op string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value,omitempty"` +} diff --git a/pkg/mode/job/constants.go b/pkg/mode/job/constants.go new file mode 100644 index 0000000..67f9958 --- /dev/null +++ b/pkg/mode/job/constants.go @@ -0,0 +1,30 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package job + +const ( + //--- Job handling - Temporary mechanism until KEP https://github.com/kubernetes/enhancements/blob/master/keps/sig-apps/sidecarcontainers.md is implemented (and we migrate on appropriate version of k8s) + jobMonitoringContainerName = "tvsi-job-babysitter" // Name of our specific sidecar container to inject in submitted jobs + jobListenerContainerName = "tvsi-vault-agent" // Name of the container listening for signal from job monitoring container + + //--- Job handling env vars + jobContainerNameEnv = "VSI_JOB_CNT_NAME" // Env var for name of the app job's container + jobWorkloadEnv = "VSI_JOB_WORKLOAD" // Env var set to "true" if submitted workload is a k8s job +) + +const ( + //--- Vault Sidecar Injector supported modes + vaultInjectorModeJob = "job" // Enable handling of Kubernetes Job +) diff --git a/pkg/mode/job/job-func-inject.go b/pkg/mode/job/job-func-inject.go new file mode 100644 index 0000000..9d179aa --- /dev/null +++ b/pkg/mode/job/job-func-inject.go @@ -0,0 +1,52 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package job + +import ( + "errors" + ctx "talend/vault-sidecar-injector/pkg/context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/klog" +) + +func jobModeInject(containerBasePath string, podContainers []corev1.Container, containerName string, env []corev1.EnvVar, context *ctx.InjectionContext) (bool, error) { + if (containerBasePath == ctx.JsonPathContainers) && (len(podContainers) != 1) { + err := errors.New("Submitted pod should contain only one container") + klog.Errorf("[%s] %s", vaultInjectorModeJob, err.Error()) + return false, err + } + + for _, cntName := range jobContainerNames[containerBasePath] { + if cntName == containerName { + klog.Infof("[%s] Injecting container %s (path: %s)", vaultInjectorModeJob, containerName, containerBasePath) + + // Resolve job env vars + for envIdx := range env { + if env[envIdx].Name == jobContainerNameEnv { + env[envIdx].Value = podContainers[0].Name + } + + if env[envIdx].Name == jobWorkloadEnv { + env[envIdx].Value = "true" + } + } + + return true, nil + } + } + + return false, nil +} diff --git a/pkg/mode/job/job.go b/pkg/mode/job/job.go new file mode 100644 index 0000000..b59cb56 --- /dev/null +++ b/pkg/mode/job/job.go @@ -0,0 +1,35 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package job + +import ( + m "talend/vault-sidecar-injector/pkg/mode" +) + +func init() { + // Register mode + m.RegisterMode( + m.VaultInjectorModeInfo{ + Key: vaultInjectorModeJob, + DefaultMode: false, + EnableDefaultMode: true, // Default mode will also be enabled if job is **the only mode on** (as it does not make sense to have only this mode) + InjectContainerFunc: jobModeInject, + }, + ) +} + +func (jobModeCfg *jobModeConfig) GetTemplate() string { + return jobModeCfg.template +} diff --git a/pkg/mode/job/types.go b/pkg/mode/job/types.go new file mode 100644 index 0000000..30de6a8 --- /dev/null +++ b/pkg/mode/job/types.go @@ -0,0 +1,25 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package job + +import ctx "talend/vault-sidecar-injector/pkg/context" + +var jobContainerNames = map[string][]string{ + ctx.JsonPathContainers: {jobMonitoringContainerName, jobListenerContainerName}, +} + +type jobModeConfig struct { + template string +} diff --git a/pkg/mode/mode.go b/pkg/mode/mode.go new file mode 100644 index 0000000..858052f --- /dev/null +++ b/pkg/mode/mode.go @@ -0,0 +1,79 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mode + +import ( + "os" + + "k8s.io/klog" +) + +// RegisterMode : register mode +func RegisterMode(modeInfo VaultInjectorModeInfo) { + klog.Infof("Registering mode: %s", modeInfo.Key) + VaultInjectorModes[modeInfo.Key] = modeInfo + + if modeInfo.InjectContainerFunc == nil { + klog.Error("Mandatory InjectContainer function not implemented") + os.Exit(1) + } +} + +// GetModesStatus : get modes' status +func GetModesStatus(requestedModes []string, modes map[string]bool) { + var defaultModeKey string + + // Init modes for current injection context + for key := range VaultInjectorModes { + modes[key] = false + + if VaultInjectorModes[key].DefaultMode { + defaultModeKey = key + } + } + + requestedModesNum := len(requestedModes) + + if requestedModesNum > 0 { + if requestedModesNum == 1 && requestedModes[0] == "" { // If no mode(s) provided then only enable default mode + modes[defaultModeKey] = true + } else { + // Look at requested modes, ignore and remove unknown values + for _, requestedMode := range requestedModes { + bModeFound := false + + for key := range VaultInjectorModes { + if requestedMode == key { + modes[requestedMode] = true + bModeFound = true + + // Look if we must also enable default mode when this mode is on (**and no other mode(s) requested**) + if requestedModesNum == 1 && VaultInjectorModes[key].EnableDefaultMode { + modes[defaultModeKey] = true + } + + break + } + } + + if !bModeFound { + klog.Warningf("Ignore unknown requested Vault Sidecar Injector mode: %s", requestedMode) + } + } + } + } else { // If no mode(s) provided then only enable default mode + modes[defaultModeKey] = true + } +} diff --git a/pkg/mode/proxy/constants.go b/pkg/mode/proxy/constants.go new file mode 100644 index 0000000..70a3276 --- /dev/null +++ b/pkg/mode/proxy/constants.go @@ -0,0 +1,40 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy + +const ( + //--- Vault Sidecar Injector modes annotation keys (without prefix) + vaultInjectorAnnotationProxyPortKey = "proxy-port" // Optional. Port assigned to local Vault proxy. +) + +const ( + proxyContainerName = "tvsi-vault-agent" // Name of our proxy container to inject + vaultProxyDefaultPort = "8200" // Default port to access local Vault proxy +) + +const ( + //--- Vault Agent placeholders related to modes + vaultProxyPortPlaceholder = "" +) + +const ( + //--- Vault Agent env vars related to modes + vaultProxyConfigPlaceholderEnv = "VSI_PROXY_CONFIG_PLACEHOLDER" +) + +const ( + //--- Vault Sidecar Injector supported modes + vaultInjectorModeProxy = "proxy" // Enable local Vault proxy +) diff --git a/pkg/webhook/proxy.go b/pkg/mode/proxy/proxy-func-compute.go similarity index 57% rename from pkg/webhook/proxy.go rename to pkg/mode/proxy/proxy-func-compute.go index 22e93f0..009a8cb 100644 --- a/pkg/webhook/proxy.go +++ b/pkg/mode/proxy/proxy-func-compute.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,21 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package webhook +package proxy import ( "strings" + cfg "talend/vault-sidecar-injector/pkg/config" + ctx "talend/vault-sidecar-injector/pkg/context" ) -// Vault Sidecar Injector: Proxy Mode -func (vaultInjector *VaultInjector) proxyMode(annotations map[string]string) (string, error) { - proxyPort := annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationProxyPortKey]] +func proxyModeCompute(config *cfg.VSIConfig, labels, annotations map[string]string) (ctx.ModeConfig, error) { + proxyPort := annotations[config.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationProxyPortKey]] if proxyPort == "" { // Default port proxyPort = vaultProxyDefaultPort } - proxyConfig := strings.Replace(vaultInjector.ProxyConfig, vaultProxyPortPlaceholder, proxyPort, -1) + template := strings.Replace(config.ProxyConfig, vaultProxyPortPlaceholder, proxyPort, -1) - return proxyConfig, nil + return &proxyModeConfig{template}, nil } diff --git a/pkg/mode/proxy/proxy-func-inject.go b/pkg/mode/proxy/proxy-func-inject.go new file mode 100644 index 0000000..cb504fe --- /dev/null +++ b/pkg/mode/proxy/proxy-func-inject.go @@ -0,0 +1,41 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy + +import ( + ctx "talend/vault-sidecar-injector/pkg/context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/klog" +) + +func proxyModeInject(containerBasePath string, podContainers []corev1.Container, containerName string, env []corev1.EnvVar, context *ctx.InjectionContext) (bool, error) { + for _, cntName := range proxyContainerNames[containerBasePath] { + if cntName == containerName { + klog.Infof("[%s] Injecting container %s (path: %s)", vaultInjectorModeProxy, containerName, containerBasePath) + + // Resolve proxy env vars + for envIdx := range env { + if env[envIdx].Name == vaultProxyConfigPlaceholderEnv { + env[envIdx].Value = context.ModesConfig[vaultInjectorModeProxy].GetTemplate() + } + } + + return true, nil + } + } + + return false, nil +} diff --git a/pkg/mode/proxy/proxy.go b/pkg/mode/proxy/proxy.go new file mode 100644 index 0000000..f38b36b --- /dev/null +++ b/pkg/mode/proxy/proxy.go @@ -0,0 +1,37 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy + +import ( + m "talend/vault-sidecar-injector/pkg/mode" +) + +func init() { + // Register mode + m.RegisterMode( + m.VaultInjectorModeInfo{ + Key: vaultInjectorModeProxy, + DefaultMode: false, + EnableDefaultMode: false, + Annotations: []string{vaultInjectorAnnotationProxyPortKey}, + ComputeTemplatesFunc: proxyModeCompute, + InjectContainerFunc: proxyModeInject, + }, + ) +} + +func (proxyModeCfg *proxyModeConfig) GetTemplate() string { + return proxyModeCfg.template +} diff --git a/pkg/mode/proxy/types.go b/pkg/mode/proxy/types.go new file mode 100644 index 0000000..3747880 --- /dev/null +++ b/pkg/mode/proxy/types.go @@ -0,0 +1,25 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy + +import ctx "talend/vault-sidecar-injector/pkg/context" + +var proxyContainerNames = map[string][]string{ + ctx.JsonPathContainers: {proxyContainerName}, +} + +type proxyModeConfig struct { + template string +} diff --git a/pkg/mode/secrets/constants.go b/pkg/mode/secrets/constants.go new file mode 100644 index 0000000..f977b6d --- /dev/null +++ b/pkg/mode/secrets/constants.go @@ -0,0 +1,60 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secrets + +const ( + //--- Vault Sidecar Injector modes annotation keys (without prefix) + vaultInjectorAnnotationSecretsPathKey = "secrets-path" // Optional. Full path, e.g.: "secret/", "aws/creds/", ... Several values separated by ','. + vaultInjectorAnnotationSecretsTemplateKey = "secrets-template" // Optional. Allow to override default template. Ignore 'secrets-path' annotation. Several values separated by ','. + vaultInjectorAnnotationTemplateDestKey = "secrets-destination" // Optional. If not set, secrets will be stored in file "secrets.properties". Several values separated by ','. + vaultInjectorAnnotationLifecycleHookKey = "secrets-hook" // Optional. If set, lifecycle hooks loaded from config will be added to pod's container(s) + vaultInjectorAnnotationSecretsTypeKey = "secrets-type" // Optional. Type of secrets to handle: dynamic (default) or static + vaultInjectorAnnotationTemplateCmdKey = "notify" // Optional. Command to run after template is rendered. Several values separated by ','. +) + +const ( + secretsContainerName = "tvsi-vault-agent" // Name of our secrets container to inject + secretsInitContainerName = "tvsi-vault-agent-init" // Name of our secrets init container to inject + secretsVolName = "secrets" // Name of the volume shared between containers to store secrets file(s) + templateAppSvcDefaultDestination = "secrets.properties" // Default secrets destination + vaultDefaultSecretsEnginePath = "secret" // Default path for Vault K/V Secrets Engine if no 'secrets-path' annotation + secretsAnnotationSeparator = "," // Generic separator for secrets annotations' values + secretsAnnotationTemplateSeparator = "---" // Separator for secrets templates annotation's values +) + +const ( + //--- Vault Agent placeholders related to modes + secretsVaultPathPlaceholder = "" + secretsDestinationPlaceholder = "" + secretsTemplateContentPlaceholder = "" + secretsTemplateCommandPlaceholder = "" + secretsVolMountPathPlaceholder = "" +) + +const ( + //--- Vault Agent env vars related to modes + secretsTemplatesPlaceholderEnv = "VSI_SECRETS_TEMPLATES_PLACEHOLDER" +) + +const ( + //--- Vault Sidecar Injector supported modes + vaultInjectorModeSecrets = "secrets" // Enable fetching of secrets from Vault store +) + +const ( + //--- Vault Sidecar Injector secrets type + vaultInjectorSecretsTypeDynamic = "dynamic" + vaultInjectorSecretsTypeStatic = "static" +) diff --git a/pkg/mode/secrets/secrets-func-compute.go b/pkg/mode/secrets/secrets-func-compute.go new file mode 100644 index 0000000..a661808 --- /dev/null +++ b/pkg/mode/secrets/secrets-func-compute.go @@ -0,0 +1,118 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secrets + +import ( + "errors" + "fmt" + "strings" + cfg "talend/vault-sidecar-injector/pkg/config" + ctx "talend/vault-sidecar-injector/pkg/context" + + "k8s.io/klog" +) + +func secretsModeCompute(config *cfg.VSIConfig, labels, annotations map[string]string) (ctx.ModeConfig, error) { + secretsType := annotations[config.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationSecretsTypeKey]] + secretsPath := strings.Split(annotations[config.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationSecretsPathKey]], secretsAnnotationSeparator) + secretsTemplate := strings.Split(annotations[config.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationSecretsTemplateKey]], secretsAnnotationTemplateSeparator) + templateDest := strings.Split(annotations[config.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationTemplateDestKey]], secretsAnnotationSeparator) + templateCmd := strings.Split(annotations[config.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationTemplateCmdKey]], secretsAnnotationSeparator) + + secretsPathNum := len(secretsPath) + secretsTemplateNum := len(secretsTemplate) + templateDestNum := len(templateDest) + + if secretsType == "" { + secretsType = vaultInjectorSecretsTypeDynamic // Dynamic secrets by default + } else { + secretsTypeSupported := false + for _, supportedSecretType := range vaultInjectorSecretsTypes { + if secretsType == supportedSecretType { + secretsTypeSupported = true + break + } + } + + if !secretsTypeSupported { + err := fmt.Errorf("Submitted pod makes use of unsupported secrets type '%s'", secretsType) + klog.Errorf("[%s] %s", vaultInjectorModeSecrets, err.Error()) + return nil, err + } + } + + if secretsPathNum == 1 && secretsPath[0] == "" { // Build default secrets path: "secret//" + applicationLabel := labels[config.ApplicationLabelKey] + applicationServiceLabel := labels[config.ApplicationServiceLabelKey] + + if applicationLabel == "" || applicationServiceLabel == "" { + err := fmt.Errorf("Submitted pod must contain labels %s and %s", config.ApplicationLabelKey, config.ApplicationServiceLabelKey) + klog.Errorf("[%s] %s", vaultInjectorModeSecrets, err.Error()) + return nil, err + } + + secretsPath[0] = vaultDefaultSecretsEnginePath + "/" + applicationLabel + "/" + applicationServiceLabel + } + + if templateDestNum == 1 && templateDest[0] == "" { // Use default + templateDest[0] = templateAppSvcDefaultDestination + } + + if secretsTemplateNum == 1 && secretsTemplate[0] == "" { + // We must have same numbers of secrets path & secrets destinations + if templateDestNum != secretsPathNum { + err := errors.New("Submitted pod must contain same numbers of secrets path and secrets destinations") + klog.Errorf("[%s] %s", vaultInjectorModeSecrets, err.Error()) + return nil, err + } + + // If no custom template(s), use default template + secretsTemplate = make([]string, templateDestNum) + for tmplIdx := 0; tmplIdx < templateDestNum; tmplIdx++ { + secretsTemplate[tmplIdx] = config.TemplateDefaultTmpl + } + } else { + // We must have same numbers of custom templates & secrets destinations ... + if templateDestNum != secretsTemplateNum { + err := errors.New("Submitted pod must contain same numbers of templates and secrets destinations") + klog.Errorf("[%s] %s", vaultInjectorModeSecrets, err.Error()) + return nil, err + } + + // ... and we ignore content of 'secrets-path' annotation ('cause we provide full template), but we need to init an empty array + // to not end up with errors in the replace loop to come + secretsPath = make([]string, templateDestNum) + } + + // Copy provided template commands, if less commands than secrets destinations: remaining commands set to "" + templateCommands := make([]string, templateDestNum) + copy(templateCommands, templateCmd) + + var templateBlock string + var templates strings.Builder + + for tmplIdx := 0; tmplIdx < templateDestNum; tmplIdx++ { + templateBlock = config.TemplateBlock + templateBlock = strings.Replace(templateBlock, secretsDestinationPlaceholder, templateDest[tmplIdx], -1) + templateBlock = strings.Replace(templateBlock, secretsTemplateContentPlaceholder, secretsTemplate[tmplIdx], -1) + templateBlock = strings.Replace(templateBlock, secretsVaultPathPlaceholder, secretsPath[tmplIdx], -1) + templateBlock = strings.Replace(templateBlock, secretsTemplateCommandPlaceholder, templateCommands[tmplIdx], -1) + + templates.WriteString(templateBlock) + templates.WriteString("\n") + } + + return &secretsModeConfig{secretsType, templates.String()}, nil +} diff --git a/pkg/mode/secrets/secrets-func-inject.go b/pkg/mode/secrets/secrets-func-inject.go new file mode 100644 index 0000000..42632a5 --- /dev/null +++ b/pkg/mode/secrets/secrets-func-inject.go @@ -0,0 +1,45 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secrets + +import ( + ctx "talend/vault-sidecar-injector/pkg/context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/klog" +) + +func secretsModeInject(containerBasePath string, podContainers []corev1.Container, containerName string, env []corev1.EnvVar, context *ctx.InjectionContext) (bool, error) { + for _, cntName := range secretsContainerNames[containerBasePath] { + if cntName == containerName { + // Look type of secrets: inject init container only for static secrets + if (isSecretsStatic(context) && (containerBasePath == ctx.JsonPathInitContainers)) || + (!isSecretsStatic(context) && (containerBasePath == ctx.JsonPathContainers)) { + klog.Infof("[%s] Injecting container %s (path: %s)", vaultInjectorModeSecrets, containerName, containerBasePath) + + // Resolve secrets env vars + for envIdx := range env { + if env[envIdx].Name == secretsTemplatesPlaceholderEnv { + env[envIdx].Value = context.ModesConfig[vaultInjectorModeSecrets].GetTemplate() + } + } + + return true, nil + } + } + } + + return false, nil +} diff --git a/pkg/mode/secrets/secrets-func-patch.go b/pkg/mode/secrets/secrets-func-patch.go new file mode 100644 index 0000000..50b80fe --- /dev/null +++ b/pkg/mode/secrets/secrets-func-patch.go @@ -0,0 +1,96 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secrets + +import ( + "errors" + "strconv" + "strings" + cfg "talend/vault-sidecar-injector/pkg/config" + ctx "talend/vault-sidecar-injector/pkg/context" + + corev1 "k8s.io/api/core/v1" + + "k8s.io/klog" +) + +func secretsModePatch(config *cfg.VSIConfig, podSpec corev1.PodSpec, annotations map[string]string, context *ctx.InjectionContext) (patch []ctx.PatchOperation, err error) { + // Add lifecycle hooks to requesting pod's container(s) if needed + if patchHooks, err := addLifecycleHooks(config, podSpec.Containers, annotations, context); err == nil { + patch = append(patch, patchHooks...) + return patch, nil + } + + return nil, err +} + +func addLifecycleHooks(config *cfg.VSIConfig, podContainers []corev1.Container, annotations map[string]string, context *ctx.InjectionContext) (patch []ctx.PatchOperation, err error) { + // Only for dynamic secrets + if !isSecretsStatic(context) { + switch strings.ToLower(annotations[config.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationLifecycleHookKey]]) { + default: + return patch, nil + case "y", "yes", "true", "on": + if config.PodslifecycleHooks.PostStart != nil { + secretsVolMountPath, err := getMountPathOfSecretsVolume(podContainers) + if err != nil { + return nil, err + } + + if config.PodslifecycleHooks.PostStart.Exec == nil { + err = errors.New("Unsupported lifecycle hook. Only support Exec type") + klog.Errorf("[%s] %s", vaultInjectorModeSecrets, err.Error()) + return nil, err + } + + // We will modify some values here so make a copy to not change origin + hookCommand := make([]string, len(config.PodslifecycleHooks.PostStart.Exec.Command)) + copy(hookCommand, config.PodslifecycleHooks.PostStart.Exec.Command) + + for commandIdx := range hookCommand { + hookCommand[commandIdx] = strings.Replace(hookCommand[commandIdx], secretsVolMountPathPlaceholder, secretsVolMountPath, -1) + } + + postStartHook := &corev1.Handler{Exec: &corev1.ExecAction{Command: hookCommand}} + + // Add hooks to container(s) of requesting pod + for podCntIdx, podCnt := range podContainers { + if podCnt.Lifecycle != nil { + if podCnt.Lifecycle.PostStart != nil { + klog.Warningf("[%s] Replacing existing postStart hook for container %s", vaultInjectorModeSecrets, podCnt.Name) + } + + podCnt.Lifecycle.PostStart = postStartHook + } else { + podCnt.Lifecycle = &corev1.Lifecycle{ + PostStart: postStartHook, + } + } + + // Here we have to use 'replace' JSON Patch operation + patch = append(patch, ctx.PatchOperation{ + Op: ctx.JsonPatchOpReplace, + Path: ctx.JsonPathContainers + "/" + strconv.Itoa(podCntIdx), + Value: podCnt, + }) + } + } + + return patch, nil + } + } else { + return patch, nil + } +} diff --git a/pkg/mode/secrets/secrets.go b/pkg/mode/secrets/secrets.go new file mode 100644 index 0000000..1b2a41c --- /dev/null +++ b/pkg/mode/secrets/secrets.go @@ -0,0 +1,45 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secrets + +import ( + m "talend/vault-sidecar-injector/pkg/mode" +) + +func init() { + // Register mode + m.RegisterMode( + m.VaultInjectorModeInfo{ + Key: vaultInjectorModeSecrets, + DefaultMode: true, // Secrets will be enabled if no mode explicitly set via mode annotation in manifest + EnableDefaultMode: false, + Annotations: []string{ + vaultInjectorAnnotationSecretsPathKey, + vaultInjectorAnnotationSecretsTemplateKey, + vaultInjectorAnnotationTemplateDestKey, + vaultInjectorAnnotationLifecycleHookKey, + vaultInjectorAnnotationSecretsTypeKey, + vaultInjectorAnnotationTemplateCmdKey, + }, + ComputeTemplatesFunc: secretsModeCompute, + PatchPodFunc: secretsModePatch, + InjectContainerFunc: secretsModeInject, + }, + ) +} + +func (secModeCfg *secretsModeConfig) GetTemplate() string { + return secModeCfg.template +} diff --git a/pkg/mode/secrets/types.go b/pkg/mode/secrets/types.go new file mode 100644 index 0000000..81e4e39 --- /dev/null +++ b/pkg/mode/secrets/types.go @@ -0,0 +1,32 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secrets + +import ctx "talend/vault-sidecar-injector/pkg/context" + +var vaultInjectorSecretsTypes = [...]string{ + vaultInjectorSecretsTypeDynamic, + vaultInjectorSecretsTypeStatic, +} + +var secretsContainerNames = map[string][]string{ + ctx.JsonPathInitContainers: {secretsInitContainerName}, + ctx.JsonPathContainers: {secretsContainerName}, +} + +type secretsModeConfig struct { + secretsType string + template string +} diff --git a/pkg/mode/secrets/utils.go b/pkg/mode/secrets/utils.go new file mode 100644 index 0000000..32a41f4 --- /dev/null +++ b/pkg/mode/secrets/utils.go @@ -0,0 +1,73 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secrets + +import ( + "errors" + "fmt" + ctx "talend/vault-sidecar-injector/pkg/context" + + corev1 "k8s.io/api/core/v1" + + "k8s.io/klog" +) + +func getSecretsType(config ctx.ModeConfig) (string, error) { + if config != nil { + secModeCfg, ok := config.(*secretsModeConfig) // here we use type assertion (https://golang.org/ref/spec#Type_assertions) + + if ok { + return secModeCfg.secretsType, nil + } + + err := errors.New("Provided type cannot be casted to 'secretsModeConfig'") + klog.Error(err.Error()) + return "", err + } + + err := errors.New("Null mode config") + klog.Error(err.Error()) + return "", err +} + +func isSecretsStatic(context *ctx.InjectionContext) bool { + if secretsType, err := getSecretsType(context.ModesConfig[vaultInjectorModeSecrets]); err == nil { + return secretsType == vaultInjectorSecretsTypeStatic + } + + return false +} + +func getMountPathOfSecretsVolume(cnts []corev1.Container) (string, error) { + var secretsVolMountPath string + +Loop: + for _, sourceContainer := range cnts { + for _, volMount := range sourceContainer.VolumeMounts { + if volMount.Name == secretsVolName { + secretsVolMountPath = volMount.MountPath + break Loop + } + } + } + + if secretsVolMountPath == "" { + err := fmt.Errorf("Volume Mount %s not found in submitted pod", secretsVolName) + klog.Error(err.Error()) + return "", err + } + + return secretsVolMountPath, nil +} diff --git a/pkg/mode/types.go b/pkg/mode/types.go new file mode 100644 index 0000000..7c07db4 --- /dev/null +++ b/pkg/mode/types.go @@ -0,0 +1,48 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mode + +import ( + cfg "talend/vault-sidecar-injector/pkg/config" + ctx "talend/vault-sidecar-injector/pkg/context" + + corev1 "k8s.io/api/core/v1" +) + +// VaultInjectorModes : modes +var VaultInjectorModes = make(map[string]VaultInjectorModeInfo) + +// VaultInjectorModeInfo : mode info +type VaultInjectorModeInfo struct { + Key string // mode key (== mode's name or id) + DefaultMode bool // mode to enable when no mode explicitly requested in incoming manifest + EnableDefaultMode bool // should we also enable the default mode when this mode is the only one being requested? + Annotations []string // mode's annotations + ComputeTemplatesFunc func( + config *cfg.VSIConfig, + labels, + annotations map[string]string) (ctx.ModeConfig, error) // to compute templates used in injected container(s) + PatchPodFunc func( + config *cfg.VSIConfig, + podSpec corev1.PodSpec, + annotations map[string]string, + context *ctx.InjectionContext) (patch []ctx.PatchOperation, err error) // to patch submitted pod's container(s) + InjectContainerFunc func( + containerBasePath string, + podContainers []corev1.Container, + containerName string, + env []corev1.EnvVar, + context *ctx.InjectionContext) (bool, error) // to test if container should be injected and, if so, resolve env vars +} diff --git a/pkg/webhook/constants.go b/pkg/webhook/constants.go index 6e9920e..f3b1f4a 100644 --- a/pkg/webhook/constants.go +++ b/pkg/webhook/constants.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,63 +16,41 @@ package webhook const ( //--- Vault Sidecar Injector annotation keys (without prefix) - vaultInjectorAnnotationInjectKey = "inject" // Mandatory - vaultInjectorAnnotationAuthMethodKey = "auth" // Optional. Vault Auth Method to use: kubernetes (default) or approle - vaultInjectorAnnotationModeKey = "mode" // Optional. Comma-separated list of mode(s) to enable. - vaultInjectorAnnotationProxyPortKey = "proxy-port" // Optional. Port assigned to local Vault proxy. - vaultInjectorAnnotationRoleKey = "role" // Optional. To explicitly provide Vault role to use - vaultInjectorAnnotationSATokenKey = "sa-token" // Optional. Full path to service account token used for Vault Kubernetes authentication - vaultInjectorAnnotationSecretsPathKey = "secrets-path" // Optional. Full path, e.g.: "secret/", "aws/creds/", ... Several values separated by ','. - vaultInjectorAnnotationSecretsTemplateKey = "secrets-template" // Optional. Allow to override default template. Ignore 'secrets-path' annotation. Several values separated by ','. - vaultInjectorAnnotationTemplateDestKey = "secrets-destination" // Optional. If not set, secrets will be stored in file "secrets.properties". Several values separated by ','. - vaultInjectorAnnotationLifecycleHookKey = "secrets-hook" // Optional. If set, lifecycle hooks loaded from config will be added to pod's container(s) - vaultInjectorAnnotationTemplateCmdKey = "notify" // Optional. Command to run after template is rendered. Several values separated by ','. - vaultInjectorAnnotationWorkloadKey = "workload" // Optional. If set to "job", supplementary container and signaling mechanism will also be injected to properly handle k8s job - vaultInjectorAnnotationStatusKey = "status" // Not to be set by requesting pods: set by the Webhook Admission Controller if injection ok + // Input annotations (set on incoming manifest) + vaultInjectorAnnotationInjectKey = "inject" // Mandatory + vaultInjectorAnnotationAuthMethodKey = "auth" // Optional. Vault Auth Method to use: kubernetes (default) or approle + vaultInjectorAnnotationModeKey = "mode" // Optional. Comma-separated list of mode(s) to enable. + vaultInjectorAnnotationRoleKey = "role" // Optional. To explicitly provide Vault role to use + vaultInjectorAnnotationSATokenKey = "sa-token" // Optional. Full path to service account token used for Vault Kubernetes authentication + vaultInjectorAnnotationWorkloadKey = "workload" // Optional. If set to "job", supplementary container and signaling mechanism will also be injected to properly handle k8s job + // Output annotation (set by VSI webhook) + vaultInjectorAnnotationStatusKey = "status" // Not to be set by requesting pods: set by the Webhook Admission Controller if injection ok +) - //--- Vault Sidecar Injector annotation values - vaultInjectorAnnotationStatusValue = "injected" - vaultInjectorAnnotationWorkloadJobValue = "job" +const ( + //--- Vault Sidecar Injector status + vaultInjectorStatusInjected = "injected" +) - //--- Vault Sidecar Injector supported modes - vaultInjectorModeSecrets = "secrets" // Enable dynamic fetching of secrets from Vault store - vaultInjectorModeProxy = "proxy" // Enable local Vault proxy +const ( + //--- Vault Sidecar Injector workloads + vaultInjectorWorkloadJob = "job" +) +const ( //--- Vault Sidecar Injector mount path for service accounts vaultInjectorSATokenVolMountPath = "/var/run/secrets/talend/vault-sidecar-injector/serviceaccount" k8sDefaultSATokenVolMountPath = "/var/run/secrets/kubernetes.io/serviceaccount" + //-- Volumes + secretsVolName = "secrets" // Name of the volume shared between containers to store secrets file(s) +) - //--- Vault Agent placeholders - vaultRolePlaceholder = "" - vaultAuthMethodPlaceholder = "" - vaultProxyConfigPlaceholder = "" - vaultProxyPortPlaceholder = "" - vaultAppSvcSecretsPathPlaceholder = "" - templateAppSvcDestinationPlaceholder = "" - templateContentPlaceholder = "" - templateCommandPlaceholder = "" - templateTemplatesPlaceholder = "" - appSvcSecretsVolMountPathPlaceholder = "" - - vaultK8sAuthMethod = "kubernetes" // Default auth method used by Vault Agent - appSvcSecretsVolName = "secrets" // Name of the volume shared between containers to store secrets file(s) - templateAppSvcDefaultDestination = "secrets.properties" // Default secrets destination - vaultDefaultSecretsEnginePath = "secret" // Default path for Vault K/V Secrets Engine if no 'secrets-path' annotation - vaultProxyDefaultPort = "8200" // Default port to access local Vault proxy - - //--- Job handling - Temporary mechanism until KEP https://github.com/kubernetes/enhancements/blob/master/keps/sig-apps/sidecarcontainers.md is implemented (and we migrate on appropriate version of k8s) - jobMonitoringContainerName = "tvsi-job-babysitter" // Name of our specific sidecar container to inject in submitted jobs - appJobContainerNamePlaceholder = "" // Name of the app job's container - appJobVarPlaceholder = "" // Special script var set to "true" if submitted workload is a k8s job - - //--- JSON Patch operations - jsonPatchOpAdd = "add" - jsonPatchOpReplace = "replace" +const ( + vaultK8sAuthMethod = "kubernetes" // Default auth method used by Vault Agent +) - //--- JSON Path - jsonPathAnnotations = "/metadata/annotations" - jsonPathSecurityCtx = "/spec/securityContext" - jsonPathInitContainers = "/spec/initContainers" - jsonPathContainers = "/spec/containers" - jsonPathVolumes = "/spec/volumes" +const ( + //--- Vault Agent env vars + vaultRoleEnv = "VSI_VAULT_ROLE" + vaultAuthMethodEnv = "VSI_VAULT_AUTH_METHOD" ) diff --git a/pkg/webhook/modes.go b/pkg/webhook/modes.go new file mode 100644 index 0000000..f40781f --- /dev/null +++ b/pkg/webhook/modes.go @@ -0,0 +1,21 @@ +// Copyright © 2019-2020 Talend - www.talend.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webhook + +import ( + _ "talend/vault-sidecar-injector/pkg/mode/job" // blank import to init job mode (registration) + _ "talend/vault-sidecar-injector/pkg/mode/proxy" // blank import to init proxy mode (registration) + _ "talend/vault-sidecar-injector/pkg/mode/secrets" // blank import to init secrets mode (registration) +) diff --git a/pkg/webhook/secrets.go b/pkg/webhook/secrets.go deleted file mode 100644 index 6c222c5..0000000 --- a/pkg/webhook/secrets.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright © 2019 Talend -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package webhook - -import ( - "errors" - "fmt" - "strings" - - "k8s.io/klog" -) - -// Vault Sidecar Injector: Secrets Mode -func (vaultInjector *VaultInjector) secretsMode(labels, annotations map[string]string) (string, error) { - secretsPath := strings.Split(annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationSecretsPathKey]], ",") - secretsTemplate := strings.Split(annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationSecretsTemplateKey]], "---") - templateDest := strings.Split(annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationTemplateDestKey]], ",") - templateCmd := strings.Split(annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationTemplateCmdKey]], ",") - - secretsPathNum := len(secretsPath) - secretsTemplateNum := len(secretsTemplate) - templateDestNum := len(templateDest) - - if secretsPathNum == 1 && secretsPath[0] == "" { // Build default secrets path: "secret//" - applicationLabel := labels[vaultInjector.ApplicationLabelKey] - applicationServiceLabel := labels[vaultInjector.ApplicationServiceLabelKey] - - if applicationLabel == "" || applicationServiceLabel == "" { - klog.Errorf("Submitted pod must contain labels %s and %s", vaultInjector.ApplicationLabelKey, vaultInjector.ApplicationServiceLabelKey) - return "", fmt.Errorf("Submitted pod must contain labels %s and %s", vaultInjector.ApplicationLabelKey, vaultInjector.ApplicationServiceLabelKey) - } - - secretsPath[0] = vaultDefaultSecretsEnginePath + "/" + applicationLabel + "/" + applicationServiceLabel - } - - if templateDestNum == 1 && templateDest[0] == "" { // Use default - templateDest[0] = templateAppSvcDefaultDestination - } - - if secretsTemplateNum == 1 && secretsTemplate[0] == "" { - // We must have same numbers of secrets path & secrets destinations - if templateDestNum != secretsPathNum { - klog.Error("Submitted pod must contain same numbers of secrets path and secrets destinations") - return "", errors.New("Submitted pod must contain same numbers of secrets path and secrets destinations") - } - - // If no custom template(s), use default template - secretsTemplate = make([]string, templateDestNum) - for tmplIdx := 0; tmplIdx < templateDestNum; tmplIdx++ { - secretsTemplate[tmplIdx] = vaultInjector.TemplateDefaultTmpl - } - } else { - // We must have same numbers of custom templates & secrets destinations ... - if templateDestNum != secretsTemplateNum { - klog.Error("Submitted pod must contain same numbers of templates and secrets destinations") - return "", errors.New("Submitted pod must contain same numbers of templates and secrets destinations") - } - - // ... and we ignore content of 'secrets-path' annotation ('cause we provide full template), but we need to init an empty array - // to not end up with errors in the replace loop to come - secretsPath = make([]string, templateDestNum) - } - - // Copy provided template commands, if less commands than secrets destinations: remaining commands set to "" - templateCommands := make([]string, templateDestNum) - copy(templateCommands, templateCmd) - - var templateBlock string - var templates strings.Builder - - for tmplIdx := 0; tmplIdx < templateDestNum; tmplIdx++ { - templateBlock = vaultInjector.TemplateBlock - templateBlock = strings.Replace(templateBlock, templateAppSvcDestinationPlaceholder, templateDest[tmplIdx], -1) - templateBlock = strings.Replace(templateBlock, templateContentPlaceholder, secretsTemplate[tmplIdx], -1) - templateBlock = strings.Replace(templateBlock, vaultAppSvcSecretsPathPlaceholder, secretsPath[tmplIdx], -1) - templateBlock = strings.Replace(templateBlock, templateCommandPlaceholder, templateCommands[tmplIdx], -1) - - templates.WriteString(templateBlock) - templates.WriteString("\n") - } - - return templates.String(), nil -} diff --git a/pkg/webhook/types.go b/pkg/webhook/types.go index 315f478..dfe7d31 100644 --- a/pkg/webhook/types.go +++ b/pkg/webhook/types.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,50 +16,22 @@ package webhook import ( "net/http" - "talend/vault-sidecar-injector/pkg/config" + cfg "talend/vault-sidecar-injector/pkg/config" ) -var vaultInjectorAnnotationKeys = [...]string{ +// VaultInjector : Webhook Server entity +type VaultInjector struct { + *cfg.VSIConfig + Server *http.Server +} + +// Supported annotations (modes' annotations will be appended to this array) +var vaultInjectorAnnotationKeys = []string{ vaultInjectorAnnotationInjectKey, vaultInjectorAnnotationAuthMethodKey, vaultInjectorAnnotationModeKey, - vaultInjectorAnnotationProxyPortKey, vaultInjectorAnnotationRoleKey, vaultInjectorAnnotationSATokenKey, - vaultInjectorAnnotationSecretsPathKey, - vaultInjectorAnnotationSecretsTemplateKey, - vaultInjectorAnnotationTemplateDestKey, - vaultInjectorAnnotationLifecycleHookKey, - vaultInjectorAnnotationTemplateCmdKey, vaultInjectorAnnotationWorkloadKey, vaultInjectorAnnotationStatusKey, } - -var vaultInjectorModes = [...]string{ - vaultInjectorModeSecrets, - vaultInjectorModeProxy, -} - -// VaultInjector : Webhook Server entity -type VaultInjector struct { - *config.InjectionConfig - Server *http.Server -} - -// Struct to carry computed placeholders' values and context info for current injection -type sidecarContext struct { - modes map[string]bool - k8sDefaultSATokenVolumeName string - vaultInjectorSATokenVolumeName string - vaultAuthMethod string - vaultRole string - proxy string - templates string -} - -// This struct represents a JSON Patch operation (see http://jsonpatch.com/) -type patchOperation struct { - Op string `json:"op"` - Path string `json:"path"` - Value interface{} `json:"value,omitempty"` -} diff --git a/pkg/webhook/update-pod.go b/pkg/webhook/update-pod.go index 2d7da42..b7cedad 100644 --- a/pkg/webhook/update-pod.go +++ b/pkg/webhook/update-pod.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,59 +20,66 @@ import ( "strconv" "strings" + ctx "talend/vault-sidecar-injector/pkg/context" + m "talend/vault-sidecar-injector/pkg/mode" + corev1 "k8s.io/api/core/v1" "k8s.io/klog" ) -func (vaultInjector *VaultInjector) updatePodSpec(pod *corev1.Pod) (patch []patchOperation, err error) { - // Add Security Context (if provided) - if vaultInjector.SidecarConfig.SecurityContext != nil { - var op string +func (vaultInjector *VaultInjector) updatePodSpec(pod *corev1.Pod) (patch []ctx.PatchOperation, err error) { + var context *ctx.InjectionContext + var patchPod, patchInitContainers, patchContainers []ctx.PatchOperation - if pod.Spec.SecurityContext == nil { - op = jsonPatchOpAdd - } else { - op = jsonPatchOpReplace + // We expect at least one container in submitted pod + if len(pod.Spec.Containers) == 0 { + err = errors.New("Submitted pod must contain at least one container") + klog.Error(err.Error()) + return + } + + // 1) Extract labels and annotations to compute values for placeholders in injection configuration + if context, err = vaultInjector.computeContext(pod.Spec.Containers, pod.Labels, pod.Annotations); err == nil { + if klog.V(5) { // enabled by providing '-v=5' at least + klog.Infof("context=%+v", context) } - patch = append(patch, patchOperation{Op: op, Path: jsonPathSecurityCtx, Value: *vaultInjector.SidecarConfig.SecurityContext}) - } + // 2) Patch submitted pod + if patchPod, err = vaultInjector.patchPod(pod.Spec, pod.Annotations, context); err == nil { + patch = append(patch, patchPod...) - // Extract labels and annotations to compute values for placeholders in sidecars' configuration - context, err := vaultInjector.computeContext(pod.Spec.Containers, pod.Labels, pod.Annotations) - if err == nil { - // Add lifecycle hooks to requesting pod's container(s) if needed - patchHooks, err := vaultInjector.addLifecycleHooks(pod.Spec.Containers, pod.Annotations, *context) - if err == nil { - patch = append(patch, patchHooks...) - - // Add sidecars' initcontainers - patchInitContainers, err := vaultInjector.addContainer(pod.Spec.InitContainers, pod.Annotations, jsonPathInitContainers, *context) - if err == nil { + // 3) Add init container(s) to submitted pod + if patchInitContainers, err = vaultInjector.addContainer(pod.Spec.InitContainers, ctx.JsonPathInitContainers, context); err == nil { patch = append(patch, patchInitContainers...) - // Add sidecars' containers - patchContainers, err := vaultInjector.addContainer(pod.Spec.Containers, pod.Annotations, jsonPathContainers, *context) - if err == nil { + // 4) Add sidecar(s) to submitted pod + if patchContainers, err = vaultInjector.addContainer(pod.Spec.Containers, ctx.JsonPathContainers, context); err == nil { patch = append(patch, patchContainers...) - // Add volume(s) - patch = append(patch, vaultInjector.addVolume(pod.Spec.Volumes, jsonPathVolumes)...) - return patch, nil + // 5) Add volume(s) + patch = append(patch, vaultInjector.addVolume(pod.Spec.Volumes, ctx.JsonPathVolumes)...) } } } } - return nil, err + return } -func (vaultInjector *VaultInjector) computeContext(podContainers []corev1.Container, labels, annotations map[string]string) (*sidecarContext, error) { - var k8sSaSecretsVolName, vaultInjectorSaSecretsVolName, proxyConfig, secretsTemplates string +func (vaultInjector *VaultInjector) computeContext(podContainers []corev1.Container, labels, annotations map[string]string) (*ctx.InjectionContext, error) { + var k8sSaSecretsVolName, vaultInjectorSaSecretsVolName string + + // Get status for Vault Sidecar Injector modes + modesStatus := make(map[string]bool, len(m.VaultInjectorModes)) + m.GetModesStatus(strings.Split(annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationModeKey]], ","), modesStatus) - // Get enabled Vault Sidecar Injector modes - modes := make(map[string]bool, len(vaultInjectorModes)) - getModes(strings.Split(annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationModeKey]], ","), modes) + // !!! This annotation is deprecated !!! Enable job mode if used + if annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationWorkloadKey]] == vaultInjectorWorkloadJob { + klog.Warningf("Annotation '%s' is deprecated but still supported. Use '%s' instead", vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationWorkloadKey], vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationModeKey]) + modesStatus[vaultInjectorWorkloadJob] = true + } + + klog.Infof("Modes status: %+v", modesStatus) vaultAuthMethod := annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationAuthMethodKey]] vaultRole := annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationRoleKey]] @@ -82,13 +89,14 @@ func (vaultInjector *VaultInjector) computeContext(podContainers []corev1.Contai vaultAuthMethod = vaultK8sAuthMethod } - if vaultRole == "" && vaultAuthMethod == vaultK8sAuthMethod { // If role annotation not provided and "kubernetes" Vault Auth + if (vaultRole == "") && (vaultAuthMethod == vaultK8sAuthMethod) { // If role annotation not provided and "kubernetes" Vault Auth // Look after application label to set role vaultRole = labels[vaultInjector.ApplicationLabelKey] if vaultRole == "" { - klog.Errorf("Submitted pod must contain label %s", vaultInjector.ApplicationLabelKey) - return nil, fmt.Errorf("Submitted pod must contain label %s", vaultInjector.ApplicationLabelKey) + err := fmt.Errorf("Submitted pod must contain label %s", vaultInjector.ApplicationLabelKey) + klog.Error(err.Error()) + return nil, err } } @@ -110,154 +118,111 @@ func (vaultInjector *VaultInjector) computeContext(podContainers []corev1.Contai } } - // Proxy mode - if modes[vaultInjectorModeProxy] { - proxyConfig, err = vaultInjector.proxyMode(annotations) - if err != nil { - return nil, err - } - } + // Loop through enabled modes and call associated compute functions to compute configs + modesConfig := make(map[string]ctx.ModeConfig, len(m.VaultInjectorModes)) - // Secrets mode - if modes[vaultInjectorModeSecrets] { - secretsTemplates, err = vaultInjector.secretsMode(labels, annotations) - if err != nil { - return nil, err + for mode, enabled := range modesStatus { + if enabled && m.VaultInjectorModes[mode].ComputeTemplatesFunc != nil { + modesConfig[mode], err = m.VaultInjectorModes[mode].ComputeTemplatesFunc(vaultInjector.VSIConfig, labels, annotations) + if err != nil { + return nil, err + } } } - return &sidecarContext{modes, k8sSaSecretsVolName, vaultInjectorSaSecretsVolName, vaultAuthMethod, vaultRole, proxyConfig, secretsTemplates}, nil + return &ctx.InjectionContext{ + K8sDefaultSATokenVolumeName: k8sSaSecretsVolName, + VaultInjectorSATokenVolumeName: vaultInjectorSaSecretsVolName, + VaultAuthMethod: vaultAuthMethod, + VaultRole: vaultRole, + ModesStatus: modesStatus, + ModesConfig: modesConfig}, nil } -func (vaultInjector *VaultInjector) addLifecycleHooks(podContainers []corev1.Container, annotations map[string]string, context sidecarContext) (patch []patchOperation, err error) { - if context.modes[vaultInjectorModeSecrets] { - switch strings.ToLower(annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationLifecycleHookKey]]) { - default: - return patch, nil - case "y", "yes", "true", "on": - if vaultInjector.PodslifecycleHooks.PostStart != nil { - // As we inject hooks, there should have existing containers so len(podContainers) shoud be > 0 - if len(podContainers) == 0 { - klog.Error("Submitted pod must contain at least one container") - return nil, errors.New("Submitted pod must contain at least one container") - } - - secretsVolMountPath, err := getMountPathOfSecretsVolume(podContainers) - if err != nil { - return nil, err - } - - if vaultInjector.PodslifecycleHooks.PostStart.Exec == nil { - klog.Error("Unsupported lifecycle hook. Only support Exec type") - return nil, errors.New("Unsupported lifecycle hook. Only support Exec type") - } - - // We will modify some values here so make a copy to not change origin - hookCommand := make([]string, len(vaultInjector.PodslifecycleHooks.PostStart.Exec.Command)) - copy(hookCommand, vaultInjector.PodslifecycleHooks.PostStart.Exec.Command) - - for commandIdx := range hookCommand { - hookCommand[commandIdx] = strings.Replace(hookCommand[commandIdx], appSvcSecretsVolMountPathPlaceholder, secretsVolMountPath, -1) - } - - postStartHook := &corev1.Handler{Exec: &corev1.ExecAction{Command: hookCommand}} - - // Add hooks to container(s) of requesting pod - for podCntIdx, podCnt := range podContainers { - if podCnt.Lifecycle != nil { - if podCnt.Lifecycle.PostStart != nil { - klog.Warningf("Replacing existing postStart hook for container %s", podCnt.Name) - } - - podCnt.Lifecycle.PostStart = postStartHook - } else { - podCnt.Lifecycle = &corev1.Lifecycle{ - PostStart: postStartHook, - } - } - - // Here we have to use 'replace' JSON Patch operation - patch = append(patch, patchOperation{ - Op: jsonPatchOpReplace, - Path: jsonPathContainers + "/" + strconv.Itoa(podCntIdx), - Value: podCnt, - }) - } +func (vaultInjector *VaultInjector) patchPod(podSpec corev1.PodSpec, annotations map[string]string, context *ctx.InjectionContext) (patch []ctx.PatchOperation, err error) { + for mode, enabled := range context.ModesStatus { + if enabled && m.VaultInjectorModes[mode].PatchPodFunc != nil { + patchPod, err := m.VaultInjectorModes[mode].PatchPodFunc(vaultInjector.VSIConfig, podSpec, annotations, context) + if err != nil { + return nil, err } - return patch, nil + patch = append(patch, patchPod...) } - } else { - return patch, nil } + + return patch, nil } // Deal with both InitContainers & Containers -func (vaultInjector *VaultInjector) addContainer(podContainers []corev1.Container, annotations map[string]string, basePath string, context sidecarContext) (patch []patchOperation, err error) { - var first bool +func (vaultInjector *VaultInjector) addContainer(podContainers []corev1.Container, basePath string, context *ctx.InjectionContext) (patch []ctx.PatchOperation, err error) { var value interface{} - var sidecarCfgContainers []corev1.Container - - initContainer := (basePath == jsonPathInitContainers) - jobWorkload := (annotations[vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationWorkloadKey]] == vaultInjectorAnnotationWorkloadJobValue) - // As we inject containers to a pod, there should have existing containers so len(podContainers) shoud be > 0 - if !initContainer && len(podContainers) == 0 { - klog.Error("Submitted pod must contain at least one container") - return nil, errors.New("Submitted pod must contain at least one container") - } + first := false + injectionCfgContainers := vaultInjector.InjectionConfig.Containers + initContainer := (basePath == ctx.JsonPathInitContainers) if initContainer { - // there may be no initContainers in the requesting pod - first = len(podContainers) == 0 - sidecarCfgContainers = vaultInjector.SidecarConfig.InitContainers - } else { - first = false - sidecarCfgContainers = vaultInjector.SidecarConfig.Containers - - // If workload is a job we expect only one container (good assumption given current limitations using job with sidecars) - // Limitation to remove when KEP https://github.com/kubernetes/enhancements/blob/master/keps/sig-apps/sidecarcontainers.md is implemented and supported on our clusters - if jobWorkload && len(podContainers) > 1 { - klog.Error("Submitted pod contains more than one container: not supported for job workload") - return nil, errors.New("Submitted pod contains more than one container: not supported for job workload") - } + // there may be no init container in the requesting pod + first = (len(podContainers) == 0) + injectionCfgContainers = vaultInjector.InjectionConfig.InitContainers } // Add our injected containers/initContainers to the submitted pod - sidecarCntIdx := 0 - for _, sidecarCnt := range sidecarCfgContainers { - if !jobWorkload && sidecarCnt.Name == jobMonitoringContainerName { - // Workload is not a job so do not inject our job specific sidecar + injectionCntIdx := 0 + for _, injectionCnt := range injectionCfgContainers { + container := injectionCnt + + // We will modify env vars so make a copy to not change origin + container.Env = make([]corev1.EnvVar, len(injectionCnt.Env)) + copy(container.Env, injectionCnt.Env) + + // Iterate over enabled mode(s) to check if we inject this container and resolve env vars if needed + inject := false + for mode, enabled := range context.ModesStatus { + if enabled { + modeInject, err := m.VaultInjectorModes[mode].InjectContainerFunc(basePath, podContainers, container.Name, container.Env, context) + if err != nil { + return nil, err + } + + if modeInject { + inject = true + } + } + } + + // If no enabled mode(s) want this container to be injected: skip it + if !inject { continue } - container := sidecarCnt - // We will modify some values here so make a copy to not change origin - container.Command = make([]string, len(sidecarCnt.Command)) - copy(container.Command, sidecarCnt.Command) + // Set Vault role and Auth Method env vars + for envIdx := range container.Env { + if container.Env[envIdx].Name == vaultRoleEnv { + container.Env[envIdx].Value = context.VaultRole + } - for commandIdx := range container.Command { - if !initContainer && jobWorkload { - container.Command[commandIdx] = strings.Replace(container.Command[commandIdx], appJobContainerNamePlaceholder, podContainers[0].Name, -1) + if container.Env[envIdx].Name == vaultAuthMethodEnv { + container.Env[envIdx].Value = context.VaultAuthMethod } - container.Command[commandIdx] = strings.Replace(container.Command[commandIdx], appJobVarPlaceholder, strconv.FormatBool(jobWorkload), -1) - container.Command[commandIdx] = strings.Replace(container.Command[commandIdx], vaultAuthMethodPlaceholder, context.vaultAuthMethod, -1) - container.Command[commandIdx] = strings.Replace(container.Command[commandIdx], vaultRolePlaceholder, context.vaultRole, -1) - container.Command[commandIdx] = strings.Replace(container.Command[commandIdx], vaultProxyConfigPlaceholder, context.proxy, -1) - container.Command[commandIdx] = strings.Replace(container.Command[commandIdx], templateTemplatesPlaceholder, context.templates, -1) + } + + if klog.V(5) { // enabled by providing '-v=5' at least + klog.Infof("Env vars: %+v", container.Env) } // We will modify some values here so make a copy to not change origin - container.VolumeMounts = make([]corev1.VolumeMount, len(sidecarCnt.VolumeMounts)) - copy(container.VolumeMounts, sidecarCnt.VolumeMounts) + container.VolumeMounts = make([]corev1.VolumeMount, len(injectionCnt.VolumeMounts)) + copy(container.VolumeMounts, injectionCnt.VolumeMounts) // Loop to set proper volume names (extracted values from submitted pod) for volMountIdx := range container.VolumeMounts { switch container.VolumeMounts[volMountIdx].MountPath { case k8sDefaultSATokenVolMountPath: - container.VolumeMounts[volMountIdx].Name = context.k8sDefaultSATokenVolumeName + container.VolumeMounts[volMountIdx].Name = context.K8sDefaultSATokenVolumeName case vaultInjectorSATokenVolMountPath: - container.VolumeMounts[volMountIdx].Name = context.vaultInjectorSATokenVolumeName + container.VolumeMounts[volMountIdx].Name = context.VaultInjectorSATokenVolumeName } } @@ -274,42 +239,44 @@ func (vaultInjector *VaultInjector) addContainer(podContainers []corev1.Containe first = false value = []corev1.Container{container} } else { - // JSON Patch: use '/' to add our container/initContainer at the beginning of the array - path = path + "/" + strconv.Itoa(sidecarCntIdx) + // JSON Patch: use '/' to add our container/initContainer at the beginning of the array + path = path + "/" + strconv.Itoa(injectionCntIdx) } - patch = append(patch, patchOperation{ - Op: jsonPatchOpAdd, + patch = append(patch, ctx.PatchOperation{ + Op: ctx.JsonPatchOpAdd, Path: path, Value: value, }) - sidecarCntIdx++ + injectionCntIdx++ } return patch, nil } -func (vaultInjector *VaultInjector) addVolume(podVolumes []corev1.Volume, basePath string) (patch []patchOperation) { +func (vaultInjector *VaultInjector) addVolume(podVolumes []corev1.Volume, basePath string) (patch []ctx.PatchOperation) { first := len(podVolumes) == 0 - isSecretsVolumeInPod := false var value interface{} - for _, sidecarVol := range vaultInjector.SidecarConfig.Volumes { + for _, sidecarVol := range vaultInjector.InjectionConfig.Volumes { // Do not inject the 'secrets' volume we define in our injector config if the pod we mutate already has a definition for such volume - if sidecarVol.Name == appSvcSecretsVolName && len(podVolumes) > 0 { + isSecretsVolumeInPod := false + if sidecarVol.Name == secretsVolName && len(podVolumes) > 0 { for _, podVol := range podVolumes { - if podVol.Name == appSvcSecretsVolName { + if podVol.Name == secretsVolName { isSecretsVolumeInPod = true break } } if isSecretsVolumeInPod { // Volume 'secrets' exists in pod so do not add ours - klog.Infof("Found existing '%s' volume in requesting pod: skip injector volume definition", appSvcSecretsVolName) + klog.Infof("Found existing '%s' volume in submitted pod: skip injector volume definition", secretsVolName) continue } + + klog.Infof("Injecting volume '%s' in submitted pod", secretsVolName) } value = sidecarVol @@ -323,8 +290,8 @@ func (vaultInjector *VaultInjector) addVolume(podVolumes []corev1.Volume, basePa path = path + "/-" } - patch = append(patch, patchOperation{ - Op: jsonPatchOpAdd, + patch = append(patch, ctx.PatchOperation{ + Op: ctx.JsonPatchOpAdd, Path: path, Value: value, }) diff --git a/pkg/webhook/utils.go b/pkg/webhook/utils.go index e6876c5..4290c25 100644 --- a/pkg/webhook/utils.go +++ b/pkg/webhook/utils.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import ( "fmt" "strings" + ctx "talend/vault-sidecar-injector/pkg/context" + "k8s.io/api/admission/v1beta1" admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" corev1 "k8s.io/api/core/v1" @@ -71,7 +73,7 @@ func mutationRequired(ignoredList []string, vaultInjectorAnnotations map[string] // determine whether to perform mutation based on annotation for the target resource var required bool - if strings.ToLower(status) == vaultInjectorAnnotationStatusValue { + if strings.ToLower(status) == vaultInjectorStatusInjected { required = false } else { switch strings.ToLower(annotations[vaultInjectorAnnotations[vaultInjectorAnnotationInjectKey]]) { @@ -100,78 +102,29 @@ Loop: } if k8sSaSecretsVolName == "" { - klog.Errorf("Volume Mount for path %s not found in submitted pod", saTokenPath) - return "", fmt.Errorf("Volume Mount for path %s not found in submitted pod", saTokenPath) + err := fmt.Errorf("Volume Mount for path %s not found in submitted pod", saTokenPath) + klog.Error(err.Error()) + return "", err } return k8sSaSecretsVolName, nil } -func getMountPathOfSecretsVolume(cnts []corev1.Container) (string, error) { - var secretsVolMountPath string - -Loop: - for _, sourceContainer := range cnts { - for _, volMount := range sourceContainer.VolumeMounts { - if volMount.Name == appSvcSecretsVolName { - secretsVolMountPath = volMount.MountPath - break Loop - } - } - } - - if secretsVolMountPath == "" { - klog.Errorf("Volume Mount %s not found in submitted pod", appSvcSecretsVolName) - return "", fmt.Errorf("Volume Mount %s not found in submitted pod", appSvcSecretsVolName) - } - - return secretsVolMountPath, nil -} - -func getModes(requestedModes []string, modes map[string]bool) { - // Init modes for current injection context - for _, mode := range vaultInjectorModes { - modes[mode] = false - } - - requestedModesNum := len(requestedModes) - - if requestedModesNum > 0 { - if requestedModesNum == 1 && requestedModes[0] == "" { // If no mode(s) provided then only enable "secrets" mode - modes[vaultInjectorModeSecrets] = true - } else { - // Look at requested modes, ignore and remove unsupported values - for _, requestedMode := range requestedModes { - switch requestedMode { - case vaultInjectorModeSecrets, vaultInjectorModeProxy: - modes[requestedMode] = true - default: - klog.Warningf("Ignore unsupported requested Vault Sidecar Injector mode: %s", requestedMode) - } - } - } - } else { // If no mode(s) provided then only enable "secrets" mode - modes[vaultInjectorModeSecrets] = true - } - - klog.Infof("Modes: %+v", modes) -} - -func updateAnnotation(target map[string]string, added map[string]string) (patch []patchOperation) { +func updateAnnotation(target map[string]string, added map[string]string) (patch []ctx.PatchOperation) { for key, value := range added { if target == nil || target[key] == "" { target = map[string]string{} - patch = append(patch, patchOperation{ - Op: jsonPatchOpAdd, - Path: jsonPathAnnotations, + patch = append(patch, ctx.PatchOperation{ + Op: ctx.JsonPatchOpAdd, + Path: ctx.JsonPathAnnotations, Value: map[string]string{ key: value, }, }) } else { - patch = append(patch, patchOperation{ - Op: jsonPatchOpReplace, - Path: jsonPathAnnotations + "/" + key, + patch = append(patch, ctx.PatchOperation{ + Op: ctx.JsonPatchOpReplace, + Path: ctx.JsonPathAnnotations + "/" + key, Value: value, }) } diff --git a/pkg/webhook/webhook-server.go b/pkg/webhook/webhook-server.go index ca00f57..71adb43 100644 --- a/pkg/webhook/webhook-server.go +++ b/pkg/webhook/webhook-server.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,9 @@ import ( "fmt" "io/ioutil" "net/http" - "talend/vault-sidecar-injector/pkg/config" + cfg "talend/vault-sidecar-injector/pkg/config" + ctx "talend/vault-sidecar-injector/pkg/context" + m "talend/vault-sidecar-injector/pkg/mode" "k8s.io/api/admission/v1beta1" corev1 "k8s.io/api/core/v1" @@ -41,24 +43,29 @@ var ignoredNamespaces = []string{ } // New : init new VaultInjector type -func New(cfg *config.InjectionConfig, server *http.Server) *VaultInjector { +func New(config *cfg.VSIConfig, server *http.Server) *VaultInjector { + // Add mode annotations + for _, mode := range m.VaultInjectorModes { + vaultInjectorAnnotationKeys = append(vaultInjectorAnnotationKeys, mode.Annotations...) + } + // Compute FQ annotations - cfg.VaultInjectorAnnotationsFQ = make(map[string]string, len(vaultInjectorAnnotationKeys)) + config.VaultInjectorAnnotationsFQ = make(map[string]string, len(vaultInjectorAnnotationKeys)) for _, vaultAnnotationKey := range vaultInjectorAnnotationKeys { - if cfg.VaultInjectorAnnotationKeyPrefix != "" { - cfg.VaultInjectorAnnotationsFQ[vaultAnnotationKey] = cfg.VaultInjectorAnnotationKeyPrefix + "/" + vaultAnnotationKey + if config.VaultInjectorAnnotationKeyPrefix != "" { + config.VaultInjectorAnnotationsFQ[vaultAnnotationKey] = config.VaultInjectorAnnotationKeyPrefix + "/" + vaultAnnotationKey } else { - cfg.VaultInjectorAnnotationsFQ[vaultAnnotationKey] = vaultAnnotationKey + config.VaultInjectorAnnotationsFQ[vaultAnnotationKey] = vaultAnnotationKey } } return &VaultInjector{ - InjectionConfig: cfg, - Server: server, + VSIConfig: config, + Server: server, } } -// create mutation patch for resoures +// Create mutation patch for resoures func (vaultInjector *VaultInjector) createPatch(pod *corev1.Pod, annotations map[string]string) ([]byte, error) { patchPodSpec, err := vaultInjector.updatePodSpec(pod) @@ -66,7 +73,7 @@ func (vaultInjector *VaultInjector) createPatch(pod *corev1.Pod, annotations map return nil, err } - var patch []patchOperation + var patch []ctx.PatchOperation patch = append(patch, patchPodSpec...) patch = append(patch, updateAnnotation(pod.Annotations, annotations)...) @@ -74,7 +81,7 @@ func (vaultInjector *VaultInjector) createPatch(pod *corev1.Pod, annotations map return json.Marshal(patch) } -// main mutation process +// Main mutation process func (vaultInjector *VaultInjector) mutate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { req := ar.Request var pod corev1.Pod @@ -113,7 +120,7 @@ func (vaultInjector *VaultInjector) mutate(ar *v1beta1.AdmissionReview) *v1beta1 klog.Infof("AdmissionReview for GroupVersionKind=%+v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%+v", req.Kind, req.Namespace, req.Name, podName, req.UID, req.Operation, req.UserInfo) - // determine whether to perform mutation + // Determine whether to perform mutation if !mutationRequired(ignoredNamespaces, vaultInjector.VaultInjectorAnnotationsFQ, &pod.ObjectMeta) { klog.Infof("Skipping mutation for %s/%s due to policy check", podNamespace, podName) return &v1beta1.AdmissionResponse{ @@ -121,10 +128,11 @@ func (vaultInjector *VaultInjector) mutate(ar *v1beta1.AdmissionReview) *v1beta1 } } - annotations := map[string]string{vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationStatusKey]: vaultInjectorAnnotationStatusValue} + annotations := map[string]string{vaultInjector.VaultInjectorAnnotationsFQ[vaultInjectorAnnotationStatusKey]: vaultInjectorStatusInjected} patchBytes, err := vaultInjector.createPatch(&pod, annotations) if err != nil { return &v1beta1.AdmissionResponse{ + Allowed: false, Result: &metav1.Status{ Message: err.Error(), }, @@ -156,7 +164,7 @@ func (vaultInjector *VaultInjector) Serve(w http.ResponseWriter, r *http.Request return } - // verify the content type is accurate + // Verify the content type is accurate contentType := r.Header.Get("Content-Type") if contentType != "application/json" { klog.Errorf("Content-Type=%s, expect application/json", contentType) diff --git a/pkg/webhook/inline_test.go b/pkg/webhook/webhook-server_test.go similarity index 55% rename from pkg/webhook/inline_test.go rename to pkg/webhook/webhook-server_test.go index 7cbe569..1006440 100644 --- a/pkg/webhook/inline_test.go +++ b/pkg/webhook/webhook-server_test.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Talend +// Copyright © 2019-2020 Talend - www.talend.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,10 +17,13 @@ package webhook import ( "encoding/json" "errors" + "flag" "io/ioutil" + "os" "path/filepath" - "strings" - "talend/vault-sidecar-injector/pkg/config" + "strconv" + cfg "talend/vault-sidecar-injector/pkg/config" + ctx "talend/vault-sidecar-injector/pkg/context" "testing" "k8s.io/api/admission/v1beta1" @@ -42,41 +45,87 @@ type testResource struct { podTemplateSpec *corev1.PodTemplateSpec } -func TestInlineInjector(t *testing.T) { - injectionCfg, err := config.Load( - config.WhSvrParameters{ - 0, 0, "", "", - "sidecar.vault.talend.org", "com.talend.application", "com.talend.service", - "../../test/config/sidecarconfig.yaml", - "../../test/config/proxyconfig.hcl", - "../../test/config/tmplblock.hcl", - "../../test/config/tmpldefault.tmpl", - "../../test/config/podlifecyclehooks.yaml", - }, - ) +func TestWebhookServerOK(t *testing.T) { + // Create webhook instance + vaultInjector, err := createVaultInjector() + if err != nil { + t.Fatalf("Loading error \"%s\"", err) + } + + // Get all test workloads + workloads, err := filepath.Glob("../../test/workloads/ok/*.yaml") if err != nil { - t.Errorf("Loading error \"%s\"", err) + t.Fatalf("Fail listing files: %s", err) + } + + // Loop on all test workloads: mutate and display JSON Patch structure + for _, workloadManifest := range workloads { + klog.Info("\n\n ") + klog.Infof("Loading workload %s", workloadManifest) + + ar, err := (&testResource{ + manifest: workloadManifest, + workloadType: func(w string) string { + if bMatched, _ := filepath.Match("*-dep-*.yaml", filepath.Base(w)); bMatched { + return "deployment" + } else if bMatched, _ := filepath.Match("*-job-*.yaml", filepath.Base(w)); bMatched { + return "job" + } else { + return "" + } + }(workloadManifest), + }).load() + if err != nil { + t.Fatalf("Error creating AR \"%s\"", err) + } + + // Mutate pod + resp := vaultInjector.mutate(ar) + + assert.Condition(t, func() bool { + // Handle injection cases *and* also pod submitted without `inject: "true"` annotation + if (resp.Allowed && resp.Patch != nil && resp.Result == nil) || (resp.Allowed && resp.Patch == nil && resp.Result == nil) { + return true + } + + return false + }, "Inconsistent AdmissionResponse") + + if resp.Patch != nil { + var patch []ctx.PatchOperation + if err = yaml.Unmarshal(resp.Patch, &patch); err != nil { + t.Errorf("JSON Patch unmarshal error \"%s\"", err) + } + + klog.Infof("JSON Patch=%+v", patch) + } } +} +func TestWebhookServerKO(t *testing.T) { // Create webhook instance - vaultInjector := New(injectionCfg, nil) + vaultInjector, err := createVaultInjector() + if err != nil { + t.Fatalf("Loading error \"%s\"", err) + } // Get all test workloads - workloads, err := filepath.Glob("../../test/workloads/*.yaml") + workloads, err := filepath.Glob("../../test/workloads/ko/*.yaml") if err != nil { - t.Errorf("Fail listing files: %s", err) + t.Fatalf("Fail listing files: %s", err) } // Loop on all test workloads: mutate and display JSON Patch structure for _, workloadManifest := range workloads { + klog.Info("\n\n ") klog.Infof("Loading workload %s", workloadManifest) ar, err := (&testResource{ manifest: workloadManifest, workloadType: func(w string) string { - if strings.HasSuffix(w, "-deployment.yaml") { + if bMatched, _ := filepath.Match("*-dep-*.yaml", filepath.Base(w)); bMatched { return "deployment" - } else if strings.HasSuffix(w, "-job.yaml") { + } else if bMatched, _ := filepath.Match("*-job-*.yaml", filepath.Base(w)); bMatched { return "job" } else { return "" @@ -84,25 +133,53 @@ func TestInlineInjector(t *testing.T) { }(workloadManifest), }).load() if err != nil { - t.Errorf("Error creating AR \"%s\"", err) + t.Fatalf("Error creating AR \"%s\"", err) } // Mutate pod resp := vaultInjector.mutate(ar) - assert.Equal(t, true, resp.Allowed) - assert.Nil(t, resp.Result) - assert.NotNil(t, resp.Patch) + assert.Condition(t, func() bool { + // Handle error cases + if !resp.Allowed && resp.Patch == nil && resp.Result != nil { + return true + } - var patch []patchOperation - if err = yaml.Unmarshal(resp.Patch, &patch); err != nil { - t.Errorf("JSON Patch unmarshal error \"%s\"", err) - } + return false + }, "Inconsistent AdmissionResponse") - klog.Infof("JSON Patch=%+v", patch) + klog.Infof("Result=%+v", resp.Result) } } +func createVaultInjector() (*VaultInjector, error) { + verbose, _ := strconv.ParseBool(os.Getenv("VERBOSE")) + if verbose { + // Set Klog verbosity level to have detailed logs from our webhook (where we use level 5+ to log such info) + klogFlags := flag.NewFlagSet("klog", flag.ExitOnError) + klog.InitFlags(klogFlags) + klogFlags.Set("v", "5") + } + + vsiCfg, err := cfg.Load( + cfg.WhSvrParameters{ + 0, 0, "", "", + "sidecar.vault.talend.org", "com.talend.application", "com.talend.service", + "../../test/config/injectionconfig.yaml", + "../../test/config/proxyconfig.hcl", + "../../test/config/tmplblock.hcl", + "../../test/config/tmpldefault.tmpl", + "../../test/config/podlifecyclehooks.yaml", + }, + ) + if err != nil { + return nil, err + } + + // Create webhook instance + return New(vsiCfg, nil), nil +} + func (tr *testResource) load() (*v1beta1.AdmissionReview, error) { // TODO: Beware, there may be several resources. Only keep and mutate the ones with Vault Sidecar Injector's `sidecar.vault.talend.org/inject: "true"` annotation data, err := ioutil.ReadFile(tr.manifest) @@ -152,7 +229,10 @@ func (tr *testResource) addSATokenVolume() { }, } - tr.podTemplateSpec.Spec.Containers[0].VolumeMounts = append(tr.podTemplateSpec.Spec.Containers[0].VolumeMounts, saTokenVolumeMount) + if len(tr.podTemplateSpec.Spec.Containers) > 0 { + tr.podTemplateSpec.Spec.Containers[0].VolumeMounts = append(tr.podTemplateSpec.Spec.Containers[0].VolumeMounts, saTokenVolumeMount) + } + tr.podTemplateSpec.Spec.Volumes = append(tr.podTemplateSpec.Spec.Volumes, saTokenVolume) } diff --git a/test/workloads/test-app-1-deployment.yaml b/samples/app-dep-1-secrets.yaml similarity index 94% rename from test/workloads/test-app-1-deployment.yaml rename to samples/app-dep-1-secrets.yaml index e77157b..1306321 100644 --- a/test/workloads/test-app-1-deployment.yaml +++ b/samples/app-dep-1-secrets.yaml @@ -1,7 +1,7 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: test-app + name: app namespace: default spec: replicas: 1 @@ -19,7 +19,7 @@ spec: spec: serviceAccountName: default containers: - - name: test-app-container + - name: app-container image: busybox:1.28 command: - "sh" diff --git a/deploy/samples/test-app2-deployment.yaml b/samples/app-dep-2-secrets.yaml similarity index 94% rename from deploy/samples/test-app2-deployment.yaml rename to samples/app-dep-2-secrets.yaml index 748e58e..57933a6 100644 --- a/deploy/samples/test-app2-deployment.yaml +++ b/samples/app-dep-2-secrets.yaml @@ -1,7 +1,7 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: test-app2 + name: app2 namespace: default spec: replicas: 1 @@ -33,7 +33,7 @@ spec: spec: serviceAccountName: default containers: - - name: test-app2-container-1 + - name: app2-container-1 image: busybox:1.28 command: - "sh" @@ -43,7 +43,7 @@ spec: volumeMounts: - name: secrets mountPath: /opt/talend/secrets - - name: test-app2-container-2 + - name: app2-container-2 image: busybox:1.28 command: - "sh" diff --git a/test/workloads/test-app-3-deployment.yaml b/samples/app-dep-3-secrets.yaml similarity index 95% rename from test/workloads/test-app-3-deployment.yaml rename to samples/app-dep-3-secrets.yaml index c11bb91..38ed304 100644 --- a/test/workloads/test-app-3-deployment.yaml +++ b/samples/app-dep-3-secrets.yaml @@ -1,7 +1,7 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: test-app3 + name: app3 namespace: default spec: replicas: 1 @@ -22,7 +22,7 @@ spec: spec: serviceAccountName: default containers: - - name: test-app3-container + - name: app3-container image: busybox:1.28 command: - "sh" diff --git a/samples/app-dep-4-secrets_static.yaml b/samples/app-dep-4-secrets_static.yaml new file mode 100644 index 0000000..2103d81 --- /dev/null +++ b/samples/app-dep-4-secrets_static.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app4 + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/secrets-type: "static" # static secrets + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: app4-container + image: busybox:1.28 + command: + - "sh" + - "-c" + - | + set -e + echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)" + while true;do + sleep 5 + done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/samples/app-dep-5-secrets_static-proxy.yaml b/samples/app-dep-5-secrets_static-proxy.yaml new file mode 100644 index 0000000..40d167d --- /dev/null +++ b/samples/app-dep-5-secrets_static-proxy.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app5 + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/mode: "secrets,proxy" # Enable both 'secrets' and 'proxy' modes + sidecar.vault.talend.org/secrets-type: "static" # static secrets + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: app5-container + image: everpeace/curl-jq + command: + - "sh" + - "-c" + - | + set -e + echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)" + echo + echo "Now using Vault Agent as a proxy to leverage Encryption as a Service feature (will encrypt and decrypt our secrets here)" + echo "Advantage: you do not need to deal with any Vault tokens and you just have to send requests to the local Vault Agent sidecar (available at 127.0.0.1) that will then forward everything to Vault server." + echo + + isVaultReady=$(curl -s -X GET http://127.0.0.1:8200/v1/sys/health | jq --raw-output .initialized) + while [ "$isVaultReady" != "true" ];do + sleep 5 + isVaultReady=$(curl -s -X GET http://127.0.0.1:8200/v1/sys/health | jq --raw-output .initialized) + done + + plaintext=$(cat /opt/talend/secrets/secrets.properties | grep SECRET1) + echo "Data that is going to be ciphered and deciphered: $plaintext" + echo + b64Plaintext=$(echo "$plaintext" | base64) + + ciphertext=$(curl -s -X POST --data "{\"plaintext\": \"$b64Plaintext\"}" http://127.0.0.1:8200/v1/transit/encrypt/test-key | jq --raw-output .data.ciphertext) + echo "Ciphertext" + echo "==========" + echo "$ciphertext" + echo + + cleartext=$(curl -s -X POST --data "{\"ciphertext\": \"$ciphertext\"}" http://127.0.0.1:8200/v1/transit/decrypt/test-key | jq --raw-output .data.plaintext) + echo "Cleartext" + echo "==========" + echo "$cleartext" | base64 -d + echo + while true;do + sleep 5 + done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/samples/app-dep-6-secrets-proxy.yaml b/samples/app-dep-6-secrets-proxy.yaml new file mode 100644 index 0000000..7b5fd6d --- /dev/null +++ b/samples/app-dep-6-secrets-proxy.yaml @@ -0,0 +1,69 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app6 + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/mode: "secrets,proxy" # Enable both 'secrets' and 'proxy' modes + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: app6-container + image: everpeace/curl-jq + command: + - "sh" + - "-c" + - | + set -e + while true; do + echo "Wait for secrets file ..." + if [ -f "/opt/talend/secrets/secrets.properties" ]; then + echo "Secrets available" + break + fi + sleep 2 + done + while true;do + echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)" + echo + echo "Now using Vault Agent as a proxy to leverage Encryption as a Service feature (will encrypt and decrypt our secrets here)" + echo "Advantage: you do not need to deal with any Vault tokens and you just have to send requests to the local Vault Agent sidecar (available at 127.0.0.1) that will then forward everything to Vault server." + echo + + plaintext=$(cat /opt/talend/secrets/secrets.properties | grep SECRET1) + echo "Data that is going to be ciphered and deciphered: $plaintext" + echo + b64Plaintext=$(echo "$plaintext" | base64) + + ciphertext=$(curl -s -X POST --data "{\"plaintext\": \"$b64Plaintext\"}" http://127.0.0.1:8200/v1/transit/encrypt/test-key | jq --raw-output .data.ciphertext) + echo "Ciphertext" + echo "==========" + echo "$ciphertext" + echo + + cleartext=$(curl -s -X POST --data "{\"ciphertext\": \"$ciphertext\"}" http://127.0.0.1:8200/v1/transit/decrypt/test-key | jq --raw-output .data.plaintext) + echo "Cleartext" + echo "==========" + echo "$cleartext" | base64 -d + echo + sleep 5 + done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/samples/app-job-1-secrets.yaml b/samples/app-job-1-secrets.yaml new file mode 100644 index 0000000..a719180 --- /dev/null +++ b/samples/app-job-1-secrets.yaml @@ -0,0 +1,88 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: job-sa + namespace: default +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: job-pod-status +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: job-pod-status +subjects: + - kind: ServiceAccount + name: job-sa +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: job-pod-status +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: app-job + namespace: default +spec: + backoffLimit: 1 + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/mode: "job" # Enable 'job' mode (will also enable 'secrets' mode) + # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. + # Annotation below is deprecated, use 'sidecar.vault.talend.org/mode' instead. + #sidecar.vault.talend.org/workload: "job" + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + restartPolicy: Never + # custom serviceAccountName with role allowing to perform GET on pods (needed to poll for job's pod status) + serviceAccountName: job-sa + containers: + - name: app-job-container + image: busybox:1.28 + command: + - "sh" + - "-c" + - | + set -e + while true; do + echo "Wait for secrets file before running job..." + if [ -f "/opt/talend/secrets/secrets.properties" ]; then + echo "Secrets available" + break + fi + sleep 2 + done + echo "Job started" + echo "I am a job... still working - 1" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 2" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 3" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 4" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 5" + cat /opt/talend/secrets/secrets.properties + echo "Job stopped" + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/deploy/samples/test-app-job-proxy.yaml b/samples/app-job-2-proxy.yaml similarity index 79% rename from deploy/samples/test-app-job-proxy.yaml rename to samples/app-job-2-proxy.yaml index 40f78f1..0cdf099 100644 --- a/deploy/samples/test-app-job-proxy.yaml +++ b/samples/app-job-2-proxy.yaml @@ -28,7 +28,7 @@ roleRef: apiVersion: batch/v1 kind: Job metadata: - name: test-app-job-proxy + name: app2-job namespace: default spec: backoffLimit: 1 @@ -36,9 +36,10 @@ spec: metadata: annotations: sidecar.vault.talend.org/inject: "true" - sidecar.vault.talend.org/mode: "proxy" # Enable only 'proxy' mode - # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. Need annotation below. - sidecar.vault.talend.org/workload: "job" + sidecar.vault.talend.org/mode: "proxy,job" # Enable only 'proxy' and 'job' modes + # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. + # Annotation below is deprecated, use 'sidecar.vault.talend.org/mode' instead. + #sidecar.vault.talend.org/workload: "job" labels: com.talend.application: test spec: @@ -46,20 +47,24 @@ spec: # custom serviceAccountName with role allowing to perform GET on pods (needed to poll for job's pod status) serviceAccountName: job-sa containers: - - name: test-app-job-proxy-container + - name: app2-job-container image: everpeace/curl-jq command: - "sh" - "-c" - | set -e - echo "Wait a while to make sure Vault Agent is started and authenticated..." - sleep 10 echo "Job started" echo "Now using Vault Agent as a proxy to leverage Encryption as a Service feature" echo "Advantage: you do not need to deal with any Vault tokens and you just have to send requests to the local Vault Agent sidecar (available at 127.0.0.1) that will then forward everything to Vault server." echo + isVaultReady=$(curl -s -X GET http://127.0.0.1:8200/v1/sys/health | jq --raw-output .initialized) + while [ "$isVaultReady" != "true" ];do + sleep 5 + isVaultReady=$(curl -s -X GET http://127.0.0.1:8200/v1/sys/health | jq --raw-output .initialized) + done + plaintext="Secret data to protect" echo "Data that is going to be ciphered and deciphered: $plaintext" echo diff --git a/deploy/samples/test-app-job-secrets-proxy.yaml b/samples/app-job-3-secrets-proxy.yaml similarity index 90% rename from deploy/samples/test-app-job-secrets-proxy.yaml rename to samples/app-job-3-secrets-proxy.yaml index db27e84..e3efc72 100644 --- a/deploy/samples/test-app-job-secrets-proxy.yaml +++ b/samples/app-job-3-secrets-proxy.yaml @@ -28,7 +28,7 @@ roleRef: apiVersion: batch/v1 kind: Job metadata: - name: test-app-job-secrets-proxy + name: app3-job namespace: default spec: backoffLimit: 1 @@ -36,10 +36,11 @@ spec: metadata: annotations: sidecar.vault.talend.org/inject: "true" - sidecar.vault.talend.org/mode: "secrets,proxy" # Enable both 'secrets' and 'proxy' modes + sidecar.vault.talend.org/mode: "secrets,proxy,job" # Enable 'secrets', 'proxy' and 'job' modes sidecar.vault.talend.org/proxy-port: "9999" # Override default proxy port value (8200) - # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. Need annotation below. - sidecar.vault.talend.org/workload: "job" + # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. + # Annotation below is deprecated, use 'sidecar.vault.talend.org/mode' instead. + #sidecar.vault.talend.org/workload: "job" labels: com.talend.application: test com.talend.service: test-app-svc @@ -48,7 +49,7 @@ spec: # custom serviceAccountName with role allowing to perform GET on pods (needed to poll for job's pod status) serviceAccountName: job-sa containers: - - name: test-app-job-secrets-proxy-container + - name: app3-job-container image: everpeace/curl-jq command: - "sh" diff --git a/samples/app-job-4-secrets_static.yaml b/samples/app-job-4-secrets_static.yaml new file mode 100644 index 0000000..d78fce2 --- /dev/null +++ b/samples/app-job-4-secrets_static.yaml @@ -0,0 +1,51 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: app4-job + namespace: default +spec: + backoffLimit: 1 + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/secrets-type: "static" # static secrets + # When dealing with static secrets: no need to enable 'job' mode + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + restartPolicy: Never + # When dealing with static secrets: no need for a custom serviceAccountName + serviceAccountName: default + containers: + - name: app4-job-container + image: busybox:1.28 + command: + - "sh" + - "-c" + - | + set -e + echo "Job started" + echo "I am a job... still working - 1" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 2" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 3" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 4" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 5" + cat /opt/talend/secrets/secrets.properties + echo "Job stopped" + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/deploy/samples/test-app-job.yaml b/samples/app-job-5-secrets-deprecated_annot.yaml similarity index 92% rename from deploy/samples/test-app-job.yaml rename to samples/app-job-5-secrets-deprecated_annot.yaml index 68ea27b..f761007 100644 --- a/deploy/samples/test-app-job.yaml +++ b/samples/app-job-5-secrets-deprecated_annot.yaml @@ -28,7 +28,7 @@ roleRef: apiVersion: batch/v1 kind: Job metadata: - name: test-app-job + name: app5-job namespace: default spec: backoffLimit: 1 @@ -36,7 +36,8 @@ spec: metadata: annotations: sidecar.vault.talend.org/inject: "true" - # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. Need annotation below. + # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. + # Annotation below is deprecated, use 'sidecar.vault.talend.org/mode' instead. sidecar.vault.talend.org/workload: "job" labels: com.talend.application: test @@ -46,7 +47,7 @@ spec: # custom serviceAccountName with role allowing to perform GET on pods (needed to poll for job's pod status) serviceAccountName: job-sa containers: - - name: test-app-job-container + - name: app5-job-container image: busybox:1.28 command: - "sh" diff --git a/test/config/sidecarconfig.yaml b/test/config/injectionconfig.yaml similarity index 51% rename from test/config/sidecarconfig.yaml rename to test/config/injectionconfig.yaml index 747a05d..7f97dc8 100644 --- a/test/config/sidecarconfig.yaml +++ b/test/config/injectionconfig.yaml @@ -1,15 +1,69 @@ -#securityContext: -# fsGroup: 61000 +initContainers: + - name: tvsi-vault-agent-init + image: vault:1.3.2 + imagePullPolicy: Always + env: + - name: SKIP_SETCAP + value: "true" + - name: VAULT_ADDR + value: https://vault:8200 + # env var set by webhook + - name: VSI_SECRETS_TEMPLATES_PLACEHOLDER + value: "" + # env var set by webhook + - name: VSI_VAULT_ROLE + value: "" + command: + - "sh" + - "-c" + - | + cat < vault-agent-config.hcl + pid_file = "/home/vault/pidfile" + + auto_auth { + method "kubernetes" { + mount_path = "auth/kubernetes" + config = { + role = "${VSI_VAULT_ROLE}" + token_path = "/var/run/secrets/talend/vault-sidecar-injector/serviceaccount/token" + } + } + + sink "file" { + config = { + path = "/home/vault/.vault-token" + } + } + } + + ${VSI_SECRETS_TEMPLATES_PLACEHOLDER} + EOF + + docker-entrypoint.sh agent -config=vault-agent-config.hcl -exit-after-auth=true -log-level=info + export VAULT_TOKEN=$(cat /home/vault/.vault-token) + vault token revoke -self + volumeMounts: + # Mount path used to share secrets. The associated volume is expected to be defined in application's manifest but in case it is not, + # a default 'secrets' volume will be injected in the requesting pod (see definition below) so that mutation process does not fail. + - name: secrets + mountPath: /opt/talend/secrets + # The name's value will be overridden by the webhook to point to container's service account volume to use for Vault authentication. + - name: TVSI_SA_SECRETS_VOL_NAME + mountPath: /var/run/secrets/talend/vault-sidecar-injector/serviceaccount + readOnly: true containers: # This container is only injected in K8S jobs to monitor app job's container termination and send signal to vault sidecar - name: tvsi-job-babysitter image: everpeace/curl-jq - imagePullPolicy: IfNotPresent + imagePullPolicy: Always env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name + # env var set by webhook + - name: VSI_JOB_CNT_NAME + value: "" command: - "sh" - "-c" @@ -23,8 +77,8 @@ containers: fi while true; do - cntStatus=$(curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq -c --raw-output '.status.containerStatuses[] | select(.name == "").state | keys[0]') - if [ $cntStatus = "terminated" ]; then + cntStatus=$(curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq -c --raw-output --arg cntname "${VSI_JOB_CNT_NAME}" '.status.containerStatuses[] | select(.name == $cntname).state | keys[0]') + if [ "$cntStatus" = "terminated" ]; then echo "=> job container terminated: send signal" touch /opt/talend/tvsi/vault-sidecars-signal-terminate exit 0 @@ -32,21 +86,36 @@ containers: sleep 2 done volumeMounts: - # Mount path used by injected sidecars to share data + # Mount path used by injected sidecars to share data - name: tvsi-shared mountPath: /opt/talend/tvsi - # The name's value will be overridden by the webhook + # The name's value will be overridden by the webhook - name: K8S_SA_SECRETS_VOL_NAME mountPath: /var/run/secrets/kubernetes.io/serviceaccount readOnly: true - name: tvsi-vault-agent - image: vault:1.3.1 - imagePullPolicy: IfNotPresent + image: vault:1.3.2 + imagePullPolicy: Always env: - name: SKIP_SETCAP value: "true" - name: VAULT_ADDR value: https://vault:8200 + # env var set by webhook + - name: VSI_JOB_WORKLOAD + value: "false" + # env var set by webhook + - name: VSI_PROXY_CONFIG_PLACEHOLDER + value: "" + # env var set by webhook + - name: VSI_SECRETS_TEMPLATES_PLACEHOLDER + value: "" + # env var set by webhook + - name: VSI_VAULT_AUTH_METHOD + value: "kubernetes" + # env var set by webhook + - name: VSI_VAULT_ROLE + value: "" command: - "sh" - "-c" @@ -58,7 +127,7 @@ containers: method "kubernetes" { mount_path = "auth/kubernetes" config = { - role = "" + role = "${VSI_VAULT_ROLE}" token_path = "/var/run/secrets/talend/vault-sidecar-injector/serviceaccount/token" } } @@ -70,12 +139,11 @@ containers: } } - + ${VSI_PROXY_CONFIG_PLACEHOLDER} - + ${VSI_SECRETS_TEMPLATES_PLACEHOLDER} EOF - workload_is_job="" - if [ $workload_is_job = "true" ]; then + if [ "${VSI_JOB_WORKLOAD}" = "true" ]; then docker-entrypoint.sh agent -config=vault-agent-config.hcl -log-level=info & while true; do if [ -f "/opt/talend/tvsi/vault-sidecars-signal-terminate" ]; then @@ -99,14 +167,14 @@ containers: export VAULT_TOKEN=$(cat /home/vault/.vault-token); vault token revoke -self; volumeMounts: - # Mount path used by injected sidecars to share data + # Mount path used by injected sidecars to share data - name: tvsi-shared mountPath: /opt/talend/tvsi - # Mount path used to share secrets. The associated volume is expected to be defined in application's manifest but in case it is not, - # a default 'secrets' volume will be injected in the requesting pod (see definition below) so that mutation process does not fail. + # Mount path used to share secrets. The associated volume is expected to be defined in application's manifest but in case it is not, + # a default 'secrets' volume will be injected in the requesting pod (see definition below) so that mutation process does not fail. - name: secrets mountPath: /opt/talend/secrets - # The name's value will be overridden by the webhook to point to container's service account volume to use for Vault authentication. + # The name's value will be overridden by the webhook to point to container's service account volume to use for Vault authentication. - name: TVSI_SA_SECRETS_VOL_NAME mountPath: /var/run/secrets/talend/vault-sidecar-injector/serviceaccount readOnly: true diff --git a/test/config/sidecarconfig.yaml.resolved b/test/config/injectionconfig.yaml.resolved similarity index 61% rename from test/config/sidecarconfig.yaml.resolved rename to test/config/injectionconfig.yaml.resolved index e710ae2..abd2169 100644 --- a/test/config/sidecarconfig.yaml.resolved +++ b/test/config/injectionconfig.yaml.resolved @@ -12,8 +12,8 @@ containers: fi while true; do - cntStatus=$(curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq -c --raw-output '.status.containerStatuses[] | select(.name == "").state | keys[0]') - if [ $cntStatus = "terminated" ]; then + cntStatus=$(curl -s -X GET -H "Authorization: Bearer $jwt_sa_token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$pod_ns/pods/$POD_NAME?pretty=false | jq -c --raw-output --arg cntname "${VSI_JOB_CNT_NAME}" '.status.containerStatuses[] | select(.name == $cntname).state | keys[0]') + if [ "$cntStatus" = "terminated" ]; then echo "=> job container terminated: send signal" touch /opt/talend/tvsi/vault-sidecars-signal-terminate exit 0 @@ -25,8 +25,9 @@ containers: valueFrom: fieldRef: fieldPath: metadata.name + - name: VSI_JOB_CNT_NAME image: everpeace/curl-jq - imagePullPolicy: IfNotPresent + imagePullPolicy: Always name: tvsi-job-babysitter resources: {} volumeMounts: @@ -46,7 +47,7 @@ containers: method "kubernetes" { mount_path = "auth/kubernetes" config = { - role = "" + role = "${VSI_VAULT_ROLE}" token_path = "/var/run/secrets/talend/vault-sidecar-injector/serviceaccount/token" } } @@ -58,12 +59,11 @@ containers: } } - + ${VSI_PROXY_CONFIG_PLACEHOLDER} - + ${VSI_SECRETS_TEMPLATES_PLACEHOLDER} EOF - workload_is_job="" - if [ $workload_is_job = "true" ]; then + if [ "${VSI_JOB_WORKLOAD}" = "true" ]; then docker-entrypoint.sh agent -config=vault-agent-config.hcl -log-level=info & while true; do if [ -f "/opt/talend/tvsi/vault-sidecars-signal-terminate" ]; then @@ -82,8 +82,15 @@ containers: value: "true" - name: VAULT_ADDR value: https://vault:8200 - image: vault:1.3.1 - imagePullPolicy: IfNotPresent + - name: VSI_JOB_WORKLOAD + value: "false" + - name: VSI_PROXY_CONFIG_PLACEHOLDER + - name: VSI_SECRETS_TEMPLATES_PLACEHOLDER + - name: VSI_VAULT_AUTH_METHOD + value: kubernetes + - name: VSI_VAULT_ROLE + image: vault:1.3.2 + imagePullPolicy: Always lifecycle: preStop: exec: @@ -102,8 +109,53 @@ containers: - mountPath: /var/run/secrets/talend/vault-sidecar-injector/serviceaccount name: TVSI_SA_SECRETS_VOL_NAME readOnly: true -initContainers: null -securityContext: null +initContainers: +- command: + - sh + - -c + - | + cat < vault-agent-config.hcl + pid_file = "/home/vault/pidfile" + + auto_auth { + method "kubernetes" { + mount_path = "auth/kubernetes" + config = { + role = "${VSI_VAULT_ROLE}" + token_path = "/var/run/secrets/talend/vault-sidecar-injector/serviceaccount/token" + } + } + + sink "file" { + config = { + path = "/home/vault/.vault-token" + } + } + } + + ${VSI_SECRETS_TEMPLATES_PLACEHOLDER} + EOF + + docker-entrypoint.sh agent -config=vault-agent-config.hcl -exit-after-auth=true -log-level=info + export VAULT_TOKEN=$(cat /home/vault/.vault-token) + vault token revoke -self + env: + - name: SKIP_SETCAP + value: "true" + - name: VAULT_ADDR + value: https://vault:8200 + - name: VSI_SECRETS_TEMPLATES_PLACEHOLDER + - name: VSI_VAULT_ROLE + image: vault:1.3.2 + imagePullPolicy: Always + name: tvsi-vault-agent-init + resources: {} + volumeMounts: + - mountPath: /opt/talend/secrets + name: secrets + - mountPath: /var/run/secrets/talend/vault-sidecar-injector/serviceaccount + name: TVSI_SA_SECRETS_VOL_NAME + readOnly: true volumes: - emptyDir: medium: Memory diff --git a/test/config/podlifecyclehooks.yaml b/test/config/podlifecyclehooks.yaml index 36f3a96..c8c2bb4 100644 --- a/test/config/podlifecyclehooks.yaml +++ b/test/config/podlifecyclehooks.yaml @@ -3,4 +3,4 @@ postStart: command: - "sh" - "-c" - - cat /* >/dev/null 2>&1 \ No newline at end of file + - cat /* >/dev/null 2>&1 diff --git a/test/config/podlifecyclehooks.yaml.resolved b/test/config/podlifecyclehooks.yaml.resolved index 8a97c0d..9a3d0a0 100644 --- a/test/config/podlifecyclehooks.yaml.resolved +++ b/test/config/podlifecyclehooks.yaml.resolved @@ -3,4 +3,4 @@ postStart: command: - sh - -c - - cat /* >/dev/null 2>&1 + - cat /* >/dev/null 2>&1 diff --git a/test/config/proxyconfig.hcl b/test/config/proxyconfig.hcl index 3b279b4..f7163d9 100644 --- a/test/config/proxyconfig.hcl +++ b/test/config/proxyconfig.hcl @@ -3,6 +3,6 @@ cache { } listener "tcp" { - address = "127.0.0.1:" + address = "127.0.0.1:" tls_disable = true } \ No newline at end of file diff --git a/test/config/tmplblock.hcl b/test/config/tmplblock.hcl index 650cdb3..0f9923e 100644 --- a/test/config/tmplblock.hcl +++ b/test/config/tmplblock.hcl @@ -1,9 +1,9 @@ template { - destination = "/opt/talend/secrets/" + destination = "/opt/talend/secrets/" contents = < + EOH - command = "" + command = "" wait { min = "1s" max = "2s" diff --git a/test/config/tmpldefault.tmpl b/test/config/tmpldefault.tmpl index 3fc3dfd..519fd0c 100644 --- a/test/config/tmpldefault.tmpl +++ b/test/config/tmpldefault.tmpl @@ -1,3 +1,3 @@ -{{ with secret "" }}{{ range \$k, \$v := .Data }} -{{ \$k }}={{ \$v }} +{{ with secret "" }}{{ range $k, $v := .Data }} +{{ $k }}={{ $v }} {{ end }}{{ end }} \ No newline at end of file diff --git a/test/workloads/ko/test-app-dep-1.yaml b/test/workloads/ko/test-app-dep-1.yaml new file mode 100644 index 0000000..f06c9e6 --- /dev/null +++ b/test/workloads/ko/test-app-dep-1.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app-no-container + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ko/test-app-dep-2.yaml b/test/workloads/ko/test-app-dep-2.yaml new file mode 100644 index 0000000..3bdc190 --- /dev/null +++ b/test/workloads/ko/test-app-dep-2.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app-unsupported-secrets-type + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/secrets-type: "kubernetes" # unsupported secrets type + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: test-app-unsupported-secrets-type + image: busybox:1.28 + command: + - "sh" + - "-c" + - > + echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)" + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ko/test-app-dep-3.yaml b/test/workloads/ko/test-app-dep-3.yaml new file mode 100644 index 0000000..769b8c8 --- /dev/null +++ b/test/workloads/ko/test-app-dep-3.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app-missing-label + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + labels: + com.talend.application: test + spec: + serviceAccountName: default + containers: + - name: test-app-missing-label + image: busybox:1.28 + command: + - "sh" + - "-c" + - > + while true;do echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)"; sleep 5; done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ko/test-app-dep-4.yaml b/test/workloads/ko/test-app-dep-4.yaml new file mode 100644 index 0000000..fd6d1d2 --- /dev/null +++ b/test/workloads/ko/test-app-dep-4.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app-secrets-path-dest + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test-app3 + com.talend.service: test-app3-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/role: "test" + sidecar.vault.talend.org/secrets-path: "secret/test/test-app-svc" + sidecar.vault.talend.org/secrets-destination: "secrets.properties,supersecrets.properties" + labels: + com.talend.application: test-app3 + com.talend.service: test-app3-svc + spec: + serviceAccountName: default + containers: + - name: test-app-secrets-path-dest + image: busybox:1.28 + command: + - "sh" + - "-c" + - > + while true;do echo "My secrets are: $(cat /opt/talend/secrets/supersecrets.properties)"; sleep 5; done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ko/test-app-dep-5.yaml b/test/workloads/ko/test-app-dep-5.yaml new file mode 100644 index 0000000..96f9b82 --- /dev/null +++ b/test/workloads/ko/test-app-dep-5.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app-num-secrets-tmpl-dest + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test2 + com.talend.service: test-app2-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/secrets-destination: "secrets.properties,secrets2.properties" + # Test several templates + sidecar.vault.talend.org/secrets-template: | + {{ with secret "secret/test2/test-app2-svc" }} + {{ if .Data.SECRET1 }} + bob={{ .Data.SECRET1 }} + {{ end }} + {{ end }} + --- + {{ with secret "secret/test2/test-app2-svc" }} + {{ if .Data.SECRET2 }} + alice={{ .Data.SECRET2 }} + joe={{ .Data.SECRET3 }} + {{ end }} + {{ end }} + --- + {{ with secret "secret/test2/test-app2-svc" }}{{ range \$k, \$v := .Data }} + {{ \$k }}:{{ \$v }} + {{ end }}{{ end }} + labels: + com.talend.application: test2 + com.talend.service: test-app2-svc + spec: + serviceAccountName: default + containers: + - name: test-app-num-secrets-tmpl-dest-1 + image: busybox:1.28 + command: + - "sh" + - "-c" + - > + while true;do echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)"; sleep 5; done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + - name: test-app-num-secrets-tmpl-dest-2 + image: busybox:1.28 + command: + - "sh" + - "-c" + - > + while true;do echo "My secrets 2 are: $(cat /opt/talend/secrets/secrets2.properties)"; sleep 5; done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + - name: test-app-num-secrets-tmpl-dest-3 + image: busybox:1.28 + command: + - "sh" + - "-c" + - > + while true;do echo "My secrets 3 are: $(cat /opt/talend/secrets/secrets3.properties)"; sleep 5; done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ko/test-app-job-1.yaml b/test/workloads/ko/test-app-job-1.yaml new file mode 100644 index 0000000..06d71d5 --- /dev/null +++ b/test/workloads/ko/test-app-job-1.yaml @@ -0,0 +1,95 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: test-app-job-more-than-one-container + namespace: default +spec: + backoffLimit: 1 + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/mode: "job" + # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. + # Annotation below is deprecated, use 'sidecar.vault.talend.org/mode' instead. + #sidecar.vault.talend.org/workload: "job" + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + restartPolicy: Never + # custom serviceAccountName with role allowing to perform GET on pods (needed to poll for job's pod status) + serviceAccountName: job-sa + containers: + - name: test-app-job-more-than-one-container-1 + image: busybox:1.28 + command: + - "sh" + - "-c" + - | + set -e + while true; do + echo "Wait for secrets file before running job..." + if [ -f "/opt/talend/secrets/secrets.properties" ]; then + echo "Secrets available" + break + fi + sleep 2 + done + echo "Job started" + echo "I am a job... still working - 1" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 2" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 3" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 4" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 5" + cat /opt/talend/secrets/secrets.properties + echo "Job stopped" + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + - name: test-app-job-more-than-one-container-2 + image: busybox:1.28 + command: + - "sh" + - "-c" + - | + set -e + while true; do + echo "Wait for secrets file before running job..." + if [ -f "/opt/talend/secrets/secrets.properties" ]; then + echo "Secrets available" + break + fi + sleep 2 + done + echo "Job started" + echo "I am a job... still working - 1" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 2" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 3" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 4" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 5" + cat /opt/talend/secrets/secrets.properties + echo "Job stopped" + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/deploy/samples/test-app-deployment.yaml b/test/workloads/ok/test-app-dep-1.yaml similarity index 100% rename from deploy/samples/test-app-deployment.yaml rename to test/workloads/ok/test-app-dep-1.yaml diff --git a/test/workloads/ok/test-app-dep-10.yaml b/test/workloads/ok/test-app-dep-10.yaml new file mode 100644 index 0000000..37523f1 --- /dev/null +++ b/test/workloads/ok/test-app-dep-10.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app-no-secrets-volume + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: test-app-no-secrets-volume + image: busybox:1.28 + command: + - "sh" + - "-c" + - > + while true;do echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)"; sleep 5; done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets \ No newline at end of file diff --git a/test/workloads/test-app-2-deployment.yaml b/test/workloads/ok/test-app-dep-2.yaml similarity index 100% rename from test/workloads/test-app-2-deployment.yaml rename to test/workloads/ok/test-app-dep-2.yaml diff --git a/deploy/samples/test-app3-deployment.yaml b/test/workloads/ok/test-app-dep-3.yaml similarity index 100% rename from deploy/samples/test-app3-deployment.yaml rename to test/workloads/ok/test-app-dep-3.yaml diff --git a/test/workloads/test-app-4-deployment.yaml b/test/workloads/ok/test-app-dep-4.yaml similarity index 100% rename from test/workloads/test-app-4-deployment.yaml rename to test/workloads/ok/test-app-dep-4.yaml diff --git a/test/workloads/ok/test-app-dep-5.yaml b/test/workloads/ok/test-app-dep-5.yaml new file mode 100644 index 0000000..18e5b2e --- /dev/null +++ b/test/workloads/ok/test-app-dep-5.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app5 + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/secrets-type: "static" # static secrets + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: test-app5-container + image: busybox:1.28 + command: + - "sh" + - "-c" + - | + set -e + echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)" + while true;do + sleep 5 + done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ok/test-app-dep-6.yaml b/test/workloads/ok/test-app-dep-6.yaml new file mode 100644 index 0000000..2e8c12a --- /dev/null +++ b/test/workloads/ok/test-app-dep-6.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app6 + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/mode: "secrets,proxy" # Enable both 'secrets' and 'proxy' modes + sidecar.vault.talend.org/secrets-type: "static" # static secrets + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: test-app6-container + image: everpeace/curl-jq + command: + - "sh" + - "-c" + - | + set -e + echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)" + echo + echo "Now using Vault Agent as a proxy to leverage Encryption as a Service feature (will encrypt and decrypt our secrets here)" + echo "Advantage: you do not need to deal with any Vault tokens and you just have to send requests to the local Vault Agent sidecar (available at 127.0.0.1) that will then forward everything to Vault server." + echo + + isVaultReady=$(curl -s -X GET http://127.0.0.1:8200/v1/sys/health | jq --raw-output .initialized) + while [ "$isVaultReady" != "true" ];do + sleep 5 + isVaultReady=$(curl -s -X GET http://127.0.0.1:8200/v1/sys/health | jq --raw-output .initialized) + done + + plaintext=$(cat /opt/talend/secrets/secrets.properties | grep SECRET1) + echo "Data that is going to be ciphered and deciphered: $plaintext" + echo + b64Plaintext=$(echo "$plaintext" | base64) + + ciphertext=$(curl -s -X POST --data "{\"plaintext\": \"$b64Plaintext\"}" http://127.0.0.1:8200/v1/transit/encrypt/test-key | jq --raw-output .data.ciphertext) + echo "Ciphertext" + echo "==========" + echo "$ciphertext" + echo + + cleartext=$(curl -s -X POST --data "{\"ciphertext\": \"$ciphertext\"}" http://127.0.0.1:8200/v1/transit/decrypt/test-key | jq --raw-output .data.plaintext) + echo "Cleartext" + echo "==========" + echo "$cleartext" | base64 -d + echo + while true;do + sleep 5 + done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ok/test-app-dep-7.yaml b/test/workloads/ok/test-app-dep-7.yaml new file mode 100644 index 0000000..3bf2e26 --- /dev/null +++ b/test/workloads/ok/test-app-dep-7.yaml @@ -0,0 +1,69 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app7 + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/mode: "secrets,proxy" # Enable both 'secrets' and 'proxy' modes + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: test-app7-container + image: everpeace/curl-jq + command: + - "sh" + - "-c" + - | + set -e + while true; do + echo "Wait for secrets file ..." + if [ -f "/opt/talend/secrets/secrets.properties" ]; then + echo "Secrets available" + break + fi + sleep 2 + done + while true;do + echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)" + echo + echo "Now using Vault Agent as a proxy to leverage Encryption as a Service feature (will encrypt and decrypt our secrets here)" + echo "Advantage: you do not need to deal with any Vault tokens and you just have to send requests to the local Vault Agent sidecar (available at 127.0.0.1) that will then forward everything to Vault server." + echo + + plaintext=$(cat /opt/talend/secrets/secrets.properties | grep SECRET1) + echo "Data that is going to be ciphered and deciphered: $plaintext" + echo + b64Plaintext=$(echo "$plaintext" | base64) + + ciphertext=$(curl -s -X POST --data "{\"plaintext\": \"$b64Plaintext\"}" http://127.0.0.1:8200/v1/transit/encrypt/test-key | jq --raw-output .data.ciphertext) + echo "Ciphertext" + echo "==========" + echo "$ciphertext" + echo + + cleartext=$(curl -s -X POST --data "{\"ciphertext\": \"$ciphertext\"}" http://127.0.0.1:8200/v1/transit/decrypt/test-key | jq --raw-output .data.plaintext) + echo "Cleartext" + echo "==========" + echo "$cleartext" | base64 -d + echo + sleep 5 + done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ok/test-app-dep-8.yaml b/test/workloads/ok/test-app-dep-8.yaml new file mode 100644 index 0000000..7afc4db --- /dev/null +++ b/test/workloads/ok/test-app-dep-8.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app8 + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/secrets-hook: "true" + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: test-app8-container + image: busybox:1.28 + command: + - "sh" + - "-c" + - > + while true;do echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)"; sleep 5; done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ok/test-app-dep-9.yaml b/test/workloads/ok/test-app-dep-9.yaml new file mode 100644 index 0000000..7822e7a --- /dev/null +++ b/test/workloads/ok/test-app-dep-9.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app-no-injection + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + com.talend.application: test + com.talend.service: test-app-svc + template: + metadata: + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + serviceAccountName: default + containers: + - name: test-app-no-injection + image: busybox:1.28 + command: + - "sh" + - "-c" + - > + while true;do echo "My secrets are: $(cat /opt/talend/secrets/secrets.properties)"; sleep 5; done + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/ok/test-app-job-1.yaml b/test/workloads/ok/test-app-job-1.yaml new file mode 100644 index 0000000..63fed03 --- /dev/null +++ b/test/workloads/ok/test-app-job-1.yaml @@ -0,0 +1,61 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: test-app-job + namespace: default +spec: + backoffLimit: 1 + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/mode: "job" # Enable 'job' mode (will also enable 'secrets' mode) + # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. + # Annotation below is deprecated, use 'sidecar.vault.talend.org/mode' instead. + #sidecar.vault.talend.org/workload: "job" + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + restartPolicy: Never + # custom serviceAccountName with role allowing to perform GET on pods (needed to poll for job's pod status) + serviceAccountName: job-sa + containers: + - name: test-app-job-container + image: busybox:1.28 + command: + - "sh" + - "-c" + - | + set -e + while true; do + echo "Wait for secrets file before running job..." + if [ -f "/opt/talend/secrets/secrets.properties" ]; then + echo "Secrets available" + break + fi + sleep 2 + done + echo "Job started" + echo "I am a job... still working - 1" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 2" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 3" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 4" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 5" + cat /opt/talend/secrets/secrets.properties + echo "Job stopped" + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/test-app-2-job.yaml b/test/workloads/ok/test-app-job-2.yaml similarity index 77% rename from test/workloads/test-app-2-job.yaml rename to test/workloads/ok/test-app-job-2.yaml index a74a6b0..b6dec23 100644 --- a/test/workloads/test-app-2-job.yaml +++ b/test/workloads/ok/test-app-job-2.yaml @@ -9,9 +9,10 @@ spec: metadata: annotations: sidecar.vault.talend.org/inject: "true" - sidecar.vault.talend.org/mode: "proxy" # Enable only 'proxy' mode - # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. Need annotation below. - sidecar.vault.talend.org/workload: "job" + sidecar.vault.talend.org/mode: "proxy,job" # Enable 'proxy' and 'job' modes + # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. + # Annotation below is deprecated, use 'sidecar.vault.talend.org/mode' instead. + #sidecar.vault.talend.org/workload: "job" labels: com.talend.application: test spec: @@ -26,13 +27,17 @@ spec: - "-c" - | set -e - echo "Wait a while to make sure Vault Agent is started and authenticated..." - sleep 10 echo "Job started" echo "Now using Vault Agent as a proxy to leverage Encryption as a Service feature" echo "Advantage: you do not need to deal with any Vault tokens and you just have to send requests to the local Vault Agent sidecar (available at 127.0.0.1) that will then forward everything to Vault server." echo + isVaultReady=$(curl -s -X GET http://127.0.0.1:8200/v1/sys/health | jq --raw-output .initialized) + while [ "$isVaultReady" != "true" ];do + sleep 5 + isVaultReady=$(curl -s -X GET http://127.0.0.1:8200/v1/sys/health | jq --raw-output .initialized) + done + plaintext="Secret data to protect" echo "Data that is going to be ciphered and deciphered: $plaintext" echo diff --git a/test/workloads/test-app-3-job.yaml b/test/workloads/ok/test-app-job-3.yaml similarity index 90% rename from test/workloads/test-app-3-job.yaml rename to test/workloads/ok/test-app-job-3.yaml index eec0e91..4afb102 100644 --- a/test/workloads/test-app-3-job.yaml +++ b/test/workloads/ok/test-app-job-3.yaml @@ -9,10 +9,11 @@ spec: metadata: annotations: sidecar.vault.talend.org/inject: "true" - sidecar.vault.talend.org/mode: "secrets,proxy" # Enable both 'secrets' and 'proxy' modes + sidecar.vault.talend.org/mode: "secrets,proxy,job" # Enable 'secrets', 'proxy' and 'job' modes sidecar.vault.talend.org/proxy-port: "9999" # Override default proxy port value (8200) - # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. Need annotation below. - sidecar.vault.talend.org/workload: "job" + # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. + # Annotation below is deprecated, use 'sidecar.vault.talend.org/mode' instead. + #sidecar.vault.talend.org/workload: "job" labels: com.talend.application: test com.talend.service: test-app-svc diff --git a/test/workloads/ok/test-app-job-4.yaml b/test/workloads/ok/test-app-job-4.yaml new file mode 100644 index 0000000..8af80dc --- /dev/null +++ b/test/workloads/ok/test-app-job-4.yaml @@ -0,0 +1,51 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: test-app-job-secrets-static + namespace: default +spec: + backoffLimit: 1 + template: + metadata: + annotations: + sidecar.vault.talend.org/inject: "true" + sidecar.vault.talend.org/secrets-type: "static" # static secrets + # When dealing with static secrets: no need to enable 'job' mode + labels: + com.talend.application: test + com.talend.service: test-app-svc + spec: + restartPolicy: Never + # When dealing with static secrets: no need for a custom serviceAccountName + serviceAccountName: default + containers: + - name: test-app-job-secrets-static-container + image: busybox:1.28 + command: + - "sh" + - "-c" + - | + set -e + echo "Job started" + echo "I am a job... still working - 1" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 2" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 3" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 4" + cat /opt/talend/secrets/secrets.properties + sleep 5 + echo "I am a job... still working - 5" + cat /opt/talend/secrets/secrets.properties + echo "Job stopped" + volumeMounts: + - name: secrets + mountPath: /opt/talend/secrets + volumes: + - name: secrets + emptyDir: + medium: Memory \ No newline at end of file diff --git a/test/workloads/test-app-1-job.yaml b/test/workloads/ok/test-app-job-5.yaml similarity index 89% rename from test/workloads/test-app-1-job.yaml rename to test/workloads/ok/test-app-job-5.yaml index 63017c7..41d2079 100644 --- a/test/workloads/test-app-1-job.yaml +++ b/test/workloads/ok/test-app-job-5.yaml @@ -1,7 +1,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: test-app-job + name: test-app-job-deprecated-annot namespace: default spec: backoffLimit: 1 @@ -9,7 +9,8 @@ spec: metadata: annotations: sidecar.vault.talend.org/inject: "true" - # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. Need annotation below. + # Vault Sidecar Injector receive the pod spec: don't know whether it is a job or a deployment. + # Annotation below is deprecated, use 'sidecar.vault.talend.org/mode' instead. sidecar.vault.talend.org/workload: "job" labels: com.talend.application: test @@ -19,7 +20,7 @@ spec: # custom serviceAccountName with role allowing to perform GET on pods (needed to poll for job's pod status) serviceAccountName: job-sa containers: - - name: test-app-job-container + - name: test-app-job-deprecated-annot-container image: busybox:1.28 command: - "sh"