Policy-as-Code Enforcement: Integrating CloudFormation Guard with Java CI/CD


Article

In cloud-native Java applications, Infrastructure as Code (IaC) security is as critical as application security. AWS CloudFormation Guard is an open-source policy-as-code tool that validates CloudFormation templates against security, compliance, and operational policies. For Java development teams deploying to AWS, integrating CloudFormation Guard into CI/CD pipelines ensures infrastructure meets security standards before deployment.

What is CloudFormation Guard?

CloudFormation Guard is a policy validation tool that:

  • Defines Policies: Create rules in a simple, domain-specific language
  • Validates Templates: Check CloudFormation templates against policies
  • Integrates with CI/CD: Automate security checks in pipelines
  • Supports Multiple Formats: Works with JSON, YAML, and CloudFormation templates
  • Open Source: Extensible and customizable for organizational needs

Why Java Teams Need CloudFormation Guard

  1. Security Compliance: Ensure infrastructure meets security standards
  2. Cost Control: Prevent deployment of non-compliant resources
  3. Operational Excellence: Enforce tagging, naming conventions, and best practices
  4. Shift-Left Security: Catch issues before they reach production
  5. Audit Trail: Maintain policy validation records for compliance

CloudFormation Guard Architecture for Java CI/CD

Java Application → CloudFormation Templates → CloudFormation Guard → Policy Validation
↓
CI/CD Pipeline → Fail Build if Policy Violations
↓
AWS Deployment → Secure Infrastructure

Setting Up CloudFormation Guard

1. Installation:

# Download CloudFormation Guard
curl -L https://github.com/aws-cloudformation/cloudformation-guard/releases/latest/download/cfn-guard-linux-x86_64.tar.gz -o cfn-guard.tar.gz
tar -xzf cfn-guard.tar.gz
sudo mv cfn-guard /usr/local/bin/
# Verify installation
cfn-guard --version

2. Basic Usage:

# Validate template against rules
cfn-guard validate --template template.yaml --rules rules.guard
# Check rule syntax
cfn-guard rulegen --template template.yaml
# Test rules
cfn-guard test --rules-file rules.guard --test-data test-data.yaml

Policy Rules for Java Applications

1. Security Rules (security.guard):

# security.guard
rule ensure_rds_encryption_enabled {
Resources.*[ Type == /AWS::RDS::DBInstance/ ] {
Properties.StorageEncrypted == true
}
}
rule ensure_s3_bucket_encryption {
Resources.*[ Type == /AWS::S3::Bucket/ ] {
Properties.BucketEncryption.ServerSideEncryptionConfiguration[*] {
ServerSideEncryptionByDefault.SSEAlgorithm == "AES256" or
ServerSideEncryptionByDefault.SSEAlgorithm == "aws:kms"
}
}
}
rule ensure_alb_logging_enabled {
Resources.*[ Type == /AWS::ElasticLoadBalancingV2::LoadBalancer/ ] {
Properties.LoadBalancerAttributes[*] {
Key == "access_logs.s3.enabled"
Value == "true"
}
}
}
rule ensure_ec2_imdsv2 {
Resources.*[ Type == /AWS::EC2::Instance/ ] {
Properties.MetadataOptions.HttpTokens == "required"
}
}
rule ensure_security_groups_no_wide_open {
Resources.*[ Type == /AWS::EC2::SecurityGroup/ ] {
Properties.SecurityGroupIngress[*] {
CidrIp != "0.0.0.0/0" or
CidrIpv6 != "::/0"
}
}
}
rule ensure_cloudtrail_enabled {
Resources.*[ Type == /AWS::CloudTrail::Trail/ ] {
Properties.IsLogging == true
Properties.IsMultiRegionTrail == true
}
}

2. Java Application-Specific Rules (java-apps.guard):

# java-apps.guard
rule ensure_java_app_has_correct_memory {
Resources.*[ 
Type == /AWS::ECS::TaskDefinition/ or 
Type == /AWS::Lambda::Function/
] {
when Properties.Memory exists {
Properties.Memory >= 512
Properties.Memory <= 8192
}
}
}
rule ensure_java_runtime_environment {
Resources.*[ Type == /AWS::Lambda::Function/ ] {
Properties.Runtime in ["java11", "java17", "java21"]
}
}
rule ensure_ecs_java_container_security {
Resources.*[ Type == /AWS::ECS::TaskDefinition/ ] {
Properties.ContainerDefinitions[*] {
Image == /.*java.*/ {
Privileged != true
ReadonlyRootFilesystem == true
}
}
}
}
rule ensure_java_app_proper_logging {
Resources.*[ 
Type == /AWS::ECS::TaskDefinition/ or 
Type == /AWS::Lambda::Function/
] {
Properties.* exists {
some LogConfiguration exists {
LogDriver in ["awslogs", "splunk", "fluentd"]
}
}
}
}
rule ensure_java_app_environment_variables {
Resources.*[ Type == /AWS::ECS::TaskDefinition/ ] {
Properties.ContainerDefinitions[*] {
Image == /.*java.*/ {
Environment[*] {
when Name == "JAVA_TOOL_OPTIONS" {
Value == /.*-Djava.security.egd=file:\/dev\/\.\/urandom.*/
}
}
}
}
}
}

