👀

Cloudflare Zero Trustを使ってプライベートなS3静的ホスティングサイトを閲覧する

2025/04/01に公開

はじめに

CIなどによって自動化されたテストは大量のアウトプットを出力しますが、必ずしもViewerが提供されているともかぎりません。
弊社のE2Eテストも同様で、Github Actionsで実行されたPlaywrightのArtifactsは都度ローカルマシンにダウンロードして解凍して閲覧していました。
Artifactsがそれなりに大きなサイズであることもあって、これらをプライベートかつサクっと見れるwebサイトにできませんか?というご相談があり、今回やってみました。

今回のキーワード

  • S3 Static Website Hosting
  • AWSマルチアカウント構成
  • Cloudflare Zero Trust(WARPを使わない&cloudflaredのみ)
  • Google Workspace Groupによるアクセス制御

構成

AWSにインフラ管理しているアカウント(以降A)と対象のS3バケットのある開発用アカウント(以降B)の2つがあります。
Cloudflare Zero Trustはsite.example.netにアクセスしたクライアントをGoogleとのOauthによって認証します。その後、認証されたクライアントのリクエストをAアカウントのプライベートなネットワークにルーティングし、AアカウントのVPCからS3へのプライベートなアクセスを実現します。

登場した構成要素

  • Aアカウント: インフラ管理用アカウント
    • Cloudflare用トンネルサーバ
    • S3へのVPCエンドポイント
  • Bアカウント: 開発用アカウント
    • S3静的ホスティングサイト
  • Cloudflare Zero Trust:
    • site.example.netの認証
    • Aアカウントへのトンネル
  • Google Workspace: Oauth認証のためのIDP
  • Github Actions: E2Eテストのワークロード

【AWS】プライベートなS3静的ホスティングサイトの設定

静的ホスティングサイトとして利用するS3バケットを作成します。
今回バケット名はsite.example.netです。

【AWS】VPCの設定

VPC Endpoint

AアカウントにBアカウントのS3リソースに対してのプライベートなVPC Endpointを作成します。
構成管理にterraformを利用しているので下記のような出力になります。(idなどはマスクしています)

# aws_vpc_endpoint.site["production"]:
resource "aws_vpc_endpoint" "site" {
    arn                   = "arn:aws:ec2:ap-northeast-1:account_A_id:vpc-endpoint/vpcendpoint_id"
    cidr_blocks           = [
        "X.X.X.X/XX",
    ]
    dns_entry             = []
    id                    = "vpcendpoint_id"
    network_interface_ids = []
    owner_id              = "account_A_id"
    policy                = jsonencode(
        {
            Statement = [
                {
                    Action    = "*"
                    Effect    = "Allow"
                    Principal = "*"
                    Resource  = "*"
                },
            ]
            Version   = "2008-10-17"
        }
    )
    private_dns_enabled   = falsed
    requester_managed     = false
    route_table_ids       = [
        "account_A_production_private_routetable_id",
    ]
    security_group_ids    = []
    service_name          = "com.amazonaws.ap-northeast-1.s3"
    state                 = "available"
    subnet_ids            = []
    tags                  = {
        "Name"    = "site-example-net-production-endpoint"
        "env"     = "production"
        "service" = "site-example-net"
    }

    vpc_endpoint_type     = "Gateway"
    vpc_id                = "accont_A_vpc_id"

    timeouts {}
}

マネジメントコンソールから作るときは、下記を押さえておきます。

  • サービスカテゴリはAWSのサービス
  • サービス名はcom.amazonaws.ap-northeast-1.s3
  • サービスのタイプはGateway
  • ルートテーブルはプライベート環境用のものを作成・選択

S3 Bucket Access Policy

アカウントBにあるS3バケットのポリシーを編集して前述のEndpointからのアクセスを許可します。
以下はポリシーのjsonです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowVPC",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::site.example.net",
                "arn:aws:s3:::site.example.net/*"
            ],
            "Condition": {
                "StringEquals": {
                    "aws:SourceVpce": [
                        "account_A_vpc_endpoint_id"
                    ]
                }
            }
        }
    ]
}

【Cloudflare】 Cloudflare Accessの設定

Tunnel

アカウントAにあるEC2にcloudflaredをインストールしてCloudflare->AWSのトンネルを開通させます。
CloudflareのマネジメントコンソールにログインしZero Trustを選択、遷移先のダッシュボードの左メニューからNetworks/Tunnelsを選択して、あとはトンネル作成のナビゲーションに従うのみ。
下記のようにパッケージインストールのコマンドまで用意されています。

curl -L --output cloudflared.rpm https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-aarch64.rpm && 
sudo yum localinstall -y cloudflared.rpm && 
sudo cloudflared service install xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

