MNTSQ Techブログ

「MNTSQ(モンテスキュー)」のTechブログです。

IAM Identity Center を IdP として Redash に SSO ログインして操作ログを Athena で追う

MNTSQ Tech Blog TOP > 記事一覧 > IAM Identity Center を IdP として Redash に SSO ログインして操作ログを Athena で追う

はじめに

弊社では BI ツールとして Redash を EC2 上でセルフホストするかたちで利用しています。CS や Sales 等の部門で日々の指標を追うのに使われるだけでなく、開発や運用のためのアラーティングにも活用されています。

今回この Redash へ SSO ログインの仕組みを導入し、Redash 利用者の操作履歴を 部分的に 監査できるようにしました。SSO の IdP としては掲題通り IAM Identity Center を選定しています。これら対応について記録します。

なぜ IAM Identity Center を IdP に?

去る2025年5月下旬に以下記事を公開しました。

この中で

ユーザから見た場合に各 AWS アカウントへのアクセスが透過的になるのも IAM Identity Center 導入での嬉しさのひとつですが、IAM Identity Center 自体がアイデンティプロバイダ (IdP) としての利用も可能であるという点から、SRE 主体での他のサービスへの SSO 化の試みについても着手し易くなってきました。今後は社内で独自のユーザ管理体系が存在する諸々のサービスを着実に SSO 化し、利用者の利便性向上やセキュアな体制構築に繋げてゆこうと計画しています。

という一節があります。弊社では Redash を SRE と CRE とが連携して運用しており、SSO 化の取り組みは SRE の責任範囲にて実施することとしました。弊社 CRE については以下もぜひご参照ください。

note.mntsq.co.jp

さて前掲の IAM Identity Center に関する記事は Entra ID を IdP として IAM Identity Center から AWS への SSO ログインを可能にするという趣旨になります。つまり IAM Identity Center が抱え込んでいるユーザ / グループの元ネタは Entra ID となります。

ここで IAM Identity Center を IdP として Redash へ SSO するよう構成することで、Entra ID 側での対応をすることなく Entra ID ユーザ / グループを活用して Redash へのログインが可能になります。 これは Redash を利用する立場にしてみると Entra ID ユーザを使って Redash が使えるという体験につながり、弊社が普段の業務で使う他の SaaS と同様の利用体験が得られる格好になります。サービス利用の煩雑さが低減できるという嬉しさがあるでしょう。

構成

SSO ログイン化前後での構成変遷を示します。なお Redash のためのインフラは EC2 + ALB で構成され、Redash そのものは EC2 インスタンス内で docker-compose によって動かしています。公式ドキュメントに沿った方法*1といえるでしょう。

SSO ログイン前

至極シンプルです。パスワードマネージャ(弊社では全社的に Keeper*2 を使用しています)上の Redash ユーザ名およびパスワードを必要に応じて利用しログインする方式です。

SSO ログイン後

相応に複雑になりました。Redash 以外にも登場人物が増えています。

  • IAM Identity Center に Entra ID から連携されたユーザを使い、Redash へログインします
  • Redash が出力するログを EC2 から CloudWatch Logs に出力し、そこから Data Firehose を経由し、最終的には S3 に配置します
  • IAM Identity Center 経由での Redash へのログイン履歴を CloudTrail ログ(これも最終的には S3 に配置されます)から追跡します
  • S3 上に配置された各種ログは必要に応じて Athena から検索が可能です

SSO 設定

おおむね以下のような順番での作業が必要になります:

  1. IAM Identity Center アプリケーションの用意
  2. IAM Identity Center アプリケーションからの必要な情報の取得と設定
  3. Redash 上での SAML 設定

IAM Identity Center アプリケーションの用意

前述のとおり、IAM Identity Center は外部 IdP を引き受けて AWS アカウントへのアクセスを可能とする利用形態以外に、自身が IdP となり AWS サービスや外部サービスへのアクセスを担うこともできます。 これには IAM Identity Center 内でアプリケーションというものを用意し、アクセスさせたいユーザ / グループをアプリケーションに紐付けることで達成可能です*3AWS サービスと連携させる場合とそれ以外とでそれぞれ対応が異なり、今回のように Redash が対象の場合は利用者自身が管理する (customer managed) アプリケーションで構成するとよいでしょう*4

Redash 公式ドキュメントのうち SSO に関するもの*5 を読むと、今回使うべきは SAML であることがわかります。したがって IAM Identity Center も SAML を前提としたアプリケーションを用意します。 サクッと Terraform で作ってしまいましょう。コード例は以下の通りです。

