在 Argo CD 中使用 Sops 增强 GitOps 安全性

zaqi
发布于 2022-8-25 11:25
浏览
0收藏

作者 |阳明
来源 | k8s技术圈(ID:kube100)

GitOps 的核心理念就是一切皆代码,意味着用户名、密码、证书、token 等敏感信息也要存储到 Git 仓库中,这显然是非常不安全的,不过我们可以通过 Vault、Keycloak、SOPS 等 Secret 管理工具来解决,最简单的方式是使用 SOPS,因为它可以使用 PGP 密钥来加密内容,如果你使用 kustomize 则还可以在集群内使用相同的 PGP 密钥解密 Secret。ArgoCD 虽然没有内置的 Secret 管理,但是却可以与任何 Secret 管理工具集成。

sops 是一款开源的加密文件的编辑器,支持 YAML、JSON、ENV、INI 和 BINARY 格式,同时可以用 AWS KMS、GCP KMS、Azure Key Vault、age 和 PGP 进行加密,官方推荐使用 age 来进行加解密,所以我们这里使用 age。age[1] 是一个简单、现代且安全的加密工具(和 Go 库)。

SOPS 与 AGE
首先需要安装 age 工具,可以直接从 Release 页面[2] 下载对应的安装包:

$ wget https://github.91chi.fun/https://github.com//FiloSottile/age/releases/download/v1.0.0/age-v1.0.0-linux-amd64.tar.gz
$ tar -xvf age-v1.0.0-linux-amd64.tar.gz
$ mv age/age /usr/local/bin
$ mv age/age-keygen /usr/local/bin
$ age --version
v1.0.0

然后安装 sops,同样直接从 Release 页面[3]下载对应的安装包:

$ wget https://github.91chi.fun/https://github.com//mozilla/sops/releases/download/v3.7.3/sops-v3.7.3.linux.amd64
$ mv sops-v3.7.3.linux.amd64 sops && chmod +x sops
$ mv sops /usr/local/bin

通过下述命令来查看安装是否成功:

$ sops --version
sops 3.7.3 (latest)

我们先创建一个简单的 Secret 来测试下使用 sops 进行加密:

$ kubectl create secret generic app-secret \
--from-literal=token=SOPS-AGE-TOKEN-TEST \
--dry-run=client \
-o yaml > secret.yaml

生成的 secret 资源清单文件如下所示:

apiVersion: v1
data:
  token: U09QUy1BR0UtVE9LRU4tVEVTVA==
kind: Secret
metadata:
  name: app-secret

接下来我们使用 age-keygen 命令生成加密的公钥和私钥,可以用如下命令将私钥保存到一个 key.txt 文件中:

$ age-keygen -o key.txt
Public key: age1wvdahagxfgqc53awmmgz52njdk2zm6vkw760tc368gstsypgvusqy7zvtt

然后我们可以使用上面的私钥来加密生成的 secret.yaml 文件:

$ age -o secret.enc.yaml -r age1wvdahagxfgqc53awmmgz52njdk2zm6vkw760tc368gstsypgvusqy7zvtt secret.yaml

加密后生成的 secret.enc.yaml 文件内容如下所示,显示乱码:

age-encryption.org/v1
-> X25519 x8bynJlv6Sz03ks71Jvn92RZQ6IlTj9B8zgU3lJsOFQ
sqrP+zq9nw93mafbBjuc5F6GWIjjzdYtQV6DtV9KiTw
---
6W1cpc//EBqXkF983yVBUBExiYEx/7Y0wEvHjPlmWNg
��NY0Y���^�/A��i��.�N���=�ԦPb�ļ���҈v?-<t�t�
Ӓ/$�Zs�۸�gKz�U���Kf�aϛ��        �+
��Y��j��g��IDP>��>g��2m9R�a��qfC�����߻q�n���@�O�'g�P6

同样我们还可以对该加密文件进行解密:

$ age --decrypt -i key.txt secret.enc.yaml
apiVersion: v1
data:
  token: U09QUy1BR0UtVE9LRU4tVEVTVA==
kind: Secret
metadata:
  creationTimestamp: null
  name: app-secret

