aws-cloudformation / aws-cloudformation/cloudformation-coverage-roadmap
[AWS::WAFv2::WebACL] - [BUG] - GetResource returns mutually exclusive SearchString properties, causing UpdateResource to fail
- Dominant language
- No language data
- Stars
- 1.1k
- Forks
- 62
- PR merge metrics
- No merged PRs in 30d
Description
### Name of the resource
AWS::WAFv2::WebACL
### Resource Name
_No response_
### Issue Description
AWS Cloud Control returns both SearchString and SearchStringBase64 for a WAFv2 ByteMatchStatement created with only one of those properties.
For example, when the resource is created with only:
```json
{
"SearchString": "/login"
}
```
GetResource returns:
```json
{
"SearchString": "/login",
"SearchStringBase64": "L2xvZ2lu"
}
```
The same happens in the other direction: when the resource is created with only:
```json
{
"SearchStringBase64": "L2xvZ2lu"
}
```
GetResource still returns both properties.
These properties are mutually exclusive for WAF updates: exactly one of SearchString and SearchStringBase64 may be specified.
This causes unrelated Cloud Control updates to fail. Cloud Control documents that UpdateResource retrieves the current resource state, combines that state with the caller's JSON Patch operations, and then passes the resulting desired state to the resource update handler.
Consequently, an update containing only a patch for an unrelated property such as /TokenDomains retains both search-string properties from the current state. The WAFv2 update handler then rejects the complete desired model:
```text
You must only specify exactly one of SearchString and SearchStringBase64
```
Neither search-string property was included in the update patch. The additional alias was introduced by the Cloud Control read model.
This was reproduced directly using AWS Cloud Control CLI operations in us-west-2. No third-party infrastructure provider was involved.
### Expected Behavior
A resource model returned by GetResource should be valid when Cloud Control uses it as the current state for a subsequent UpdateResource operation.
For ByteMatchStatement, Cloud Control should either:
1. Return only one canonical representation, such as SearchStringBase64; or
2. Normalize equivalent SearchString and SearchStringBase64 values before invoking the WAFv2 update handler.
An unrelated update, such as changing TokenDomains, should succeed without requiring the caller to resend or rewrite the complete Rules property.
Creating the resource with only SearchStringBase64 should also not cause GetResource to introduce SearchString.
### Observed Behavior
A WebACL created with only:
```json
{
"SearchString": "/login"
}
```
is returned by GetResource with:
```json
{
"SearchStringBase64": "L2xvZ2lu",
"TextTransformations": [
{
"Type": "NONE",
"Priority": 0
}
],
"PositionalConstraint": "CONTAINS",
"SearchString": "/login",
"FieldToMatch": {
"UriPath": {}
}
}
```
An UpdateResource request containing only:
```json
[
{
"op": "replace",
"path": "/TokenDomains",
"value": [
"example.com",
"login.example.com"
]
}
]
```
produces a Cloud Control ResourceModel that still contains both aliases:
```json
{
"ByteMatchStatement": {
"SearchString": "/login",
"SearchStringBase64": "L2xvZ2lu"
}
}
```
The operation then fails:
```json
{
"Operation": "UPDATE",
"OperationStatus": "FAILED",
"StatusMessage": "You must only specify exactly one of SearchString and SearchStringBase64",
"ErrorCode": "GeneralServiceException"
}
```
The same behavior was reproduced when the WebACL was created with only SearchStringBase64.
### Test Cases
### 1. Create a WebACL with only SearchString
Create webacl.json:
```json
{
"Name": "cloudcontrol-byte-match-repro",
"Description": "CloudControl SearchString alias reproduction",
"Scope": "REGIONAL",
"DefaultAction": {
"Allow": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "cloudcontrol-byte-match-repro"
},
"Rules": [
{
"Name": "byte-match",
"Priority": 0,
"Action": {
"Count": {}
},
"Statement": {
"ByteMatchStatement": {
"SearchString": "/login",
"FieldToMatch": {
"UriPath": {}
},
"TextTransformations": [
{
"Priority": 0,
"Type": "NONE"
}
],
"PositionalConstraint": "CONTAINS"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "byte-match"
}
}
],
"TokenDomains": [
"example.com"
]
}
```
Create the resource:
```bash
CREATE_TOKEN=$(
aws cloudcontrol create-resource \
--region us-west-2 \
--type-name AWS::WAFv2::WebACL \
--desired-state file://webacl.json \
--query 'ProgressEvent.RequestToken' \
--output text
)
```
Poll until creation succeeds:
```bash
aws cloudcontrol get-resource-request-status \
--region us-west-2 \
--request-token "$CREATE_TOKEN"
```
Get the resource identifier:
```bash
IDENTIFIER=$(
aws cloudcontrol get-resource-request-status \
--region us-west-2 \
--request-token "$CREATE_TOKEN" \
--query 'ProgressEvent.Identifier' \
--output text
)
```
Read the Cloud Control model:
```bash
aws cloudcontrol get-resource \
--region us-west-2 \
--type-name AWS::WAFv2::WebACL \
--identifier "$IDENTIFIER" \
--query 'ResourceDescription.Properties' \
--output text | jq .
```
Although creation specified only SearchString, the returned ByteMatchStatement contains both:
```json
{
"SearchString": "/login",
"SearchStringBase64": "L2xvZ2lu"
}
```
### 2. Submit an unrelated update
Submit a patch that changes only TokenDomains:
```bash
UPDATE_TOKEN=$(
aws cloudcontrol update-resource \
--region us-west-2 \
--type-name AWS::WAFv2::WebACL \
--identifier "$IDENTIFIER" \
--patch-document '[
{
"op": "replace",
"path": "/TokenDomains",
"value": [
"example.com",
"login.example.com"
]
}
]' \
--query 'ProgressEvent.RequestToken' \
--output text
)
```
Poll the update:
```bash
aws cloudcontrol get-resource-request-status \
--region us-west-2 \
--request-token "$UPDATE_TOKEN"
```
Observed result:
```json
{
"Operation": "UPDATE",
"OperationStatus": "FAILED",
"StatusMessage": "You must only specify exactly one of SearchString and SearchStringBase64",
"ErrorCode": "GeneralServiceException"
}
```
### 3. Repeat using only SearchStringBase64
Replace:
```json
"SearchString": "/login"
```
with:
```json
"SearchStringBase64": "L2xvZ2lu"
```
Create another WebACL and call GetResource.
Observed result: Cloud Control again returns both:
```json
{
"SearchString": "/login",
"SearchStringBase64": "L2xvZ2lu"
}
```
An unrelated /TokenDomains update fails with the same GeneralServiceException.
### Other Details
Relevant documentation and reports:
- Cloud Control update behavior:
https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/resource-operations-update.html
- WAFv2 WebACL ByteMatchStatement documentation:
https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-wafv2-webacl-bytematchstatement.html
- External report that initially exposed the update failure:
https://github.com/pulumi/pulumi-aws-native/issues/3078
- Historical report showing both aliases returned during import:
https://github.com/pulumi/pulumi-aws-native/issues/725
The published AWS::WAFv2::WebACL resource schema currently declares both properties as optional strings and does not encode their mutual-exclusion relationship. Adding a machine-readable
constraint would be useful, but the primary issue is that the read handler emits both properties and the update handler rejects that same model.
The CloudFormation language server already treats these properties as mutually exclusive through explicit validation metadata:
https://github.com/aws-cloudformation/cloudformation-languageserver/blob/98fecc2e8da480caa7e07f39d590bbc14b25f63f/src/schema/transformers/MutuallyExclusivePropertiesForValidation.ts
Contributor guide
Research direction
Start with the AWS Cloud Control CLI reproduction using create-resource, get-resource, and update-resource in us-west-2, then read the linked Cloud Control update and WAFv2 ByteMatchStatement documentation. Done means GetResource preserves a single mutually exclusive search-string representation and an unrelated TokenDomains patch succeeds for either creation form.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws
- Domain
- cloud, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100