StandardIAMConfiguration.java
package com.cloudforgeci.api.core.iam;
import com.cloudforge.core.enums.TopologyType;
import com.cloudforge.core.enums.RuntimeType;
import com.cloudforge.core.enums.SecurityProfile;
import com.cloudforgeci.api.core.SystemContext;
import com.cloudforge.core.enums.IAMProfile;
import com.cloudforgeci.api.interfaces.IAMConfiguration;
import com.cloudforgeci.api.interfaces.Rule;
import io.github.cdklabs.cdknag.NagPackSuppression;
import io.github.cdklabs.cdknag.NagSuppressions;
import software.amazon.awscdk.services.iam.ManagedPolicy;
import software.amazon.awscdk.services.iam.PolicyStatement;
import software.amazon.awscdk.services.iam.Role;
import software.amazon.awscdk.services.iam.ServicePrincipal;
import java.util.List;
import static com.cloudforgeci.api.core.rules.RuleKit.require;
/**
* Standard IAM configuration with balanced permissions.
* Suitable for staging and development environments.
*
* Key features:
* - Standard operational permissions
* - Limited administrative access
* - Enhanced monitoring and logging
* - Backup and recovery permissions
*/
public final class StandardIAMConfiguration implements IAMConfiguration {
@Override
public IAMProfile kind() {
return IAMProfile.STANDARD;
}
@Override
public String id() {
return "iam:STANDARD";
}
@Override
public List<Rule> rules(SystemContext c) {
var rules = new java.util.ArrayList<Rule>();
rules.add(require("vpc", x -> x.vpc));
// Instance security group is only required for EC2 runtime
if (c.runtime == RuntimeType.EC2) {
rules.add(require("instance security group", x -> x.instanceSg));
}
rules.add(require("alb security group", x -> x.albSg));
rules.add(require("efs security group", x -> x.efsSg));
return rules;
}
@Override
public void wire(SystemContext c) {
// Create standard IAM roles based on runtime type
if (c.runtime == RuntimeType.EC2) {
createStandardEC2Role(c);
} else if (c.runtime == RuntimeType.FARGATE) {
createStandardFargateRoles(c);
}
}
private void createStandardEC2Role(SystemContext c) {
// Get account ID from stack
String accountId = software.amazon.awscdk.Stack.of(c).getAccount();
// Use stackName for application-aware log paths and bucket patterns
String appLogPattern = c.stackName != null && !c.stackName.isEmpty()
? "/aws/*/" + c.stackName + "*"
: "/aws/*";
String backupBucketPattern = c.stackName != null && !c.stackName.isEmpty()
? c.stackName.toLowerCase() + "-backup-*"
: "*-backup-*";
// For PRODUCTION, use Customer Managed Policies
if (c.security == SecurityProfile.PRODUCTION) {
var policyStatements = new java.util.ArrayList<PolicyStatement>();
// SSM Core permissions
policyStatements.add(PolicyStatement.Builder.create()
.sid("SSMCore")
.actions(List.of(
"ssm:UpdateInstanceInformation",
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel"
))
.resources(List.of("*")) // SSM requires wildcard
.build());
// SSM S3 access for agent downloads
policyStatements.add(PolicyStatement.Builder.create()
.sid("SSMS3Access")
.actions(List.of("s3:GetObject"))
.resources(List.of(
"arn:aws:s3:::aws-ssm-" + c.cfc.region() + "/*",
"arn:aws:s3:::aws-windows-downloads-" + c.cfc.region() + "/*",
"arn:aws:s3:::amazon-ssm-" + c.cfc.region() + "/*",
"arn:aws:s3:::amazon-ssm-packages-" + c.cfc.region() + "/*"
))
.build());
// CloudWatch Logs permissions
policyStatements.add(PolicyStatement.Builder.create()
.sid("StandardCloudWatchLogs")
.actions(List.of(
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutRetentionPolicy"
))
.resources(List.of(
"arn:aws:logs:" + c.cfc.region() + ":" + accountId + ":log-group:" + appLogPattern + ":*"
))
.build());
// CloudWatch Metrics permissions (service-level, requires wildcard)
policyStatements.add(PolicyStatement.Builder.create()
.sid("StandardCloudWatchMetrics")
.actions(List.of(
"cloudwatch:PutMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics"
))
.resources(List.of("*")) // CloudWatch metrics are service-level
.build());
// Add EFS permissions if EFS is used
if (c.efs.get().isPresent()) {
policyStatements.add(PolicyStatement.Builder.create()
.sid("StandardEfsAccess")
.actions(List.of(
"elasticfilesystem:ClientMount",
"elasticfilesystem:ClientWrite",
"elasticfilesystem:DescribeMountTargets",
"elasticfilesystem:DescribeFileSystems"
))
.resources(List.of(c.efs.get().orElseThrow().getFileSystemArn()))
.build());
}
// S3 backup permissions
policyStatements.add(PolicyStatement.Builder.create()
.sid("StandardS3Backup")
.actions(List.of(
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
))
.resources(List.of(
"arn:aws:s3:::" + backupBucketPattern,
"arn:aws:s3:::" + backupBucketPattern + "/*"
))
.build());
// Create Customer Managed Policy
ManagedPolicy ec2Policy = ManagedPolicy.Builder.create(c, "StandardEc2Policy")
.description("Standard EC2 permissions (HIPAA/PCI-DSS compliant)")
.statements(policyStatements)
.build();
// Create role with Customer Managed Policy
Role ec2Role = Role.Builder.create(c, "StandardEc2Role")
.assumedBy(new ServicePrincipal("ec2.amazonaws.com"))
.managedPolicies(List.of(ec2Policy))
.build();
// Add CDK-NAG suppressions for necessary wildcards
NagSuppressions.addResourceSuppressions(
ec2Policy,
List.of(
NagPackSuppression.builder()
.id("AwsSolutions-IAM5")
.reason("SSM service endpoints and CloudWatch metrics require wildcards - AWS service requirements")
.build()
),
Boolean.TRUE
);
c.ec2InstanceRole.set(ec2Role);
} else {
// For DEV/STAGING, use AWS managed policies and inline policies
Role ec2Role = Role.Builder.create(c, "StandardEc2Role")
.assumedBy(new ServicePrincipal("ec2.amazonaws.com"))
.managedPolicies(List.of(
ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"),
ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy")
))
.build();
NagSuppressions.addResourceSuppressions(
ec2Role,
List.of(
NagPackSuppression.builder()
.id("AwsSolutions-IAM4")
.reason("AWS managed policies (AmazonSSMManagedInstanceCore, CloudWatchAgentServerPolicy) " +
"are AWS-recommended for EC2 instances. They provide minimal permissions " +
"for SSM connectivity and CloudWatch monitoring.")
.build()
),
Boolean.TRUE
);
// Add CloudWatch Logs permissions
ec2Role.addToPolicy(PolicyStatement.Builder.create()
.sid("StandardCloudWatchLogs")
.actions(List.of(
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutRetentionPolicy"
))
.resources(List.of(
"arn:aws:logs:" + c.cfc.region() + ":" + accountId + ":log-group:" + appLogPattern + ":*"
))
.build());
// Add CloudWatch metrics permissions
ec2Role.addToPolicy(PolicyStatement.Builder.create()
.sid("StandardCloudWatchMetrics")
.actions(List.of(
"cloudwatch:PutMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics"
))
.resources(List.of("*"))
.build());
// Add EFS permissions if EFS is used
if (c.efs.get().isPresent()) {
ec2Role.addToPolicy(PolicyStatement.Builder.create()
.sid("StandardEfsAccess")
.actions(List.of(
"elasticfilesystem:ClientMount",
"elasticfilesystem:ClientWrite",
"elasticfilesystem:DescribeMountTargets",
"elasticfilesystem:DescribeFileSystems"
))
.resources(List.of(c.efs.get().orElseThrow().getFileSystemArn()))
.build());
}
// Add S3 backup permissions
ec2Role.addToPolicy(PolicyStatement.Builder.create()
.sid("StandardS3Backup")
.actions(List.of(
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
))
.resources(List.of(
"arn:aws:s3:::" + backupBucketPattern,
"arn:aws:s3:::" + backupBucketPattern + "/*"
))
.build());
c.ec2InstanceRole.set(ec2Role);
}
}
private void createStandardFargateRoles(SystemContext c) {
// Get account ID from stack
String accountId = software.amazon.awscdk.Stack.of(c).getAccount();
// Use stackName for application-aware log paths and bucket patterns
String appLogPattern = c.stackName != null && !c.stackName.isEmpty()
? "/aws/*/" + c.stackName + "*"
: "/aws/*";
String backupBucketPattern = c.stackName != null && !c.stackName.isEmpty()
? c.stackName.toLowerCase() + "-backup-*"
: "*-backup-*";
// For PRODUCTION, use Customer Managed Policies
if (c.security == SecurityProfile.PRODUCTION) {
// Create Customer Managed Execution Policy
ManagedPolicy executionPolicy = ManagedPolicy.Builder.create(c, "StandardTaskExecutionPolicy")
.description("Standard ECS task execution permissions (HIPAA/PCI-DSS compliant)")
.statements(List.of(
PolicyStatement.Builder.create()
.sid("ECRImagePull")
.actions(List.of(
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
))
.resources(List.of("*")) // ECR GetAuthorizationToken requires wildcard
.build(),
PolicyStatement.Builder.create()
.sid("CloudWatchLogs")
.actions(List.of(
"logs:CreateLogStream",
"logs:PutLogEvents"
))
.resources(List.of(
"arn:aws:logs:" + c.cfc.region() + ":" + accountId + ":log-group:/aws/ecs/" + c.stackName + "*:*"
))
.build()
))
.build();
Role executionRole = Role.Builder.create(c, "StandardTaskExecutionRole")
.assumedBy(new ServicePrincipal("ecs-tasks.amazonaws.com"))
.managedPolicies(List.of(executionPolicy))
.build();
// Create Customer Managed Task Policy
var taskStatements = new java.util.ArrayList<PolicyStatement>();
// Add EFS permissions if EFS is used
if (c.efs.get().isPresent() && c.ap.get().isPresent()) {
taskStatements.add(PolicyStatement.Builder.create()
.sid("StandardEfsAccess")
.actions(List.of(
"elasticfilesystem:ClientMount",
"elasticfilesystem:ClientWrite",
"elasticfilesystem:ClientRootAccess",
"elasticfilesystem:DescribeMountTargets"
))
.resources(List.of(
c.efs.get().orElseThrow().getFileSystemArn(),
c.ap.get().orElseThrow().getAccessPointArn()
))
.build());
}
// CloudWatch Logs permissions
taskStatements.add(PolicyStatement.Builder.create()
.sid("StandardCloudWatchLogs")
.actions(List.of(
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutRetentionPolicy"
))
.resources(List.of(
"arn:aws:logs:" + c.cfc.region() + ":" + accountId + ":log-group:" + appLogPattern + ":*"
))
.build());
// CloudWatch Metrics permissions (service-level)
taskStatements.add(PolicyStatement.Builder.create()
.sid("StandardCloudWatchMetrics")
.actions(List.of(
"cloudwatch:PutMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics"
))
.resources(List.of("*")) // CloudWatch metrics are service-level
.build());
// S3 backup permissions
taskStatements.add(PolicyStatement.Builder.create()
.sid("StandardS3Backup")
.actions(List.of(
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
))
.resources(List.of(
"arn:aws:s3:::" + backupBucketPattern,
"arn:aws:s3:::" + backupBucketPattern + "/*"
))
.build());
ManagedPolicy taskPolicy = ManagedPolicy.Builder.create(c, "StandardTaskPolicy")
.description("Standard ECS task permissions for application runtime")
.statements(taskStatements)
.build();
Role taskRole = Role.Builder.create(c, "StandardTaskRole")
.assumedBy(new ServicePrincipal("ecs-tasks.amazonaws.com"))
.managedPolicies(List.of(taskPolicy))
.build();
// Add CDK-NAG suppressions for necessary wildcards
NagSuppressions.addResourceSuppressions(
executionPolicy,
List.of(
NagPackSuppression.builder()
.id("AwsSolutions-IAM5")
.reason("ECR GetAuthorizationToken and CloudWatch Logs patterns require wildcards - AWS service requirements")
.build()
),
Boolean.TRUE
);
NagSuppressions.addResourceSuppressions(
taskPolicy,
List.of(
NagPackSuppression.builder()
.id("AwsSolutions-IAM5")
.reason("CloudWatch Logs patterns and CloudWatch metrics require wildcards - AWS service requirements")
.build()
),
Boolean.TRUE
);
c.fargateExecutionRole.set(executionRole);
c.fargateTaskRole.set(taskRole);
} else {
// For DEV/STAGING, use AWS managed policies and inline policies
Role executionRole = Role.Builder.create(c, "StandardTaskExecutionRole")
.assumedBy(new ServicePrincipal("ecs-tasks.amazonaws.com"))
.managedPolicies(List.of(
ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonECSTaskExecutionRolePolicy")
))
.build();
NagSuppressions.addResourceSuppressions(
executionRole,
List.of(
NagPackSuppression.builder()
.id("AwsSolutions-IAM4")
.reason("AmazonECSTaskExecutionRolePolicy is an AWS-managed policy specifically " +
"designed for ECS task execution. It provides minimal required permissions " +
"for pulling container images from ECR and writing logs to CloudWatch.")
.build()
),
Boolean.TRUE
);
Role taskRole = Role.Builder.create(c, "StandardTaskRole")
.assumedBy(new ServicePrincipal("ecs-tasks.amazonaws.com"))
.build();
// Add EFS permissions if EFS is used
if (c.efs.get().isPresent() && c.ap.get().isPresent()) {
taskRole.addToPolicy(PolicyStatement.Builder.create()
.sid("StandardEfsAccess")
.actions(List.of(
"elasticfilesystem:ClientMount",
"elasticfilesystem:ClientWrite",
"elasticfilesystem:ClientRootAccess",
"elasticfilesystem:DescribeMountTargets"
))
.resources(List.of(
c.efs.get().orElseThrow().getFileSystemArn(),
c.ap.get().orElseThrow().getAccessPointArn()
))
.build());
}
// CloudWatch Logs permissions
taskRole.addToPolicy(PolicyStatement.Builder.create()
.sid("StandardCloudWatchLogs")
.actions(List.of(
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutRetentionPolicy"
))
.resources(List.of(
"arn:aws:logs:" + c.cfc.region() + ":" + accountId + ":log-group:" + appLogPattern + ":*"
))
.build());
// CloudWatch Metrics permissions
taskRole.addToPolicy(PolicyStatement.Builder.create()
.sid("StandardCloudWatchMetrics")
.actions(List.of(
"cloudwatch:PutMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics"
))
.resources(List.of("*"))
.build());
// S3 backup permissions
taskRole.addToPolicy(PolicyStatement.Builder.create()
.sid("StandardS3Backup")
.actions(List.of(
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
))
.resources(List.of(
"arn:aws:s3:::" + backupBucketPattern,
"arn:aws:s3:::" + backupBucketPattern + "/*"
))
.build());
c.fargateExecutionRole.set(executionRole);
c.fargateTaskRole.set(taskRole);
}
}
}