3. Tagging and Compliance Rules (compliance.guard):

# compliance.guard
rule ensure_resource_tagging {
Resources.*[
Type != /AWS::CloudFormation::(Stack|WaitCondition.*)/
] {
Properties.Tags[*] {
Key == "Environment"
Value in ["dev", "staging", "production"]
}
Properties.Tags[*] {
Key == "Application"
Value == /^[a-z-]+$/
}
Properties.Tags[*] {
Key == "Owner"
Value != ""
}
Properties.Tags[*] {
Key == "CostCenter"
Value == /^[0-9]+$/
}
}
}
rule ensure_production_security_standards {
when %input.Tags[[email protected] == "Environment" && @.Value == "production"] !empty {
rule ensure_encryption_everywhere {
Resources.*[ 
Type == /AWS::RDS::DBInstance/ or
Type == /AWS::S3::Bucket/ or
Type == /AWS::EFS::FileSystem/
] {
Properties.StorageEncrypted == true or
Properties.BucketEncryption exists
}
}
rule ensure_deletion_protection {
Resources.*[ Type == /AWS::RDS::DBInstance/ ] {
Properties.DeletionProtection == true
}
}
}
}

Java CI/CD Integration

1. Maven Plugin Configuration:

<!-- pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>validate-cloudformation</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>cfn-guard</executable>
<arguments>
<argument>validate</argument>
<argument>--template</argument>
<argument>${project.basedir}/cloudformation/template.yaml</argument>
<argument>--rules</argument>
<argument>${project.basedir}/cloudformation/rules.guard</argument>
<argument>--output-format</argument>
<argument>json</argument>
<argument>--show-summary</argument>
<argument>fail</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

2. GitHub Actions Workflow:

# .github/workflows/cloudformation-validation.yml
name: CloudFormation Guard Validation
on:
push:
branches: [ main, develop ]
paths:
- 'cloudformation/**'
- 'src/**'
pull_request:
branches: [ main ]
jobs:
validate-cloudformation:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup CloudFormation Guard
run: |
curl -L https://github.com/aws-cloudformation/cloudformation-guard/releases/latest/download/cfn-guard-linux-x86_64.tar.gz -o cfn-guard.tar.gz
tar -xzf cfn-guard.tar.gz
sudo mv cfn-guard /usr/local/bin/
- name: Validate CloudFormation Templates
run: |
for template in cloudformation/*.yaml cloudformation/*.yml; do
if [ -f "$template" ]; then
echo "Validating $template"
cfn-guard validate \
--template "$template" \
--rules cloudformation/rules.guard \
--output-format json \
--show-summary fail
fi
done
- name: Fail on Critical Issues
if: failure()
run: |
echo "Critical CloudFormation policy violations found"
exit 1

3. Jenkins Pipeline Integration:

// Jenkinsfile
pipeline {
agent any
environment {
CFN_GUARD_VERSION = '3.0.0'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install CloudFormation Guard') {
steps {
sh """
curl -L https://github.com/aws-cloudformation/cloudformation-guard/releases/download/${CFN_GUARD_VERSION}/cfn-guard-linux-x86_64.tar.gz -o cfn-guard.tar.gz
tar -xzf cfn-guard.tar.gz
"""
}
}
stage('Validate CloudFormation') {
steps {
script {
def templates = findFiles(glob: 'cloudformation/*.yaml')
templates.each { template ->
sh """
./cfn-guard validate \
--template ${template.path} \
--rules cloudformation/rules.guard \
--output-format json \
--show-summary fail
"""
}
}
}
}
stage('Deploy to AWS') {
when {
expression { currentBuild.result == 'SUCCESS' }
}
steps {
sh 'aws cloudformation deploy --template-file cloudformation/template.yaml --stack-name java-app-stack'
}
}
}
post {
always {
archiveArtifacts artifacts: '**/cfn-guard-*.json', fingerprint: true
}
}
}

Java Application Integration

1. CloudFormation Template for Java Application:

# cloudformation/java-app-stack.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation stack for Java application
Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, production]
Default: dev
ApplicationName:
Type: String
Default: my-java-app
JavaVersion:
Type: String
AllowedValues: [java11, java17, java21]
Default: java17
Resources:
# ECS Cluster for Java Application
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub ${ApplicationName}-cluster-${Environment}
CapacityProviders:
- FARGATE
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Application
Value: !Ref ApplicationName
- Key: Owner
Value: java-team
- Key: CostCenter
Value: "12345"
# ECS Task Definition
JavaAppTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub ${ApplicationName}-task-${Environment}
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
Cpu: 512
Memory: 1024
ExecutionRoleArn: !GetAtt ECSExecutionRole.Arn
TaskRoleArn: !GetVar ECSTaskRole.Arn
ContainerDefinitions:
- Name: java-app-container
Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ApplicationName}:latest
Memory: 1024
PortMappings:
- ContainerPort: 8080
Protocol: tcp
Environment:
- Name: SPRING_PROFILES_ACTIVE
Value: !Ref Environment
- Name: JAVA_TOOL_OPTIONS
Value: -Djava.security.egd=file:/dev/./urandom -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref CloudWatchLogsGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: ecs
ReadonlyRootFilesystem: true
Privileged: false
# Security Groups
AppSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for Java application
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
CidrIp: 10.0.0.0/16
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Application
Value: !Ref ApplicationName
# Load Balancer
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${ApplicationName}-alb-${Environment}
Scheme: internet-facing
LoadBalancerAttributes:
- Key: access_logs.s3.enabled
Value: "true"
- Key: access_logs.s3.bucket
Value: !Ref AccessLogsBucket
- Key: access_logs.s3.prefix
Value: !Sub ${ApplicationName}-${Environment}
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroups:
- !Ref LoadBalancerSecurityGroup
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Application
Value: !Ref ApplicationName
# RDS Database
ApplicationDatabase:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: !Sub ${ApplicationName}-db-${Environment}
AllocatedStorage: 20
DBInstanceClass: db.t3.small
Engine: postgres
EngineVersion: "13.7"
MasterUsername: dbadmin
MasterUserPassword: !Ref DatabasePassword
StorageEncrypted: true
DeletionProtection: !If [IsProduction, true, false]
BackupRetentionPeriod: !If [IsProduction, 7, 1]
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Application
Value: !Ref ApplicationName
# S3 Bucket for Application
ApplicationBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${ApplicationName}-${Environment}-${AWS::AccountId}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Application
Value: !Ref ApplicationName
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Outputs:
LoadBalancerDNS:
Description: Load Balancer DNS Name
Value: !GetAtt ApplicationLoadBalancer.DNSName
DatabaseEndpoint:
Description: RDS Endpoint
Value: !GetAtt ApplicationDatabase.Endpoint.Address

2. Java Application Configuration Class:

@Configuration
@ConfigurationProperties(prefix = "aws")
@Data
public class AwsConfig {
private String environment;
private String applicationName;
private String region;
@Value("${database.url}")
private String databaseUrl;
@Value("${s3.bucket.name}")
private String s3BucketName;
@Bean
public S3Client s3Client() {
return S3Client.builder()
.region(Region.of(region))
.build();
}
@Bean
public RdsClient rdsClient() {
return RdsClient.builder()
.region(Region.of(region))
.build();
}
}

Advanced Policy Rules

1. Custom Java Application Rules:

# java-advanced.guard
rule ensure_java_app_health_checks {
Resources.*[ Type == /AWS::ECS::TaskDefinition/ ] {
Properties.ContainerDefinitions[*] {
Image == /.*java.*/ {
HealthCheck exists {
Command[*] == /.*curl.*health.*/ or
Command[*] == /.*wget.*health.*/
}
}
}
}
}
rule ensure_java_app_monitoring {
Resources.*[ 
Type == /AWS::ECS::Service/ or 
Type == /AWS::Lambda::Function/
] {
when %input.Parameters.Environment == "production" {
Properties.Tags[*] {
Key == "Monitoring"
Value == "enabled"
}
}
}
}
rule ensure_java_app_auto_scaling {
Resources.*[ Type == /AWS::ECS::Service/ ] {
when %input.Parameters.Environment == "production" {
Properties.Tags[*] {
Key == "AutoScaling"
Value == "enabled"
}
}
}
}
rule ensure_java_app_backup_policy {
Resources.*[ Type == /AWS::S3::Bucket/ ] {
when %input.Parameters.Environment == "production" {
Properties.VersioningConfiguration.Status == "Enabled"
}
}
}

2. Cost Optimization Rules:

# cost-optimization.guard
rule prevent_oversized_ec2_instances {
Resources.*[ Type == /AWS::EC2::Instance/ ] {
Properties.InstanceType in ["t3.micro", "t3.small", "t3.medium"]
}
}
rule ensure_rds_right_sizing {
Resources.*[ Type == /AWS::RDS::DBInstance/ ] {
Properties.DBInstanceClass in ["db.t3.small", "db.t3.medium"]
}
}
rule prevent_unused_elastic_ips {
Resources.*[ Type == /AWS::EC2::EIP/ ] {
Properties.InstanceId != "" or
Properties.NetworkInterfaceId != ""
}
}

Java Integration Library

1. CloudFormation Guard Java Wrapper:

@Service
public class CloudFormationGuardService {
private static final Logger logger = LoggerFactory.getLogger(CloudFormationGuardService.class);
public ValidationResult validateTemplate(String templatePath, String rulesPath) {
try {
ProcessBuilder processBuilder = new ProcessBuilder(
"cfn-guard", "validate",
"--template", templatePath,
"--rules", rulesPath,
"--output-format", "json",
"--show-summary", "fail"
);
Process process = processBuilder.start();
String output = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
String error = new String(process.getErrorStream().readAllBytes(), StandardCharsets.UTF_8);
int exitCode = process.waitFor();
if (exitCode == 0) {
return ValidationResult.success(output);
} else {
return ValidationResult.failure(output, error);
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException("CloudFormation Guard validation failed", e);
}
}
public void validateAllTemplates(String templatesDirectory, String rulesPath) {
try {
List<Path> templateFiles = Files.walk(Paths.get(templatesDirectory))
.filter(path -> path.toString().endsWith(".yaml") || path.toString().endsWith(".yml"))
.collect(Collectors.toList());
List<ValidationResult> results = new ArrayList<>();
for (Path templateFile : templateFiles) {
logger.info("Validating template: {}", templateFile);
ValidationResult result = validateTemplate(templateFile.toString(), rulesPath);
results.add(result);
if (!result.isSuccess()) {
logger.error("Validation failed for {}: {}", templateFile, result.getError());
}
}
long failedValidations = results.stream().filter(r -> !r.isSuccess()).count();
if (failedValidations > 0) {
throw new ValidationException(failedValidations + " templates failed validation");
}
} catch (IOException e) {
throw new RuntimeException("Failed to validate templates", e);
}
}
}
@Data
class ValidationResult {
private final boolean success;
private final String output;
private final String error;
public static ValidationResult success(String output) {
return new ValidationResult(true, output, null);
}
public static ValidationResult failure(String output, String error) {
return new ValidationResult(false, output, error);
}
}
class ValidationException extends RuntimeException {
public ValidationException(String message) {
super(message);
}
}

2. Spring Boot Actuator Endpoint:

@Component
@Endpoint(id = "cloudformation-validation")
public class CloudFormationValidationEndpoint {
private final CloudFormationGuardService guardService;
public CloudFormationValidationEndpoint(CloudFormationGuardService guardService) {
this.guardService = guardService;
}
@ReadOperation
public ValidationStatus validateTemplates() {
try {
guardService.validateAllTemplates("cloudformation", "cloudformation/rules.guard");
return new ValidationStatus("SUCCESS", "All templates passed validation");
} catch (ValidationException e) {
return new ValidationStatus("FAILED", e.getMessage());
}
}
@Data
@AllArgsConstructor
public static class ValidationStatus {
private String status;
private String message;
}
}

Best Practices for Java Teams

  1. Version Control for Rules: Store Guard rules alongside application code
  2. Automated Testing: Include Guard validation in all CI/CD pipelines
  3. Policy Evolution: Regularly update rules based on new security requirements
  4. Team Collaboration: Involve both development and security teams in rule creation
  5. Monitoring: Track policy violations and trends over time
@Component
public class PolicyComplianceMonitor {
private final CloudFormationGuardService guardService;
private final MeterRegistry meterRegistry;
public PolicyComplianceMonitor(CloudFormationGuardService guardService, 
MeterRegistry meterRegistry) {
this.guardService = guardService;
this.meterRegistry = meterRegistry;
}
@Scheduled(fixedRate = 3600000) // Run every hour
public void monitorCompliance() {
try {
guardService.validateAllTemplates("cloudformation", "cloudformation/rules.guard");
meterRegistry.counter("cloudformation.validation.success").increment();
} catch (ValidationException e) {
meterRegistry.counter("cloudformation.validation.failure").increment();
logger.warn("CloudFormation compliance check failed: {}", e.getMessage());
}
}
}

Conclusion

CloudFormation Guard provides Java development teams with a powerful policy-as-code solution for ensuring infrastructure security and compliance. By integrating Guard validation into CI/CD pipelines, teams can catch security misconfigurations early, enforce organizational standards, and maintain audit trails for compliance requirements.

The combination of comprehensive policy rules, automated validation in Java build processes, and continuous monitoring creates a robust infrastructure security posture that complements application-level security measures. As cloud infrastructure becomes increasingly complex, CloudFormation Guard offers Java teams the confidence that their AWS deployments meet security and operational excellence standards.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper