Terraform CloudのPolicy as code、Sentinelの導入 - APC 技術ブログ

APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Terraform CloudのPolicy as code、Sentinelの導入

Terraform Cloudの魅力的な機能としてPolicy as codeの機能があります。

Terraformで管理するシステムの規模が大きくなってきたり、システム数が増えてくると、設定のレビューをすべて人力で行うのが難しくなってくるかと思います。
そこでPolicy as codeのSentinelを利用することで、機械的にチェックできるようになり、
レビューも効率的に回せるようになってガバナンスの向上にも繋がってくるでしょう。

Terraform CloudのPolicy as Code

Terraform CloudではTeam & Governance以上のプランでPolicy as Codeの機能が利用できるようになります。
1ヶ月のトライアル版でもTeam & Governance相当の機能が利用できるのでご興味あれば誰でも触ってみることが可能です。

Policy as codeのツールとしては、Hashicorpが提供するSentinelとOSSでRego言語で記述するOPA (Open Policy Agent)の2つが対応しています。
今回はSentinelについてご紹介しようと思います。

Sentinelについて

Sentinelは独自のSentinel言語で書かれるポリシーのフレームワークです。
一般的なプログラミング言語と同様に演算子や比較、変数定義などができますし、ビルトインのパッケージ(Imports)をインポートして利用することも可能です。

docs.hashicorp.com

特徴的なのはruleです。
ruleブロックの中で1つないし複数の評価をしてBool値を返し、そのruleを mainに渡すことでポリシーとして機能するようになります。

param a default 1
main = rule {
    a == 1
}

上の例であれば、aの値がデフォルトのまま1であればtrue, それ以外の値が渡されていればfalseとなります。

Sentinelには以下のようなPlayground環境が提供されているので、Web上で挙動を確認することができます。

play.sentinelproject.io

SentinelをTerraformと連携するためにはtfplan, tfconfig, tfstate, tfrun といったTerraformのデータブロックをimport文で読み込ませることでそれぞれのデータにアクセスすることができるようになります。
tfrun以外にはterraform v0.12以降のバージョンに対応したv2が用意されており、特にtfplan/v2が利用されることが多いようです。

Import
tfplan Terraform planのデータ。主に変更されるリソースのパラメータの評価に利用する
tfconfig .tfのTerraform構成ファイル内の評価に利用する
tfstate ローカルないしリモートにあるtfstateファイルに記述されているデータを評価する
tfrun Terraform CloudにおけるWorkspaceの情報(コスト予測など)を評価する

Terraform CloudにおけるSentinelの利用方法

Sentinel自体がどんなものかを簡単に紹介したところで実際にTerraform Cloudで利用してみましょう。

個別のWorkspaceではなくグローバルのサイドメニューにあるPoliciesPolicy setsがポリシーに関するメニューです。

PoliciesはTerraform Cloud上でPolicyを直接記述するメニューで、Policy setsでポリシーとWorkspaceとの紐付けを行います。
Policy setsの中でGitHub等のVCS上のポリシーを読み込むことができるので、ポリシーごとコード管理したい場合はPoliciesメニューは利用せずにPolicy setsのみ利用すれば問題ありません。

ということでGitHubのリポジトリを作成してコードを書いていこうと思います。

実践

今回はAKSをTerraformで構築するにあたって、ポリシーとしてノードプールのVMのサイズを許可されたものかどうかをチェックしようと思います。

GitHubリポジトリに設定ファイルを作成

まずはTerraformおよびSentinelの設定ファイルを作成します。以下のようなファイル構成になります。

.
├── main.tf
└── policies
    ├── sentinel.hcl
    └── vm_size.sentinel

まずチェック対象のTerraformの構成ファイルを記述します。今回はリソースグループとAKSを作っています。

main.tf

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg" {
  name     = "sentinel-test"
  location = "japaneast"
}

resource "azurerm_kubernetes_cluster" "k8s" {
  name                = "sentinel-aks"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  dns_prefix          = "sentinel-aks"
  tags                = {
    Environment = "Development"
  }

  default_node_pool {
    name       = "agentpool"
    vm_size    = "Standard_D2_v2"
    node_count = 1
  }
  
  network_profile {
    network_plugin    = "azure"
  }
  
  identity {
    type = "SystemAssigned"
  }
}

