Ritolabo
  1. Home
  2. Kubernetes
  3. ArgoCD
  4. Argo CD で Secret を暗号化して反映する

Argo CD で Secret を暗号化して反映する

  • 公開日
  • カテゴリ:ArgoCD
  • タグ:Kubernetes,ArgoCD,学習メモ
Argo CD で Secret を暗号化して反映する

パスワードのような秘密の値は、Secret の YAML をそのまま Git に置くと平文で残ってしまう。かといって Secret だけ kubectl create secret で手で入れると、Git にないものがクラスタにある状態になり、クラスタの中身を Git で決めるという前提が崩れる。

この記事では、Secret の値だけを sops で暗号化して Git に置き、Argo CD が Git を読むときに復号して、クラスタに平文の Secret ができる状態にする。

contents

  1. 前提
  2. Secret の置き方は 2 系統ある
    1. sops と age
  3. 鍵を作る
  4. Secret を暗号化して Git に置く
    1. .sops.yaml
    2. 平文を書いて暗号化する
    3. 平文を消す
  5. Argo CD に復号させる
    1. 仕組み
    2. 秘密鍵を Secret にする
    3. プラグインの設定を ConfigMap にする
    4. repo-server に sidecar を追加する
  6. push して Secret ができる
    1. Deployment に Secret を読ませる
    2. push する
  7. 値を変える
  8. 鍵・暗号文・平文の全体像
  9. 用語メモ
  10. 参考 URL

前提

次の状態から始める。

  • GitHub のリポジトリの web/ ディレクトリに、nginx の Deployment・ConfigMap・Service・Ingress の YAML がある
  • Argo CD の Application web が、その web/ を default namespace に入れている
  • Application には automated・selfHeal: true・prune: true が設定してあり、Git に push すれば人が操作しなくても反映される
  • バージョンは Argo CD v3.5.5、sops 3.13.3、age 1.2.1
$ kubectl get applications -n argocd
NAME   SYNC STATUS   HEALTH STATUS
web    Synced        Healthy

$ kubectl get secret -n default
No resources found in default namespace.

この記事で使う Argo CD の用語。

言葉意味
SyncGit の YAML をクラスタに apply すること。自分で kubectl apply を実行する代わりに、Argo CD が実行する
argocd-repo-serverArgo CD の中で、Git からファイルを取得して YAML を取り出すプログラム(argocd namespace の Pod)。クラスタと比べて apply するのは別の argocd-application-controller
Argo CD の画面ブラウザで開く管理画面。kubectl port-forward svc/argocd-server -n argocd 8080:443 を実行して https://localhost:8080 を開く

Secret の置き方は 2 系統ある

Argo CD で Secret を扱う方法は 2 系統あり、公式ドキュメントはどちらを薦めているかを先に確認しておく。

方式何をするか例
クラスタ側で Secret を作るGit には「どの Secret が必要か」だけを置き、クラスタの中のプログラムが値を取得して Secret を作る。Argo CD は値に触れないExternal Secrets Operator、Sealed Secrets
Argo CD が YAML を作るときに復号する暗号化した YAML を Git に置き、Argo CD のプラグインが復号して平文の YAML を作るsops + プラグイン、argocd-vault-plugin

公式ドキュメントは前者を強く推奨し、後者には強く注意を促している。理由は、Argo CD が平文を持つことになる(復号した YAML は Argo CD の Redis に平文で置かれる)ことと、Secret の更新が Sync と一体になることの 2 つ。

この記事では、それでも後者の sops を使う。鍵・暗号文・復号の場所が全部手元で見えて、GitOps で秘密の値をどう扱うかの仕組みがわかること、学習用のクラスタなので上のリスクが問題にならないことが理由。実務でこの方式を使うなら、Argo CD へのアクセスを絞る前提になる。

sops と age

この記事で使う暗号化のツールは 2 つで、暗号化そのものを sops が、その鍵を age が受け持つ。

名前何をするかこの記事での役割
sopsYAML や JSON の「値だけ」を暗号化するコマンド。kind や name は平文のまま、password の値だけが ENC[...] になる。CNCF のプロジェクト手元で暗号化し、Argo CD の中で復号する
ageファイル暗号化のための鍵の方式とコマンド。公開鍵(age1...)で暗号化し、秘密鍵(AGE-SECRET-KEY-1...)で復号するsops が使う鍵。手元の学習ではいちばん手軽

sops は AWS KMS や GCP KMS のようなクラウドの鍵管理サービスも使える。その場合は秘密鍵のファイルをどこにも置かず、「クラウドに復号を頼む権限があるか」で復号できるかが決まる。

鍵を作る

sops と age を入れて、age の鍵を 1 組作る。

$ brew install sops age

鍵の置き場所は、macOS の sops が秘密鍵を探す既定の場所 ~/Library/Application Support/sops/age/keys.txt にする(Linux では ~/.config/sops/age/keys.txt)。ここに置けば、復号するときに鍵の場所を指定しなくてよい。

$ mkdir -p ~/Library/Application\ Support/sops/age
$ age-keygen -o ~/Library/Application\ Support/sops/age/keys.txt
# => Public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

$ cat ~/Library/Application\ Support/sops/age/keys.txt
# created: 2026-10-18T00:50:00Z
# public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AGE-SECRET-KEY-1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

1 つのファイルに公開鍵と秘密鍵が両方入っている。それぞれの置き場所と使う人は次のとおり。

鍵置く場所誰が使うか
公開鍵 age1...Git リポジトリの .sops.yaml。見られてよい手元の sops が暗号化に使う
秘密鍵 AGE-SECRET-KEY-1...手元の keys.txt と、クラスタの argocd namespace の Secret。Git には置かない手元の sops(復号の確認)と、Argo CD の中の sops(復号)

Secret を暗号化して Git に置く

.sops.yaml

sops は、カレントディレクトリから上に向かって .sops.yaml を探し、そこに書いてある鍵で暗号化する。リポジトリのルートに置く。

creation_rules:
  - encrypted_regex: ^(data|stringData)$
    age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • encrypted_regex: 暗号化するキーの条件。data と stringData の下だけを暗号化し、kind や metadata.name は平文のまま残す
  • age: 暗号化に使う公開鍵

平文を書いて暗号化する

平文の Secret を web/secret.yaml に書く。

apiVersion: v1
kind: Secret
metadata:
  name: web-secret
type: Opaque
stringData:
  password: hello-sops

sops -e(encrypt)で暗号化したものを web/secret.enc.yaml に書き出す。

$ sops -e web/secret.yaml > web/secret.enc.yaml
$ cat web/secret.enc.yaml
apiVersion: v1
kind: Secret
metadata:
    name: web-secret
type: Opaque
stringData:
    password: ENC[AES256_GCM,data:MgV/CwnAS4J0uw==,iv:6DK/W6FlcsX158eix4XwMp6bQiHO2uO1FxV64/NOpbQ=,tag:j3fudPJ3RR87jbQFMaQu0g==,type:str]
sops:
    age:
        - enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            (省略)
            -----END AGE ENCRYPTED FILE-----
          recipient: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    encrypted_regex: ^(data|stringData)$
    lastmodified: "2026-10-18T00:57:35Z"
    mac: ENC[AES256_GCM,data:C/KcfLHc7aR7...,type:str]
    version: 3.13.3
  • password の値だけが ENC[...] になり、kind と name はそのまま残っている
  • 末尾の sops: は sops が追加したもので、どの公開鍵で暗号化したか(recipient)や改ざん検知用の値(mac)が入っている。復号に使うので消さない

手元の秘密鍵で戻せることを sops -d(decrypt)で確認する。

$ sops -d web/secret.enc.yaml
apiVersion: v1
kind: Secret
metadata:
    name: web-secret
type: Opaque
stringData:
    password: hello-sops

平文を消す

Git に入れるのは .sops.yaml と web/secret.enc.yaml で、平文の web/secret.yaml は削除する。

$ rm web/secret.yaml
$ git status --short
?? .sops.yaml
?? web/secret.enc.yaml

この時点ではまだ push しない。push しても Argo CD は secret.enc.yaml を復号できず、apply に失敗する。復号のやり方を Argo CD に教えるのが次の節。

Argo CD に復号させる

仕組み

