DeploymentConfig.java
package com.cloudforge.core.config;
import com.cloudforge.core.annotation.ConfigField;
import com.cloudforge.core.annotation.FieldTag;
import com.cloudforge.core.enums.AuthMode;
import com.cloudforge.core.enums.ComplianceFrameworkType;
import com.cloudforge.core.enums.ComplianceMode;
import com.cloudforge.core.enums.LoadBalancerType;
import com.cloudforge.core.enums.NetworkMode;
import com.cloudforge.core.enums.RuntimeType;
import com.cloudforge.core.enums.SecurityProfile;
import com.cloudforge.core.enums.TopologyType;
import com.cloudforge.core.interfaces.ApplicationSpec;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Universal deployment configuration for CloudForge applications.
*
* <p>This is the canonical configuration structure used by both interactive deployers
* and non-interactive deployment tools. It maps directly to deployment-context.json
* and can be serialized/deserialized via Jackson.</p>
*
* <p><b>Architecture:</b> This class lives in cloudforge-core (the contract layer) as
* it defines the data model interface between libraries and consumers. This ensures
* cloudforge-cli and other consumers always use the latest configuration schema without
* duplication.</p>
*
* @since CloudForge 3.0.0
*/
public class DeploymentConfig {
// ========== Basic Configuration ==========
/** CloudFormation stack name */
@ConfigField(
displayName = "Stack Name",
description = "CloudFormation stack name (lowercase alphanumeric with hyphens)",
category = "basic",
required = true,
pattern = "^[a-zA-Z][a-zA-Z0-9-]{0,127}$",
example = "my-app-stack",
order = 10
)
public String stackName;
/** Environment name (e.g., "dev", "staging", "production") */
@ConfigField(
displayName = "Environment",
description = "Deployment environment name",
category = "basic",
allowedValues = {"dev", "staging", "prod"},
example = "dev",
order = 20
)
public String environment;
/** Application identifier (e.g., "jenkins", "gitlab", "vault") */
@ConfigField(
displayName = "Application ID",
description = "Application identifier from available plugins",
category = "basic",
required = true,
order = 5
)
public String applicationId;
/** Human-readable application name */
@ConfigField(
displayName = "Application Name",
description = "Human-readable application display name",
category = "basic",
order = 6
)
public String applicationName;
/** ApplicationSpec instance (not serialized to JSON) */
@JsonIgnore
public ApplicationSpec applicationSpec;
// ========== Domain Configuration ==========
/**
* Subdomain prefix (e.g., "ci", "gitlab") — ordered ahead of {@link #domain}: you pick what
* this app is called (the subdomain) before which domain it hangs off of, and the combined
* result (subdomain + "." + domain) is what gets requested, not the other way
* around.
*/
@ConfigField(
displayName = "Subdomain",
description = "Subdomain prefix (e.g., 'ci' for ci.example.com)",
category = "domain",
pattern = "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$",
example = "ci",
order = 10
)
public String subdomain;
/** Primary domain (e.g., "example.com") */
@ConfigField(
displayName = "Domain",
description = "Primary domain for the application (e.g., example.com)",
category = "domain",
pattern = "^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$",
example = "example.com",
order = 20
)
public String domain;
/**
* Fully qualified domain name — always {@code subdomain + "." + domain} once both are set;
* this field only matters when you need to override that computed result directly (e.g. a
* domain structure the subdomain+domain pair can't express), or when a caller only has the
* full FQDN available and no reliable way to split it (e.g. recovering it from a live
* CloudFormation template's ACM certificate, where the domain/subdomain boundary isn't
* knowable without a hosted-zone lookup). Previously {@code @JsonIgnore}d on the theory that
* it should never round-trip as its own JSON key — that silently dropped any caller-supplied
* override during deserialization (an explicit {@code fqdn} in a submitted JSON body was
* discarded, reverting to null and falling through to the subdomain+domain computation
* instead), which is the opposite of "override."
*/
@ConfigField(
displayName = "FQDN (advanced override)",
description = "Computed as Subdomain + \".\" + Domain — only set this directly to override that computed value",
category = "domain",
order = 30
)
public String fqdn;
/** Enable SSL certificate via ACM */
@ConfigField(
displayName = "Enable SSL",
description = "Enable SSL/TLS with AWS Certificate Manager",
category = "domain",
order = 30
)
public Boolean enableSsl;
/**
* ARN of an existing ACM certificate to use for the ALB's HTTPS listener, instead of
* provisioning a new one. Takes priority over both of {@code FargateRuntimeConfiguration}'s
* other two certificate paths — the DNS-validated public cert (needs a Route53 hosted zone
* this deployment controls) and the AWS Private CA cert (issued for the bare ALB DNS name,
* NOT trusted by browsers — see that class's comments). This is how a deployment gets a
* publicly-trusted certificate without either of those: import your own cert
* (issued by any public CA — ACM's own DNS/email validation, Let's Encrypt, a purchased
* cert, ...) into ACM yourself first (e.g. {@code aws acm import-certificate}, entirely
* within your own account — the key material never has to pass through this deployment
* config or CloudFormation), then point this at the resulting ARN.
*/
@ConfigField(
displayName = "Existing Certificate ARN",
description = "ARN of an ACM certificate you've already issued/imported — used as-is instead of provisioning a new one",
category = "domain",
pattern = "^arn:aws[a-zA-Z-]*:acm:[a-z0-9-]+:\\d{12}:certificate/[a-f0-9-]+$",
example = "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012",
visibleWhen = "enableSsl == true",
order = 35
)
public String certificateArn;
// ========== Runtime Configuration ==========
/** Runtime type (FARGATE or EC2) */
@ConfigField(
displayName = "Runtime Type",
description = "Container runtime: FARGATE (serverless) or EC2 (instance-based)",
category = "basic",
allowedValues = {"FARGATE", "EC2"},
required = true,
order = 30
)
public RuntimeType runtime;
/** Topology type — user-selectable, not just an auto-derived display value, so a future
* topology (or an app implementing more than one applicable interface) isn't locked out of
* being chosen explicitly. No {@code allowedValues} override here on purpose: leaving it
* unset lets the schema builder enumerate every {@link TopologyType} constant automatically —
* a hardcoded single-value override would hide CMS_SERVICE/JENKINS_SERVICE/S3_WEBSITE from
* the wizard entirely, locking out a CmsSpec app's actual topology even though {@link
* DeploymentContextPreparer} already defaults to it correctly. */
@ConfigField(
displayName = "Topology",
description = "Deployment topology pattern",
category = "basic",
order = 40
)
public TopologyType topology;
/** Security profile (DEV, STAGING, PRODUCTION) */
@ConfigField(
displayName = "Security Profile",
description = "Security profile determines compliance requirements and defaults",
category = "security",
allowedValues = {"DEV", "STAGING", "PRODUCTION"},
required = true,
order = 10
)
public SecurityProfile securityProfile = SecurityProfile.DEV;
// ========== Network Configuration ==========
/** Network mode for VPC topology */
@ConfigField(
displayName = "Network Mode",
description = "VPC topology. Private with NAT is recommended for production; public is suitable for development; isolated requires VPC endpoints.",
category = "network",
allowedValues = {"public", "private-with-nat", "isolated"},
example = "private-with-nat",
order = 10
)
public NetworkMode networkMode = NetworkMode.PUBLIC;
/** Load balancer type */
@ConfigField(
displayName = "Load Balancer Type",
description = "ALB (HTTP/HTTPS) or NLB (TCP/UDP) - ALB required for OIDC",
category = "network",
order = 20
)
public LoadBalancerType lbType = LoadBalancerType.ALB;
/** Create Route53 hosted zone */
@ConfigField(
displayName = "Create Route53 Zone",
description = "Create new Route53 hosted zone for the domain",
category = "domain",
order = 40
)
public Boolean createZone = false;
/** Enable VPC flow logs */
@ConfigField(
displayName = "Enable Flow Logs",
description = "Enable VPC flow logs for network traffic analysis",
category = "network",
order = 30
)
public Boolean enableFlowlogs = null; // Null means security profile determines default
/** Enable AWS WAF */
@ConfigField(
displayName = "Enable WAF",
description = "Enable AWS Web Application Firewall for ALB protection",
category = "network",
visibleWhen = "lbType == alb",
order = 40
)
public Boolean wafEnabled;
/** HTTPS-only mode (no HTTP listener when SSL enabled) */
@ConfigField(
displayName = "HTTPS Strict Mode",
description = "Disable HTTP listener when SSL enabled (no HTTP→HTTPS redirect)",
category = "network",
visibleWhen = "enableSsl == true",
order = 45
)
public Boolean httpsStrictEnabled;
/** Enable ALB access logs to S3. Null means the security profile decides. */
@ConfigField(
displayName = "ALB Access Logging",
description = "Log all ALB requests to S3 for auditing",
category = "network",
visibleWhen = "lbType == alb",
order = 50
)
public Boolean albAccessLogging;
/** Enable CloudFront CDN */
@ConfigField(
displayName = "Enable CloudFront",
description = "Enable CloudFront CDN for global content delivery",
category = "network",
tags = {FieldTag.BILLING_IMPACT},
order = 60
)
@JsonAlias("cloudfront")
public Boolean cloudfrontEnabled;
/** CIDR for bastion/VPN SSH access */
@ConfigField(
displayName = "Bastion CIDR",
description = "CIDR for bastion/VPN SSH access (PRODUCTION profile)",
category = "network",
pattern = "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/\\d{1,2}$",
example = "10.0.1.0/24",
order = 70
)
public String bastionCidr = "10.0.1.0/24";
// ========== Resource Configuration ==========
/** Minimum instance capacity for auto-scaling */
@ConfigField(
displayName = "Min Instance Capacity",
description = "Minimum number of instances for auto-scaling",
category = "resources",
min = 1,
max = 100,
order = 10
)
public int minInstanceCapacity = 1;
/** Maximum instance capacity for auto-scaling */
@ConfigField(
displayName = "Max Instance Capacity",
description = "Maximum number of instances for auto-scaling",
category = "resources",
min = 1,
max = 100,
validators = {"CapacityValidator"},
tags = {FieldTag.BILLING_IMPACT},
order = 20
)
public int maxInstanceCapacity = 1;
/** CPU target utilization percentage for auto-scaling */
@ConfigField(
displayName = "CPU Target Utilization (%)",
description = "Target CPU utilization for auto-scaling triggers",
category = "resources",
min = 10,
max = 90,
visibleWhen = "maxInstanceCapacity > 1",
order = 30
)
public int cpuTargetUtilization = 60;
/** Fargate CPU units (256, 512, 1024, 2048, 4096) */
@ConfigField(
displayName = "CPU (Fargate units)",
description = "Fargate CPU units: 256=0.25vCPU, 512=0.5vCPU, 1024=1vCPU, etc.",
category = "resources",
allowedValues = {"256", "512", "1024", "2048", "4096", "8192", "16384"},
visibleWhen = "runtime == FARGATE",
validators = {"FargateCpuMemoryValidator"},
tags = {FieldTag.BILLING_IMPACT},
order = 40
)
public int cpu = 1024;
/** Fargate memory in MB */
@ConfigField(
displayName = "Memory (MB)",
description = "Container memory in MB (must be valid for CPU selection)",
category = "resources",
min = 512,
max = 122880,
visibleWhen = "runtime == FARGATE",
validators = {"FargateCpuMemoryValidator"},
tags = {FieldTag.BILLING_IMPACT},
order = 50
)
public int memory = 2048;
/** EC2 instance type (e.g., "t3.micro", "t3.small") */
@ConfigField(
displayName = "EC2 Instance Type",
description = "EC2 instance size for compute capacity",
category = "resources",
// Choices come from InstanceCatalog.ec2InstanceTypes().
visibleWhen = "runtime == EC2",
defaultFrom = "defaultInstanceType",
tags = {FieldTag.BILLING_IMPACT},
order = 60
)
public String instanceType = "t3.micro";
/** Override container image tag */
@ConfigField(
displayName = "Container Image",
description = "Override container image tag (e.g., 'v1.2.3' or '2024.1')",
category = "resources",
example = "v1.2.3",
order = 65
)
public String containerImage;
// ========== Storage Configuration ==========
/** Retain EFS/EBS volumes on stack deletion */
@ConfigField(
displayName = "Retain Storage",
description = "Retain EFS/EBS volumes on stack deletion (agnostic - works for any workload)",
category = "storage",
order = 10
)
public Boolean retainStorage = false;
/** Reuse existing EFS by ID (for disaster recovery workflows) */
@ConfigField(
displayName = "Existing File System ID",
description = "Reuse existing EFS by ID (for disaster recovery workflows)",
category = "storage",
example = "fs-12345678",
order = 20
)
public String existingFileSystemId;
/** S3 bucket for artifacts */
@ConfigField(
displayName = "Artifacts Bucket",
description = "S3 bucket name for build artifacts",
category = "storage",
order = 30
)
public String artifactsBucket;
/** S3 prefix for artifacts */
@ConfigField(
displayName = "Artifacts Prefix",
description = "S3 prefix for build artifacts",
category = "storage",
example = "jenkins/job/${JOB_NAME}/${BUILD_NUMBER}",
order = 40
)
public String artifactsPrefix = "jenkins/job/${JOB_NAME}/${BUILD_NUMBER}";
// ========== Authentication Configuration ==========
/** Authentication mode */
@ConfigField(
displayName = "Authentication Mode",
description = "Authentication integration type: ALB-level OIDC, application-level, or none",
category = "security",
visibleWhen = "supportsOidc",
order = 20
)
public AuthMode authMode = AuthMode.NONE;
/** OIDC provider (none, cognito, identity-center, external-idp) */
@ConfigField(
displayName = "OIDC Provider",
description = "Identity provider for OIDC authentication",
category = "security",
allowedValues = {"none", "cognito", "identity-center", "external-idp", "cloudforge-manager"},
visibleWhen = "authMode != none",
order = 30
)
public String oidcProvider = "none";
/** URL path patterns that require authentication, replacing the application's own default
* list ({@code ApplicationSpec#protectedPaths()}) when set. Consumed by both
* {@code CognitoAuthenticationFactory} and {@code OidcAuthenticationFactory} -- see their
* {@code calculateEffectiveProtectedPaths()}: unset/empty means "use the application's
* default protected paths", not "protect nothing". */
@ConfigField(
displayName = "Protected Paths",
description = "URL path patterns requiring authentication (replaces the application's defaults)",
category = "security",
visibleWhen = "authMode != none",
example = "/admin/*,/api/*",
order = 31
)
public List<String> protectedPaths = null;
/** Additional URL path patterns requiring authentication, added on top of whichever base list
* ({@link #protectedPaths} or the application default) is in effect. */
@ConfigField(
displayName = "Additional Protected Paths",
description = "Extra URL path patterns requiring authentication, added to the base list",
category = "security",
visibleWhen = "authMode != none",
example = "/reports/*",
order = 32
)
public List<String> additionalProtectedPaths = null;
/** URL path patterns explicitly excluded from authentication, removed from the effective
* protected-paths list after {@link #protectedPaths}/{@link #additionalProtectedPaths} and
* the application's own defaults are combined. */
@ConfigField(
displayName = "Public Paths",
description = "URL path patterns excluded from authentication",
category = "security",
visibleWhen = "authMode != none",
example = "/health,/public/*",
order = 33
)
public List<String> publicPaths = null;
/** Auto-provision new Cognito User Pool */
@ConfigField(
displayName = "Auto-Provision Cognito",
description = "Create a new Cognito User Pool for authentication",
category = "security",
visibleWhen = "oidcProvider == cognito",
order = 40
)
public Boolean cognitoAutoProvision = false;
/** Cognito User Pool name */
@ConfigField(
displayName = "Cognito User Pool Name",
description = "Name for the Cognito User Pool",
category = "security",
visibleWhen = "cognitoAutoProvision == true",
order = 50
)
public String cognitoUserPoolName = null;
/** Cognito domain prefix (must be globally unique) */
@ConfigField(
displayName = "Cognito Domain Prefix",
description = "Globally unique prefix for Cognito hosted UI domain",
category = "security",
pattern = "^[a-z][a-z0-9-]{0,62}$",
visibleWhen = "cognitoAutoProvision == true",
order = 60
)
public String cognitoDomainPrefix = null;
/** Enable MFA for Cognito */
@ConfigField(
displayName = "Enable MFA",
description = "Require multi-factor authentication for Cognito users",
category = "security",
visibleWhen = "cognitoAutoProvision == true",
order = 70
)
public Boolean cognitoMfaEnabled = false;
/** Cognito MFA method */
@ConfigField(
displayName = "MFA Method",
description = "MFA method: totp (authenticator app), sms, or both",
category = "security",
allowedValues = {"totp", "sms", "both"},
visibleWhen = "cognitoMfaEnabled == true",
order = 75
)
public String cognitoMfaMethod = "both";
/**
* Overrides the security-profile default for whether users can self-register on the
* Cognito hosted UI (development defaults to allowed, staging/production to admin-only).
* Left {@code null}, the profile default applies unchanged. Self-signup is not enforced
* by any compliance framework rule today — a self-registered user still gets no group
* membership and resolves to the lowest-privilege role — but enabling it while a compliance
* framework is active (advisory or enforce) logs a warning at synthesis time, since open
* self-registration into an ops panel is a common audit finding independent of what role
* it grants.
*/
@ConfigField(
displayName = "Enable Self-Signup",
description = "Override the security profile's default for self-service Cognito signup",
category = "security",
visibleWhen = "cognitoAutoProvision == true",
order = 78
)
public Boolean cognitoSelfSignupEnabled = null;
/** Create admin and user groups in Cognito */
@ConfigField(
displayName = "Create User Groups",
description = "Create admin and user groups in Cognito",
category = "security",
visibleWhen = "cognitoAutoProvision == true",
order = 80
)
public Boolean cognitoCreateGroups = true;
/** Admin group name */
@ConfigField(
displayName = "Admin Group Name",
description = "Name for the Cognito admin group",
category = "security",
visibleWhen = "cognitoCreateGroups == true",
order = 90
)
public String cognitoAdminGroupName = null;
/** User group name */
@ConfigField(
displayName = "User Group Name",
description = "Name for the Cognito users group",
category = "security",
visibleWhen = "cognitoCreateGroups == true",
order = 100
)
public String cognitoUserGroupName = null;
/** Initial admin email address */
@ConfigField(
displayName = "Initial Admin Email",
description = "Email address for the first admin user",
category = "security",
pattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
visibleWhen = "cognitoAutoProvision == true",
example = "admin@example.com",
order = 110
)
public String cognitoInitialAdminEmail = null;
/** Initial admin phone number (E.164 format) */
@ConfigField(
displayName = "Initial Admin Phone",
description = "Phone number for admin user (E.164 format: +1234567890)",
category = "security",
pattern = "^\\+[1-9]\\d{1,14}$",
visibleWhen = "cognitoMfaEnabled == true",
example = "+15551234567",
order = 120
)
public String cognitoInitialAdminPhone = null;
/** Existing Cognito User Pool ID */
@ConfigField(
displayName = "Existing User Pool ID",
description = "ID of existing Cognito User Pool to use",
category = "security",
visibleWhen = "oidcProvider == cognito && cognitoAutoProvision == false",
order = 130
)
public String cognitoUserPoolId = null;
/** Existing Cognito App Client ID */
@ConfigField(
displayName = "Existing App Client ID",
description = "ID of existing Cognito App Client to use",
category = "security",
visibleWhen = "oidcProvider == cognito && cognitoAutoProvision == false",
order = 140
)
public String cognitoAppClientId = null;
/** Manually configured Cognito app client ID, for a client that wasn't auto-provisioned.
* Distinct from {@link #cognitoAppClientId} above (used to reference an *existing user
* pool's* client when {@code cognitoAutoProvision == false}) -- this is read directly by
* {@code ApplicationOidcFactory} as a manual-configuration fallback. */
@ConfigField(
displayName = "Cognito User Pool Client ID (manual)",
description = "Manually configured Cognito app client ID",
category = "security",
order = 141
)
public String cognitoUserPoolClientId = null;
// ========== External OIDC Configuration ==========
/** OIDC issuer URL */
@ConfigField(
displayName = "OIDC Issuer URL",
description = "Issuer URL from your identity provider (e.g., https://login.example.com)",
category = "security",
visibleWhen = "oidcProvider == external-idp",
pattern = "^https://.*$",
example = "https://login.example.com",
order = 200
)
public String oidcIssuer = null;
/** CloudForge Manager's own public URL, used as the OIDC issuer when this app trusts a
* CloudForge Manager install as its identity provider (see
* com.cloudforgeci.manager.auth.oidcprovider's own package javadoc). Authorization/token/
* userinfo endpoints are computed from this one URL (CloudForge Manager's OIDC-provider
* endpoints are at fixed paths — /oauth2/authorize, /oauth2/token, /userinfo — so unlike
* the generic external-idp option above, only the base URL needs entering here, not each
* endpoint separately). */
@ConfigField(
displayName = "CloudForge Manager URL",
description = "Public URL of the CloudForge Manager install this app trusts as its identity provider",
category = "security",
visibleWhen = "oidcProvider == cloudforge-manager",
pattern = "^https?://.*$",
example = "https://manager.example.com",
order = 195
)
public String cloudforgeManagerIssuerUrl = null;
/** OIDC authorization endpoint */
@ConfigField(
displayName = "Authorization Endpoint",
description = "OIDC authorization endpoint URL",
category = "security",
visibleWhen = "oidcProvider == external-idp",
pattern = "^https://.*$",
order = 210
)
public String oidcAuthorizationEndpoint = null;
/** OIDC token endpoint */
@ConfigField(
displayName = "Token Endpoint",
description = "OIDC token endpoint URL",
category = "security",
visibleWhen = "oidcProvider == external-idp",
pattern = "^https://.*$",
order = 220
)
public String oidcTokenEndpoint = null;
/** OIDC user info endpoint */
@ConfigField(
displayName = "User Info Endpoint",
description = "OIDC user info endpoint URL",
category = "security",
visibleWhen = "oidcProvider == external-idp",
pattern = "^https://.*$",
order = 230
)
public String oidcUserInfoEndpoint = null;
/** OIDC client ID — shared by external-idp and cloudforge-manager, both providers where an
* admin registers this app's client credentials by hand (external IdP's own console, or
* CloudForge Manager's Trusted Apps settings page) rather than CloudForge auto-provisioning
* them, so the field means the same thing either way. */
@ConfigField(
displayName = "OIDC Client ID",
description = "Client ID from your identity provider",
category = "security",
visibleWhen = "oidcProvider == external-idp || oidcProvider == cloudforge-manager",
order = 240
)
public String oidcClientId = null;
/** OIDC client secret name in Secrets Manager — same sharing rationale as {@link
* #oidcClientId}. */
@ConfigField(
displayName = "Client Secret (Secrets Manager)",
description = "Name of the secret in AWS Secrets Manager containing the client secret",
category = "security",
visibleWhen = "oidcProvider == external-idp || oidcProvider == cloudforge-manager",
sensitive = true,
order = 250
)
public String oidcClientSecretName = null;
// ========== Optional Ports Configuration ==========
// These enable optional services on applications that support them.
// Ports are NOT exposed by default - must be explicitly enabled.
/** Enable JNLP build agent port (Jenkins: 50000) */
@ConfigField(
displayName = "Enable Build Agents",
description = "Enable JNLP build agent port for distributed builds (Jenkins: 50000)",
category = "ports",
visibleWhen = "supportsAgents",
order = 10
)
public boolean enableAgents = false;
/** Enable Git SSH port (GitLab: 22, Gitea: 2222) */
@ConfigField(
displayName = "Enable Git SSH",
description = "Enable Git SSH access for repository cloning (GitLab: 22, Gitea: 2222)",
category = "ports",
visibleWhen = "supportsSsh",
order = 20
)
public boolean enableSsh = false;
/** Enable SMTP email port (Mattermost: 587) */
@ConfigField(
displayName = "Enable SMTP",
description = "Enable SMTP port for outbound email (port 587)",
category = "ports",
visibleWhen = "supportsSmtp",
order = 30
)
public boolean enableSmtp = false;
/** Enable SMTP TLS email port (Mattermost: 465) */
@ConfigField(
displayName = "Enable SMTPS",
description = "Enable SMTP over TLS port for secure outbound email (port 465)",
category = "ports",
visibleWhen = "supportsSmtps",
order = 40
)
public boolean enableSmtps = false;
/** Enable clustering ports (Mattermost: 8074-8075, Vault: 8201) */
@ConfigField(
displayName = "Enable Clustering",
description = "Enable inter-node clustering ports for high availability",
category = "ports",
visibleWhen = "supportsClustering",
order = 50
)
public boolean enableClustering = false;
/** Enable container registry port (GitLab: 5050, Nexus: 5000-5002) */
@ConfigField(
displayName = "Enable Docker Registry",
description = "Enable container registry ports (GitLab: 5050, Nexus: 5000-5002)",
category = "ports",
visibleWhen = "supportsDockerRegistry",
order = 60
)
public boolean enableDockerRegistry = false;
/** Enable Prometheus metrics port (GitLab: 9090) */
@ConfigField(
displayName = "Enable Metrics Port",
description = "Enable Prometheus metrics endpoint port (typically 9090)",
category = "ports",
visibleWhen = "supportsMetrics",
order = 70
)
public boolean enableMetrics = false;
/** Enable Notary content trust port (Harbor: 4443) */
@ConfigField(
displayName = "Enable Notary",
description = "Enable Docker Content Trust Notary port (Harbor: 4443)",
category = "ports",
visibleWhen = "supportsNotary",
order = 80
)
public boolean enableNotary = false;
/** Enable Trivy vulnerability scanner port (Harbor: 8080) */
@ConfigField(
displayName = "Enable Trivy Scanner",
description = "Enable Trivy vulnerability scanner port (Harbor: 8080)",
category = "ports",
visibleWhen = "supportsTrivy",
order = 90
)
public boolean enableTrivy = false;
/** Enable Redis Sentinel port (Redis: 26379) */
@ConfigField(
displayName = "Enable Sentinel",
description = "Enable Redis Sentinel port for HA monitoring (port 26379)",
category = "ports",
visibleWhen = "supportsSentinel",
order = 100
)
public boolean enableSentinel = false;
/** Enable Redis Cluster bus port (Redis: 16379) */
@ConfigField(
displayName = "Enable Cluster Bus",
description = "Enable Redis Cluster bus port for cluster gossip (port 16379)",
category = "ports",
visibleWhen = "supportsClusterBus",
order = 110
)
public boolean enableCluster = false;
// ========== IAM Identity Center Configuration ==========
/** Auto-provision SAML application in IAM Identity Center */
@ConfigField(
displayName = "Auto-Provision Identity Center",
description = "Create SAML application in IAM Identity Center",
category = "security",
visibleWhen = "oidcProvider == identity-center",
order = 260
)
public Boolean autoProvisionIdentityCenter = false;
/** IAM Identity Center (SSO) Instance ARN */
@ConfigField(
displayName = "SSO Instance ARN",
description = "ARN of IAM Identity Center instance",
category = "security",
visibleWhen = "oidcProvider == identity-center",
order = 270
)
public String ssoInstanceArn = null;
/** SSO Group ID */
@ConfigField(
displayName = "SSO Group ID",
description = "IAM Identity Center group UUID",
category = "security",
visibleWhen = "oidcProvider == identity-center",
order = 275
)
public String ssoGroupId = null;
/** SSO Target Account ID */
@ConfigField(
displayName = "SSO Target Account ID",
description = "12-digit AWS account ID for SSO target",
category = "security",
pattern = "^\\d{12}$",
visibleWhen = "oidcProvider == identity-center",
order = 278
)
public String ssoTargetAccountId = null;
/** Identity Center group name for user assignment */
@ConfigField(
displayName = "Identity Center Group",
description = "IAM Identity Center group for user assignment",
category = "security",
visibleWhen = "oidcProvider == identity-center",
order = 280
)
public String identityCenterGroupName = null;
// ========== Database Configuration ==========
/**
* Provision RDS database for application.
* Only shown for applications with optional database support (e.g., Metabase, Grafana).
* Applications requiring database (e.g., Mattermost, GitLab) always provision one.
*/
@ConfigField(
displayName = "Provision RDS Database",
description = "Create managed RDS database for high availability and automatic backups",
category = "database",
visibleWhen = "supportsDatabase && !requiresDatabase",
required = false,
order = 10
)
public Boolean provisionDatabase = false;
/**
* Database engine (e.g., postgres, mysql, mariadb).
* Default comes from ApplicationSpec.databaseRequirement().engine()
*
* <p><b>Deliberately no static default here (nor on the other {@code database*} fields below
* with a {@code defaultFrom}):</b> {@link com.cloudforge.core.config.DeploymentContextPreparer}
* only applies a field's {@code defaultFrom}-resolved ApplicationSpec value when the field is
* currently null/blank, so a non-null Java initializer would always look "already set" and
* block that resolution. Mixing a caller-supplied engine with a static default version can
* then produce an invalid combination (e.g. {@code databaseEngine=mysql} with a Postgres
* version yields a nonexistent "mysql15" RDS parameter group family). {@code cloudforge-api}'s
* {@code ApplicationFactory} merges these fields against
* {@code ApplicationSpec.databaseRequirement()} with the same null-check pattern.</p>
*/
@ConfigField(
displayName = "Database Engine",
description = "RDS database engine type",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
allowedValues = {"postgres", "mysql", "mariadb"},
defaultFrom = "databaseRequirement().engine",
example = "postgres",
order = 20
)
public String databaseEngine;
/**
* Database engine version.
* Default comes from ApplicationSpec.databaseRequirement().effectiveDefaultVersion(). The
* application's declared version is a minimum: a higher version may be chosen, a lower one is
* rejected, and a version above the application's tested maximum is accepted with a warning.
*/
@ConfigField(
displayName = "Database Version",
description = "Database engine version (e.g., 15 for PostgreSQL 15)",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
example = "15",
defaultFrom = "databaseRequirement().effectiveDefaultVersion",
order = 30
)
public String databaseVersion;
/**
* RDS instance class (e.g., db.t3.small, db.m5.large).
* DESTRUCTIVE: Changing this requires resource replacement.
* BILLING_IMPACT: Larger instances cost more.
*/
@ConfigField(
displayName = "Database Instance Class",
description = "RDS instance type - larger instances provide more CPU/memory",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
// Choices come from InstanceCatalog.databaseInstanceClasses().
example = "db.t3.small",
defaultFrom = "databaseRequirement().instanceClass",
tags = {FieldTag.DESTRUCTIVE, FieldTag.BILLING_IMPACT},
order = 40
)
public String databaseInstanceClass;
/**
* Allocated storage in GB.
* BILLING_IMPACT: More storage costs more.
*/
@ConfigField(
displayName = "Database Storage (GB)",
description = "Allocated storage for RDS database in GB",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
min = 20,
max = 65536,
example = "100",
defaultFrom = "databaseRequirement().allocatedStorageGB",
tags = {FieldTag.BILLING_IMPACT},
order = 50
)
public Integer databaseAllocatedStorageGB;
/**
* Enable Multi-AZ deployment for high availability.
* BILLING_IMPACT: Multi-AZ doubles database costs.
*
* <p>Deliberately no static default: any non-null value is treated as an explicit override of
* the security profile, so a {@code false} initializer would keep PRODUCTION and
* compliance-driven Multi-AZ from ever applying. When unset, the profile decides.</p>
*/
@ConfigField(
displayName = "Multi-AZ Deployment",
description = "Deploy database across multiple availability zones for high availability",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
tags = {FieldTag.BILLING_IMPACT},
order = 60
)
public Boolean databaseMultiAz;
/**
* Automatic minor-version upgrades for the RDS instance.
*
* <p>Deliberately no static default: unset lets RdsFactory fall back to its
* security-profile default (PRODUCTION only). A {@code false} initializer would
* make an explicit override indistinguishable from "not set".</p>
*/
@ConfigField(
displayName = "RDS Auto Minor Version Upgrade",
description = "Apply RDS engine minor-version security patches automatically",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
order = 61
)
public Boolean rdsAutoMinorVersionUpgrade;
/**
* RDS Performance Insights (query-level performance monitoring).
*
* <p>Deliberately no static default -- see {@link #databaseMultiAz}. RdsFactory
* also skips this regardless of override on instance classes too small to support it
* (e.g. db.t3.micro).</p>
*/
@ConfigField(
displayName = "RDS Performance Insights",
description = "Enable Performance Insights for query-level RDS monitoring",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
order = 62
)
public Boolean performanceInsightsEnabled;
/**
* RDS Enhanced Monitoring (OS-level metrics at a configurable interval).
*
* <p>Deliberately no static default -- see {@link #databaseMultiAz}.</p>
*/
@ConfigField(
displayName = "RDS Enhanced Monitoring",
description = "Enable RDS Enhanced Monitoring for real-time OS metrics",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
order = 63
)
public Boolean rdsEnhancedMonitoringEnabled;
/**
* Optional number of RDS read replicas. When unset, an application may provide
* its own default (CloudForge Manager defaults to one); zero explicitly disables
* replicas for applications that support them.
*/
@ConfigField(
displayName = "Database Read Replicas",
description = "Number of read-only RDS replicas to deploy (0 disables replicas)",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
min = 0,
max = 5,
example = "1",
tags = {FieldTag.BILLING_IMPACT},
order = 65
)
public Integer databaseReadReplicaCount = null;
/**
* Database name.
* IMMUTABLE: Cannot be changed after creation.
*/
@ConfigField(
displayName = "Database Name",
description = "Initial database name to create",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
pattern = "^[a-zA-Z][a-zA-Z0-9_]{0,62}$",
example = "appdb",
defaultFrom = "databaseRequirement().databaseName",
tags = {FieldTag.IMMUTABLE},
order = 70
)
public String databaseName;
/**
* Backup retention period in days.
* Compliance frameworks may override: PCI-DSS (90 days), HIPAA (30 days), SOC2 (14 days).
*/
@ConfigField(
displayName = "Backup Retention (Days)",
description = "Number of days to retain automated backups (0 = disabled)",
category = "database",
visibleWhen = "provisionDatabase",
dependsOn = "provisionDatabase",
min = 0,
max = 35,
example = "7",
order = 80
)
public Integer databaseBackupRetentionDays = 7;
// ========== Redis Session Store (CloudForge Manager) ==========
/**
* Provision an ElastiCache Redis cluster and bind CloudForge Manager's own container to it
* as a shared session store (CFC_MANAGER_SESSION_MODE=redis), so every Manager instance
* behind the same ALB recognizes sessions any of the others created — the missing piece for
* running Manager itself horizontally scaled. Only {@code ApplicationFactory} acts on this,
* and only when {@code applicationId == "cloudforge-manager"}; {@code visibleWhen} below keeps
* it out of every other application's deploy form for the same reason. Requires RDS
* (embedded H2 isn't safe to share across instances) — see {@code
* ManagerDeploymentPreset.rdsWithRedisSessions(...)} in cloudforge-manager-deployment, the
* typed pairing this mirrors. Single-instance Manager (the common case) needs nothing beyond
* the in-memory default and should leave this false.
*/
@ConfigField(
displayName = "Redis Session Store",
description = "Provision ElastiCache Redis and share sessions across every CloudForge "
+ "Manager instance — required for running Manager itself horizontally scaled behind an ALB",
category = "database",
visibleWhen = "applicationId == \"cloudforge-manager\" && provisionDatabase",
dependsOn = "provisionDatabase",
tags = {FieldTag.BILLING_IMPACT},
order = 90
)
public Boolean provisionManagerRedisSessions = false;
/**
* Provision a dedicated AWS Secrets Manager entry holding the AES cipher key CloudForge
* Manager uses to encrypt cross-account connection secrets (external IDs) at rest — see
* {@code SecretCipher}/{@code AesGcmSecretCipher} in cloudforge-manager. Injected into
* Manager's own ECS task as {@code CFC_MANAGER_ACCOUNT_SECRET_KEY} via {@code
* ecs.Secret.fromSecretsManager(...)}, mirroring how {@code CFC_MANAGER_DATABASE_PASSWORD}
* is already delivered — never a literal value in the task definition. Only {@code
* ApplicationFactory} acts on this, and only when {@code applicationId ==
* cloudforge-manager}. Defaults to {@code true} (unlike {@code
* provisionManagerRedisSessions}) because without it Manager falls back to {@code
* PlaintextSecretCipher} — leaving it on is the secure-by-default choice for any AWS
* deployment of Manager, not an opt-in scaling feature like Redis. Independent of {@code
* provisionDatabase}: even an embedded-H2 Manager deployment benefits from encrypting the
* secrets it holds.
*/
@ConfigField(
displayName = "Account Connection Cipher Key",
description = "Provision a Secrets Manager entry for the AES key CloudForge Manager uses "
+ "to encrypt cross-account connection secrets — leave enabled unless you are "
+ "supplying CFC_MANAGER_ACCOUNT_SECRET_KEY through another mechanism",
category = "database",
visibleWhen = "applicationId == \"cloudforge-manager\"",
tags = {FieldTag.BILLING_IMPACT},
order = 91
)
public Boolean provisionManagerAccountCipherKey = true;
/**
* A customer's LicenseSeat license key ({@code CFC-XXXX-XXXX-XXXX-XXXX}), injected at deploy
* time so an install activates immediately instead of requiring a follow-up visit to the
* owner-only License settings screen after the stack comes up. When set, {@code
* ApplicationFactory} provisions a dedicated Secrets Manager entry for it and {@code
* ContainerFactory} binds that as {@code CFC_MANAGER_LICENSESEAT_LICENSE_KEY} — the exact
* env var {@code ManagerRuntimeConfiguration} reads for its environment-supplied license key
* (see its {@code LicenseSeat} record javadoc, cloudforge-manager). Purely additive: the in-app License
* settings screen still works, and a key activated through it still takes precedence (see
* {@code LicenseKeyStore}, cloudforge-manager) — this only seeds the initial value.
*
* <p>{@code category = "database"} despite not being a database setting: it sits right next
* to {@code provisionManagerAccountCipherKey} above for the same reason that field does —
* {@code InteractiveDeployer.configureDatabaseOptions()} is the one place that currently
* discovers and prompts for manager-only fields by reflection ({@code
* ConfigurationIntrospector.discoverVisibleFields(..., "database")}); a "security"
* category discovery pass would also surface ~30 unrelated existing security fields that
* were never meant for CLI prompting.
*/
@ConfigField(
displayName = "License Key",
description = "LicenseSeat customer license key (CFC-XXXX-XXXX-XXXX-XXXX) to activate this "
+ "install with on first boot, delivered via a dedicated Secrets Manager entry",
category = "database",
visibleWhen = "applicationId == \"cloudforge-manager\"",
sensitive = true,
order = 92
)
public String managerLicenseKey;
/** Enable RDS deletion protection remediation */
@ConfigField(
displayName = "RDS Deletion Protection Remediation",
description = "Enable automatic remediation of RDS deletion protection compliance violations",
category = "compliance",
visibleWhen = "provisionDatabase && awsConfigEnabled",
dependsOn = "awsConfigEnabled",
order = 400
)
public Boolean enableRdsDeletionProtectionRemediation = false;
/** Enable RDS auto minor version upgrade remediation */
@ConfigField(
displayName = "RDS Auto Minor Version Upgrade Remediation",
description = "Enable automatic remediation of RDS auto minor version upgrade compliance violations",
category = "compliance",
visibleWhen = "provisionDatabase && awsConfigEnabled",
dependsOn = "awsConfigEnabled",
order = 410
)
public Boolean enableRdsAutoMinorVersionUpgradeRemediation = false;
// ========== Compliance Configuration ==========
/**
* Compliance frameworks to enable.
*
* <p>Supports comma-separated string format in JSON for backward compatibility:
* <pre>{"complianceFrameworks": "soc2,pci-dss,hipaa"}</pre>
*
* <p>In Java code, use the type-safe List:
* <pre>config.complianceFrameworks.contains(ComplianceFrameworkType.HIPAA)</pre>
*/
@ConfigField(
displayName = "Compliance Frameworks",
description = "Compliance frameworks to enable (soc2, pci-dss, hipaa, gdpr)",
category = "compliance",
// allowedValues lets UIs render checkboxes with the exact expected tokens (matching
// ComplianceFrameworkType's JSON values) instead of a free-text list prone to typos.
allowedValues = {"soc2", "pci-dss", "hipaa", "gdpr"},
example = "soc2,pci-dss",
order = 300
)
@JsonDeserialize(using = ComplianceFrameworkListConverter.Deserializer.class)
@JsonSerialize(using = ComplianceFrameworkListConverter.Serializer.class)
public List<ComplianceFrameworkType> complianceFrameworks = new ArrayList<>();
/**
* Raw compliance-framework token override for an on-demand advisory check (see
* {@code CloudForgeSynthesizer#synthesizeAdvisoryDryRun}) -- lets a caller ask cdk-nag /
* FrameworkRules to validate against tokens with no {@link ComplianceFrameworkType} entry at
* all (e.g. {@code "ISO-27001"}, or {@code "AWS-BEST-PRACTICES"} for cdk-nag's own generic
* {@code AwsSolutionsChecks} fallback pack — see {@code SecurityRules#mapFrameworkToNagPack}'s
* {@code default} case), without extending the license-gated {@link #complianceFrameworks}
* enum real deploys use. Comma-separated, same format {@link #complianceFrameworks} itself
* serializes to.
*
* <p>Deliberately NOT part of {@link #toContextMap}'s output: the "cfc" context map it builds
* gets round-tripped back through a strict {@code DeploymentConfig} deserialization inside
* {@code DeploymentContext.from} (jsii construct-tree plumbing), and that round trip enforces
* the same {@link ComplianceFrameworkType} enum this field exists specifically to bypass —
* feeding it through that key would just re-throw the same "unknown compliance framework"
* error one layer later. {@code CloudForgeSynthesizer#synthesize} instead seeds this under its
* own separate, sibling top-level CDK context key (not nested in "cfc" at all), and {@code
* SecurityRules#install} reads it directly off the construct tree rather than through {@code
* DeploymentContext}. {@code transient}/{@code @JsonIgnore} since it must never round-trip
* through a saved deployment context the way every other field here does.</p>
*/
@JsonIgnore
public transient String complianceFrameworksRawOverride;
/** Legacy single-framework override, superseded by {@link #complianceFrameworks} (which
* supports multiple). Kept for backward compatibility with older deployment contexts. */
@ConfigField(
displayName = "Audit Manager Framework ID (legacy)",
description = "Single compliance framework to enable (legacy -- prefer complianceFrameworks)",
category = "compliance",
order = 301
)
public String auditManagerFrameworkId = null;
/**
* Compliance validation mode controlling how validation failures are handled.
*/
@ConfigField(
displayName = "Compliance Mode",
description = "How to handle compliance validation failures",
category = "compliance",
allowedValues = {"enforce", "advisory", "disabled"},
order = 310
)
public ComplianceMode complianceMode; // null = use defaultForProfile(securityProfile)
/** CloudWatch Logs retention days */
@ConfigField(
displayName = "Log Retention (Days)",
description = "CloudWatch Logs retention period in days",
category = "monitoring",
allowedValues = {"1", "3", "5", "7", "14", "30", "60", "90", "120", "150", "180", "365", "400", "545", "731", "1096", "1827", "2192", "2557", "2922", "3288", "3653"},
order = 10
)
public String logRetentionDays = null;
// ========== Monitoring Configuration ==========
/** Enable CloudWatch monitoring */
@ConfigField(
displayName = "Enable Monitoring",
description = "Enable CloudWatch metrics and alarms",
category = "monitoring",
order = 20
)
public Boolean enableMonitoring = true;
/** Enable encryption at rest */
@ConfigField(
displayName = "Enable Encryption",
description = "Enable encryption at rest for all resources",
category = "security",
order = 320
)
public Boolean enableEncryption = true;
/** Enable AWS Config */
@ConfigField(
displayName = "Enable AWS Config",
description = "Enable AWS Config for configuration compliance monitoring",
category = "compliance",
order = 30
)
public Boolean awsConfigEnabled = false;
/** Create AWS Config infrastructure — an account+region-wide singleton (AWS allows exactly
* one recorder and one delivery channel per account per region), so this must stay an
* explicit, single-stack-only opt-in. {@code false} by default, same as {@link
* #awsConfigEnabled}: unset means neither compliance rules nor the recorder/channel they'd
* attach to ever get created. */
@ConfigField(
displayName = "Create Config Infrastructure",
description = "Create AWS Config recorder and delivery channel (only one per region — "
+ "only ever set this true on exactly one stack per account/region)",
category = "compliance",
visibleWhen = "awsConfigEnabled == true",
order = 40
)
public Boolean createConfigInfrastructure = false;
/** Scope AWS Config rules to only monitor this deployment's own resources (by stack name)
* instead of every resource in the account/region. */
@ConfigField(
displayName = "Scope Config Rules To Deployment",
description = "Limit AWS Config rule monitoring to this deployment's own resources",
category = "compliance",
visibleWhen = "awsConfigEnabled == true",
order = 45
)
public Boolean scopeConfigRulesToDeployment = false;
/** Enable GuardDuty threat detection.
* Deliberately no static default -- see {@link #macieEnabled}. isGuardDutyEnabled() falls
* back to "always enabled for production" when unset; a {@code false} initializer here
* would make every deployment look like an explicit opt-out, permanently skipping both that
* fallback and the compliance-matrix requirement check ahead of it. */
@ConfigField(
displayName = "Enable GuardDuty",
description = "Enable Amazon GuardDuty for threat detection",
category = "compliance",
order = 50
)
public Boolean guardDutyEnabled;
/** Create GuardDuty detector (account-region singleton).
* Deliberately no static default -- see {@link #guardDutyEnabled} above; GuardDutyFactory
* only auto-sets this to true when it's unset. */
@ConfigField(
displayName = "Create GuardDuty Detector",
description = "Create GuardDuty detector (only one per account/region)",
category = "compliance",
visibleWhen = "guardDutyEnabled == true",
order = 60
)
public Boolean createGuardDutyDetector;
/** GuardDuty alerts configured (EventBridge to SNS/SIEM) */
@ConfigField(
displayName = "Configure GuardDuty Alerts",
description = "Configure EventBridge rules to forward GuardDuty findings to SNS/SIEM",
category = "compliance",
visibleWhen = "guardDutyEnabled == true",
order = 65
)
public Boolean guardDutyAlertsConfigured = false;
/** Certificate expiration monitoring enabled */
@ConfigField(
displayName = "Certificate Expiration Monitoring",
description = "Enable CloudWatch alarms for ACM certificate expiration",
category = "monitoring",
visibleWhen = "enableSsl == true",
order = 68
)
public Boolean certificateExpirationMonitoring = false;
/** Enable CloudTrail for API audit logging */
@ConfigField(
displayName = "Enable CloudTrail",
description = "Enable CloudTrail for API audit logging",
category = "compliance",
order = 70
)
public Boolean cloudTrailEnabled = false;
/** Enable security monitoring */
@ConfigField(
displayName = "Security Monitoring",
description = "Enable security monitoring features",
category = "compliance",
order = 72
)
public Boolean securityMonitoringEnabled = false;
/** Enable EFS encryption in transit */
@ConfigField(
displayName = "EFS Encryption in Transit",
description = "Enable EFS encryption in transit for data protection",
category = "security",
order = 340
)
public Boolean efsEncryptionInTransitEnabled = true;
/** Restrict security group egress to VPC CIDR only (requires VPC endpoints for AWS service access) */
@ConfigField(
displayName = "Restrict Security Group Egress",
description = "Restrict security group egress to VPC CIDR only. Requires VPC endpoints for AWS services (CloudWatch, RDS monitoring). If false, allows 0.0.0.0/0 egress (default CDK behavior).",
category = "security",
order = 345
)
public Boolean restrictSecurityGroupEgress = false;
/** Enable automated backups (null = use security profile default) */
@ConfigField(
displayName = "Automated Backups",
description = "Enable automated backups for EFS and databases (null = profile default: PRODUCTION=true, others=false)",
category = "storage",
order = 50
)
public Boolean automatedBackupEnabled = null;
/** Enable cross-region backups (null = use security profile default) */
@ConfigField(
displayName = "Cross-Region Backups",
description = "Enable cross-region backup replication for disaster recovery (null = profile default)",
category = "storage",
tags = {FieldTag.BILLING_IMPACT},
order = 60
)
public Boolean crossRegionBackupEnabled = null;
/** ARN of the AWS Backup vault, in another region, that backups are copied to. */
@ConfigField(
displayName = "Cross-Region Backup Vault ARN",
description = "Destination backup vault for cross-region copies; no copy is made when unset",
category = "storage",
visibleWhen = "crossRegionBackupEnabled",
order = 65
)
public String backupCrossRegionVaultArn;
// ========== Advanced Monitoring & Threat Protection ==========
/** Enable Amazon Macie for PII/PHI discovery (HIPAA/GDPR).
* Deliberately no static default -- see {@link #crossRegionBackupEnabled}. Left unset,
* isMacieEnabled() falls through to the compliance-matrix requirement and then the
* profile default; a {@code false} initializer here would make every deployment look like
* an explicit opt-out, permanently skipping both. */
@ConfigField(
displayName = "Enable Macie",
description = "Enable Amazon Macie for PII/PHI discovery (required for HIPAA/GDPR)",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
order = 80
)
public Boolean macieEnabled;
/** Enable Macie automated discovery jobs */
@ConfigField(
displayName = "Macie Auto-Discovery",
description = "Enable Macie automated discovery jobs for continuous scanning",
category = "compliance",
visibleWhen = "macieEnabled == true",
dependsOn = "macieEnabled",
tags = {FieldTag.BILLING_IMPACT},
order = 90
)
// Deliberately no static default -- see macieEnabled above; isMacieAutomatedDiscoveryEnabled()
// falls back to isMacieEnabled() when unset.
public Boolean macieAutomatedDiscovery;
/** Enable AWS Security Hub for centralized security findings.
* Deliberately no static default -- see {@link #macieEnabled} above. */
@ConfigField(
displayName = "Enable Security Hub",
description = "Enable AWS Security Hub for centralized security findings",
category = "compliance",
order = 100
)
public Boolean securityHubEnabled;
/** Enable the CIS AWS Foundations Benchmark standard in Security Hub */
@ConfigField(
displayName = "Security Hub: CIS Benchmark",
description = "Enable the CIS AWS Foundations Benchmark standard",
category = "compliance",
visibleWhen = "securityHubEnabled == true",
dependsOn = "securityHubEnabled",
order = 101
)
public Boolean securityHubCisEnabled = false;
/**
* CIS AWS Foundations Benchmark version to enable. AWS has shipped 1.2.0, 1.4.0, 3.0.0,
* and 5.0.0 as coexisting standard versions over time -- configurable so a future version
* bump is a config change, not a code change.
*/
@ConfigField(
displayName = "Security Hub: CIS Benchmark Version",
description = "CIS AWS Foundations Benchmark version to enable",
category = "compliance",
visibleWhen = "securityHubCisEnabled == true",
dependsOn = "securityHubCisEnabled",
example = "5.0.0",
order = 101
)
public String securityHubCisVersion = "5.0.0";
/** Enable the AWS Foundational Security Best Practices standard in Security Hub */
@ConfigField(
displayName = "Security Hub: AWS Foundational Security Best Practices",
description = "Enable the AWS Foundational Security Best Practices standard",
category = "compliance",
visibleWhen = "securityHubEnabled == true",
dependsOn = "securityHubEnabled",
order = 102
)
public Boolean securityHubAwsFoundationalEnabled = false;
/**
* AWS Foundational Security Best Practices version to enable. Has stayed at 1.0.0 since
* the standard launched in 2020 (controls are added within the version, not by version
* bump), but configurable for consistency with the other two standards.
*/
@ConfigField(
displayName = "Security Hub: FSBP Version",
description = "AWS Foundational Security Best Practices version to enable",
category = "compliance",
visibleWhen = "securityHubAwsFoundationalEnabled == true",
dependsOn = "securityHubAwsFoundationalEnabled",
example = "1.0.0",
order = 103
)
public String securityHubFsbpVersion = "1.0.0";
/** Enable the PCI DSS standard in Security Hub */
@ConfigField(
displayName = "Security Hub: PCI DSS",
description = "Enable the PCI DSS standard",
category = "compliance",
visibleWhen = "securityHubEnabled == true",
dependsOn = "securityHubEnabled",
order = 104
)
public Boolean securityHubPciDssEnabled = false;
/**
* PCI DSS version to enable. 4.0.1 is current -- the PCI Council retired the prior
* version, and AWS Security Hub follows suit. Configurable for the next version bump.
*/
@ConfigField(
displayName = "Security Hub: PCI DSS Version",
description = "PCI DSS version to enable",
category = "compliance",
visibleWhen = "securityHubPciDssEnabled == true",
dependsOn = "securityHubPciDssEnabled",
example = "4.0.1",
order = 105
)
public String securityHubPciDssVersion = "4.0.1";
/** Enable Amazon Inspector for vulnerability scanning.
* Deliberately no static default -- see {@link #macieEnabled} above. */
@ConfigField(
displayName = "Enable Inspector",
description = "Enable Amazon Inspector for vulnerability scanning",
category = "compliance",
order = 110
)
public Boolean inspectorEnabled;
/** Enable Inspector EC2 instance scanning */
@ConfigField(
displayName = "Inspector: EC2 Scanning",
description = "Enable Inspector vulnerability scanning for EC2 instances",
category = "compliance",
visibleWhen = "inspectorEnabled == true",
dependsOn = "inspectorEnabled",
order = 111
)
public Boolean inspectorEc2Scanning = true;
/** Enable Inspector ECR image scanning */
@ConfigField(
displayName = "Inspector: ECR Scanning",
description = "Enable Inspector vulnerability scanning for ECR container images",
category = "compliance",
visibleWhen = "inspectorEnabled == true",
dependsOn = "inspectorEnabled",
order = 112
)
public Boolean inspectorEcrScanning = true;
/** Enable anti-malware scanning */
@ConfigField(
displayName = "Anti-Malware Scanning",
description = "Enable anti-malware scanning for uploaded files",
category = "compliance",
order = 120
)
public Boolean antiMalwareEnabled = false;
/** Enable file integrity monitoring */
@ConfigField(
displayName = "File Integrity Monitoring",
description = "Monitor critical files for unauthorized changes",
category = "compliance",
order = 130
)
public Boolean fileIntegrityMonitoring = false;
/** Enable container runtime security monitoring */
@ConfigField(
displayName = "Container Runtime Security",
description = "Monitor container runtime for suspicious activity",
category = "compliance",
order = 140
)
public Boolean containerRuntimeSecurity = false;
/** Enable container image vulnerability scanning */
@ConfigField(
displayName = "Container Image Scanning",
description = "Scan container images for known vulnerabilities",
category = "compliance",
order = 150
)
public Boolean containerImageScanning = false;
/** Enable AWS Audit Manager */
@ConfigField(
displayName = "Enable Audit Manager",
description = "Enable AWS Audit Manager for compliance evidence collection",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
order = 160
)
public Boolean auditManagerEnabled = false;
/**
* Enable the AWS Audit Manager service itself (null = use security profile default).
*
* <p>This is distinct from {@link #auditManagerEnabled}, which is the master gate
* {@code SecurityRules.install()} uses to decide whether any CloudForge FrameworkRules
* compliance validation runs at all. This field is the independent, configurable fact of
* whether the Audit Manager service should actually be provisioned/enabled for a given
* deployment -- it is what {@code SecurityProfileConfiguration#isAuditManagerEnabled()}
* reads.</p>
*/
@ConfigField(
displayName = "Enable Audit Manager Service",
description = "Provision the AWS Audit Manager service for this deployment (null = profile default: PRODUCTION=true, others=false)",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
order = 165
)
public Boolean auditManagerServiceEnabled = null;
/** Enable CloudWatch Logs KMS encryption */
@ConfigField(
displayName = "CloudWatch Logs KMS Encryption",
description = "Encrypt CloudWatch Logs with customer-managed KMS keys (required for PCI-DSS, HIPAA, SOC2)",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
order = 170
)
public Boolean cloudWatchLogsKmsEncryptionEnabled = false;
/** Enable CloudTrail Insights */
@ConfigField(
displayName = "CloudTrail Insights",
description = "Enable CloudTrail Insights for API activity anomaly detection (required for SOC2, NIST)",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
visibleWhen = "cloudTrailEnabled == true",
dependsOn = "cloudTrailEnabled",
order = 180
)
public Boolean cloudTrailInsightsEnabled = false;
/** Enable Route53 Query Logging */
@ConfigField(
displayName = "Route53 Query Logging",
description = "Enable DNS query logging for Route53 hosted zones (required for SOC2, NIST)",
category = "compliance",
order = 190
)
public Boolean route53QueryLoggingEnabled = false;
/** Enable S3 Object Lock for audit buckets (HIPAA/PCI-DSS immutability requirement) */
@ConfigField(
displayName = "S3 Object Lock",
description = "Enable S3 Object Lock for compliance audit buckets to ensure immutability (HIPAA § 164.312(c)(1), PCI-DSS Req 10.7)",
category = "compliance",
order = 195
)
public Boolean s3ObjectLockEnabled = false;
/** Enable EBS volume encryption (null = profile default: PRODUCTION/STAGING=true unless the
* compliance matrix already requires it, DEV=true). Independent of {@link #efsEncryptionAtRestEnabled}
* and {@link #s3EncryptionEnabled} -- every framework's Rules.java reads these as 3 separate controls. */
@ConfigField(
displayName = "EBS Encryption",
description = "Encrypt EBS volumes at rest (required for PCI-DSS, HIPAA, SOC2, GDPR)",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
order = 196
)
public Boolean ebsEncryptionEnabled = null;
/** Enable EFS at-rest encryption (null = profile default). See {@link #ebsEncryptionEnabled}. */
@ConfigField(
displayName = "EFS Encryption at Rest",
description = "Encrypt EFS file systems at rest (required for PCI-DSS, HIPAA, SOC2, GDPR)",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
order = 197
)
public Boolean efsEncryptionAtRestEnabled = null;
/** Enable S3 bucket encryption (null = profile default). See {@link #ebsEncryptionEnabled}. */
@ConfigField(
displayName = "S3 Encryption",
description = "Encrypt S3 buckets at rest (required for PCI-DSS, HIPAA, SOC2, GDPR)",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
order = 198
)
public Boolean s3EncryptionEnabled = null;
/** Enable AWS Backup Vault Lock (null = profile default: false except when the compliance
* matrix requires BACKUP_RECOVERY). Vault Lock makes the vault's retention policy immutable. */
@ConfigField(
displayName = "Backup Vault Lock",
description = "Lock the AWS Backup vault's retention policy (required for PCI-DSS, HIPAA when enforced)",
category = "compliance",
order = 199
)
public Boolean backupVaultLockEnabled = null;
/** Retain the AWS Backup vault on stack deletion (null = profile default). */
@ConfigField(
displayName = "Backup Vault Retention",
description = "Retain the AWS Backup vault when the stack is deleted",
category = "compliance",
order = 200
)
public Boolean backupVaultRetentionEnabled = null;
/** Enable RDS deletion protection (null = profile default: PRODUCTION=false unless the
* compliance matrix requires DELETION_PROTECTION, STAGING/DEV=false). */
@ConfigField(
displayName = "RDS Deletion Protection",
description = "Block accidental deletion of the RDS instance",
category = "compliance",
order = 201
)
public Boolean rdsDeletionProtectionEnabled = null;
/** Enable RDS Multi-AZ (null = profile default: PRODUCTION=true, STAGING/DEV=false unless the
* compliance matrix requires DATABASE_MULTI_AZ). */
@ConfigField(
displayName = "RDS Multi-AZ",
description = "Deploy the RDS instance across multiple availability zones",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
order = 202
)
public Boolean rdsDatabaseMultiAzEnabled = null;
/** Enable SNS topic KMS encryption (null = profile default: false unless the compliance
* matrix requires SNS_KMS_ENCRYPTION). */
@ConfigField(
displayName = "SNS KMS Encryption",
description = "Encrypt SNS topics with a customer-managed KMS key",
category = "compliance",
tags = {FieldTag.BILLING_IMPACT},
order = 203
)
public Boolean snsKmsEncryptionEnabled = null;
/** Require IMDSv2 on EC2 instances (null = profile default: PRODUCTION/STAGING=true unless the
* compliance matrix requires EC2_IMDSV2, DEV=false). ADVISORY for PCI-DSS and GDPR -- this
* field is what lets a deployer opt out where the framework doesn't mandate it. */
@ConfigField(
displayName = "IMDSv2 Required",
description = "Require EC2 Instance Metadata Service Version 2 (blocks the legacy v1 endpoint)",
category = "compliance",
order = 204
)
public Boolean imdsv2Required = null;
/** Enable S3 versioning remediation */
@ConfigField(
displayName = "S3 Versioning Remediation",
description = "Enable automatic remediation of S3 versioning compliance violations",
category = "compliance",
visibleWhen = "awsConfigEnabled == true",
order = 420
)
public Boolean enableS3VersioningRemediation = false;
/** Enable CloudTrail bucket access logging remediation */
@ConfigField(
displayName = "CloudTrail Bucket Access Logging Remediation",
description = "Enable automatic remediation of CloudTrail S3 bucket access logging violations",
category = "compliance",
visibleWhen = "awsConfigEnabled == true && cloudTrailEnabled == true",
order = 430
)
public Boolean enableCloudTrailBucketAccessRemediation = false;
/** Enable GuardDuty finding remediation (null = profile default: auto-enabled in PRODUCTION,
* same pattern as {@link #imdsv2Required} -- ComplianceFactory's consumers treat {@code null}
* as "PRODUCTION enables it, otherwise off" and an explicit value as an override either way,
* so this must NOT get a static default or that fallback can never trigger. */
@ConfigField(
displayName = "GuardDuty Remediation",
description = "Enable automatic remediation of GuardDuty findings",
category = "compliance",
visibleWhen = "awsConfigEnabled == true",
order = 440
)
public Boolean enableGuardDutyRemediation;
/** Enable Security Hub finding remediation. Deliberately no static default -- see {@link
* #enableGuardDutyRemediation}. */
@ConfigField(
displayName = "Security Hub Remediation",
description = "Enable automatic remediation of Security Hub findings",
category = "compliance",
visibleWhen = "awsConfigEnabled == true",
order = 441
)
public Boolean enableSecurityHubRemediation;
/** Enable Inspector finding remediation. Deliberately no static default -- see {@link
* #enableGuardDutyRemediation}. */
@ConfigField(
displayName = "Inspector Remediation",
description = "Enable automatic remediation of Amazon Inspector findings",
category = "compliance",
visibleWhen = "awsConfigEnabled == true",
order = 442
)
public Boolean enableInspectorRemediation;
/** Enable Macie finding remediation. Deliberately no static default -- see {@link
* #enableGuardDutyRemediation}. */
@ConfigField(
displayName = "Macie Remediation",
description = "Enable automatic remediation of Amazon Macie findings",
category = "compliance",
visibleWhen = "awsConfigEnabled == true",
order = 443
)
public Boolean enableMacieRemediation;
// ========== Health Check Configuration ==========
@ConfigField(
displayName = "Health Check Grace Period (seconds)",
description = "Time to wait before starting health checks after container starts. GitLab needs 600s due to database migrations.",
category = "resources",
min = 60,
max = 900,
defaultFrom = "defaultHealthCheckGracePeriod",
order = 600
)
public int healthCheckGracePeriod = 300;
@ConfigField(
displayName = "Health Check Interval (seconds)",
description = "Time between health checks",
category = "resources",
min = 5,
max = 300,
order = 610
)
public int healthCheckInterval = 30;
@ConfigField(
displayName = "Health Check Timeout (seconds)",
description = "Time to wait for health check response",
category = "resources",
min = 2,
max = 60,
order = 620
)
public int healthCheckTimeout = 5;
@ConfigField(
displayName = "Healthy Threshold Count",
description = "Number of consecutive successful health checks before marking healthy",
category = "resources",
min = 1,
max = 10,
order = 630
)
public int healthyThreshold = 2;
@ConfigField(
displayName = "Unhealthy Threshold Count",
description = "Number of consecutive failed health checks before marking unhealthy",
category = "resources",
min = 1,
max = 10,
order = 640
)
public int unhealthyThreshold = 3;
// ========== Region Configuration ==========
/** AWS region (e.g., "us-east-1", "us-west-2") */
@ConfigField(
displayName = "AWS Region",
description = "AWS region for deployment",
category = "basic",
allowedValues = {
"us-east-1", "us-east-2", "us-west-1", "us-west-2",
"ca-central-1", "sa-east-1",
"eu-west-1", "eu-west-2", "eu-west-3", "eu-central-1", "eu-north-1",
"ap-south-1", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3",
"ap-southeast-1", "ap-southeast-2"
},
required = true,
order = 50
)
public String region = "us-east-1";
/**
* Target AWS account id to pin CDK's {@code Environment.account} to at synth time.
*
* <p>Deliberately <b>not</b> a {@code @ConfigField} — {@link
* com.cloudforge.core.config.ConfigurationIntrospector} and {@link
* com.cloudforge.core.config.DeploymentContextPreparer} both skip unannotated fields, so this
* never appears in the generated deployment-context form/schema and never gets a default
* resolved for it. It exists purely for callers that already know the target account
* programmatically (cross-account deploy: CloudForge Manager resolves this from an assumed
* IAM role before synthesis — see {@code cloudforge-manager}'s account-connection feature,
* which owns every concept of "which account" beyond this bare pass-through field).</p>
*
* <p>When {@code null} (the default), {@code cloudforge-api}'s {@code CloudForgeSynthesizer}
* falls back to {@code CDK_DEFAULT_ACCOUNT} if set, or omits the account, producing an
* account-agnostic template.</p>
*/
public String account;
/**
* GDPR data transfer approval flag for non-EU deployments.
*/
@ConfigField(
displayName = "GDPR Data Transfer Approved",
description = "Confirm proper data transfer mechanisms (SCCs, BCRs) are in place for non-EU deployments with GDPR",
category = "compliance",
visibleWhen = "complianceFrameworks contains gdpr",
dependsOn = "complianceFrameworks",
order = 330
)
public Boolean gdprDataTransferApproved = false;
/**
* Availability zone suffixes for deployment (region-relative — "a" means whichever zone the
* deploy's own region calls "a", not a specific full zone name, since the same suffix set is
* valid regardless of which region gets picked elsewhere in the form).
*/
@ConfigField(
displayName = "Availability Zones",
description = "Availability zone suffixes to deploy across (leave empty for automatic selection)",
category = "network",
allowedValues = {"a", "b", "c", "d"},
example = "a,b",
order = 5
)
public String[] availabilityZones;
/** Enable auto-scaling */
@ConfigField(
displayName = "Enable Auto-Scaling",
description = "Enable automatic scaling based on CPU utilization",
category = "resources",
order = 5
)
public Boolean enableAutoScaling = false;
// ========== JSON Serialization/Deserialization ==========
/**
* Creates a pre-configured ObjectMapper for DeploymentConfig serialization.
*
* <p>Configuration:
* <ul>
* <li>Field visibility (not getters) for public field serialization</li>
* <li>Enums serialized as strings</li>
* <li>Unknown properties ignored for forward compatibility</li>
* <li>Null values excluded from output</li>
* </ul>
*/
private static ObjectMapper createMapper() {
ObjectMapper mapper = JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
// serializationInclusion(Include) is deprecated since Jackson 2.20 in favor of
// defaultPropertyInclusion(Value); ALL_NON_NULL is the equivalent Value constant.
.defaultPropertyInclusion(JsonInclude.Value.ALL_NON_NULL)
.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
.build();
return mapper;
}
/**
* Load DeploymentConfig from a JSON file (e.g., deployment-context.json).
*
* @param path Path to the JSON file
* @return DeploymentConfig populated from JSON
* @throws IOException if file cannot be read or parsed
*/
public static DeploymentConfig fromFile(Path path) throws IOException {
return createMapper().readValue(path.toFile(), DeploymentConfig.class);
}
/**
* Load DeploymentConfig from a JSON file path string.
*
* @param filePath Path to the JSON file
* @return DeploymentConfig populated from JSON
* @throws IOException if file cannot be read or parsed
*/
public static DeploymentConfig fromFile(String filePath) throws IOException {
return fromFile(Path.of(filePath));
}
/**
* Load DeploymentConfig from a JSON string.
*
* @param json JSON string
* @return DeploymentConfig populated from JSON
* @throws JsonProcessingException if JSON cannot be parsed
*/
public static DeploymentConfig fromJson(String json) throws JsonProcessingException {
return createMapper().readValue(json, DeploymentConfig.class);
}
/**
* Load DeploymentConfig from a Map (e.g., CDK context).
*
* <p>Uses Jackson's type-safe conversion to handle:
* <ul>
* <li>String → Enum conversion via @JsonCreator methods</li>
* <li>String/Number → Boolean conversion (supports "1", "yes", "0", "no")</li>
* <li>Comma-separated strings → List conversion (complianceFrameworks)</li>
* <li>Unknown properties are ignored for forward compatibility</li>
* </ul>
*
* @param map Map containing configuration key-value pairs
* @return DeploymentConfig populated from the map
*/
public static DeploymentConfig fromMap(java.util.Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return new DeploymentConfig();
}
// Normalize the map: handle CDK context key rename and boolean string coercion
java.util.Map<String, Object> normalized = new java.util.LinkedHashMap<>(map);
if (normalized.containsKey("env") && !normalized.containsKey("environment")) {
normalized.put("environment", normalized.get("env"));
}
// Coerce boolean-like string values before Jackson processing
normalized.replaceAll((key, value) -> coerceBooleanIfNeeded(value));
return createMapper().convertValue(normalized, DeploymentConfig.class);
}
/**
* Coerces common boolean-like string values to actual Boolean objects.
* Supports: "yes", "on" → true; "no", "off" → false.
*
* <p>Note: "1" and "0" are NOT coerced to boolean because they could be valid
* integer values (e.g., minInstanceCapacity="1"). Jackson handles "true"/"false"
* natively, so we only need to handle non-standard boolean representations.</p>
*/
private static Object coerceBooleanIfNeeded(Object value) {
if (value instanceof String str) {
String lower = str.trim().toLowerCase();
return switch (lower) {
// Note: "1" and "0" removed - they're ambiguous with integer values
case "yes", "on" -> Boolean.TRUE;
case "no", "off" -> Boolean.FALSE;
default -> value; // Keep original string for Jackson to parse
};
}
return value;
}
/**
* Serialize this DeploymentConfig to a JSON string.
*
* @return JSON string representation
* @throws JsonProcessingException if serialization fails
*/
public String toJson() throws JsonProcessingException {
return createMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this);
}
/**
* Save this DeploymentConfig to a JSON file.
*
* @param path Path to write the JSON file
* @throws IOException if file cannot be written
*/
public void toFile(Path path) throws IOException {
createMapper().writerWithDefaultPrettyPrinter().writeValue(path.toFile(), this);
}
/**
* Save this DeploymentConfig to a JSON file path string.
*
* @param filePath Path to write the JSON file
* @throws IOException if file cannot be written
*/
public void toFile(String filePath) throws IOException {
toFile(Path.of(filePath));
}
// ========== Compliance Framework Helpers ==========
/**
* Returns the compliance frameworks as a comma-separated string.
* Provided for backward compatibility with code expecting the old string format.
*
* @return comma-separated framework string (e.g., "soc2,pci-dss,hipaa")
*/
public String getComplianceFrameworksAsString() {
return ComplianceFrameworkType.toCommaSeparated(complianceFrameworks);
}
/**
* Checks if a specific compliance framework is enabled.
*
* @param framework the framework to check
* @return true if the framework is in the list
*/
public boolean hasComplianceFramework(ComplianceFrameworkType framework) {
return complianceFrameworks != null && complianceFrameworks.contains(framework);
}
/**
* Checks if any compliance framework is enabled.
*
* @return true if at least one framework is configured
*/
public boolean hasAnyComplianceFramework() {
return complianceFrameworks != null && !complianceFrameworks.isEmpty();
}
// ========== CloudForge Manager ==========
/**
* Opt-in: grants CloudForge Manager's own task/instance role the direct-deploy IAM
* capability ({@code CFN_DEPLOY} — see {@code ManagerAwsCapabilityCatalog}),
* condition-scoped to CloudForge-tagged resources.
* Only has any effect when this deployment's {@code applicationId} is
* {@code cloudforge-manager}; a no-op for every other application. Defaults to false — Manager
* deploying AWS infrastructure on a caller's behalf (via {@code deploy:create}) is a
* materially broader permission grant than Manager's normal inventory/operator role, so it
* must be explicitly requested per deployment, not inherited automatically from IAM profile.
*/
@ConfigField(
displayName = "Manager Direct Deploy",
description = "Grant CloudForge Manager's task role permission to create/update AWS "
+ "infrastructure directly (CFN CreateStack/UpdateStack + Service Catalog "
+ "ProvisionProduct), scoped to CloudForge-tagged resources. Only applies when "
+ "applicationId is cloudforge-manager.",
category = "operations",
visibleWhen = "applicationId == \"cloudforge-manager\"",
required = false,
tags = {FieldTag.REQUIRES_APPROVAL, FieldTag.EXPERIMENTAL},
propertyKey = "cfc.manager.direct-deploy-enabled",
order = 9030
)
public Boolean managerDirectDeployEnabled = false;
/**
* Convert this DeploymentConfig to a Map for CDK context.
*
* <p>Special handling:
* <ul>
* <li>Renames "environment" to "env" for CDK compatibility</li>
* <li>Excludes null values</li>
* </ul>
*
* @return Map suitable for CDK App context
*/
@SuppressWarnings("unchecked")
public Map<String, Object> toContextMap() {
ObjectMapper mapper = createMapper();
Map<String, Object> context = mapper.convertValue(this, Map.class);
// CDK uses "env" not "environment"
if (context.containsKey("environment")) {
context.put("env", context.remove("environment"));
}
return context;
}
}