こちらを参照

locals {
  entra_id_groups = {
    /*
     Redash へのアクセスを許可したいユーザが属する Entra ID グループ名を指定
     Entra ID グループ / ユーザが IAM Identity Center へ SCIM で連携などされていることを前提にする
     */
    redash = [
      "Engineer",
      "CS",
      "Sales",
      ...
    ]
  }
}

/*
 IAM Identity Center インスタンスに対し必要な設定が行われる
 これは Terraform リソースでの管理が難しいので、設定は AWS マネジメントコンソール上から行い、Terraform からは参照に留める
 */
data "aws_ssoadmin_instances" "main" {}

# IAM Identity Center アプリケーション定義。SAML を前提とするもの
resource "aws_ssoadmin_application" "redash" {
  name                     = "Redash"
  description              = "IAM Identity Center appliction to access Redash"
  application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom-saml"
  instance_arn             = tolist(data.aws_ssoadmin_instances.main.arns)[0]

  portal_options {
    visibility = "ENABLED"
    sign_in_options {
      origin = "IDENTITY_CENTER"
    }
  }
}

# IAM Identity Center アプリケーションの利用を許可する Entra ID グループ(が IAM Identity Center に同期されたもの)を参照できるようにする
data "aws_identitystore_group" "redash" {
  for_each = toset(local.entra_id_groups.redash)

  identity_store_id = tolist(data.aws_ssoadmin_instances.main.identity_store_ids)[0]
  alternate_identifier {
    unique_attribute {
      attribute_path  = "DisplayName"
      attribute_value = each.key
    }
  }
}

# アプリケーション / Entra ID グループ間の紐付けをする
resource "aws_ssoadmin_application_assignment" "redash" {
  for_each = toset(local.entra_id_groups.redash)

  application_arn = aws_ssoadmin_application.redash.arn
  principal_id    = data.aws_identitystore_group.redash[each.key].group_id
  principal_type  = "GROUP"
}

IAM Identity Center アプリケーションからの必要な情報の取得と設定

ここまでで IAM Identity Center アプリケーションの用意はできました。しかし残念ながら SAML 設定そのものは Terraform では設定できません。公式ドキュメントを参照しつつ、適宜設定する必要があります。以下を見るとよいでしょう。

docs.aws.amazon.com

明示的な設定が必要になるのは Application metadata における以下内容です。必要な値は Redash 公式ドキュメント*6をもとに設定します。

なお Application metadata と同じ画面にある IAM Identity Center metadata はその内容を控えておきます。Redash 側の設定で使います。IAM Identity Center としてはこのあたりの設定をまとめた XML ファイルの取得が可能なのですが、生憎 Redash にはこの XML ファイルを読み取れる仕組みがありません。よって逐次設定を行う必要があります。

重要:attribute mapping の設定

ここまで済めば一段落……と思いきや、前述の AWS 公式ドキュメントには以下の一節があります。

However, you must also provide additional SAML attribute mappings for your own SAML 2.0 applications. These mappings enable IAM Identity Center to populate the SAML 2.0 assertion correctly for your application. You can provide this additional SAML attribute mapping when you set up the application for the first time. You can also provide SAML 2.0 attribute mappings on the application details page in the IAM Identity Center console.

このあたりも Terraform では直接の設定が不可能です。AWS マネジメントコンソールから設定してしまうのが手っ取り早いでしょう。 設定した IAM Identity Center アプリケーションの詳細画面から Actions -> Edit attribute mappings と辿り、以下の値を設定します。

User attribute in the application Maps to this string value or user attribute in IAM Identity Center Format
Subject(変更不可) ${user:email} emailAddress
FirstName ${user:givenName} basic
LastName ${user:familyName} basic

Redash 上での SAML 設定

基本的には Redash の公式ドキュメントに従えば設定が済みます*7。Admin グループに属する Redash ユーザでないと設定画面が出ないので注意が必要です。実際に設定が必要な内容は以下の通りです。

  • SAML Enabled:チェックをいれる
  • SAML Metadata URL:IAM Identity Center 側 IAM Identity Center metadata 内のうち “IAM Identity Center SAML metadata file” で示される URL を記入
  • SAML Entity ID:こだわりがなければ Redash
    • IAM Identity Center 側 Application metadata“Application SAML audience” と一致していればよい
  • SAML NameID Formaturn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
    • 固定値

ログ監査

