Skip to content

Commit

Permalink
feat(WAF): support associating WAF without creating (#504)
Browse files Browse the repository at this point in the history
Co-authored-by: bboure <[email protected]>
  • Loading branch information
rikhilrai and bboure authored Mar 7, 2023
1 parent a57e18c commit 69a110a
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 18 deletions.
17 changes: 15 additions & 2 deletions doc/WAF.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ You can configure WAF rules under the `appSync.waf` attribute.

## Quick start

You can define a collection of rules for your web ACL and associate it:

```yaml
appSync:
name: my-api
Expand All @@ -19,17 +21,28 @@ appSync:
- disableIntrospection
```
Or directly associate an existing web ACL:
```yaml
appSync:
name: my-api
waf:
enabled: true
arn: 'arn:aws:wafv2:us-east-1:123456789012:regional/webacl/my-Waf/d7b694d2-4f7d-4dd6-a9a9-843dd1931330'
```
## Configuration
- `enabled`: Boolean. Enable or disable WAF. Defaults to `true` when `appSync.waf` is defined.
- `arn`: Optional. The WAF's ARN to associate with your AppSync resource.
- `name`: Optional. The name of this WAF instance. Defaults to the name of your API.
- `defaultAction`: Optional. The default action if a request does not match a rule. `Allow` or `Block`. Defaults to `Allow`.
- `description`: A description for this WAF instance.
- `description`: Optional. A description for this WAF instance.
- `visibilityConfig`: Optional. A [visibility config](https://docs.aws.amazon.com/waf/latest/APIReference/API_VisibilityConfig.html) for this WAF
- `name`: Metric name
- `cloudWatchMetricsEnabled`: A boolean indicating whether the associated resource sends metrics to Amazon CloudWatch
- `sampledRequestsEnabled`: A boolean indicating whether AWS WAF should store a sampling of the web requests that match the rule
- `rules`: An array of [rules](#rules).
- `rules`: Required. An array of [rules](#rules). Optional when `arn` is present

## Rules

Expand Down
17 changes: 17 additions & 0 deletions src/__tests__/__snapshots__/waf.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,23 @@ Array [
]
`;

exports[`Waf Base Resources should generate only the waf association 1`] = `
Object {
"GraphQlWafAssoc": Object {
"Properties": Object {
"ResourceArn": Object {
"Fn::GetAtt": Array [
"GraphQlApi",
"Arn",
],
},
"WebACLArn": "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/my-Waf/d7b694d2-4f7d-4dd6-a9a9-843dd1931330",
},
"Type": "AWS::WAFv2::WebACLAssociation",
},
}
`;

exports[`Waf Base Resources should generate waf Resources 1`] = `
Object {
"GraphQlWaf": Object {
Expand Down
8 changes: 5 additions & 3 deletions src/__tests__/validation/__snapshots__/base.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ exports[`Valdiation Log Invalid should validate a Invalid 1`] = `
`;

exports[`Valdiation Waf Invalid should validate a Invalid 1`] = `
"/waf/enabled: must be boolean
/waf/name: must be string
"/waf/name: must be string
/waf/defaultAction: must be 'Allow' or 'Block'
/waf/rules/0: must be a valid WAF rule
/waf/rules/1: must be a valid WAF rule
/waf/rules/2: must be a valid WAF rule"
/waf/rules/2: must be a valid WAF rule
/waf/enabled: must be boolean"
`;

exports[`Valdiation Waf Invalid should validate a Invalid arn 1`] = `"/waf/arn: must be a string or a CloudFormation intrinsic function"`;

exports[`Valdiation Waf Invalid should validate a Throttle limit 1`] = `
"/waf/rules/0: must be a valid WAF rule
/waf/rules/1: must be a valid WAF rule"
Expand Down
19 changes: 19 additions & 0 deletions src/__tests__/validation/base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ describe('Valdiation', () => {
},
} as AppSyncConfigInput,
},
{
name: 'Using arn',
config: {
...basicConfig,
waf: {
enabled: true,
arn: 'arn:aws:',
},
},
},
];

assertions.forEach((config) => {
Expand Down Expand Up @@ -187,6 +197,15 @@ describe('Valdiation', () => {
},
},
},
{
name: 'Invalid arn',
config: {
...basicConfig,
waf: {
arn: 123,
},
},
},
{
name: 'Throttle limit',
config: {
Expand Down
9 changes: 9 additions & 0 deletions src/__tests__/waf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ describe('Waf', () => {
);
expect(api.compileWafRules()).toEqual({});
});

it('should generate only the waf association', () => {
const api = new Api(given.appSyncConfig(), plugin);
const waf = new Waf(api, {
enabled: true,
arn: 'arn:aws:wafv2:us-east-1:123456789012:regional/webacl/my-Waf/d7b694d2-4f7d-4dd6-a9a9-843dd1931330',
});
expect(waf.compile()).toMatchSnapshot();
});
});

describe('Throttle rules', () => {
Expand Down
16 changes: 14 additions & 2 deletions src/resources/Waf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,23 @@ export class Waf {
if (wafConfig.enabled === false) {
return {};
}
const apiLogicalId = this.api.naming.getApiLogicalId();
const wafAssocLogicalId = this.api.naming.getWafAssociationLogicalId();

if (wafConfig.arn) {
return {
[wafAssocLogicalId]: {
Type: 'AWS::WAFv2::WebACLAssociation',
Properties: {
ResourceArn: { 'Fn::GetAtt': [apiLogicalId, 'Arn'] },
WebACLArn: wafConfig.arn,
},
},
};
}

const name = wafConfig.name || `${this.api.config.name}Waf`;
const apiLogicalId = this.api.naming.getApiLogicalId();
const wafLogicalId = this.api.naming.getWafLogicalId();
const wafAssocLogicalId = this.api.naming.getWafAssociationLogicalId();
const defaultActionSource = wafConfig.defaultAction || 'Allow';
const defaultAction: CfnWafAction =
typeof defaultActionSource === 'string'
Expand Down
3 changes: 2 additions & 1 deletion src/types/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ export type IamStatement = {

export type WafConfig = {
enabled?: boolean;
arn?: string;
name?: string;
defaultAction?: WafAction;
description?: string;
visibilityConfig?: VisibilityConfig;
rules: WafRule[];
rules?: WafRule[];
};

export type WafThrottleConfig =
Expand Down
32 changes: 22 additions & 10 deletions src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,19 +664,31 @@ export const appSyncSchema = {
type: 'object',
properties: {
enabled: { type: 'boolean' },
name: { type: 'string' },
defaultAction: {
type: 'string',
enum: ['Allow', 'Block'],
errorMessage: "must be 'Allow' or 'Block'",
},
if: {
required: ['arn'],
},
then: {
properties: {
arn: { $ref: '#/definitions/stringOrIntrinsicFunction' },
},
description: { type: 'string' },
rules: {
type: 'array',
items: { $ref: '#/definitions/wafRule' },
},
else: {
properties: {
name: { type: 'string' },
defaultAction: {
type: 'string',
enum: ['Allow', 'Block'],
errorMessage: "must be 'Allow' or 'Block'",
},
description: { type: 'string' },
rules: {
type: 'array',
items: { $ref: '#/definitions/wafRule' },
},
},
required: ['rules'],
},
required: ['rules'],
},
tags: {
type: 'object',
Expand Down

0 comments on commit 69a110a

Please sign in to comment.