同样对于 sops 来说也是支持和 age 进行集成的,我们可以使用下面的 sops 命令来对 secret.yaml 文件进行加密:

$ sops --encrypt --age age1wvdahagxfgqc53awmmgz52njdk2zm6vkw760tc368gstsypgvusqy7zvtt secret.yaml > secret.enc.yaml

加密后的文件内容如下所示:

apiVersion: ENC[AES256_GCM,data:e7E=,iv:Pfwj3/74CygAHtWlt9tsnexrH74nfa0teNZzknzfGwA=,tag:U2yJjnalFOuGe8rQK+c7Ng==,type:str]
data:
  token: ENC[AES256_GCM,data:8kwq4GqETBJjHbrtS5S3AqJIPcq3Nmf8Gg1muQ==,iv:l7O1UnjzcXOkc48EVvbqGPVv0RQxxNX3aIzCU5B/7/o=,tag:XuNw/N7XDLU17BOQkjn5Rg==,type:str]
kind: ENC[AES256_GCM,data:U4hGrF9C,iv:CloG5/RgWHXN/lNGKHGNxeZJXj8kfjw8OmFAxQblUgY=,tag:gq0wKDUa50odvRNcak+Vig==,type:str]
metadata:
  creationTimestamp: null
  name: ENC[AES256_GCM,data:PEhXQdE3/vj+bA==,iv:dkWCj5cAqc4IeB2lXdxC7otmCmFn3vGe5s2Ij3uh8ag=,tag:bbUaA1dqXnrLaTnCPVnxpQ==,type:str]
sops:
  kms: []
  gcp_kms: []
  azure_kv: []
  hc_vault: []
  age:
    - recipient: age1wvdahagxfgqc53awmmgz52njdk2zm6vkw760tc368gstsypgvusqy7zvtt
      enc: |
        -----BEGIN AGE ENCRYPTED FILE-----
        YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwY3JKdmxVM2lFb0NpTjVj
        cGdyc3d1QXBvN3RVdzAwMVNVMXZrS3pLYm1zCnZqTGZ5TzVBL2VSay80RkFFTlBC
        R1NtZmxoYVlTd2RyWUl1c24wME83K00KLS0tIENlczRGb1QzRCtCeWxpMU9PTXN5
        QnpyckI1bmZSSFliMUgyUDlkd0tWalUKK4vKJwcGLsZn5wT9WHh5tvNOEGScOlAb
        Fx118rutRK4nVpfIhAvhfS9TDqvhaQ2wFVv3N/a/BhkYpwTrE/cjmQ==
        -----END AGE ENCRYPTED FILE-----
  lastmodified: "2022-08-18T08:04:35Z"
  mac: ENC[AES256_GCM,data:/ujRqRKFR/5uqRBGAZzVIsdVR95In18zUrKuHFuJnHrrfRAt4WXzSUTBovIqOaGPQxXvY4jqkWnd7kqlO629CjK3SA6selEb8N6ytN5kGquGUqSYlOAjsnk575VtpMKXIr8jeaGkzJRmU6aEnbPa18kekw0FCX1aP6yubD8Ce2Y=,iv:/bRn1tk7iXplz4OGxqkUGD4UQRRtb5jUnICQyFnT4fg=,tag:kt9CzFye1OXsq+MKXTZeXA==,type:str]
  pgp:
    - created_at: "2022-08-18T08:04:35Z"
      enc: |-
        -----BEGIN PGP MESSAGE-----

        wcFMA0Eva10jiAHJAQ/+LgUsrJKoo95yCIxbMT1OPjnJhAK/LkIwY9EdHbJewphI
        CKwpDwvsrbdpjcmBkCt4sL4S30bPR3qdAjLxJCnGTJPZQzxjOEIzvJNAG5nC3zk/
        UVPAWj7nV26CCPMc+/j/GHGwMphoLviMr9et0adtaWILSP0yhMuH8LVzGa04WVEz
        AihT849sF/+WrUy4f7axI4Z2IH2mEepSqNZDQR9mmiu+nA9e+QZqsfazLJXRPsNd
        2hQn7qSGPZ10bzy9ccA5nO5r1oU2J+GEEMYujur/RL8y5oi3BCSvWc0udfuU0dka
        Nn77OA73zS8aziA9pj3D46wgeGYFfX7h2XKytSI15GGTAT7RmM6D2cB9xWzeQncy
        4TN0LDvcw/7SRjxY55iDyYHPLTNlMfajKwXoKfeQX5nd0rnZRCovYDoj2OrqZDff
        1N25EEWN6MSztZML0eE/k/p7RDBG9bJ6lntXNAXQJRjzhUYeHMnXLc9NCN5P3WdW
        Ny155SsGK6n9Ok1SdAolqlOFRKiO8AA+2jPVS7aDUrWktqPCa8hzf/Bm1ttBoYjw
        D5Xc5x3IcyZDIISqz/9cQYfiPusZohpGnfwoea5qhvXEY/wM5IwfLdTm8u78djho
        HMLFdFUzuprkHZlZlP3HfPbZi5wGpmiqAuYX+i40teOEaQNGhE7HKCJZkAVS0J3S
        UQHmBMxL1SL/JGAdSsuddB0liIIriENIxr14W04zeJ+pClxvnzxNYigOYM3Jk8wF
        w7zmhD3IvEpSLG0f4a/c486LpNryBBz6qzBZRYqnJ87PQQ==
        =K5dC
        -----END PGP MESSAGE-----
      fp: CCC4D0692165A88405EF1F579CC5737D5CCB9760
  unencrypted_suffix: _unencrypted
  version: 3.7.3

