@kensio/yulin 1.22.0 → 1.23.1

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.
Files changed (52) hide show
  1. package/dist/serve/controller/sim-service-controller.d.ts +6 -0
  2. package/dist/serve/controller/sim-service-controller.js +7 -0
  3. package/dist/serve/http/request/sim-aws-request-authenticator.js +3 -0
  4. package/dist/service/apigateway/serve/sim-api-gateway-controller.js +1 -0
  5. package/dist/service/apigateway/serve/sim-rest-api-web-acl-inspection.d.ts +2 -0
  6. package/dist/service/apigateway/serve/sim-rest-api-web-acl-inspection.js +1 -0
  7. package/dist/service/cloudfront/controller/dependency/sim-cf-controller-dependency.js +6 -3
  8. package/dist/service/cloudfront/controller/sim-cloudfront-request-pipeline.js +2 -1
  9. package/dist/service/cloudfront/controller/web-acl/sim-cf-web-acl-guard.d.ts +25 -3
  10. package/dist/service/cloudfront/controller/web-acl/sim-cf-web-acl-guard.js +35 -6
  11. package/dist/service/cognito/serve/sim-cognito-controller.js +4 -3
  12. package/dist/service/cognito/serve/sim-cognito-domain-controller.js +2 -0
  13. package/dist/service/cognito/serve/sim-cognito-web-acl-inspection.d.ts +2 -0
  14. package/dist/service/cognito/serve/sim-cognito-web-acl-inspection.js +1 -0
  15. package/dist/service/iam/index.d.ts +1 -0
  16. package/dist/service/iam/index.js +1 -0
  17. package/dist/service/iam/request/error/sim-aws-request-auth.error.d.ts +1 -1
  18. package/dist/service/iam/request/error/sim-aws-request-auth.error.js +1 -1
  19. package/dist/service/iam/request/sim-aws-control-headers.d.ts +2 -1
  20. package/dist/service/iam/request/sim-aws-control-headers.js +4 -1
  21. package/dist/service/iam/request/sim-aws-source-ip-header.d.ts +19 -0
  22. package/dist/service/iam/request/sim-aws-source-ip-header.js +32 -0
  23. package/dist/service/wafv2/association/sim-waf-associations.js +1 -1
  24. package/dist/service/wafv2/association/sim-waf-protection.d.ts +5 -0
  25. package/dist/service/wafv2/association/sim-waf-request-inspection.d.ts +2 -0
  26. package/dist/service/wafv2/association/sim-waf-request-inspection.js +7 -2
  27. package/dist/service/wafv2/evaluate/sim-waf-blocked-response.d.ts +12 -0
  28. package/dist/service/wafv2/evaluate/sim-waf-blocked-response.js +1 -0
  29. package/dist/service/wafv2/evaluate/sim-waf-evaluation-request.d.ts +5 -0
  30. package/dist/service/wafv2/evaluate/sim-waf-inspected-request.d.ts +12 -1
  31. package/dist/service/wafv2/evaluate/sim-waf-inspected-request.js +12 -1
  32. package/dist/service/wafv2/index.d.ts +2 -1
  33. package/dist/service/wafv2/index.js +1 -0
  34. package/dist/service/wafv2/managed/group/sim-waf-ip-reputation.d.ts +8 -5
  35. package/dist/service/wafv2/managed/group/sim-waf-ip-reputation.js +10 -5
  36. package/dist/service/wafv2/managed/sim-waf-managed-address-declaration.d.ts +32 -0
  37. package/dist/service/wafv2/managed/sim-waf-managed-address-declaration.js +52 -0
  38. package/dist/service/wafv2/managed/sim-waf-managed-address-range.d.ts +21 -0
  39. package/dist/service/wafv2/managed/sim-waf-managed-address-range.js +73 -0
  40. package/dist/service/wafv2/managed/sim-waf-managed-rule-groups.d.ts +7 -0
  41. package/dist/service/wafv2/managed/sim-waf-managed-rule-groups.js +15 -0
  42. package/dist/service/wafv2/managed/sim-waf-managed-rule.type.d.ts +5 -0
  43. package/dist/service/wafv2/managed/sim-waf-managed-rules.d.ts +27 -1
  44. package/dist/service/wafv2/managed/sim-waf-managed-rules.js +33 -13
  45. package/dist/service/wafv2/sim-wafv2.js +2 -1
  46. package/dist/service/wafv2/statement/sim-waf-rate-based-input.d.ts +5 -4
  47. package/dist/service/wafv2/statement/sim-waf-rate-based-input.js +6 -6
  48. package/dist/service/wafv2/web-acl/sim-waf-action-input.js +1 -0
  49. package/docs/services/cloudfront/README.md +17 -1
  50. package/docs/services/iam/README.md +6 -0
  51. package/docs/services/wafv2/README.md +112 -15
  52. package/package.json +3 -3