ここまでで Redash へ IAM Identity Center を IdP として SSO アクセスする手筈が整いました。折角 IAM Identity Center を経由しての操作となるので、CloudTrail あたりから操作ログを棚卸したくなるのが人情というものでしょう。このあたりの整備をします。

ただし本稿冒頭で

Redash 利用者の操作履歴を 部分的に 監査できるようにしました

と記載した通り、Redash へのログインからログアウトまでの全操作をユーザにひもづくかたちで追跡できるような状況には出来ていません。ユーザを絞れる状態で追えるログは以下までとなります。

  • IAM Identity Center (CloudTrail) ログで追跡可
    • Redash にログインした
  • Redash ログを駆使して追跡可
    • クエリを実行した(実行したクエリまで追跡化)

以下は上記ログでは追跡不可な様子でした。無論ログ自体はあるのですが、ユーザにひもづく形でのログは存在せず、他の情報と突き合わせて追跡する必要があります。

  • クエリ実行結果を見た
  • ダッシュボードを見た

追跡可能な範囲でログをどのように追えるかを示せればと思います。

IAM Identity Center (CloudTrail) ログから Redash ログイン形跡を追う

いきなり結論です。以下のような Athena クエリが使えます。直近7日間のログを追跡する例です

select
    userIdentity.onBehalfOf.userId as userId,
    json_extract_scalar(serviceEventDetails, '$["app-id"]') as appId,
    eventtime
from
    <CloudTrail ログ用 Athena DB>
where
    timestamp between date_format(current_date - interval '7' day, '%Y/%m/%d') and date_format(current_date, '%Y/%m/%d')
    and eventSource = 'sso.amazonaws.com'
    and eventName = 'Federate'
    and json_extract_scalar(serviceEventDetails, '$["app-id"]') = '<Redash への SAML ログインで使う IAM Identity Center アプリケーション ID>'

日本語で記載してある箇所について解説します。

  1. CloudTrail ログ用 Athena DB:S3 上にある CloudTrail ログを取り扱う為の Athena DB。パーティション射影を使う AWS 公式ドキュメントに沿った内容を前提とする*8
  2. Redash への SAML ログインで使う IAM Identity Center アプリケーション ID:IAM Identity Center アプリケーション に対応するインスタンスの ID

2. については解説が必要でしょう。ここで指定すべき ID は ARN に含まれる ID (ex. api-xxx) ではなく、 URL 中に示される ID (ex. ins-xxx) のほうです。伏字が多い例で恐縮ですが、以下スクリーンショット中の赤枠の箇所を使って始めて上記クエリで所望のログが得られます。

このあたりについては AWS 公式のドキュメントを含めても公開されている情報がなく、弊社を担当頂いております AWS 社の SA の方々に多大なるご協力を頂き実現したものとなります。この場を借りて感謝申し上げます。なおこの内容は今後変化する可能性が多分にある模様で、その際にログの取り方を変えられるようにしておく必要があります。

弊社では上記 Athena クエリを名前付きクエリとして整備したうえで専用の workgroup を用意し、適当な期間でのログ検索とその結果のレポーティングの仕組みを運用するようにしています。これは今回の Redash ログ以外でも同様の仕組みでレポーティングを行っているものがあるので、それらをまとめて別の機会にブログ化できればと思います。少しだけネタばらしをすると以下のような仕組みです。

  1. Lambda で Athena クエリを実行
  2. Athena がクエリの実行を終える
  3. Athena workgroup にひもづくかたちでクエリ実行完了イベントが EventBridge から発火する
  4. 3. のイベントを拾う Lambda 関数が Athena クエリ実行結果を取得し、必要な処理を実行
  5. 最終的な結果を Lambda -> SNS -> AWS Chatbot -> Slack という流れで通知

Redash ログからクエリ実行形跡を追う

弊社のように EC2 インスタンス上で Redash をセルフホストする場合、当然ながらそのログは EC2 インスタンス上に存在することになります。 これではログを継続的に追う場合に都合が悪いので、EC2 上からいったん CloudWatch Logs ロググループにログを出し、そのうえで Data Firehose を経由し S3 にログを出力し、Athena で追跡が可能なようにしています。

EC2 から CloudWatch Logs へログを出すにはいくつか方法がありますが、幸いなことに弊社での Redash の動かし方は docker-compose によるのものなため、以下を利用できます。 docs.docker.com

これを使い

    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: <CloudWatch ロググループ名>
        awslogs-create-group: "false"

といった記述を Redash コンテナ管理用の docker-compose.yml へ追記し、EC2 インスタンスプロファイルへ CloudWatch Logs 関係の必要なアクションを許可してやれば、EC2 から CloudWatch Logs へのログ出力については解決します。具体的には以下アクションが認可できていれば OK です。

  • logs:CreateLogStream
  • logs:PutLogEvents

Redash の典型的なクエリ実行ログは Redash サーバにおける

[2025-08-08 02:11:13,060][PID:31][INFO][root] Inserting job for 915b2acd6d435dfd3ea3578a457f4ded with metadata={'Username': u'foo.bar@example.com', 'Query ID': u'1234'}

なので、これだけが Athena で取り扱えればひとまずは充分という指針のもと、以下のような仕組みを入れています。

  1. Redash サーバが CloudWatch Logs へログを全て出力する
  2. CloudWatch Logs は log subscription filter を介して Data Firehose へログを送出する
    • この際に必要なログだけを Data Firehose へ送出するようフィルタ設定を行う
  3. Data Firehose は最終目的地の S3 バケットへログを送出する

Terraform コードとしては以下のようになります。EC2 から送られてくるログを一旦集める CloudWatch Logs ロググループの用意から S3 バケットまでを担当しています。

こちらを参照

data "aws_caller_identity" "current" {}

/*
 Redash ログ保管用
 EC2 内で docker-compose にて動かしているコンテナ群からログを飛ばす想定
 */
resource "aws_cloudwatch_log_group" "redash_log" {
  name              = "/${var.env}-redash"
  retention_in_days = 7 # ほとんど実用に堪えない内容なので長く保持しておく必要なし
}

resource "aws_cloudwatch_log_subscription_filter" "redash_log" {
  name           = "${var.env}-redash-log-to-data-firehose"
  log_group_name = aws_cloudwatch_log_group.redash_log.name

  /*
    クエリ実行ログでしか Redash 操作者を絞れない
    このログは "Inserting job" を含むものになるので、こいつだけを S3 へ送出するログとする
   */
  filter_pattern = "Inserting job"

  destination_arn = aws_kinesis_firehose_delivery_stream.redash_log.arn
  role_arn        = aws_iam_role.redash_log_to_data_firehose.arn
}

# Redash ログを Athena で取り扱えるように S3 へ保存したい。そのためのバケット
resource "aws_s3_bucket" "redash_log" {
  bucket = "${var.env}-redash-log"
}

# 半年程度はログを確保できておくようにする。監査目的であればもっと長く取っておくべきかも
resource "aws_s3_bucket_lifecycle_configuration" "redash_log" {
  bucket = aws_s3_bucket.redash_log.id
  rule {
    status = "Enabled"
    id     = "delete after 180 days"
    expiration {
      days = 180
    }
    filter {
      prefix = ""
    }
  }
}

