sopsとageを使ってYAMLファイル内の機密情報だけを暗号化

はじめに

fujiwaraさんsops+ageのスクラップブックをみて、自分でも調べてみたメモです。

私が想定している使い方は、サーバー上で人間が関与せずに、設定ファイルを復号して参照するというものです。 復号に必要な情報はサーバー上にそろっているので、サーバーに侵入されてファイルを参照されると復号されてしまいますが、それは許容するものとします。

sopsとageについて

age

先にageについてのメモ。

ageの作者FiloSottile (Filippo Valsorda)さんは元Goチームメンバで現在もGoのcryptoパッケージのメンテナをされている暗号の専門家です。

age and Authenticated Encryptionによるとageはファイルの暗号化に特化したツールとして作ったそうです。GnuPGを置き換えるつもりではないが、GnuPGがファイルの暗号化に適していないので、そのギャップを埋めるために作ったとのこと。

READMEによるとFilippoさんはアゲと発音されているそうです。

sops

sopsはREADMEによるとSecret OPerationSの略です。安全な運用をサポートするためのツールということでしょうか。

SOPS is an editor of encrypted files that supports YAML, JSON, ENV, INI and BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault, age, and PGP.という説明文がありました。

「6. Motivation」の項によると元はMozillaで開発していたようです。

インストール

sopsもageもリリースページにLinux amd64のスタティックリンクのバイナリがあります。それをダウンロードしてPATHの通ったディレクトリに置けばOKです。

準備

ageでの鍵作成

sopsのREADMEの2.3 Encrypting using ageにsopsがageのキーを探すときのデフォルトのパスが書かれています。以下はそこに置く場合の手順です。

mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt

age-keygenを実行したときに、以下のように公開鍵が出力されます。

Public key: age1vhw6fwfr058c364r4lwfzq98gtdrz4arlf9lc4zpeu0h4xqckdcq9p4h5d

公開鍵はメモっておかなくても 秘密鍵があればage-keygen -yで確認できます。

$ age-keygen -y ~/.config/sops/age/keys.txt
age1vhw6fwfr058c364r4lwfzq98gtdrz4arlf9lc4zpeu0h4xqckdcq9p4h5d

.sops.yaml設定ファイルで暗号化対象のファイル名と項目を指定

ドキュメントEncrypting only parts of a fileの項に説明があります。

例えば拡張子が.yamlのファイル内のpasswordの項目だけを暗号化の対象とする場合は、以下の内容で.sops.yamlファイルを作成します。

creation_rules:
  - path_regex: '^.*\.yaml$'
    encrypted_regex: '^password$'

ファイル名は.sops.ymlだと警告を出すようになっているので.sops.yamlにしてください。

他の指定方法もありますので、詳しくは上記のドキュメントを参照してください。

.sops.yaml設定ファイルでYAMLのインデントを指定

YAML indentationに説明があります。

デフォルトではインデントは4になっています。2に変更したい場合は、都度引数で--indent=2と指定しても良いそうですが、以下の設定を追加するのが楽です。

stores:
  yaml:
    indent: 2

暗号化

例えば以下のexample.yamlというファイルを暗号化してみます。

db:
  # ユーザー名
  user: foo
  # パスワード
  password: hogehoge
sops encrypt --age $(age-keygen -y ~/.config/sops/age/keys.txt) example.yaml > encrypted.yaml

暗号化されたファイルを見てみると以下のようになっていました。

db:
  # ユーザー名
  user: foo
  # パスワード
  password: ENC[AES256_GCM,data:2Gxd7D9xehA=,iv:qZSPph9pMMqPtOMDQVvzQc1gfagDuEdFpOkPJFbWUHw=,tag:Je12h+2ERRcYy0ebajNYGw==,type:str]
sops:
  age:
    - recipient: age1xwzkkvxll278p92rxawljdmm6rk0maufrsz668a26x30rnuwn53q2jr4m6
      enc: |
        -----BEGIN AGE ENCRYPTED FILE-----
        YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIM1Y3R0xmRVZudjVhOXRi
        bXhkaVkzc1AxNjVySkJYQlUxQXk5Qi9sUlRjCjF2YWZjRU5oNGhnbTZocjJ2VWRK
        NE52cDM2TU42ZDRHYTVZdENaWjVEZzgKLS0tIG5DRlpiUTd5WG1ucGZ4a3V4Q01X
        ZHgyYll4VlhYN2pLQVg1UTJXeUlFTWMKnfQkQ2/hz9SS4TsdMWyW+JDPHYy8yUgt
        lF5BkP94bOfqL0/KZiOcWmT/uIme8QEkdjwhXT5ZAyTi3QVUx91aCQ==
        -----END AGE ENCRYPTED FILE-----
  lastmodified: "2025-08-03T12:58:55Z"
  mac: ENC[AES256_GCM,data:S30XyB3F/W7sk6iAn/W9BXqx0KtTgxCt8wkVGPvSPMnCh1cMQfdxRIJAvJxq/0HCntl0Sq2wKRHoIDVYAnP4Hd9Rl6gcY5gv+MM1TUeK4jBgoC8D/qFYDBnPwNXIdb0GvmGDy9gaJcXOQjcMcDTXmAO4JCEBIHaJC14RCWw0nZY=,iv:t1I1QrmYO7C35Mvh27BG2hEhIkmzGeiKwr/HQwrc/jQ=,tag:hJkhLCSIa/EAlYUh/mLT0Q==,type:str]
  encrypted_regex: ^password$
  version: 3.10.2

userのキーは暗号化されずにそのままになっており、passwordのキーは暗号化されています。 またsopsというキーに復号に必要な情報などが追加されています。

agerecipientは受信者という意味ですが、値としては公開鍵になっています。

今回は違いますが、秘密鍵を持っている人に向けて暗号を送るという使い方を想定したときに、送信側はその人の公開鍵で暗号化して、受け取った側は秘密鍵で復号するというところから来ているようです。

なお、sops encrypt--in-placeオプションを使えば、ファイルを暗号化した結果で上書きもできます。詳しくはsops encrypt --helpを参照してください。

復号

上記で暗号化したファイルを復号してみます。

sops decrypt encrypted.yaml > decrypted.yaml

元のファイルと内容が一致していることを確認しました。

$ cmp decrypted.yaml example.yaml
$

sops decrypt--in-placeオプションを使えば、ファイルを暗号化した結果で上書きもできます。詳しくはsops decrypt --helpを参照してください。

ファイルが暗号化されているかをsopsコマンドで確認する

sops filestatus ファイル名でファイルが暗号化されているかいないかを確認できます。

$ sops filestatus encrypted.yaml
{"encrypted":true}
$ sops filestatus decrypted.yaml
{"encrypted":false}

gitのpre-commitで暗号化されていないファイルをコミットするのを防ぐ

Q: How to prevent unencrypted files from being committed · Issue #571 · getsops/sopsというイシューがありました。

May 12, 2023のコメントのリンク先を見てみましたが、Pythonのpre-commitというモジュールに依存しているのが、個人的には好きになれませんでした。

そこでsopsコマンドを使ったシェルスクリプトを書いてみました。

dashでも動くように配列を使わないようにしています。その代わり空白を含んだファイル名が扱えないという制限があります。

.git/hooks/pre-commitを以下のような内容で作成します。files_to_encypt変数の値はレポジトリに応じて適宜調整してください。

#!/bin/sh
#
# A "pre-commit" hook script to prevent unencrypted files from being committed.
#

set -eu

#
# List of files that must be encrypted with the "sops" command.
#
# NOTE: This must be adjusted for each project.
#
files_to_encrypt="example.yaml example2.yaml"

if ! type sops >/dev/null 2>&1; then
  echo 1>&2 "Please install the \"sops\" command and ensure it is available in your PATH."
  exit 2
fi

staged_files=$(git diff --cached --name-only)

for file in $files_to_encrypt; do
  for staged_file in $staged_files; do
    if [ "$staged_file" = "$file" ]; then
      if [ $(sops filestatus "$staged_file") != '{"encrypted":true}' ]; then
        echo 1>&2 "Please encrypt \"$file\" with the \"sops\" command."
        exit 1
      fi
    fi
  done
done

その他試してないことのメモ

Adding and removing keysの項によると、暗号化したファイルに鍵を追加したり削除したりローテートなどもできるようです。

rotate commandのドキュメントではageについての記載がないですが、sops rotate --helpで確認すると、--add-age--rm-ageオプションがあるのでageも対応しているようです。

また、sops --helpを見るとexec-envexec-fileサブコマンドで復号した値を使ってコマンドを実行したり、editサブコマンドで暗号化されたファイルを直接編集したりもできるようです。