@@ -25,6 +25,7 @@ interface SimAwsServiceRequestProperties {
25
25
  readonly request: Request;
26
26
  readonly caller?: SimAwsRequestCaller | undefined;
27
27
  readonly source?: SimAwsRequestSource | undefined;
28
+ readonly sourceIp?: string | undefined;
28
29
  readonly body?: Uint8Array | undefined;
29
30
  }
30
31
  /**
@@ -49,6 +50,11 @@ export declare class SimAwsServiceRequest {
49
50
  * conditioned on, and most requests state none.
50
51
  */
51
52
  readonly source: SimAwsRequestSource | undefined;
53
+ /**
54
+ * The client address the request came from, which is loopback unless the
55
+ * request stated another with `x-sim-aws-source-ip`.
56
+ */
57
+ readonly sourceIp: string;
52
58
  readonly body?: Uint8Array | undefined;
53
59
  constructor(properties: SimAwsServiceRequestProperties);
54
60
  }
@@ -1,3 +1,4 @@
1
+ import { simAwsProxiedSourceIp } from "../http/sim-aws-proxied-connection.js";
1
2
  import { SimAwsRequestCaller } from "../../service/iam/request/sim-aws-request-caller.js";
2
3
  /**
3
4
  * One HTTP request, routed to a simulated service and attributed to a
@@ -21,6 +22,11 @@ export class SimAwsServiceRequest {
21
22
  * conditioned on, and most requests state none.
22
23
  */
23
24
  source;
25
+ /**
26
+ * The client address the request came from, which is loopback unless the
27
+ * request stated another with `x-sim-aws-source-ip`.
28
+ */
29
+ sourceIp;
24
30
  body;
25
31
  constructor(properties) {
26
32
  this.target = properties.target;
@@ -28,6 +34,7 @@ export class SimAwsServiceRequest {
28
34
  // An unattributed request is anonymous, never the Account root.
29
35
  this.caller = properties.caller ?? SimAwsRequestCaller.anonymous();
30
36
  this.source = properties.source;
37
+ this.sourceIp = properties.sourceIp ?? simAwsProxiedSourceIp;
31
38
  this.body = properties.body;
32
39
  }
33
40
  }
@@ -1,6 +1,7 @@
1
1
  import { simAwsServiceSigningName } from "../../controller/sim-aws-service-signing-name.js";
2
2
  import { SimAwsServiceRequest, } from "../../controller/sim-service-controller.js";
3
3
  import { SimAwsRequestSource } from "../../../service/iam/request/sim-aws-request-source.js";
4
+ import { simAwsRequestSourceIp } from "../../../service/iam/request/sim-aws-source-ip-header.js";
4
5
  import { SimAwsReceivedRequest } from "./sim-aws-received-request.js";
5
6
  /**
6
7
  * Turns a received HTTP request into a request a simulated service can serve.
@@ -40,11 +41,13 @@ export class SimAwsRequestAuthenticator {
40
41
  },
41
42
  });
42
43
  const source = SimAwsRequestSource.fromHeaders(request.headers);
44
+ const sourceIp = simAwsRequestSourceIp(request.headers);
43
45
  return new SimAwsServiceRequest({
44
46
  target,
45
47
  request: received.forService(),
46
48
  caller,
47
49
  ...(source !== undefined && { source }),
50
+ sourceIp,
48
51
  body: received.body,
49
52
  });
50
53
  }
@@ -77,6 +77,7 @@ export class SimApiGatewayServiceController {
77
77
  restApi,
78
78
  stageName: restApiRequest.segments[0] ?? "",
79
79
  request: serviceRequest.request,
80
+ sourceIp: serviceRequest.sourceIp,
80
81
  });
81
82
  if (inspected.blocked !== undefined) {
82
83
  return inspected.blocked;
@@ -5,6 +5,8 @@ interface SimRestApiInspectionInput {
5
5
  /** The first path segment of the request, which names the stage. */
6
6
  readonly stageName: string;
7
7
  readonly request: Request;
8
+ /** The client address the request came from. */
9
+ readonly sourceIp: string;
8
10
  }
