PermissionMatrix.java
package com.cloudforgeci.api.core.iam;
import com.cloudforge.core.enums.IAMProfile;
import com.cloudforge.core.enums.RuntimeType;
import com.cloudforge.core.enums.TopologyType;
import java.util.List;
import java.util.Map;
/**
* Permission Matrix defining the minimum required permissions for each topology/runtime combination.
* This ensures that no unnecessary permissions are granted and follows the principle of least privilege.
*/
public final class PermissionMatrix {
private PermissionMatrix() {}
/**
* Core permissions required for all Jenkins deployments regardless of topology/runtime.
*/
public static final List<String> CORE_PERMISSIONS = List.of(
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
);
/**
* EC2-specific permissions based on IAM profile.
*/
public static final Map<IAMProfile, List<String>> EC2_PERMISSIONS = Map.of(
IAMProfile.MINIMAL, List.of(
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath",
"ssm:SendCommand",
"ssm:ListCommandInvocations",
"cloudwatch:PutMetricData"
),
IAMProfile.STANDARD, List.of(
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath",
"ssm:SendCommand",
"ssm:ListCommandInvocations",
"ssm:DescribeInstanceInformation",
"cloudwatch:PutMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics",
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
),
IAMProfile.EXTENDED, List.of(
"ssm:*",
"cloudwatch:*",
"ec2:DescribeInstances",
"ec2:DescribeVolumes",
"ec2:DescribeSnapshots",
"ec2:DescribeImages",
"ec2:DescribeSecurityGroups",
"ec2:DescribeVpcs",
"ec2:DescribeSubnets",
"s3:*"
)
);
/**
* Fargate-specific permissions based on IAM profile.
*/
public static final Map<IAMProfile, List<String>> FARGATE_PERMISSIONS = Map.of(
IAMProfile.MINIMAL, List.of(
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
),
IAMProfile.STANDARD, List.of(
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:DescribeRepositories",
"ecr:ListImages",
"cloudwatch:PutMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics",
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
),
IAMProfile.EXTENDED, List.of(
"ecr:*",
"ecs:DescribeClusters",
"ecs:DescribeServices",
"ecs:DescribeTasks",
"ecs:DescribeTaskDefinition",
"ecs:ListTasks",
"ecs:ListServices",
"cloudwatch:*",
"s3:*"
)
);
/**
* EFS permissions based on IAM profile.
*/
public static final Map<IAMProfile, List<String>> EFS_PERMISSIONS = Map.of(
IAMProfile.MINIMAL, List.of(
"elasticfilesystem:ClientMount",
"elasticfilesystem:ClientWrite"
),
IAMProfile.STANDARD, List.of(
"elasticfilesystem:ClientMount",
"elasticfilesystem:ClientWrite",
"elasticfilesystem:ClientRootAccess",
"elasticfilesystem:DescribeMountTargets"
),
IAMProfile.EXTENDED, List.of(
"elasticfilesystem:*"
)
);
/**
* ALB permissions based on IAM profile.
*/
public static final Map<IAMProfile, List<String>> ALB_PERMISSIONS = Map.of(
IAMProfile.MINIMAL, List.of(
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth"
),
IAMProfile.STANDARD, List.of(
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:DescribeListeners",
"elasticloadbalancing:DescribeRules"
),
IAMProfile.EXTENDED, List.of(
"elasticloadbalancing:*"
)
);
/**
* Gets the required permissions for a specific topology/runtime/iam combination.
*
* @param topology the topology type
* @param runtime the runtime type
* @param iamProfile the IAM profile
* @return list of required permissions
*/
public static List<String> getRequiredPermissions(TopologyType topology, RuntimeType runtime, IAMProfile iamProfile) {
List<String> permissions = new java.util.ArrayList<>(CORE_PERMISSIONS);
// Add runtime-specific permissions
if (runtime == RuntimeType.EC2) {
permissions.addAll(EC2_PERMISSIONS.get(iamProfile));
} else if (runtime == RuntimeType.FARGATE) {
permissions.addAll(FARGATE_PERMISSIONS.get(iamProfile));
}
// Add topology-specific permissions
if (topology == TopologyType.JENKINS_SERVICE) {
permissions.addAll(EFS_PERMISSIONS.get(iamProfile));
permissions.addAll(ALB_PERMISSIONS.get(iamProfile));
}
return permissions;
}
/**
* Validates that the provided permissions are appropriate for the given combination.
*
* @param topology the topology type
* @param runtime the runtime type
* @param iamProfile the IAM profile
* @param providedPermissions the permissions being granted
* @return validation result with any issues found
*/
public static ValidationResult validatePermissions(TopologyType topology, RuntimeType runtime,
IAMProfile iamProfile, List<String> providedPermissions) {
List<String> requiredPermissions = getRequiredPermissions(topology, runtime, iamProfile);
List<String> issues = new java.util.ArrayList<>();
// Check for missing required permissions
for (String required : requiredPermissions) {
if (!hasPermission(providedPermissions, required)) {
issues.add("Missing required permission: " + required);
}
}
// Check for excessive permissions (only for MINIMAL profile)
if (iamProfile == IAMProfile.MINIMAL) {
for (String provided : providedPermissions) {
if (!isPermissionAllowed(requiredPermissions, provided)) {
issues.add("Excessive permission for MINIMAL profile: " + provided);
}
}
}
return new ValidationResult(issues.isEmpty(), issues);
}
/**
* Checks if a permission is present in the provided list (supports wildcards).
*/
private static boolean hasPermission(List<String> permissions, String required) {
return permissions.stream().anyMatch(perm ->
perm.equals(required) ||
perm.equals("*") ||
perm.endsWith("*") && required.startsWith(perm.substring(0, perm.length() - 1))
);
}
/**
* Checks if a permission is allowed based on the required permissions list.
*/
private static boolean isPermissionAllowed(List<String> allowedPermissions, String permission) {
return allowedPermissions.stream().anyMatch(allowed ->
allowed.equals(permission) ||
allowed.equals("*") ||
allowed.endsWith("*") && permission.startsWith(allowed.substring(0, allowed.length() - 1))
);
}
/**
* Validation result containing success status and any issues found.
*
* @param isValid whether the validation passed
* @param issues list of validation issues found (empty if valid)
*/
public static record ValidationResult(boolean isValid, List<String> issues) {
public boolean hasIssues() {
return !issues.isEmpty();
}
public String getIssuesAsString() {
return String.join("\n", issues);
}
}
}