SOPSとKMSで作るセキュアなビルド環境

こんにちは，グレンジ Advent Calendar 2024の25日目担当の石川です．

今年は，SOPSの利用例をいくつか紹介したいと思います．


SOPSは，Secrets OPerationSの略称です．
Mozillaが公開している暗号化ツールで、YAML，JSON，ENV，INIとBINARY形式をサポートし，AWS KMS、GCP KMS，Azure Key Vault，age，およびPGPで暗号化する暗号化ファイルのエディターです．

グレンジでは、`GCP KMS`をを利用して暗号化しています．

SOPSを利用することで，暗号化したいデータをGitHubなどのリポジトリで安全に管理することができます．
これにより，チーム内で安全に暗号化されたデータを共有したり，CI/CDパイプラインで安全に利用することができます．

## 基本的な使い方

Cloud KMS（Cloud Key Management）を利用する場合は，次のコマンドでkeyringとkeyを作成します．
`PROJECT`は，自身のGCPのプロジェクト名を指定してください．

```
$ gcloud kms keyrings create sops-keyring \
  --location=global \
  --project=PROJECT
```

```
$ gcloud kms keys create sops-key \
    --location=global \
    --keyring=sops-keyring \
    --purpose=encryption \
    --project=PROJECT
```

sopsの実行ファイルをダウンロードして，実行権限を付与します．

```
$ curl -Lo sops https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.darwin.arm64
$ chmod u+x sops
```

他のOS，バージョンは，[https://github.com/getsops/sops/releases][github.com/getsops/sops/releases]を参照してください．

以下のような内容で，configファイルとして`.sops.yaml`を作成します．
`path_regex`には，暗号化したいファイルを正規表現で指定します．

```yaml
creation_rules:
  - path_regex: development
    gcp_kms: projects/PORJECT/locations/global/keyRings/sops-keyring/cryptoKeys/sops-key
```

次に暗号化したいファイルを`development.yaml`として作成します．

```yaml
title: グレンジ Advent Calendar 2024
url: https://qiita.com/advent-calendar/2024/grenge
```

次のようなコマンドで暗号化します．

```
sops --encrypt \
  --config .sops.yaml \
  development.yaml > development.enc.yaml
```

暗号化されたファイルは，次のようになります．

```yaml
title: ENC[AES256_GCM,data:9YIX+tN2OmEe+g/awztiMf1PBrJihQ4XxTpiuBYjAJfj,iv:dKQmXDBhCcVuLPoQ4Z2IJi/V4yCBJdxt1QW0x9xFa3w=,tag:1E9daZW8DyWao94dyoLuqQ==,type:str]
url: ENC[AES256_GCM,data:Ofmcwn6mmfv/T9St79tibxDJllMXqPTG7RgX3VpWw1z6bOrFmIiUA68HjcDT,iv:Cri8ektRfNX+OI8C0wZy04uaFh+KHttY7EX2a82TsC8=,tag:Q71jIB6qKiz4QQmrCIU1Zg==,type:str]
sops:
    kms: []
    gcp_kms:
        - resource_id: projects/PORJECT/locations/global/keyRings/sops-keyring/cryptoKeys/sops-key
          created_at: "2024-12-22T14:29:52Z"
          enc: CiQAUsJ80Roob/ap3jr9HMpwB4nWNJA6rnE8TgMLr+URf1zrWxISSQDfbxQ3Ps4FAreYG5D3800LPc47MOttinERMBWOJlELSWiT3XbTtuYfP1iIZTjgfJ9Nr3NRsY4HWdgkGdDxg38H7+0dVS1iV6U=
    azure_kv: []
    hc_vault: []
    age: []
    lastmodified: "2024-12-22T14:29:53Z"
    mac: ENC[AES256_GCM,data:ELxroxCUYVWWbAzZTKcKC20HzhyKT+56sAGKpkJxHbD0D+Oy0pwDroQ/BGtkJma2C93/6VTsSwr/3cgBvwwdqaLWMoir2Nn20Nx9h+qusfU+9dQuB08LWB46olF2RxMOR3S0Fc//pLKnOfrDmgO1c7Lhd22HxMglHwDmqhEBBgs=,iv:DA7viPjyV4KaudbrROLB8PsMZc+7HBmNwbTmzdj/EDc=,tag:uA+uRX0Q3yM/57bKUaycHg==,type:str]
    pgp: []
    unencrypted_suffix: _unencrypted
    version: 3.8.1
```

暗号化されたファイルから元のファイルを復号化するには，次のようなコマンドを実行します．

```
build/.toolchain/bin/sops --decrypt
  --config .sops.yaml \
  development.enc.yaml > development.yaml
```

## GitHub Actionsの環境変数を暗号化する

GitHub Actionsで利用するプラグインもあります．
[mozilla-sops-action][github.com/mdgreenwald/mozilla-sops-action]を利用することで，GitHub Actionsの環境変数を暗号化することができます．

また，Google Cloudの[Workload Identity][google_cloud/identity-security]を利用して，GCPのサービスアカウントを利用する場合にも，SOPSを利用することで，環境変数を暗号化することができます．

GitHubにも，`Actions secrets and variables`からsecretを設定することができますが，SOPSを利用することで，ファイルで管理することができます．
ただ，GitHub Actionsで環境変数に設定すると，実行結果で値が表示されてしまうため，`::add-mask::`を利用して，値をマスクすることで秘匿することができます．
また，管理は`yaml`ファイルで行い，output-typeを`binary`にすることで，容易に環境変数に設定することができます．

```
    # Configure Workload Identity Federation and generate an access token.
    - id: auth
      name: Authenticate to Google Cloud
      uses: google-github-actions/auth@v2.1.7
      with:
        workload_identity_provider: projects/${{ inputs.project_number }}/locations/global/workloadIdentityPools/${{ inputs.pool }}/providers/${{ inputs.provider }}
        service_account: ${{ inputs.service_account }}

    # Setup gcloud CLI
    - name: Set up Cloud SDK
      uses: google-github-actions/setup-gcloud@v2.1.2

    - name: Set up sops
      uses: mdgreenwald/mozilla-sops-action@v1.6.0
      id: install

    - name: Decrypt secrets
      run: |
        sops --decrypt \
          --config .github/actions/secret/.sops.yaml \
          --output-type binary \
          .github/actions/secret/secret.${{ inputs.environment }}.enc.yaml > .github/actions/secret/secret
      shell: bash

    - name: Set up development secret variables
      run: |
        cat .github/actions/secret/secret >> $GITHUB_ENV
        sed -e 's/^.*=/::add-mask::/' -e '/^.*<<EOF/ { :a; N; /\nEOF$/!ba; s/^.*<<EOF\n/::add-mask::/; s/\nEOF//; s/%/%25/g; s/\r/%0D/g; s/\n/%0A/g }' .github/actions/secret/secret
      shell: bash
```

## Terragruntの変数を暗号化する

Terragruntでも，[sops_decrypt_file(file_path)][terragrunt/sops_decrypt_file]を利用して，SOPSで暗号化されたファイルを復号化することができます．

```
locals {
  secret_vars = yamldecode(sops_decrypt_file(find_in_parent_folders("secrets.yaml")))
}

inputs = merge(
  local.secret_vars,
  {
    # additional inputs
  }
)
```

## まとめ

SOPSの利用方法をいくつかご紹介しました．
Secretは，アプリケーションのソースコードやTerraformなどのインフラストラクチャの構成ファイルなどと連携することが多いので，コードに近くに配置しておきたくなります．
ただ，そのまま平文で管理すると，リポジトリが漏洩した場合に大きなリスクになります．
そういったリスク回避のためにSOPSを利用するという手段は，有効な手段の一つです．
また，GitHub ActionsやTerragruntとの連携ができるので，CI/CDパイプラインでの利用も容易です．

今回の内容が少しでも参考になれば幸いです．

## 参考

- [getsops/sops - GitHub][github.com/getsops/sops]
- [mdgreenwald/mozilla-sops-action - GitHub][github.com/mdgreenwald/mozilla-sops-action]
- [Cloud Key Management -Goolgle Cloud][google_cloud/cloud_key_management]
- [GitHub Actions からのキーなしの認証の有効化][google_cloud/identity-security]
- [Terragrunt Built-in Functions - sops_decrypt_file][terragrunt/sops_decrypt_file]

[//]:#(RefUrlStart)

[github.com/getsops/sops]: https://github.com/getsops/sops
[github.com/getsops/sops/releases]: https://github.com/getsops/sops/releases
[github.com/mdgreenwald/mozilla-sops-action]: https://github.com/mdgreenwald/mozilla-sops-action

[google_cloud/cloud_key_management]: https://cloud.google.com/security/products/security-key-management?hl=ja
[google_cloud/identity-security]: https://cloud.google.com/blog/ja/products/identity-security/enabling-keyless-authentication-from-github-actions

[terragrunt/sops_decrypt_file]: https://terragrunt.gruntwork.io/docs/reference/built-in-functions/#sops_decrypt_file

[//]:#(RefUrlEnd)