可以看到主要字段都被加密了。但是其他字段比如 kind 也被加密了,我们可以通过创建一个 .sops.yaml 文件来指定需要被加密的字段,如下所示:

# .sops.yaml
creation_rules:
  - encrypted_regex: "^(username|password|)$"
    age: "CCC4D0692165A88405EF1F579CC5737D5CCB9760"

这样的话则只会对 username 和 password 两个字段进行加密。

ArgoCD 集成 SOPS
现在我们可以使用 sops 来对私密的文件进行加解密了,前面示例中我们在 ArgoCD 中使用的 Helm Chart 方式来同步应用,比如我们会在 values 文件中提供一些比较私密的信息,直接明文提供存储到 Git 仓库上显然是非常不安全的,这个时候我们就可以使用 sops 来对这些 values 文件进行加密,当然在同步应用的时候自然就需要 ArgoCD 能够支持对手 SOPS 进行解密了,这里我们还需要使用到 helm-secrets[4] 这个 Helm 插件。

接下来我们需要让 Argo CD 来支持 SOPS,一般来说主要有两种方法:

  • 使用 helm 和 sops 创建自定义的 ArgoCD Docker 镜像,并使用自定义 Docker 镜像,但是 Argo CD 的每个新版本都需要更新该镜像。
  • 在 Argo CD 存储库服务器部署中添加一个初始化容器,以获取带有 sops 的 helm 插件,如此处所述,并在 Pod 中使用它。即使更新了 Argo CD 版本,也不需要更新插件,除非插件版本和 Argo CD 版本存在兼容性问题。
    为了简单我们这里使用第一种自定义镜像的方式,如下所示的 Dockerfile,它将 sops 和 helm-secrets 集成到 Argo CD 镜像中:
ARG ARGOCD_VERSION="v2.4.9"
FROM argoproj/argocd:$ARGOCD_VERSION
ARG SOPS_VERSION="3.7.3"
ARG VALS_VERSION="0.18.0"
ARG HELM_SECRETS_VERSION="3.15.0"
ARG KUBECTL_VERSION="1.24.3"
# In case wrapper scripts are used, HELM_SECRETS_HELM_PATH needs to be the path of the real helm binary
ENV HELM_SECRETS_HELM_PATH=/usr/local/bin/helm \
    HELM_PLUGINS="/home/argocd/.local/share/helm/plugins/" \
    HELM_SECRETS_VALUES_ALLOW_SYMLINKS=false \
    HELM_SECRETS_VALUES_ALLOW_ABSOLUTE_PATH=false \
    HELM_SECRETS_VALUES_ALLOW_PATH_TRAVERSAL=false