次にpoliciesディレクトリを作成し、vm_size.sentinelというファイルを作成します。

policies/vm_size.sentinel

import "tfplan/v2" as tfplan

# Allowed VM Sizes
param allowed_vm_sizes default [
    "Standard_A1",
    "Standard_A2",
    "Standard_A3",
    "Standard_D2_v2",
]

# Find all AKS clusters
clusters = filter tfplan.resource_changes as _, rc {
    rc.mode is "managed" and
        rc.type is "azurerm_kubernetes_cluster" and
        rc.change.actions is ["create"]
}

# check vmsize of nodopools on clusters
vm_size_allowed = rule {
    all clusters as name, instances {
        all instances.change.after.default_node_pool as _, pool {
            pool.vm_size in allowed_vm_sizes
        }
    }
}

main = rule {
    vm_size_allowed
}

1行目ではtfplan/v2をtfpanというエイリアス名でインポートしています。

3-9行目でallowed_vm_sizesというリストを作成してデフォルト値を設定しています。
この値はTerraform Cloud側から値を上書きすることが可能です。

11-16行目ではtfplanからAKSのリソースを抽出しています。
12行目のfilterはリストやマップ形式のオブジェクトに対してループを回して、
カッコ内の条件分にマッチしたオブジェクトのリストないしマップを返す式です。
ちなみに13行目のrc.mode is "managed"は、既存リソースを参照するdata型ではなくresource型であることをチェック、
14行目のrc.type is "azurerm_kubernetes_cluster"は、リソースタイプがazurerm_kubernetes_clusterであることをチェック、
15行目のrc.change.actions is ["create"]はこのリソースが新規作成であることをチェックしています。

18-25行目では↑で取得したAKSリソースのすべてのノードプールのVMサイズがallowed_vm_sizesのリストに含まれるかをチェックするルールを作成しています。

27-29行目で上記ルールをmainルールに追加しています。

最後にpolicies/sentinel.hclファイルを作成します。
これはTerraform Cloudにポリシーセットに含まれるポリシーを認識させるためのファイルとなっています。

policies/sentinel.hcl

policy "vm_size" {
    # source = "./vm_size.sentinel"
    enforcement_level = "soft-mandatory"
}

同一ディレクトリ内にSentinelファイルがある場合はポリシー名とファイル名を一致させれば読み込ませられますが、ディレクトリを分けたりする場合はsourceで相対ファイルパスを指定します。
enforcement_levelはポリシーが違反した場合の動作を指定できます。各ポリシーの許容度に合わせてenforcement_levelを設定しましょう。

level
hard-mandatory ポリシーに違反した場合、Terraform CloudのApplyは停止されます。違反が解決されるまで基本的に適用できません。
soft-mandatory hard-mandatoryと似ていますが、ポリシーのオーバーライドの管理権限を持つユーザーは、ケースバイケースでポリシーの違反を許容してApplyを実行することができます。
advisory 実行を中断することはなく、ユーザーへの情報としてポリシーの違反のみを表示します。

上記のファイル群を作成し、GitHubリポジトリにコミットします。

Terraform Cloudでポリシーセットを設定

続いて作成したポリシーファイルをTerraform Cloudに読み込ませます。

グローバル設定のPolicy sets メニューを開き、Connect anew policy setボタンをクリックします。

VCSとしてGitHubを選択します。
アクセス可能なリポジトリの一覧が出てくるので先程作成したリポジトリを選択します。

こちらの画面でポリシーの設定をしていきます。

Policy frameworkは今回Sentinelを利用するのでデフォルトのままSentinelにします。
Nameにはデフォルトでリポジトリ名が入るので適宜修正してください。

Policy Set Sourceの部分は最初は折りたたまれているのでクリックしてメニューを展開してください。
Policies Pathでポリシーファイルが配置されているパスを記述します。今回はここに/policiesと指定する必要があります。

Scope of Policiesですが、すべてのWorkspaceにポリシーを適用するか、あるいは適用するWorkspaceを指定するか、
後者の場合はどのWorkspaceに適用するかを指定することになります。
今回は簡易的にすべてのWorkspaceに割り当てます。

最後にConnect policy setをクリックして問題がなければポリシーセットが適用されます。

Workspaceを作成して実行

ポリシーセットが作成できたので最後にWorkspaceを作成して実行してみましょう。

適当なWorkspaceを作ってVersion ControlからGitHubの先程のリポジトリを選択しましょう。(最近微妙にUIが変わったようです)

認証の設定として、Azure側のサービスプリンシパルの作成とTerraform Cloud側の変数設定を行ってください。
こちらのやり方は以前の記事で紹介しているのでご参考ください。

techblog.ap-com.co.jp

設定が終わったらWebUIもしくはリポジトリに何かしらのコミットをして実行してみましょう。
すると…

このようにPlanの実行の後にPolicy Checkが実施されています。
今回はvm_sizeStandard_D2_v2allowed_vm_sizesに含まれているのでvm_size_allowedのルールにパスしていますね。
ちなみに今回ポリシーとして設定したvm_size_allowedルールの直上の18行目のコメントがDescriptionとして表示されています。

Planだけでポリシーチェックができますので、今回はDiscard Runをクリックして実行を中止しましょう。

パラメーターを上書きしてポリシー違反の動作確認

今度はパラメーターを上書きしてポリシー違反となるか確認してみましょう。

再度Policy Setsのメニュー画面に移動して、先程作成したポリシーセットをクリックして再度メニューを開きます。

するとUpdate policy setボタンの下にSentinel Paramatersというブロックが増えています。
Sentinelファイルに設定したparamの値は、ここのKey/Valueを設定することで上書きすることができます。

Keyにallowed_vm_sizes、Valueに[ "Standard_A1", "Standard_A2", "Standard_A3"]Standard_D2_v2を除外して上書きしてみましょう。
入力が終わったらSave parameterでパラメーターをセーブします。

それでは再度Workspaceに戻り、Plan and Applyを実行してみましょう。

すると今度はvm_size_allowedfalseとなりポリシーチェックは失敗になります。

また、今回のポリシーのenforcement_levelsoft-mandatoryですので、実行を中止することもできますし、
権限のあるユーザであればOverride & Continueを選択してポリシー違反を許容してApplyに進めることも選択できます。

このような形でpolicy as codeが実現されました。

いい感じのポリシーセットを適用する

以上でSentinelの適用方法は理解できたかと思いますが、
いい感じのポリシーを書くのはSentinel言語に習熟する必要があったり手間があります。

そこでこちらのリポジトリをご紹介します。

github.com

Hashicorpから各種クラウドに対応したポリシーやクラウドに依存しないポリシー例が用意されています。

Azureを例に取るとVMやAppService, AKSなどのポリシーがあります。

これらのポリシー群はTerraform RegistryのPolicy Librariesに移行中のようですが、まだベータということもあってか全てのポリシーがホストされているわけではないようです。

registry.terraform.io

まずはリポジトリを各々の組織にフォークして、ポリシーの取捨選択やパラメーターの調整などカスタマイズして使ってみると良いと思います。

今回書いた例のようにtfplan/v2などを直接インポートせずに、共通の処理をcommon-functions内にモジュール化することでSentinelネイティブにはない比較用の関数などを再利用していますので、 これらのモジュールを活用するのも良いと思います。
オリジナルのポリシーを作成する上でも様々なSentinelコードがあるのでサンプルとしても利用できるでしょう。

また今回は紹介しませんでしたが、Sentinelではモックを使ったSentinelコードのテストができ、
そのモックやテストコードも書かれていますのでそちらも参考になると思います。

おわりに

最初に書いたようにポリシーチェック機能はシステム規模が大きくなったりサービスレベルを高めるためには避けては通れないものに当たると思います。
今回のSentinelを使ったポリシーチェックのご紹介で、少しでもポリシーチェックのイメージが具体的になれば幸いです。

個人的にはOpen Policy Agentの方も少し触っているので、ニーズがあればそちらのご紹介もしたいと思います。


私達ACS事業部はAzure・AKSなどのクラウドネイティブ技術を活用した内製化のご支援をしております。

www.ap-com.co.jp

また、一緒に働いていただける仲間も募集中です!
今年もまだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。

www.ap-com.co.jp

本記事の投稿者: 安藤 知樹
AzureとTerraformをメインにインフラ系のご支援を担当しています。 Shunsuke Yoshikawa - Credly