9
11
  /**
10
12
  * Puts a request to the web ACL in front of the stage it reached.
@@ -20,6 +20,7 @@ export class SimRestApiWebAclInspection {
20
20
  protection: restApi.webAcls,
21
21
  resourceArn: restApi.stageArn(input.stageName),
22
22
  request,
23
+ sourceIp: input.sourceIp,
23
24
  forwardBody: true,
24
25
  });
25
26
  }
@@ -29,6 +29,10 @@ export class SimCloudFrontControllerDependenciesFactory {
29
29
  new DefaultSimCloudFrontBehaviorResolver();
30
30
  const originFetcher = properties.originFetcher ?? new SimCloudFrontOriginFetcher();
31
31
  const edgeFunctions = new SimAwsCfEdgeFunctions(simAws);
32
+ // A request the web ACL blocks with WAF's default 403 gets the same custom
33
+ // error response an Origin's 403 would, so the one responder serves both.
34
+ const customErrorResponder = properties.customErrorResponder ??
35
+ new SimCfCustomErrorResponder({ behaviourResolver, originFetcher });
32
36
  return {
33
37
  distroRouter: properties.distroRouter ??
34
38
  new DefaultSimCloudFrontDistroRouter({
@@ -36,14 +40,13 @@ export class SimCloudFrontControllerDependenciesFactory {
36
40
  cloudFrontRegistry,
37
41
  }),
38
42
  webAclGuard: properties.webAclGuard ??
39
- new SimCfWebAclGuard(makeSimCfWebAclResolver(simAws)),
43
+ new SimCfWebAclGuard(makeSimCfWebAclResolver(simAws), customErrorResponder),
40
44
  behaviourResolver,
41
45
  cffApplicator: properties.cffApplicator ?? new SimCffApplicator(),
42
46
  edgeApplicator: properties.edgeApplicator ??
43
47
  new SimLambdaEdgeApplicator({ edgeFunctions }),
44
48
  contentStage: new SimCfContentStage(new SimCfOriginStage(originFetcher, properties.edgeOriginApplicator ??
45
- new SimLambdaEdgeOriginApplicator({ edgeFunctions })), properties.customErrorResponder ??
46
- new SimCfCustomErrorResponder({ behaviourResolver, originFetcher })),
49
+ new SimLambdaEdgeOriginApplicator({ edgeFunctions })), customErrorResponder),
47
50
  responseHeadersApplicator: properties.responseHeadersApplicator ??
48
51
  new SimCfResponseHeadersApplicator(),
49
52
  };
@@ -44,7 +44,8 @@ export class SimCloudFrontRequestPipeline {
44
44
  // Put the request through the Distribution's web ACL, if it has one.
45
45
  // CloudFront asks WAF before any content handling, so a blocked request is
46
46
  // answered at the edge without a Behavior, a CloudFront Function or the
47
- // Origin ever seeing it.
47
+ // Origin ever seeing it. Its 403 still goes through the Distribution's
48
+ // custom error responses, unless the block action named its own response.
48
49
  const webAclResult = await this.stages.webAclGuard.apply(requestReference, distro);
49
50
  if (webAclResult instanceof Response) {
50
51
  return webAclResult;
@@ -1,17 +1,26 @@
1
1
  import type { SimCloudFrontDistribution } from "../../distribution/sim-cloudfront-distribution.js";
2
2
  import type { SimCfWebAclResolver } from "../../web-acl/sim-cf-web-acl.js";
3
+ import type { SimCfCustomErrorResponder } from "../error/sim-cf-custom-error-responder.js";
3
4
  /**
4
5
  * Puts a request through the web ACL a Distribution names, before anything
5
6
  * else has a look at it.
6
7
  *
7
8
  * CloudFront asks WAF ahead of every content-handling stage, so a blocked
8
9
  * request never reaches a viewer-request CloudFront Function, a cache
9
- * Behavior, a response headers policy or the Origin. It is answered at the
10
- * edge with the 403 the web ACL's block action describes.
10
+ * Behavior, a response headers policy or the Origin.
11
+ *
12
+ * A blocked request is answered with the custom response its block action
13
+ * names, where it names one. Otherwise WAF hands CloudFront its default 403,
14
+ * and CloudFront treats that like a 403 from the Origin: a custom error
15
+ * response the Distribution configures for 403 replaces it. CloudFront cannot
16
+ * tell the two 403s apart, so one rule covers both.
17
+ *
18
+ * https://docs.aws.amazon.com/waf/latest/developerguide/cloudfront-waf-use-cases.html
11
19
  */
12
20
  export declare class SimCfWebAclGuard {
13
21
  private readonly resolveWebAcl;
14
- constructor(resolveWebAcl: SimCfWebAclResolver);
22
+ private readonly customErrorResponder;
23
+ constructor(resolveWebAcl: SimCfWebAclResolver, customErrorResponder: SimCfCustomErrorResponder);
15
24
  /**
16
25
  * Answer with the request to carry on with, or the response a blocked
17
26
  * request gets.
@@ -21,4 +30,17 @@ export declare class SimCfWebAclGuard {
21
30
  * request handling is for.
22
31
  */
23
32
  apply(request: Request, distribution: SimCloudFrontDistribution): Promise<Request | Response>;
33
+ /**
34
+ * The response a blocked request gets.
35
+ *
36
+ * A custom response from the block action takes precedence over anything
37
+ * the Distribution configures, whatever its status. WAF's default 403 goes
38
+ * through the Distribution's custom error responses instead.
39
+ *
40
+ * The answer is not stored in the Distribution's cache. WAF decides every
41
+ * request before the cache is consulted, so a blocked response stored under
42
+ * the request's cache key would only ever be served to a request WAF had
43
+ * already allowed.
44
+ */
45
+ private blockedResponse;
24
46
  }