USER root
RUN apt-get update && \
    apt-get install -y \
      curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN curl -fsSL https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl \
    -o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl
# sops backend installation
RUN curl -fsSL https://github.com/mozilla/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux \
    -o /usr/local/bin/sops && chmod +x /usr/local/bin/sops
# vals backend installation
RUN curl -fsSL https://github.com/variantdev/vals/releases/download/v${VALS_VERSION}/vals_${VALS_VERSION}_linux_amd64.tar.gz \
    | tar xzf - -C /usr/local/bin/ vals \
    && chmod +x /usr/local/bin/vals
USER 999
RUN helm plugin install --version ${HELM_SECRETS_VERSION} https://github.com/jkroepke/helm-secrets

使用上面的 Dockerfile 重新构建镜像(cnych/argocd:v2.4.9)后,重新替换 argocd-repo-server 应用的镜像,其他组件不需要。

由于默认情况下 ArgoCD 只支持 http:// 和 https:// 作为远程 value 协议,所以我们需要将 helm-secrets 协议也添加到 argocd-cm 这个 ConfigMap 中去。

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/name: argocd-cm
    app.kubernetes.io/part-of: argocd
  name: argocd-cm
data:
  helm.valuesFileSchemes: >-
    secrets+gpg-import, secrets+gpg-import-kubernetes,
    secrets+age-import, secrets+age-import-kubernetes,
    secrets,
    https

接下来我们还需要配置 Argo CD 存储库服务器,使它可以访问私钥来解密加密的文件。这里使用前面 age-keygen 命令生成的私钥文件 key.txt 创建一个 Kubernetes Secret 对象:

$ kubectl create secret generic helm-secrets-private-keys --from-file=key.txt -n argocd

现在我们需要将该 Secret 以 Volume 的形式挂载到 argocd-repo-server 中去:

volumes:
  - name: helm-secrets-private-keys
    secret:
      secretName: helm-secrets-private-keys
# ......
  volumeMounts:
    - mountPath: /helm-secrets-private-keys/
      name: helm-secrets-private-keys
......

然后更新 argocd-repo-server 组件,更新完成后我们就可以创建如下所示的 Argo CD 应用来对加密文件进行解密了:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app
spec:
  source:
    helm:
      valueFiles:
        # Method 1: Mount the gpg key from a kubernetes secret as volume
        # secrets+gpg-import://<key-volume-mount>/<key-name>.asc?<relative/path/to/the/encrypted/secrets.yaml>
        # secrets+age-import://<key-volume-mount>/<key-name>.txt?<relative/path/to/the/encrypted/secrets.yaml>
        # Example Method 1: (Assumptions: key-volume-mount=/helm-secrets-private-keys, key-name=app, secret.yaml is in the root folder)
        - secrets+age-import:///helm-secrets-private-keys/key.txt?secrets.yaml

现在我们再次使用前面的 devops-demo 应用示例进行测试。 在 Argo CD 中使用 Sops 增强 GitOps 安全性-鸿蒙开发者社区

devops-demo deploy

我们使用 sops 将要部署的 my-values.yaml 文件进行加密:

$ sops --encrypt --age age1wvdahagxfgqc53awmmgz52njdk2zm6vkw760tc368gstsypgvusqy7zvtt my-values.yaml > my-values.enc.yaml

加密后的文件内容如下所示:

image:
  repository: ENC[AES256_GCM,data:ZDnA7yTAe2B+TbcQYhcs4yufLgXJWHzX7IUnYdOXtsqzfEo=,iv:4yn+RkQoTHNVW8Y5yDzHsY2hhpMo8yw6j/uj9g6AvMA=,tag:IPwFo2AfLT7yBwoKrvCLCg==,type:str]
  tag: ENC[AES256_GCM,data:koDRtD5NfWn03JJLAZnYYWLgwsJr/kSKtw8WHJoeSLD8Zco4M0Doqw==,iv:DbxefZ03J7dGRviRq2DQHhRkcBiBY5FgSh1lJwjwzEg=,tag:zc6ZL5ObSymSVH+caxUzpA==,type:str]
  pullPolicy: ENC[AES256_GCM,data:dJ+xl6llTN2NcEKL,iv:XhX3RGirpJI0Wc1Q/9ld2xWQYqE+6ZLL6laIXEI1unQ=,tag:dDwEUa7nTq9TOkYI2cE0Pg==,type:str]
