SecurityRules.java
package com.cloudforgeci.api.core.rules;
import com.cloudforgeci.api.core.SystemContext;
import com.cloudforgeci.api.core.security.DevSecurityConfiguration;
import com.cloudforgeci.api.core.security.ProductionSecurityConfiguration;
import com.cloudforgeci.api.core.security.StagingSecurityConfiguration;
import com.cloudforgeci.api.core.security.DevSecurityProfileConfiguration;
import com.cloudforgeci.api.core.security.StagingSecurityProfileConfiguration;
import com.cloudforgeci.api.core.security.ProductionSecurityProfileConfiguration;
import com.cloudforgeci.api.interfaces.SecurityConfiguration;
import com.cloudforgeci.api.interfaces.SecurityProfileConfiguration;
import com.cloudforgeci.api.interfaces.Rule;
import com.cloudforge.core.interfaces.FrameworkRules;
import io.github.cdklabs.cdknag.AwsSolutionsChecks;
import io.github.cdklabs.cdknag.HIPAASecurityChecks;
import io.github.cdklabs.cdknag.NagReportFormat;
import io.github.cdklabs.cdknag.PCIDSS321Checks;
import io.github.cdklabs.cdknag.NIST80053R5Checks;
import io.github.cdklabs.cdknag.NagPack;
import software.amazon.awscdk.Aspects;
import com.cloudforge.core.enums.ComplianceMode;
import com.cloudforge.core.enums.SecurityProfile;
import java.util.*;
import java.util.logging.Logger;
/**
* Security rules installation and compliance framework orchestration.
*
* <p>This class coordinates security profile configuration and compliance framework
* validation. Starting in v3.0.0, it uses auto-discovery to load compliance frameworks,
* enabling plugin-based extensibility.</p>
*
* <h2>Version History:</h2>
* <ul>
* <li><strong>v3.0.0:</strong> Hardcoded framework loading</li>
* <li><strong>v3.0.0:</strong> Auto-discovery via {@link FrameworkLoader} with backward compatibility</li>
* <li><strong>v4.0.0 (future):</strong> Pure plugin-based architecture</li>
* </ul>
*
* @since 3.0.0
*/
public final class SecurityRules {
private static final Logger LOG = Logger.getLogger(SecurityRules.class.getName());
public static void install(SystemContext ctx) {
// Create and set the SecurityProfileConfiguration in SystemContext
// Pass deployment context so security profiles can override defaults
final SecurityProfileConfiguration profileConfig = switch (ctx.security) {
case DEV -> new DevSecurityProfileConfiguration(ctx.cfc);
case STAGING -> new StagingSecurityProfileConfiguration(ctx.cfc);
case PRODUCTION -> new ProductionSecurityProfileConfiguration(ctx.cfc);
};
ctx.securityProfileConfig.set(profileConfig);
final SecurityConfiguration p = switch (ctx.security) {
case DEV -> new DevSecurityConfiguration();
case STAGING -> new StagingSecurityConfiguration();
case PRODUCTION -> new ProductionSecurityConfiguration();
};
ctx.getNode().addValidation(() -> {
List<String> errs = new ArrayList<>();
for (Rule r : p.rules(ctx)) errs.addAll(r.check(ctx));
return errs;
});
ctx.once("ProfileWiring:Security:" + p.kind(), () -> {
p.wire(ctx);
});
// Get enabled compliance frameworks (comma-separated list)
String frameworksConfig = ctx.cfc.complianceFrameworks();
// Parse enabled frameworks into a set for fast lookup
Set<String> enabledFrameworks = new LinkedHashSet<>(
(frameworksConfig == null || frameworksConfig.trim().isEmpty())
? Collections.emptySet()
: Arrays.stream(frameworksConfig.split(","))
.map(String::trim)
.map(String::toUpperCase)
.collect(java.util.stream.Collectors.toSet()));
// See DeploymentConfig#complianceFrameworksRawOverride's own javadoc -- an on-demand advisory
// check's escape hatch for tokens with no ComplianceFrameworkType entry at all (e.g.
// "ISO-27001", or "AWS-BEST-PRACTICES" for cdk-nag's own generic fallback pack below), read
// directly off the construct tree rather than through DeploymentContext/DeploymentConfig
// since it deliberately can't round-trip through either. Validated against a fixed charset
// (rather than trusted verbatim) since it never passes through ComplianceFrameworkType's own
// enum parsing the way the comma-separated field above does -- every valid token this feature
// actually offers (see ComplianceService#checkableFrameworks) is already upper-case
// letters/digits/hyphens, so anything else is dropped rather than logged or installed.
Object rawOverride = ctx.getNode().tryGetContext("complianceFrameworksRawOverride");
if (rawOverride instanceof String rawOverrideText && !rawOverrideText.isBlank()) {
for (String token : rawOverrideText.split(",")) {
String candidate = token.trim().toUpperCase(java.util.Locale.ROOT);
if (candidate.matches("[A-Z0-9-]{1,64}")) {
enabledFrameworks.add(candidate);
}
}
}
// CDK-nag validation only runs for PRODUCTION with enabled frameworks
if (ctx.security == SecurityProfile.PRODUCTION && !enabledFrameworks.isEmpty()) {
applyCdkNagValidation(ctx, enabledFrameworks);
}
// CloudForge FrameworkRules validation requires auditManagerEnabled
if (!Boolean.TRUE.equals(ctx.cfc.auditManagerEnabled())) {
LOG.info("Skipping CloudForge FrameworkRules validation (auditManagerEnabled = false)");
return;
}
// Log what we're doing
if (enabledFrameworks.isEmpty()) {
LOG.info("No compliance frameworks specified - installing only alwaysLoad validators (e.g., ConfigurationValidationRules)");
} else {
LOG.info("Installing CloudForge FrameworkRules validation for: " + frameworksConfig);
}
// Discover all available compliance frameworks (v3.0.0 plugin architecture)
List<FrameworkRules<SystemContext>> allFrameworks = FrameworkLoader.discover();
LOG.info("Discovered " + allFrameworks.size() + " compliance frameworks");
// Install frameworks in priority order
int installedCount = 0;
for (FrameworkRules<SystemContext> framework : allFrameworks) {
boolean shouldInstall = framework.alwaysLoad() ||
enabledFrameworks.contains(framework.frameworkId().toUpperCase());
if (shouldInstall) {
try {
framework.install(ctx);
installedCount++;
LOG.info(" ✓ " + framework.displayName() + " (priority=" + framework.priority() + ")");
} catch (Exception e) {
LOG.severe(" ✗ Failed to install " + framework.frameworkId() + ": " + e.getMessage());
throw new RuntimeException("Failed to install compliance framework: " + framework.frameworkId(), e);
}
} else {
LOG.fine(" - Skipping " + framework.frameworkId() + " (not enabled)");
}
}
LOG.info("Successfully installed " + installedCount + " CloudForge FrameworkRules validators");
}
/**
* Auto-apply cdk-nag validation packs based on enabled compliance frameworks.
*
* <p>Maps framework names to appropriate cdk-nag rule packs:</p>
* <ul>
* <li><strong>HIPAA:</strong> HIPAASecurityChecks</li>
* <li><strong>PCI-DSS:</strong> PCIDSS321Checks</li>
* <li><strong>SOC2:</strong> AwsSolutionsChecks</li>
* <li><strong>Custom:</strong> AwsSolutionsChecks (fallback)</li>
* </ul>
*
* <p>The enforcement mode is determined by {@code complianceMode}:</p>
* <ul>
* <li><strong>enforce:</strong> Blocks synthesis on violations</li>
* <li><strong>advisory:</strong> Logs warnings only</li>
* </ul>
*
* @param ctx the system context containing deployment configuration
* @param enabledFrameworks set of enabled framework IDs (uppercase)
* @since 3.1.0
*/
private static void applyCdkNagValidation(SystemContext ctx, Set<String> enabledFrameworks) {
if (enabledFrameworks.isEmpty()) {
LOG.info("No frameworks enabled - skipping cdk-nag validation");
return;
}
boolean enforce = ctx.cfc.complianceMode() == ComplianceMode.ENFORCE;
LOG.info("Applying cdk-nag validation (mode=" + (enforce ? "enforce" : "advisory") + ")");
int appliedCount = 0;
for (String framework : enabledFrameworks) {
NagPack pack = mapFrameworkToNagPack(framework, enforce);
if (pack != null) {
Aspects.of(ctx.getNode().getRoot()).add(pack);
appliedCount++;
}
}
LOG.info("Applied " + appliedCount + " cdk-nag validation pack(s) across "
+ enabledFrameworks.size() + " enabled framework(s)");
}
/**
* Maps a compliance framework to its corresponding cdk-nag rule pack.
*
* @param framework the framework ID (uppercase)
* @param enforce true to enforce violations, false to log only
* @return the corresponding NagPack, or null if no mapping exists
* @since 3.1.0
*/
private static NagPack mapFrameworkToNagPack(String framework, boolean enforce) {
// Report formats for compliance auditing. These reports are also how ComplianceMode.ENFORCE
// blocks: app.synth() does not throw on cdk-nag annotations, so CloudForgeSynthesizer reads
// <Pack>-<Stack>-NagReport.json after synthesis (see NagReportReader). additionalLoggers is
// not used because registering a second INagLogger triggers a jsii/cdk-nag StackOverflowError
// from reentrant kernel calls; the built-in report logger avoids that path.
var reportFormats = List.of(NagReportFormat.JSON, NagReportFormat.CSV);
return switch (framework) {
case "HIPAA" -> HIPAASecurityChecks.Builder.create()
.logIgnores(!enforce)
.reports(true)
.reportFormats(reportFormats)
.build();
case "PCI-DSS" -> PCIDSS321Checks.Builder.create()
.logIgnores(!enforce)
.reports(true)
.reportFormats(reportFormats)
.build();
case "SOC2" -> AwsSolutionsChecks.Builder.create()
.logIgnores(!enforce)
.reports(true)
.reportFormats(reportFormats)
.build();
// FEDRAMP/FEDRAMPHIGH: NIST 800-53 Rev 5 is the control baseline both share -- cdk-nag has
// no separate Low/Moderate/High pack, so one mapping covers both tokens. Runs alongside
// FedRampRules.java rather than replacing it, same as HIPAA/PCI-DSS/SOC2 pairing a cdk-nag
// pack with their own FrameworkRules validator.
case "FEDRAMP", "FEDRAMP-HIGH", "FEDRAMPHIGH" -> NIST80053R5Checks.Builder.create()
.logIgnores(!enforce)
.reports(true)
.reportFormats(reportFormats)
.build();
// Custom frameworks: fallback to AWS Solutions best practices
default -> {
LOG.info(" - Applying AwsSolutionsChecks (fallback) for an unrecognized/custom framework");
yield AwsSolutionsChecks.Builder.create()
.logIgnores(!enforce)
.reports(true)
.reportFormats(reportFormats)
.build();
}
};
}
}