@@ -6,13 +6,22 @@ import { SimCloudFrontInvalidWebAclId } from "../../error/sim-cloudfront.error.j
6
6
  *
7
7
  * CloudFront asks WAF ahead of every content-handling stage, so a blocked
8
8
  * request never reaches a viewer-request CloudFront Function, a cache
9
- * Behavior, a response headers policy or the Origin. It is answered at the
10
- * edge with the 403 the web ACL's block action describes.
9
+ * Behavior, a response headers policy or the Origin.
10
+ *
11
+ * A blocked request is answered with the custom response its block action
12
+ * names, where it names one. Otherwise WAF hands CloudFront its default 403,
13
+ * and CloudFront treats that like a 403 from the Origin: a custom error
14
+ * response the Distribution configures for 403 replaces it. CloudFront cannot
15
+ * tell the two 403s apart, so one rule covers both.
16
+ *
17
+ * https://docs.aws.amazon.com/waf/latest/developerguide/cloudfront-waf-use-cases.html
11
18
  */
12
19
  export class SimCfWebAclGuard {
13
20
  resolveWebAcl;
14
- constructor(resolveWebAcl) {
21
+ customErrorResponder;
22
+ constructor(resolveWebAcl, customErrorResponder) {
15
23
  this.resolveWebAcl = resolveWebAcl;
24
+ this.customErrorResponder = customErrorResponder;
16
25
  }
17
26
  /**
18
27
  * Answer with the request to carry on with, or the response a blocked
@@ -41,9 +50,29 @@ export class SimCfWebAclGuard {
41
50
  body: await requestBody(request),
42
51
  resourceType: "CLOUDFRONT",
43
52
  });
44
- return decision.action === "BLOCK"
45
- ? simWafBlockedHttpResponse(decision.blocked ?? simWafDefaultBlockedResponse())
46
- : allowedRequest(request, decision);
53
+ if (decision.action === "BLOCK") {
54
+ return await this.blockedResponse(request, distribution, decision.blocked ?? simWafDefaultBlockedResponse());
55
+ }
56
+ return allowedRequest(request, decision);
57
+ }
58
+ /**
59
+ * The response a blocked request gets.
60
+ *
61
+ * A custom response from the block action takes precedence over anything
62
+ * the Distribution configures, whatever its status. WAF's default 403 goes
63
+ * through the Distribution's custom error responses instead.
64
+ *
65
+ * The answer is not stored in the Distribution's cache. WAF decides every
66
+ * request before the cache is consulted, so a blocked response stored under
67
+ * the request's cache key would only ever be served to a request WAF had
68
+ * already allowed.
69
+ */
70
+ async blockedResponse(request, distribution, blocked) {
71
+ const response = simWafBlockedHttpResponse(blocked);
72
+ if (blocked.isCustom) {
73
+ return response;
74
+ }
75
+ return await this.customErrorResponder.apply(request, distribution, response);
47
76
  }
48
77
  }
49
78
  /**
@@ -69,7 +69,7 @@ export class SimCognitoServiceController {
69
69
  // The web ACL sits in front of the endpoint rather than behind it, so a
70
70
  // request it blocks is answered before the method is judged. A write the
71
71
  // rules turned away gets WAF's 403 and never learns the endpoint reads.
72
- const blocked = await this.blocked(pool, segment, request);
72
+ const blocked = await this.blocked(pool, segment, serviceRequest);
73
73
  if (blocked !== undefined) {
74
74
  return blocked;
75
75
  }
@@ -90,14 +90,15 @@ export class SimCognitoServiceController {
90
90
  * A blocked request is answered before the document is read, so a verifier
91
91
  * the rules turn away gets 403 and no keys.
92
92
  */
93
- async blocked(pool, segment, request) {
93
+ async blocked(pool, segment, serviceRequest) {
94
94
  if (!simCognitoProtectedSegment(segment)) {
95
95
  return undefined;
96
96
  }
97
97
  const inspected = await this.webAcl.inspect({
98
98
  pool,
99
99
  cognito: simCognitoPoolScope(this.simAws, pool),
100
- request,
100
+ request: serviceRequest.request,
101
+ sourceIp: serviceRequest.sourceIp,
101
102
  });
102
103
  return inspected.blocked;
103
104
  }
@@ -44,6 +44,7 @@ export class SimCognitoDomainController {
44
44
  pool,
45
45
  cognito,
46
46
  request: serviceRequest.request,
47
+ sourceIp: serviceRequest.sourceIp,
47
48
  });
48
49
  if (inspected.blocked !== undefined) {
49
50
  return inspected.blocked;
@@ -95,6 +96,7 @@ function forwarded(serviceRequest, request) {
95
96
  request,
96
97
  caller: serviceRequest.caller,
97
98
  source: serviceRequest.source,
99
+ sourceIp: serviceRequest.sourceIp,
98
100
  body: serviceRequest.body,
99
101
  });
100
102
  }
@@ -6,6 +6,8 @@ interface SimCognitoInspectionInput {
6
6
  /** The simulated Cognito scope the pool belongs to. */
7
7
  readonly cognito: SimCognitoIdentityProvider;
8
8
  readonly request: Request;
9
+ /** The client address the request came from. */
10
+ readonly sourceIp: string;
9
11
  }
10
12
  /**
11
13
  * Puts a request to the web ACL in front of the pool it reached.
@@ -23,6 +23,7 @@ export class SimCognitoWebAclInspection {
23
23
  protection: input.cognito.webAcls(),
24
24
  resourceArn: input.pool.arn.value,
25
25
  request: input.request,
26
+ sourceIp: input.sourceIp,
26
27
  forwardBody: false,
27
28
  });
28
29
  }
@@ -9,4 +9,5 @@ export type { SimIamSigV4ExpectedScope } from "./sigv4/sim-iam-sigv4-expected-sc
9
9
  export { simAwsCallerHeaderName, simAwsCallerHeaderPrincipal, simAwsCallerHeaderValue, } from "./request/sim-aws-caller-header.js";
10
10
  export { type SimAwsRequestAuthMethod, SimAwsRequestCaller, } from "./request/sim-aws-request-caller.js";
11
11
  export { SimAwsRequestSource, simAwsSourceAccountHeaderName, simAwsSourceArnHeaderName, } from "./request/sim-aws-request-source.js";
12
+ export { simAwsRequestSourceIp, simAwsSourceIpHeaderName, } from "./request/sim-aws-source-ip-header.js";
12
13
  export { isSimAwsRequestAuthFailure, SimAwsInvalidCallerHeader, type SimAwsRequestAuthFailure, } from "./request/error/sim-aws-request-auth.error.js";
@@ -4,4 +4,5 @@ export { SimIamCredentialScopeMismatch, SimIamExpiredToken, SimIamIncompleteSign
4
4
  export { simAwsCallerHeaderName, simAwsCallerHeaderPrincipal, simAwsCallerHeaderValue, } from "./request/sim-aws-caller-header.js";
5
5
  export { SimAwsRequestCaller, } from "./request/sim-aws-request-caller.js";
6
6
  export { SimAwsRequestSource, simAwsSourceAccountHeaderName, simAwsSourceArnHeaderName, } from "./request/sim-aws-request-source.js";
7
+ export { simAwsRequestSourceIp, simAwsSourceIpHeaderName, } from "./request/sim-aws-source-ip-header.js";
7
8
  export { isSimAwsRequestAuthFailure, SimAwsInvalidCallerHeader, } from "./request/error/sim-aws-request-auth.error.js";
@@ -1,7 +1,7 @@
1
1
  import { SimIamSigV4Error } from "../../sigv4/error/sim-iam-sigv4.error.js";
2
2
  /**
3
3
  * Raised when the `x-sim-aws-caller` header does not name a principal the
4
- * simulator understands.
4
+ * simulator understands, or `x-sim-aws-source-ip` names no address.
5
5
  *
6
6
  * This is a simulator control header rather than anything real AWS defines, so
7
7
  * a bad value is the caller's mistake in using Yulin, not a failed AWS
@@ -1,7 +1,7 @@
1
1
  import { SimIamSigV4Error } from "../../sigv4/error/sim-iam-sigv4.error.js";
2
2
  /**
3
3
  * Raised when the `x-sim-aws-caller` header does not name a principal the
4
- * simulator understands.
4
+ * simulator understands, or `x-sim-aws-source-ip` names no address.
5
5
  *
6
6
  * This is a simulator control header rather than anything real AWS defines, so
7
7
  * a bad value is the caller's mistake in using Yulin, not a failed AWS
@@ -2,7 +2,8 @@
2
2
  * Header names that are instructions to the simulator rather than part of the
3
3
  * request being simulated.
4
4
  *
5
- * They state who a request is from and what it is for, so anything passing a
5
+ * They state who a request is from, where it came from and what it is for, so
6
+ * anything passing a
6
7
  * request on to a simulated service has to decide what to do with them rather
7
8
  * than forward them by accident: the HTTP boundary strips them once the
8
9
  * request has been attributed, and sim CloudFront strips a viewer's before
@@ -1,10 +1,12 @@
1
1
  import { simAwsCallerHeaderName } from "./sim-aws-caller-header.js";
2
+ import { simAwsSourceIpHeaderName } from "./sim-aws-source-ip-header.js";
2
3
  import { simAwsSourceAccountHeaderName, simAwsSourceArnHeaderName, } from "./sim-aws-request-source.js";
3
4
  /**
4
5
  * Header names that are instructions to the simulator rather than part of the
5
6
  * request being simulated.
6
7
  *
7
- * They state who a request is from and what it is for, so anything passing a
8
+ * They state who a request is from, where it came from and what it is for, so
9
+ * anything passing a
8
10
  * request on to a simulated service has to decide what to do with them rather
9
11
  * than forward them by accident: the HTTP boundary strips them once the
10
12
  * request has been attributed, and sim CloudFront strips a viewer's before
@@ -14,6 +16,7 @@ export const simAwsControlHeaderNames = [
14
16
  simAwsCallerHeaderName,
15
17
  simAwsSourceArnHeaderName,
16
18
  simAwsSourceAccountHeaderName,
19
+ simAwsSourceIpHeaderName,
17
20
  ];
18
21
  /**
19
22
  * Remove the simulator's control headers from a set of request headers.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The header naming the client address a request into simulated AWS comes
3
+ * from.
4
+ *
5
+ * A request arrives in process or over loopback, so there is no remote address
6
+ * for the simulation to read. Whatever decides by where a request came from,
7
+ * such as a web ACL's IP reputation and anonymous IP rules, reads this instead,
8
+ * and a test sends it to say which client a request stands for.
9
+ */
10
+ export declare const simAwsSourceIpHeaderName = "x-sim-aws-source-ip";
11
+ /**
12
+ * Read the client address a request states, or the loopback address when it
13
+ * states none.
14
+ *
15
+ * The header holds one IPv4 or IPv6 address. A value that is neither is
16
+ * refused rather than read as loopback, since a test that misspelt an address
17
+ * would otherwise pass for the wrong reason.
18
+ */
19
+ export declare function simAwsRequestSourceIp(headers: Headers): string;
@@ -0,0 +1,32 @@
1
+ import { isIP } from "node:net";
2
+ import { simAwsProxiedSourceIp } from "../../../serve/http/sim-aws-proxied-connection.js";
3
+ import { SimAwsInvalidCallerHeader } from "./error/sim-aws-request-auth.error.js";
4
+ /**
5
+ * The header naming the client address a request into simulated AWS comes
6
+ * from.
7
+ *
8
+ * A request arrives in process or over loopback, so there is no remote address
9
+ * for the simulation to read. Whatever decides by where a request came from,
10
+ * such as a web ACL's IP reputation and anonymous IP rules, reads this instead,
11
+ * and a test sends it to say which client a request stands for.
12
+ */
13
+ export const simAwsSourceIpHeaderName = "x-sim-aws-source-ip";
14
+ /**
15
+ * Read the client address a request states, or the loopback address when it
16
+ * states none.
17
+ *
18
+ * The header holds one IPv4 or IPv6 address. A value that is neither is
19
+ * refused rather than read as loopback, since a test that misspelt an address
20
+ * would otherwise pass for the wrong reason.
21
+ */
22
+ export function simAwsRequestSourceIp(headers) {
23
+ const value = headers.get(simAwsSourceIpHeaderName)?.trim();
24
+ if (value === undefined || value === "") {
25
+ return simAwsProxiedSourceIp;
26
+ }
27
+ if (isIP(value) === 0) {
28
+ throw new SimAwsInvalidCallerHeader(`${simAwsSourceIpHeaderName} value ${value} must be one IPv4 or IPv6 ` +
29
+ `address`);
30
+ }
31
+ return value;
32
+ }
@@ -75,7 +75,7 @@ export class SimWafAssociations {
75
75
  if (association === undefined) {
76
76
  return undefined;
77
77
  }
78
- return association.webAcl.evaluate(simWafInspectedRequest(request.request, request.body, association.webAcl.bodyInspectionLimitBytes(association.resourceType)));
78
+ return association.webAcl.evaluate(simWafInspectedRequest(request.request, request.body, association.webAcl.bodyInspectionLimitBytes(association.resourceType), request.sourceIp));
79
79
  }
