Skip to content

Commit

Permalink
Add WAF rules to protect Jenkins endpoint (opensearch-project#404)
Browse files Browse the repository at this point in the history
Signed-off-by: Sayali Gaikawad <[email protected]>
  • Loading branch information
gaiksaya authored Mar 20, 2024
1 parent 766302e commit cee156e
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lib/ci-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { RunAdditionalCommands } from './compute/run-additional-commands';
import { JenkinsMonitoring } from './monitoring/ci-alarms';
import { JenkinsExternalLoadBalancer } from './network/ci-external-load-balancer';
import { JenkinsSecurityGroups } from './security/ci-security-groups';
import { JenkinsWAF } from './security/waf';

export interface CIStackProps extends StackProps {
/** Should the Jenkins use https */
Expand Down Expand Up @@ -202,6 +203,10 @@ export class CIStack extends Stack {
accessLogBucket: auditloggingS3Bucket.bucket,
});

const waf = new JenkinsWAF(this, {
loadBalancer: externalLoadBalancer.loadBalancer,
});

const artifactBucket = new Bucket(this, 'BuildBucket');

this.monitoring = new JenkinsMonitoring(this, externalLoadBalancer, mainJenkinsNode);
Expand Down
120 changes: 120 additions & 0 deletions lib/security/waf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Stack, StackProps } from 'aws-cdk-lib';
import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { CfnWebACL, CfnWebACLAssociation, CfnWebACLAssociationProps } from 'aws-cdk-lib/aws-wafv2';
import { Construct } from 'constructs';

interface WafRule {
name: string;
rule: CfnWebACL.RuleProperty;
}

const awsManagedRules: WafRule[] = [
// AWS IP Reputation list includes known malicious actors/bots and is regularly updated
{
name: 'AWS-AWSManagedRulesAmazonIpReputationList',
rule: {
name: 'AWS-AWSManagedRulesAmazonIpReputationList',
priority: 0,
statement: {
managedRuleGroupStatement: {
vendorName: 'AWS',
name: 'AWSManagedRulesAmazonIpReputationList',
},
},
overrideAction: {
none: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'AWSManagedRulesAmazonIpReputationList',
},
},
},
// Blocks common SQL Injection
{
name: 'AWS-AWSManagedRulesSQLiRuleSet',
rule: {
name: 'AWS-AWSManagedRulesSQLiRuleSet',
priority: 1,
statement: {
managedRuleGroupStatement: {
vendorName: 'AWS',
name: 'AWSManagedRulesSQLiRuleSet',
excludedRules: [],
},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'AWS-AWSManagedRulesSQLiRuleSet',
},
overrideAction: {
none: {},
},
},
},
// Block request patterns associated with the exploitation of vulnerabilities specific to WordPress sites.
{
name: 'AWS-AWSManagedRulesWordPressRuleSet',
rule: {
name: 'AWS-AWSManagedRulesWordPressRuleSet',
priority: 2,
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'AWS-AWSManagedRulesWordPressRuleSet',
},
overrideAction: {
none: {},
},
statement: {
managedRuleGroupStatement: {
vendorName: 'AWS',
name: 'AWSManagedRulesWordPressRuleSet',
excludedRules: [],
},
},
},
},
];

export class WAF extends CfnWebACL {
constructor(scope: Construct, id: string) {
super(scope, id, {
defaultAction: { allow: {} },
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'jenkins-WAF',
sampledRequestsEnabled: true,
},
scope: 'REGIONAL',
name: 'jenkins-WAF',
rules: awsManagedRules.map((wafRule) => wafRule.rule),
});
}
}

export class WebACLAssociation extends CfnWebACLAssociation {
constructor(scope: Construct, id: string, props: CfnWebACLAssociationProps) {
super(scope, id, {
resourceArn: props.resourceArn,
webAclArn: props.webAclArn,
});
}
}

export interface WafProps extends StackProps{
loadBalancer: ApplicationLoadBalancer
}

export class JenkinsWAF {
constructor(stack: Stack, props: WafProps) {
const waf = new WAF(stack, 'WAFv2');
// Create an association with the alb
new WebACLAssociation(stack, 'wafALBassociation', {
resourceArn: props.loadBalancer.loadBalancerArn,
webAclArn: waf.attrArn,
});
}
}
111 changes: 111 additions & 0 deletions test/ci-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,114 @@ test('LoadBalancer Access Logging', () => {
},
});
});

test('WAF rules', () => {
const app = new App({
context: {
useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
},
});

// WHEN
const stack = new CIStack(app, 'MyTestStack', {
env: { account: 'test-account', region: 'us-east-1' },
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::WAFv2::WebACL', {
DefaultAction: {
Allow: {},
},
Scope: 'REGIONAL',
VisibilityConfig: {
CloudWatchMetricsEnabled: true,
MetricName: 'jenkins-WAF',
SampledRequestsEnabled: true,
},
Name: 'jenkins-WAF',
Rules: [
{
Name: 'AWS-AWSManagedRulesAmazonIpReputationList',
OverrideAction: {
None: {},
},
Priority: 0,
Statement: {
ManagedRuleGroupStatement: {
Name: 'AWSManagedRulesAmazonIpReputationList',
VendorName: 'AWS',
},
},
VisibilityConfig: {
CloudWatchMetricsEnabled: true,
MetricName: 'AWSManagedRulesAmazonIpReputationList',
SampledRequestsEnabled: true,
},
},
{
Name: 'AWS-AWSManagedRulesSQLiRuleSet',
OverrideAction: {
None: {},
},
Priority: 1,
Statement: {
ManagedRuleGroupStatement: {
ExcludedRules: [],
Name: 'AWSManagedRulesSQLiRuleSet',
VendorName: 'AWS',
},
},
VisibilityConfig: {
CloudWatchMetricsEnabled: true,
MetricName: 'AWS-AWSManagedRulesSQLiRuleSet',
SampledRequestsEnabled: true,
},
},
{
Name: 'AWS-AWSManagedRulesWordPressRuleSet',
OverrideAction: {
None: {},
},
Priority: 2,
Statement: {
ManagedRuleGroupStatement: {
ExcludedRules: [],
Name: 'AWSManagedRulesWordPressRuleSet',
VendorName: 'AWS',
},
},
VisibilityConfig: {
CloudWatchMetricsEnabled: true,
MetricName: 'AWS-AWSManagedRulesWordPressRuleSet',
SampledRequestsEnabled: true,
},
},
],
});
});

test('Test WAF association with ALB', () => {
const app = new App({
context: {
useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
},
});

// WHEN
const stack = new CIStack(app, 'MyTestStack', {
env: { account: 'test-account', region: 'us-east-1' },
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::WAFv2::WebACLAssociation', {
ResourceArn: {
Ref: 'JenkinsALB9F3D5428',
},
WebACLArn: {
'Fn::GetAtt': [
'WAFv2',
'Arn',
],
},
});
});

0 comments on commit cee156e

Please sign in to comment.