dbt docsをS3+CloudFrontに公開しCognitoでログイン保護する【AWS・Lambda@Edge・CloudFront・CDK例付き】

はじめに
dbtで生成する dbt Docs(dbtプロジェクトのドキュメント)は、チーム内で共有するためにWeb上にホスティングしたいケースがあります。しかし、そのドキュメントは外部には公開せず、認可されたユーザーだけが閲覧できるように認証保護をかけたいところです。AWSのサービスを活用すると、サーバーレスかつセキュアにこの要件を満たすことができます。具体的には、Amazon CloudFrontによるコンテンツ配信にLambda@EdgeでAmazon Cognitoによるユーザー認証を行うようにすることで、ドキュメントサイトへのアクセスにログインを要求する仕組みを構築します。
本記事では、dbt DocsをCognito認証付きでCloudFrontにホスティングする方法について、あるサンプル実装を基に解説します。この実装により、dbtで生成した静的なドキュメントサイトをAWS上で手軽に公開しつつ、閲覧にはログイン(認証)を必要とするようにできます。後述のGitHub上のサンプルリポジトリにはAWS CDKを用いたインフラ構成とデプロイ用のスクリプト群が含まれておりますので、参考になれば幸いです。
今回作ったもの
ソースコード
下記のリポジトリに置いてあります。
サンプルのdbtプロジェクトとAWSインフラ構成コード、および各種コマンドをまとめたタスク定義(Taskを活用)を含みます。主なディレクトリ構成は以下の通りです。
dbt-sample/: サンプルのdbtプロジェクト一式が入っています。今回はデータ基盤にDatabricksを利用した構成になっており、接続情報などがこの中に定義されています(別のデータウェアハウスを使いたい場合は、このdbtプロジェクト配下の設定を変更すれば、インフラ側のコードはそのまま利用可能です)。infrastructure/: AWS CDK(TypeScript)によるインフラ構築用のコードが入っています。ここにCloudFrontやCognito、S3、Lambda@Edgeなど今回必要となるAWSリソースの定義が実装されています。TaskFile.yml: コマンド実行用のタスク定義ファイルです。ローカルでのdbt Docs生成やAWSリソースのデプロイ/更新処理を簡単に行えるよう、複数のタスクが記述されています。タスク実行ツールのTaskを利用しており、後述するように簡単なコマンドで一連の処理を実行できます。
概要
インフラ構成は下記のようになっています。