Argo CD で Git を読んで YAML を取り出すのは argocd-repo-server。そのままではディレクトリの YAML をそのまま読む。argocd-repo-server には Config Management Plugin(CMP)という仕組みがあり、「このディレクトリの YAML は、このコマンドの出力を使う」と差し替えられる。プラグインは、argocd-repo-server の Pod に同居する 2 つ目のコンテナ(sidecar)として動く。

今回のプラグイン sops が決めるのは 2 つ。

設定内容
discoverディレクトリに *.enc.yaml があれば、このプラグインの担当にする
generateディレクトリの *.yaml を順に読み、*.enc.yaml は sops -d で復号、それ以外は cat で出力する。その出力を Argo CD に渡す

入れるものは 3 つ。

ここまで、クラスタの中身は Git に置いて Argo CD に反映させてきたが、この 3 つは Git に置かず、kubectl のコマンドで直接クラスタに入れる。本来の GitOps の形から外れている点なので、理由を先に書いておく。

  • 書き換える相手が Argo CD 自身(argocd-repo-server)で、この記事の環境では Argo CD 本体を kubectl apply で入れたままにしていて、Argo CD 自身を指す Application を作っていない。Git に YAML を置いても、それを反映する Application がない
  • 実務では、Argo CD 自身も Argo CD に管理させることが多い。公式の install.yaml に sidecar の追加を Kustomize で重ねたものを Git に置き、それを指す Application を作る。そうすれば、以下の kubectl は鍵の Secret を作る 1 回を除いて不要になる
  • この記事では、Argo CD 本体の Git 管理と Kustomize が別の話になるため、そこには踏み込まず kubectl で入れる。学習用の手順であり、実務の形ではない
入れるもの何か
Secret sops-age秘密鍵。sidecar にファイルとしてマウントする
ConfigMap sops-pluginプラグインの設定(plugin.yaml)
argocd-repo-server の変更sidecar コンテナ sops と、上の 2 つをマウントする volume を追加する

秘密鍵を Secret にする

$ kubectl create secret generic sops-age -n argocd --from-file=keys.txt="$HOME/Library/Application Support/sops/age/keys.txt"
# => secret/sops-age created

この Secret は、Argo CD 自身が復号に使う鍵なので Git には置けない。鍵を Git に置いたら暗号化の意味がなくなる。この 1 つだけは手で入れる(実務では、クラスタを構築する手順に組み込み、鍵の元はパスワード管理ツールやクラウドの秘密保管サービスに置く)。

プラグインの設定を ConfigMap にする

apiVersion: v1
kind: ConfigMap
metadata:
  name: sops-plugin
  namespace: argocd
data:
  plugin.yaml: |
    apiVersion: argoproj.io/v1alpha1
    kind: ConfigManagementPlugin
    metadata:
      name: sops
    spec:
      discover:
        fileName: "*.enc.yaml"
      generate:
        command: [sh, -c]
        args:
          - |
            set -e
            for f in *.yaml; do
              case "$f" in
                *.enc.yaml) sops -d "$f" ;;
                *) cat "$f" ;;
              esac
              printf '\n---\n'
            done

ConfigManagementPlugin は Kubernetes のリソースではなく、sidecar の中に置く設定ファイルの書式。apply するのは、それを包んだ ConfigMap のほう。

$ kubectl apply -f plugin-configmap.yaml
# => configmap/sops-plugin created

repo-server に sidecar を追加する

Deployment argocd-repo-server に、sidecar コンテナと volume を追加する patch。