80
80
  /**
81
81
  * Forget whatever is in front of one resource.
@@ -6,6 +6,11 @@ export interface SimWafProtectedRequest {
6
6
  /** The resource the request reached, by ARN. */
7
7
  readonly resourceArn: string;
8
8
  readonly request: Request;
9
+ /**
10
+ * The client address the request came from, which is loopback when nothing
11
+ * says otherwise.
12
+ */
13
+ readonly sourceIp?: string | undefined;
9
14
  /**
10
15
  * The request body, when the rules might inspect it. A request body is a
11
16
  * stream that cannot be read twice, so it is passed in already buffered.
@@ -5,6 +5,8 @@ interface SimWafInspectionInput {
5
5
  /** The resource the request reached, by ARN. */
6
6
  readonly resourceArn: string;
7
7
  readonly request: Request;
8
+ /** The client address the request came from. */
9
+ readonly sourceIp: string;
8
10
  /**
9
11
  * Whether the fronting service forwards the request body to AWS WAF.
10
12
  *
@@ -27,12 +27,17 @@ export class SimWafRequestInspection {
27
27
  * Put one request to whatever protects the resource it addressed.
28
28
  */
29
29
  async inspect(input) {
30
- const { protection, resourceArn, request } = input;
30
+ const { protection, resourceArn, request, sourceIp } = input;
31
31
  if (!protection.protects(resourceArn)) {
32
32
  return new SimWafInspected({ request });
33
33
  }
34
34
  const body = input.forwardBody ? await inspectedBody(request) : undefined;
35
- const decision = protection.decide({ resourceArn, request, body });
35
+ const decision = protection.decide({
36
+ resourceArn,
37
+ request,
38
+ sourceIp,
39
+ body,
40
+ });
36
41
  // A decision carries a blocked response when its action was to block, and
37
42
  // carries none when the request was allowed. That one reading is what
38
43
  // decides the request here.
@@ -7,6 +7,18 @@ export interface SimWafBlockedResponse {
7
7
  readonly contentType: string;
8
8
  readonly body: string;
9
9
  readonly headers: readonly SimWafHeader[];
10
+ /**
11
+ * Whether the block action named a `CustomResponse` of its own.
12
+ *
13
+ * A fronting service treats the two differently. A custom response goes to
14
+ * the client exactly as the rule describes it, and the service's own error
15
+ * handling has no say in it. WAF's default 403 is handed to the service as
16
+ * any other 403 would be, so a CloudFront custom error response for 403
17
+ * replaces it.
18
+ *
19
+ * https://docs.aws.amazon.com/waf/latest/developerguide/customizing-the-response-for-blocked-requests.html
20
+ */
21
+ readonly isCustom: boolean;
10
22
  }
11
23
  /**
12
24
  * The body a block action with no custom response of its own answers with.
@@ -15,6 +15,7 @@ export function simWafDefaultBlockedResponse() {
15
15
  body: "<html><head><title>403 Forbidden</title></head>" +
16
16
  "<body>Request blocked by AWS WAF.</body></html>",
17
17
  headers: [],
18
+ isCustom: false,
18
19
  };
19
20
  }
20
21
  /**
@@ -6,6 +6,11 @@ export interface SimWafEvaluationRequest {
6
6
  /** The web ACL to evaluate against, by ARN. */
7
7
  readonly webAclArn: string;
8
8
  readonly request: Request;
9
+ /**
10
+ * The client address the request came from. A request naming none comes
11
+ * from `127.0.0.1`, as every request does that states no other address.
12
+ */
13
+ readonly sourceIp?: string | undefined;
9
14
  /**
10
15
  * The request body, when the rules might inspect it. A request body is a
11
16
  * stream that cannot be read twice, so it is passed in already buffered.
@@ -20,6 +20,14 @@ export interface SimWafInspectedRequest {
20
20
  * header with the Yulin-local suffix taken off it.
21
21
  */
22
22
  readonly host: string;
23
+ /**
24
+ * The client address the request came from, which is what the IP-based
25
+ * rules read.
26
+ *
27
+ * Nothing reaches this simulation over a public network, so this is
28
+ * `127.0.0.1` unless the request said otherwise with `x-sim-aws-source-ip`.
29
+ */
30
+ readonly sourceIp: string;
23
31
  /** The request body, or nothing when the request carried none. */
24
32
  readonly body: Uint8Array | undefined;
25
33
  /**
@@ -49,5 +57,8 @@ export interface SimWafInspectedRequest {
49
57
  * The body inspection limit defaults to the 16 KB every resource type this
50
58
  * simulation protects reads by default. A web ACL raising it with
51
59
  * `AssociationConfig` passes the raised figure in.
60
+ *
61
+ * The client address defaults to loopback, which is where a request comes from
62
+ * when it states no other address.
52
63
  */
53
- export declare function simWafInspectedRequest(request: Request, body?: Uint8Array, bodyInspectionLimitBytes?: number): SimWafInspectedRequest;
64
+ export declare function simWafInspectedRequest(request: Request, body?: Uint8Array, bodyInspectionLimitBytes?: number, sourceIp?: string): SimWafInspectedRequest;
@@ -1,4 +1,7 @@
1
+ import { isIP } from "node:net";
2
+ import { simAwsProxiedSourceIp } from "../../../serve/http/sim-aws-proxied-connection.js";
1
3
  import { simAwsRequestHostname } from "../../../serve/http/url/sim-aws-request-hostname.js";
4
+ import { SimWafDeclarationError } from "../error/sim-wafv2.error.js";
2
5
  import { simWafBodyInspectionLimitBytes } from "../web-acl/sim-waf-association-config.js";
3
6
  import { SimWafRequestLabels } from "./sim-waf-request-labels.js";
4
7
  /**
@@ -16,8 +19,15 @@ import { SimWafRequestLabels } from "./sim-waf-request-labels.js";
16
19
  * The body inspection limit defaults to the 16 KB every resource type this
17
20
  * simulation protects reads by default. A web ACL raising it with
18
21
  * `AssociationConfig` passes the raised figure in.
22
+ *
23
+ * The client address defaults to loopback, which is where a request comes from
24
+ * when it states no other address.
19
25
  */
20
- export function simWafInspectedRequest(request, body, bodyInspectionLimitBytes = simWafBodyInspectionLimitBytes) {
26
+ export function simWafInspectedRequest(request, body, bodyInspectionLimitBytes = simWafBodyInspectionLimitBytes, sourceIp = simAwsProxiedSourceIp) {
27
+ if (isIP(sourceIp) === 0) {
28
+ throw new SimWafDeclarationError(`A request's source address must be one IPv4 or IPv6 address, and ` +
29
+ `${sourceIp} is neither`);
30
+ }
21
31
  const url = new URL(request.url);
22
32
  return {
23
33
  method: request.method,
@@ -28,6 +38,7 @@ export function simWafInspectedRequest(request, body, bodyInspectionLimitBytes =
28
38
  queryString: url.search.replace(/^\?/u, ""),
29
39
  headers: request.headers,
30
40
  host: simAwsRequestHostname(request),
41
+ sourceIp,
31
42
  body,
32
43
  bodyInspectionLimitBytes,
33
44
  labels: new SimWafRequestLabels(),
@@ -25,13 +25,14 @@ export { SimWafIpSet, type SimWafIpAddressVersion, } from "./ip-set/sim-waf-ip-s
25
25
  export { SimWafRegexPatternSet } from "./regex-pattern-set/sim-waf-regex-pattern-set.js";
26
26
  export type { SimWafDecision } from "./evaluate/sim-waf-decision.js";
27
27
  export { SimWafRequestLabels } from "./evaluate/sim-waf-request-labels.js";
28
- export { SimWafManagedRules, type SimWafManagedMatchDeclaration, } from "./managed/sim-waf-managed-rules.js";
28
+ export { SimWafManagedRules, type SimWafManagedAddressDeclaration, type SimWafManagedMatchDeclaration, } from "./managed/sim-waf-managed-rules.js";
29
29
  export type { SimWafBotDeclaration } from "./managed/sim-waf-bot-declaration.js";
30
30
  export type { SimWafManagedRuleReport } from "./managed/sim-waf-managed-rule-report.js";
31
31
  export type { SimWafManagedRuleTier } from "./managed/sim-waf-managed-rule.type.js";
32
32
  export type { SimWafManagedRuleGroupStatementInput, SimWafOverrideActionInput, SimWafRuleActionOverrideInput, } from "./managed/sim-waf-managed-group.type.js";
33
33
  export { simWafBlockedHttpResponse, type SimWafBlockedResponse, simWafDefaultBlockedResponse, } from "./evaluate/sim-waf-blocked-response.js";
34
34
  export { type SimWafInspectedRequest, simWafInspectedRequest, } from "./evaluate/sim-waf-inspected-request.js";
35
+ export { simAwsSourceIpHeaderName } from "../iam/request/sim-aws-source-ip-header.js";
35
36
  export { simWafHeaderInspectionLimitBytes } from "./statement/sim-waf-field-content.js";
36
37
  export { type SimWafAssociationConfigInput, type SimWafBodyInspectionResourceType, simWafBodyInspectionLimitBytes, } from "./web-acl/sim-waf-association-config.js";
37
38
  export type { SimWafRateBasedStatementInput } from "./statement/sim-waf-rate-based.type.js";
@@ -20,6 +20,7 @@ export { SimWafRequestLabels } from "./evaluate/sim-waf-request-labels.js";
20
20
  export { SimWafManagedRules, } from "./managed/sim-waf-managed-rules.js";
21
21
  export { simWafBlockedHttpResponse, simWafDefaultBlockedResponse, } from "./evaluate/sim-waf-blocked-response.js";
22
22
  export { simWafInspectedRequest, } from "./evaluate/sim-waf-inspected-request.js";
23
+ export { simAwsSourceIpHeaderName } from "../iam/request/sim-aws-source-ip-header.js";
23
24
  export { simWafHeaderInspectionLimitBytes } from "./statement/sim-waf-field-content.js";
24
25
  export { simWafBodyInspectionLimitBytes, } from "./web-acl/sim-waf-association-config.js";
25
26
  export { SimWafAssociatedItemException, SimWafDeclarationError, SimWafDuplicateItemException, SimWafError, SimWafInvalidParameterException, SimWafNonexistentItemException, SimWafOptimisticLockException, SimWafUnavailableEntityException, SimWafUnsimulatedInputException, } from "./error/sim-wafv2.error.js";