├── README.md ├── apps ├── git │ └── nginx │ │ ├── app.yaml │ │ └── manifests │ │ └── all.yaml └── helm │ ├── complex-redis │ ├── README.md │ ├── app.yaml │ ├── child-apps │ │ ├── extra-manifests-app.yaml │ │ └── redis.yaml │ └── extra-manifests │ │ └── secret.yaml │ └── simple-redis │ ├── README.md │ └── app.yaml ├── argocd ├── gh-credentials.yaml └── overlays │ ├── README.md │ ├── argocd-cm.yaml │ ├── argocd-repo-server.yaml │ └── kustomization.yaml └── vault ├── README.md └── app.yaml /README.md: -------------------------------------------------------------------------------- 1 | # Kubecon 2021 Demo: argocd-vault-plugin 2 | 3 | ## Outline 4 | 5 | 1. Deploy ArgoCD and Hashicorp Vault 6 | 7 | - Install argocd-vault-plugin (AVP) 8 | 9 | - Enable Kubernetes authentication 10 | 11 | 1. Deploy a simple Git-based Argo CD application 12 | 13 | 1. Deploy a Helm chart through Argo CD 14 | 15 | Details for all manifests applied to our clusters are available in `README` files in the manifests containing folder. 16 | 17 | ## Deploy Argo CD and Vault 18 | 19 | ### Deploy Argo CD 20 | ```sh 21 | kustomize build argocd/overlays | kubectl apply -f - 22 | 23 | kubectl port-forward svc/argocd-server 8080:80 &>/dev/null & 24 | 25 | kubectl get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d | pbcopy 26 | ``` 27 | 28 | ### Deploy Vault 29 | ```sh 30 | kubectl apply -f vault/app.yaml 31 | ``` 32 | 33 | ## Deploy a Git app (Nginx) with argocd-vault-plugin 34 | 35 | ### Create a set of YAMLs for our app 36 | ```sh 37 | cat > apps/git/nginx/manifests/all.yaml << EOF 38 | apiVersion: v1 39 | kind: ConfigMap 40 | metadata: 41 | name: my-nginx 42 | data: 43 | nginx.conf: | 44 | events {} 45 | env MY_SECRET; 46 | http { 47 | server { 48 | listen 8080; 49 | location / { 50 | set_by_lua \$my_secret 'return os.getenv("MY_SECRET")'; 51 | return 200 \$my_secret; 52 | } 53 | } 54 | } 55 | --- 56 | apiVersion: v1 57 | kind: Secret 58 | metadata: 59 | name: my-nginx 60 | annotations: 61 | 62 | # Our special annotation to tell AVP where the secrets are 63 | avp.kubernetes.io/path: "secret/data/my-nginx" 64 | stringData: 65 | MY_SECRET: 66 | --- 67 | apiVersion: apps/v1 68 | kind: Deployment 69 | metadata: 70 | name: my-nginx 71 | spec: 72 | selector: 73 | matchLabels: 74 | app: nginx 75 | replicas: 1 76 | template: 77 | metadata: 78 | labels: 79 | app: nginx 80 | spec: 81 | containers: 82 | - name: nginx 83 | image: openresty/openresty:1.19.9.1-0-alpine 84 | ports: 85 | - containerPort: 8080 86 | envFrom: 87 | - secretRef: 88 | name: my-nginx 89 | volumeMounts: 90 | - name: nginx-conf 91 | mountPath: /usr/local/openresty/nginx/conf/ 92 | volumes: 93 | - name: nginx-conf 94 | configMap: 95 | name: my-nginx 96 | EOF 97 | 98 | git add apps/git/nginx/all.yaml 99 | git commit -m "demo: Creating the files for my ArgoCD app" 100 | git push 101 | ``` 102 | 103 | ### Create a secret in our Secret Manager 104 | ```sh 105 | kubectl port-forward svc/vault 8200 &>/dev/null & 106 | export VAULT_ADDR=http://localhost:8200 107 | export VAULT_TOKEN=root 108 | vault kv put secret/my-nginx password="secret-password" 109 | vault kv get secret/my-nginx 110 | ``` 111 | 112 | ### Create our Argo app 113 | ```sh 114 | cat > apps/git/nginx/app.yaml << EOF 115 | apiVersion: argoproj.io/v1alpha1 116 | kind: Application 117 | metadata: 118 | name: my-nginx 119 | spec: 120 | destination: 121 | namespace: default 122 | server: https://kubernetes.default.svc 123 | project: default 124 | source: 125 | repoURL: 'https://github.com/jkayani/avp-demo-kubecon-2021' 126 | targetRevision: HEAD 127 | path: apps/git/nginx/manifests 128 | 129 | # We're telling Argo CD to use our plugin to deploy the manifests 130 | plugin: 131 | name: argocd-vault 132 | EOF 133 | 134 | kubectl apply -f apps/git/nginx/app.yaml 135 | 136 | kubectl port-forward deployment/my-nginx 8081:8080 137 | ``` 138 | 139 | ### Modify our secret in our Secret Manager 140 | ```sh 141 | vault kv put secret/my-nginx password="secret-password-edited" 142 | vault kv get secret/my-nginx 143 | ``` 144 | 145 | ### Hard-refresh our Argo app and restart our deployment to see the change 146 | ```sh 147 | argocd app diff example-git-app --hard-refresh 148 | kubectl rollout restart deployment/my-nginx 149 | ``` 150 | 151 | ## Deploy a Helm chart (Redis) with argocd-vault-plugin 152 | 153 | ### Create a secret in our Secret Manager 154 | ```sh 155 | vault kv put secret/my-redis password="shhh" 156 | vault kv get secret/my-redis 157 | ``` 158 | 159 | ### Create our Argo app 160 | ```sh 161 | cat > apps/helm/simple-redis/app.yaml << EOF 162 | apiVersion: argoproj.io/v1alpha1 163 | kind: Application 164 | metadata: 165 | name: my-redis 166 | spec: 167 | destination: 168 | namespace: default 169 | server: https://kubernetes.default.svc 170 | project: default 171 | source: 172 | repoURL: https://github.com/bitnami/charts/ 173 | targetRevision: HEAD 174 | path: bitnami/redis 175 | plugin: 176 | name: argocd-vault-helm 177 | env: 178 | 179 | # These are the arguments we pass to "helm template" 180 | - name: helm_args 181 | value: | 182 | --set architecture=standalone 183 | --set auth.enabled=true 184 | --set global.redis.password= 185 | EOF 186 | 187 | kubectl apply -f apps/helm/simple-redis/app.yaml 188 | 189 | kubectl port-forward svc/my-redis-master 6379 190 | 191 | redis-cli --askpass 192 | ``` 193 | -------------------------------------------------------------------------------- /apps/git/nginx/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: my-nginx 5 | spec: 6 | destination: 7 | namespace: default 8 | server: https://kubernetes.default.svc 9 | project: default 10 | source: 11 | repoURL: 'https://github.com/jkayani/avp-demo-kubecon-2021' 12 | targetRevision: HEAD 13 | path: apps/git/nginx/manifests 14 | 15 | # We're telling Argo CD to use our plugin to deploy the manifests 16 | plugin: 17 | name: argocd-vault 18 | -------------------------------------------------------------------------------- /apps/git/nginx/manifests/all.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: my-nginx 5 | data: 6 | nginx.conf: | 7 | events {} 8 | env MY_SECRET; 9 | http { 10 | server { 11 | listen 8080; 12 | location / { 13 | set_by_lua $my_secret 'return os.getenv("MY_SECRET")'; 14 | return 200 $my_secret; 15 | } 16 | } 17 | } 18 | --- 19 | apiVersion: v1 20 | kind: Secret 21 | metadata: 22 | name: my-nginx 23 | annotations: 24 | 25 | # Our special annotation to tell AVP where the secrets are 26 | avp.kubernetes.io/path: "secret/data/my-nginx" 27 | stringData: 28 | MY_SECRET: 29 | --- 30 | apiVersion: apps/v1 31 | kind: Deployment 32 | metadata: 33 | name: my-nginx 34 | spec: 35 | selector: 36 | matchLabels: 37 | app: nginx 38 | replicas: 1 39 | template: 40 | metadata: 41 | labels: 42 | app: nginx 43 | spec: 44 | containers: 45 | - name: nginx 46 | image: docker.io/openresty/openresty:1.19.9.1-0-alpine 47 | imagePullPolicy: IfNotPresent 48 | ports: 49 | - containerPort: 8080 50 | envFrom: 51 | - secretRef: 52 | name: my-nginx 53 | volumeMounts: 54 | - name: nginx-conf 55 | mountPath: /usr/local/openresty/nginx/conf/ 56 | volumes: 57 | - name: nginx-conf 58 | configMap: 59 | name: my-nginx 60 | -------------------------------------------------------------------------------- /apps/helm/complex-redis/README.md: -------------------------------------------------------------------------------- 1 | # complex-redis 2 | 3 | This shows how you can integrate argocd-vault-plugin with Helm chart via pre-existing secrets deployed via AVP. Rather than injecting placeholders into the Helm values, and having AVP process them after rendering the chart, you can simply deploy the "extra" secrets a Helm charts needs _before_ deploying the chart via the app-of-apps pattern and sync-waves. 4 | 5 | We create an `app.yaml` that will deploy 2 child Argo apps from `child-apps` - this is the app of apps pattern. 6 | 7 | The first child app to be deployed will be the `redis-manifests` app which will contain the secret we need to exist in the cluster before installing the chart. It will be deployed first because it's `annotation` value is lower than the the `redis` app. 8 | 9 | The second child app, `redis`, just installs the Redis Helm chart from upstream. We can use the `helm`-native Argo integration here since the secrets and placeholders are handled by the `redis-manifests` app. 10 | 11 | The `extra-manifests` folder just contains the YAMLs that comprise the `redis-manifests` app - in our case, a secret we want to process via AVP. -------------------------------------------------------------------------------- /apps/helm/complex-redis/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: my-redis 5 | spec: 6 | destination: 7 | namespace: default 8 | server: https://kubernetes.default.svc 9 | project: default 10 | source: 11 | repoURL: 'https://github.com/jkayani/avp-demo-kubecon-2021' 12 | targetRevision: HEAD 13 | path: apps/helm/complex-redis/child-apps 14 | -------------------------------------------------------------------------------- /apps/helm/complex-redis/child-apps/extra-manifests-app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: redis-manifests 5 | annotations: 6 | argocd.argoproj.io/sync-wave: "-1" 7 | spec: 8 | destination: 9 | namespace: default 10 | server: https://kubernetes.default.svc 11 | project: default 12 | source: 13 | repoURL: 'https://github.com/jkayani/avp-demo-kubecon-2021' 14 | targetRevision: HEAD 15 | path: apps/helm/complex-redis/extra-manifests 16 | plugin: 17 | name: argocd-vault 18 | syncPolicy: 19 | automated: 20 | prune: true 21 | selfHeal: true 22 | -------------------------------------------------------------------------------- /apps/helm/complex-redis/child-apps/redis.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: redis 5 | annotations: 6 | argocd.argoproj.io/sync-wave: "1" 7 | spec: 8 | destination: 9 | namespace: default 10 | server: https://kubernetes.default.svc 11 | project: default 12 | source: 13 | repoURL: https://charts.bitnami.com/bitnami 14 | chart: redis 15 | targetRevision: 15.4.0 16 | helm: 17 | values: | 18 | architecture: standalone 19 | auth: 20 | enabled: true 21 | existingSecret: my-redis 22 | existingSecretPasswordKey: redis-password 23 | syncPolicy: 24 | automated: 25 | prune: true 26 | selfHeal: true 27 | -------------------------------------------------------------------------------- /apps/helm/complex-redis/extra-manifests/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: my-redis 5 | annotations: 6 | avp.kubernetes.io/path: "secret/data/my-redis" 7 | type: Opaque 8 | stringData: 9 | redis-password: -------------------------------------------------------------------------------- /apps/helm/simple-redis/README.md: -------------------------------------------------------------------------------- 1 | # simple-redis 2 | 3 | This shows how you can integrate argocd-vault-plugin with a Helm chart with no extra YAMLs. 4 | 5 | `app.yaml` is an Argo app that uses the Redis Helm chart as the set of manifests, and then uses AVP to process and deploy the Helm chart. We specifically inject our AVP placeholders into the Helm values, and then have Argo run `helm template | argocd-vault-plugin generate -` to render and process the chart templates. 6 | 7 | We're using [inline-path placeholders](https://ibm.github.io/argocd-vault-plugin/v1.4.0/howitworks/#inline-path-placeholders) to interpolate the value of the global Redis password. Our placeholder `` says to take the `my-redis` secret and interpolate its `password` key for the value of that Helm chart parameter. 8 | -------------------------------------------------------------------------------- /apps/helm/simple-redis/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: my-redis 5 | spec: 6 | destination: 7 | namespace: default 8 | server: https://kubernetes.default.svc 9 | project: default 10 | source: 11 | repoURL: https://charts.bitnami.com/bitnami 12 | chart: redis 13 | targetRevision: 15.4.0 14 | plugin: 15 | name: argocd-vault-helm 16 | env: 17 | 18 | # These are the arguments we pass to "helm template" 19 | - name: helm_args 20 | value: | 21 | --set architecture=standalone 22 | --set auth.enabled=true 23 | --set global.redis.password= 24 | -------------------------------------------------------------------------------- /argocd/gh-credentials.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | annotations: 5 | avp.kubernetes.io/path: secret/data/github 6 | name: gh-credentials 7 | type: Opaque 8 | stringData: 9 | username: 10 | password: -------------------------------------------------------------------------------- /argocd/overlays/README.md: -------------------------------------------------------------------------------- 1 | # Installing AVP with Argo CD 2 | 3 | 2 main things: 4 | 5 | - Modify repo-server deployment to use an initContainer to download our AVP binary: 6 | 7 | - Configure AVP (which runs within the repo-server container) via env variables on the repo-server: 8 | 9 | - Mount our service account token 10 | 11 | - Register 2 custom plugins using AVP 12 | 13 | - The first one is for plain manifests 14 | 15 | - Second is for using AVP with Helm charts: -------------------------------------------------------------------------------- /argocd/overlays/argocd-cm.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: argocd-cm 5 | data: 6 | admin.enabled: 'true' 7 | users.anonymous.enabled: 'false' 8 | 9 | configManagementPlugins: | 10 | - name: argocd-vault 11 | generate: 12 | command: [argocd-vault-plugin] 13 | args: 14 | - generate 15 | - ./ 16 | 17 | - name: argocd-vault-helm 18 | init: 19 | command: [sh, -c] 20 | args: 21 | - | 22 | helm repo add bitnami https://charts.bitnami.com/bitnami && 23 | helm dependency build 24 | 25 | generate: 26 | command: [sh, -c] 27 | args: 28 | - | 29 | helm template $ARGOCD_APP_NAME ${helm_args} -n $ARGOCD_APP_NAMESPACE . |\ 30 | argocd-vault-plugin generate - -------------------------------------------------------------------------------- /argocd/overlays/argocd-repo-server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: argocd-repo-server 5 | spec: 6 | template: 7 | spec: 8 | 9 | volumes: 10 | - name: custom-tools 11 | emptyDir: {} 12 | 13 | initContainers: 14 | - name: download-tools 15 | image: registry.access.redhat.com/ubi8 16 | command: [sh, -c] 17 | args: 18 | - curl -L https://github.com/IBM/argocd-vault-plugin/releases/download/v$(AVP_VERSION)/argocd-vault-plugin_$(AVP_VERSION)_linux_amd64 -o argocd-vault-plugin 19 | &&\ 20 | 21 | chmod +x argocd-vault-plugin &&\ 22 | 23 | mv argocd-vault-plugin /custom-tools/ 24 | env: 25 | - name: AVP_VERSION 26 | value: 1.4.0 27 | volumeMounts: 28 | - mountPath: /custom-tools 29 | name: custom-tools 30 | 31 | containers: 32 | - name: argocd-repo-server 33 | 34 | volumeMounts: 35 | - name: custom-tools 36 | subPath: argocd-vault-plugin 37 | mountPath: /usr/local/bin/argocd-vault-plugin 38 | 39 | # Configure AVP to use Kubernetes auth to Vault 40 | env: 41 | - name: AVP_TYPE 42 | value: vault 43 | - name: VAULT_ADDR 44 | value: http://vault:8200 45 | - name: AVP_AUTH_TYPE 46 | value: k8s 47 | - name: AVP_K8S_ROLE 48 | value: argocd 49 | 50 | # Optional. Useful if you want to either: 51 | # - Pass the argocd-vault-plugin configuration via a pre-existing Secret 52 | # - Use Kuberenetes auth to your Vault instance 53 | automountServiceAccountToken: true -------------------------------------------------------------------------------- /argocd/overlays/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - https://github.com/argoproj/argo-cd/manifests/cluster-install?ref=v2.1.6 6 | 7 | patchesStrategicMerge: 8 | - argocd-repo-server.yaml 9 | - argocd-cm.yaml 10 | -------------------------------------------------------------------------------- /vault/README.md: -------------------------------------------------------------------------------- 1 | # Configuring Vault to use Kubernetes auth 2 | 3 | [Previously](../argocd/overlays) we configured AVP within our repo-server to use Kubernetes authentication (service account token auth). To configure the Vault side of this, we have to: 4 | 5 | - Create a policy permitting `read`ing secrets from the KV-V2 secret path (`secret`) 6 | 7 | - Enable Kubernetes auth in Vault, configuring Vault to use our Kubernetes API server to verify our SA token. We disable issuer validation because of a change in Kubernetes 1.21. 8 | 9 | - Create an `argocd` Vault role, granted to the `default` SA in the `default` namespace, and give it the policy we created earlier 10 | -------------------------------------------------------------------------------- /vault/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: vault 5 | spec: 6 | destination: 7 | namespace: default 8 | server: https://kubernetes.default.svc 9 | project: default 10 | source: 11 | repoURL: 'https://helm.releases.hashicorp.com' 12 | chart: vault 13 | targetRevision: 0.15.0 14 | helm: 15 | releaseName: vault 16 | values: | 17 | injector: 18 | enabled: false 19 | 20 | server: 21 | dev: 22 | enabled: true 23 | 24 | postStart: 25 | - /bin/sh 26 | - -c 27 | 28 | - | 29 | sleep 10s &&\ 30 | 31 | printf 'path "secret/*"{\ncapabilities=["read"]\n}' | vault policy write demo-policy - &&\ 32 | 33 | vault auth enable kubernetes &&\ 34 | vault write auth/kubernetes/config\ 35 | kubernetes_host=https://kubernetes.docker.internal:6443 \ 36 | disable_iss_validation=true &&\ 37 | vault write auth/kubernetes/role/argocd \ 38 | bound_service_account_names=default \ 39 | bound_service_account_namespaces=default \ 40 | policies="demo-policy" 41 | syncPolicy: 42 | automated: 43 | prune: true 44 | selfHeal: true 45 | --------------------------------------------------------------------------------