spec:
  template:
    spec:
      containers:
        - name: sops
          image: ghcr.io/getsops/sops:v3.13.3-alpine
          command: [/var/run/argocd/argocd-cmp-server]
          securityContext:
            runAsNonRoot: true
            runAsUser: 999
          env:
            - name: SOPS_AGE_KEY_FILE
              value: /sops-age/keys.txt
          volumeMounts:
            - mountPath: /var/run/argocd
              name: var-files
            - mountPath: /home/argocd/cmp-server/plugins
              name: plugins
            - mountPath: /home/argocd/cmp-server/config/plugin.yaml
              subPath: plugin.yaml
              name: sops-plugin
            - mountPath: /tmp
              name: sops-tmp
            - mountPath: /sops-age
              name: sops-age
              readOnly: true
      volumes:
        - name: sops-plugin
          configMap:
            name: sops-plugin
        - name: sops-tmp
          emptyDir: {}
        - name: sops-age
          secret:
            secretName: sops-age
  • image: sops 公式の image(alpine 版)。sops を入れる以外に image を自作しない
  • command: Argo CD の決まり。argocd-cmp-server は Argo CD がプラグインと通信するための小さなサーバーで、repo-server の volume var-files の中にある。sidecar はこれを起動し、generate を頼まれたときにスクリプトを実行する
  • runAsUser: 999 と、var-files・plugins・/tmp のマウントは公式ドキュメントの指定どおり。plugins は repo-server と共有していて、ここにプラグインのソケットができる
  • SOPS_AGE_KEY_FILE: sops が秘密鍵を読む場所。Secret sops-age を /sops-age/keys.txt にマウントして、そこを指す

kubectl patch は、クラスタにあるリソースの一部だけを書き換えるコマンド。kubectl apply のように定義全体を渡すのではなく、追加したい部分だけを書いた YAML(--patch-file)を既存の定義に重ねる。containers と volumes は名前で突き合わされるので、既存の argocd-repo-server コンテナは残り、sops コンテナと volume が追加される。

$ kubectl patch deployment argocd-repo-server -n argocd --patch-file repo-server-sidecar.yaml
# => deployment.apps/argocd-repo-server patched

$ kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-repo-server
NAME                                  READY   STATUS    RESTARTS   AGE
argocd-repo-server-84d54f897c-jqmzj   2/2     Running   0          27s

$ kubectl logs -n argocd deploy/argocd-repo-server -c sops
{"level":"info","msg":"ArgoCD ConfigManagementPlugin Server is starting", ... "version":"v3.5.5"}
{"level":"info","msg":"argocd-cmp-server v3.5.5 serving on /home/argocd/cmp-server/plugins/sops.sock", ...}

Pod のコンテナが argocd-repo-server と sops の 2 つになり(2/2)、sidecar のログにプラグイン sops が登録されたことが出る。この時点では web/ に *.enc.yaml がまだないので、Application web は何も変わらない。

push して Secret ができる

Deployment に Secret を読ませる

Secret ができただけでは何も使わないので、Deployment の Pod にファイルとして置く。web/deployment.yaml の volumeMounts と volumes に 1 つずつ追加する。

          volumeMounts:
            - name: html
              mountPath: /usr/share/nginx/html
            - name: secret
              mountPath: /etc/web-secret
              readOnly: true
      volumes:
        - name: html
          configMap:
            name: web-html
        - name: secret
          secret:
            secretName: web-secret

Secret web-secret の password が、コンテナの /etc/web-secret/password というファイルになる。

push する

git status に平文の web/secret.yaml がないことを確認してから push する。Application web の YAML は変えない。

$ git status --short
 M web/deployment.yaml
?? .sops.yaml
?? web/secret.enc.yaml

$ git add .sops.yaml web
$ git commit -m "Add encrypted secret"
$ git push

Argo CD は GitHub からファイルを定期的に取得する。既定では 2〜3 分に 1 回で、設定で変えられる。(すぐに反映したいときは、Argo CD の画面の REFRESH ボタンを押す)

push 後に Argo CD の中で起きることは次のとおり。

  1. argocd-repo-server が GitHub から web/ を取得する
  2. ディレクトリの種類を判定する。Argo CD はプラグインの discover を最初に試すので、web/ に secret.enc.yaml があることでプラグイン sops の担当になる
  3. sidecar の中で generate が動き、secret.enc.yaml だけを sops -d で復号して、5 ファイル分の YAML を argocd-application-controller に渡す
  4. argocd-application-controller がクラスタと比べて apply する。Secret web-secret ができ、Deployment が変わったので Pod が入れ替わる
$ kubectl get applications -n argocd
NAME   SYNC STATUS   HEALTH STATUS
web    Synced        Healthy

$ kubectl get secret,pods -n default
NAME                TYPE     DATA   AGE
secret/web-secret   Opaque   1      68s

