DatabricksにおけるAssumeRoleポリシーを用いたS3バケットに対するセキュアなクロスアカウントアクセス

[Secure access to S3 buckets across accounts using instance profiles with an AssumeRole policy \| Databricks on AWS](https://docs.databricks.com/administration-guide/cloud-configurations/aws/assume-role.html) [2022/12/13時点]の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

AWSにおいては、あるアカウントの計算処理が別のアカウントのバケットにアクセスできるように、クロスアカウントアクセスをセットアップすることができます。アクセスを許可する方法の一つは、[Databricksにおけるインスタンスプロファイルを用いたS3バケットへのセキュアなアクセス](https://qiita.com/taka_yayoi/items/446c7971be354f88c679)で説明されているように、アカウントに対して、別のアカウントのバケットへのアクセスを直接許可するというものです。バケットへのアクセスを許可する別の方法は、アカウントが別のアカウントの *ロールの一時クレデンシャルを要求する(assume a role)* ことを許可するというものです。

アカウントIDが`<deployment-acct-id>`であるAWSアカウントAと、アカウントIDが`<bucket-owner-acct-id>`であるAWSアカウントBを考えます。アカウントAは[Databricksにサインアップ](https://docs.databricks.com/administration-guide/account-api/iam-role.html)する際に用いたものであり、EC2サービスとDBFSルートバケットはこのアカウントによって管理されています。アカウントBはバケット`<s3-bucket-name>`を保有しています。

本書では、アカウントAがアカウントBのロールとして`<s3-bucket-name>`のファイルにアクセスするために、[AWSのAssumeRole](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html)アクションを使用するように設定するステップを説明します。このアクセスを有効にするために、アカウントA、アカウントB、そしてDatabricksのAdmin Consoleでの設定が必要となります。また、バケットにアクセスするノートブックや、Databricksクラスターに設定を行う必要があります。

# 要件

- DatabricksをデプロイしたAWSアカウントとS3バケットのAWSアカウントでIAMロール、ポリシーにアクセスできるAWS管理者
- ターゲットのS3バケット
- S3バケットに対する[暗号化](https://docs.databricks.com/dbfs/mounts.html#s3-encryption)を有効化する場合には、設定で指定されるKMSキーに対する**Key User**としてインスタンスプロファイルを追加する必要があります。[s3a://パスに対するKMS暗号化の設定](https://qiita.com/taka_yayoi/items/a26a1fb49c0e217ad10b#s3a%E3%83%91%E3%82%B9%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8Bkms%E6%9A%97%E5%8F%B7%E5%8C%96%E3%81%AE%E8%A8%AD%E5%AE%9A)をご覧ください。

# ステップ1: アカウントAで`MyRoleA`を作成し、ポリシーをアタッチする

1. アカウントAで`MyRoleA`というロールを作成します。インスタンスプロファイルのARNは`arn:aws:iam::<deployment-acct-id>:instance-profile/MyRoleA`となります。
1. アカウントAのロールが、アカウントBのロール`MyRoleB`の一時クレデンシャルの要求 (AssumeRole)を許可するポリシーを作成します。ポリシーを`MyRoleA`にアタッチします。![](https://docs.databricks.com/_images/add-inline-policy.png)をクリックし、ポリシーに貼り付けます。

    ```json:JSON
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "Stmt1487884001000",
          "Effect": "Allow",
          "Action": [
            "sts:AssumeRole"
          ],
          "Resource": [
            "arn:aws:iam::<bucket-owner-acct-id>:role/MyRoleB"
          ]
        }
      ]
    }
    ```

1. Databricksをデプロイする際に作成した[クロスアカウントIAMポリシー](https://docs.databricks.com/administration-guide/account-api/iam-role.html#create-access-policy)を更新します。`MyRoleA`に対する`iam:PassRole`アクションを追加します。

    ```json:JSON
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "Stmt1403287045000",
          "Effect": "Allow",
          "Action": [
            "ec2:AssociateDhcpOptions",
            "ec2:AssociateIamInstanceProfile",
            "ec2:AssociateRouteTable",
            "ec2:AttachInternetGateway",
            "ec2:AttachVolume",
            "ec2:AuthorizeSecurityGroupEgress",
            "ec2:AuthorizeSecurityGroupIngress",
            "ec2:CancelSpotInstanceRequests",
            "ec2:CreateDhcpOptions",
            "ec2:CreateInternetGateway",
            "ec2:CreateKeyPair",
            "ec2:CreateRoute",
            "ec2:CreateSecurityGroup",
            "ec2:CreateSubnet",
            "ec2:CreateTags",
            "ec2:CreateVolume",
            "ec2:CreateVpc",
            "ec2:CreateVpcPeeringConnection",
            "ec2:DeleteInternetGateway",
            "ec2:DeleteKeyPair",
            "ec2:DeleteRoute",
            "ec2:DeleteRouteTable",
            "ec2:DeleteSecurityGroup",
            "ec2:DeleteSubnet",
            "ec2:DeleteTags",
            "ec2:DeleteVolume",
            "ec2:DeleteVpc",
            "ec2:DescribeAvailabilityZones",
            "ec2:DescribeIamInstanceProfileAssociations",
            "ec2:DescribeInstanceStatus",
            "ec2:DescribeInstances",
            "ec2:DescribePrefixLists",
            "ec2:DescribeReservedInstancesOfferings",
            "ec2:DescribeRouteTables",
            "ec2:DescribeSecurityGroups",
            "ec2:DescribeSpotInstanceRequests",
            "ec2:DescribeSpotPriceHistory",
            "ec2:DescribeSubnets",
            "ec2:DescribeVolumes",
            "ec2:DescribeVpcs",
            "ec2:DetachInternetGateway",
            "ec2:DisassociateIamInstanceProfile",
            "ec2:ModifyVpcAttribute",
            "ec2:ReplaceIamInstanceProfileAssociation",
            "ec2:RequestSpotInstances",
            "ec2:RevokeSecurityGroupEgress",
            "ec2:RevokeSecurityGroupIngress",
            "ec2:RunInstances",
            "ec2:TerminateInstances"
          ],
          "Resource": [
              "*"
          ]
        },
        {
          "Effect": "Allow",
          "Action": "iam:PassRole",
          "Resource": [
            "arn:aws:iam::<deployment-acct-id>:role/MyRoleA"
          ]
        }
      ]
    }
    ```

:::note
**注意**
お使いのアカウントがDatabricksプラットフォームのE2バージョンの場合、`ec2:CreateKeyPair`と`ec2:DeleteKeyPair`を削除することができます。アカウントバージョンが不明の場合には、Databricks担当者にお問い合わせください。
:::

# ステップ2: アカウントBで`MyRoleB`を作成し、ポリシーをアタッチする

1. `MyRoleB`というロールを作成します。ロールのARNは`arn:aws:iam::<bucket-owner-acct-id>:role/MyRoleB`となります。
1. アカウントAのロール`MyRoleA`が、アカウントBのロールを引き受けられるように、`MyRoleB`の信頼リレーションシップを編集します。**IAM > Roles > MyRoleB > Trust relationships > Edit trust relationship**を選択し、以下を入力します。

    ```json:JSON
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "AWS": [
              "arn:aws:iam::<deployment-acct-id>:role/MyRoleA"
            ]
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }
    ```

1. バケット`<s3-bucket-name>`に対するバケットポリシーを作成します。**S3 > `<s3-bucket-name>` > Permissions > Bucket Policy**を選択します。バケットポリシーにロール(プリンシパル)`MyRoleB`を含めます。

    ```json:JSON
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "AWS": [
                "arn:aws:iam::<bucket-owner-acct-id>:role/MyRoleB"
            ]
          },
          "Action": [
            "s3:GetBucketLocation",
            "s3:ListBucket"
          ],
          "Resource": "arn:aws:s3:::<s3-bucket-name>"
        },
        {
          "Effect": "Allow",
          "Principal": {
              "AWS": [
                  "arn:aws:iam::<bucket-owner-acct-id>:role/MyRoleB"
              ]
          },
          "Action": [
            "s3:PutObject",
            "s3:PutObjectAcl",
            "s3:GetObject",
            "s3:DeleteObject"
          ],
          "Resource": "arn:aws:s3:::<s3-bucket-name>/*"
        }
      ]
    }
    ```

:::note
**ティップス**
`Principal error`が表示された場合には、**Trust relationship**だけを変更したことを確認してください。
:::

# ステップ3: MyRoleAをDatabricksワークスペースに追加する

DatabricksのAdmin Consoleで、ステップ1で取得した`MyRoleA`のインスタンスプロファイルARN`arn:aws:iam::<deployment-acct-id>:instance-profile/MyRoleA`を用いてMyRoleAの[インスタンスプロファイルをDatabricksに追加](https://docs.databricks.com/administration-guide/cloud-configurations/aws/instance-profiles.html#add-instanceprofile)します。

# ステップ4: MyRoleAでクラスターを設定する

1. クラスターを選択、作成します。
1. **Advanced Options**セクションを展開します。
1. **Instances**タブで、`MyRoleA`の[インスタンスプロファイル](https://docs.databricks.com/administration-guide/cloud-configurations/aws/instance-profiles.html#cluster-instance-profile)を選択します。
1. **Spark**タブで、オプションとして`assumeRole`の認証タイプと、ロールを委任する`MyRoleB`のARNを指定します。

    :::note
    **注意**
    Databricksランタイム 7.3 LTS以降では、[open\-source Hadoop options](https://hadoop.apache.org/docs/r3.1.3/hadoop-aws/tools/hadoop-aws)を用いたS3Aファイルシステムの設定をサポートしています。グローバルプロパティあるいはバケットごとのプロパティを設定することができます。
   :::

    すべてのバケットに対して設定するには:

    ```bash:Bash
    fs.s3a.aws.credentials.provider org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider
    fs.s3a.assumed.role.arn arn:aws:iam::<bucket-owner-account-id>:role/MyRoleB
    ```

    特定のバケットに設定するには:

    ```bash:Bash
    fs.s3a.bucket.<s3-bucket-name>.aws.credentials.provider org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider
    fs.s3a.bucket.<s3-bucket-name>.assumed.role.arn arn:aws:iam::<bucket-owner-account-id>:role/MyRoleB
    ```

1. クラスターを起動します。
1. ノートブックをクラスターにアタッチします。
1. 以下のコマンドを実行して`<s3-bucket-name>`にアクセスできることを検証します。

    ```py:Python
    dbutils.fs.ls("s3a://<s3-bucket-name>/")
    ```

# ステップ5: AssumeRoleを用いてクロスアカウントバケットをマウントする

リモートデータにアクセスする際、相対パスを使えるようにクロスアカウントバケットをマウントすることができます。[Mount a bucket using instance profiles with the AssumeRole policy](https://docs.databricks.com/dbfs/mounts.html#mount-a-bucket-using-instance-profiles-with-the-assumerole-policy)をご覧ください。

# Terraformを用いた設定の自動化

自動でAWS IAMロールとクラスターへのアタッチメントを設定するために、[Databricks Terraformプロバイダー](https://qiita.com/taka_yayoi/items/691ee4909b0961de5ad1)を活用することができます。

このサンプル設定に示すように最初に2つの変数を設定します。

```terraform
variable "prefix" {
  default = "changeme"
}

variable "databricks_account_id" {
  description = "Account ID. You can get your account ID in the bottom left corner of the account console. See https://accounts.cloud.databricks.com"
}
```

[aws\_s3\_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket)を用いてバケットを作成します。

```terraform
resource "aws_s3_bucket" "ds" {
  bucket = "${var.prefix}-ds"
  acl    = "private"
  versioning {
    enabled = false
  }
  force_destroy = true
  tags = merge(var.tags, {
    Name = "${var.prefix}-ds"
  })
}
```

[aws\_iam\_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role)を用いてデータアクセスのためのIAMロールを作成します。

```terraform
data "aws_iam_policy_document" "assume_role_for_ec2" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      identifiers = ["ec2.amazonaws.com"]
      type        = "Service"
    }
  }
}

resource "aws_iam_role" "data_role" {
  name               = "${var.prefix}-first-ec2s3"
  description        = "(${var.prefix}) EC2 Assume Role role for S3 access"
  assume_role_policy = data.aws_iam_policy_document.assume_role_for_ec2.json
  tags               = var.tags
}
```

このバケットにフルアクセスを許可する[databricks\_aws\_bucket\_policy](https://registry.terraform.io/providers/databricks/databricks/latest/docs/data-sources/aws_bucket_policy)を用いたバケットポリシーを作成します。[aws\_s3\_bucket\_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy)を用いて新規に作成したバケットにインラインのS3バケットポリシーを適用します。

```terraform
data "databricks_aws_bucket_policy" "ds" {
  provider         = databricks.mws
  full_access_role = aws_iam_role.data_role.arn
  bucket           = aws_s3_bucket.ds.bucket
}

resource "aws_s3_bucket_policy" "ds" {
  bucket = aws_s3_bucket.ds.id
  policy = data.databricks_aws_bucket_policy.ds.json
}
```

[aws\_iam\_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy)を用いてDatabricksがデータロールのリストをパスできるクロスアカウントポリシーを作成します。

```terraform
data "databricks_aws_crossaccount_policy" "this" {
  pass_roles = [aws_iam_role.data_role.arn]
}

resource "aws_iam_policy" "cross_account_policy" {
  name   = "${var.prefix}-crossaccount-iam-policy"
  policy = data.databricks_aws_crossaccount_policy.this.json
}
```

[aws\_iam\_role\_policy\_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment)を用いた信頼関係の設定を通じてDatabrikcsがあなたのアカウント内でアクションを実行することを許可します。

```terraform
data "databricks_aws_assume_role_policy" "this" {
    external_id = var.databricks_account_id
}

resource "aws_iam_role" "cross_account" {
  name               = "${var.prefix}-crossaccount-iam-role"
  assume_role_policy = data.databricks_aws_assume_role_policy.this.json
  description        = "Grants Databricks full access to VPC resources"
}

resource "aws_iam_role_policy_attachment" "cross_account" {
  policy_arn = aws_iam_policy.cross_account_policy.arn
  role       = aws_iam_role.cross_account.name
}
```

[E2 workspace setup](https://docs.databricks.com/dev-tools/terraform/e2-workspace.html)にクロスアカウントロールを登録します。

```terraform
resource "databricks_mws_credentials" "this" {
  provider         = databricks.mws
  account_id       = var.databricks_account_id
  credentials_name = "${var.prefix}-creds"
  role_arn         = aws_iam_role.cross_account.arn
}
```

ワークスペースが作成されたら、[databricks\_instance\_profile](https://registry.terraform.io/providers/databricks/databricks/latest/docs/resources/instance_profile)として[aws\_iam\_instance\_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile)のデータロールを登録します。

```terraform
resource "aws_iam_instance_profile" "this" {
  name = "${var.prefix}-first-profile"
  role = aws_iam_role.data_role.name
}

resource "databricks_instance_profile" "ds" {
  instance_profile_arn = aws_iam_instance_profile.this.arn
}
```

最後のステップとして、指定されたインスタンスプロファイルを用いてマウントポイント`/mnt/experiments`とクラスターを作成します。

```terraform
resource "databricks_aws_s3_mount" "this" {
    instance_profile = databricks_instance_profile.ds.id
    s3_bucket_name = aws_s3_bucket.this.bucket
    mount_name = "experiments"
}

data "databricks_node_type" "smallest" {
  local_disk = true
}

data "databricks_spark_version" "latest_lts" {
  long_term_support = true
}

resource "databricks_cluster" "shared_autoscaling" {
  cluster_name            = "Shared Autoscaling"
  spark_version           = data.databricks_spark_version.latest_lts.id
  node_type_id            = data.databricks_node_type.smallest.id
  autotermination_minutes = 20

  autoscale {
    min_workers = 1
    max_workers = 50
  }

  aws_attributes {
    instance_profile_arn = databricks_instance_profile.ds.id
  }
}
```

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)