diff --git a/.env.example b/.env.example index 79539fe0..eeea5d0c 100644 --- a/.env.example +++ b/.env.example @@ -10,8 +10,8 @@ SMTP_USER=test_userdadad@example.com # Example SMTP user AWS_DEFAULT_REGION="us-east-1" -AWS_SECRET_KEY="some-secret-key" -AWS_ACCESS_KEY="some-access-key" +AWS_ACCESS_KEY_ID="some-access-key" +AWS_SECRET_ACCESS_KEY="some-secret-key" AWS_SES_ENDPOINT="http://localhost:3003/api/ses" AWS_SNS_ENDPOINT="http://localhost:3003/api/sns" diff --git a/.env.selfhost.example b/.env.selfhost.example index d294bd34..976d7782 100644 --- a/.env.selfhost.example +++ b/.env.selfhost.example @@ -25,10 +25,12 @@ GITHUB_SECRET="" GOOGLE_CLIENT_ID="" GOOGLE_CLIENT_SECRET="" -# AWS details - required +# AWS details +# Provide static credentials OR rely on the AWS default credential chain +# (IAM role, ECS task role, instance profile, etc.) by omitting these vars. AWS_DEFAULT_REGION="us-east-1" -AWS_SECRET_KEY="" -AWS_ACCESS_KEY="" +AWS_ACCESS_KEY_ID="" +AWS_SECRET_ACCESS_KEY="" diff --git a/apps/web/.env.test.example b/apps/web/.env.test.example index 1f62dec1..58459ee8 100644 --- a/apps/web/.env.test.example +++ b/apps/web/.env.test.example @@ -5,8 +5,8 @@ NEXTAUTH_SECRET=test-secret DATABASE_URL=postgresql://usesend:password@127.0.0.1:54329/usesend_test REDIS_URL=redis://127.0.0.1:6380/15 -AWS_ACCESS_KEY=test-access-key -AWS_SECRET_KEY=test-secret-key +AWS_ACCESS_KEY_ID=test-access-key +AWS_SECRET_ACCESS_KEY=test-secret-key AWS_DEFAULT_REGION=us-east-1 NEXT_PUBLIC_IS_CLOUD=true diff --git a/apps/web/src/env.js b/apps/web/src/env.js index 9c37cdde..8e73959a 100644 --- a/apps/web/src/env.js +++ b/apps/web/src/env.js @@ -31,8 +31,8 @@ export const env = createEnv({ ), GITHUB_ID: z.string().optional(), GITHUB_SECRET: z.string().optional(), - AWS_ACCESS_KEY: z.string(), - AWS_SECRET_KEY: z.string(), + AWS_ACCESS_KEY_ID: z.string().optional(), + AWS_SECRET_ACCESS_KEY: z.string().optional(), USESEND_API_KEY: z.string().optional(), UNSEND_API_KEY: z.string().optional(), GOOGLE_CLIENT_ID: z.string().optional(), @@ -99,8 +99,8 @@ export const env = createEnv({ NEXTAUTH_URL: process.env.NEXTAUTH_URL, GITHUB_ID: process.env.GITHUB_ID, GITHUB_SECRET: process.env.GITHUB_SECRET, - AWS_ACCESS_KEY: process.env.AWS_ACCESS_KEY, - AWS_SECRET_KEY: process.env.AWS_SECRET_KEY, + AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY, + AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY || process.env.AWS_SECRET_KEY, USESEND_API_KEY: process.env.USESEND_API_KEY, UNSEND_API_KEY: process.env.UNSEND_API_KEY, GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, diff --git a/apps/web/src/server/aws/credentials.ts b/apps/web/src/server/aws/credentials.ts new file mode 100644 index 00000000..551dee82 --- /dev/null +++ b/apps/web/src/server/aws/credentials.ts @@ -0,0 +1,22 @@ +import { env } from "~/env"; + +export function getAwsCredentialOptions() { + const hasKey = !!env.AWS_ACCESS_KEY_ID; + const hasSecret = !!env.AWS_SECRET_ACCESS_KEY; + + if (hasKey !== hasSecret) { + throw new Error( + "AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must both be set or both be omitted" + ); + } + + if (hasKey) { + return { + credentials: { + accessKeyId: env.AWS_ACCESS_KEY_ID!, + secretAccessKey: env.AWS_SECRET_ACCESS_KEY!, + }, + }; + } + return {}; +} diff --git a/apps/web/src/server/aws/ses.ts b/apps/web/src/server/aws/ses.ts index 0fda94c8..7c19e17d 100644 --- a/apps/web/src/server/aws/ses.ts +++ b/apps/web/src/server/aws/ses.ts @@ -17,6 +17,7 @@ import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts"; import { generateKeyPairSync } from "crypto"; import nodemailer from "nodemailer"; import { env } from "~/env"; +import { getAwsCredentialOptions } from "./credentials"; import { EmailContent } from "~/types"; import { logger } from "../logger/log"; import { buildHeaders } from "~/server/utils/email-headers"; @@ -30,10 +31,7 @@ async function getAccountId(region: string) { const stsClient = new STSClient({ region: region, - credentials: { - accessKeyId: env.AWS_ACCESS_KEY, - secretAccessKey: env.AWS_SECRET_KEY, - }, + ...getAwsCredentialOptions(), }); const command = new GetCallerIdentityCommand({}); const response = await stsClient.send(command); @@ -50,10 +48,7 @@ function getSesClient(region: string) { return new SESv2Client({ region: region, endpoint: env.AWS_SES_ENDPOINT, - credentials: { - accessKeyId: env.AWS_ACCESS_KEY, - secretAccessKey: env.AWS_SECRET_KEY, - }, + ...getAwsCredentialOptions(), }); } diff --git a/apps/web/src/server/aws/sns.ts b/apps/web/src/server/aws/sns.ts index 0b99b135..00ad0224 100644 --- a/apps/web/src/server/aws/sns.ts +++ b/apps/web/src/server/aws/sns.ts @@ -5,15 +5,13 @@ import { DeleteTopicCommand, } from "@aws-sdk/client-sns"; import { env } from "~/env"; +import { getAwsCredentialOptions } from "./credentials"; function getSnsClient(region: string) { return new SNSClient({ endpoint: env.AWS_SNS_ENDPOINT, region: region, - credentials: { - accessKeyId: env.AWS_ACCESS_KEY, - secretAccessKey: env.AWS_SECRET_KEY, - }, + ...getAwsCredentialOptions(), }); } diff --git a/apps/web/src/test/setup/setup-env.ts b/apps/web/src/test/setup/setup-env.ts index 96d56b45..c6f58790 100644 --- a/apps/web/src/test/setup/setup-env.ts +++ b/apps/web/src/test/setup/setup-env.ts @@ -4,8 +4,8 @@ const defaultEnv: Record = { NEXTAUTH_SECRET: "test-secret", DATABASE_URL: "postgresql://usesend:password@127.0.0.1:54329/usesend_test", REDIS_URL: "redis://127.0.0.1:6380/15", - AWS_ACCESS_KEY: "test-access-key", - AWS_SECRET_KEY: "test-secret-key", + AWS_ACCESS_KEY_ID: "test-access-key", + AWS_SECRET_ACCESS_KEY: "test-secret-key", AWS_DEFAULT_REGION: "us-east-1", NEXT_PUBLIC_IS_CLOUD: "true", API_RATE_LIMIT: "2",