技術部の小池です。
この記事は面白法人グループ Advent Calendar 2025の12日目の記事です。
AWSでDifyをセルフホストした際のアクセス制限についての取り組みを紹介します。
Difyのセルフホスト版におけるアクセス制限問題
DIfyはノーコードで生成AIアプリケーションを構築できるプラットフォームです。SaaS版とセルフホスト版があり、セルフホスト版ではインフラコストはかかるものの、Dify自体は無料で利用することができます。
セルフホスト版では、メールサーバの設定を未設定にしておくことで事前に招待したユーザのみDifyの画面にアクセスできるよう制限することができます。一方で、Difyで作成し公開したDifyアプリケーション自体は誰でもアクセスできてしまいます。社内などの制限された環境にDifyアプリケーションを提供したい場合には問題になります。
Difyを許可されたドメインでのみ利用できるようにする
そこで、弊社の谷脇が作成したcoastguardというOSSプロダクトを用いて、許可されたドメインでのみDifyアプリケーションを利用できるよう制限していきます。
coastguardの概要をREADME.ja.mdから引用します。
Coastguardは、AWS CloudFrontディストリビューションの前に配置される認証レイヤーです。OIDC(OpenID Connect)プロバイダー(例: Google)とCloudFront署名付きCookieを使用して、CloudFrontによって保護されているオリジンリソース(S3でホストされる静的Webアセット、Origin Access Control (OAC) で設定されたLambda Function URLやApplication Load Balancer (ALB) など)へのアクセスを認証されたユーザーのみに許可します。
coastguardはCloudFrontレイヤーで認証の仕組みを提供します。これにより既存のアプリケーション側のインフラ構成に手を加えることなく導入することができます。
今回構築したDify環境は以下のような構成になっています。

Amazon CloudFrontとApplication Load Balancerがあり、AWS FargateのServiceとしてDifyのアプリケーションがあり、データストアとしてAmazon Aurora PostgreSQLやAmazon S3などがあるという一般的なWebアプリケーションと同様の構成です。
coastguardの導入
上記の構成のDify環境に対して、coastguard用に以下のようなTerraformリソースを定義します。 はじめにAWS Lambdaまわりのリソースです。
resource "aws_lambda_function" "coastguard" { function_name = "dify-coastguard" handler = "bootstrap" runtime = "provided.al2023" filename = "${path.module}/coastguard.zip" role = aws_iam_role.coastguard.arn architectures = ["arm64"] memory_size = 128 timeout = 3 environment { variables = { SSMWRAP_PATHS = "/dify/coastguard/*" OIDC_ISSUER = "https://accounts.google.com" PRESIGN_COOKIE_AGE = "10h" RESTRICT_PATH = "/" ALLOWED_DOMAINS = "kayac.com,example.com" CLOUDFRONT_KEY_PAIR_ID = aws_cloudfront_public_key.coastguard.id } } logging_config { log_group = aws_cloudwatch_log_group.coastguard.name log_format = "Text" } } resource "aws_lambda_alias" "coastguard_current" { name = "current" function_name = aws_lambda_function.coastguard.arn function_version = "$LATEST" lifecycle { ignore_changes = all } } resource "aws_lambda_function_url" "coastguard" { function_name = aws_lambda_function.coastguard.function_name authorization_type = "AWS_IAM" qualifier = aws_lambda_alias.coastguard_current.name } resource "aws_lambda_permission" "allow_cloudfront_coastguard" { statement_id = "AllowCloudFrontServicePrincipalCoastguard" action = "lambda:InvokeFunctionUrl" function_name = aws_lambda_function.coastguard.function_name principal = "cloudfront.amazonaws.com" source_arn = aws_cloudfront_distribution.main.arn qualifier = aws_lambda_alias.coastguard_current.name } resource "aws_cloudwatch_log_group" "coastguard" { name = "/lambda/dify/coastguard" retention_in_days = 30 }
今回はOIDCプロバイダーとしてGoogleを使っています。
ここでAWS Lambdaに渡すALLOWED_DOMAINS envで許可するドメインのリストをカンマ区切りで記述できます。今回の記述ではkayac.comドメインとexample.comドメインが許可されます。
続いてAmazon CloudFrontまわりです。
resource "aws_cloudfront_distribution" "main" { enabled = true is_ipv6_enabled = true http_version = "http2and3" aliases = [var.domain] comment = "dify" default_cache_behavior { trusted_key_groups = [aws_cloudfront_key_group.coastguard.id] trusted_signers = [] 省略 } ordered_cache_behavior { allowed_methods = ["GET", "HEAD", "OPTIONS"] cached_methods = ["GET", "HEAD"] target_origin_id = aws_lambda_function.coastguard.function_name cache_policy_id = data.aws_cloudfront_cache_policy.Managed-CachingDisabled.id origin_request_policy_id = data.aws_cloudfront_origin_request_policy.Managed-AllViewerExceptHostHeader.id viewer_protocol_policy = "redirect-to-https" compress = true default_ttl = 0 max_ttl = 0 min_ttl = 0 path_pattern = "/__auth/*" smooth_streaming = false trusted_key_groups = [] trusted_signers = [] } origin { アプリケーションのオリジン 省略 } origin { connection_timeout = 10 domain_name = "${aws_lambda_function_url.coastguard.url_id}.lambda-url.${data.aws_region.current.region}.on.aws" origin_access_control_id = aws_cloudfront_origin_access_control.coastguard.id origin_id = aws_lambda_function.coastguard.function_name custom_origin_config { http_port = 80 https_port = 443 origin_keepalive_timeout = 5 origin_protocol_policy = "https-only" origin_read_timeout = 30 origin_ssl_protocols = ["TLSv1.2"] } } restrictions { geo_restriction { restriction_type = "none" } } custom_error_response { error_caching_min_ttl = 10 error_code = 403 response_code = 403 response_page_path = "/__auth/unauthorized" } viewer_certificate { 省略 } } resource "aws_cloudfront_public_key" "coastguard" { name = "coastguard" encoded_key = file("public_key.pem") } resource "aws_cloudfront_key_group" "coastguard" { name = "coastguard" items = [aws_cloudfront_public_key.coastguard.id] } resource "aws_cloudfront_origin_access_control" "coastguard" { name = "coastguard" origin_access_control_origin_type = "lambda" signing_behavior = "always" signing_protocol = "sigv4" }
公開鍵をAmazon CloudFrontに登録し、CloudFrontが署名付きCookie検証してアクセス制御を行います。ここで未認証の場合にcoastguardのLambda Function URLの認証用エンドポイント /__auth/unauthorized にリダイレクトされるようになっています。
最後にOIDCプロバイダーのクライアントID/シークレット情報や署名用のキーペア、AWS Lambdaで使用するcredentialsです。
OIDCプロバイダーの情報は oidc.json として保存し、キーペアは private_key.pem、public_key.pem として保存しています。
locals { oidc_file_content = file("${path.module}/oidc.json") oidc_file_exists = fileexists("${path.module}/oidc.json") oidc_data = local.oidc_file_exists ? jsondecode(local.oidc_file_content) : {} oidc_client_id = local.oidc_file_exists ? try(local.oidc_data.web.client_id, local.oidc_data.client_id) : null oidc_client_secret = local.oidc_file_exists ? try(local.oidc_data.web.client_secret, local.oidc_data.client_secret) : null } resource "aws_ssm_parameter" "coastguard_CLIENT_ID" { name = "/dify/coastguard/CLIENT_ID" type = "String" value = local.oidc_client_id } resource "aws_ssm_parameter" "coastguard_CLIENT_SECRET" { name = "/dify/coastguard/CLIENT_SECRET" type = "SecureString" value = local.oidc_client_secret } resource "aws_ssm_parameter" "coastguard_SESSION_SECRET" { name = "/dify/coastguard/SESSION_SECRET" type = "SecureString" value = data.external.random_bytes.result["rand"] } data "external" "random_bytes" { program = ["bash", "-c", "openssl rand -base64 32 | jq -R '{rand: (. | sub(\"=+$\"; \"\"))}'"] } resource "aws_ssm_parameter" "coastguard_BASE_URL" { name = "/dify/coastguard/BASE_URL" type = "String" value = "https://${aws_route53_record.YOUR_ROUTE53_RECORD.fqdn}/" } resource "aws_ssm_parameter" "coastguard_SIGN_PRIVATE_KEY" { name = "/dify/coastguard/SIGN_PRIVATE_KEY" type = "SecureString" value = fileexists("private_key.pem") ? sensitive(file("private_key.pem")) : null }
coastguardのリポジトリにはサンプルのTerraform一式があるのでそちらもあわせて見ていただくとよりイメージが湧くかと思います。
coastguardを導入して初回アクセスすると以下のようなOIDCプロバイダー(今回はGoogle)の認証の画面にリダイレクトされます。許可されたドメインで認証すればこれ以降は通常通りアクセスできるようになります。

おわりに
今回はDifyアプリケーションへのアクセスを許可されたドメインのみに制限するというものでしたが、coastguardはAmazon CloudFrontに認証レイヤーを提供するものなので汎用的に使うことができます。
ひとつのCloudFrontにちょっとした社内ツールがたくさんぶら下がっていて、会社のドメインだけに利用を制限したいといったユースケースなどにもまとめて認証の仕組みを提供できて便利です。
- TerraformとAWS FargateのService/Task定義をClaude Codeに読ませてdraw.ioのXML形式で出力させたら一発でほとんど完璧な構成図が出てきました。便利な世の中ですね。↩