@jaypie/constructs 1.2.32 → 1.2.34

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.
@@ -5,8 +5,38 @@ import * as lambda from "aws-cdk-lib/aws-lambda";
5
5
  import * as route53 from "aws-cdk-lib/aws-route53";
6
6
  import * as s3 from "aws-cdk-lib/aws-s3";
7
7
  import { LambdaDestination } from "aws-cdk-lib/aws-s3-notifications";
8
+ import * as wafv2 from "aws-cdk-lib/aws-wafv2";
8
9
  import { Construct } from "constructs";
9
10
  import { HostConfig } from "./helpers";
11
+ export interface JaypieWafConfig {
12
+ /**
13
+ * Whether WAF is enabled
14
+ * @default true
15
+ */
16
+ enabled?: boolean;
17
+ /**
18
+ * WAF logging bucket.
19
+ * - true/undefined: create a logging bucket with Datadog forwarding (default)
20
+ * - false: disable WAF logging
21
+ * - IBucket: use an existing bucket (must have "aws-waf-logs-" prefix)
22
+ * @default true
23
+ */
24
+ logBucket?: boolean | s3.IBucket;
25
+ /**
26
+ * Managed rule group names to apply
27
+ * @default ["AWSManagedRulesCommonRuleSet", "AWSManagedRulesKnownBadInputsRuleSet"]
28
+ */
29
+ managedRules?: string[];
30
+ /**
31
+ * Rate limit per IP per 5-minute window
32
+ * @default 2000
33
+ */
34
+ rateLimitPerIp?: number;
35
+ /**
36
+ * Use an existing WebACL ARN instead of creating one
37
+ */
38
+ webAclArn?: string;
39
+ }
10
40
  export interface SecurityHeadersOverrides {
11
41
  contentSecurityPolicy?: string;
12
42
  frameOption?: cloudfront.HeadersFrameOption;
@@ -99,6 +129,14 @@ export interface JaypieDistributionProps extends Omit<cloudfront.DistributionPro
99
129
  * @default CDK.ROLE.HOSTING
100
130
  */
101
131
  roleTag?: string;
132
+ /**
133
+ * WAF WebACL configuration for the CloudFront distribution.
134
+ * - true/undefined: create and attach a WebACL with sensible defaults
135
+ * - false: disable WAF
136
+ * - JaypieWafConfig: customize WAF behavior
137
+ * @default true
138
+ */
139
+ waf?: boolean | JaypieWafConfig;
102
140
  /**
103
141
  * The hosted zone for DNS records
104
142
  * @default CDK_ENV_API_HOSTED_ZONE || CDK_ENV_HOSTED_ZONE
@@ -116,11 +154,14 @@ export declare class JaypieDistribution extends Construct implements cloudfront.
116
154
  readonly host?: string;
117
155
  readonly logBucket?: s3.IBucket;
118
156
  readonly responseHeadersPolicy?: cloudfront.IResponseHeadersPolicy;
157
+ readonly wafLogBucket?: s3.IBucket;
158
+ readonly webAcl?: wafv2.CfnWebACL;
119
159
  constructor(scope: Construct, id: string, props: JaypieDistributionProps);
120
160
  private isIOrigin;
121
161
  private isIFunctionUrl;
122
162
  private isIFunction;
123
163
  private isExportNameObject;
164
+ private resolveWafConfig;
124
165
  private resolveLogBucket;
125
166
  get env(): {
126
167
  account: string;
@@ -89,6 +89,7 @@ export declare class JaypieDynamoDb extends Construct implements dynamodb.ITable
89
89
  get tableRef(): dynamodb.TableReference;
90
90
  get tableStreamArn(): string | undefined;
91
91
  get encryptionKey(): import("aws-cdk-lib/aws-kms").IKey | undefined;
92
+ get grants(): dynamodb.TableGrants;
92
93
  applyRemovalPolicy(policy: RemovalPolicy): void;
93
94
  grant(grantee: import("aws-cdk-lib/aws-iam").IGrantable, ...actions: string[]): import("aws-cdk-lib/aws-iam").Grant;
94
95
  grantFullAccess(grantee: import("aws-cdk-lib/aws-iam").IGrantable): import("aws-cdk-lib/aws-iam").Grant;
@@ -0,0 +1,21 @@
1
+ import { Construct } from "constructs";
2
+ import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
3
+ import * as lambda from "aws-cdk-lib/aws-lambda";
4
+ import { JaypieLambda } from "./JaypieLambda";
5
+ import type { SecretsArrayItem } from "./helpers/index.js";
6
+ export interface JaypieMigrationProps {
7
+ /** Path to the bundled migration code (esbuild output directory) */
8
+ code: lambda.Code | string;
9
+ /** Constructs that must be created before the migration runs */
10
+ dependencies?: Construct[];
11
+ /** Lambda handler entry point */
12
+ handler?: string;
13
+ /** Secrets to make available to the migration Lambda */
14
+ secrets?: SecretsArrayItem[];
15
+ /** DynamoDB tables to grant read/write access */
16
+ tables?: dynamodb.ITable[];
17
+ }
18
+ export declare class JaypieMigration extends Construct {
19
+ readonly lambda: JaypieLambda;
20
+ constructor(scope: Construct, id: string, props: JaypieMigrationProps);
21
+ }
@@ -1,4 +1,5 @@
1
1
  import { IBucket } from "aws-cdk-lib/aws-s3";