ingress:
  enabled: ENC[AES256_GCM,data:eZB9GA==,iv:p12fWs14ATWke0IiMz0SpAb2rW+ViYcEpGRbOoNt9Uk=,tag:w371uI/KRESNP30eD9rrTQ==,type:bool]
  ingressClassName: ENC[AES256_GCM,data:WviAhbo=,iv:Vqx0R8RVWkGipZkR2HZfyOYyZdkc+1fhFEV7AdpI4t0=,tag:fv2hf94svXOQeqfjqXN4gg==,type:str]
  path: ENC[AES256_GCM,data:jg==,iv:cRm/OXlGEbNEHhAAm/JpPx5sP9GRmW1fyEAi+SZhfjY=,tag:QAJmQSQ5qWfjnzrm+MWLbQ==,type:str]
  hosts:
    - ENC[AES256_GCM,data:tb32cnmE1d2qnzzsmG2NzMVOPxkW,iv:RH57dgs0gIS28mB83YX+SQNFNjwoTfPa28YvZsCAJW4=,tag:J7SJXkZKPyydx8NvvCh22w==,type:str]
resources:
  limits:
    cpu: ENC[AES256_GCM,data:uys2,iv:UfAl2lP2wLzc0GkLcBs33vl4dQqLiXWmoyyucqovuVM=,tag:yXRpMIS11s0iqVZQpJ/Bdw==,type:str]
    memory: ENC[AES256_GCM,data:fBHSfog=,iv:lf6fTZfOPlhQVspm2BAl56ps8Q5W6Qz4tMT7A8Au9tA=,tag:XZqHEWEb2qBjWms/qTsAOQ==,type:str]
  requests:
    cpu: ENC[AES256_GCM,data:MDYW,iv:/j6A3oVQ4HILXFLVAr8Rjcq2CDdHrtPa70uySxQQeBI=,tag:EyWwWl0hFkTWzHFBXndFeA==,type:str]
    memory: ENC[AES256_GCM,data:qiwPiRI=,iv:m/oFxJrcdysf26ry7LEcL6IQRRqi5B8Zsjc/YJOkO7c=,tag:3brvdx+dFUN0VyJ6KO8biQ==,type:str]
