@jaypie/constructs 1.2.31 → 1.2.33

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;
@@ -10,6 +10,7 @@ export interface JaypieEnvSecretProps {
10
10
  export?: string;
11
11
  generateSecretString?: secretsmanager.SecretStringGenerator;
12
12
  provider?: boolean;
13
+ removalPolicy?: boolean | RemovalPolicy;
13
14
  roleTag?: string;
14
15
  vendorTag?: string;
15
16
  value?: string;
@@ -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
  /**
@@ -21,10 +21,12 @@ 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');
26
27
  var cdkNextjsStandalone = require('cdk-nextjs-standalone');
27
28
  var path = require('path');
29
+ var awsAccessanalyzer = require('aws-cdk-lib/aws-accessanalyzer');
28
30
  var awsCloudtrail = require('aws-cdk-lib/aws-cloudtrail');
29
31
  var awsSso = require('aws-cdk-lib/aws-sso');
30
32
  var awsSam = require('aws-cdk-lib/aws-sam');
@@ -63,6 +65,7 @@ var lambdaEventSources__namespace = /*#__PURE__*/_interopNamespaceDefault(lambda
63
65
  var logs__namespace = /*#__PURE__*/_interopNamespaceDefault(logs);
64
66
  var cloudfront__namespace = /*#__PURE__*/_interopNamespaceDefault(cloudfront);
65
67
  var origins__namespace = /*#__PURE__*/_interopNamespaceDefault(origins);
68
+ var wafv2__namespace = /*#__PURE__*/_interopNamespaceDefault(wafv2);
66
69
  var dynamodb__namespace = /*#__PURE__*/_interopNamespaceDefault(dynamodb);
67
70
  var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
68
71
  var apigatewayv2__namespace = /*#__PURE__*/_interopNamespaceDefault(apigatewayv2);
@@ -945,7 +948,7 @@ class JaypieEnvSecret extends constructs.Construct {
945
948
  process.env[idOrEnvKey] !== "";
946
949
  const id = treatAsEnvKey ? `EnvSecret_${idOrEnvKey}` : idOrEnvKey;
947
950
  super(scope, id);
948
- const { consumer = checkEnvIsConsumer$1(), envKey: envKeyProp, export: exportParam, generateSecretString, provider = checkEnvIsProvider$1(), roleTag, vendorTag, value, } = props || {};
951
+ const { consumer = checkEnvIsConsumer$1(), envKey: envKeyProp, export: exportParam, generateSecretString, provider = checkEnvIsProvider$1(), removalPolicy, roleTag, vendorTag, value, } = props || {};
949
952
  const envKey = treatAsEnvKey ? idOrEnvKey : envKeyProp;
950
953
  this._envKey = envKey;
951
954
  let exportName;
@@ -972,6 +975,14 @@ class JaypieEnvSecret extends constructs.Construct {
972
975
  : undefined,
973
976
  };
974
977
  this._secret = new secretsmanager__namespace.Secret(this, id, secretProps);
978
+ if (removalPolicy !== undefined) {
979
+ const policy = typeof removalPolicy === "boolean"
980
+ ? removalPolicy
981
+ ? cdk.RemovalPolicy.RETAIN
982
+ : cdk.RemovalPolicy.DESTROY
983
+ : removalPolicy;
984
+ this._secret.applyRemovalPolicy(policy);
985
+ }
975
986
  if (roleTag) {
976
987
  cdk.Tags.of(this._secret).add(CDK$2.TAG.ROLE, roleTag);
977
988
  }
@@ -2366,10 +2377,15 @@ class JaypieDatadogSecret extends JaypieEnvSecret {
2366
2377
  }
2367
2378
  }
2368
2379
 
2380
+ const DEFAULT_RATE_LIMIT = 2000;
2381
+ const DEFAULT_MANAGED_RULES = [
2382
+ "AWSManagedRulesCommonRuleSet",
2383
+ "AWSManagedRulesKnownBadInputsRuleSet",
2384
+ ];
2369
2385
  class JaypieDistribution extends constructs.Construct {
2370
2386
  constructor(scope, id, props) {
2371
2387
  super(scope, id);
2372
- 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;
2388
+ 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;
2373
2389
  // Validate environment variables
2374
2390
  if (process.env.CDK_ENV_API_SUBDOMAIN &&
2375
2391
  !isValidSubdomain(process.env.CDK_ENV_API_SUBDOMAIN)) {
@@ -2612,6 +2628,119 @@ class JaypieDistribution extends constructs.Construct {
2612
2628
  this.distributionDomainName = this.distribution.distributionDomainName;
2613
2629
  this.distributionId = this.distribution.distributionId;
2614
2630
  this.domainName = this.distribution.domainName;
2631
+ // Create and attach WAF WebACL
2632
+ let resolvedWebAclArn;
2633
+ const wafConfig = this.resolveWafConfig(wafProp);
2634
+ if (wafConfig) {
2635
+ if (wafConfig.webAclArn) {
2636
+ // Use existing WebACL
2637
+ resolvedWebAclArn = wafConfig.webAclArn;
2638
+ this.distribution.attachWebAclId(wafConfig.webAclArn);
2639
+ }
2640
+ else {
2641
+ // Create new WebACL
2642
+ const { managedRules = DEFAULT_MANAGED_RULES, rateLimitPerIp = DEFAULT_RATE_LIMIT, } = wafConfig;
2643
+ let priority = 0;
2644
+ const rules = [];
2645
+ // Add managed rule groups
2646
+ for (const ruleName of managedRules) {
2647
+ rules.push({
2648
+ name: ruleName,
2649
+ priority: priority++,
2650
+ overrideAction: { none: {} },
2651
+ statement: {
2652
+ managedRuleGroupStatement: {
2653
+ name: ruleName,
2654
+ vendorName: "AWS",
2655
+ },
2656
+ },
2657
+ visibilityConfig: {
2658
+ cloudWatchMetricsEnabled: true,
2659
+ metricName: ruleName,
2660
+ sampledRequestsEnabled: true,
2661
+ },
2662
+ });
2663
+ }
2664
+ // Add rate-based rule
2665
+ rules.push({
2666
+ name: "RateLimitPerIp",
2667
+ priority: priority++,
2668
+ action: { block: {} },
2669
+ statement: {
2670
+ rateBasedStatement: {
2671
+ aggregateKeyType: "IP",
2672
+ limit: rateLimitPerIp,
2673
+ },
2674
+ },
2675
+ visibilityConfig: {
2676
+ cloudWatchMetricsEnabled: true,
2677
+ metricName: "RateLimitPerIp",
2678
+ sampledRequestsEnabled: true,
2679
+ },
2680
+ });
2681
+ const webAcl = new wafv2__namespace.CfnWebACL(this, "WebAcl", {
2682
+ defaultAction: { allow: {} },
2683
+ name: constructEnvName("WebAcl"),
2684
+ rules,
2685
+ scope: "CLOUDFRONT",
2686
+ visibilityConfig: {
2687
+ cloudWatchMetricsEnabled: true,
2688
+ metricName: constructEnvName("WebAcl"),
2689
+ sampledRequestsEnabled: true,
2690
+ },
2691
+ });
2692
+ this.webAcl = webAcl;
2693
+ resolvedWebAclArn = webAcl.attrArn;
2694
+ this.distribution.attachWebAclId(webAcl.attrArn);
2695
+ cdk.Tags.of(webAcl).add(CDK$2.TAG.ROLE, roleTag);
2696
+ }
2697
+ }
2698
+ // Create WAF logging
2699
+ if (resolvedWebAclArn && wafConfig) {
2700
+ const { logBucket: wafLogBucketProp = true } = wafConfig;
2701
+ let wafLogBucket;
2702
+ if (wafLogBucketProp === true) {
2703
+ // Create inline WAF logging bucket with Datadog forwarding
2704
+ const createdBucket = new s3__namespace.Bucket(this, constructEnvName("WafLogBucket"), {
2705
+ autoDeleteObjects: true,
2706
+ bucketName: `aws-waf-logs-${constructEnvName("waf").toLowerCase()}`,
2707
+ lifecycleRules: [
2708
+ {
2709
+ expiration: cdk.Duration.days(90),
2710
+ transitions: [
2711
+ {
2712
+ storageClass: s3__namespace.StorageClass.INFREQUENT_ACCESS,
2713
+ transitionAfter: cdk.Duration.days(30),
2714
+ },
2715
+ ],
2716
+ },
2717
+ ],
2718
+ objectOwnership: s3__namespace.ObjectOwnership.OBJECT_WRITER,
2719
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
2720
+ });
2721
+ cdk.Tags.of(createdBucket).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
2722
+ // Add Datadog forwarder notification
2723
+ if (destinationProp !== false) {
2724
+ const lambdaDestination = destinationProp === true
2725
+ ? new s3n.LambdaDestination(resolveDatadogForwarderFunction(this))
2726
+ : destinationProp;
2727
+ createdBucket.addEventNotification(s3__namespace.EventType.OBJECT_CREATED, lambdaDestination);
2728
+ }
2729
+ wafLogBucket = createdBucket;
2730
+ }
2731
+ else if (typeof wafLogBucketProp === "object") {
2732
+ // Use provided IBucket
2733
+ wafLogBucket = wafLogBucketProp;
2734
+ }
2735
+ // wafLogBucketProp === false → no logging
2736
+ if (wafLogBucket) {
2737
+ this.wafLogBucket = wafLogBucket;
2738
+ new wafv2__namespace.CfnLoggingConfiguration(this, "WafLoggingConfig", {
2739
+ logDestinationConfigs: [wafLogBucket.bucketArn],
2740
+ resourceArn: resolvedWebAclArn,
2741
+ });
2742
+ }
2743
+ }
2615
2744
  // Create DNS records if we have host and zone
2616
2745
  if (host && hostedZone) {
2617
2746
  const aRecord = new route53__namespace.ARecord(this, "AliasRecord", {
@@ -2658,6 +2787,15 @@ class JaypieDistribution extends constructs.Construct {
2658
2787
  "exportName" in value &&
2659
2788
  typeof value.exportName === "string");
2660
2789
  }
2790
+ resolveWafConfig(wafProp) {
2791
+ if (wafProp === false)
2792
+ return undefined;
2793
+ if (wafProp === true)
2794
+ return {};
2795
+ if (wafProp.enabled === false)
2796
+ return undefined;
2797
+ return wafProp;
2798
+ }
2661
2799
  resolveLogBucket(logBucketProp) {
2662
2800
  // true = use account logging bucket
2663
2801
  if (logBucketProp === true) {
@@ -3497,7 +3635,7 @@ class JaypieOrganizationTrail extends constructs.Construct {
3497
3635
  // Resolve options with defaults
3498
3636
  const { bucketName = process.env.PROJECT_NONCE
3499
3637
  ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
3500
- : "organization-cloudtrail", enableDatadogNotifications = true, enableFileValidation = false, expirationDays = 365, glacierTransitionDays = 180, infrequentAccessTransitionDays = 30, project, service = CDK$2.SERVICE.INFRASTRUCTURE, trailName = process.env.PROJECT_NONCE
3638
+ : "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
3501
3639
  ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
3502
3640
  : "organization-cloudtrail", } = props;
3503
3641
  // Create the S3 bucket for CloudTrail logs
@@ -3557,12 +3695,34 @@ class JaypieOrganizationTrail extends constructs.Construct {
3557
3695
  managementEvents: awsCloudtrail.ReadWriteType.ALL,
3558
3696
  trailName,
3559
3697
  });
3698
+ // Add data event selectors
3699
+ if (enableLambdaDataEvents) {
3700
+ this.trail.logAllLambdaDataEvents();
3701
+ }
3702
+ if (enableS3DataEvents) {
3703
+ this.trail.logAllS3DataEvents();
3704
+ }
3560
3705
  // Add tags to trail
3561
3706
  cdk__namespace.Tags.of(this.trail).add(CDK$2.TAG.SERVICE, service);
3562
3707
  cdk__namespace.Tags.of(this.trail).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
3563
3708
  if (project) {
3564
3709
  cdk__namespace.Tags.of(this.trail).add(CDK$2.TAG.PROJECT, project);
3565
3710
  }
3711
+ // Create IAM Access Analyzer
3712
+ if (enableAccessAnalyzer) {
3713
+ const analyzerName = process.env.PROJECT_NONCE
3714
+ ? `organization-access-analyzer-${process.env.PROJECT_NONCE}`
3715
+ : "organization-access-analyzer";
3716
+ this.analyzer = new awsAccessanalyzer.CfnAnalyzer(this, "AccessAnalyzer", {
3717
+ analyzerName,
3718
+ type: "ORGANIZATION",
3719
+ });
3720
+ cdk__namespace.Tags.of(this.analyzer).add(CDK$2.TAG.SERVICE, service);
3721
+ cdk__namespace.Tags.of(this.analyzer).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
3722
+ if (project) {
3723
+ cdk__namespace.Tags.of(this.analyzer).add(CDK$2.TAG.PROJECT, project);
3724
+ }
3725
+ }
3566
3726
  }
3567
3727
  }
3568
3728