はじめに
こんにちは、Platform Engineeringに所属している縄司です。
今日は、以前紹介したデータカタログの共有方法をアップデートしましたのでご紹介します。
出力先の現状と課題
https://blog.howtelevision.co.jp/entry/2024/02/16/161754に記載していますが、SchemaspyやDBTで作成したデータカタログをGitHub Action経由でSlackに出力させる方法を採用しています。
これにより、全社員が最新のデータカタログにアクセスすることができるようになりました。少しずつ社内でも活用される場面が増えていることは嬉しい限りです。
ただ利用する中で感じたこととしては、都度ファイルをダウンロードして確認するのが面倒臭いということです。ダウンロードした後ローカルで最新のファイルを探したり、古いバージョンが溜まっていくことに嫌気がさしてきます。
そこでエンジニア・非エンジニア問わず社員限定で閲覧できるという要件を満たし、より手軽にアクセスが可能な状態にできないかと模索しました。
元々Amazon S3にデータを保管しアクセスする案があったのですが、社内限定で公開するためには非エンジニアの方もアカウントを発行する必要があり管理コストが増えるため不採用となっていました。
ユーザー認証ができかつユーザー管理の負担が少ないツールはないかと探している時に見つけたのがAWS Cognitoです。
AWS Cognito
Amazon Cognitoは、AWSが提供する認証およびユーザー管理サービスでGoogleやFacebook、Login with Amazon、Appleなど様々な外部IDプロバイダーにも対応しています。
そのため、Google認証でアクセスできるようにすれば余計なユーザー管理が不要になります。
また50,000 MAU(月間アクティブユーザー)まで無料利用枠があるため、社内利用のみであるならば基本無料で使うことができます。
https://aws.amazon.com/jp/cognito/
これを基に環境を再構築することにしました。
実装の流れ
構想
AWS Cognitoはユーザーの認証部分のみを担う機能なので、実際にS3にアクセスするためにCloudfrontとLambda@Edgeを採用し全体の流れを構成しました。
実装はTerraformで行っていますが、全てのリソースを一度にApplyするとCycle Errorが発生し、うまく構築することができなかったので段階的に実装しています。
1. GCPでOAuth 2.0クライアントIDを作成
Google認証をCognitoで利用するための設定です。
- APIとサービス > 認証情報に移動
- 「認証情報を作成」ボタンをクリックし、「OAuth クライアントID」を選択
- 「同意画面の設定」で、必要な情報を入力
「アプリケーションの種類」を「ウェブアプリケーション」に設定し、名前を入力
※ User Typeを内部に設定することで組織内だけのアクセスに制限することができます。
「承認されたリダイレクトURI」は、Cognitoの名称とリージョンが決まっていれば
{cognito name}.{region}.amazoncognito.com
を入力
以上により生成されたクライアントIDとクライアントシークレットを保持しておきます。
2. Cognitoの作成
生成されたクライアントIDとクライアントシークレットを基に実装します。
# cognito.tf resource "aws_cognito_user_pool" "access_via_google_auth" { name = "access-via-google-auth" admin_create_user_config { allow_admin_create_user_only = false } auto_verified_attributes = ["email"] deletion_protection = "INACTIVE" email_configuration { email_sending_account = "COGNITO_DEFAULT" } mfa_configuration = "OFF" user_attribute_update_settings { attributes_require_verification_before_update = ["email"] } username_attributes = ["email"] username_configuration { case_sensitive = false } verification_message_template { default_email_option = "CONFIRM_WITH_CODE" } } resource "aws_cognito_user_pool_client" "access_via_google_auth" { name = "access-via-google-auth" user_pool_id = aws_cognito_user_pool.access_via_google_auth.id allowed_oauth_flows = ["code"] allowed_oauth_flows_user_pool_client = true allowed_oauth_scopes = ["email", "openid", "phone"] callback_urls = ["**********"] ## リダイレクト先を設定 supported_identity_providers = ["Google"] enable_token_revocation = true explicit_auth_flows = ["ALLOW_REFRESH_TOKEN_AUTH", "ALLOW_USER_SRP_AUTH"] token_validity_units { access_token = "days" id_token = "days" refresh_token = "days" } access_token_validity = 1 id_token_validity = 1 refresh_token_validity = 7 # 7日間 } resource "aws_cognito_user_pool_domain" "access_via_google_auth" { domain = "access-via-google-auth" user_pool_id = aws_cognito_user_pool.access_via_google_auth.id } resource "aws_cognito_identity_provider" "google_provider" { user_pool_id = aws_cognito_user_pool.access_via_google_auth.id provider_name = "Google" provider_type = "Google" provider_details = { authorize_scopes = "email" client_id = "***********.apps.googleusercontent.com" ## Google クライアントID client_secret = "**************" ## Google クライアントシークレット } attribute_mapping = { email = "email" username = "sub" } }
この段階で一度terraform applyを実行します。
完了した後はGCP側で先ほどスキップした「承認されたリダイレクトURI」に、cognitoのドメインを入力します。
Cloudfront + Lambda@Edge + S3の作成
S3は、Cloudfront側からのみのアクセス制限をかけ実装しています。
# s3.tf resource "aws_s3_bucket" "data_catalog" { bucket = "data-catalog" } resource "aws_s3_bucket_policy" "data_catalog" { bucket = aws_s3_bucket.data_catalog.id policy = jsonencode( { Statement = [ { Sid = "CloudFrontAccessS3" Effect = "Allow" Principal = { "Service" : "cloudfront.amazonaws.com" } Action = "s3:GetObject" Resource = "arn:aws:s3:::${aws_s3_bucket.data_catalog.bucket}/*" Condition = { "ArnLike" : { "aws:SourceArn" : format("arn:aws:cloudfront::%s:distribution/%s", local.aws_account_id, aws_cloudfront_distribution.data_catalog.id) } } }, ] Version = "2012-10-17" } ) } output "aws_s3_bucket_name" { value = aws_s3_bucket.data_catalog.bucket }
Cloudfront側は次のように実装しています。
# cloudfront.tf resource "aws_cloudfront_cache_policy" "cache_policy" { name = "cloudfront-cache-policy" min_ttl = 1 max_ttl = 6000 default_ttl = 600 parameters_in_cache_key_and_forwarded_to_origin { headers_config { header_behavior = "none" } cookies_config { cookie_behavior = "none" } query_strings_config { query_string_behavior = "none" } } } resource "aws_cloudfront_distribution" "data_catalog" { origin { domain_name = aws_s3_bucket.data_catalog.bucket_regional_domain_name origin_id = aws_s3_bucket.data_catalog.id # OACの設定 origin_access_control_id = aws_cloudfront_origin_access_control.main.id } enabled = true default_root_object = "index.html" default_cache_behavior { allowed_methods = ["GET", "HEAD"] cached_methods = ["GET", "HEAD"] target_origin_id = aws_s3_bucket.data_catalog.id compress = true cache_policy_id = aws_cloudfront_cache_policy.cache_policy.id lambda_function_association { event_type = "viewer-request" include_body = false lambda_arn = aws_lambda_function.cognito_edge.qualified_arn } viewer_protocol_policy = "redirect-to-https" } price_class = "PriceClass_200" restrictions { geo_restriction { restriction_type = "none" locations = [] } } viewer_certificate { cloudfront_default_certificate = true } } resource "aws_cloudfront_origin_access_control" "main" { name = aws_s3_bucket.data_catalog.bucket origin_access_control_origin_type = "s3" signing_behavior = "always" signing_protocol = "sigv4" }
Lambdaは、cloudfrontにアクセスした際にcognitoの認証を経由させる目的ですが、cloudfrontではlambda@edgeしか対応していないためus-east-1
にリージョンを設定しています。
またlambdaで使用する認証用のindex.jsには、cognitoのユーザープール情報を入力する必要がありますが、Terraformでは環境変数を直接index.jsに渡すことができないため、generate.shを作成し、動的にindex.jsを作成することで柔軟にcognitoの情報を渡せるようにしています。
## generate.sh #/bin/bash eval "$(jq -r '@sh "OUTPUT=\(.output) REGION=\(.region) USER_POOL_ID=\(.user_pool_id) USER_POOL_APP_ID=\(.user_pool_app_id) USER_POOL_DOMAIN=\(.user_pool_domain)"')" cat > "${OUTPUT}" <<EOF const {Authenticator} = require("cognito-at-edge"); const authenticator = new Authenticator({ region: "${REGION}", userPoolId: "${USER_POOL_ID}", userPoolAppId: "${USER_POOL_APP_ID}", userPoolDomain: "${USER_POOL_DOMAIN}", }); exports.handler = async (request) => authenticator.handle(request); EOF echo "{}"
## lambda.tf data "aws_iam_policy_document" "policy_for_lambda" { statement { effect = "Allow" principals { type = "Service" identifiers = [ "lambda.amazonaws.com", "edgelambda.amazonaws.com" ] } actions = ["sts:AssumeRole"] } } resource "aws_iam_role" "iam_for_lambda" { name = "CloudFrontAccessLambda" assume_role_policy = data.aws_iam_policy_document.policy_for_lambda.json } resource "aws_iam_role_policy_attachment" "lambda_basic_execution" { role = aws_iam_role.iam_for_lambda.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } # external data sourceを使用してスクリプトを呼び出す data "external" "generate_lambda_auth" { program = ["/bin/bash", "${path.module}/generate.sh"] query = { output = "${path.module}/lambdaEdge/index.js" region = var.cognito_region user_pool_id = var.cognito_user_pool_id user_pool_app_id = var.cognito_user_pool_app_id user_pool_domain = var.cognito_user_pool_domain } } data "archive_file" "cognito_lambdaedge" { type = "zip" source_dir = "./lambdaEdge" output_path = "./lambda_function_cognito_cloudfront.zip" } # Lambda関数の作成 resource "aws_lambda_function" "cognito_edge" { provider = aws.us_east_1 function_name = "cognito_edge" role = aws_iam_role.iam_for_lambda.arn handler = "index.handler" runtime = "nodejs20.x" filename = data.archive_file.cognito_lambdaedge.output_path source_code_hash = data.archive_file.cognito_lambdaedge.output_base64sha256 publish = true # Lambda関数のバージョニングを有効にする }
以上で、terraform applyで無事に作成されれば完了です。
まとめ
Cognito + Cloudfront + Lambda@Edge + S3を利用することで、データカタログを手軽にアクセスできるようになりました。CognitoはコールバックURLを複数指定できるため、1つ作成することで他の静的コンテンツに応用することができます。手軽なアクセスを可能にし、ドキュメントをより充実させていければと思います。
ゼロイチのTerraform実装は初めてでしたが、理解が深まり良いキャッチアップになりました!