diff --git a/src/schema/transformers/MutuallyExclusivePropertiesForValidation.ts b/src/schema/transformers/MutuallyExclusivePropertiesForValidation.ts index 2ba63fb7..6c2b96ce 100644 --- a/src/schema/transformers/MutuallyExclusivePropertiesForValidation.ts +++ b/src/schema/transformers/MutuallyExclusivePropertiesForValidation.ts @@ -66,6 +66,11 @@ function getDependentExcludedMap() { HealthCheckCustomConfig: ['HealthCheckConfig'], }); + dependentExcludedMap.set('AWS::WAFv2::WebACL', { + SearchString: ['SearchStringBase64'], + SearchStringBase64: ['SearchString'], + }); + return dependentExcludedMap as ReadonlyMap; } diff --git a/src/schema/transformers/RemoveMutuallyExclusivePropertiesTransformer.ts b/src/schema/transformers/RemoveMutuallyExclusivePropertiesTransformer.ts index a64b6a5c..72110e27 100644 --- a/src/schema/transformers/RemoveMutuallyExclusivePropertiesTransformer.ts +++ b/src/schema/transformers/RemoveMutuallyExclusivePropertiesTransformer.ts @@ -56,8 +56,15 @@ export class RemoveMutuallyExclusivePropertiesTransformer implements ResourceTem return; } visited.add(resourceProperty); - // Schema refs are already resolved by ResourceSchema - const resolvedSchema = rawSchema; + + // Resolve $ref if present + let resolvedSchema = rawSchema; + if (rawSchema.$ref && resourceSchema) { + const resolved = resourceSchema.resolveRef(rawSchema.$ref); + if (resolved) { + resolvedSchema = resolved; + } + } // If this object has mutually exclusive properties, remove keys in current object const meResources = this.getMEResources(resolvedSchema, resourceSchema); @@ -92,7 +99,7 @@ export class RemoveMutuallyExclusivePropertiesTransformer implements ResourceTem const staticKeyList = Object.keys(resourceProperty); for (const key of staticKeyList) { const newPath = `${path}${this.PATH_SEPARATOR}${key}`; - const subschema = this.getSubSchema(resolvedSchema, key, newPath); + const subschema = this.getSubSchema(resolvedSchema, key, newPath, resourceSchema); if (subschema && resourceProperty[key] !== undefined) { const value = resourceProperty[key]; @@ -102,7 +109,7 @@ export class RemoveMutuallyExclusivePropertiesTransformer implements ResourceTem value, subschema, newPath, - undefined, + resourceSchema, depth + 1, visited, ); @@ -116,13 +123,18 @@ export class RemoveMutuallyExclusivePropertiesTransformer implements ResourceTem const itemPath = `${newPath}${this.PATH_SEPARATOR}${i}`; if (this.isObject(item)) { - const arraySchemaItem = this.getSubSchema(arraySubschema, this.UNINDEXED_PATH, itemPath); + const arraySchemaItem = this.getSubSchema( + arraySubschema, + this.UNINDEXED_PATH, + itemPath, + resourceSchema, + ); if (arraySchemaItem) { this.traverseResourcePropertiesAndRemoveMutuallyExclusiveProperties( item, arraySchemaItem, itemPath, - undefined, + resourceSchema, depth + 1, visited, ); @@ -232,16 +244,30 @@ export class RemoveMutuallyExclusivePropertiesTransformer implements ResourceTem return arraySchema ?? schema; } - private getSubSchema(schema: PropertyType, id: string, path: string): PropertyType | undefined { + private getSubSchema( + schema: PropertyType, + id: string, + path: string, + resourceSchema?: ResourceSchema, + ): PropertyType | undefined { try { - // Simplified implementation - in a real scenario, this would use a schema helper + let result: PropertyType | undefined; + if (schema.properties?.[id]) { - return schema.properties[id]; + result = schema.properties[id]; + } else if (schema.items && id === this.UNINDEXED_PATH) { + result = schema.items; } - if (schema.items && id === this.UNINDEXED_PATH) { - return schema.items; + + // Resolve $ref if present + if (result?.$ref && resourceSchema) { + const resolved = resourceSchema.resolveRef(result.$ref); + if (resolved) { + return resolved; + } } - return; + + return result; } catch { if (id !== this.REF_ID && id !== this.GETATT_ID) { logger.info(`Unable to find schema at path ${path}`); diff --git a/tst/resources/schemas/aws-wafv2-webacl.json b/tst/resources/schemas/aws-wafv2-webacl.json new file mode 100644 index 00000000..d9e6bb85 --- /dev/null +++ b/tst/resources/schemas/aws-wafv2-webacl.json @@ -0,0 +1,1851 @@ +{ + "sourceUrl" : "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-wafv2.git", + "tagging" : { + "permissions" : [ "wafv2:TagResource", "wafv2:UntagResource", "wafv2:ListTagsForResource" ], + "tagOnCreate" : true, + "taggable" : true, + "tagUpdatable" : true, + "tagProperty" : "/properties/Tags", + "cloudFormationSystemTags" : true + }, + "handlers" : { + "read" : { + "permissions" : [ "wafv2:GetWebACL", "wafv2:ListTagsForResource" ] + }, + "create" : { + "permissions" : [ "wafv2:CreateWebACL", "wafv2:GetWebACL", "wafv2:ListTagsForResource", "wafv2:TagResource", "wafv2:UntagResource" ] + }, + "update" : { + "permissions" : [ "wafv2:UpdateWebACL", "wafv2:GetWebACL", "wafv2:ListTagsForResource", "wafv2:TagResource", "wafv2:UntagResource" ] + }, + "list" : { + "permissions" : [ "wafv2:listWebACLs" ], + "handlerSchema" : { + "properties" : { + "Scope" : { + "$ref" : "resource-schema.json#/properties/Scope" + } + }, + "required" : [ "Scope" ] + } + }, + "delete" : { + "permissions" : [ "wafv2:DeleteWebACL", "wafv2:GetWebACL" ] + } + }, + "typeName" : "AWS::WAFv2::WebACL", + "readOnlyProperties" : [ "/properties/Arn", "/properties/Capacity", "/properties/Id", "/properties/LabelNamespace" ], + "description" : "Contains the Rules that identify the requests that you want to allow, block, or count. In a WebACL, you also specify a default action (ALLOW or BLOCK), and the action for each Rule that you add to a WebACL, for example, block requests from specified IP addresses or block requests from specified referrers. You also associate the WebACL with a CloudFront distribution to identify the requests that you want AWS WAF to filter. If you add more than one Rule to a WebACL, a request needs to match only one of the specifications to be allowed, blocked, or counted.", + "createOnlyProperties" : [ "/properties/Name", "/properties/Scope" ], + "additionalProperties" : false, + "primaryIdentifier" : [ "/properties/Name", "/properties/Id", "/properties/Scope" ], + "definitions" : { + "CustomHTTPHeaderName" : { + "minLength" : 1, + "description" : "HTTP header name.", + "type" : "string", + "maxLength" : 64 + }, + "SearchString" : { + "description" : "String that is searched to find a match.", + "type" : "string" + }, + "BodyParsingFallbackBehavior" : { + "description" : "The inspection behavior to fall back to if the JSON in the request body is invalid.", + "type" : "string", + "enum" : [ "MATCH", "NO_MATCH", "EVALUATE_AS_STRING" ] + }, + "DataProtect" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Field" : { + "$ref" : "#/definitions/FieldToProtect" + }, + "Action" : { + "$ref" : "#/definitions/DataProtectionAction" + }, + "ExcludeRateBasedDetails" : { + "type" : "boolean" + }, + "ExcludeRuleMatchDetails" : { + "type" : "boolean" + } + }, + "required" : [ "Field", "Action" ] + }, + "ResponseStatusCode" : { + "description" : "Custom response code.", + "maximum" : 599, + "type" : "integer", + "minimum" : 200 + }, + "HeaderMatchPattern" : { + "description" : "The pattern to look for in the request headers.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "All" : { + "description" : "Inspect all parts of the web request headers.", + "type" : "object" + }, + "IncludedHeaders" : { + "minItems" : 1, + "maxItems" : 199, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 64 + } + }, + "ExcludedHeaders" : { + "minItems" : 1, + "maxItems" : 199, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 64 + } + } + } + }, + "CustomResponse" : { + "description" : "Custom response.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ResponseCode" : { + "$ref" : "#/definitions/ResponseStatusCode" + }, + "CustomResponseBodyKey" : { + "pattern" : "^[\\w\\-]+$", + "description" : "Custom response body key.", + "type" : "string" + }, + "ResponseHeaders" : { + "minItems" : 1, + "description" : "Collection of HTTP headers.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/CustomHTTPHeader" + } + } + }, + "required" : [ "ResponseCode" ] + }, + "JA4Fingerprint" : { + "description" : "Includes the JA4 fingerprint of a web request.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "FallbackBehavior" : { + "type" : "string", + "enum" : [ "MATCH", "NO_MATCH" ] + } + }, + "required" : [ "FallbackBehavior" ] + }, + "RegexMatchStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + }, + "RegexString" : { + "minLength" : 1, + "type" : "string", + "maxLength" : 512 + }, + "FieldToMatch" : { + "$ref" : "#/definitions/FieldToMatch" + } + }, + "required" : [ "RegexString", "FieldToMatch", "TextTransformations" ] + }, + "RegexPatternSetReferenceStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + }, + "Arn" : { + "$ref" : "#/definitions/ResourceArn" + }, + "FieldToMatch" : { + "$ref" : "#/definitions/FieldToMatch" + } + }, + "required" : [ "Arn", "FieldToMatch", "TextTransformations" ] + }, + "IPSetReferenceStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "IPSetForwardedIPConfig" : { + "$ref" : "#/definitions/IPSetForwardedIPConfiguration" + }, + "Arn" : { + "$ref" : "#/definitions/ResourceArn" + } + }, + "required" : [ "Arn" ] + }, + "RequestBodyAssociatedResourceTypeConfig" : { + "description" : "Configures the inspection size in the request body.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "DefaultSizeInspectionLimit" : { + "$ref" : "#/definitions/SizeInspectionLimit" + } + }, + "required" : [ "DefaultSizeInspectionLimit" ] + }, + "RateLimitJA3Fingerprint" : { + "description" : "Specifies the request's JA3 fingerprint as an aggregate key for a rate-based rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "FallbackBehavior" : { + "type" : "string", + "enum" : [ "MATCH", "NO_MATCH" ] + } + }, + "required" : [ "FallbackBehavior" ] + }, + "AssociationConfig" : { + "description" : "AssociationConfig for body inspection", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "RequestBody" : { + "$ref" : "#/definitions/RequestBody" + } + } + }, + "JsonMatchScope" : { + "description" : "The parts of the JSON to match against using the MatchPattern.", + "type" : "string", + "enum" : [ "ALL", "KEY", "VALUE" ] + }, + "RulePriority" : { + "description" : "Priority of the Rule, Rules get evaluated from lower to higher priority.", + "type" : "integer", + "minimum" : 0 + }, + "RuleActionOverride" : { + "description" : "Action override for rules in the rule group.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ActionToUse" : { + "$ref" : "#/definitions/RuleAction" + }, + "Name" : { + "$ref" : "#/definitions/EntityName" + } + }, + "required" : [ "Name", "ActionToUse" ] + }, + "ExcludedRule" : { + "description" : "Excluded Rule in the RuleGroup or ManagedRuleGroup will not be evaluated.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Name" : { + "$ref" : "#/definitions/EntityName" + } + }, + "required" : [ "Name" ] + }, + "EntityDescription" : { + "pattern" : "^[a-zA-Z0-9=:#@/\\-,.][a-zA-Z0-9+=:#@/\\-,.\\s]+[a-zA-Z0-9+=:#@/\\-,.]{1,256}$", + "description" : "Description of the entity.", + "type" : "string" + }, + "RateLimitQueryArgument" : { + "description" : "Specifies a query argument in the request as an aggregate key for a rate-based rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + }, + "Name" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "description" : "The name of the query argument to use.", + "type" : "string", + "maxLength" : 64 + } + }, + "required" : [ "Name", "TextTransformations" ] + }, + "Rule" : { + "description" : "Rule of WebACL that contains condition and action.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Action" : { + "$ref" : "#/definitions/RuleAction" + }, + "Priority" : { + "$ref" : "#/definitions/RulePriority" + }, + "Statement" : { + "$ref" : "#/definitions/Statement" + }, + "ChallengeConfig" : { + "$ref" : "#/definitions/ChallengeConfig" + }, + "OverrideAction" : { + "$ref" : "#/definitions/OverrideAction" + }, + "RuleLabels" : { + "description" : "Collection of Rule Labels.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Label" + } + }, + "VisibilityConfig" : { + "$ref" : "#/definitions/VisibilityConfig" + }, + "CaptchaConfig" : { + "$ref" : "#/definitions/CaptchaConfig" + }, + "Name" : { + "$ref" : "#/definitions/EntityName" + } + }, + "required" : [ "Name", "Priority", "Statement", "VisibilityConfig" ] + }, + "TextTransformationPriority" : { + "description" : "Priority of Rule being evaluated.", + "type" : "integer", + "minimum" : 0 + }, + "RateLimitHeader" : { + "description" : "Specifies a header as an aggregate key for a rate-based rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + }, + "Name" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "description" : "The name of the header to use.", + "type" : "string", + "maxLength" : 64 + } + }, + "required" : [ "Name", "TextTransformations" ] + }, + "IPSetForwardedIPConfiguration" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "FallbackBehavior" : { + "type" : "string", + "enum" : [ "MATCH", "NO_MATCH" ] + }, + "HeaderName" : { + "pattern" : "^[a-zA-Z0-9-]+{1,255}$", + "type" : "string" + }, + "Position" : { + "type" : "string", + "enum" : [ "FIRST", "LAST", "ANY" ] + } + }, + "required" : [ "HeaderName", "FallbackBehavior", "Position" ] + }, + "SizeConstraintStatement" : { + "description" : "Size Constraint statement.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ComparisonOperator" : { + "type" : "string", + "enum" : [ "EQ", "NE", "LE", "LT", "GE", "GT" ] + }, + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + }, + "Size" : { + "maximum" : 21474836480, + "type" : "number", + "minimum" : 0 + }, + "FieldToMatch" : { + "$ref" : "#/definitions/FieldToMatch" + } + }, + "required" : [ "FieldToMatch", "ComparisonOperator", "Size", "TextTransformations" ] + }, + "AndStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Statements" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Statement" + } + } + }, + "required" : [ "Statements" ] + }, + "ResponseContent" : { + "minLength" : 1, + "description" : "Response content.", + "type" : "string", + "maxLength" : 10240 + }, + "JA3Fingerprint" : { + "description" : "Includes the JA3 fingerprint of a web request.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "FallbackBehavior" : { + "type" : "string", + "enum" : [ "MATCH", "NO_MATCH" ] + } + }, + "required" : [ "FallbackBehavior" ] + }, + "EntityName" : { + "pattern" : "^[0-9A-Za-z_-]{1,128}$", + "description" : "Name of the WebACL.", + "type" : "string" + }, + "NotStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Statement" : { + "$ref" : "#/definitions/Statement" + } + }, + "required" : [ "Statement" ] + }, + "ResponseInspectionStatusCode" : { + "description" : "Response status codes that indicate success or failure of a login request", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "SuccessCodes" : { + "minItems" : 1, + "maxItems" : 10, + "type" : "array", + "items" : { + "minLength" : 0, + "type" : "integer", + "maxLength" : 999 + } + }, + "FailureCodes" : { + "minItems" : 1, + "maxItems" : 10, + "type" : "array", + "items" : { + "minLength" : 0, + "type" : "integer", + "maxLength" : 999 + } + } + }, + "required" : [ "SuccessCodes", "FailureCodes" ] + }, + "PhoneNumberField" : { + "$ref" : "#/definitions/FieldIdentifier" + }, + "RuleAction" : { + "description" : "Action taken when Rule matches its condition.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Captcha" : { + "$ref" : "#/definitions/CaptchaAction" + }, + "Block" : { + "$ref" : "#/definitions/BlockAction" + }, + "Count" : { + "$ref" : "#/definitions/CountAction" + }, + "Allow" : { + "$ref" : "#/definitions/AllowAction" + }, + "Challenge" : { + "$ref" : "#/definitions/ChallengeAction" + } + } + }, + "RateLimitUriPath" : { + "description" : "Specifies the request's URI Path as an aggregate key for a rate-based rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + } + }, + "required" : [ "TextTransformations" ] + }, + "TextTransformationType" : { + "description" : "Type of text transformation.", + "type" : "string", + "enum" : [ "NONE", "COMPRESS_WHITE_SPACE", "HTML_ENTITY_DECODE", "LOWERCASE", "CMD_LINE", "URL_DECODE", "BASE64_DECODE", "HEX_DECODE", "MD5", "REPLACE_COMMENTS", "ESCAPE_SEQ_DECODE", "SQL_HEX_DECODE", "CSS_DECODE", "JS_DECODE", "NORMALIZE_PATH", "NORMALIZE_PATH_WIN", "REMOVE_NULLS", "REPLACE_NULLS", "BASE64_DECODE_EXT", "URL_DECODE_UNI", "UTF8_TO_UNICODE" ] + }, + "LabelName" : { + "pattern" : "^[0-9A-Za-z_:-]{1,1024}$", + "description" : "Name of the Label.", + "type" : "string" + }, + "PositionalConstraint" : { + "description" : "Position of the evaluation in the FieldToMatch of request.", + "type" : "string", + "enum" : [ "EXACTLY", "STARTS_WITH", "ENDS_WITH", "CONTAINS", "CONTAINS_WORD" ] + }, + "CustomHTTPHeaderValue" : { + "minLength" : 1, + "description" : "HTTP header value.", + "type" : "string", + "maxLength" : 255 + }, + "LabelMatchStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Scope" : { + "$ref" : "#/definitions/LabelMatchScope" + }, + "Key" : { + "$ref" : "#/definitions/LabelMatchKey" + } + }, + "required" : [ "Scope", "Key" ] + }, + "ResponseInspectionBodyContains" : { + "description" : "Response body contents that indicate success or failure of a login request", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "SuccessStrings" : { + "minItems" : 1, + "maxItems" : 5, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 100 + } + }, + "FailureStrings" : { + "minItems" : 1, + "maxItems" : 5, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 100 + } + } + }, + "required" : [ "SuccessStrings", "FailureStrings" ] + }, + "ExcludedRules" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ExcludedRule" + } + }, + "FieldToMatch" : { + "description" : "Field of the request to match.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "AllQueryArguments" : { + "description" : "All query arguments of a web request.", + "type" : "object" + }, + "JA3Fingerprint" : { + "$ref" : "#/definitions/JA3Fingerprint" + }, + "SingleQueryArgument" : { + "description" : "One query argument in a web request, identified by name, for example UserName or SalesRegion. The name can be up to 30 characters long and isn't case sensitive.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Name" : { + "type" : "string" + } + }, + "required" : [ "Name" ] + }, + "QueryString" : { + "description" : "The query string of a web request. This is the part of a URL that appears after a ? character, if any.", + "type" : "object" + }, + "Headers" : { + "$ref" : "#/definitions/Headers" + }, + "Method" : { + "description" : "The HTTP method of a web request. The method indicates the type of operation that the request is asking the origin to perform.", + "type" : "object" + }, + "UriFragment" : { + "$ref" : "#/definitions/UriFragment" + }, + "JsonBody" : { + "$ref" : "#/definitions/JsonBody" + }, + "UriPath" : { + "description" : "The path component of the URI of a web request. This is the part of a web request that identifies a resource, for example, /images/daily-ad.jpg.", + "type" : "object" + }, + "Cookies" : { + "$ref" : "#/definitions/Cookies" + }, + "JA4Fingerprint" : { + "$ref" : "#/definitions/JA4Fingerprint" + }, + "Body" : { + "$ref" : "#/definitions/Body" + }, + "SingleHeader" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Name" : { + "type" : "string" + } + }, + "required" : [ "Name" ] + } + } + }, + "RequestBody" : { + "patternProperties" : { + "^(CLOUDFRONT|API_GATEWAY|COGNITO_USER_POOL|APP_RUNNER_SERVICE|VERIFIED_ACCESS_INSTANCE)$" : { + "$ref" : "#/definitions/RequestBodyAssociatedResourceTypeConfig" + } + }, + "description" : "Map of AssociatedResourceType and RequestBodyAssociatedResourceTypeConfig", + "additionalProperties" : false, + "type" : "object" + }, + "Statement" : { + "description" : "First level statement that contains conditions, such as ByteMatch, SizeConstraint, etc", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "SizeConstraintStatement" : { + "$ref" : "#/definitions/SizeConstraintStatement" + }, + "AndStatement" : { + "$ref" : "#/definitions/AndStatement" + }, + "XssMatchStatement" : { + "$ref" : "#/definitions/XssMatchStatement" + }, + "NotStatement" : { + "$ref" : "#/definitions/NotStatement" + }, + "ByteMatchStatement" : { + "$ref" : "#/definitions/ByteMatchStatement" + }, + "RateBasedStatement" : { + "$ref" : "#/definitions/RateBasedStatement" + }, + "GeoMatchStatement" : { + "$ref" : "#/definitions/GeoMatchStatement" + }, + "RuleGroupReferenceStatement" : { + "$ref" : "#/definitions/RuleGroupReferenceStatement" + }, + "LabelMatchStatement" : { + "$ref" : "#/definitions/LabelMatchStatement" + }, + "RegexMatchStatement" : { + "$ref" : "#/definitions/RegexMatchStatement" + }, + "SqliMatchStatement" : { + "$ref" : "#/definitions/SqliMatchStatement" + }, + "RegexPatternSetReferenceStatement" : { + "$ref" : "#/definitions/RegexPatternSetReferenceStatement" + }, + "OrStatement" : { + "$ref" : "#/definitions/OrStatement" + }, + "ManagedRuleGroupStatement" : { + "$ref" : "#/definitions/ManagedRuleGroupStatement" + }, + "IPSetReferenceStatement" : { + "$ref" : "#/definitions/IPSetReferenceStatement" + } + } + }, + "ChallengeConfig" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ImmunityTimeProperty" : { + "$ref" : "#/definitions/ImmunityTimeProperty" + } + } + }, + "RateBasedStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "AggregateKeyType" : { + "type" : "string", + "enum" : [ "CONSTANT", "IP", "FORWARDED_IP", "CUSTOM_KEYS" ] + }, + "CustomKeys" : { + "maxItems" : 5, + "description" : "Specifies the aggregate keys to use in a rate-base rule.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/RateBasedStatementCustomKey" + } + }, + "ForwardedIPConfig" : { + "$ref" : "#/definitions/ForwardedIPConfiguration" + }, + "Limit" : { + "$ref" : "#/definitions/RateLimit" + }, + "EvaluationWindowSec" : { + "$ref" : "#/definitions/EvaluationWindowSec" + }, + "ScopeDownStatement" : { + "$ref" : "#/definitions/Statement" + } + }, + "required" : [ "Limit", "AggregateKeyType" ] + }, + "LabelMatchKey" : { + "pattern" : "^[0-9A-Za-z_:-]{1,1024}$", + "type" : "string" + }, + "FieldIdentifier" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Identifier" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 512 + } + }, + "required" : [ "Identifier" ] + }, + "Rules" : { + "description" : "Collection of Rules.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Rule" + } + }, + "OversizeHandling" : { + "description" : "Handling of requests containing oversize fields", + "type" : "string", + "enum" : [ "CONTINUE", "MATCH", "NO_MATCH" ] + }, + "SearchStringBase64" : { + "description" : "Base64 encoded string that is searched to find a match.", + "type" : "string" + }, + "UriPath" : { + "type" : "object" + }, + "CustomResponseBodies" : { + "patternProperties" : { + "^[\\w\\-]+$" : { + "$ref" : "#/definitions/CustomResponseBody" + } + }, + "description" : "Custom response key and body map.", + "additionalProperties" : false, + "type" : "object", + "minProperties" : 1 + }, + "Tag" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Value" : { + "minLength" : 0, + "type" : "string", + "maxLength" : 256 + }, + "Key" : { + "minLength" : 1, + "type" : "string", + "maxLength" : 128 + } + } + }, + "CookieMatchPattern" : { + "description" : "The pattern to look for in the request cookies.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "All" : { + "description" : "Inspect all parts of the web request cookies.", + "type" : "object" + }, + "IncludedCookies" : { + "minItems" : 1, + "maxItems" : 199, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 60 + } + }, + "ExcludedCookies" : { + "minItems" : 1, + "maxItems" : 199, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 60 + } + } + } + }, + "ResponseInspectionHeader" : { + "description" : "Response headers that indicate success or failure of a login request", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "SuccessValues" : { + "minItems" : 1, + "maxItems" : 3, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 100 + } + }, + "FailureValues" : { + "minItems" : 1, + "maxItems" : 3, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 100 + } + }, + "Name" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 200 + } + }, + "required" : [ "Name", "SuccessValues", "FailureValues" ] + }, + "CaptchaAction" : { + "description" : "Checks valid token exists with request.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "CustomRequestHandling" : { + "$ref" : "#/definitions/CustomRequestHandling" + } + } + }, + "BlockAction" : { + "description" : "Block traffic towards application.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "CustomResponse" : { + "$ref" : "#/definitions/CustomResponse" + } + } + }, + "DataProtectionAction" : { + "type" : "string", + "enum" : [ "SUBSTITUTION", "HASH" ] + }, + "Label" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Name" : { + "$ref" : "#/definitions/LabelName" + } + }, + "required" : [ "Name" ] + }, + "DataProtectionConfig" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "DataProtections" : { + "$ref" : "#/definitions/DataProtections" + } + }, + "required" : [ "DataProtections" ] + }, + "UriFragment" : { + "description" : "The path component of the URI Fragment. This is the part of a web request that identifies a fragment uri, for example, /abcd#introduction", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "FallbackBehavior" : { + "type" : "string", + "enum" : [ "MATCH", "NO_MATCH" ] + } + } + }, + "ResponseInspectionJson" : { + "description" : "Response JSON that indicate success or failure of a login request", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Identifier" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 512 + }, + "SuccessValues" : { + "minItems" : 1, + "maxItems" : 5, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 100 + } + }, + "FailureValues" : { + "minItems" : 1, + "maxItems" : 5, + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 100 + } + } + }, + "required" : [ "Identifier", "SuccessValues", "FailureValues" ] + }, + "FieldToProtect" : { + "description" : "Field in log to protect.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "FieldKeys" : { + "description" : "List of field keys to protect", + "type" : "array", + "items" : { + "$ref" : "#/definitions/FieldToProtectKeyName" + } + }, + "FieldType" : { + "description" : "Field type to protect", + "type" : "string", + "enum" : [ "SINGLE_HEADER", "SINGLE_COOKIE", "SINGLE_QUERY_ARGUMENT", "QUERY_STRING", "BODY" ] + } + }, + "required" : [ "FieldType" ] + }, + "ManagedRuleGroupConfig" : { + "description" : "ManagedRuleGroupConfig.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "UsernameField" : { + "$ref" : "#/definitions/FieldIdentifier" + }, + "LoginPath" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "type" : "string", + "maxLength" : 256 + }, + "AWSManagedRulesATPRuleSet" : { + "$ref" : "#/definitions/AWSManagedRulesATPRuleSet" + }, + "AWSManagedRulesBotControlRuleSet" : { + "$ref" : "#/definitions/AWSManagedRulesBotControlRuleSet" + }, + "PasswordField" : { + "$ref" : "#/definitions/FieldIdentifier" + }, + "AWSManagedRulesACFPRuleSet" : { + "$ref" : "#/definitions/AWSManagedRulesACFPRuleSet" + }, + "PayloadType" : { + "type" : "string", + "enum" : [ "JSON", "FORM_ENCODED" ] + } + } + }, + "Cookies" : { + "description" : "Includes cookies of a web request.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "MatchScope" : { + "$ref" : "#/definitions/MapMatchScope" + }, + "MatchPattern" : { + "$ref" : "#/definitions/CookieMatchPattern" + }, + "OversizeHandling" : { + "$ref" : "#/definitions/OversizeHandling" + } + }, + "required" : [ "MatchPattern", "MatchScope", "OversizeHandling" ] + }, + "AllowAction" : { + "description" : "Allow traffic towards application.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "CustomRequestHandling" : { + "$ref" : "#/definitions/CustomRequestHandling" + } + } + }, + "AWSManagedRulesBotControlRuleSet" : { + "description" : "Configures how to use the Bot Control managed rule group in the web ACL", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "InspectionLevel" : { + "type" : "string", + "enum" : [ "COMMON", "TARGETED" ] + }, + "EnableMachineLearning" : { + "type" : "boolean" + } + }, + "required" : [ "InspectionLevel" ] + }, + "SqliMatchStatement" : { + "description" : "Sqli Match Statement.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "SensitivityLevel" : { + "$ref" : "#/definitions/SensitivityLevel" + }, + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + }, + "FieldToMatch" : { + "$ref" : "#/definitions/FieldToMatch" + } + }, + "required" : [ "FieldToMatch", "TextTransformations" ] + }, + "ManagedRuleGroupStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "VendorName" : { + "type" : "string" + }, + "Version" : { + "minLength" : 1, + "pattern" : "^[\\w#:\\.\\-/]+$", + "type" : "string", + "maxLength" : 64 + }, + "RuleActionOverrides" : { + "maxItems" : 100, + "description" : "Action overrides for rules in the rule group.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/RuleActionOverride" + } + }, + "ManagedRuleGroupConfigs" : { + "description" : "Collection of ManagedRuleGroupConfig.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/ManagedRuleGroupConfig" + } + }, + "ExcludedRules" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ExcludedRule" + } + }, + "Name" : { + "$ref" : "#/definitions/EntityName" + }, + "ScopeDownStatement" : { + "$ref" : "#/definitions/Statement" + } + }, + "required" : [ "VendorName", "Name" ] + }, + "EvaluationWindowSec" : { + "type" : "integer", + "enum" : [ 60, 120, 300, 600 ] + }, + "RateLimitCookie" : { + "description" : "Specifies a cookie as an aggregate key for a rate-based rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + }, + "Name" : { + "minLength" : 1, + "pattern" : ".*\\S.*", + "description" : "The name of the cookie to use.", + "type" : "string", + "maxLength" : 64 + } + }, + "required" : [ "Name", "TextTransformations" ] + }, + "AddressField" : { + "$ref" : "#/definitions/FieldIdentifier" + }, + "JsonMatchPattern" : { + "description" : "The pattern to look for in the JSON body.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "All" : { + "description" : "Inspect all parts of the web request's JSON body.", + "type" : "object" + }, + "IncludedPaths" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/JsonPointerPath" + } + } + } + }, + "RateLimitIP" : { + "description" : "Specifies the IP address in the web request as an aggregate key for a rate-based rule.", + "type" : "object" + }, + "ChallengeAction" : { + "description" : "Checks that the request has a valid token with an unexpired challenge timestamp and, if not, returns a browser challenge to the client.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "CustomRequestHandling" : { + "$ref" : "#/definitions/CustomRequestHandling" + } + } + }, + "FieldToProtectKeyName" : { + "minLength" : 1, + "description" : "Key of the field to protect.", + "type" : "string", + "maxLength" : 64 + }, + "CountAction" : { + "description" : "Allow traffic towards application.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "CustomRequestHandling" : { + "$ref" : "#/definitions/CustomRequestHandling" + } + } + }, + "ByteMatchStatement" : { + "description" : "Byte Match statement.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "SearchStringBase64" : { + "$ref" : "#/definitions/SearchStringBase64" + }, + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + }, + "PositionalConstraint" : { + "$ref" : "#/definitions/PositionalConstraint" + }, + "SearchString" : { + "$ref" : "#/definitions/SearchString" + }, + "FieldToMatch" : { + "$ref" : "#/definitions/FieldToMatch" + } + }, + "required" : [ "FieldToMatch", "PositionalConstraint", "TextTransformations" ] + }, + "SizeInspectionLimit" : { + "type" : "string", + "enum" : [ "KB_16", "KB_32", "KB_48", "KB_64" ] + }, + "CustomRequestHandling" : { + "description" : "Custom request handling.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "InsertHeaders" : { + "minItems" : 1, + "description" : "Collection of HTTP headers.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/CustomHTTPHeader" + } + } + }, + "required" : [ "InsertHeaders" ] + }, + "DataProtections" : { + "minItems" : 1, + "type" : "array", + "items" : { + "$ref" : "#/definitions/DataProtect" + } + }, + "OverrideAction" : { + "description" : "Override a RuleGroup or ManagedRuleGroup behavior. This can only be applied to Rule that has RuleGroupReferenceStatement or ManagedRuleGroupReferenceStatement.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Count" : { + "description" : "Count traffic towards application.", + "type" : "object" + }, + "None" : { + "description" : "Keep the RuleGroup or ManagedRuleGroup behavior as is.", + "type" : "object" + } + } + }, + "GeoMatchStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ForwardedIPConfig" : { + "$ref" : "#/definitions/ForwardedIPConfiguration" + }, + "CountryCodes" : { + "type" : "array", + "items" : { + "minLength" : 1, + "type" : "string", + "maxLength" : 2 + } + } + } + }, + "VisibilityConfig" : { + "description" : "Visibility Metric of the WebACL.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "MetricName" : { + "minLength" : 1, + "type" : "string", + "maxLength" : 128 + }, + "SampledRequestsEnabled" : { + "type" : "boolean" + }, + "CloudWatchMetricsEnabled" : { + "type" : "boolean" + } + }, + "required" : [ "SampledRequestsEnabled", "CloudWatchMetricsEnabled", "MetricName" ] + }, + "AWSManagedRulesACFPRuleSet" : { + "description" : "Configures how to use the Account creation fraud prevention managed rule group in the web ACL", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "RegistrationPagePath" : { + "type" : "string" + }, + "ResponseInspection" : { + "$ref" : "#/definitions/ResponseInspection" + }, + "CreationPath" : { + "type" : "string" + }, + "EnableRegexInPath" : { + "type" : "boolean" + }, + "RequestInspection" : { + "$ref" : "#/definitions/RequestInspectionACFP" + } + }, + "required" : [ "CreationPath", "RegistrationPagePath", "RequestInspection" ] + }, + "RuleGroupReferenceStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "RuleActionOverrides" : { + "maxItems" : 100, + "description" : "Action overrides for rules in the rule group.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/RuleActionOverride" + } + }, + "Arn" : { + "$ref" : "#/definitions/ResourceArn" + }, + "ExcludedRules" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ExcludedRule" + } + } + }, + "required" : [ "Arn" ] + }, + "TokenDomains" : { + "description" : "List of domains to accept in web request tokens, in addition to the domain of the protected resource.", + "type" : "array", + "items" : { + "minLength" : 1, + "pattern" : "^[\\w\\.\\-/]+$", + "type" : "string", + "maxLength" : 253 + } + }, + "RateLimitJA4Fingerprint" : { + "description" : "Specifies the request's JA4 fingerprint as an aggregate key for a rate-based rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "FallbackBehavior" : { + "type" : "string", + "enum" : [ "MATCH", "NO_MATCH" ] + } + }, + "required" : [ "FallbackBehavior" ] + }, + "ResourceArn" : { + "minLength" : 20, + "description" : "ARN of the WAF entity.", + "type" : "string", + "maxLength" : 2048 + }, + "DefaultAction" : { + "description" : "Default Action WebACL will take against ingress traffic when there is no matching Rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Block" : { + "$ref" : "#/definitions/BlockAction" + }, + "Allow" : { + "$ref" : "#/definitions/AllowAction" + } + } + }, + "JsonPointerPath" : { + "pattern" : "^[\\/]+([^~]*(~[01])*)*{1,512}$", + "description" : "JSON pointer path in the web request's JSON body", + "type" : "string" + }, + "RateBasedStatementCustomKey" : { + "description" : "Specifies a single custom aggregate key for a rate-base rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Cookie" : { + "$ref" : "#/definitions/RateLimitCookie" + }, + "ForwardedIP" : { + "$ref" : "#/definitions/RateLimitForwardedIP" + }, + "QueryArgument" : { + "$ref" : "#/definitions/RateLimitQueryArgument" + }, + "JA3Fingerprint" : { + "$ref" : "#/definitions/RateLimitJA3Fingerprint" + }, + "Header" : { + "$ref" : "#/definitions/RateLimitHeader" + }, + "HTTPMethod" : { + "$ref" : "#/definitions/RateLimitHTTPMethod" + }, + "QueryString" : { + "$ref" : "#/definitions/RateLimitQueryString" + }, + "UriPath" : { + "$ref" : "#/definitions/RateLimitUriPath" + }, + "IP" : { + "$ref" : "#/definitions/RateLimitIP" + }, + "JA4Fingerprint" : { + "$ref" : "#/definitions/RateLimitJA4Fingerprint" + }, + "LabelNamespace" : { + "$ref" : "#/definitions/RateLimitLabelNamespace" + } + } + }, + "ResponseInspection" : { + "description" : "Configures the inspection of login responses", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Header" : { + "$ref" : "#/definitions/ResponseInspectionHeader" + }, + "BodyContains" : { + "$ref" : "#/definitions/ResponseInspectionBodyContains" + }, + "Json" : { + "$ref" : "#/definitions/ResponseInspectionJson" + }, + "StatusCode" : { + "$ref" : "#/definitions/ResponseInspectionStatusCode" + } + } + }, + "LabelMatchScope" : { + "type" : "string", + "enum" : [ "LABEL", "NAMESPACE" ] + }, + "RateLimitHTTPMethod" : { + "description" : "Specifies the request's HTTP method as an aggregate key for a rate-based rule.", + "type" : "object" + }, + "RequestInspectionACFP" : { + "description" : "Configures the inspection of sign-up requests", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "UsernameField" : { + "$ref" : "#/definitions/FieldIdentifier" + }, + "EmailField" : { + "$ref" : "#/definitions/FieldIdentifier" + }, + "PasswordField" : { + "$ref" : "#/definitions/FieldIdentifier" + }, + "AddressFields" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AddressField" + } + }, + "PayloadType" : { + "type" : "string", + "enum" : [ "JSON", "FORM_ENCODED" ] + }, + "PhoneNumberFields" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/PhoneNumberField" + } + } + }, + "required" : [ "PayloadType" ] + }, + "ImmunityTimeProperty" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ImmunityTime" : { + "maximum" : 259200, + "type" : "integer", + "minimum" : 60 + } + }, + "required" : [ "ImmunityTime" ] + }, + "RateLimitLabelNamespace" : { + "description" : "Specifies a label namespace to use as an aggregate key for a rate-based rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Namespace" : { + "pattern" : "^[0-9A-Za-z_:-]{1,1024}$", + "description" : "The namespace to use for aggregation.", + "type" : "string" + } + }, + "required" : [ "Namespace" ] + }, + "SingleQueryArgument" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Name" : { + "type" : "string" + } + } + }, + "XssMatchStatement" : { + "description" : "Xss Match Statement.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + }, + "FieldToMatch" : { + "$ref" : "#/definitions/FieldToMatch" + } + }, + "required" : [ "FieldToMatch", "TextTransformations" ] + }, + "Headers" : { + "description" : "Includes headers of a web request.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "MatchScope" : { + "$ref" : "#/definitions/MapMatchScope" + }, + "MatchPattern" : { + "$ref" : "#/definitions/HeaderMatchPattern" + }, + "OversizeHandling" : { + "$ref" : "#/definitions/OversizeHandling" + } + }, + "required" : [ "MatchPattern", "MatchScope", "OversizeHandling" ] + }, + "RateLimitForwardedIP" : { + "description" : "Specifies the first IP address in an HTTP header as an aggregate key for a rate-based rule.", + "type" : "object" + }, + "AWSManagedRulesATPRuleSet" : { + "description" : "Configures how to use the Account Takeover Prevention managed rule group in the web ACL", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ResponseInspection" : { + "$ref" : "#/definitions/ResponseInspection" + }, + "EnableRegexInPath" : { + "type" : "boolean" + }, + "LoginPath" : { + "type" : "string" + }, + "RequestInspection" : { + "$ref" : "#/definitions/RequestInspection" + } + }, + "required" : [ "LoginPath" ] + }, + "RequestInspection" : { + "description" : "Configures the inspection of login requests", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "UsernameField" : { + "$ref" : "#/definitions/FieldIdentifier" + }, + "PasswordField" : { + "$ref" : "#/definitions/FieldIdentifier" + }, + "PayloadType" : { + "type" : "string", + "enum" : [ "JSON", "FORM_ENCODED" ] + } + }, + "required" : [ "PayloadType", "UsernameField", "PasswordField" ] + }, + "JsonBody" : { + "description" : "Inspect the request body as JSON. The request body immediately follows the request headers.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "MatchScope" : { + "$ref" : "#/definitions/JsonMatchScope" + }, + "MatchPattern" : { + "$ref" : "#/definitions/JsonMatchPattern" + }, + "InvalidFallbackBehavior" : { + "$ref" : "#/definitions/BodyParsingFallbackBehavior" + }, + "OversizeHandling" : { + "$ref" : "#/definitions/OversizeHandling" + } + }, + "required" : [ "MatchPattern", "MatchScope" ] + }, + "RateLimit" : { + "maximum" : 2000000000, + "type" : "integer", + "minimum" : 10 + }, + "OrStatement" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Statements" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Statement" + } + } + }, + "required" : [ "Statements" ] + }, + "Body" : { + "description" : "The body of a web request. This immediately follows the request headers.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "OversizeHandling" : { + "$ref" : "#/definitions/OversizeHandling" + } + } + }, + "CustomHTTPHeader" : { + "description" : "HTTP header.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Value" : { + "$ref" : "#/definitions/CustomHTTPHeaderValue" + }, + "Name" : { + "$ref" : "#/definitions/CustomHTTPHeaderName" + } + }, + "required" : [ "Name", "Value" ] + }, + "RateLimitQueryString" : { + "description" : "Specifies the request's query string as an aggregate key for a rate-based rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TextTransformations" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/TextTransformation" + } + } + }, + "required" : [ "TextTransformations" ] + }, + "QueryString" : { + "type" : "object" + }, + "ResponseContentType" : { + "description" : "Valid values are TEXT_PLAIN, TEXT_HTML, and APPLICATION_JSON.", + "type" : "string", + "enum" : [ "TEXT_PLAIN", "TEXT_HTML", "APPLICATION_JSON" ] + }, + "TextTransformation" : { + "description" : "Text Transformation on the Search String before match.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Type" : { + "$ref" : "#/definitions/TextTransformationType" + }, + "Priority" : { + "$ref" : "#/definitions/TextTransformationPriority" + } + }, + "required" : [ "Priority", "Type" ] + }, + "EntityId" : { + "pattern" : "^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$", + "description" : "Id of the WebACL", + "type" : "string" + }, + "SensitivityLevel" : { + "description" : "Sensitivity Level current only used for sqli match statements.", + "type" : "string", + "enum" : [ "LOW", "HIGH" ] + }, + "Scope" : { + "description" : "Use CLOUDFRONT for CloudFront WebACL, use REGIONAL for Application Load Balancer and API Gateway.", + "type" : "string", + "enum" : [ "CLOUDFRONT", "REGIONAL" ] + }, + "CustomResponseBody" : { + "description" : "Custom response body.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ContentType" : { + "$ref" : "#/definitions/ResponseContentType" + }, + "Content" : { + "$ref" : "#/definitions/ResponseContent" + } + }, + "required" : [ "ContentType", "Content" ] + }, + "ForwardedIPConfiguration" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "FallbackBehavior" : { + "type" : "string", + "enum" : [ "MATCH", "NO_MATCH" ] + }, + "HeaderName" : { + "pattern" : "^[a-zA-Z0-9-]+{1,255}$", + "type" : "string" + } + }, + "required" : [ "HeaderName", "FallbackBehavior" ] + }, + "CaptchaConfig" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ImmunityTimeProperty" : { + "$ref" : "#/definitions/ImmunityTimeProperty" + } + } + }, + "SingleHeader" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Name" : { + "type" : "string" + } + } + }, + "MapMatchScope" : { + "description" : "The parts of the request to match against using the MatchPattern.", + "type" : "string", + "enum" : [ "ALL", "KEY", "VALUE" ] + } + }, + "required" : [ "DefaultAction", "Scope", "VisibilityConfig" ], + "properties" : { + "Description" : { + "$ref" : "#/definitions/EntityDescription" + }, + "AssociationConfig" : { + "$ref" : "#/definitions/AssociationConfig" + }, + "ChallengeConfig" : { + "$ref" : "#/definitions/ChallengeConfig" + }, + "DataProtectionConfig" : { + "description" : "Collection of dataProtects.", + "$ref" : "#/definitions/DataProtectionConfig" + }, + "Rules" : { + "description" : "Collection of Rules.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Rule" + } + }, + "VisibilityConfig" : { + "$ref" : "#/definitions/VisibilityConfig" + }, + "LabelNamespace" : { + "$ref" : "#/definitions/LabelName" + }, + "Name" : { + "$ref" : "#/definitions/EntityName" + }, + "TokenDomains" : { + "$ref" : "#/definitions/TokenDomains" + }, + "DefaultAction" : { + "$ref" : "#/definitions/DefaultAction" + }, + "Scope" : { + "$ref" : "#/definitions/Scope" + }, + "Capacity" : { + "type" : "integer", + "minimum" : 0 + }, + "CustomResponseBodies" : { + "$ref" : "#/definitions/CustomResponseBodies" + }, + "Id" : { + "$ref" : "#/definitions/EntityId" + }, + "Arn" : { + "$ref" : "#/definitions/ResourceArn" + }, + "CaptchaConfig" : { + "$ref" : "#/definitions/CaptchaConfig" + }, + "Tags" : { + "minItems" : 1, + "type" : "array", + "items" : { + "$ref" : "#/definitions/Tag" + } + } + } +} \ No newline at end of file diff --git a/tst/unit/schema/transformers/RemoveMutuallyExclusivePropertiesTransformer.test.ts b/tst/unit/schema/transformers/RemoveMutuallyExclusivePropertiesTransformer.test.ts index a09353a0..a1ff5bae 100644 --- a/tst/unit/schema/transformers/RemoveMutuallyExclusivePropertiesTransformer.test.ts +++ b/tst/unit/schema/transformers/RemoveMutuallyExclusivePropertiesTransformer.test.ts @@ -84,4 +84,117 @@ describe('RemoveMutuallyExclusivePropertiesTransformer', () => { SubnetId: 'subnet-12345678', }); }); + + it('should remove mutually exclusive properties from nested WebsiteConfiguration in AWS::S3::Bucket', () => { + const schema = schemas.schemas.get('AWS::S3::Bucket')!; + const resourceProperties = { + BucketName: 'test-bucket', + WebsiteConfiguration: { + RedirectAllRequestsTo: { + HostName: 'example.com', + Protocol: 'https', + }, + IndexDocument: 'index.html', + ErrorDocument: 'error.html', + }, + }; + + transformer.transform(resourceProperties, schema); + + // RedirectAllRequestsTo excludes ErrorDocument, IndexDocument, RoutingRules + // First encountered wins, so RedirectAllRequestsTo is kept + const websiteConfig = resourceProperties.WebsiteConfiguration as any; + expect(websiteConfig.RedirectAllRequestsTo).toBeDefined(); + expect(websiteConfig.IndexDocument).toBeUndefined(); + expect(websiteConfig.ErrorDocument).toBeUndefined(); + }); + + it('should remove mutually exclusive properties from nested Rule in AWS::S3::Bucket LifecycleConfiguration', () => { + const schema = schemas.schemas.get('AWS::S3::Bucket')!; + const resourceProperties = { + BucketName: 'test-bucket', + LifecycleConfiguration: { + Rules: [ + { + Id: 'TestRule', + Status: 'Enabled', + ObjectSizeLessThan: '1000', + AbortIncompleteMultipartUpload: { + DaysAfterInitiation: 7, + }, + }, + ], + }, + }; + + transformer.transform(resourceProperties, schema); + + // ObjectSizeLessThan excludes AbortIncompleteMultipartUpload + const rule = (resourceProperties.LifecycleConfiguration as any).Rules[0]; + expect(rule.ObjectSizeLessThan).toBe('1000'); + expect(rule.AbortIncompleteMultipartUpload).toBeUndefined(); + }); + + it('should remove mutually exclusive properties from nested NetworkInterface in AWS::EC2::Instance', () => { + const schema = schemas.schemas.get('AWS::EC2::Instance')!; + const resourceProperties = { + ImageId: 'ami-12345678', + InstanceType: 't2.micro', + NetworkInterfaces: [ + { + DeviceIndex: 0, + AssociatePublicIpAddress: true, + NetworkInterfaceId: 'eni-12345678', + }, + ], + }; + + transformer.transform(resourceProperties, schema); + + // AssociatePublicIpAddress excludes NetworkInterfaceId + const networkInterface = (resourceProperties.NetworkInterfaces as any[])[0]; + expect(networkInterface.AssociatePublicIpAddress).toBe(true); + expect(networkInterface.NetworkInterfaceId).toBeUndefined(); + }); + + it('should remove mutually exclusive properties from nested ByteMatchStatement in AWS::WAFv2::WebACL', () => { + const schema = schemas.schemas.get('AWS::WAFv2::WebACL')!; + const resourceProperties = { + Name: 'TestWebACL', + Scope: 'REGIONAL', + DefaultAction: { Allow: {} }, + VisibilityConfig: { + CloudWatchMetricsEnabled: true, + MetricName: 'TestMetric', + SampledRequestsEnabled: true, + }, + Rules: [ + { + Name: 'TestRule', + Priority: 1, + Statement: { + ByteMatchStatement: { + FieldToMatch: { UriPath: {} }, + PositionalConstraint: 'CONTAINS', + SearchString: 'test', + SearchStringBase64: 'dGVzdA==', + TextTransformations: [{ Priority: 0, Type: 'NONE' }], + }, + }, + VisibilityConfig: { + CloudWatchMetricsEnabled: true, + MetricName: 'TestRuleMetric', + SampledRequestsEnabled: true, + }, + }, + ], + }; + + transformer.transform(resourceProperties, schema); + + // SearchString excludes SearchStringBase64 (first encountered wins) + const byteMatchStatement = (resourceProperties.Rules as any[])[0].Statement.ByteMatchStatement; + expect(byteMatchStatement.SearchString).toBe('test'); + expect(byteMatchStatement.SearchStringBase64).toBeUndefined(); + }); }); diff --git a/tst/utils/SchemaUtils.ts b/tst/utils/SchemaUtils.ts index 6d9111be..96009d8d 100644 --- a/tst/utils/SchemaUtils.ts +++ b/tst/utils/SchemaUtils.ts @@ -131,6 +131,12 @@ export const Schemas = { return loadSchema('aws-synthetics-canary.json'); }, }, + WAFv2WebACL: { + fileName: 'file://aws-wafv2-webacl.json', + get contents() { + return loadSchema('aws-wafv2-webacl.json'); + }, + }, }; export const SamSchemaFiles = {