mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 08:53:15 +00:00
fix(infra): send proposals from seahaven.com domain identity (primary SES domain) (#230)
Switch the API Lambda's SES sender from the email identity proposals@seahavenind.com to proposals@seahaven.com under the seahaven.com DOMAIN identity — the org's primary sending domain for all workload stacks. The domain identity is account-shared and managed out-of-band in seahaven-prod (Easy-DKIM verified in the seahaven.com Route53 zone; production-access request submitted), so this stack references its ARN instead of creating an EmailIdentity (avoids per-stack duplication + cross-account DNS at deploy). Send scoped to identity/seahaven.com with a required ses:FromAddress condition. GPT-4.1 cross-review APPROVE. Drops the unused aws-ses import. Follow-up: codify the seahaven.com identity in the seahaven-prod account baseline.
This commit is contained in:
parent
590f0c075c
commit
c19c7fda72
1 changed files with 14 additions and 19 deletions
|
|
@ -5,7 +5,6 @@ import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
|
||||||
import * as apigatewayv2Authorizers from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
|
import * as apigatewayv2Authorizers from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
|
||||||
import * as apigatewayv2Integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
|
import * as apigatewayv2Integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
|
||||||
import * as iam from 'aws-cdk-lib/aws-iam';
|
import * as iam from 'aws-cdk-lib/aws-iam';
|
||||||
import * as ses from 'aws-cdk-lib/aws-ses';
|
|
||||||
import * as s3 from 'aws-cdk-lib/aws-s3';
|
import * as s3 from 'aws-cdk-lib/aws-s3';
|
||||||
import * as sqs from 'aws-cdk-lib/aws-sqs';
|
import * as sqs from 'aws-cdk-lib/aws-sqs';
|
||||||
import * as sns from 'aws-cdk-lib/aws-sns';
|
import * as sns from 'aws-cdk-lib/aws-sns';
|
||||||
|
|
@ -216,7 +215,7 @@ export class ComputeStack extends cdk.Stack {
|
||||||
Auth__CognitoDomain: `${config.cognitoDomainPrefix}.auth.${this.region}.amazoncognito.com`,
|
Auth__CognitoDomain: `${config.cognitoDomainPrefix}.auth.${this.region}.amazoncognito.com`,
|
||||||
COGNITO_WEB_CLIENT_ID: props.webClientId,
|
COGNITO_WEB_CLIENT_ID: props.webClientId,
|
||||||
COGNITO_MOBILE_CLIENT_ID: props.mobileClientId,
|
COGNITO_MOBILE_CLIENT_ID: props.mobileClientId,
|
||||||
SES_FROM_ADDRESS: 'proposals@seahavenind.com',
|
SES_FROM_ADDRESS: 'proposals@seahaven.com',
|
||||||
},
|
},
|
||||||
tracing: lambda.Tracing.ACTIVE,
|
tracing: lambda.Tracing.ACTIVE,
|
||||||
logRetention: logs.RetentionDays.TWO_MONTHS,
|
logRetention: logs.RetentionDays.TWO_MONTHS,
|
||||||
|
|
@ -234,31 +233,27 @@ export class ComputeStack extends cdk.Stack {
|
||||||
resources: [props.userPool.userPoolArn],
|
resources: [props.userPool.userPoolArn],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// PR4: SES email identity for proposal delivery.
|
// SES sender: proposals@seahaven.com. seahaven.com is the org's PRIMARY sending
|
||||||
// NOTE: New SES identities start in the **sandbox**. While in sandbox mode the
|
// domain for all workload stacks. The domain identity is an ACCOUNT-SHARED resource
|
||||||
// account can only send email to *verified* recipient addresses. To send to
|
// managed OUT OF BAND in seahaven-prod (verified via Easy DKIM in the seahaven.com
|
||||||
// arbitrary recipients, submit a production-access request via the AWS SES console
|
// Route53 zone; production-access request submitted 2026-07-15) — this stack
|
||||||
// (Support Center → "SES Sending Limits Increase"). The email identity itself
|
// references it rather than owning it, so it is not re-created per stack and does not
|
||||||
// must also be verified — AWS sends a click-link email to the address below after
|
// depend on cross-account DNS at deploy time. Do NOT create an EmailIdentity here
|
||||||
// deployment; someone with access to the mailbox must click it.
|
// (CloudFormation would fail "already exists" against the out-of-band identity).
|
||||||
const sesFromAddress = 'proposals@seahavenind.com';
|
// TODO: codify the seahaven.com identity in the seahaven-prod account baseline.
|
||||||
const sesSenderIdentity = new ses.EmailIdentity(this, 'SesSenderIdentity', {
|
const sesFromAddress = 'proposals@seahaven.com';
|
||||||
identity: ses.Identity.email(sesFromAddress),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Grant the API Lambda least-privilege SES send permission, scoped to the
|
// Least-privilege SES send: scope to the seahaven.com domain identity ARN. A domain
|
||||||
// sender identity ARN only (not ses:* / resource:*).
|
// identity authorizes sending as ANY user@seahaven.com, so the ses:FromAddress
|
||||||
const senderIdentityArn = `arn:aws:ses:${this.region}:${this.account}:identity/${sesFromAddress}`;
|
// condition is REQUIRED (not optional) to pin the Lambda to proposals@seahaven.com.
|
||||||
|
const senderIdentityArn = `arn:aws:ses:${this.region}:${this.account}:identity/seahaven.com`;
|
||||||
apiFunction.addToRolePolicy(new iam.PolicyStatement({
|
apiFunction.addToRolePolicy(new iam.PolicyStatement({
|
||||||
actions: ['ses:SendEmail', 'ses:SendRawEmail'],
|
actions: ['ses:SendEmail', 'ses:SendRawEmail'],
|
||||||
resources: [senderIdentityArn],
|
resources: [senderIdentityArn],
|
||||||
// Defense-in-depth (cross-review): only send AS this address, even if the
|
|
||||||
// identity scope is later widened.
|
|
||||||
conditions: {
|
conditions: {
|
||||||
StringEquals: { 'ses:FromAddress': sesFromAddress },
|
StringEquals: { 'ses:FromAddress': sesFromAddress },
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
void sesSenderIdentity;
|
|
||||||
|
|
||||||
// Fix: LAM-C1/INF-H1 — require IAM auth on Function URL (was authType NONE).
|
// Fix: LAM-C1/INF-H1 — require IAM auth on Function URL (was authType NONE).
|
||||||
// NOTE: Lambda HTTP clients (suggestions, pdf-extract, pdf-generate, library-ingest)
|
// NOTE: Lambda HTTP clients (suggestions, pdf-extract, pdf-generate, library-ingest)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue