CloudPlans Docs

AWS

CloudPlans never receives AWS keys. It assumes an IAM role of your account that you create once:

  • The role must trust arn:aws:iam::605134468875:role/cloudplansbatch (AWS account 605134468875) with the external ID cloudplans.
  • The role needs the permissions of the services you want to manage (below).
  • You paste the role ARN in the credential (Create).

The console generates this for you: in Credentials → Add Credential → AWS, click Helper: AWS CloudShell setup, tick only the services you use and run the script it shows in AWS CloudShell. The script and the Terraform below are the ones for all the services, so you can trim them to what you use.

Permissions per service

Always required (they validate the credential and let Find Resources list your regions): ec2:DescribeInstances
ec2:DescribeRegions

Service IAM actions needed
AWS EC2 (Stoppable) ec2:DescribeInstances
ec2:StartInstances
ec2:StopInstances
AWS EC2 (Vertical Resize) ec2:DescribeInstances
ec2:StartInstances
ec2:StopInstances
ec2:ModifyInstanceAttribute
AWS EKS (Scalable) eks:ListClusters
eks:ListNodegroups
eks:DescribeNodegroup
eks:UpdateNodegroupConfig
AWS ECS (Scalable) ecs:ListClusters
ecs:ListServices
ecs:DescribeServices
ecs:UpdateService
AWS Auto Scaling (Scalable) autoscaling:DescribeAutoScalingGroups
autoscaling:UpdateAutoScalingGroup
autoscaling:DescribeLaunchConfigurations
ec2:DescribeLaunchTemplateVersions
AWS RDS (Stoppable) rds:DescribeDBInstances
rds:StartDBInstance
rds:StopDBInstance
AWS RDS / Aurora (Vertical Resize) rds:DescribeDBInstances
rds:StartDBInstance
rds:StopDBInstance
rds:ModifyDBInstance
AWS DocumentDB (Stoppable) rds:DescribeDBClusters
rds:StartDBCluster
rds:StopDBCluster
rds:DescribeDBInstances
AWS Elastic Beanstalk (Scalable) elasticbeanstalk:DescribeEnvironments
elasticbeanstalk:DescribeEnvironmentResources
autoscaling:DescribeAutoScalingGroups
autoscaling:UpdateAutoScalingGroup
AWS Fargate (Scalable) ecs:ListClusters
ecs:ListServices
ecs:DescribeServices
ecs:UpdateService
AWS Amazon MQ (Vertical Resize) mq:ListBrokers
mq:DescribeBroker
mq:UpdateBroker
mq:RebootBroker
AWS SageMaker Endpoint (Scalable) sagemaker:ListEndpoints
sagemaker:DescribeEndpoint
sagemaker:UpdateEndpointWeightsAndCapacities
AWS ElastiCache (Vertical Resize) elasticache:DescribeReplicationGroups
elasticache:ModifyReplicationGroup
AWS Redshift Provisioned (Stoppable) redshift:DescribeClusters
redshift:PauseCluster
redshift:ResumeCluster
AWS DynamoDB Provisioned (Vertical Resize) dynamodb:ListTables
dynamodb:DescribeTable
dynamodb:UpdateTable

Amazon DocumentDB is authorised through the rds: IAM namespace (there is no docdb: IAM prefix). The Auto Scaling Group read actions on launch configurations/templates are only used to estimate the savings of the group.

CloudShell script

# Run this in AWS CloudShell, in the AWS account you want CloudPlans to manage.
ROLE_NAME="CloudPlansSchedulerRole"
EXTERNAL_ID="cloudplans"   # optional, recommended

# 1. Trust policy: who can assume the role
cat > /tmp/trust.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::605134468875:role/cloudplansbatch" },
    "Action": "sts:AssumeRole",
    "Condition": { "StringEquals": { "sts:ExternalId": "cloudplans" } }
  }]
}
JSON

