概要

先日AWSをTerraformで使う上でのベストプラクティスの記事が公開されたので読んだ。 Terraform自体の経験があまりないので、いつか使えそうなことを記載しておく。

https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/terraform-aws-provider-best-practices/introduction.html

セキュリティ

最小権限の原則に従う

AWSリソース操作に必要な権限を最低限にする。

IAMユーザーではなくIAMロールを使い長期間使えるアクセスキーをできるだけ減らす。 JenkinsにもAWS Credentialプラグインがあり一時的に認証情報を取得できるらしいので、 もしJenkinsでIAMロール使うときは調べてみる。

各種ツールを使ってIAMロールの使用状況を監視する。

  • IAM Access Analyzer
  • AWS Config
  • AWS Security Hub

ステートストレージを安全にする

S3のサーバー側の暗号(ServerSideEncryption:SSE)を有効にしておく。 直接ユーザーがapplyするのではなく、CI/CDでのみapplyをできるようにする。

AWS Secrets Managerを使う

平文で保存したくない機密情報はSecrets Managerで値をもつ。 Terraformが持つセンシティブな状態管理の機能を利用する。Outputに機密情報を出したくないときはsensitive = trueとする。

https://developer.hashicorp.com/terraform/tutorials/configuration-language/sensitive-variables

継続的にインフラとソースコードをスキャンする

定期的にスキャンを実施して問題を発見したら迅速に修復しようという話。 TerraformのコードはCheckovなどの静的解析ツールを使える。 Terraformで作られたリソースに対してのスキャンはもはやTerraformの範囲外だが以下のようなAWSサービスがある。

  • Amazon Inspector
  • Security Hub Detective
  • GuardDuty

ポリシーチェックを実施する

Sentinelというツールを使えば以下のようなポリシーを強制できる。

  • タグを必ず設定する
  • インスタンスタイプを制限する
  • 必須変数をいれる
  • 特定のサービス環境で削除させない

バックエンド

バックエンドをリモート上に置く

バックエンドをS3にすることで耐久性と可用性のSLA保証、暗号化、アクセス制御、バージョン管理の機能をS3がもっているのでTerraformのバックエンドとしては使い勝手が良い。 S3にリモートで保存してDynamoDBで状態のロックと一貫性のチェックを実装する。 AWS Backupを使って自動バージョニング、バックアップを有効にする。

HCP TerrafomというHashiCorpのSaaSがある。S3とDynamoDBよりも多機能なので AWSのベストプラクティスではあるが、ほとんどの人にとっては最適な方法と書かれていた。

チームでの利用を促進する

CloudTrailを使ったAPI呼び出しの追跡ができるようにする。

バックエンドを環境ごとに分ける

Terraform Workspaceを使って分離することもできるがバックエンドを分けたほうがより強力に分離できる。 本番環境はユーザーには読み取り専用アクセスに制限しCI/CDと緊急用(Break Grass)に限定する。 バックエンドのS3バケットも環境ごとに個別にし権限を分ける。

コードベースの構造

標準的なリポジトリ構造の実装

ファイルをどのように分けるかを記載していた。記事には基本的なディレクトリ構成も書かれている。 ルートモジュールのmain.tfを単一にして環境ごとにterraform.tfvarsのみを差分にしている例だった。

  • variables.tfとoutputs.tf
    • 変数を使うかは慎重にする、変更する必要がなければ公開しない
      • リテラルが複数箇所で使うときは変数でなくlocalを使用するべき
    • variablesの値を直接outputsに渡してはいけない
      • 依存関係のグラフが適切でなくなってしまうため
      • outputにはリソースの属性を渡すようにする
  • locals.tf
    • 式に名前を割り当てることで繰り返しを避ける
    • リテラルにかかわらず変数やリソースの属性を組み合わせることもできる
  • data.tf
    • データソースが少なければ必要なリソースの近くに置けばいいが量が増えてきたら分けることも考える

モジュール化のための構造

リソースや構成要素をモジュールにまとめられる。 使いすぎると構成全体の理解や保守が難しくなる。 単一のリソースをラップすると不必要に複雑になるので直接リソースを使う。 モジュールはネストすることもできるが、ネストするときは階層を深くしすぎないようにする(1-2程度)。

outputを最低1つは作ることでモジュールの依存関係をTerraformに推測させやすくする。 プロバイダの設定はモジュールではなく呼び出し元にまかせる。モジュールで使用できるバージョンはrequired_providersブロックで指定する。

命名規則

命名規則を決めてメンテナンスしやすくする。Terraformの標準に合わせる。

  • リソース名
    • リソース名はスネークケースにする
    • 唯一のリソースであればリソース名はmainやthisにする
    • リソースの目的とコンテキストを記述する
      • 例えばデータベースのメインならprimary、レプリカにはread_replicaなど
    • 複数形ではなく単体にする
    • リソース名にリソースタイプを繰り返さない
  • 変数名
    • 単位をいれることでわかりやすくする
      • ストレージには2進数単位のMiBやGiB、それ以外は10進数単位のMBやGBを使う
    • disableではなくenableのようなポジティブな名前をつける

Attachmentリソースを使う

疑似リソースで属性としてリソースを埋め込むことができるが極力使わないほうがいい。 別途リソースを定義するようにする。

理由も書かれているが翻訳してもよくわからなかった。

例としてaws_security_groupはingressとegressを属性としてもたせるのではなく、aws_security_group_ruleソースとして別途定義する。

デフォルトのタグを使う

aws_defalut_tagsを定義することですべてのリソースにタグを設定する。

推奨するモジュールソースを利用する

moduleブロックのsourceでソースを指定する際の指定方法は、頻繁に修正するならローカルに置き相対パスにする。 共有目的であればTerraform Registoryやバージョン管理のプロバイダを使う。

  • Terraform registryの場合
    • ハッシュ値を指定してあげることで変更を検知する
  • GitHubの場合
    • refでバージョンを指定する

コーディングの標準

フォーマットルールとスタイルをCI/CDのスタイルチェックで強制する。 pre-commitフックを活用する。

  • terraform fmtでフォーマットする
  • terraform validateで構文、構造をチェックする
  • tflintやCheckovで静的解析する

AWS Providerのバージョン管理

providerのバージョン管理を注意する。

CI/CDでバージョンのチェックを追加してバージョンの固定がされることを検証する。 tflintのAWS Provider用のルールセットプラグインを使える。

リリースノートを監視して更新履歴がないかチェックする。

その他参考情報