WafFactory.java
package com.cloudforgeci.api.observability;
import com.cloudforgeci.api.core.annotation.BaseFactory;
import com.cloudforgeci.api.core.rules.AwsConfigRule;
import com.cloudforge.core.annotation.SystemContext;
import com.cloudforge.core.enums.SecurityProfile;
import com.cloudforge.core.interfaces.ApplicationSpec;
import software.amazon.awscdk.RemovalPolicy;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.services.iam.PolicyStatement;
import software.amazon.awscdk.services.iam.ServicePrincipal;
import software.amazon.awscdk.services.iam.Effect;
import software.amazon.awscdk.services.kms.Key;
import software.amazon.awscdk.services.logs.LogGroup;
import software.amazon.awscdk.services.wafv2.CfnWebACL;
import software.amazon.awscdk.services.wafv2.CfnWebACLAssociation;
import software.amazon.awscdk.services.wafv2.CfnLoggingConfiguration;
import software.constructs.Construct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* Factory for creating AWS WAF WebACL resources.
* Creates Web Application Firewall protection for Application Load Balancers.
*
* <p><strong>Compliance Coverage:</strong></p>
* <ul>
* <li>SOC2-CC6.6-WAF: Web Application Firewall for boundary protection</li>
* <li>PCI-DSS Req 6.6: Web application security (WAF or code review)</li>
* <li>HIPAA ยง164.312(e)(1): Transmission security controls</li>
* <li>GDPR Art. 32: Security of processing (protection against attacks)</li>
* </ul>
*
* <p><strong>Managed Rule Groups:</strong></p>
* <ul>
* <li>AWSManagedRulesKnownBadInputsRuleSet: Protection against known malicious inputs</li>
* <li>AWSManagedRulesSQLiRuleSet: SQL injection protection</li>
* <li>AWSManagedRulesLinuxRuleSet: Linux-specific vulnerability protection</li>
* </ul>
*/
public class WafFactory extends BaseFactory {
private static final Logger LOG = Logger.getLogger(WafFactory.class.getName());
@SystemContext("security")
private SecurityProfile security;
@SystemContext("stackName")
private String stackName;
@SystemContext("applicationSpec")
private ApplicationSpec applicationSpec;
public WafFactory(Construct scope, String id) {
super(scope, id);
}
@Override
public void create() {
LOG.info("Creating WAF WebACL for security profile: " + security);
if (config.isWafEnabled()) {
createWafWebAcl();
} else {
LOG.info("WAF disabled for security profile: " + security);
}
LOG.info("WAF resources created successfully for profile: " + security);
}
/**
* Create AWS WAF WebACL with managed rule groups for Jenkins.
*/
private void createWafWebAcl() {
LOG.info("Creating WAF WebACL for web application protection");
// Create list of managed rule groups
List<Object> rules = new ArrayList<>();
int priority = 0;
List<String> knownBadInputsExclusions = new ArrayList<>();
knownBadInputsExclusions.add("Host_localhost_HEADER"); // Jenkins may run on localhost in dev
knownBadInputsExclusions.add("JavaDeserializationRCE_BODY"); // Jenkins serialization data
knownBadInputsExclusions.add("JavaDeserializationRCE_QUERYSTRING"); // Jenkins serialization in URLs
knownBadInputsExclusions.add("JavaDeserializationRCE_HEADER"); // Jenkins remoting headers
knownBadInputsExclusions.add("JavaDeserializationRCE_URIPATH"); // Jenkins remoting endpoints
// AWS Managed Rules - Known Bad Inputs (protects against known malicious inputs)
// Excludes localhost header check for development/testing
rules.add(createManagedRuleGroupStatement(
"AWS-AWSManagedRulesKnownBadInputsRuleSet",
priority++,
"AWS",
"AWSManagedRulesKnownBadInputsRuleSet",
knownBadInputsExclusions
));
// AWS Managed Rules - SQL Injection (protects against SQL injection attacks)
// Exclude SQLi_BODY for Jenkins login - form data can trigger false positives
List<String> sqliExclusions = new ArrayList<>();
sqliExclusions.add("SQLi_BODY"); // Jenkins login forms can trigger this
sqliExclusions.add("SQLi_QUERYARGUMENTS"); // Jenkins query parameters
sqliExclusions.add("SQLi_COOKIE"); // Jenkins session cookies
rules.add(createManagedRuleGroupStatement(
"AWS-AWSManagedRulesSQLiRuleSet",
priority++,
"AWS",
"AWSManagedRulesSQLiRuleSet",
sqliExclusions
));
// AWS Managed Rules - Linux Operating System (protects against Linux-specific vulnerabilities)
// No exclusions - Jenkins shouldn't trigger these rules
rules.add(createManagedRuleGroupStatement(
"AWS-AWSManagedRulesLinuxRuleSet",
priority++,
"AWS",
"AWSManagedRulesLinuxRuleSet",
null
));
// Create WAF WebACL
String appId = applicationSpec != null ? applicationSpec.applicationId() : "app";
CfnWebACL webAcl = CfnWebACL.Builder.create(this, getNode().getId() + "-WebACL")
.scope("REGIONAL") // REGIONAL for ALB (CLOUDFRONT for CloudFront distributions)
.defaultAction(CfnWebACL.DefaultActionProperty.builder()
.allow(CfnWebACL.AllowActionProperty.builder().build()) // Allow by default, block specific threats
.build())
.rules(rules)
.visibilityConfig(CfnWebACL.VisibilityConfigProperty.builder()
.cloudWatchMetricsEnabled(true)
.metricName(appId + "-waf-" + stackName.toLowerCase())
.sampledRequestsEnabled(true)
.build())
.name(appId + "-waf-" + stackName.toLowerCase())
.description("WAF WebACL for " + appId + " ALB - " + security.name())
.build();
ctx.wafWebAcl.set(webAcl);
// Create KMS key for CloudWatch Logs encryption (PCI-DSS Req 3.4)
Key wafLogsKmsKey = Key.Builder.create(this, getNode().getId() + "-WafLogsKmsKey")
.description("KMS key for WAF CloudWatch Logs (PCI-DSS compliance)")
.enableKeyRotation(true)
// Always destroy - WAF logs are for operational monitoring, not long-term audit retention
.removalPolicy(RemovalPolicy.DESTROY)
.build();
// Grant CloudWatch Logs service permission to use the KMS key
// Required for CloudWatch Logs to encrypt log data
wafLogsKmsKey.addToResourcePolicy(PolicyStatement.Builder.create()
.sid("Allow CloudWatch Logs to use the key")
.effect(Effect.ALLOW)
.principals(List.of(new ServicePrincipal("logs." + cfc.region() + ".amazonaws.com")))
.actions(List.of(
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:CreateGrant",
"kms:DescribeKey"
))
.resources(List.of("*"))
.conditions(Map.of(
"ArnLike", Map.of(
"kms:EncryptionContext:aws:logs:arn",
"arn:aws:logs:" + cfc.region() + ":" + Stack.of(this).getAccount() + ":*"
)
))
.build());
// Create CloudWatch Logs group for WAF logging (PCI-DSS Req 10.2)
// WAFv2 requires log group name to start with "aws-waf-logs-"
// Use config.getLogRetentionDays() for compliance-aware retention (HIPAA requires 6 years)
LogGroup wafLogGroup = LogGroup.Builder.create(this, getNode().getId() + "-WafLogs")
.logGroupName("aws-waf-logs-" + appId + "-" + stackName.toLowerCase())
.retention(config.getLogRetentionDays()) // Compliance-aware retention from security profile
// Always destroy - WAF logs are for operational monitoring, not long-term audit retention
.removalPolicy(RemovalPolicy.DESTROY)
.encryptionKey(wafLogsKmsKey) // KMS encryption for PCI-DSS
.build();
// Enable WAF logging to CloudWatch Logs
CfnLoggingConfiguration.Builder.create(this, getNode().getId() + "-WafLogging")
.resourceArn(webAcl.getAttrArn())
.logDestinationConfigs(List.of(wafLogGroup.getLogGroupArn()))
.build();
LOG.info("WAF logging enabled to CloudWatch Logs: " + wafLogGroup.getLogGroupName());
// Associate WAF WebACL with ALB
// The ALB must be available before WafFactory.create() is called
// This is ensured by SecurityRules calling this factory AFTER infrastructure creation
if (!ctx.alb.get().isPresent()) {
throw new IllegalStateException("ALB must be created before WafFactory - check factory orchestration order");
}
String albArn = ctx.alb.get().orElseThrow().getLoadBalancerArn();
CfnWebACLAssociation.Builder.create(this, getNode().getId() + "-Association")
.resourceArn(albArn)
.webAclArn(webAcl.getAttrArn())
.build();
// Register AWS Config rules for WAF compliance monitoring
ctx.requireConfigRule(AwsConfigRule.ALB_WAF_ENABLED);
ctx.requireConfigRule(AwsConfigRule.WAFV2_LOGGING_ENABLED);
LOG.info("WAF WebACL created: " + webAcl.getAttrArn());
LOG.info("WAF WebACL associated with ALB: " + albArn);
}
/**
* Create a managed rule group statement for WAF with optional rule exclusions.
*
* @param name The name of the rule
* @param priority The priority of the rule
* @param vendorName The vendor name (e.g., "AWS")
* @param ruleGroupName The name of the managed rule group
* @param excludedRules List of specific rule names to exclude from this rule group
*/
private Map<String, Object> createManagedRuleGroupStatement(
String name,
int priority,
String vendorName,
String ruleGroupName,
List<String> excludedRules
) {
Map<String, Object> rule = new HashMap<>();
rule.put("name", name);
rule.put("priority", priority);
Map<String, Object> statement = new HashMap<>();
Map<String, Object> managedRuleGroup = new HashMap<>();
managedRuleGroup.put("vendorName", vendorName);
managedRuleGroup.put("name", ruleGroupName);
// Add excluded rules if provided
if (excludedRules != null && !excludedRules.isEmpty()) {
List<Map<String, String>> excludedRulesList = new ArrayList<>();
for (String ruleName : excludedRules) {
Map<String, String> excludedRule = new HashMap<>();
excludedRule.put("name", ruleName);
excludedRulesList.add(excludedRule);
}
managedRuleGroup.put("excludedRules", excludedRulesList);
}
statement.put("managedRuleGroupStatement", managedRuleGroup);
rule.put("statement", statement);
// Use overrideAction: none to allow managed rules to BLOCK threats
// The excludedRules parameter above ensures Jenkins-specific false positives are excluded
// This provides actual security protection while preventing 403s on legitimate Jenkins traffic
Map<String, Object> overrideAction = new HashMap<>();
overrideAction.put("none", new HashMap<>()); // BLOCK mode: enforce all rules except exclusions
rule.put("overrideAction", overrideAction);
Map<String, Object> visibilityConfig = new HashMap<>();
visibilityConfig.put("cloudWatchMetricsEnabled", true);
visibilityConfig.put("metricName", name);
visibilityConfig.put("sampledRequestsEnabled", true);
rule.put("visibilityConfig", visibilityConfig);
return rule;
}
}