Terraform利用時のアクセスキー管理から脱却!assume_roleを活用して複数環境の管理を楽にする方法

AWS

2022.11.7

Topics

こんにちは、tkgです。

AWSの活用を進めている場合、一つのワークロードでも複数のAWSアカウントを保有している場合があります。
弊社のようなマネージドサービス事業者の場合は言わずもがな、様々なお客様のアカウントをお預かりしています。

Terraformで複数のアカウントをコード管理しようとすると、ネックになるのは認証情報、アクセスキーの取り扱いです。
手動での対応は対象アカウントの間違えや事故につながりますので、何かしらの手段を講じる必要があります。

複数アカウント環境でのスムーズなTerraformの利用について、現在利用している仕組みをまとめてみたいと思います。

ロールでの権限の継承

AWSアカウントを複数利用する場合、スイッチロールやIAM Identity Centerを利用する場合があります。
Terraformでも、Provider定義の際に引き継ぐロールの指定(assume_role)の設定がありますので、こちらを利用していきます。

今回はTerraformを実行する環境Aと、それを展開する環境Bという構成となります。
環境A上でCloud9を利用しTerraformを作成・運用する仕組みとし、環境Bロールを継承、リソースを展開する仕組みとなっています。

環境A(Terraform実行アカウント) のセットアップ

まず 環境AへTerraformを実行する環境の作成を行います。
Cloud9は外部疎通の可能な任意のVPCで起動してください。

Cloud9用のロールの作成と適用

環境AでTerraform環境Bのロールを引き継いで展開する仕組みとしますので、
Cloud9上で環境Bのロールを継承できるようにする必要があります。

まず任意のロールを作成します。許可ポリシーはEC2のみとします。図ではTerraformRole という名前としています。
下記のような設定でポリシーを作成しロールに適用するか、インラインポリシーをロール上に作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::*:role/terraform-*-role"
        }
    ]
}

これで任意のアカウント上の terraform-*-roleを継承できるロールが作成できました。
また、stateの管理にs3、バックエンドのlock機構にDynamoDBを利用しますので、利用するs3、DynamoDBを利用できるポリシーもロール付与しています。

作成したロールをCloud9が動作しているEC2に付与します。

Cloud9の設定変更

適用したロールは最初の設定ではCloud9上で利用できません。
初期設定がAWS Managed Temporary Credentialsを利用する設定となっていますので、
これをEC2に付与されているロールを利用する設定に変更します。

Cloud9左上アイコンから Performance > AWS Settings を選択し、CredentialsAWS Managed Temporary Credentialsdisable(赤、xマーク)になるように変更します。
これでEC2に付与したロールをCloud9上から利用することが可能となります。

環境B(Terraformの内容を展開するアカウント) のセットアップ

次に、リソースを展開したい環境B上でのロールのセットアップを行います。

環境Bのアカウント上で下記のようなCloudFomationテンプレートを実行します。

AWSTemplateFormatVersion: "2010-09-09"
Description: ""
Resources:
    IAMRole:
        Type: "AWS::IAM::Role"
        Properties:
            Path: "/"
            RoleName: "terraform-admin-role"
            AssumeRolePolicyDocument: !Sub "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::[環境AのAWSID]:role/Cloud9TerraformRole\"},\"Action\":\"sts:AssumeRole\",\"Condition\": {\"StringEquals\": {\"sts:ExternalId\": \"[任意の文字列]\"}}]}"
            MaxSessionDuration: 3600
            ManagedPolicyArns:
              - "arn:aws:iam::aws:policy/AdministratorAccess"
            Description: ""

    IAMRole2:
        Type: "AWS::IAM::Role"
        Properties:
            Path: "/"
            RoleName: "terraform-default-role"
            AssumeRolePolicyDocument: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::[環境AのAWSID]:role/TerraformRole\"},\"Action\":\"sts:AssumeRole\",\"Condition\": {\"StringEquals\": {\"sts:ExternalId\": \"[任意の文字列]\"}}]}"
            MaxSessionDuration: 3600
            ManagedPolicyArns:
              - "arn:aws:iam::aws:policy/ReadOnlyAccess"
            Description: ""

※ 便宜上適用ポリシーに強い権限を持ったものを利用していますが、実際に利用する場合は、利用するサービスに絞った権限設定を推奨します。

こちらで作成される terraform-defalut-role および terraform-admin-role を利用してリソースの展開等を行います。
ロールの権限として、terraform-default-roleをReadOnly、terraform-admin-roleを実際にterraform applyするときに利用するロールとして設計しています。

実際に利用してみる

各種ロールの設定が終わりましたので、実際にTerraformを実行していきます。

Cloud9上で任意のディレクトリ上に providers.tfを作成し、下記のような形でprovider定義でassume_roleする設定を入れ、展開先のroleを引き継げるようにします。
また、合わせて動作確認用にvpc.tfも作成し、簡単にVPC周りも作成してみます。

▼providers.tf

locals {
  aws_id = [展開先のAWSID]
  region = "ap-northeast-1"
}

terraform {
  backend "s3" {
    bucket         = "[バケット名]"
    dynamodb_table = "[DynamoDBのテーブル名]"
    key            = "[stateを置きたいディレクトリ]/terraform.tfstate"
    region         = "ap-northeast-1"
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

variable "role" {
  type        = string
  default     = "default"
  description = "select iam role"
}

provider "aws" {
  region = "${local.region}"
  assume_role {
    role_arn     = "arn:aws:iam::${local.aws_id}:role/Techorus-terraform-${var.role}-role"
    session_name = "terraform"
    external_id  = "[CloudFomaitonのsts:ExternalIdで設定した文字列]"
  }
}

▼vpc.tf

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "test-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["${local.region}a"]
  public_subnets  = ["10.0.0.0/24"]

}

上記コードを利用し、Terrafrom の動作を確認します。

環境Bに作成したロールの権限とproviders.tf の中にある 変数 “role” を確認してもらうと解りますが、普通に terraform apply した場合だと ReadOnlyAccessポリシーが呼ばれる仕組みとしています。
これはCI/CDを利用する場合などでもread権限だけは付与してterraform planの実行だけは許可しよう、という考えのものになります。

手動でapplyしたい場合は terraform apply -var=role=admin のような形でassumeするロールをadminに変更してあげる必要があります。

その後の展開について

今回の構成では環境Aを管理用のアカウント、環境Bを被管理アカウントとして構成しました。
環境Bの設定をそのまま横展開することで、複数環境の管理が可能となります。

開発したコードをGithubやCodeCommit等で管理し、CI/CDパイプラインでの自動apply等を検討することも可能です。
Github Actionsであれば、OIDCプロバイダの登録を環境Aに追加し、Github Actionsが利用するロールを環境BにCloudFomationで作成したロールのプリンシパルに追加してあげるだけで動作させるための準備が完了します。
あとは動作させたい形でYAMLファイルを記載、配置してください。

まとめ

現在、私の所属しているチームでは上記の様な形で管理を進めています。
予め管理用アカウントからの操作のみを許可しておき、そのアカウントへのアクセスを保護することで、IAMアクセスキーの流出などのリスクをなくすことができています。
IAMアクセスキーを利用する形に比べるとAWSのベストプラクティスに近い利用方法となりますので、複数のAWS環境をTerraformで管理している方はぜひassume_roleの機能を使ってみてはいかがでしょうか。

こちらの構成も運用を進めている中で問題点が発生することもあると思いますので、少し立ってから運用してみた結果を共有することができれば良いかなと思っています。

tkg

2016年入社のインフラエンジニアです。 写真が趣味。防湿庫からはレンズが生え、押入れには機材が生えます。 2D/3DCG方面に触れていた時期もありました。

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら