セルフホストDifyアプリケーションにアクセスできるドメインを制限したい

技術部の小池です。

この記事は面白法人グループ Advent Calendar 2025の12日目の記事です。

AWSでDifyをセルフホストした際のアクセス制限についての取り組みを紹介します。

Difyのセルフホスト版におけるアクセス制限問題

DIfyはノーコードで生成AIアプリケーションを構築できるプラットフォームです。SaaS版とセルフホスト版があり、セルフホスト版ではインフラコストはかかるものの、Dify自体は無料で利用することができます。

セルフホスト版では、メールサーバの設定を未設定にしておくことで事前に招待したユーザのみDifyの画面にアクセスできるよう制限することができます。一方で、Difyで作成し公開したDifyアプリケーション自体は誰でもアクセスできてしまいます。社内などの制限された環境にDifyアプリケーションを提供したい場合には問題になります。

Difyを許可されたドメインでのみ利用できるようにする

そこで、弊社の谷脇が作成したcoastguardというOSSプロダクトを用いて、許可されたドメインでのみDifyアプリケーションを利用できるよう制限していきます。

github.com

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環境は以下のような構成になっています。

インフラ構成図1

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.pempublic_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にちょっとした社内ツールがたくさんぶら下がっていて、会社のドメインだけに利用を制限したいといったユースケースなどにもまとめて認証の仕組みを提供できて便利です。

カヤックではエンジニアを募集しています!


  1. TerraformとAWS FargateのService/Task定義をClaude Codeに読ませてdraw.ioのXML形式で出力させたら一発でほとんど完璧な構成図が出てきました。便利な世の中ですね。