2
+ import { CfnAnalyzer } from "aws-cdk-lib/aws-accessanalyzer";
2
3
  import { Trail } from "aws-cdk-lib/aws-cloudtrail";
3
4
  import { Construct } from "constructs";
4
5
  export interface JaypieOrganizationTrailProps {
@@ -26,11 +27,26 @@ export interface JaypieOrganizationTrailProps {
26
27
  * Optional project tag value
27
28
  */
28
29
  project?: string;
30
+ /**
31
+ * Whether to enable IAM Access Analyzer (organization-level)
32
+ * @default true
33
+ */
34
+ enableAccessAnalyzer?: boolean;
29
35
  /**
30
36
  * Whether to enable file validation for the trail
31
- * @default false
37
+ * @default true
32
38
  */
33
39
  enableFileValidation?: boolean;
40
+ /**
41
+ * Whether to enable Lambda data events in CloudTrail
42
+ * @default true
43
+ */
44
+ enableLambdaDataEvents?: boolean;
45
+ /**
46
+ * Whether to enable S3 data events in CloudTrail
47
+ * @default false (opt-in due to potential high volume/cost)
48
+ */
49
+ enableS3DataEvents?: boolean;
34
50
  /**
35
51
  * Number of days before logs expire
36
52
  * @default 365
@@ -53,6 +69,7 @@ export interface JaypieOrganizationTrailProps {
53
69
  enableDatadogNotifications?: boolean;
54
70
  }
55
71
  export declare class JaypieOrganizationTrail extends Construct {
72
+ readonly analyzer?: CfnAnalyzer;
56
73
  readonly bucket: IBucket;
57
74
  readonly trail: Trail;
58
75
  /**
@@ -0,0 +1 @@
1
+ export {};
@@ -21,10 +21,13 @@ var awsEvents = require('aws-cdk-lib/aws-events');
21
21
  var awsEventsTargets = require('aws-cdk-lib/aws-events-targets');
22
22
  var cloudfront = require('aws-cdk-lib/aws-cloudfront');
23
23
  var origins = require('aws-cdk-lib/aws-cloudfront-origins');
24
+ var wafv2 = require('aws-cdk-lib/aws-wafv2');
24
25
  var dynamodb = require('aws-cdk-lib/aws-dynamodb');
25
26
  var fabric = require('@jaypie/fabric');
27
+ var cr = require('aws-cdk-lib/custom-resources');
26
28
  var cdkNextjsStandalone = require('cdk-nextjs-standalone');
27
29
  var path = require('path');
30
+ var awsAccessanalyzer = require('aws-cdk-lib/aws-accessanalyzer');
28
31
  var awsCloudtrail = require('aws-cdk-lib/aws-cloudtrail');
29
32
  var awsSso = require('aws-cdk-lib/aws-sso');
30
33
  var awsSam = require('aws-cdk-lib/aws-sam');
@@ -63,7 +66,9 @@ var lambdaEventSources__namespace = /*#__PURE__*/_interopNamespaceDefault(lambda
63
66
  var logs__namespace = /*#__PURE__*/_interopNamespaceDefault(logs);
64
67
  var cloudfront__namespace = /*#__PURE__*/_interopNamespaceDefault(cloudfront);
65
68
  var origins__namespace = /*#__PURE__*/_interopNamespaceDefault(origins);
69
+ var wafv2__namespace = /*#__PURE__*/_interopNamespaceDefault(wafv2);
66
70
  var dynamodb__namespace = /*#__PURE__*/_interopNamespaceDefault(dynamodb);
71
+ var cr__namespace = /*#__PURE__*/_interopNamespaceDefault(cr);
67
72
  var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
68
73
  var apigatewayv2__namespace = /*#__PURE__*/_interopNamespaceDefault(apigatewayv2);
69
74
  var apigatewayv2Integrations__namespace = /*#__PURE__*/_interopNamespaceDefault(apigatewayv2Integrations);
@@ -2374,10 +2379,15 @@ class JaypieDatadogSecret extends JaypieEnvSecret {
2374
2379
  }
2375
2380
  }
2376
2381
 
2382
+ const DEFAULT_RATE_LIMIT = 2000;
2383
+ const DEFAULT_MANAGED_RULES = [
2384
+ "AWSManagedRulesCommonRuleSet",
2385
+ "AWSManagedRulesKnownBadInputsRuleSet",
2386
+ ];
2377
2387
  class JaypieDistribution extends constructs.Construct {
2378
2388
  constructor(scope, id, props) {
2379
2389
  super(scope, id);
2380
- const { certificate: certificateProp = true, defaultBehavior: propsDefaultBehavior, destination: destinationProp = true, handler, host: propsHost, logBucket: logBucketProp, originReadTimeout = cdk.Duration.seconds(CDK$2.DURATION.CLOUDFRONT_API), responseHeadersPolicy: responseHeadersPolicyProp, roleTag = CDK$2.ROLE.API, securityHeaders: securityHeadersProp, streaming = false, zone: propsZone, ...distributionProps } = props;
2390
+ const { certificate: certificateProp = true, defaultBehavior: propsDefaultBehavior, destination: destinationProp = true, handler, host: propsHost, logBucket: logBucketProp, originReadTimeout = cdk.Duration.seconds(CDK$2.DURATION.CLOUDFRONT_API), responseHeadersPolicy: responseHeadersPolicyProp, roleTag = CDK$2.ROLE.API, securityHeaders: securityHeadersProp, streaming = false, waf: wafProp = true, zone: propsZone, ...distributionProps } = props;
2381
2391
  // Validate environment variables
2382
2392
  if (process.env.CDK_ENV_API_SUBDOMAIN &&
2383
2393
  !isValidSubdomain(process.env.CDK_ENV_API_SUBDOMAIN)) {
@@ -2620,6 +2630,118 @@ class JaypieDistribution extends constructs.Construct {
2620
2630
  this.distributionDomainName = this.distribution.distributionDomainName;
2621
2631
  this.distributionId = this.distribution.distributionId;
2622
2632
  this.domainName = this.distribution.domainName;
2633
+ // Create and attach WAF WebACL
2634
+ let resolvedWebAclArn;
2635
+ const wafConfig = this.resolveWafConfig(wafProp);
2636
+ if (wafConfig) {
2637
+ if (wafConfig.webAclArn) {
2638
+ // Use existing WebACL
2639
+ resolvedWebAclArn = wafConfig.webAclArn;
2640
+ this.distribution.attachWebAclId(wafConfig.webAclArn);
2641
+ }
2642
+ else {
2643
+ // Create new WebACL
2644
+ const { managedRules = DEFAULT_MANAGED_RULES, rateLimitPerIp = DEFAULT_RATE_LIMIT, } = wafConfig;
2645
+ let priority = 0;
2646
+ const rules = [];
2647
+ // Add managed rule groups
2648
+ for (const ruleName of managedRules) {
2649
+ rules.push({
2650
+ name: ruleName,
2651
+ priority: priority++,
2652
+ overrideAction: { none: {} },
2653
+ statement: {
2654
+ managedRuleGroupStatement: {
2655
+ name: ruleName,
2656
+ vendorName: "AWS",
2657
+ },
2658
+ },
2659
+ visibilityConfig: {
2660
+ cloudWatchMetricsEnabled: true,
2661
+ metricName: ruleName,
2662
+ sampledRequestsEnabled: true,
2663
+ },
2664
+ });
2665
+ }
2666
+ // Add rate-based rule
2667
+ rules.push({
2668
+ name: "RateLimitPerIp",
2669
+ priority: priority++,
2670
+ action: { block: {} },
2671
+ statement: {
2672
+ rateBasedStatement: {
2673
+ aggregateKeyType: "IP",
2674
+ limit: rateLimitPerIp,
2675
+ },
2676
+ },
2677
+ visibilityConfig: {
2678
+ cloudWatchMetricsEnabled: true,
2679
+ metricName: "RateLimitPerIp",
2680
+ sampledRequestsEnabled: true,
2681
+ },
2682
+ });
2683
+ const webAcl = new wafv2__namespace.CfnWebACL(this, "WebAcl", {
2684
+ defaultAction: { allow: {} },
2685
+ name: constructEnvName("WebAcl"),
2686
+ rules,
2687
+ scope: "CLOUDFRONT",
2688
+ visibilityConfig: {
2689
+ cloudWatchMetricsEnabled: true,
2690
+ metricName: constructEnvName("WebAcl"),
2691
+ sampledRequestsEnabled: true,
2692
+ },
2693
+ });
2694
+ this.webAcl = webAcl;
2695
+ resolvedWebAclArn = webAcl.attrArn;
2696
+ this.distribution.attachWebAclId(webAcl.attrArn);
2697
+ cdk.Tags.of(webAcl).add(CDK$2.TAG.ROLE, roleTag);
2698
+ }
2699
+ }
2700
+ // Create WAF logging
2701
+ if (resolvedWebAclArn && wafConfig) {
2702
+ const { logBucket: wafLogBucketProp = true } = wafConfig;
2703
+ let wafLogBucket;
2704
+ if (wafLogBucketProp === true) {
2705
+ // Create inline WAF logging bucket with Datadog forwarding
2706
+ const createdBucket = new s3__namespace.Bucket(this, constructEnvName("WafLogBucket"), {
2707
+ bucketName: `aws-waf-logs-${constructEnvName("waf").toLowerCase()}`,
2708
+ lifecycleRules: [
2709
+ {
2710
+ expiration: cdk.Duration.days(90),
2711
+ transitions: [
2712
+ {
2713
+ storageClass: s3__namespace.StorageClass.INFREQUENT_ACCESS,
2714
+ transitionAfter: cdk.Duration.days(30),
2715
+ },
2716
+ ],
2717
+ },
2718
+ ],
2719
+ objectOwnership: s3__namespace.ObjectOwnership.OBJECT_WRITER,
2720
+ removalPolicy: cdk.RemovalPolicy.RETAIN,
2721
+ });
2722
+ cdk.Tags.of(createdBucket).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
2723
+ // Add Datadog forwarder notification
2724
+ if (destinationProp !== false) {
2725
+ const lambdaDestination = destinationProp === true
2726
+ ? new s3n.LambdaDestination(resolveDatadogForwarderFunction(this))
2727
+ : destinationProp;
2728
+ createdBucket.addEventNotification(s3__namespace.EventType.OBJECT_CREATED, lambdaDestination);
2729
+ }
2730
+ wafLogBucket = createdBucket;
2731
+ }
2732
+ else if (typeof wafLogBucketProp === "object") {
2733
+ // Use provided IBucket
2734
+ wafLogBucket = wafLogBucketProp;
2735
+ }
2736
+ // wafLogBucketProp === false → no logging
2737
+ if (wafLogBucket) {
2738
+ this.wafLogBucket = wafLogBucket;
2739
+ new wafv2__namespace.CfnLoggingConfiguration(this, "WafLoggingConfig", {
2740
+ logDestinationConfigs: [wafLogBucket.bucketArn],
2741
+ resourceArn: resolvedWebAclArn,
2742
+ });
2743
+ }
2744
+ }
2623
2745
  // Create DNS records if we have host and zone
2624
2746
  if (host && hostedZone) {
2625
2747
  const aRecord = new route53__namespace.ARecord(this, "AliasRecord", {
@@ -2666,6 +2788,15 @@ class JaypieDistribution extends constructs.Construct {
2666
2788
  "exportName" in value &&
2667
2789
  typeof value.exportName === "string");
2668
2790
  }
2791
+ resolveWafConfig(wafProp) {
2792
+ if (wafProp === false)
2793
+ return undefined;
2794
+ if (wafProp === true)
2795
+ return {};
2796
+ if (wafProp.enabled === false)
2797
+ return undefined;
2798
+ return wafProp;
2799
+ }
2669
2800
  resolveLogBucket(logBucketProp) {
2670
2801
  // true = use account logging bucket
2671
2802
  if (logBucketProp === true) {
@@ -2929,6 +3060,9 @@ class JaypieDynamoDb extends constructs.Construct {
2929
3060
  get encryptionKey() {
2930
3061
  return this._table.encryptionKey;
2931
3062
  }
3063
+ get grants() {
3064
+ return this._table.grants;
3065
+ }
2932
3066
  applyRemovalPolicy(policy) {
2933
3067
  this._table.applyRemovalPolicy(policy);
2934
3068
  }
@@ -3287,6 +3421,35 @@ class JaypieInfrastructureStack extends JaypieStack {
3287
3421
  }
3288
3422
  }
3289
3423
 
3424
+ class JaypieMigration extends constructs.Construct {
3425
+ constructor(scope, id, props) {
3426
+ super(scope, id);
3427
+ const { code, dependencies = [], handler = "index.handler", secrets = [], tables = [], } = props;
3428
+ // Migration Lambda — 5 minute timeout for long-running migrations
3429
+ this.lambda = new JaypieLambda(this, "MigrationLambda", {
3430
+ code,
3431
+ description: "DynamoDB migration custom resource",
3432
+ handler,
3433
+ roleTag: CDK$2.ROLE.PROCESSING,
3434
+ secrets,
3435
+ tables,
3436
+ timeout: cdk__namespace.Duration.minutes(5),
3437
+ });
3438
+ // Custom Resource provider wrapping the Lambda
3439
+ const provider = new cr__namespace.Provider(this, "MigrationProvider", {
3440
+ onEventHandler: this.lambda,
3441
+ });
3442
+ // Custom Resource that triggers on every deploy
3443
+ const resource = new cdk__namespace.CustomResource(this, "MigrationResource", {
3444
+ serviceToken: provider.serviceToken,
3445
+ });
3446
+ // Ensure dependencies are created before the migration runs
3447
+ for (const dep of dependencies) {
3448
+ resource.node.addDependency(dep);
3449
+ }
3450
+ }
3451
+ }
3452
+
3290
3453
  class JaypieMongoDbSecret extends JaypieEnvSecret {
3291
3454
  constructor(scope, id = "MongoConnectionString", props) {
3292
3455
  const defaultProps = {
@@ -3505,7 +3668,7 @@ class JaypieOrganizationTrail extends constructs.Construct {
3505
3668
  // Resolve options with defaults
3506
3669
  const { bucketName = process.env.PROJECT_NONCE
3507
3670
  ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
3508
- : "organization-cloudtrail", enableDatadogNotifications = true, enableFileValidation = false, expirationDays = 365, glacierTransitionDays = 180, infrequentAccessTransitionDays = 30, project, service = CDK$2.SERVICE.INFRASTRUCTURE, trailName = process.env.PROJECT_NONCE
3671
+ : "organization-cloudtrail", enableAccessAnalyzer = true, enableDatadogNotifications = true, enableFileValidation = true, enableLambdaDataEvents = true, enableS3DataEvents = false, expirationDays = 365, glacierTransitionDays = 180, infrequentAccessTransitionDays = 30, project, service = CDK$2.SERVICE.INFRASTRUCTURE, trailName = process.env.PROJECT_NONCE
3509
3672
  ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
3510
3673
  : "organization-cloudtrail", } = props;
3511
3674
  // Create the S3 bucket for CloudTrail logs
@@ -3565,12 +3728,34 @@ class JaypieOrganizationTrail extends constructs.Construct {
3565
3728
  managementEvents: awsCloudtrail.ReadWriteType.ALL,
3566
3729
  trailName,
3567
3730
  });
3731
+ // Add data event selectors
3732
+ if (enableLambdaDataEvents) {
3733
+ this.trail.logAllLambdaDataEvents();
3734
+ }
3735
+ if (enableS3DataEvents) {
3736
+ this.trail.logAllS3DataEvents();
3737
+ }
3568
3738
  // Add tags to trail
3569
3739
  cdk__namespace.Tags.of(this.trail).add(CDK$2.TAG.SERVICE, service);
3570
3740
  cdk__namespace.Tags.of(this.trail).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
3571
3741
  if (project) {
3572
3742
  cdk__namespace.Tags.of(this.trail).add(CDK$2.TAG.PROJECT, project);
3573
3743
  }
3744
+ // Create IAM Access Analyzer
3745
+ if (enableAccessAnalyzer) {
3746
+ const analyzerName = process.env.PROJECT_NONCE
3747
+ ? `organization-access-analyzer-${process.env.PROJECT_NONCE}`
3748
+ : "organization-access-analyzer";
3749
+ this.analyzer = new awsAccessanalyzer.CfnAnalyzer(this, "AccessAnalyzer", {
3750
+ analyzerName,
3751
+ type: "ORGANIZATION",
3752
+ });
3753
+ cdk__namespace.Tags.of(this.analyzer).add(CDK$2.TAG.SERVICE, service);
3754
+ cdk__namespace.Tags.of(this.analyzer).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
3755
+ if (project) {
3756
+ cdk__namespace.Tags.of(this.analyzer).add(CDK$2.TAG.PROJECT, project);
3757
+ }
3758
+ }
3574
3759
  }
3575
3760
  }
3576
3761
 
@@ -4633,6 +4818,7 @@ exports.JaypieGitHubDeployRole = JaypieGitHubDeployRole;
4633
4818
  exports.JaypieHostedZone = JaypieHostedZone;
4634
4819
  exports.JaypieInfrastructureStack = JaypieInfrastructureStack;
4635
4820
  exports.JaypieLambda = JaypieLambda;
4821
+ exports.JaypieMigration = JaypieMigration;
4636
4822
  exports.JaypieMongoDbSecret = JaypieMongoDbSecret;
4637
4823
  exports.JaypieNextJs = JaypieNextJs;
4638
4824
  exports.JaypieOpenAiSecret = JaypieOpenAiSecret;