resource "aws_s3_bucket_versioning" "redash_log" {
  bucket = aws_s3_bucket.redash_log.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_public_access_block" "redash_log" {
  bucket                  = aws_s3_bucket.redash_log.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_server_side_encryption_configuration" "redash_log" {
  bucket = aws_s3_bucket.redash_log.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

data "aws_iam_policy_document" "redash_log_bucket_policy" {
  statement {
    effect    = "Allow"
    actions   = ["s3:PutObject"]
    resources = ["${aws_s3_bucket.redash_log.arn}/*"]

    principals {
      type        = "Service"
      identifiers = ["logging.s3.amazonaws.com"]
    }

    condition {
      test     = "StringEquals"
      variable = "aws:SourceAccount"
      values   = [data.aws_caller_identity.current.account_id]
    }
  }
}

resource "aws_s3_bucket_policy" "redash_log" {
  bucket = aws_s3_bucket.redash_log.bucket
  policy = data.aws_iam_policy_document.redash_log_bucket_policy.json
}

/*
 CloudWatch Logs から S3 へログを保存するための Data Firehose リソース
 CW Logs 側 subscription filter を経由して流れてくるデータを扱う
 */
resource "aws_kinesis_firehose_delivery_stream" "redash_log" {
  name        = "${var.env}-redash-log"
  destination = "extended_s3"

  extended_s3_configuration {
    role_arn   = aws_iam_role.redash_log_to_s3.arn
    bucket_arn = aws_s3_bucket.redash_log.arn

    buffering_size = 64 # MB 単位。dynamic partitioning が有効の場合必須

    dynamic_partitioning_configuration {
      enabled = "true"
    }

    processing_configuration {
      enabled = "true"
      processors {
        type = "MetadataExtraction"
        parameters {
          parameter_name  = "JsonParsingEngine"
          parameter_value = "JQ-1.6"
        }
        parameters {
          parameter_name  = "MetadataExtractionQuery"
          parameter_value = "{prefix: {dummy: (\"logs\")} | .dummy}" # 実質的に "logs" という固定文字列を返すだけ
        }
      }
      processors {
        type = "Decompression"
        parameters {
          parameter_name  = "CompressionFormat"
          parameter_value = "GZIP"
        }
      }
      processors {
        type = "AppendDelimiterToRecord"
      }
    }

    prefix              = "!{partitionKeyFromQuery:prefix}/${data.aws_caller_identity.current.account_id}/!{timestamp:yyyy}/!{timestamp:MM}/!{timestamp:dd}/"
    error_output_prefix = "error/!{firehose:error-output-type}/${data.aws_caller_identity.current.account_id}/!{timestamp:yyyy}/!{timestamp:MM}/!{timestamp:dd}/"

    cloudwatch_logging_options {
      enabled         = true
      log_group_name  = aws_cloudwatch_log_group.redash_log.name
      log_stream_name = "S3Delivery"
    }
  }
}

Athena テーブル / ビューの例は以下のようになります。Redash ログは少々やんちゃな内容となる場合があり、冗長な表記になっています。

CREATE TABLE 文

CREATE EXTERNAL TABLE "redash_server_log_source" (
    messageType string,
    owner string,
    logGroup string,
    logStream string,
    subscriptionFilters array<string>,
    logEvents array<struct<id:string, timestamp:bigint, message:string>>
)
PARTITIONED BY (
    `timestamp` string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://mntsq-ops-redash-log/logs/465191204183/'
TBLPROPERTIES (
    'projection.enabled' = 'true',
    'projection.timestamp.type' = 'date',
    'projection.timestamp.format' = 'yyyy/MM/dd',
    'projection.timestamp.range' = '2025/01/01,NOW',
    'projection.timestamp.interval' = '1',
    'projection.timestamp.interval.unit' = 'DAYS',
    'storage.location.template' = 's3://mntsq-ops-redash-log/logs/465191204183/${timestamp}/'
);

CREATE VIEW 文

CREATE OR REPLACE VIEW redash_server_log AS
WITH
unnested_logs AS (
    SELECT
        "day",
        l.id AS event_id,
        from_unixtime(l."timestamp" / 1000) AS timestamp,
        l.message
    FROM
        "redash_server_log_source"
    CROSS JOIN
        UNNEST(logEvents) AS t (l)
),
parsed_logs AS (
    SELECT
        *,
        -- TRY関数を追加
        TRY(regexp_extract(message, '([ -:,0-9]+)', 1)) AS log_timestamp,
        TRY(regexp_extract(message, '\[PID:([0-9]+)\]', 1)) AS pid,
        TRY(regexp_extract(message, '\[([A-Z]+)\]', 1)) AS log_level,
        TRY(regexp_extract(message, 'Inserting job for ([a-f0-9]+)', 1)) AS job_id,
        TRY(regexp_extract(message, '''Username'': u''([^'']+)''', 1)) AS username,
        TRY(regexp_extract(message, '''Query ID'': u''([^'']+)''', 1)) AS query_id
    FROM
        unnested_logs
)
SELECT
    "day",
    event_id,
    timestamp,
    log_timestamp,
    pid,
    log_level,
    job_id,
    username,
    query_id,
    message AS original_message
FROM
    parsed_logs;

なおテーブル以外にビューを作成しているのは Data Firehose 内でログの整形などをせず S3 に送出しているために本来欲しいログが message エントリ内に押し込められている点の対策によります。これは拙稿の以下と同様です。

tech.mntsq.co.jp

おわりに

IAM Identity Center を IdP として SSO アクセスするケースについて、Redash を題材に解説しました。Redash への SSO アクセスに必要な情報よりもログ関連のオペレーションに関する内容のほうが多いように思い、本稿題名をミスったのではないかと今にして思ってしまいました。

SSO アクセスすることでの ID 管理手法の整頓やサービス利便性向上もさることながら、利用対象とは別の箇所でログが得られることでの状況把握が出来るようになるというメリットも、外部 IdP を使うことでのメリットといえるでしょう。本稿で述べたように CloudTrail ログが使える場合であってもその探索には難儀するケースがありますが、手を尽くせば追跡は可能という事実に勇気付けられる面もあり、また実務上も有益な点はあると感じました。

Redash をお使いの方で IdP の選定に悩まれている方の一助になれば幸いです。

MNTSQ 株式会社 SRE 秋本