sst 2.0.0-rc.63 → 2.0.0-rc.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/dev.js +1 -0
- package/cli/sst.js +4 -2
- package/constructs/ApiGatewayV1Api.d.ts +2 -1
- package/constructs/ApiGatewayV1Api.js +2 -1
- package/constructs/AppSyncApi.d.ts +1 -1
- package/constructs/AppSyncApi.js +4 -1
- package/constructs/BaseSite.d.ts +10 -10
- package/constructs/BaseSite.js +2 -2
- package/constructs/EventBus.js +1 -1
- package/constructs/NextjsSite.js +1 -1
- package/constructs/SsrSite.js +2 -2
- package/constructs/StaticSite.js +5 -5
- package/constructs/cdk/certificate-base.d.ts +18 -0
- package/constructs/cdk/certificate-base.js +26 -0
- package/constructs/cdk/dns-validated-certificate.d.ts +77 -0
- package/constructs/cdk/dns-validated-certificate.js +125 -0
- package/constructs/cdk/website-redirect.d.ts +53 -0
- package/constructs/cdk/website-redirect.js +77 -0
- package/package.json +3 -2
- package/pothos.js +1 -0
- package/sst.mjs +5 -2
- package/support/base-site-archiver.mjs +1 -1
- package/support/certificate-requestor/index.js +549 -0
package/cli/commands/dev.js
CHANGED
package/cli/sst.js
CHANGED
|
@@ -38,8 +38,10 @@ consoleCommand(program);
|
|
|
38
38
|
diff(program);
|
|
39
39
|
version(program);
|
|
40
40
|
telemetry(program);
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
if ("setSourceMapsEnabled" in process) {
|
|
42
|
+
// @ts-expect-error
|
|
43
|
+
process.setSourceMapsEnabled(true);
|
|
44
|
+
}
|
|
43
45
|
process.removeAllListeners("uncaughtException");
|
|
44
46
|
process.on("uncaughtException", (err) => {
|
|
45
47
|
Logger.debug(err);
|
|
@@ -9,6 +9,7 @@ import * as apigV1AccessLog from "./util/apiGatewayV1AccessLog.js";
|
|
|
9
9
|
import { Bucket } from "./Bucket.js";
|
|
10
10
|
import { Duration } from "./util/duration.js";
|
|
11
11
|
import { SSTConstruct } from "./Construct.js";
|
|
12
|
+
import { DnsValidatedCertificate } from "./cdk/dns-validated-certificate.js";
|
|
12
13
|
import { Function as Fn, FunctionProps, FunctionInlineDefinition, FunctionDefinition } from "./Function.js";
|
|
13
14
|
import { Permissions } from "./util/permission.js";
|
|
14
15
|
export interface ApiGatewayV1ApiAccessLogProps extends apigV1AccessLog.AccessLogProps {
|
|
@@ -491,7 +492,7 @@ export declare class ApiGatewayV1Api<Authorizers extends Record<string, ApiGatew
|
|
|
491
492
|
/**
|
|
492
493
|
* The internally created certificate
|
|
493
494
|
*/
|
|
494
|
-
certificate?: acm.Certificate |
|
|
495
|
+
certificate?: acm.Certificate | DnsValidatedCertificate;
|
|
495
496
|
};
|
|
496
497
|
private _deployment?;
|
|
497
498
|
private _customDomainUrl?;
|
|
@@ -9,6 +9,7 @@ import * as apigV1AccessLog from "./util/apiGatewayV1AccessLog.js";
|
|
|
9
9
|
import { Stack } from "./Stack.js";
|
|
10
10
|
import { toCdkDuration } from "./util/duration.js";
|
|
11
11
|
import { getFunctionRef, isCDKConstruct } from "./Construct.js";
|
|
12
|
+
import { DnsValidatedCertificate } from "./cdk/dns-validated-certificate.js";
|
|
12
13
|
import { Function as Fn, } from "./Function.js";
|
|
13
14
|
const allowedMethods = [
|
|
14
15
|
"ANY",
|
|
@@ -447,7 +448,7 @@ export class ApiGatewayV1Api extends Construct {
|
|
|
447
448
|
/////////////////////
|
|
448
449
|
if (!apigDomainName && !certificate) {
|
|
449
450
|
if (endpointType === "edge") {
|
|
450
|
-
certificate = new
|
|
451
|
+
certificate = new DnsValidatedCertificate(this, "CrossRegionCertificate", {
|
|
451
452
|
domainName: domainName,
|
|
452
453
|
hostedZone: hostedZone,
|
|
453
454
|
region: "us-east-1",
|
|
@@ -245,7 +245,7 @@ export interface AppSyncApiProps {
|
|
|
245
245
|
* });
|
|
246
246
|
* ```
|
|
247
247
|
*/
|
|
248
|
-
schema
|
|
248
|
+
schema?: string | string[];
|
|
249
249
|
/**
|
|
250
250
|
* Specify a custom domain to use in addition to the automatically generated one. SST currently supports domains that are configured using [Route 53](https://aws.amazon.com/route53/)
|
|
251
251
|
*
|
package/constructs/AppSyncApi.js
CHANGED
|
@@ -278,7 +278,10 @@ export class AppSyncApi extends Construct {
|
|
|
278
278
|
{});
|
|
279
279
|
// build schema
|
|
280
280
|
let mainSchema;
|
|
281
|
-
if (
|
|
281
|
+
if (!schema) {
|
|
282
|
+
throw new Error(`Missing "schema" in "${id}" AppSyncApi`);
|
|
283
|
+
}
|
|
284
|
+
else if (typeof schema === "string") {
|
|
282
285
|
mainSchema = appsync.SchemaFile.fromAsset(schema);
|
|
283
286
|
}
|
|
284
287
|
else {
|
package/constructs/BaseSite.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { IHostedZone } from "aws-cdk-lib/aws-route53";
|
|
2
|
+
import { ErrorResponse, DistributionProps, BehaviorOptions, IOrigin } from "aws-cdk-lib/aws-cloudfront";
|
|
3
|
+
import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
4
4
|
/**
|
|
5
5
|
* The customDomain for this website. SST supports domains that are hosted either on [Route 53](https://aws.amazon.com/route53/) or externally.
|
|
6
6
|
*
|
|
@@ -61,13 +61,13 @@ export interface BaseSiteDomainProps {
|
|
|
61
61
|
/**
|
|
62
62
|
* Import the underlying Route 53 hosted zone.
|
|
63
63
|
*/
|
|
64
|
-
hostedZone?:
|
|
64
|
+
hostedZone?: IHostedZone;
|
|
65
65
|
/**
|
|
66
66
|
* Import the certificate for the domain. By default, SST will create a certificate with the domain name. The certificate will be created in the `us-east-1`(N. Virginia) region as required by AWS CloudFront.
|
|
67
67
|
*
|
|
68
68
|
* Set this option if you have an existing certificate in the `us-east-1` region in AWS Certificate Manager you want to use.
|
|
69
69
|
*/
|
|
70
|
-
certificate?:
|
|
70
|
+
certificate?: ICertificate;
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
export interface BaseSiteEnvironmentOutputsInfo {
|
|
@@ -82,11 +82,11 @@ export interface BaseSiteReplaceProps {
|
|
|
82
82
|
search: string;
|
|
83
83
|
replace: string;
|
|
84
84
|
}
|
|
85
|
-
export declare function buildErrorResponsesForRedirectToIndex(indexPage: string):
|
|
86
|
-
export declare function buildErrorResponsesFor404ErrorPage(errorPage: string):
|
|
87
|
-
export interface BaseSiteCdkDistributionProps extends Omit<
|
|
88
|
-
defaultBehavior?: Omit<
|
|
89
|
-
origin?:
|
|
85
|
+
export declare function buildErrorResponsesForRedirectToIndex(indexPage: string): ErrorResponse[];
|
|
86
|
+
export declare function buildErrorResponsesFor404ErrorPage(errorPage: string): ErrorResponse[];
|
|
87
|
+
export interface BaseSiteCdkDistributionProps extends Omit<DistributionProps, "defaultBehavior"> {
|
|
88
|
+
defaultBehavior?: Omit<BehaviorOptions, "origin"> & {
|
|
89
|
+
origin?: IOrigin;
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
export declare function getBuildCmdEnvironment(siteEnvironment?: {
|
package/constructs/BaseSite.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Token } from "aws-cdk-lib";
|
|
2
2
|
export function buildErrorResponsesForRedirectToIndex(indexPage) {
|
|
3
3
|
return [
|
|
4
4
|
{
|
|
@@ -35,7 +35,7 @@ export function getBuildCmdEnvironment(siteEnvironment) {
|
|
|
35
35
|
//
|
|
36
36
|
const buildCmdEnvironment = {};
|
|
37
37
|
Object.entries(siteEnvironment || {}).forEach(([key, value]) => {
|
|
38
|
-
buildCmdEnvironment[key] =
|
|
38
|
+
buildCmdEnvironment[key] = Token.isUnresolved(value)
|
|
39
39
|
? `{{ ${key} }}`
|
|
40
40
|
: value;
|
|
41
41
|
});
|
package/constructs/EventBus.js
CHANGED
|
@@ -183,7 +183,7 @@ export class EventBus extends Construct {
|
|
|
183
183
|
eventBusName: this.cdk.eventBus.eventBusName,
|
|
184
184
|
rules: Object.entries(this.targetsData).map(([ruleName, rule]) => ({
|
|
185
185
|
key: ruleName,
|
|
186
|
-
targets: Object.values(rule).map(getFunctionRef),
|
|
186
|
+
targets: Object.values(rule).map(getFunctionRef).filter(Boolean),
|
|
187
187
|
targetNames: Object.keys(rule),
|
|
188
188
|
})),
|
|
189
189
|
},
|
package/constructs/NextjsSite.js
CHANGED
package/constructs/SsrSite.js
CHANGED
|
@@ -13,15 +13,15 @@ import { Function, Code, Runtime, FunctionUrlAuthType, } from "aws-cdk-lib/aws-l
|
|
|
13
13
|
import { HostedZone, ARecord, AaaaRecord, RecordTarget, } from "aws-cdk-lib/aws-route53";
|
|
14
14
|
import { Asset } from "aws-cdk-lib/aws-s3-assets";
|
|
15
15
|
import { Distribution, ViewerProtocolPolicy, AllowedMethods, CachedMethods, LambdaEdgeEventType, CachePolicy, CacheQueryStringBehavior, CacheHeaderBehavior, CacheCookieBehavior, } from "aws-cdk-lib/aws-cloudfront";
|
|
16
|
-
import { DnsValidatedCertificate, } from "aws-cdk-lib/aws-certificatemanager";
|
|
17
16
|
import { AwsCliLayer } from "aws-cdk-lib/lambda-layer-awscli";
|
|
18
17
|
import { S3Origin, HttpOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
19
18
|
import { CloudFrontTarget } from "aws-cdk-lib/aws-route53-targets";
|
|
20
|
-
import { HttpsRedirect } from "aws-cdk-lib/aws-route53-patterns";
|
|
21
19
|
import { Stack } from "./Stack.js";
|
|
22
20
|
import { Logger } from "../logger.js";
|
|
23
21
|
import { isCDKConstruct } from "./Construct.js";
|
|
24
22
|
import { getBuildCmdEnvironment, } from "./BaseSite.js";
|
|
23
|
+
import { HttpsRedirect } from "./cdk/website-redirect.js";
|
|
24
|
+
import { DnsValidatedCertificate } from "./cdk/dns-validated-certificate.js";
|
|
25
25
|
import { attachPermissionsToRole } from "./util/permission.js";
|
|
26
26
|
import { ENVIRONMENT_PLACEHOLDER, getParameterPath, } from "./util/functionBinding.js";
|
|
27
27
|
import { SiteEnv } from "../site-env.js";
|
package/constructs/StaticSite.js
CHANGED
|
@@ -7,17 +7,17 @@ import { Construct } from "constructs";
|
|
|
7
7
|
import { Token, Duration, CfnOutput, RemovalPolicy, CustomResource, } from "aws-cdk-lib";
|
|
8
8
|
import * as s3 from "aws-cdk-lib/aws-s3";
|
|
9
9
|
import * as s3Assets from "aws-cdk-lib/aws-s3-assets";
|
|
10
|
-
import * as acm from "aws-cdk-lib/aws-certificatemanager";
|
|
11
10
|
import * as iam from "aws-cdk-lib/aws-iam";
|
|
12
11
|
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
13
12
|
import * as route53 from "aws-cdk-lib/aws-route53";
|
|
14
|
-
import * as route53Patterns from "aws-cdk-lib/aws-route53-patterns";
|
|
15
13
|
import * as route53Targets from "aws-cdk-lib/aws-route53-targets";
|
|
16
14
|
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
|
|
17
15
|
import * as cfOrigins from "aws-cdk-lib/aws-cloudfront-origins";
|
|
18
16
|
import { AwsCliLayer } from "aws-cdk-lib/lambda-layer-awscli";
|
|
19
17
|
import { Stack } from "./Stack.js";
|
|
20
18
|
import { getBuildCmdEnvironment, buildErrorResponsesFor404ErrorPage, buildErrorResponsesForRedirectToIndex, } from "./BaseSite.js";
|
|
19
|
+
import { HttpsRedirect } from "./cdk/website-redirect.js";
|
|
20
|
+
import { DnsValidatedCertificate } from "./cdk/dns-validated-certificate.js";
|
|
21
21
|
import { isCDKConstruct } from "./Construct.js";
|
|
22
22
|
import { ENVIRONMENT_PLACEHOLDER, getParameterPath, } from "./util/functionBinding.js";
|
|
23
23
|
import { gray } from "colorette";
|
|
@@ -519,7 +519,7 @@ interface ImportMeta {
|
|
|
519
519
|
// HostedZone is set for Route 53 domains
|
|
520
520
|
if (this.hostedZone) {
|
|
521
521
|
if (typeof customDomain === "string") {
|
|
522
|
-
acmCertificate = new
|
|
522
|
+
acmCertificate = new DnsValidatedCertificate(this, "Certificate", {
|
|
523
523
|
domainName: customDomain,
|
|
524
524
|
hostedZone: this.hostedZone,
|
|
525
525
|
region: "us-east-1",
|
|
@@ -529,7 +529,7 @@ interface ImportMeta {
|
|
|
529
529
|
acmCertificate = customDomain.cdk.certificate;
|
|
530
530
|
}
|
|
531
531
|
else {
|
|
532
|
-
acmCertificate = new
|
|
532
|
+
acmCertificate = new DnsValidatedCertificate(this, "Certificate", {
|
|
533
533
|
domainName: customDomain.domainName,
|
|
534
534
|
hostedZone: this.hostedZone,
|
|
535
535
|
region: "us-east-1",
|
|
@@ -568,7 +568,7 @@ interface ImportMeta {
|
|
|
568
568
|
new route53.AaaaRecord(this, "AliasRecordAAAA", recordProps);
|
|
569
569
|
// Create Alias redirect record
|
|
570
570
|
if (domainAlias) {
|
|
571
|
-
new
|
|
571
|
+
new HttpsRedirect(this, "Redirect", {
|
|
572
572
|
zone: this.hostedZone,
|
|
573
573
|
recordNames: [domainAlias],
|
|
574
574
|
targetDomain: recordName,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Metric, MetricOptions } from "aws-cdk-lib/aws-cloudwatch";
|
|
2
|
+
import { Resource } from "aws-cdk-lib";
|
|
3
|
+
import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
4
|
+
/**
|
|
5
|
+
* Shared implementation details of ICertificate implementations.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare abstract class CertificateBase extends Resource implements ICertificate {
|
|
10
|
+
abstract readonly certificateArn: string;
|
|
11
|
+
/**
|
|
12
|
+
* If the certificate is provisionned in a different region than the
|
|
13
|
+
* containing stack, this should be the region in which the certificate lives
|
|
14
|
+
* so we can correctly create `Metric` instances.
|
|
15
|
+
*/
|
|
16
|
+
protected readonly region?: string;
|
|
17
|
+
metricDaysToExpiry(props?: MetricOptions): Metric;
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Metric, Stats } from "aws-cdk-lib/aws-cloudwatch";
|
|
2
|
+
import { Duration, Resource } from "aws-cdk-lib";
|
|
3
|
+
/**
|
|
4
|
+
* Shared implementation details of ICertificate implementations.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export class CertificateBase extends Resource {
|
|
9
|
+
/**
|
|
10
|
+
* If the certificate is provisionned in a different region than the
|
|
11
|
+
* containing stack, this should be the region in which the certificate lives
|
|
12
|
+
* so we can correctly create `Metric` instances.
|
|
13
|
+
*/
|
|
14
|
+
region;
|
|
15
|
+
metricDaysToExpiry(props) {
|
|
16
|
+
return new Metric({
|
|
17
|
+
period: Duration.days(1),
|
|
18
|
+
...props,
|
|
19
|
+
dimensionsMap: { CertificateArn: this.certificateArn },
|
|
20
|
+
metricName: "DaysToExpiry",
|
|
21
|
+
namespace: "AWS/CertificateManager",
|
|
22
|
+
region: this.region,
|
|
23
|
+
statistic: Stats.MINIMUM,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Construct } from "constructs";
|
|
2
|
+
import { ITaggable, TagManager, RemovalPolicy } from "aws-cdk-lib";
|
|
3
|
+
import { CertificateProps, ICertificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
4
|
+
import * as iam from "aws-cdk-lib/aws-iam";
|
|
5
|
+
import * as route53 from "aws-cdk-lib/aws-route53";
|
|
6
|
+
import { CertificateBase } from "./certificate-base.js";
|
|
7
|
+
/**
|
|
8
|
+
* Properties to create a DNS validated certificate managed by AWS Certificate Manager
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
export interface DnsValidatedCertificateProps extends CertificateProps {
|
|
12
|
+
/**
|
|
13
|
+
* Route 53 Hosted Zone used to perform DNS validation of the request. The zone
|
|
14
|
+
* must be authoritative for the domain name specified in the Certificate Request.
|
|
15
|
+
*/
|
|
16
|
+
readonly hostedZone: route53.IHostedZone;
|
|
17
|
+
/**
|
|
18
|
+
* AWS region that will host the certificate. This is needed especially
|
|
19
|
+
* for certificates used for CloudFront distributions, which require the region
|
|
20
|
+
* to be us-east-1.
|
|
21
|
+
*
|
|
22
|
+
* @default the region the stack is deployed in.
|
|
23
|
+
*/
|
|
24
|
+
readonly region?: string;
|
|
25
|
+
/**
|
|
26
|
+
* An endpoint of Route53 service, which is not necessary as AWS SDK could figure
|
|
27
|
+
* out the right endpoints for most regions, but for some regions such as those in
|
|
28
|
+
* aws-cn partition, the default endpoint is not working now, hence the right endpoint
|
|
29
|
+
* need to be specified through this prop.
|
|
30
|
+
*
|
|
31
|
+
* Route53 is not been officially launched in China, it is only available for AWS
|
|
32
|
+
* internal accounts now. To make DnsValidatedCertificate work for internal accounts
|
|
33
|
+
* now, a special endpoint needs to be provided.
|
|
34
|
+
*
|
|
35
|
+
* @default - The AWS SDK will determine the Route53 endpoint to use based on region
|
|
36
|
+
*/
|
|
37
|
+
readonly route53Endpoint?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Role to use for the custom resource that creates the validated certificate
|
|
40
|
+
*
|
|
41
|
+
* @default - A new role will be created
|
|
42
|
+
*/
|
|
43
|
+
readonly customResourceRole?: iam.IRole;
|
|
44
|
+
/**
|
|
45
|
+
* When set to true, when the DnsValidatedCertificate is deleted,
|
|
46
|
+
* the associated Route53 validation records are removed.
|
|
47
|
+
*
|
|
48
|
+
* CAUTION: If multiple certificates share the same domains (and same validation records),
|
|
49
|
+
* this can cause the other certificates to fail renewal and/or not validate.
|
|
50
|
+
* Not recommended for production use.
|
|
51
|
+
*
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
readonly cleanupRoute53Records?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A certificate managed by AWS Certificate Manager. Will be automatically
|
|
58
|
+
* validated using DNS validation against the specified Route 53 hosted zone.
|
|
59
|
+
*
|
|
60
|
+
* @resource AWS::CertificateManager::Certificate
|
|
61
|
+
*/
|
|
62
|
+
export declare class DnsValidatedCertificate extends CertificateBase implements ICertificate, ITaggable {
|
|
63
|
+
readonly certificateArn: string;
|
|
64
|
+
/**
|
|
65
|
+
* Resource Tags.
|
|
66
|
+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-certificate.html#cfn-certificatemanager-certificate-tags
|
|
67
|
+
*/
|
|
68
|
+
readonly tags: TagManager;
|
|
69
|
+
protected readonly region?: string;
|
|
70
|
+
private normalizedZoneName;
|
|
71
|
+
private hostedZoneId;
|
|
72
|
+
private domainName;
|
|
73
|
+
private _removalPolicy?;
|
|
74
|
+
constructor(scope: Construct, id: string, props: DnsValidatedCertificateProps);
|
|
75
|
+
applyRemovalPolicy(policy: RemovalPolicy): void;
|
|
76
|
+
private validateDnsValidatedCertificate;
|
|
77
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import url from "url";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { Token, TagManager, TagType, Duration, Stack, CustomResource, Lazy, } from "aws-cdk-lib";
|
|
4
|
+
import * as iam from "aws-cdk-lib/aws-iam";
|
|
5
|
+
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
6
|
+
import { CertificateBase } from "./certificate-base.js";
|
|
7
|
+
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
8
|
+
/**
|
|
9
|
+
* A certificate managed by AWS Certificate Manager. Will be automatically
|
|
10
|
+
* validated using DNS validation against the specified Route 53 hosted zone.
|
|
11
|
+
*
|
|
12
|
+
* @resource AWS::CertificateManager::Certificate
|
|
13
|
+
*/
|
|
14
|
+
export class DnsValidatedCertificate extends CertificateBase {
|
|
15
|
+
certificateArn;
|
|
16
|
+
/**
|
|
17
|
+
* Resource Tags.
|
|
18
|
+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-certificate.html#cfn-certificatemanager-certificate-tags
|
|
19
|
+
*/
|
|
20
|
+
tags;
|
|
21
|
+
region;
|
|
22
|
+
normalizedZoneName;
|
|
23
|
+
hostedZoneId;
|
|
24
|
+
domainName;
|
|
25
|
+
_removalPolicy;
|
|
26
|
+
constructor(scope, id, props) {
|
|
27
|
+
super(scope, id);
|
|
28
|
+
this.region = props.region;
|
|
29
|
+
this.domainName = props.domainName;
|
|
30
|
+
// check if domain name is 64 characters or less
|
|
31
|
+
if (!Token.isUnresolved(props.domainName) && props.domainName.length > 64) {
|
|
32
|
+
throw new Error("Domain name must be 64 characters or less");
|
|
33
|
+
}
|
|
34
|
+
this.normalizedZoneName = props.hostedZone.zoneName;
|
|
35
|
+
// Remove trailing `.` from zone name
|
|
36
|
+
if (this.normalizedZoneName.endsWith(".")) {
|
|
37
|
+
this.normalizedZoneName = this.normalizedZoneName.substring(0, this.normalizedZoneName.length - 1);
|
|
38
|
+
}
|
|
39
|
+
// Remove any `/hostedzone/` prefix from the Hosted Zone ID
|
|
40
|
+
this.hostedZoneId = props.hostedZone.hostedZoneId.replace(/^\/hostedzone\//, "");
|
|
41
|
+
this.tags = new TagManager(TagType.MAP, "AWS::CertificateManager::Certificate");
|
|
42
|
+
let certificateTransparencyLoggingPreference;
|
|
43
|
+
if (props.transparencyLoggingEnabled !== undefined) {
|
|
44
|
+
certificateTransparencyLoggingPreference =
|
|
45
|
+
props.transparencyLoggingEnabled ? "ENABLED" : "DISABLED";
|
|
46
|
+
}
|
|
47
|
+
const requestorFunction = new lambda.Function(this, "CertificateRequestorFunction", {
|
|
48
|
+
code: lambda.Code.fromAsset(path.join(__dirname, "../../support/certificate-requestor")),
|
|
49
|
+
handler: "index.certificateRequestHandler",
|
|
50
|
+
runtime: lambda.Runtime.NODEJS_14_X,
|
|
51
|
+
timeout: Duration.minutes(15),
|
|
52
|
+
role: props.customResourceRole,
|
|
53
|
+
});
|
|
54
|
+
requestorFunction.addToRolePolicy(new iam.PolicyStatement({
|
|
55
|
+
actions: [
|
|
56
|
+
"acm:RequestCertificate",
|
|
57
|
+
"acm:DescribeCertificate",
|
|
58
|
+
"acm:DeleteCertificate",
|
|
59
|
+
"acm:AddTagsToCertificate",
|
|
60
|
+
],
|
|
61
|
+
resources: ["*"],
|
|
62
|
+
}));
|
|
63
|
+
requestorFunction.addToRolePolicy(new iam.PolicyStatement({
|
|
64
|
+
actions: ["route53:GetChange"],
|
|
65
|
+
resources: ["*"],
|
|
66
|
+
}));
|
|
67
|
+
requestorFunction.addToRolePolicy(new iam.PolicyStatement({
|
|
68
|
+
actions: ["route53:changeResourceRecordSets"],
|
|
69
|
+
resources: [
|
|
70
|
+
`arn:${Stack.of(requestorFunction).partition}:route53:::hostedzone/${this.hostedZoneId}`,
|
|
71
|
+
],
|
|
72
|
+
conditions: {
|
|
73
|
+
"ForAllValues:StringEquals": {
|
|
74
|
+
"route53:ChangeResourceRecordSetsRecordTypes": ["CNAME"],
|
|
75
|
+
"route53:ChangeResourceRecordSetsActions": props.cleanupRoute53Records ? ["UPSERT", "DELETE"] : ["UPSERT"],
|
|
76
|
+
},
|
|
77
|
+
"ForAllValues:StringLike": {
|
|
78
|
+
"route53:ChangeResourceRecordSetsNormalizedRecordNames": [
|
|
79
|
+
addWildcard(props.domainName),
|
|
80
|
+
...(props.subjectAlternativeNames ?? []).map((d) => addWildcard(d)),
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}));
|
|
85
|
+
const certificate = new CustomResource(this, "CertificateRequestorResource", {
|
|
86
|
+
serviceToken: requestorFunction.functionArn,
|
|
87
|
+
properties: {
|
|
88
|
+
DomainName: props.domainName,
|
|
89
|
+
SubjectAlternativeNames: Lazy.list({ produce: () => props.subjectAlternativeNames }, { omitEmpty: true }),
|
|
90
|
+
CertificateTransparencyLoggingPreference: certificateTransparencyLoggingPreference,
|
|
91
|
+
HostedZoneId: this.hostedZoneId,
|
|
92
|
+
Region: props.region,
|
|
93
|
+
Route53Endpoint: props.route53Endpoint,
|
|
94
|
+
RemovalPolicy: Lazy.any({ produce: () => this._removalPolicy }),
|
|
95
|
+
// Custom resources properties are always converted to strings; might as well be explict here.
|
|
96
|
+
CleanupRecords: props.cleanupRoute53Records ? "true" : undefined,
|
|
97
|
+
Tags: Lazy.list({ produce: () => this.tags.renderTags() }),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
this.certificateArn = certificate.getAtt("Arn").toString();
|
|
101
|
+
this.node.addValidation({
|
|
102
|
+
validate: () => this.validateDnsValidatedCertificate(),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
applyRemovalPolicy(policy) {
|
|
106
|
+
this._removalPolicy = policy;
|
|
107
|
+
}
|
|
108
|
+
validateDnsValidatedCertificate() {
|
|
109
|
+
const errors = [];
|
|
110
|
+
// Ensure the zone name is a parent zone of the certificate domain name
|
|
111
|
+
if (!Token.isUnresolved(this.normalizedZoneName) &&
|
|
112
|
+
this.domainName !== this.normalizedZoneName &&
|
|
113
|
+
!this.domainName.endsWith("." + this.normalizedZoneName)) {
|
|
114
|
+
errors.push(`DNS zone ${this.normalizedZoneName} is not authoritative for certificate domain name ${this.domainName}`);
|
|
115
|
+
}
|
|
116
|
+
return errors;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/specifying-rrset-conditions.html
|
|
120
|
+
function addWildcard(domainName) {
|
|
121
|
+
if (domainName.startsWith("*.")) {
|
|
122
|
+
return domainName;
|
|
123
|
+
}
|
|
124
|
+
return `*.${domainName}`;
|
|
125
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
2
|
+
import { IHostedZone } from "aws-cdk-lib/aws-route53";
|
|
3
|
+
import { Construct } from "constructs";
|
|
4
|
+
/**
|
|
5
|
+
* Properties to configure an HTTPS Redirect
|
|
6
|
+
*/
|
|
7
|
+
export interface HttpsRedirectProps {
|
|
8
|
+
/**
|
|
9
|
+
* Hosted zone of the domain which will be used to create alias record(s) from
|
|
10
|
+
* domain names in the hosted zone to the target domain. The hosted zone must
|
|
11
|
+
* contain entries for the domain name(s) supplied through `recordNames` that
|
|
12
|
+
* will redirect to the target domain.
|
|
13
|
+
*
|
|
14
|
+
* Domain names in the hosted zone can include a specific domain (example.com)
|
|
15
|
+
* and its subdomains (acme.example.com, zenith.example.com).
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
readonly zone: IHostedZone;
|
|
19
|
+
/**
|
|
20
|
+
* The redirect target fully qualified domain name (FQDN). An alias record
|
|
21
|
+
* will be created that points to your CloudFront distribution. Root domain
|
|
22
|
+
* or sub-domain can be supplied.
|
|
23
|
+
*/
|
|
24
|
+
readonly targetDomain: string;
|
|
25
|
+
/**
|
|
26
|
+
* The domain names that will redirect to `targetDomain`
|
|
27
|
+
*
|
|
28
|
+
* @default - the domain name of the hosted zone
|
|
29
|
+
*/
|
|
30
|
+
readonly recordNames?: string[];
|
|
31
|
+
/**
|
|
32
|
+
* The AWS Certificate Manager (ACM) certificate that will be associated with
|
|
33
|
+
* the CloudFront distribution that will be created. If provided, the certificate must be
|
|
34
|
+
* stored in us-east-1 (N. Virginia)
|
|
35
|
+
*
|
|
36
|
+
* @default - A new certificate is created in us-east-1 (N. Virginia)
|
|
37
|
+
*/
|
|
38
|
+
readonly certificate?: ICertificate;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Allows creating a domainA -> domainB redirect using CloudFront and S3.
|
|
42
|
+
* You can specify multiple domains to be redirected.
|
|
43
|
+
*/
|
|
44
|
+
export declare class HttpsRedirect extends Construct {
|
|
45
|
+
constructor(scope: Construct, id: string, props: HttpsRedirectProps);
|
|
46
|
+
/**
|
|
47
|
+
* Creates a certificate.
|
|
48
|
+
*
|
|
49
|
+
* This is also safe to upgrade since the new certificate will be created and updated
|
|
50
|
+
* on the CloudFront distribution before the old one is deleted.
|
|
51
|
+
*/
|
|
52
|
+
private createCertificate;
|
|
53
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { CloudFrontWebDistribution, OriginProtocolPolicy, PriceClass, ViewerCertificate, ViewerProtocolPolicy, } from "aws-cdk-lib/aws-cloudfront";
|
|
2
|
+
import { ARecord, AaaaRecord, RecordTarget, } from "aws-cdk-lib/aws-route53";
|
|
3
|
+
import { CloudFrontTarget } from "aws-cdk-lib/aws-route53-targets";
|
|
4
|
+
import { BlockPublicAccess, Bucket, RedirectProtocol, } from "aws-cdk-lib/aws-s3";
|
|
5
|
+
import { ArnFormat, RemovalPolicy, Stack, Token } from "aws-cdk-lib";
|
|
6
|
+
import cdkHelpers from "aws-cdk-lib/core/lib/helpers-internal";
|
|
7
|
+
import { Construct } from "constructs";
|
|
8
|
+
import { DnsValidatedCertificate } from "./dns-validated-certificate.js";
|
|
9
|
+
const { md5hash } = cdkHelpers;
|
|
10
|
+
/**
|
|
11
|
+
* Allows creating a domainA -> domainB redirect using CloudFront and S3.
|
|
12
|
+
* You can specify multiple domains to be redirected.
|
|
13
|
+
*/
|
|
14
|
+
export class HttpsRedirect extends Construct {
|
|
15
|
+
constructor(scope, id, props) {
|
|
16
|
+
super(scope, id);
|
|
17
|
+
const domainNames = props.recordNames ?? [props.zone.zoneName];
|
|
18
|
+
if (props.certificate) {
|
|
19
|
+
const certificateRegion = Stack.of(this).splitArn(props.certificate.certificateArn, ArnFormat.SLASH_RESOURCE_NAME).region;
|
|
20
|
+
if (!Token.isUnresolved(certificateRegion) &&
|
|
21
|
+
certificateRegion !== "us-east-1") {
|
|
22
|
+
throw new Error(`The certificate must be in the us-east-1 region and the certificate you provided is in ${certificateRegion}.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const redirectCert = props.certificate ?? this.createCertificate(domainNames, props.zone);
|
|
26
|
+
const redirectBucket = new Bucket(this, "RedirectBucket", {
|
|
27
|
+
websiteRedirect: {
|
|
28
|
+
hostName: props.targetDomain,
|
|
29
|
+
protocol: RedirectProtocol.HTTPS,
|
|
30
|
+
},
|
|
31
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
32
|
+
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
|
|
33
|
+
});
|
|
34
|
+
const redirectDist = new CloudFrontWebDistribution(this, "RedirectDistribution", {
|
|
35
|
+
defaultRootObject: "",
|
|
36
|
+
originConfigs: [
|
|
37
|
+
{
|
|
38
|
+
behaviors: [{ isDefaultBehavior: true }],
|
|
39
|
+
customOriginSource: {
|
|
40
|
+
domainName: redirectBucket.bucketWebsiteDomainName,
|
|
41
|
+
originProtocolPolicy: OriginProtocolPolicy.HTTP_ONLY,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
viewerCertificate: ViewerCertificate.fromAcmCertificate(redirectCert, {
|
|
46
|
+
aliases: domainNames,
|
|
47
|
+
}),
|
|
48
|
+
comment: `Redirect to ${props.targetDomain} from ${domainNames.join(", ")}`,
|
|
49
|
+
priceClass: PriceClass.PRICE_CLASS_ALL,
|
|
50
|
+
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
51
|
+
});
|
|
52
|
+
domainNames.forEach((domainName) => {
|
|
53
|
+
const hash = md5hash(domainName).slice(0, 6);
|
|
54
|
+
const aliasProps = {
|
|
55
|
+
recordName: domainName,
|
|
56
|
+
zone: props.zone,
|
|
57
|
+
target: RecordTarget.fromAlias(new CloudFrontTarget(redirectDist)),
|
|
58
|
+
};
|
|
59
|
+
new ARecord(this, `RedirectAliasRecord${hash}`, aliasProps);
|
|
60
|
+
new AaaaRecord(this, `RedirectAliasRecordSix${hash}`, aliasProps);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Creates a certificate.
|
|
65
|
+
*
|
|
66
|
+
* This is also safe to upgrade since the new certificate will be created and updated
|
|
67
|
+
* on the CloudFront distribution before the old one is deleted.
|
|
68
|
+
*/
|
|
69
|
+
createCertificate(domainNames, zone) {
|
|
70
|
+
return new DnsValidatedCertificate(this, "RedirectCertificate", {
|
|
71
|
+
domainName: domainNames[0],
|
|
72
|
+
subjectAlternativeNames: domainNames,
|
|
73
|
+
hostedZone: zone,
|
|
74
|
+
region: "us-east-1",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sst",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.65",
|
|
4
4
|
"bin": {
|
|
5
5
|
"sst": "cli/sst.js"
|
|
6
6
|
},
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@aws-sdk/client-codebuild": "^3.257.0",
|
|
94
94
|
"@aws-sdk/types": "^3.208.0",
|
|
95
|
+
"@graphql-tools/merge": "^8.3.16",
|
|
95
96
|
"@sls-next/lambda-at-edge": "^3.7.0",
|
|
96
97
|
"@tsconfig/node16": "^1.0.3",
|
|
97
98
|
"@types/adm-zip": "^0.5.0",
|
|
@@ -108,7 +109,7 @@
|
|
|
108
109
|
"@types/ws": "^8.5.3",
|
|
109
110
|
"@types/yargs": "^17.0.13",
|
|
110
111
|
"tsx": "^3.12.1",
|
|
111
|
-
"vitest": "^0.
|
|
112
|
+
"vitest": "^0.28.3"
|
|
112
113
|
},
|
|
113
114
|
"peerDependencies": {
|
|
114
115
|
"@sls-next/lambda-at-edge": "^3.7.0"
|
package/pothos.js
CHANGED
|
@@ -77,6 +77,7 @@ export async function extractSchema(opts) {
|
|
|
77
77
|
if (!types.isMemberExpression(callPath.node.callee) ||
|
|
78
78
|
!types.isIdentifier(callPath.node.callee.object) ||
|
|
79
79
|
!types.isIdentifier(callPath.node.callee.property) ||
|
|
80
|
+
!schemaBuilder ||
|
|
80
81
|
(callPath.node.callee.object.name !==
|
|
81
82
|
schemaBuilder.node.id.name &&
|
|
82
83
|
callPath.node.callee.property.name !== "implement")) {
|