GitOps Secret Encryption¶
In the GitOps operational model, resources to be deployed are stored in a Git repository in YAML format. These files may contain sensitive information, such as database passwords, API keys, etc., which should not be stored in plain text. Additionally, even when these resources are deployed in a Kubernetes cluster as secrets, they can still be easily viewed through base64 encoding, leading to many security issues.
To address these problems, this article will introduce several solutions to implement encryption for manifest files in GitOps. The solutions are mainly divided into two categories:
-
Based on ArgoCD's plugin mechanism, decrypt and replace sensitive information when rendering manifest files
Advantages of this method:
- Closely integrated with ArgoCD, no need to install additional components
- Can be easily integrated with existing credential management systems
- Supports many credential storage backends, such as Vault, Kubernetes Secret, AWS Secret, etc.
- Supports encryption of any Kubernetes resource, such as secrets, configmaps, deployment environment variables, etc.
Disadvantage: Sensitive information changes require manual synchronization
-
Independent of ArgoCD, relying on the project's or tool's own encryption/decryption capabilities for sensitive information
Advantages of this method:
- Does not depend on a specific GitOps implementation
- Higher security (e.g., cannot be easily decrypted or viewed using
kubectl describe
) - Simple configuration
- Full GitOps experience, no need for manual synchronization
Disadvantage: Requires separate tool installation and manual configuration
Choose an appropriate solution based on the actual usage scenario.
Based on ArgoCD's Plugin Mechanism¶
This solution uses the argocd-vault-plugin plugin, which will fetch sensitive information from a specified backend storage and integrate well with ArgoCD.
It supports many backend storages. This article will use HashiCorp Vault and Kubernetes Secret as examples.
Note
How to use Vault is not the focus of this article; here we only introduce how to configure this plugin to use Vault as the backend storage for sensitive information.
Some configurations need to be done before installing ArgoCD, as follows:
Installation Configuration¶
Administrator Sets Backend Storage Access Credentials¶
Before installing ArgoCD, the administrator needs to create a secret for the plugin to access the backend storage.
-
Backend storage is HashiCorp Vault
apiVersion: v1 kind: Secret metadata: name: argocd-vault-plugin-credentials namespace: argo-cd # (1)! data: AVP_TYPE: vault # (2)! AVP_AUTH_TYPE: token # (3)! VAULT_ADDR: 10.6.10.11 # (4)! VAULT_TOKEN: cm9vdA== # (5)! type: Opaque
- Namespace where ArgoCD is deployed
- Specify the type of backend storage as Vault
- Specify the auth type of the backend storage
- Vault's address
- Initialization token obtained from Vault pod logs (in actual use, set this to a token obtained with specific permissions and access policies)
By configuring the above secret, during ArgoCD's runtime, the configurations in
data
will be passed to theargocd-vault-plugin
as environment variables for the plugin to access Vault and fetch sensitive data. -
Backend storage is Kubernetes Secret
apiVersion: v1 kind: Secret metadata: name: argocd-vault-plugin-credentials namespace: argo-cd data: AVP_TYPE: kubernetessecret # (1)! type: Opaque
- Set the backend storage type to
kubernetessecret
- Set the backend storage type to
The above is the basic configuration. If the backend storage is of another type or requires other configurations, refer to the backend storage configuration.
Install ArgoCD¶
ArgoCD is pre-installed in the application workspace, but it can also be installed separately. For detailed installation steps, see Install ArgoCD. Below are configurations for two scenarios.
Modify the Default Installation of ArgoCD in the Workspace¶
-
Add a ConfigMap to configure the plugin
Go to Container Management -> Select kpanda-global-cluster from the cluster list -> ConfigMaps and Secrets -> Create a new YAML, content as follows:
apiVersion: v1 kind: ConfigMap metadata: name: cmp-plugin namespace: argo-cd data: avp.yaml: | apiVersion: argoproj.io/v1alpha1 kind: ConfigManagementPlugin metadata: name: argocd-vault-plugin spec: allowConcurrency: true discover: find: command: - sh - "-c" - "find . -name '*.yaml' | xargs -I {} grep \"<path\\|avp\\.kubernetes\\.io\" {} | grep ." generate: command: - argocd-vault-plugin - generate - --verbose-sensitive-output=true - ./ lockRepo: false avp-helm.yaml: apiVersion: argoproj.io/v1alpha1 kind: ConfigManagementPlugin metadata: name: argocd-vault-plugin-helm spec: allowConcurrency: true discover: find: command: - sh - "-c" - "find . -name 'Chart.yaml' && find . -name 'values.yaml'" generate: command: - sh - "-c" - | helm template $argocd_APP_NAME -n $ARGOCD_APP_NAMESPACE ${argocd_ENV_HELM_ARGS} . | argocd-vault-plugin generate - lockRepo: false
-
Modify the Deployment of
argocd-repo-server
Go to Container Management -> Select kpanda-global-cluster from the cluster list -> Workloads -> Deployments -> Select the
argocd
namespace -> Edit the YAML ofargocd-repo-server
with the following modifications:apiVersion: apps/v1 kind: Deployment metadata: name: argocd-repo-server namespace: argo-cd spec: template: spec: volumes: # (1)! - name: cmp-plugin configMap: name: cmp-plugin defaultMode: 420 - name: custom-tools emptyDir: {} initContainers: - name: init-vault-plugin # (2)! image: release.daocloud.io/amamba/argocd-vault-plugin:v1.17.0 # (3)! command: - sh - "-c" args: - cp /usr/local/bin/argocd-vault-plugin /custom-tools volumeMounts: - name: custom-tools mountPath: /custom-tools containers: - name: avp # (4)! image: quay.io/argoproj/argocd:v2.10.4 # (5)! command: - /var/run/argocd/argocd-cmp-server envFrom: - secretRef: name: argocd-vault-plugin-credentials volumeMounts: - name: var-files mountPath: /var/run/argocd - name: plugins mountPath: /home/argocd/cmp-server/plugins - name: tmp mountPath: /tmp - name: cmp-plugin mountPath: /home/argocd/cmp-server/config/plugin.yaml subPath: avp.yaml - name: custom-tools mountPath: /usr/local/bin/argocd-vault-plugin subPath: argocd-vault-plugin - name: repo-server # (6)! envFrom: - secretRef: name: argocd-vault-plugin-credentials volumeMounts: - name: cmp-plugin mountPath: /home/argocd/cmp-server/config/plugin.yaml subPath: avp.yaml - name: custom-tools mountPath: /usr/local/bin/argocd-vault-plugin subPath: argocd-vault-plugin
- Add volumes
- Add a new initContainer
- Replace the address if in an offline environment
- Add a sidecar container
- The image address should be the same as the
repo-server
- Modify the existing
repo-server
container, addingenvFrom
andvolumeMounts
Install ArgoCD Separately¶
Modify the following Helm values during installation:
reposerver: # (1)!
volumes: # (2)!
- name: cmp-plugin
configMap:
name: cmp-plugin
- name: custom-tools
emptyDir: {}
initContainers:
- name: init-vault-plugin # (3)!
image: release.daocloud.io/amamba/argocd-vault-plugin:v1.17.0 # (4)!
command:
- sh
- "-c"
args:
- cp /usr/local/bin/argocd-vault-plugin /custom-tools
volumeMounts:
- name: custom-tools
mountPath: /custom-tools
envFrom: # (5)!
- secretRef:
name: argocd-vault-plugin-credentials
volumeMounts: # (6)!
- name: plugins
mountPath: /home/argocd/cmp-server/plugins
- name: tmp
mountPath: /tmp
- name: cmp-plugin
mountPath: /home/argocd/cmp-server/config/plugin.yaml
subPath: avp.yaml
- name: custom-tools
mountPath: /usr/local/bin/argocd-vault-plugin
subPath: argocd-vault-plugin
extraContainers: # (7)!
- name: avp
image: quay.io/argoproj/argocd:v2.10.4 # (8)!
command:
- /var/run/argocd/argocd-cmp-server
envFrom:
- secretRef:
name: argocd-vault-plugin-credentials
volumeMounts:
- name: var-files
mountPath: /var/run/argocd
- name: plugins
mountPath: /home/argocd/cmp-server/plugins
- name: tmp
mountPath: /tmp
- name: cmp-plugin
mountPath: /home/argocd/cmp-server/config/plugin.yaml
subPath: avp.yaml
- name: custom-tools
mountPath: /usr/local/bin/argocd-vault-plugin
subPath: argocd-vault-plugin
configs: # (9)!
cmp:
plugins:
argocd-vault-plugin:
allowConcurrency: true
discover:
find:
command:
- sh
- "-c"
- "find . -name '*.yaml' | xargs -I {} grep \"<path\\|avp\\.kubernetes\\.io\" {} | grep ."
generate:
command:
- argocd-vault-plugin
- generate
- ./
lockRepo: false
argocd-vault-plugin-helm:
allowConcurrency: true
discover:
find:
command:
- sh
- "-c"
- "find . -name 'Chart.yaml' && find . -name 'values.yaml'"
generate:
command:
- sh
- "-c"
- |
helm template $argocd_APP_NAME -n $ARGOCD_APP_NAMESPACE ${argocd_ENV_HELM_ARGS} . |
argocd-vault-plugin generate -
lockRepo: false
- Modify the configuration of the
repo-server
- Add volumes
- Add a new initContainer for copying the plugin binary
- Add an offline environment prefix if necessary
- Mount the configurations required by
argocd-vault-plugin
as environment variables - Mount the related plugin directories and configurations
- Add a new container
- The image here should be the same as the
repo-server
image - Additionally, modify the ConfigMap to add plugin configurations
Administrator Configures Sensitive Information¶
Before creating a GitOps application, the administrator needs to set up the sensitive data in advance.
For example, using Vault:
Using Secret:
apiVersion: v1
kind: Secret
metadata:
name: test-secret
namespace: default
data:
password: dGVzdC1wYXNz
username: dGVzdC1wd2Q=
type: Opaque
Modify Manifest Files in the Git Repository¶
Modify the manifest files in the Git repository, replacing sensitive information with placeholders. If the backend storage is Vault, an example:
apiVersion: v1
kind: Secret
metadata:
name: test-secret-vault
annotations:
avp.kubernetes.io/path: "secret/data/test-secret"
avp.kubernetes.io/secret-version: "2"
stringData:
password: <password>
username: <username>
Explanation:
- Add the corresponding annotations for the plugin to recognize. Annotation explanations:
avp.kubernetes.io/path
: Specifies the path to the sensitive information. If the backend storage is Vault, this path is in Vault. The value can be obtained viavault kv get secret/test-secret
. Add/data
aftersecret
.avp.kubernetes.io/secret-version
: Specifies the version of the sensitive information. If the backend storage supports version management, you can specify the version number.
- In
stringData
,password
andusername
are the keys for the sensitive information,<password>
and<username>
are placeholders. Thepassword
in<password>
is the specified key in Vault.
If the backend storage is Kubernetes Secret, an example:
apiVersion: v1
kind: Secret
metadata:
name: test-secret-k8s
annotations:
avp.kubernetes.io/path: "default/test-secret"
stringData:
password: <password>
username: <username>
Explanation:
- Kubernetes Secret does not support version management, so
avp.kubernetes.io/secret-version
is invalid. - The value in
avp.kubernetes.io/path
is the namespace and name of the secret. For example,default/test-secret
means thetest-secret
secret in thedefault
namespace. This secret is usually created by the administrator and deployed in a namespace with specific permissions, not stored in the Git repository. - The placeholders in
stringData
are the same as the placeholders for Vault.
Supported annotations are as follows:
Annotation | Description |
---|---|
avp.kubernetes.io/path | The path in Vault |
avp.kubernetes.io/ignore | If true, rendering is ignored |
avp.kubernetes.io/kv-version | The version of the KV storage engine |
avp.kubernetes.io/secret-version | Specifies the version of the value |
avp.kubernetes.io/remove-missing | For secrets and configmaps, ignore errors if keys are missing in Vault |
Placeholders also support functions, such as <password | base64>
to encode the value of password
in base64.
View Deployment Results¶
$ kubectl get secret test-secret-k8s -o yaml | yq eval ".data" -
> password: dGVzdC1wYXNz
username: dGVzdC1wd2Q=
You can see that the sensitive information in the secret has been replaced with actual values.
Sensitive Information Update¶
Note
If sensitive information changes, ArgoCD cannot detect it (even if the created Application is set to auto-sync). You need to go to the ArgoCD backend page, click the hard-refresh button, and then click the sync button to synchronize.
Relying on the Project's or Tool's Encryption/Decryption Capabilities¶
There are many implementations of this method. The biggest advantage is that they are not bound to ArgoCD, but can still be deployed using the GitOps approach. This article uses sealed-secrets as an example.
Sealed-secrets includes two tools:
- A controller for encryption, decryption, and creating secrets
- A client tool
kubeseal
Installation¶
-
Install the Controller
-
Install the Client Tool
Usage¶
The administrator generates an encrypted CR file:
# Use the command line tool to encrypt the secret
kubectl create secret generic mysecret -n argo-cd --dry-run=client --from-literal=username=xxxx -o yaml | \
kubeseal \
--controller-name=sealed-secrets-controller \ # Note the name and namespace
--controller-namespace=kube-system \
--format yaml > mysealedsecret.yaml
The generated file is as follows:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: mysecret
namespace: argo-cd
spec:
encryptedData:
username: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq..... # (1)!
template: # (2)!
type: kubernetes.io/dockerconfigjson
immutable: true
metadata:
labels:
"xxxx":"xxxx"
annotations:
"xxxx":"xxxx"
Sure, here's the translation into English:
- Ciphertext generated by kubeseal
- In addition, you can specify a template for generating secrets, similar to a pod template.
The data is encrypted using asymmetric encryption, and only the controller can decrypt it. Therefore, you can safely store the encrypted data in a Git repository.
When ArgoCD synchronizes, the sealed controller will generate the secret based on the SealedSecret
. The data in the final generated secret will be decrypted, so when sensitive information changes, you only need to update the SealedSecret
in the Git repository to achieve automatic synchronization.