S3バケット: dbtが生成した静的なサイト(HTMLやJSONファイル)の置き場所であり、CloudFrontはここをオリジンとしてコンテンツを配信します。
CloudFrontディストリビューション: S3上のコンテンツをキャッシュしつつ、ユーザーからのリクエストのエントリーポイントとなります。今回はビューワーリクエスト時に認証チェックを行うように設定されており、認証が必要なリクエストの場合、CloudFrontは後述のLambda@Edge関数を呼び出します。
Amazon Cognitoユーザープール: ログイン用のユーザ管理を行うサービスです。社内ユーザー用のユーザープールと、そのHosted UI(ログイン画面)、アプリクライアントを設定し、このユーザープールで認証されたユーザーだけがドキュメントにアクセスできるようにします。
Lambda@Edge関数: CloudFrontに組み込まれるLambda関数で、閲覧リクエスト毎に実行されます。ここでCognitoの認証クッキーを検証し、未認証であればCognitoのログインページ(Hosted UI)へリダイレクトします。認証に成功した場合のみ、CloudFrontはS3からコンテンツを取得してユーザーにドキュメントを返します。
以上のように、ユーザーがCloudFrontのURLにアクセスするとまず認証フローが走り、Cognitoでログイン成功しない限りS3上のドキュメントにアクセスできない仕組みになっています。
妥協したポイント
このサンプル実装では認証付きホスティングの基本的な部分にフォーカスするため、いくつか妥協している点があります。
独自ドメイン未設定: CloudFrontのデフォルトドメイン(*.cloudfront.net)をそのまま利用しています。つまり、ユーザーは一旦このCloudFrontドメインにアクセスしCognitoのHosted UI(*.auth.のドメイン)でログインする形です。本必要であれば、Route 53やACMを用いてCloudFrontに独自ドメイン+証明書を割り当て、Cognito側もカスタムドメインを設定することで、独自ドメインでdbt Docsをホスティングできます。
ログアウト機能未実装: 現状ではログイン後のログアウト操作が用意されていません。一度ログインして認証クッキーが発行されると、セッションが有効な間はCloudFrontへのアクセスは常に許可され続けます。対応策としては、Lambda@Edge関数で特定のパスへのリクエストを受けた際にCognitoのログアウトエンドポイントへリダイレクトし、クッキーを無効化する処理を入れるなどが考えられますが、dbt docsの中にどのようにログアウトリンクを埋め込めるか⋯等、検討できていません。もし良い方法をご存知の方がいらっしゃいましたら、私のX等にご連絡いただければ幸いです。
スタック間の依存関係の解決: 今回、認証用のCognitoとドキュメントサイトホスティング用のCloudFront・S3・Lambda@Edgeでスタックを分割しました。CognitoのリダイレクトURIはCloudFront側のスタック(展開されるURL)に依存し、Lambda@Edgeで利用するCognitoのユーザープールの情報はCognito側のスタックに依存してしまうため、相互に依存があります。スマートな解決方法もありそうですが、今回は.gitignoreしてあるJSONファイルで受け渡したり、CloudFront側をデプロイした後再度Cognito側をデプロイしたり⋯など、依存関係の解決方法を雑に行っています。(一度作ってしまえば基本的にはdbtのdocsを更新するだけだと思うので、そんなに困らないかなーと思っています。)
使い方
事前準備
AWSアカウント: AWSリソースをデプロイする先のアカウントが必要です。適切な権限があるIAMユーザーやロールを用意してください。
AWS CLIプロファイル設定: CDKやデプロイ用スクリプトが利用するAWS認証情報として、作業ディレクトリで環境変数 AWS_PROFILE に有効なAWS CLIプロファイル名を設定しておきます。(ディレクトリごとに読み込む環境変数を変更したいことも多いと思うので、direnvがおすすめです。)
AWS CDK開発環境: リポジトリのインフラコードはAWS CDK (TypeScript)で書かれているため、Node.jsとnpmが使える環境を用意します。CDK Toolkitのインストールやブートストラップも必要です。今回デプロイするリソースにはLambda@Edgeが含まれるため、デフォルトのリージョン(例: ap-northeast-1)に加えて、us-east-1リージョンでもCDKのブートストラップを実行しておく必要があります(※Lambda@Edgeのリソースはus-east-1で管理される仕様のため)。
Taskコマンドのインストール: 本リポジトリでは前述のとおりTaskFile.ymlによってコマンドを簡単に実行できるようになっています。タスク実行ツールであるTaskをシステムにインストールしてください。
サンプルリポジトリを動かす
こちらのサンプルリポジトリを動かす場合以下の手順で進めてください。(※なお、ご自身のdbtプロジェクトを利用したい場合は、このサンプルの構成を参考にインフラ側のコードはそのまま使いつつ、dbtプロジェクト部分を適宜差し替えて利用することも可能です。)
1. リポジトリのクローン
事前に当該GitHubリポジトリをローカル環境にクローンし、作業ディレクトリをそのプロジェクトルートに移動しておきます。(作業ディレクトリで環境変数 AWS_PROFILE に有効なAWS CLIプロファイル名を設定しておくことも忘れずに)
2. dbt環境のセットアップ
サンプルに含まれるdbtプロジェクトを実行・ドキュメント生成できるように、Python環境を整えます。リポジトリではuvというツールでPythonパッケージ環境を管理していますので、インストールしてください。
dbt-sample フォルダ配下で
uv installすることで、サンプルのdbt-core + dbt-databricksのpython仮想環境が作られます。
3. Databricksのセットアップとdbtとの接続
今回、個人的にあまり触ったことがなかったので、Databricksを使ってみました。基本的には公式サイトの案内に従えばセットアップできると思います。
databricksとdbtの接続設定も、公式のこちらに従えば問題なく進められると思います
4. CDKのデプロイ
スタックの概要
AWS CDKを用いてクラウド上に必要なリソースを構築しています。リポジトリには予めCDKのスタックが定義済みで、CognitoStack(認証基盤)とDocsStack(ドキュメントサイト基盤)の2つに分かれています。それぞれのスタックの内容は以下の通りです。
CognitoStack
Amazon Cognitoのユーザープールを作成します。ユーザープールには今回専用のドメインを割り当てています。また、ドキュメントサイトからの認証要求に使うためのアプリクライアントもこのスタックで作成します。
※アプリクライアントには後でCloudFrontのURLをリダイレクト先として登録します。(先述の通り依存関係の解決方法を妥協している部分です。CognitoStackをデプロイした後DocsStackをデプロイするのですが、その後にCloudFrontのURLを環境変数に設定し、再度CognitoStackをデプロイする形にしています。)
DocsStack
S3バケット(dbt Docsホスティング用)、CloudFrontディストリビューション、およびLambda@Edge関数をセットアップします。CloudFrontディストリビューションは先述の通りS3をオリジンとしつつ、ビューワーリクエストに関連付けてLambda@Edge関数を呼び出す設定になっています。このLambda関数はCloudFront -> Originの前段で走り、Cognitoによる認証チェックとリダイレクト処理を実装しています。また、デプロイ後にわかるCloudFrontのドメイン名やS3バケット名などは、後続の処理で使えるようにスタックの出力(Outputs)としてエクスポートされます。
デプロイ手順
デプロイはTaskFile.ymlでも定義しています。
task deploy-allを実行すると、CognitoStack→DocksStackの順にデプロイされます。
下記のようにデプロイ結果の出力にCloudFrontのドメインが記載されています。

