💧

aqua の Policy as Code

2022/11/15に公開

CLI ツールを YAML でバージョン管理できるツール aqua を開発しています。

https://aquaproj.github.io/

https://zenn.dev/topics/aquaclivm

今回は aqua のセキュリティ強化の一環として導入された Policy as Code に関する機能を紹介します。
aqua では利便性だけでなくセキュリティも重要であると考えています。
先日も checksum の検証によって改竄を検知する機能を導入しました。

https://zenn.dev/shunsuke_suzuki/articles/aqua-checksum-verification

今回はそれに続くセキュリティ関連の機能になります。

aqua を通じてツールをインストールする際、以下のような脅威が考えられます。

  • セキュリティ的に問題のあるバージョンが実行される
    • 修正バージョンがリリース済みだが、古いバージョンが使われている
    • 最新バージョンに問題があるので downgrade すべきだが、されていない
  • local registry や github_content registry を通じて悪意のあるツールがインストールされる
    • 怪しげなサードパーティの github_content registry が使われるのを防ぎたい
    • standard registry だけを許可したい
    • 古い registry が使われるのを防ぎたい
    • 悪意のある aqua.yaml と local registry を作成して、悪意のあるツールが実行されるのを防ぎたい

こういった脅威を防ぐために、 aqua.yaml とは別に policy ファイルというファイルでインストール・実行しても良い package を定義し、それに違反したものは install, 実行出来ないようにすることが出来るようになりました。

Getting Started

aqua の最新版を install してください。

aqua.yaml を作成します。

aqua init
aqua g -i hashicorp/terraform

policy ファイルとして aqua-policy.yaml を作成します(名前は自由です)。

aqua init-policy
aqua-policy.yaml
# aqua Policy
# https://aquaproj.github.io/docs/tutorial-extras/policy-as-code
registries:
- type: standard
  ref: semver(">= 3.0.0")
packages:
- registry: standard

Policy ファイルのフォーマットは aqua.yaml に似せています(全く同じではありません)。

環境変数 AQUA_POLICY_CONFIG で aqua-policy.yaml へのパスを指定しましょう。この環境変数がないと policy は機能しません。
環境変数 AQUA_GLOBAL_CONFIG と同様に : つなぎで複数指定することが出来ます(複数指定した場合、いずれかの policy ファイルで許可されたら許可されたことになります)。

export AQUA_POLICY_CONFIG=$PWD/aqua-policy.yaml

上の設定では standard registry の package を許可しています。
local registry を作成しましょう。

registry.yaml
packages:
  - type: github_release
    repo_owner: suzuki-shunsuke
    repo_name: github-comment
    description: CLI to create a GitHub comment
    asset: github-comment_{{trimV .Version}}_{{.OS}}_{{.Arch}}.tar.gz
    version_constraint: semver(">= 4.3.0")
    version_overrides:
      - version_constraint: "true"
        rosetta2: true
        supported_envs:
          - darwin
          - amd64
  - type: github_release
    repo_owner: suzuki-shunsuke
    repo_name: tfcmt
    asset: tfcmt_{{.OS}}_{{.Arch}}.tar.gz
    version_constraint: semver(">= 3.2.5")
    version_overrides:
      - version_constraint: "true"
        supported_envs:
          - darwin
          - linux
aqua.yaml
registries:
- type: standard
  ref: v3.94.1 # renovate: depName=aquaproj/aqua-registry
- name: local
  type: local
  path: registry.yaml
packages:
- name: hashicorp/terraform@v1.3.4

そして local registry の package を追加します。

aqua g -i local,suzuki-shunsuke/tfcmt local,suzuki-shunsuke/github-comment

install してみましょう。

$ aqua i
ERRO[0000] install the package                           aqua_version=1.24.0 doc="https://aquaproj.github.io/docs/reference/codes/002" env=darwin/arm64 error="this package isn't allowed" package_name=suzuki-shunsuke/github-comment package_version=v5.0.0 program=aqua registry=local
ERRO[0000] install the package                           aqua_version=1.24.0 doc="https://aquaproj.github.io/docs/reference/codes/002" env=darwin/arm64 error="this package isn't allowed" package_name=suzuki-shunsuke/tfcmt package_version=v4.0.0 program=aqua registry=local
INFO[0000] download and unarchive the package            aqua_version=1.24.0 env=darwin/arm64 package_name=hashicorp/terraform package_version=v1.3.4 program=aqua registry=standard
FATA[0002] aqua failed                                   aqua_version=1.24.0 env=darwin/arm64 error="it failed to install some packages" program=aqua

policy によって期待通り tfcmt と github-comment の install に失敗しました。
実行にも失敗します。

$ tfcmt -v
FATA[0000] aqua failed                                   aqua_version=1.24.0 doc="https://aquaproj.github.io/docs/reference/codes/002" env=darwin/arm64 error="validate the installed package for security: this package isn't allowed" exe_name=tfcmt package=suzuki-shunsuke/tfcmt package_version=v4.0.0 policy_files="[/Users/shunsukesuzuki/Documents/test/aqua/pr-1308-2/aqua-policy.yaml]" program=aqua

多くのユースケースでは standard registry だけで十分で、 github_content registry や local registry だけで十分でしょう。
なので github_content registry や local registry を禁止することで悪意のあるツールが意図的に実行されるリスクはだいぶ下がります。

特定の local registry や github_content registry を許可することも出来ます。
許可してみましょう。

aqua-policy.yaml
registries:
- type: standard
  ref: semver(">= 3.0.0")
- name: local
  type: local
  path: registry.yaml
packages:
- registry: standard
- registry: local

install, 実行できました。

$ tfcmt -v
INFO[0000] download and unarchive the package            aqua_version=1.24.0 env=darwin/arm64 exe_name=tfcmt exe_path=/Users/shunsukesuzuki/.local/share/aquaproj-aqua/pkgs/github_release/github.com/suzuki-shunsuke/tfcmt/v4.0.0/tfcmt_darwin_arm64.tar.gz/tfcmt package=suzuki-shunsuke/tfcmt package_name=suzuki-shunsuke/tfcmt package_version=v4.0.0 program=aqua registry=local
tfcmt version 4.0.0 (047e980d083da80303e6e8f4ebf6d5c9e7859716)

$ github-comment -v
INFO[0000] download and unarchive the package            aqua_version=1.24.0 env=darwin/arm64 exe_name=github-comment exe_path=/Users/shunsukesuzuki/.local/share/aquaproj-aqua/pkgs/github_release/github.com/suzuki-shunsuke/github-comment/v5.0.0/github-comment_5.0.0_darwin_arm64.tar.gz/github-comment package=suzuki-shunsuke/github-comment package_name=suzuki-shunsuke/github-comment package_version=v5.0.0 program=aqua registry=local
github-comment version 5.0.0 (64d3b0b4fd3b8b05bd43e7dde9a7181577b34d70)

特定の package だけ許可することも出来ます。 local registry の tfcmt だけ許可します。

aqua-policy.yaml
registries:
- type: standard
  ref: semver(">= 3.0.0")
- name: local
  type: local
  path: registry.yaml
packages:
- registry: standard
- name: suzuki-shunsuke/tfcmt
  registry: local
$ tfcmt -v
tfcmt version 4.0.0 (047e980d083da80303e6e8f4ebf6d5c9e7859716)

$ github-comment -v
FATA[0000] aqua failed                                   aqua_version=1.24.0 doc="https://aquaproj.github.io/docs/reference/codes/002" env=darwin/arm64 error="validate the installed package for security: this package isn't allowed" exe_name=github-comment package=suzuki-shunsuke/github-comment package_version=v5.0.0 policy_files="[/Users/shunsukesuzuki/Documents/test/aqua/pr-1308-2/aqua-policy.yaml]" program=aqua

version による制限も出来ます。
構文は version_overrides や version_constraint と同じです。
expr という評価エンジンが使われ、 semver は hashicorp/go-version によって評価されます。

tfcmt の >= 4.0.0 を許可します。

aqua-policy.yaml
registries:
- type: standard
  ref: semver(">= 3.0.0")
- name: local
  type: local
  path: registry.yaml
packages:
- registry: standard
- name: suzuki-shunsuke/tfcmt
  registry: local
  version: semver(">= 4.0.0")

問題なく実行できます。

$ tfcmt -v
tfcmt version 4.0.0 (047e980d083da80303e6e8f4ebf6d5c9e7859716)

tfcmt を v3.0.0 に downgrade しようとすると失敗します。

aqua.yaml
# 一部抜粋
- name: suzuki-shunsuke/tfcmt@v3.0.0 # downgrade
  registry: local
$ tfcmt -v
FATA[0000] aqua failed                                   aqua_version=1.24.0 doc="https://aquaproj.github.io/docs/reference/codes/002" env=darwin/arm64 error="validate the installed package for security: this package isn't allowed" exe_name=tfcmt package=suzuki-shunsuke/tfcmt package_version=v3.0.0 policy_files="[/Users/shunsukesuzuki/Documents/test/aqua/pr-1308-2/aqua-policy.yaml]" program=aqua

github_content registry も許可できます。

aqua-policy.yaml
# 一部抜粋
registries:
- name: aqua-registry
  type: github_content
  repo_owner: aquaproj
  repo_name: aqua-registry
  ref: semver(">= 3.0.0")
  path: registry.yaml

standard registry, github_content registry の version 制約 (ref) を省略すると、任意の version が許可されます。

aqua-policy.yaml
# 一部抜粋
registries:
- type: standard
- name: aqua-registry
  type: github_content
  repo_owner: aquaproj
  repo_name: aqua-registry
  path: registry.yaml

Getting Started は以上になります。

Policy as Code によるレビュー負荷の軽減とセキュリティの向上

Policy as Code に関しては Hashicorp の Sentinel のドキュメントが参考になるでしょう。

https://docs.hashicorp.com/sentinel/concepts/policy-as-code

Policy が言わばガードレールとなることで、 Policy に違反していない限りは自由に aqua.yaml を更新してよいということになります。
これは aqua.yaml を Renovate で自動 update している場合に、 review なしでも安心してマージできるようにするのにも役立つでしょう。

Renovate の PR の自動 merge のために aqua.yaml に CODEOWNER を設定してないとすると、人間が Renovate の PR に commit を追加して aqua.yaml を悪意を持って書き換えることが出来てしまいますが、 policy ファイルを使えばそれを防ぐことも出来ます
(policy ファイルに CODEOWNER を設定するなどして policy ファイルの改竄を防ぐ必要はあるでしょう)。

aqua.yaml の修正に review が必須となると、 update の頻度が高ければ高いほど review の負荷は大きくなり、人は消耗し、 review は雑になって問題を見過ごすようになります。
policy によって review が不要になれば負担はなくなりますし、 policy があっても運用ルール上 review は必須だとしても、 policy で機械的にチェックできる部分はチェックされ、人間はそれ以外の部分に注力して review が出来るようになるでしょう。

Monorepo の場合

Monorepo で多くの aqua.yaml を管理している場合、それら全てに関してセキュリティ的に問題がないかチェックする必要があります。
policy ファイルは aqua.yaml からは独立しており、 Monorepo で複数の aqua.yaml がある場合でも policy ファイルを共通化することが出来ます。
policy ファイルはセキュリティチームで管理しつつ、個々の aqua.yaml の管理は各チームに任せるという運用も考えられます。

例えば tfaction では Monorepo の working directory ごとに terraform や tflint, tfsec といったツールのバージョンを管理するようになっています。
こうすることで update を working directory ごとに段階的に行うことが出来るようになります。
全部の working directory でバージョンを統一となると、 working directory の数が増えた場合に一括で上げるのが困難になり、上げたくても上げられない・新規のプロジェクトなのに古いバージョンを使わないといけないといったことが考えられます。
tfaction のやり方ではそういった問題を防げるといったメリットがあるものの、
大量(規模が大きければ 1000 以上とかも考えられます)の aqua.yaml の update を全部セキュリティチームや SRE チームといった特定のチームがチェックするというのは難しいでしょう。
なので aqua.yaml の管理を各チームに任せるということになると思いますが、それでも最低限のガバナンスは効かせたいところでしょう。 policy ファイルを活用することでガバナンスを効かせつつ aqua.yaml の管理を安全に各チームに任せることが出来ます。

活用方法

CI

リポジトリ直下に Policy ファイルを置いて、環境変数 AQUA_POLICY_CONFIG を設定しましょう。

GitHub Actions であれば、以下のように workflow 単位で AQUA_POLICY_CONFIG を設定すれば良いでしょう。

env:
  AQUA_POLICY_CONFIG: ${{ github.workspace }}/aqua-policy.yaml

ローカル開発

dotfiles か何かで Policy ファイルを管理し、 .bashrc や .zshrc などで AQUA_POLICY_CONFIG を設定すれば良いでしょう。
まずは standard registry だけを許可し必要に応じて github_content registry や local registry を個別に許可すれば安全でしょう。

さいごに

aqua v1.24.0 で導入された Policy as Code に関する機能を紹介しました。
公式ドキュメントも参照してください。

https://aquaproj.github.io/docs/tutorial-extras/policy-as-code/

Policy を設定することで危険なツールの実行を防ぐことが出来るので、設定することをお勧めします。
特に standard registry 以外の registry (local registry や github_content registry) は悪用されるリスクもあるので、デフォルトで無効化することも検討しています。詳細は Issue を見てください。

https://github.com/aquaproj/aqua/issues/1404

Discussion