sops:
  kms: []
  gcp_kms: []
  azure_kv: []
  hc_vault: []
  age:
    - recipient: age1wvdahagxfgqc53awmmgz52njdk2zm6vkw760tc368gstsypgvusqy7zvtt
      enc: |
        -----BEGIN AGE ENCRYPTED FILE-----
        YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyeHNNTWJhWHZHZERJNnlh
        L1FpMkdibERFM2ZtU2FFZ1VhMnYxVG90dUNBCmpTVEk3ODg4aWlhOEY3cDdMSWFW
        ZmdoaGtQT3NDU0E0bEZPQlJqNXNuamsKLS0tIHhEcm5memczQTNaVGZzUGNGQmsw
        cW1QSDd4dDdwZnI2ZzloM2tGRFJxTW8KMPU93lWiNMMaCfOUANmsv+kfi4R7NAzP
        nV2H2EyCTQGsNTeKCS/HkmiSD4/4RLui4Z6TbPf8ALpeGHDH8rVSoA==
        -----END AGE ENCRYPTED FILE-----
  lastmodified: "2022-08-18T08:18:05Z"
  mac: ENC[AES256_GCM,data:Z+KJTZRP6L2QEcSG6S43fvqWsROAwEVnQcVkpN/yU1Kk8x0PUXXZkdyJiykQ+7HRBNWJp1wKF1TAlqnrZyUSXx7zl5fZGbalgK8kRKzzTzdSsB+Cp4Km5uYNqWUh+RFtzRVOYwOU7fOsAxiHLFMjzaqLAE6+WsCY9xjfj67NymA=,iv:Kyckp64XCkmpbeSEiampXp47Qr9ZIJRZUWsLDhHIw/4=,tag:/eH5d5e9anLRoiCxdWPS/w==,type:str]
  pgp:
    - created_at: "2022-08-18T08:18:05Z"
      enc: |-
        -----BEGIN PGP MESSAGE-----

        wcFMA0Eva10jiAHJAQ/9HZJck5xCbIB43fYrmnrMokwQB5HPMMCpl8gw/U4Cz/RD
        zs6nlIXhO1U29rQT3s2G9IjfCS0ehfwA6lKGXAuK10jY9HJ7dVthWnKlNsCq35d/
        5ZKzKIT2mvK1h6+qYai86FwGyG436nAw198oNvC4d9E46PfBcx7PXP1lRFoOJI7V
        St81HwFTWOd88tkPyIfv2XW1bcvWo7Qz8YunNqGriD3SREwgkSlcyIL4neumWAru
        YGzTmwEXFjwcTIzel57fI42Qd61wq1p7CKw8njs1pOGucC3uX1b99f1BaeLdQl3C
        lJvYrP0SYKJ/JA2kPRkeJHDd39ywI8A/iNOW4nRFxbMoAHdEiwAUg2DOCfMwDgVu
        WQiQqTF+7AycdqjpXYjYZ7SI3al6jhcDA2KxvNsPNjT8F5yl3c9MIwMdo/NRoc6G
        XNGXqbR+8kChFQiVKCUopbCqHtFaVVV6Ldhk3fB76ht3vgJx9XFR8+KYFLHAezIO
        VdzzWqVPv72lO3CkyqHfoL8FwxjNI9KAQkU1T3ETv5YJw7mUWWvdMVee9SVf8Qa1
        m3JJGqcRd9kyH/u8tMKsrgfG1/KVeyx1gStlO3ioHlCyjsNBAUZ2QIsFa7gxUmQL
        HqgCIqGC/SjFv1+5sHF807sYBBWfARQZRTum/Pg3FHpRiVhNPcvEUPIZjQhT79fS
        UQHw1EvK5Wj4Ea3/3jNt9bim+pJrxCoUAKByU8lyjL7vOsogiM7sgp50t54oI/3V
        G0hvOZNvWV/V0YLqXoTVEru/rqLUKzHunl9psutAXlUOkA==
        =4l27
        -----END PGP MESSAGE-----
      fp: CCC4D0692165A88405EF1F579CC5737D5CCB9760
  unencrypted_suffix: _unencrypted
  version: 3.7.3

现在我们需要将该文件重新提交到 Git 仓库中去,接着我们要重新创建 Application 应用,对应的资源清单文件如下所示:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: devops-demo
  namespace: argocd
spec:
  destination:
    namespace: default
    server: "https://kubernetes.default.svc"
  project: demo
  source:
    path: helm # 从 Helm 存储库创建应用程序时,chart 必须指定 path
    repoURL: "http://git.k8s.local/course/devops-demo-deploy.git"
    targetRevision: HEAD
    helm:
      parameters:
        - name: replicaCount
          value: "2"
      valueFiles:
        - secrets+age-import:///helm-secrets-private-keys/key.txt?my-values.enc.yaml

其中核心是 valuesFiles 配置的 secrets+age-import:///helm-secrets-private-keys/key.txt?my-values.enc.yaml,表示导入 /helm-secrets-private-keys/key.txt 文件中的私钥来对 my-values.enc.yaml 文件进行解密。

重新创建上面的对象后,同步应用后正常可以同步成功。在 Argo CD 中使用 Sops 增强 GitOps 安全性-鸿蒙开发者社区参考资料
[1]age: https://github.com/FiloSottile/age/

[2]Age Release 页面: https://github.com/FiloSottile/age/releases

[3]SOPS Release 页面: https://github.com/mozilla/sops/releases/download/v3.7.3/sops-v3.7.3.linux.amd64

[4]helm-secrets: https://github.com/jkroepke/helm-secrets

分类
标签
已于2022-8-25 11:25:49修改
收藏
回复
举报
回复
    相关推荐