# 2. Permissions policy — minimum actions for the resource types you selected:
#    AWS EC2 (Stoppable), AWS EC2 (Vertical Resize), AWS EKS (Scalable), AWS ECS (Scalable), AWS Auto Scaling (Scalable), AWS RDS (Stoppable), AWS RDS / Aurora (Vertical Resize), AWS DocumentDB (Stoppable), AWS Elastic Beanstalk (Scalable), AWS Fargate (Scalable), AWS Amazon MQ (Vertical Resize), AWS SageMaker Endpoint (Scalable), AWS ElastiCache (Vertical Resize), AWS Redshift Provisioned (Stoppable), AWS DynamoDB Provisioned (Vertical Resize)
cat > /tmp/perm.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeRegions",
        "ec2:StartInstances",
        "ec2:StopInstances",
        "ec2:ModifyInstanceAttribute",
        "eks:ListClusters",
        "eks:ListNodegroups",
        "eks:DescribeNodegroup",
        "eks:UpdateNodegroupConfig",
        "ecs:ListClusters",
        "ecs:ListServices",
        "ecs:DescribeServices",
        "ecs:UpdateService",
        "autoscaling:DescribeAutoScalingGroups",
        "autoscaling:UpdateAutoScalingGroup",
        "autoscaling:DescribeLaunchConfigurations",
        "ec2:DescribeLaunchTemplateVersions",
        "rds:DescribeDBInstances",
        "rds:StartDBInstance",
        "rds:StopDBInstance",
        "rds:ModifyDBInstance",
        "rds:DescribeDBClusters",
        "rds:StartDBCluster",
        "rds:StopDBCluster",
        "elasticbeanstalk:DescribeEnvironments",
        "elasticbeanstalk:DescribeEnvironmentResources",
        "mq:ListBrokers",
        "mq:DescribeBroker",
        "mq:UpdateBroker",
        "mq:RebootBroker",
        "sagemaker:ListEndpoints",
        "sagemaker:DescribeEndpoint",
        "sagemaker:UpdateEndpointWeightsAndCapacities",
        "elasticache:DescribeReplicationGroups",
        "elasticache:ModifyReplicationGroup",
        "redshift:DescribeClusters",
        "redshift:PauseCluster",
        "redshift:ResumeCluster",
        "dynamodb:ListTables",
        "dynamodb:DescribeTable",
        "dynamodb:UpdateTable"
      ],
      "Resource": "*"
    }
  ]
}
JSON

# 3. Create role + attach inline policy
aws iam create-role \
  --role-name "$ROLE_NAME" \
  --assume-role-policy-document file:///tmp/trust.json

aws iam put-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-name CloudPlansSchedulerPolicy \
  --policy-document file:///tmp/perm.json

# 4. Print the ARN you must paste in the CloudPlans console
aws iam get-role --role-name "$ROLE_NAME" --query 'Role.Arn' --output text

Terraform

data "aws_iam_policy_document" "trust" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::605134468875:role/cloudplansbatch"]
    }
    condition {
      test     = "StringEquals"
      variable = "sts:ExternalId"
      values   = ["cloudplans"]
    }
  }
}

data "aws_iam_policy_document" "permissions" {
  statement {
    effect = "Allow"
    actions = [
      "autoscaling:DescribeAutoScalingGroups",
      "autoscaling:DescribeLaunchConfigurations",
      "autoscaling:UpdateAutoScalingGroup",
      "dynamodb:DescribeTable",
      "dynamodb:ListTables",
      "dynamodb:UpdateTable",
      "ec2:DescribeInstances",
      "ec2:DescribeLaunchTemplateVersions",
      "ec2:DescribeRegions",
      "ec2:ModifyInstanceAttribute",
      "ec2:StartInstances",
      "ec2:StopInstances",
      "ecs:DescribeServices",
      "ecs:ListClusters",
      "ecs:ListServices",
      "ecs:UpdateService",
      "eks:DescribeNodegroup",
      "eks:ListClusters",
      "eks:ListNodegroups",
      "eks:UpdateNodegroupConfig",
      "elasticache:DescribeReplicationGroups",
      "elasticache:ModifyReplicationGroup",
      "elasticbeanstalk:DescribeEnvironmentResources",
      "elasticbeanstalk:DescribeEnvironments",
      "mq:DescribeBroker",
      "mq:ListBrokers",
      "mq:RebootBroker",
      "mq:UpdateBroker",
      "rds:DescribeDBClusters",
      "rds:DescribeDBInstances",
      "rds:ModifyDBInstance",
      "rds:StartDBCluster",
      "rds:StartDBInstance",
      "rds:StopDBCluster",
      "rds:StopDBInstance",
      "redshift:DescribeClusters",
      "redshift:PauseCluster",
      "redshift:ResumeCluster",
      "sagemaker:DescribeEndpoint",
      "sagemaker:ListEndpoints",
      "sagemaker:UpdateEndpointWeightsAndCapacities"
    ]
    resources = ["*"]
  }
}

resource "aws_iam_role" "cloudplans" {
  name               = "CloudPlansSchedulerRole"
  assume_role_policy = data.aws_iam_policy_document.trust.json
}

resource "aws_iam_role_policy" "cloudplans" {
  name   = "CloudPlansSchedulerPolicy"
  role   = aws_iam_role.cloudplans.id
  policy = data.aws_iam_policy_document.permissions.json
}

output "role_arn" {
  description = "Paste this ARN in the CloudPlans console"
  value       = aws_iam_role.cloudplans.arn
}