@jaypie/constructs 1.2.53 → 1.2.55
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/dist/cjs/JaypieDistribution.d.ts +31 -0
- package/dist/cjs/JaypieMigration.d.ts +3 -0
- package/dist/cjs/index.cjs +170 -53
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/JaypieDistribution.d.ts +31 -0
- package/dist/esm/JaypieMigration.d.ts +3 -0
- package/dist/esm/index.js +125 -8
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,17 @@ import { LambdaDestination } from "aws-cdk-lib/aws-s3-notifications";
|
|
|
8
8
|
import * as wafv2 from "aws-cdk-lib/aws-wafv2";
|
|
9
9
|
import { Construct } from "constructs";
|
|
10
10
|
import { HostConfig } from "./helpers";
|
|
11
|
+
/**
|
|
12
|
+
* One entry in a `waf.allow` list. Names one or more URL paths and, for each
|
|
13
|
+
* managed rule group key, the sub-rule names to flip from `block` to `count`
|
|
14
|
+
* on that path set. See JaypieWafConfig.allow.
|
|
15
|
+
*/
|
|
16
|
+
export interface JaypieWafAllowEntry {
|
|
17
|
+
/** URL path or paths. Trailing `*` → STARTS_WITH; otherwise EXACTLY. */
|
|
18
|
+
path: string | string[];
|
|
19
|
+
/** Managed-rule-group keys (e.g. AWSManagedRulesCommonRuleSet) → sub-rule names. */
|
|
20
|
+
[ruleGroupKey: string]: string | string[] | undefined;
|
|
21
|
+
}
|
|
11
22
|
export interface JaypieWafConfig {
|
|
12
23
|
/**
|
|
13
24
|
* Unique name for this distribution's WAF resources. Required when passing a
|
|
@@ -76,6 +87,26 @@ export interface JaypieWafConfig {
|
|
|
76
87
|
* @default 2000
|
|
77
88
|
*/
|
|
78
89
|
rateLimitPerIp?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Path-scoped relaxations layered on top of the default managed-rule groups.
|
|
92
|
+
* Each entry names one or more URL paths and, for each managed rule group
|
|
93
|
+
* key, the sub-rule names to flip from `block` to `count` on that path set.
|
|
94
|
+
* Strict default action is preserved on every other path.
|
|
95
|
+
*
|
|
96
|
+
* Composes with `managedRuleOverrides`: the baseline override list applies
|
|
97
|
+
* to both the relaxed and strict emissions of a group; entries in `allow`
|
|
98
|
+
* additionally relax specific (path × sub-rule) intersections.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* allow: [
|
|
102
|
+
* {
|
|
103
|
+
* path: "/hooks/*",
|
|
104
|
+
* AWSManagedRulesCommonRuleSet: ["ExploitablePaths_URIPATH"],
|
|
105
|
+
* AWSManagedRulesKnownBadInputsRuleSet: ["CrossSiteScripting_BODY"],
|
|
106
|
+
* },
|
|
107
|
+
* ]
|
|
108
|
+
*/
|
|
109
|
+
allow?: JaypieWafAllowEntry | JaypieWafAllowEntry[];
|
|
79
110
|
/**
|
|
80
111
|
* Use an existing WebACL ARN instead of creating one
|
|
81
112
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
|
+
import * as cdk from "aws-cdk-lib";
|
|
2
3
|
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
|
|
3
4
|
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
4
5
|
import { JaypieLambda } from "./JaypieLambda";
|
|
@@ -16,6 +17,8 @@ export interface JaypieMigrationProps {
|
|
|
16
17
|
secrets?: SecretsArrayItem[];
|
|
17
18
|
/** DynamoDB tables to grant read/write access */
|
|
18
19
|
tables?: dynamodb.ITable[];
|
|
20
|
+
/** Lambda timeout. Defaults to 15 minutes (Lambda max). */
|
|
21
|
+
timeout?: cdk.Duration;
|
|
19
22
|
}
|
|
20
23
|
export declare class JaypieMigration extends Construct {
|
|
21
24
|
readonly lambda: JaypieLambda;
|
package/dist/esm/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import * as route53Targets from 'aws-cdk-lib/aws-route53-targets';
|
|
|
10
10
|
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
|
11
11
|
import { DatadogLambda } from 'datadog-cdk-constructs-v2';
|
|
12
12
|
import { ConfigurationError } from '@jaypie/errors';
|
|
13
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
13
14
|
import { Role, PolicyStatement, Policy, FederatedPrincipal, Effect, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
|
|
14
15
|
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
|
|
15
16
|
import * as logs from 'aws-cdk-lib/aws-logs';
|
|
@@ -2680,13 +2681,110 @@ class JaypieDistribution extends Construct {
|
|
|
2680
2681
|
}
|
|
2681
2682
|
else {
|
|
2682
2683
|
// Create new WebACL
|
|
2683
|
-
const { managedRuleOverrides, managedRuleScopeDowns, managedRules = DEFAULT_MANAGED_RULES$1, rateLimitPerIp = DEFAULT_RATE_LIMIT$1, } = wafConfig;
|
|
2684
|
+
const { allow, managedRuleOverrides, managedRuleScopeDowns, managedRules = DEFAULT_MANAGED_RULES$1, rateLimitPerIp = DEFAULT_RATE_LIMIT$1, } = wafConfig;
|
|
2685
|
+
const allowEntries = allow
|
|
2686
|
+
? Array.isArray(allow)
|
|
2687
|
+
? allow
|
|
2688
|
+
: [allow]
|
|
2689
|
+
: [];
|
|
2690
|
+
const groupAllowances = {};
|
|
2691
|
+
for (const entry of allowEntries) {
|
|
2692
|
+
const paths = Array.isArray(entry.path) ? entry.path : [entry.path];
|
|
2693
|
+
for (const key of Object.keys(entry)) {
|
|
2694
|
+
if (key === "path")
|
|
2695
|
+
continue;
|
|
2696
|
+
const raw = entry[key];
|
|
2697
|
+
if (raw == null)
|
|
2698
|
+
continue;
|
|
2699
|
+
const ruleNames = Array.isArray(raw) ? raw : [raw];
|
|
2700
|
+
if (!groupAllowances[key])
|
|
2701
|
+
groupAllowances[key] = [];
|
|
2702
|
+
groupAllowances[key].push({ paths, ruleNames });
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
const pathToStatement = (path) => {
|
|
2706
|
+
const isPrefix = path.endsWith("*");
|
|
2707
|
+
return {
|
|
2708
|
+
byteMatchStatement: {
|
|
2709
|
+
fieldToMatch: { uriPath: {} },
|
|
2710
|
+
positionalConstraint: isPrefix ? "STARTS_WITH" : "EXACTLY",
|
|
2711
|
+
searchString: isPrefix ? path.slice(0, -1) : path,
|
|
2712
|
+
textTransformations: [{ priority: 0, type: "NONE" }],
|
|
2713
|
+
},
|
|
2714
|
+
};
|
|
2715
|
+
};
|
|
2716
|
+
const pathsToStatement = (paths) => {
|
|
2717
|
+
if (paths.length === 1)
|
|
2718
|
+
return pathToStatement(paths[0]);
|
|
2719
|
+
return {
|
|
2720
|
+
orStatement: { statements: paths.map(pathToStatement) },
|
|
2721
|
+
};
|
|
2722
|
+
};
|
|
2723
|
+
const andScopeDown = (base, extra) => base ? { andStatement: { statements: [base, extra] } } : extra;
|
|
2684
2724
|
let priority = 0;
|
|
2685
2725
|
const rules = [];
|
|
2686
2726
|
// Add managed rule groups
|
|
2687
2727
|
for (const ruleName of managedRules) {
|
|
2688
|
-
const
|
|
2689
|
-
const
|
|
2728
|
+
const baseOverrides = managedRuleOverrides?.[ruleName];
|
|
2729
|
+
const baseScopeDown = managedRuleScopeDowns?.[ruleName];
|
|
2730
|
+
const allowances = groupAllowances[ruleName];
|
|
2731
|
+
if (!allowances || allowances.length === 0) {
|
|
2732
|
+
rules.push({
|
|
2733
|
+
name: ruleName,
|
|
2734
|
+
priority: priority++,
|
|
2735
|
+
overrideAction: { none: {} },
|
|
2736
|
+
statement: {
|
|
2737
|
+
managedRuleGroupStatement: {
|
|
2738
|
+
name: ruleName,
|
|
2739
|
+
vendorName: "AWS",
|
|
2740
|
+
...(baseOverrides && { ruleActionOverrides: baseOverrides }),
|
|
2741
|
+
...(baseScopeDown && { scopeDownStatement: baseScopeDown }),
|
|
2742
|
+
},
|
|
2743
|
+
},
|
|
2744
|
+
visibilityConfig: {
|
|
2745
|
+
cloudWatchMetricsEnabled: true,
|
|
2746
|
+
metricName: ruleName,
|
|
2747
|
+
sampledRequestsEnabled: true,
|
|
2748
|
+
},
|
|
2749
|
+
});
|
|
2750
|
+
continue;
|
|
2751
|
+
}
|
|
2752
|
+
// Emit one relaxed rule per allow entry that names this group
|
|
2753
|
+
allowances.forEach(({ paths, ruleNames }, index) => {
|
|
2754
|
+
const entryOverrides = ruleNames.map((n) => ({
|
|
2755
|
+
name: n,
|
|
2756
|
+
actionToUse: { count: {} },
|
|
2757
|
+
}));
|
|
2758
|
+
const combinedOverrides = [
|
|
2759
|
+
...(baseOverrides ?? []),
|
|
2760
|
+
...entryOverrides,
|
|
2761
|
+
];
|
|
2762
|
+
const relaxedScopeDown = andScopeDown(baseScopeDown, pathsToStatement(paths));
|
|
2763
|
+
const relaxedName = `${ruleName}-allow-${index}`;
|
|
2764
|
+
rules.push({
|
|
2765
|
+
name: relaxedName,
|
|
2766
|
+
priority: priority++,
|
|
2767
|
+
overrideAction: { none: {} },
|
|
2768
|
+
statement: {
|
|
2769
|
+
managedRuleGroupStatement: {
|
|
2770
|
+
name: ruleName,
|
|
2771
|
+
vendorName: "AWS",
|
|
2772
|
+
ruleActionOverrides: combinedOverrides,
|
|
2773
|
+
scopeDownStatement: relaxedScopeDown,
|
|
2774
|
+
},
|
|
2775
|
+
},
|
|
2776
|
+
visibilityConfig: {
|
|
2777
|
+
cloudWatchMetricsEnabled: true,
|
|
2778
|
+
metricName: relaxedName,
|
|
2779
|
+
sampledRequestsEnabled: true,
|
|
2780
|
+
},
|
|
2781
|
+
});
|
|
2782
|
+
});
|
|
2783
|
+
// Emit one strict rule whose scope-down excludes every relaxed path
|
|
2784
|
+
const allPaths = allowances.flatMap((a) => a.paths);
|
|
2785
|
+
const strictScopeDown = andScopeDown(baseScopeDown, {
|
|
2786
|
+
notStatement: { statement: pathsToStatement(allPaths) },
|
|
2787
|
+
});
|
|
2690
2788
|
rules.push({
|
|
2691
2789
|
name: ruleName,
|
|
2692
2790
|
priority: priority++,
|
|
@@ -2695,8 +2793,8 @@ class JaypieDistribution extends Construct {
|
|
|
2695
2793
|
managedRuleGroupStatement: {
|
|
2696
2794
|
name: ruleName,
|
|
2697
2795
|
vendorName: "AWS",
|
|
2698
|
-
...(
|
|
2699
|
-
|
|
2796
|
+
...(baseOverrides && { ruleActionOverrides: baseOverrides }),
|
|
2797
|
+
scopeDownStatement: strictScopeDown,
|
|
2700
2798
|
},
|
|
2701
2799
|
},
|
|
2702
2800
|
visibilityConfig: {
|
|
@@ -3524,11 +3622,18 @@ class JaypieInfrastructureStack extends JaypieStack {
|
|
|
3524
3622
|
}
|
|
3525
3623
|
}
|
|
3526
3624
|
|
|
3625
|
+
const DYNAMODB_CONTROL_PLANE_ACTIONS = [
|
|
3626
|
+
"dynamodb:DescribeContinuousBackups",
|
|
3627
|
+
"dynamodb:DescribeTable",
|
|
3628
|
+
"dynamodb:DescribeTimeToLive",
|
|
3629
|
+
"dynamodb:UpdateContinuousBackups",
|
|
3630
|
+
"dynamodb:UpdateTable",
|
|
3631
|
+
"dynamodb:UpdateTimeToLive",
|
|
3632
|
+
];
|
|
3527
3633
|
class JaypieMigration extends Construct {
|
|
3528
3634
|
constructor(scope, id, props) {
|
|
3529
3635
|
super(scope, id);
|
|
3530
|
-
const { code, dependencies = [], environment, handler = "index.handler", secrets = [], tables = [], } = props;
|
|
3531
|
-
// Migration Lambda — 5 minute timeout for long-running migrations
|
|
3636
|
+
const { code, dependencies = [], environment, handler = "index.handler", secrets = [], tables = [], timeout = cdk.Duration.minutes(15), } = props;
|
|
3532
3637
|
this.lambda = new JaypieLambda(this, "MigrationLambda", {
|
|
3533
3638
|
code,
|
|
3534
3639
|
description: "DynamoDB migration custom resource",
|
|
@@ -3537,8 +3642,20 @@ class JaypieMigration extends Construct {
|
|
|
3537
3642
|
roleTag: CDK$2.ROLE.PROCESSING,
|
|
3538
3643
|
secrets,
|
|
3539
3644
|
tables,
|
|
3540
|
-
timeout
|
|
3645
|
+
timeout,
|
|
3541
3646
|
});
|
|
3647
|
+
// Grant control-plane perms on the passed tables so migrations that
|
|
3648
|
+
// alter table shape (GSIs, TTL, streams, backups) succeed. JaypieLambda
|
|
3649
|
+
// only grants data-plane access via grantReadWriteData. Issue #339.
|
|
3650
|
+
if (tables.length > 0) {
|
|
3651
|
+
this.lambda.addToRolePolicy(new iam.PolicyStatement({
|
|
3652
|
+
actions: DYNAMODB_CONTROL_PLANE_ACTIONS,
|
|
3653
|
+
resources: tables.flatMap((table) => [
|
|
3654
|
+
table.tableArn,
|
|
3655
|
+
`${table.tableArn}/index/*`,
|
|
3656
|
+
]),
|
|
3657
|
+
}));
|
|
3658
|
+
}
|
|
3542
3659
|
// Custom Resource provider wrapping the Lambda
|
|
3543
3660
|
const provider = new cr.Provider(this, "MigrationProvider", {
|
|
3544
3661
|
onEventHandler: this.lambda,
|