@collaborne/custom-cloudformation-resources
Version:
Custom CloudFormation resources
180 lines (151 loc) • 4.04 kB
text/typescript
import { WAFV2 } from 'aws-sdk';
import { SchemaType } from '@collaborne/json-schema-to-type';
import { CustomResource, Response } from '../custom-resource';
import { Logger } from '../logger';
import { Rules } from 'aws-sdk/clients/wafv2';
const SCHEMA = {
type: 'object' as const,
properties: {
Name: {
type: 'string' as const,
},
Description: {
type: 'string' as const,
},
Capacity: {
type: 'number' as const,
},
Rules: {
type: 'string' as const,
},
},
required: ['Name', 'Capacity'],
};
const SCOPE = 'CLOUDFRONT';
interface ResourceAttributes {
Arn: string;
Id: string;
}
export class CloudFrontWafV2RuleGroup extends CustomResource<
ResourceAttributes,
typeof SCHEMA
> {
region = 'us-east-1'; // Region must be `us-east-1` for `CloudFront WAF`
private wafV2 = new WAFV2({
region: this.region,
});
constructor(logicalResourceId: string, logger: Logger) {
super(SCHEMA, logicalResourceId, logger);
}
public async createResource(
physicalResourceId: string,
{ Name, Description, Capacity, Rules }: SchemaType<typeof SCHEMA>,
): Promise<Response<ResourceAttributes>> {
const rules: Rules = JSON.parse(Rules);
const ruleGroupParams = {
Name,
Description,
Scope: SCOPE,
Capacity,
Rules: rules,
VisibilityConfig: {
SampledRequestsEnabled: true,
CloudWatchMetricsEnabled: true,
MetricName: 'CommonAttackRuleGroup',
},
};
try {
const ruleGroup = await this.wafV2
.createRuleGroup(ruleGroupParams)
.promise();
if (!ruleGroup || !ruleGroup.Summary?.ARN || !ruleGroup.Summary.Id) {
throw new Error('Error creating Rule Group');
}
console.debug('Rule Group created successfully:', ruleGroup.Summary?.ARN);
return {
physicalResourceId,
attributes: {
Arn: ruleGroup.Summary?.ARN,
Id: ruleGroup.Summary?.Id,
},
};
} catch (err) {
console.error('Error creating Rule Group:', err);
throw new Error('Error creating Rule Group');
}
}
public async deleteResource(
physicalResourceId: string,
{ Name }: SchemaType<typeof SCHEMA>,
): Promise<Response<ResourceAttributes>> {
const ruleGroup = await this.getRuleGroup(Name);
if (!ruleGroup.RuleGroup || !ruleGroup.LockToken) {
throw new Error(`Rule group is not deletable ${Name}`);
}
await this.wafV2
.deleteRuleGroup({
Name,
Scope: SCOPE,
Id: ruleGroup.RuleGroup?.Id,
LockToken: ruleGroup.LockToken,
})
.promise();
return {
physicalResourceId,
};
}
public async updateResource(
physicalResourceId: string,
{ Name, Description, Rules }: SchemaType<typeof SCHEMA>,
): Promise<Response<ResourceAttributes>> {
const ruleGroup = await this.getRuleGroup(Name);
if (!ruleGroup.RuleGroup || !ruleGroup.LockToken) {
throw new Error(`Rule group is not updatable ${Name}`);
}
const rules: Rules = JSON.parse(Rules);
const updateRuleGroupParams = {
Id: ruleGroup.RuleGroup.Id,
Name,
Description,
Scope: SCOPE,
Rules: rules,
VisibilityConfig: ruleGroup.RuleGroup.VisibilityConfig,
LockToken: ruleGroup.LockToken,
};
await this.wafV2.updateRuleGroup(updateRuleGroupParams).promise();
return {
physicalResourceId,
attributes: {
Arn: ruleGroup.RuleGroup.ARN,
Id: ruleGroup.RuleGroup.Id,
},
};
}
protected async getRuleGroup(
name: string,
): Promise<WAFV2.GetRuleGroupResponse> {
const ruleGroups = await this.wafV2
.listRuleGroups({ Scope: SCOPE })
.promise();
if (!ruleGroups.RuleGroups || ruleGroups.RuleGroups.length < 1) {
throw new Error(
`There is no rule groups in your account with scope ${SCOPE}`,
);
}
const ruleGroupSummary = ruleGroups.RuleGroups.find(
ruleGroup => ruleGroup.Name === name,
);
if (!ruleGroupSummary) {
throw new Error(`Unknown rule group name ${name}`);
}
const ruleGroup = await this.wafV2
.getRuleGroup({
Name: name,
Id: ruleGroupSummary.Id,
ARN: ruleGroupSummary.ARN,
Scope: SCOPE,
})
.promise();
return ruleGroup;
}
}