🚩

SST × お名前.comのAWSフロントエンド環境構築(Basic認証付き)

に公開

はじめに

こんにちは、株式会社ゼンビット所属のゆうとです。

IaCの選択肢のひとつとしてSSTというものを知りました。
Basic認証と、ドメイン設定を行いつつ、爆速でNext.jsをデプロイしていきます!!

対象読者

  • なるべく早くAWS環境構築を進めたい人
  • SST興味を持っている方
  • 説明する内容:
    1. SSTのデプロイ
    2. 認証導入
    3. お名前.comのドメイン設定
  • 説明しない内容:
    1. Next.jsのアプリ開発部分
    2. AWS認証、GitHub ActionsやSSTに関する説明

背景

もともと Next.js + CDKTF で AWS インフラ構築を進めていたプロジェクトについて、

  • 構成がだんだん複雑化してメンテしづらい。
  • 小規模なのにIAMなどの複雑化が煩わしい。

といった課題がありました。

そこで「SST を使った構成ならシンプルになるのでは?」と社内で提案を受けました。
調べてみたところかなりシンプルにまとまりそうだったため、検証してみます。


本題

構成など

  • Node.js v20
  • pnpm v8 系
  • Next.js v15(standalone output mode) フロントエンド
  • SST (v3.x)

まずは通常の環境構築

まずは自分のプロジェクトにSST公式のモノレポひな形を取得します。

https://sst.dev/docs/set-up-a-monorepo/

自分のプロジェクト配下に所定のリポジトリをクローン

git clone https://github.com/sst/monorepo-template.git

公式記載の通りアプリの名前を変更

npx replace-in-file /monorepo-template/g trainings **/*.* --verbose

npmインストールして

npm install

生成されたsst.config.tsを更新する

sst.config.ts
/// <reference path="./.sst/platform/config.d.ts" />

export default $config({
  app(input) {
    return {
      name: "trainings",
      removal: input?.stage === "production" ? "retain" : "remove",
      protect: ["production"].includes(input?.stage),
      home: "aws",
    };
  },
  async run() {
-    const storage = await import("./infra/storage");
-    await import("./infra/api");
+    const stage = $app.stage; 
+    const site = new sst.aws.Nextjs(`${stage}-site`,{
+      path: "apps/web" // Next.js プロジェクトのパス
+    });
    return {
-      MyBucket: storage.bucket.name,
+      site: site.url,
    };
  },
});

Basic認証を設定

SST公式でBasicAuthで検索。

https://sst.dev/docs/examples/#aws-nextjs-basic-auth

sst.Secretの利用を推奨されている。
本プロジェクトのデプロイはGitHub Actions。
sst.Secretを使わず、GitHubのsecretsをenv設定すれば問題ないかと判断しました。
どなたかsst.Secretの魅力教えてください。

公式にある通りBasic認証を追記。

sst.config.ts
/// <reference path="./.sst/platform/config.d.ts" />

export default $config({
  app(input) {
    return {
      name: "trainings",
      removal: input?.stage === "production" ? "retain" : "remove",
      protect: ["production"].includes(input?.stage),
      home: "aws",
    };
  },
  async run() {
    const stage = $app.stage; 
+    const isProduction = stage === "production";

+    const username = process.env.BASIC_AUTH_USER;
+    const password = process.env.BASIC_AUTH_PASS;

+    const basicAuth = $resolve([username, password]).apply(
+      ([username, password]) =>
+        Buffer.from(`${username}:${password}`).toString("base64")
+    );

    const site = new sst.aws.Nextjs(`${stage}-site`,{
      path: "apps/web", // Next.js プロジェクトのパス
      
+      edge: !isProduction
+        ? {
+          viewerRequest: {
+            injection: $interpolate`
+              if (
+                  !event.request.headers.authorization
+                    || event.request.headers.authorization.value !== "Basic ${basicAuth}"
+                  ) {
+                return {
+                  statusCode: 401,
+                  headers: {
+                    "www-authenticate": { value: "Basic" }
+                  }
+                };
+              }`,
+          },
+        }
+        : undefined,
    });
    return {
      site: site.url,
    };
  },
});

envを設定

.env.staging
BASIC_AUTH_USER=trainings
BASIC_AUTH_PASS=tests

AWS認証して

aws sso login --profile XXXXX

デプロイ

sst deploy --stage staging

追加できた。
ベーシック認証

ドメイン設定

SST公式でDomainで検索。

https://sst.dev/docs/component/aws/solid-start/#domain

ACM証明書やdns設定はRoute 53、Cloudflare、Vercel のみサポートみたいですね。
仕方なしなのでACM証明書を作成⇒お名前.com側のDNSレコード設定を手動で行います。

まずはAWSコンソール画面からACM作成しました。
ARNは設定必要なので控えときます。
acm

お名前.com側でACM証明書のドメインに応じたDNSレコード作成。
ドメイン

ついでにドメインからCloudFront発行の配信URLに転送するDNSレコードも作成しまいます。
転送

公式にある通りドメイン設定を追記。

sst.config.ts
/// <reference path="./.sst/platform/config.d.ts" />

export default $config({
  app(input) {
    return {
      name: "trainings",
      removal: input?.stage === "production" ? "retain" : "remove",
      protect: ["production"].includes(input?.stage),
      home: "aws",
    };
  },
  async run() {
    const stage = $app.stage; // 環境分けたいのでstageを設定
    const isProduction = stage === "production";

    const username = process.env.BASIC_AUTH_USER;
    const password = process.env.BASIC_AUTH_PASS;
+    const domainName = process.env.DOMAIN_NAME;
+    const certificateArn = process.env.CERTIFICATE_ARN;

    const basicAuth = $resolve([username, password]).apply(
      ([username, password]) =>
        Buffer.from(`${username}:${password}`).toString("base64")
    );

    const site = new sst.aws.Nextjs(`${stage}-site`,{
      path: "apps/web", // Next.js プロジェクトのパス
      edge: !isProduction
        ? {
          viewerRequest: {
            injection: $interpolate`
              if (
                  !event.request.headers.authorization
                    || event.request.headers.authorization.value !== "Basic ${basicAuth}"
                  ) {
                return {
                  statusCode: 401,
                  headers: {
                    "www-authenticate": { value: "Basic" }
                  }
                };
              }`,
          },
        }
        : undefined,
+      domain:
+        domainName && certificateArn ? 
+        { name:domainName,
+          dns: false,
+          cert: certificateArn
+        } : undefined,
    });
    return {
      site: site.url,
    };
  },
});

envも更新して

.env.staging
BASIC_AUTH_USER=trainings
BASIC_AUTH_PASS=tests
DOMAIN_NAME=www.XXXXXX.com
CERTIFICATE_ARN=arn:aws:acm:us-east-1:XXXXXXX:certificate/XXXXXXXXXXXXXXXX

デプロイ

sst deploy --stage staging

デプロイ成功!
ドメイン
outputの部分が重複しているので、不要ですね。。。


結論・まとめ

結果

  • とんでもない速さとシンプルな構成でデプロイできました。
    更新したファイルは「sst.config.ts」のみ。

課題

  • ACM 証明書の作成は手作業が必要でした。
    → ここだけAWS CDKで作成するか、サポート対象のDNSを使うのが現実的かもしれません。
  • 細かい設定はプロパティ不足でハマりやすく、調整が難しい部分があります。

全体を通しての感想

  • とにかく素早く環境構築したい人には SST はかなりおすすめ!

Discussion