@empire-builder-kit/nx 1.0.0-rc.33 → 1.0.0-rc.35
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/package.json +3 -3
- package/src/aws-authority/stage-authority.js +4 -0
- package/src/aws-authority/stage-authority.js.map +1 -1
- package/src/executors/deployment-plan/deployment-plan.js +3 -0
- package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
- package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
- package/src/executors/local-deploy/local-deploy.d.ts +12 -3
- package/src/executors/local-deploy/local-deploy.js +156 -0
- package/src/executors/local-deploy/local-deploy.js.map +1 -1
- package/src/executors/local-deploy/schema.json +21 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +13 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +323 -137
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +1 -1
- package/src/executors/router-waf-rehearsal/schema.json +5 -0
- package/src/generators/preset/workspace-files-thin-deploy.d.ts +14 -1
- package/src/generators/preset/workspace-files-thin-deploy.js +897 -28
- package/src/generators/preset/workspace-files-thin-deploy.js.map +1 -1
- package/src/generators/preset/workspace-files-thin-docs.d.ts +2 -0
- package/src/generators/preset/workspace-files-thin-docs.js +51 -6
- package/src/generators/preset/workspace-files-thin-docs.js.map +1 -1
- package/src/generators/preset/workspace-files.d.ts +7 -1
- package/src/generators/preset/workspace-files.js +80 -18
- package/src/generators/preset/workspace-files.js.map +1 -1
- package/src/generators/repository-policy/repository-policy.js +1 -0
- package/src/generators/repository-policy/repository-policy.js.map +1 -1
- package/src/generators/repository-policy/schema.d.ts +2 -0
- package/src/generators/repository-policy/schema.json +26 -6
- package/src/generators/slice/built-in-plan-infrastructure-files.js +12 -0
- package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -1
- package/src/generators/upgrade/upgrade.d.ts +12 -0
- package/src/generators/upgrade/upgrade.js +68 -0
- package/src/generators/upgrade/upgrade.js.map +1 -1
- package/src/git-policy/action-pins.d.ts +5 -0
- package/src/git-policy/action-pins.js +5 -0
- package/src/git-policy/action-pins.js.map +1 -1
- package/src/git-policy/github-bootstrap.d.ts +10 -2
- package/src/git-policy/github-bootstrap.js +129 -34
- package/src/git-policy/github-bootstrap.js.map +1 -1
- package/src/git-policy/routing.d.ts +5 -1
- package/src/git-policy/routing.js +50 -0
- package/src/git-policy/routing.js.map +1 -1
- package/src/repository-policy/policy.d.ts +12 -1
- package/src/repository-policy/policy.js +42 -2
- package/src/repository-policy/policy.js.map +1 -1
- package/src/schemas/repository-policy-v1.json +55 -16
- package/src/supply-chain/generated-container-release-command.js +281 -74
- package/src/supply-chain/generated-container-release-command.js.map +1 -1
- package/src/supply-chain/generated-files.js +10 -11
- package/src/supply-chain/generated-files.js.map +1 -1
- package/src/workspace-deployment/affected-lifecycle.d.ts +53 -0
- package/src/workspace-deployment/affected-lifecycle.js +84 -0
- package/src/workspace-deployment/affected-lifecycle.js.map +1 -0
- package/src/workspace-deployment/index.d.ts +1 -0
- package/src/workspace-deployment/index.js +1 -0
- package/src/workspace-deployment/index.js.map +1 -1
|
@@ -5,6 +5,7 @@ const client_cloudwatch_1 = require("@aws-sdk/client-cloudwatch");
|
|
|
5
5
|
const client_cloudwatch_logs_1 = require("@aws-sdk/client-cloudwatch-logs");
|
|
6
6
|
const client_cloudfront_1 = require("@aws-sdk/client-cloudfront");
|
|
7
7
|
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
8
|
+
const node_https_1 = require("node:https");
|
|
8
9
|
const client_wafv2_1 = require("@aws-sdk/client-wafv2");
|
|
9
10
|
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
10
11
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -14,6 +15,7 @@ const devkit_1 = require("@nx/devkit");
|
|
|
14
15
|
const index_js_1 = require("../../foundation/index.js");
|
|
15
16
|
const router_waf_rate_rehearsal_js_1 = require("../../foundation/router-waf-rate-rehearsal.js");
|
|
16
17
|
const index_js_2 = require("../../aws-authority/index.js");
|
|
18
|
+
const private_smoke_access_js_1 = require("../../foundation/private-smoke-access.js");
|
|
17
19
|
const identity_js_1 = require("../../aws/identity.js");
|
|
18
20
|
const index_js_3 = require("../../repository-policy/index.js");
|
|
19
21
|
const deterministic_order_js_1 = require("../../deterministic-order.js");
|
|
@@ -772,6 +774,83 @@ function mergeRateEvidence(evidence, allowedRequestStatus, rateLimit, completedA
|
|
|
772
774
|
probes: { ...probes, allowedRequestStatus, rateLimit },
|
|
773
775
|
};
|
|
774
776
|
}
|
|
777
|
+
const SEED_ATTESTATION_RETRIES = 12;
|
|
778
|
+
const SEED_ATTESTATION_INTERVAL_MS = 10_000;
|
|
779
|
+
function rawGet(url, headers) {
|
|
780
|
+
return new Promise((resolve, reject) => {
|
|
781
|
+
const request = (0, node_https_1.request)(url, { headers, method: 'GET', timeout: REQUEST_TIMEOUT_MS }, (response) => {
|
|
782
|
+
response.resume();
|
|
783
|
+
resolve({ status: response.statusCode ?? 0 });
|
|
784
|
+
});
|
|
785
|
+
request.on('timeout', () => request.destroy(new Error('timeout')));
|
|
786
|
+
request.on('error', reject);
|
|
787
|
+
request.end();
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
async function attestOperatorBlocks(input) {
|
|
791
|
+
// AWS WAF publishes sampled requests with a short delay; a seeded run
|
|
792
|
+
// probed moments ago, so its attestation waits a bounded number of times.
|
|
793
|
+
for (let attempt = 0;; attempt += 1) {
|
|
794
|
+
try {
|
|
795
|
+
await Promise.all([
|
|
796
|
+
attestOperatorBlock({
|
|
797
|
+
evidence: input.seed.probes
|
|
798
|
+
.managedRule,
|
|
799
|
+
stage: input.stage,
|
|
800
|
+
type: 'managed',
|
|
801
|
+
waf: input.waf,
|
|
802
|
+
webAclArn: input.seed.webAclArn,
|
|
803
|
+
}),
|
|
804
|
+
attestOperatorBlock({
|
|
805
|
+
evidence: input.seed.probes
|
|
806
|
+
.nonAllowlisted,
|
|
807
|
+
stage: input.stage,
|
|
808
|
+
type: 'nonallowlisted',
|
|
809
|
+
waf: input.waf,
|
|
810
|
+
webAclArn: input.seed.webAclArn,
|
|
811
|
+
}),
|
|
812
|
+
]);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
catch (error) {
|
|
816
|
+
if (attempt >= input.retries)
|
|
817
|
+
throw error;
|
|
818
|
+
await input.sleep(SEED_ATTESTATION_INTERVAL_MS);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
async function discoverRouterDistribution(input) {
|
|
823
|
+
const items = [];
|
|
824
|
+
let marker;
|
|
825
|
+
const seen = new Set();
|
|
826
|
+
do {
|
|
827
|
+
const page = await input.cloudFront.send(new client_cloudfront_1.ListDistributionsCommand({ Marker: marker }));
|
|
828
|
+
items.push(...(page.DistributionList?.Items ?? []));
|
|
829
|
+
marker = page.DistributionList?.NextMarker;
|
|
830
|
+
if (marker && seen.has(marker)) {
|
|
831
|
+
throw new Error('Router inventory pagination did not advance.');
|
|
832
|
+
}
|
|
833
|
+
if (marker)
|
|
834
|
+
seen.add(marker);
|
|
835
|
+
if (seen.size > 100) {
|
|
836
|
+
throw new Error('Router inventory exceeds bounded inspection.');
|
|
837
|
+
}
|
|
838
|
+
} while (marker);
|
|
839
|
+
const routers = items.filter((router) => router.DomainName === input.probeHost ||
|
|
840
|
+
router.Aliases?.Items?.includes(input.probeHost));
|
|
841
|
+
const router = routers[0];
|
|
842
|
+
if (routers.length !== 1 ||
|
|
843
|
+
!router?.Enabled ||
|
|
844
|
+
typeof router.Id !== 'string' ||
|
|
845
|
+
router.ARN !==
|
|
846
|
+
`arn:${input.partition}:cloudfront::${input.accountId}:distribution/${router.Id}` ||
|
|
847
|
+
typeof router.WebACLId !== 'string' ||
|
|
848
|
+
!WAF_ARN.test(router.WebACLId) ||
|
|
849
|
+
WAF_ARN.exec(router.WebACLId)?.[2] !== input.accountId) {
|
|
850
|
+
throw new Error('Managed rehearsal probe host does not resolve to one exact enabled Router distribution with an account-owned Web ACL.');
|
|
851
|
+
}
|
|
852
|
+
return { distributionId: router.Id, webAclArn: router.WebACLId };
|
|
853
|
+
}
|
|
775
854
|
function createRouterWafRehearsalExecutor(dependencies = {}) {
|
|
776
855
|
return async (options, context) => {
|
|
777
856
|
try {
|
|
@@ -812,160 +891,267 @@ function createRouterWafRehearsalExecutor(dependencies = {}) {
|
|
|
812
891
|
stage,
|
|
813
892
|
});
|
|
814
893
|
const evidencePath = (0, node_path_1.join)(root, EVIDENCE_PATH);
|
|
815
|
-
const retainedEvidence = readBoundedJson(evidencePath, 'Router WAF live evidence');
|
|
816
|
-
const seed = (0, index_js_1.validateRouterWafLiveEvidenceSeed)({
|
|
817
|
-
evidence: retainedEvidence,
|
|
818
|
-
policy,
|
|
819
|
-
});
|
|
820
|
-
if (seed.stage !== stage ||
|
|
821
|
-
seed.accountId !== accountId ||
|
|
822
|
-
seed.region !== region ||
|
|
823
|
-
seed.deploymentRoleArn !== expectedRoleArn ||
|
|
824
|
-
seed.workspace !== workspace ||
|
|
825
|
-
seed.wafContractDigest !== wafContractDigest) {
|
|
826
|
-
throw new Error('Router WAF live evidence stage, account, Region, deployment role, workspace, or WAF contract differs from committed authority.');
|
|
827
|
-
}
|
|
828
|
-
const invocationStartedAt = (dependencies.now ?? (() => new Date()))();
|
|
829
|
-
if (!Number.isFinite(invocationStartedAt.getTime()) ||
|
|
830
|
-
seed.startedAt > invocationStartedAt.getTime() ||
|
|
831
|
-
seed.completedAt > invocationStartedAt.getTime() ||
|
|
832
|
-
invocationStartedAt.getTime() - seed.completedAt >
|
|
833
|
-
MAX_ROUTER_WAF_SEED_AGE_MS) {
|
|
834
|
-
throw new Error('Router WAF live evidence seed is stale or future-dated; no live request was made.');
|
|
835
|
-
}
|
|
836
|
-
const receiptPath = (0, node_path_1.join)(root, RECEIPT_PATH);
|
|
837
|
-
const retainedReceipt = (0, index_js_1.normalizeRouterWafBlockingRehearsal)(readBoundedJson(receiptPath, 'Router WAF blocking rehearsal receipt'));
|
|
838
|
-
if (retainedReceipt) {
|
|
839
|
-
throw new Error('Router WAF blocking rehearsal receipt must be reset to its exact pending seed before another live probe.');
|
|
840
|
-
}
|
|
841
894
|
const url = normalizedProbeUrl(options.probeUrl);
|
|
842
895
|
const profile = selectedProfile(options, environment);
|
|
843
896
|
const aws = clients(profile, region, dependencies);
|
|
897
|
+
const seedProbes = options.seedProbes === true;
|
|
898
|
+
const sleep = dependencies.sleep ??
|
|
899
|
+
((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
900
|
+
const seal = async () => {
|
|
901
|
+
const retainedEvidence = readBoundedJson(evidencePath, 'Router WAF live evidence');
|
|
902
|
+
const seed = (0, index_js_1.validateRouterWafLiveEvidenceSeed)({
|
|
903
|
+
evidence: retainedEvidence,
|
|
904
|
+
policy,
|
|
905
|
+
});
|
|
906
|
+
if (seed.stage !== stage ||
|
|
907
|
+
seed.accountId !== accountId ||
|
|
908
|
+
seed.region !== region ||
|
|
909
|
+
seed.deploymentRoleArn !== expectedRoleArn ||
|
|
910
|
+
seed.workspace !== workspace ||
|
|
911
|
+
seed.wafContractDigest !== wafContractDigest) {
|
|
912
|
+
throw new Error('Router WAF live evidence stage, account, Region, deployment role, workspace, or WAF contract differs from committed authority.');
|
|
913
|
+
}
|
|
914
|
+
const invocationStartedAt = (dependencies.now ?? (() => new Date()))();
|
|
915
|
+
if (!Number.isFinite(invocationStartedAt.getTime()) ||
|
|
916
|
+
seed.startedAt > invocationStartedAt.getTime() ||
|
|
917
|
+
seed.completedAt > invocationStartedAt.getTime() ||
|
|
918
|
+
invocationStartedAt.getTime() - seed.completedAt >
|
|
919
|
+
MAX_ROUTER_WAF_SEED_AGE_MS) {
|
|
920
|
+
throw new Error('Router WAF live evidence seed is stale or future-dated; no live request was made.');
|
|
921
|
+
}
|
|
922
|
+
const receiptPath = (0, node_path_1.join)(root, RECEIPT_PATH);
|
|
923
|
+
const retainedReceipt = (0, index_js_1.normalizeRouterWafBlockingRehearsal)(readBoundedJson(receiptPath, 'Router WAF blocking rehearsal receipt'));
|
|
924
|
+
if (retainedReceipt) {
|
|
925
|
+
throw new Error('Router WAF blocking rehearsal receipt must be reset to its exact pending seed before another live probe.');
|
|
926
|
+
}
|
|
927
|
+
const identity = await aws.sts.send(new client_sts_1.GetCallerIdentityCommand({}));
|
|
928
|
+
if (identity.Account !== accountId ||
|
|
929
|
+
typeof identity.Arn !== 'string' ||
|
|
930
|
+
!(0, identity_js_1.matchesExpectedAssumedRoleArn)(accountId, identity.Arn, expectedRoleArn)) {
|
|
931
|
+
throw new Error('Router WAF rehearsal caller does not match the committed account and exact stage deployment role.');
|
|
932
|
+
}
|
|
933
|
+
await (dependencies.ownership ?? index_js_4.assertWorkspaceStageOwnershipForWorkspace)(context.root, stage, profile ? { profile } : {});
|
|
934
|
+
const webAclVisibilityMetricName = (0, router_waf_rate_rehearsal_js_1.routerWafWebAclMetricName)(stage);
|
|
935
|
+
const [, webAclMetricName] = await Promise.all([
|
|
936
|
+
attestCloudFrontDistribution({
|
|
937
|
+
accountId,
|
|
938
|
+
cloudFront: aws.cloudFront,
|
|
939
|
+
distributionId: seed.distributionId,
|
|
940
|
+
partition,
|
|
941
|
+
probeHost: url.hostname,
|
|
942
|
+
stage,
|
|
943
|
+
webAclArn: seed.webAclArn,
|
|
944
|
+
workspace,
|
|
945
|
+
}),
|
|
946
|
+
attestRouterWafBaseline({
|
|
947
|
+
accountId,
|
|
948
|
+
cloudWatchLogs: aws.cloudWatchLogs,
|
|
949
|
+
plan,
|
|
950
|
+
policyDigest: (0, index_js_1.routerWafPolicyDigest)(policy),
|
|
951
|
+
stage,
|
|
952
|
+
waf: aws.waf,
|
|
953
|
+
webAclArn: seed.webAclArn,
|
|
954
|
+
webAclMetricName: webAclVisibilityMetricName,
|
|
955
|
+
workspace,
|
|
956
|
+
}),
|
|
957
|
+
]);
|
|
958
|
+
await attestOperatorBlocks({
|
|
959
|
+
retries: seedProbes ? SEED_ATTESTATION_RETRIES : 0,
|
|
960
|
+
seed,
|
|
961
|
+
sleep,
|
|
962
|
+
stage,
|
|
963
|
+
waf: aws.waf,
|
|
964
|
+
});
|
|
965
|
+
const fetchRequest = dependencies.fetch ?? fetch;
|
|
966
|
+
const baselineResponse = await fetchRequest(url, {
|
|
967
|
+
cache: 'no-store',
|
|
968
|
+
headers: { 'cache-control': 'no-cache' },
|
|
969
|
+
redirect: 'manual',
|
|
970
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
971
|
+
});
|
|
972
|
+
const allowedRequestStatus = baselineResponse.status;
|
|
973
|
+
await baselineResponse.body?.cancel();
|
|
974
|
+
if (allowedRequestStatus < 200 || allowedRequestStatus > 399) {
|
|
975
|
+
throw new Error(`Router WAF rehearsal health preflight returned unexpected HTTP ${allowedRequestStatus}; rate traffic was not sent.`);
|
|
976
|
+
}
|
|
977
|
+
const nonce = (dependencies.createNonce ?? (() => (0, node_crypto_1.randomBytes)(32).toString('hex')))();
|
|
978
|
+
if (!RATE_MARKER_NONCE_PATTERN.test(nonce)) {
|
|
979
|
+
throw new Error('Router WAF rehearsal invocation nonce is not one opaque 256-bit lowercase hexadecimal value.');
|
|
980
|
+
}
|
|
981
|
+
const rateLimit = await (dependencies.runRateRehearsal ?? router_waf_rate_rehearsal_js_1.runRouterWafRateRehearsal)({
|
|
982
|
+
dependencies: {
|
|
983
|
+
...(dependencies.now ? { now: dependencies.now } : {}),
|
|
984
|
+
observe: ({ endTime, markerDigest: expectedMarkerDigest, ruleMetricName, sequence, startTime, webAclMetricName: expectedWebAclMetricName, }) => {
|
|
985
|
+
const marker = rateMarker(nonce, sequence);
|
|
986
|
+
if (expectedWebAclMetricName !== webAclMetricName) {
|
|
987
|
+
throw new Error('Router WAF rate observation metric dimension differs from the attested physical Web ACL name.');
|
|
988
|
+
}
|
|
989
|
+
if (markerDigest(marker) !== expectedMarkerDigest) {
|
|
990
|
+
throw new Error('Router WAF rate observation marker differs from the exact blocked request.');
|
|
991
|
+
}
|
|
992
|
+
return observeRateLimit({
|
|
993
|
+
cloudWatch: aws.cloudWatch,
|
|
994
|
+
endTime,
|
|
995
|
+
marker,
|
|
996
|
+
markerDigest: expectedMarkerDigest,
|
|
997
|
+
probePath: url.pathname,
|
|
998
|
+
ruleMetricName,
|
|
999
|
+
startTime,
|
|
1000
|
+
waf: aws.waf,
|
|
1001
|
+
webAclArn: seed.webAclArn,
|
|
1002
|
+
webAclMetricName: expectedWebAclMetricName,
|
|
1003
|
+
});
|
|
1004
|
+
},
|
|
1005
|
+
request: async (sequence) => {
|
|
1006
|
+
const marker = rateMarker(nonce, sequence);
|
|
1007
|
+
const response = await fetchRequest(url, {
|
|
1008
|
+
cache: 'no-store',
|
|
1009
|
+
headers: {
|
|
1010
|
+
'cache-control': 'no-cache',
|
|
1011
|
+
[RATE_MARKER_HEADER]: marker,
|
|
1012
|
+
},
|
|
1013
|
+
redirect: 'manual',
|
|
1014
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
1015
|
+
});
|
|
1016
|
+
await response.body?.cancel();
|
|
1017
|
+
return {
|
|
1018
|
+
markerDigest: markerDigest(marker),
|
|
1019
|
+
status: response.status,
|
|
1020
|
+
};
|
|
1021
|
+
},
|
|
1022
|
+
...(dependencies.sleep ? { sleep: dependencies.sleep } : {}),
|
|
1023
|
+
},
|
|
1024
|
+
rateLimitPerIp: policy.rateLimitPerIp,
|
|
1025
|
+
stage,
|
|
1026
|
+
webAclMetricName,
|
|
1027
|
+
});
|
|
1028
|
+
const completedAt = (dependencies.now ?? (() => new Date()))().toISOString();
|
|
1029
|
+
const evidence = mergeRateEvidence(retainedEvidence, allowedRequestStatus, rateLimit, completedAt);
|
|
1030
|
+
const receipt = (0, index_js_1.createRouterWafBlockingRehearsal)({ evidence, policy });
|
|
1031
|
+
writeBoundedJson(evidencePath, evidence, 'Router WAF live evidence');
|
|
1032
|
+
writeBoundedJson(receiptPath, receipt, 'Router WAF blocking rehearsal receipt');
|
|
1033
|
+
devkit_1.logger.info(JSON.stringify({
|
|
1034
|
+
evidenceDigest: receipt.evidenceDigest,
|
|
1035
|
+
accountId: receipt.accountId,
|
|
1036
|
+
policyDigest: receipt.policyDigest,
|
|
1037
|
+
rateRequests: rateLimit.requestsSent,
|
|
1038
|
+
region: receipt.region,
|
|
1039
|
+
sealed: true,
|
|
1040
|
+
stage: receipt.stage,
|
|
1041
|
+
}));
|
|
1042
|
+
};
|
|
1043
|
+
if (!seedProbes) {
|
|
1044
|
+
await seal();
|
|
1045
|
+
return { success: true };
|
|
1046
|
+
}
|
|
1047
|
+
// Managed rehearsal: the protected dispatch performs the operator probes
|
|
1048
|
+
// itself. The non-allowlisted probe runs before the runner is admitted;
|
|
1049
|
+
// the managed-rule probe and the rest of the rehearsal run inside the
|
|
1050
|
+
// temporary allowlist admission that private smoke already governs.
|
|
1051
|
+
if (environment.GITHUB_ACTIONS !== 'true' || stage !== 'staging') {
|
|
1052
|
+
throw new Error('Managed Router WAF rehearsal seeding runs only from the protected GitHub staging dispatch.');
|
|
1053
|
+
}
|
|
1054
|
+
const pendingEvidence = readBoundedJson(evidencePath, 'Router WAF live evidence');
|
|
1055
|
+
if (pendingEvidence?.status !== 'pending') {
|
|
1056
|
+
throw new Error('Managed Router WAF rehearsal seeding requires the exact pending live evidence seed; reset both seeds before another rehearsal.');
|
|
1057
|
+
}
|
|
1058
|
+
const sourceCommit = environment.EBK_SOURCE_COMMIT?.trim() ||
|
|
1059
|
+
environment.GITHUB_SHA?.trim() ||
|
|
1060
|
+
'';
|
|
1061
|
+
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(sourceCommit)) {
|
|
1062
|
+
throw new Error('Managed Router WAF rehearsal seeding requires the exact protected source commit.');
|
|
1063
|
+
}
|
|
844
1064
|
const identity = await aws.sts.send(new client_sts_1.GetCallerIdentityCommand({}));
|
|
845
1065
|
if (identity.Account !== accountId ||
|
|
846
1066
|
typeof identity.Arn !== 'string' ||
|
|
847
1067
|
!(0, identity_js_1.matchesExpectedAssumedRoleArn)(accountId, identity.Arn, expectedRoleArn)) {
|
|
848
1068
|
throw new Error('Router WAF rehearsal caller does not match the committed account and exact stage deployment role.');
|
|
849
1069
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
cloudFront: aws.cloudFront,
|
|
856
|
-
distributionId: seed.distributionId,
|
|
857
|
-
partition,
|
|
858
|
-
probeHost: url.hostname,
|
|
859
|
-
stage,
|
|
860
|
-
webAclArn: seed.webAclArn,
|
|
861
|
-
workspace,
|
|
862
|
-
}),
|
|
863
|
-
attestRouterWafBaseline({
|
|
864
|
-
accountId,
|
|
865
|
-
cloudWatchLogs: aws.cloudWatchLogs,
|
|
866
|
-
plan,
|
|
867
|
-
policyDigest: (0, index_js_1.routerWafPolicyDigest)(policy),
|
|
868
|
-
stage,
|
|
869
|
-
waf: aws.waf,
|
|
870
|
-
webAclArn: seed.webAclArn,
|
|
871
|
-
webAclMetricName: webAclVisibilityMetricName,
|
|
872
|
-
workspace,
|
|
873
|
-
}),
|
|
874
|
-
]);
|
|
875
|
-
await Promise.all([
|
|
876
|
-
attestOperatorBlock({
|
|
877
|
-
evidence: seed.probes.managedRule,
|
|
878
|
-
stage,
|
|
879
|
-
type: 'managed',
|
|
880
|
-
waf: aws.waf,
|
|
881
|
-
webAclArn: seed.webAclArn,
|
|
882
|
-
}),
|
|
883
|
-
attestOperatorBlock({
|
|
884
|
-
evidence: seed.probes
|
|
885
|
-
.nonAllowlisted,
|
|
886
|
-
stage,
|
|
887
|
-
type: 'nonallowlisted',
|
|
888
|
-
waf: aws.waf,
|
|
889
|
-
webAclArn: seed.webAclArn,
|
|
890
|
-
}),
|
|
891
|
-
]);
|
|
892
|
-
const fetchRequest = dependencies.fetch ?? fetch;
|
|
893
|
-
const baselineResponse = await fetchRequest(url, {
|
|
894
|
-
cache: 'no-store',
|
|
895
|
-
headers: { 'cache-control': 'no-cache' },
|
|
896
|
-
redirect: 'manual',
|
|
897
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
1070
|
+
const discovered = await discoverRouterDistribution({
|
|
1071
|
+
accountId,
|
|
1072
|
+
cloudFront: aws.cloudFront,
|
|
1073
|
+
partition,
|
|
1074
|
+
probeHost: url.hostname,
|
|
898
1075
|
});
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
if (allowedRequestStatus < 200 || allowedRequestStatus > 399) {
|
|
902
|
-
throw new Error(`Router WAF rehearsal health preflight returned unexpected HTTP ${allowedRequestStatus}; rate traffic was not sent.`);
|
|
903
|
-
}
|
|
1076
|
+
const now = dependencies.now ?? (() => new Date());
|
|
1077
|
+
const get = dependencies.rawGet ?? rawGet;
|
|
904
1078
|
const nonce = (dependencies.createNonce ?? (() => (0, node_crypto_1.randomBytes)(32).toString('hex')))();
|
|
905
1079
|
if (!RATE_MARKER_NONCE_PATTERN.test(nonce)) {
|
|
906
1080
|
throw new Error('Router WAF rehearsal invocation nonce is not one opaque 256-bit lowercase hexadecimal value.');
|
|
907
1081
|
}
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1082
|
+
const startedAt = now().toISOString();
|
|
1083
|
+
const operatorProbe = async (type, ruleName) => {
|
|
1084
|
+
const marker = `${nonce}:${type}`;
|
|
1085
|
+
const response = await get(url, {
|
|
1086
|
+
'cache-control': 'no-cache',
|
|
1087
|
+
[OPERATOR_MARKER_HEADER]: marker,
|
|
1088
|
+
});
|
|
1089
|
+
const blockedAt = now().toISOString();
|
|
1090
|
+
if (response.status !== 403) {
|
|
1091
|
+
throw new Error(`Managed Router WAF ${type} probe returned HTTP ${response.status} instead of the expected 403 BLOCK.`);
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
blockedAt,
|
|
1095
|
+
marker,
|
|
1096
|
+
method: 'GET',
|
|
1097
|
+
path: url.pathname,
|
|
1098
|
+
ruleName,
|
|
1099
|
+
status: 403,
|
|
1100
|
+
};
|
|
1101
|
+
};
|
|
1102
|
+
const nonAllowlisted = await operatorProbe('nonallowlisted', 'StageIpAllowlist');
|
|
1103
|
+
await (dependencies.privateSmoke ?? private_smoke_access_js_1.runGithubPrivateSmoke)({
|
|
1104
|
+
account: accountId,
|
|
1105
|
+
deploymentProfile: operatingPolicy.deploymentProfile,
|
|
1106
|
+
root: context.root,
|
|
1107
|
+
run: async () => {
|
|
1108
|
+
const managedRule = await operatorProbe('managed', 'AWSManagedRulesCommonRuleSet#NoUserAgent_HEADER');
|
|
1109
|
+
const seedEvidence = {
|
|
1110
|
+
accountId,
|
|
1111
|
+
completedAt: now().toISOString(),
|
|
1112
|
+
deploymentRoleArn: expectedRoleArn,
|
|
1113
|
+
distributionId: discovered.distributionId,
|
|
1114
|
+
kind: 'ebk-router-waf-live-evidence',
|
|
1115
|
+
mode: 'block',
|
|
1116
|
+
policyDigest: (0, index_js_1.routerWafPolicyDigest)(policy),
|
|
1117
|
+
probes: {
|
|
1118
|
+
allowedRequestStatus: 200,
|
|
1119
|
+
cloudWatchMetricsObserved: true,
|
|
1120
|
+
countModeTuningObserved: false,
|
|
1121
|
+
managedRule,
|
|
1122
|
+
nonAllowlisted,
|
|
1123
|
+
rateLimit: null,
|
|
1124
|
+
sampledRequestsObserved: true,
|
|
1125
|
+
},
|
|
1126
|
+
region,
|
|
1127
|
+
requiredRules: [
|
|
1128
|
+
'StageIpAllowlist',
|
|
1129
|
+
'RateLimitPerIP',
|
|
1130
|
+
...index_js_1.ROUTER_WAF_REQUIRED_MANAGED_RULES,
|
|
1131
|
+
],
|
|
1132
|
+
result: 'passed',
|
|
1133
|
+
schemaVersion: index_js_1.ROUTER_WAF_LIVE_EVIDENCE_SCHEMA_VERSION,
|
|
1134
|
+
sourceCommit,
|
|
1135
|
+
stage,
|
|
1136
|
+
startedAt,
|
|
1137
|
+
wafContractDigest,
|
|
1138
|
+
webAclArn: discovered.webAclArn,
|
|
1139
|
+
webAclScope: 'CLOUDFRONT',
|
|
1140
|
+
workspace,
|
|
1141
|
+
};
|
|
1142
|
+
writeBoundedJson(evidencePath, seedEvidence, 'Router WAF live evidence');
|
|
1143
|
+
await seal();
|
|
950
1144
|
},
|
|
951
|
-
|
|
1145
|
+
source: sourceCommit,
|
|
952
1146
|
stage,
|
|
953
|
-
|
|
1147
|
+
url: url.href,
|
|
1148
|
+
workspace,
|
|
1149
|
+
}, {
|
|
1150
|
+
...(dependencies.environment
|
|
1151
|
+
? { environment: dependencies.environment }
|
|
1152
|
+
: {}),
|
|
1153
|
+
...(dependencies.fetch ? { fetch: dependencies.fetch } : {}),
|
|
954
1154
|
});
|
|
955
|
-
const completedAt = (dependencies.now ?? (() => new Date()))().toISOString();
|
|
956
|
-
const evidence = mergeRateEvidence(retainedEvidence, allowedRequestStatus, rateLimit, completedAt);
|
|
957
|
-
const receipt = (0, index_js_1.createRouterWafBlockingRehearsal)({ evidence, policy });
|
|
958
|
-
writeBoundedJson(evidencePath, evidence, 'Router WAF live evidence');
|
|
959
|
-
writeBoundedJson(receiptPath, receipt, 'Router WAF blocking rehearsal receipt');
|
|
960
|
-
devkit_1.logger.info(JSON.stringify({
|
|
961
|
-
evidenceDigest: receipt.evidenceDigest,
|
|
962
|
-
accountId: receipt.accountId,
|
|
963
|
-
policyDigest: receipt.policyDigest,
|
|
964
|
-
rateRequests: rateLimit.requestsSent,
|
|
965
|
-
region: receipt.region,
|
|
966
|
-
sealed: true,
|
|
967
|
-
stage: receipt.stage,
|
|
968
|
-
}));
|
|
969
1155
|
return { success: true };
|
|
970
1156
|
}
|
|
971
1157
|
catch (error) {
|