トンネル作成の公式ドキュメント

今回はEC2インスタンスにcloudfalredをインストールしました。その状態のインスタンスでイメージを作成しAutoscalingGroupのテンプレートに組みこむことで冗長化しています。

Domain

作成したトンネルの設定を編集をして、PublicHostnameにレコードを追加します。
serivceのURLはS3バケットの静的ホスティングサイト設定にあるバケットウェブサイトエンドポイントを入力します。

  • public hostname
    • subdomain: site
    • domain: example.net
  • service
    • type: http
    • URL: site.example.net.s3-website-ap-northeast-1.amazonaws.com

※本記事ではexample.netとしていますが、本来はCloudflareでサイトとして登録したドメインから選びます。

Authentication

Google WorkspaceをIDPとしてsite.example.netのアクセスに対してOauth認証を設定します。
Zero Trustダッシュボードの左メニューsettingsからAuthenticationに遷移しLogin methodsのAdd newを選択します。あとはナビゲーションの通りに作成して連携ができます。
名前はgoogle_workspace_auth_01にしています。

Google WorkspaceによるSSO手順の公式ドキュメント

Application

最後にLogin methodsに追加した認証方法を用いて具体的にsite.example.netを閲覧可能なポリシーを設定していきます。
Zero Trustのダッシュボードの左メニューAccessからApplicationに遷移してAdd an applicationします。基本的には今まで作成したリソースを指定して埋めていきます。

  • type: self-hosted
  • ApplicationConfiguration
    • ApplicationName: s3-private-static-hosting-site
    • ApplicationDomain: site.example.net
  • Authentication
    • IdentityProviders: google_workspace_auth_01

Policies

ポリシーを作成します。
ポリシーでは個別のメールアドレスなどで許可することもできますが、今回、Google Workspaceと連携したのでGoogle Group情報によるアクセス制限を適用してみます。Google Groupであれば閲覧を許可したいユーザへのアタッチが簡単ですし非開発者に運用をお任せしやすいためオススメです。

  1. Configure rulesのSelectorプルダウンからGoogle Groupsを選択します。
  2. Valueにはアクセス許可したいGroupのメールアドレスを入力します。
  3. Add policyして完了です。

Test

Chromeのシークレットモードでsite.example.comにアクセスしてみたところ、無事Googleのログインが求められるようになりました。

【Github Actions】E2Eテストの結果をS3へアップロード

workflow.yml

最後、site.example.netのバケットへPlaywright Artifactsをアップロードするようにします。
いろいろ端折ってはいますが、.github/workflowsは下記のような感じで記述しています。STATIC_SITE_S3_BUCKET変数に入る値は、今回作成したsite.example.netのバケット名です。

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - name: Run Playwright tests (default)
    - name: Upload Playwright report
      uses: actions/upload-artifact@v4
      if: always()
      id: upload-report
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 3
    - name: Upload Paywright report to S3
      id: s3-upload
      if: always()
      run: |
          dest=${{ vars.STATIC_SITE_S3_BUCKET }}/e2e/playwright-report_${{ github.run_number }}_$(date +%Y%m%d%H%M%S)
          aws s3 sync ./playwright-report/ s3://${dest}/
          echo "site-url=https://${dest}/index.html" >> $GITHUB_OUTPUT
    - name: Notify Slack
      if: ${{ always() && github.event_name != 'pull_request' }}
      uses: XXXXXXX/action-slack@v3
      with:
        status: custom
        custom_payload: |
          {
            "attachments": [
              {
                "color": '${{ job.status }}' === 'success' ? 'good' : 'danger',
                "title": '${{ job.status }}' === 'success' ? 'E2E Test Succeeded' : 'E2E Test Failed',
                "text": "テストの内容は以下のURLから確認できます。\n\nSiteURL: ${{ steps.s3-upload.outputs.site-url }}\n\nArtifactURL: ${{ steps.upload-report.outputs.artifact-url }}"
              }
            ]
          }

Test

slackに通知されたURLにアクセスしてみます。

ちゃんとPlaywrightの結果が表示されました!

まとめ

  1. AWSアカウントにS3バケットを作成し静的ホスティングサイト機能を有効にする
  2. S3へアクセスするVPCエンドポイントを作成し、バケットのポリシーによってVPCエンドポイントを許可する
  3. Cloudflare AccessのトンネルをAWSのVPC内のインスタンスにセットアップする
  4. CloudflareにS3静的ホスティングサイトのためのドメインとアクセスポリシーを設定する
  5. Github ActionsにS3アップロードとslack通知を追加する

以上でした。

Seibiiテックブログ

Discussion