こちらのドメインを
CLOUDFRONT_URL=https://[出力されたドメイン].cloudfront.netの形で環境変数に設定したうえで
task deploy-cognitoを実行し、再度CognitoStackをデプロイすることで、Cognito側にリダイレクト先としてCloudFrontのURLが適用されます。
動作チェック
ログインに使用するユーザーアカウントは事前にCognitoのユーザープールで作成しておく必要があります。アプリからの新規ユーザー登録機能等は許可していませんので、AWSマネジメントコンソール等で直接ユーザーを追加してください。こちらの記事などが参考になります。
実際にCloudFrontのURLにアクセスすると、以下のようなCognitoのログイン画面が表示されます。

ユーザー名とパスワードを入力してログインすると、自動的にdbt Docsのページに遷移します。認証後に表示されたサイトは、dbtが生成したプロジェクトドキュメントのトップページです。モデルやソースのリスト、リネージ図などが閲覧できるようになります。


以上で、Cognito認証を備えたdbt Docsサイトのホスティングが実現できています。未認証ユーザーがドキュメントにアクセスすることはできません。
まとめ
AWSのマネージドサービスを組み合わせてdbt Docsを社内向けに安全に公開する仕組みを構築しました。Cognitoユーザープールによる認証とCloudFront + Lambda@Edgeによるアクセス制御を活用することで、静的サイトでありながら柔軟なログイン認証フローを実現しています。サーバーレス構成のため運用負荷やコストも抑えられ、必要に応じてユーザー追加や権限管理もCognito上で完結できます。AWS CDKを利用することで、環境の再現性が担保され、Infrastructure as Codeのメリットを享受できています。
以上、dbt DocsをCognito認証付きでCloudFrontにホスティングするサンプル実装について解説しました。社内向けのデータカタログやドキュメント共有に役立つアイデアとして、ぜひ参考にしてみてください。下記にリポジトリを再掲します。コードが公開されていますので、詳細を確認したい方はそちらも参照してください。