Auth0をSAML IdPにしたAmazon Managed GrafanaをTerraformで構築する

# はじめに
Auth0活用記事第2弾。
第一弾では、AWSのマネージメントコンソールログインをSAML認証で行えるようにした。

https://qiita.com/neruneruo/items/14ff2e9d53eb9c082faa

今回は、Amazon Managed GrafanaへのログインをAuth0をIdPに設定して行えるようにする。

Amazon Managed Grafanaは、Grafanaをフルマネージド化した魅力的なサービスだが、GrafanaへのログインがAmazonのマネージメントコンソールログインとは分けられており、かつSAML認証が必要である。
AWSに閉じて利用しようとすると、Organizationsを前提としたIAM Identity Centerを設定しなければならず、お手軽には使いにくい。
おためしで使ったり、小さいチームで利用するユースケースとしては、Auth0等のIDaaSの機能を利用するのが良いだろう。

今回は、このAmazon Managed GrafanaとAuth0の連携部分についてTerraformで自動構築をしていく。

なお、Terraformを扱うにあたってのプロバイダの設定とAuth0テナントのユーザは前回の設定をそのまま流用するため、Amazon Managed Grafanaのみ使いたい方については、[前回記事の「Auth0のTerraformプロバイダの設定」の章](https://qiita.com/neruneruo/items/14ff2e9d53eb9c082faa#auth0%E3%81%AEterraform%E3%83%97%E3%83%AD%E3%83%90%E3%82%A4%E3%83%80%E3%81%AE%E8%A8%AD%E5%AE%9A)を参照していただきたい。

# Amazon Managed Grafanaの設定
まずは、Amazon Managed Grafanaのワークスペースを設定する。
```authentication_providers```には、IAM Identity Centerを使う場合は```AWS_SSO```を設定するが、今回は外部のSAML IdPを利用するため、```SAML```を設定する。

:::note info
本記事では、ログインするところまでを目的としているので、IAMロールのみ作ってIAMポリシーのアタッチを行っていないが、実際の運用ではポリシーの設定まで行う。
:::

```HCL
resource "aws_grafana_workspace" "example" {
  name = local.amg_workpace_name

  account_access_type      = "CURRENT_ACCOUNT"
  authentication_providers = ["SAML"]

  permission_type = "CUSTOMER_MANAGED"
  role_arn        = aws_iam_role.amg.arn
}

resource "aws_iam_role" "amg" {
  name               = local.iam_amg_role_name
  assume_role_policy = data.aws_iam_policy_document.amg_assume.json
}

data "aws_iam_policy_document" "amg_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "grafana.amazonaws.com",
      ]
    }
  }
}
```

# Auth0のクライアントアプリケーションの設定
Auth0のクライアントアプリケーションは以下のように設定する。

```callbacks```と、```addons.samlp.audience```および```addons.samlp.destination```には、Amazon Managed Grafanaワークスペースの構築時に払い出されるエンドポイントを設定する。

```HCL
resource "auth0_client" "amazon_managed_grafana" {
  name        = "AWS Managed Grafana"
  description = "AWS Managed Grafana Login"
  app_type    = "regular_web"

  custom_login_page_on                = true
  is_first_party                      = true
  is_token_endpoint_ip_header_trusted = false
  oidc_conformant                     = true
  require_proof_of_possession         = false

  callbacks = ["https://${aws_grafana_workspace.example.endpoint}/saml/acs"]

  grant_types = [
    "authorization_code",
    "implicit",
    "refresh_token",
    "client_credentials",
  ]

  jwt_configuration {
    alg                 = "RS256"
    lifetime_in_seconds = 36000
    secret_encoded      = false
  }

  refresh_token {
    leeway          = 0
    token_lifetime  = 31557600
    rotation_type   = "non-rotating"
    expiration_type = "non-expiring"
  }

  addons {
    samlp {
      audience    = "https://${aws_grafana_workspace.example.endpoint}/saml/metadata"
      destination = "https://${aws_grafana_workspace.example.endpoint}/saml/acs"
      mappings = {
        email    = "email"
        nickname = "nickname"
      }
      create_upn_claim                   = false
      passthrough_claims_with_no_mapping = false
      map_unknown_claims_as_is           = false
    }
  }
}
```

# 再び、Amazon Managed Grafanaの設定(SAML設定)
Auth0のクライアントアプリケーションの登録が完了したら、再びAWSの設定に戻る。

Amazon Managed Grafanaとワークスペースの連携は、```aws_grafana_workspace_saml_configuration```のリソースで設定を行う。

```idp_metadata_url```は、Auth0クライアントアプリケーションのメタデータURLを設定する。前回のIDプロバイダーの記事では、SAMLメタデータのBodyが直接必要であったため、```http```データソースを利用したが、今回はエンドポイントのURLだけあれば良いので楽だ。

```*_assertion```は、SAML認証のアサーションのどの項目をGrafanaの設定値にマッピングするかの設定だ。

```HCL
resource "aws_grafana_workspace_saml_configuration" "example" {
  workspace_id = aws_grafana_workspace.example.id

  editor_role_values = ["editor"]

  idp_metadata_url = "https://${var.auth0_domain}/samlp/metadata/${auth0_client.amazon_managed_grafana.id}"

  admin_role_values = [auth0_user.user.email]
  login_assertion   = "email"
  name_assertion    = "nickname"
  email_assertion   = "email"
  role_assertion    = "email"
}
```

これで、設定は完了だ。少ない手順で実行できて嬉しい。

# いざ、動かす！
```terraform apply```したら、Amazon Managed Grafanaのログイン画面にアクセスしよう。
ログインのエンドポイントは、マネージメントコンソールの以下のURLで良い。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/a3d4010b-706e-d84d-6e37-15bcf06f04d7.png)

アクセスすると、以下のようにSAMLログイン用のボタンが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/c530c348-e404-97b6-b19f-26c948f797cb.png)

ボタンを押すと、前回同様のAuth0のログイン画面に遷移する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/841645cd-81f0-c38d-7e2d-b9b0ac358918.png)

ここで、前回作成したユーザで認証を行うと、無事、Amazon Managed Grafanaのコンソール画面にアクセスできた！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/de49542c-7681-3848-403c-a6cfd9659648.png)

:::note info
なお、Auth0のテナントを前回と同じにしておくことで、ログイン状態が共有される。
Amazon Managed Grafana用のログインを行っている状態で、AWSのマネージメントコンソールのログインエンドポイントのURLにアクセスすると、認証なしでマネージメントコンソールにもアクセスができる。
同じテナントにすることで、アプリケーションによらずSSOができるようになるということだ。便利！

ただし、ログイン状態が共有されるということはログアウトも同期される。
単純にAmazon Managed Grafanaからログアウトしただけでは、Auth0のログインセッションは残るため、再度Amazon Managed Grafanaにアクセスすると認証なしでログインができてしまう。
一方で、ログアウト用のエンドポイントを叩いてしまうと、マネージメントコンソールのログアウトもしてしまう。

この問題は非常に難しいようで、Auth0も枠外にリンクしたようなブログ記事を投稿している。
エンドユーザに関連するサービスを提供する場合、ログイン管理における要件を整理する必要があることを留意していただきたい。
:::

https://auth0.com/blog/jp-the-not-so-easy-art-of-logging-out/