NAME                       READY   STATUS    RESTARTS   AGE
pod/web-7dd4f56c74-8zwh6   1/1     Running   0          68s

クラスタの Secret には平文(base64)で入っていて、Pod の中からも読める。

$ kubectl get secret web-secret -n default -o jsonpath='{.data.password}' | base64 -d; echo
# => hello-sops

$ kubectl exec deploy/web -n default -- cat /etc/web-secret/password; echo
# => hello-sops

GitHub にあるのは ENC[...]、クラスタにあるのは hello-sops。間で復号したのは argocd-repo-server の sidecar。

Argo CD の画面で Application web を開くと、Secret web-secret が追加されている。

Argo CD の画面。Application web の下に Secret web-secret が増えている

Secret を開いて LIVE MANIFEST を見ると、data の値は ++++++++ に伏せられている。Argo CD は Secret の値を画面に表示しない。

Argo CD の画面で Secret web-secret の LIVE MANIFEST を開いたところ。password の値が ++++++++ で伏せられている

値を変える

暗号化したファイルは、エディタで ENC[...] の部分を書き換えると壊れる。値を変えるときは sops に任せる。sops set は、復号・値の差し替え・再暗号化を 1 回で行う。

$ sops set web/secret.enc.yaml '["stringData"]["password"]' '"hello-again"'
$ sops -d web/secret.enc.yaml | grep password
    password: hello-again

$ git diff --stat
 web/secret.enc.yaml | 6 +++---
$ git commit -am "Change password"
$ git push

git diff に出るのは ENC[...]・mac・lastmodified の変化だけで、新しい値は Git のどこにも出ない。(エディタで直したいときは sops edit web/secret.enc.yaml を使う。復号した内容がエディタで開き、保存すると再暗号化される)

push が反映されると、クラスタの Secret が新しい値になる。

$ kubectl get secret web-secret -n default -o jsonpath='{.data.password}' | base64 -d; echo
# => hello-again

Deployment の template は変わっていないので Pod は入れ替わらず、Pod の中のファイル /etc/web-secret/password は、kubelet が差し替えるまで少し遅れて(1 分ほど)新しい値になる。

鍵・暗号文・平文の全体像

ものどこにあるか誰が読むか
公開鍵 age1...Git の .sops.yaml手元の sops(暗号化するとき)
秘密鍵 AGE-SECRET-KEY-1...手元の keys.txt、クラスタの Secret sops-age(argocd namespace)手元の sops(-d、set)、sidecar の sops(Argo CD の復号)
暗号文 ENC[...]Git の web/secret.enc.yamlsidecar の sops
平文 hello-againクラスタの Secret web-secret(default namespace)、Pod の /etc/web-secret/password。Argo CD の Redis にも置かれる(公式ドキュメントが注意している点)nginx の Pod

自分が git push したあと、Argo CD が Secret を作った状態。(図の角が二重の箱は動くプログラム、それ以外の箱は etcd に保存されたデータ)

図を描画しています…

用語メモ

用語意味
sopsYAML や JSON の値だけを暗号化するコマンド。Secrets OPerationS の略
ageファイル暗号化のための鍵の方式とコマンド。公開鍵で暗号化し、秘密鍵で復号する
Config Management PluginArgo CD の argocd-repo-server に、Git から YAML を作る処理を追加する仕組み。sidecar コンテナとして動く
sidecar同じ Pod に同居させる 2 つ目のコンテナ。主役のコンテナを変えずに機能を追加するときに使う

まとめ

  • Secret の値だけを sops で暗号化すれば、kind や name は読める状態のまま Git に置ける。暗号化は公開鍵、復号は秘密鍵
  • Argo CD に復号させるには、argocd-repo-server に sops の sidecar を Config Management Plugin として追加し、秘密鍵を Secret としてマウントする。discover で担当するディレクトリを決め、generate で復号済みの YAML を作る
  • Argo CD の公式ドキュメントは、クラスタ側で Secret を作る方式(External Secrets Operator、Sealed Secrets)を推奨している。Argo CD の中で復号する方式は、Argo CD が平文を持つことを理解したうえで使う

参考 URL


[Prev] Argo CD で Kubernetes 上のアプリを公開する

Author

rito

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級