IAM Policy Reference
This page is the complete reference for every IAM policy frugally.app uses. Each section shows the exact JSON policy statements, what they allow, and when they are needed.
All policies follow the principle of least privilege — frugally.app only requests the permissions it needs for the features you enable.
Trust policy
Every IAM role used by frugally.app requires a trust policy that allows frugally.app to assume the role. This is the same for both Organisation and Standalone setups.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::829513654501:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "YOUR_EXTERNAL_ID"
}
}
}
]
}
| Element | Purpose |
|---|---|
Principal.AWS | The frugally.app AWS account (829513654501). Only this account can assume the role. |
sts:ExternalId | A unique token generated by frugally.app for your Connection. Prevents confused-deputy attacks. |
Replace YOUR_EXTERNAL_ID with the value shown in the frugally.app connection form or Organisation wizard.
Execution policy
The execution policy grants frugally.app access to discover and manage your resources. This is the core policy needed for scheduling start/stop actions.
Resource discovery
Required for all Connections. Lets frugally.app scan your account for supported resources.
{
"Sid": "FrugallyResourceDiscovery",
"Effect": "Allow",
"Action": [
"tag:GetResources",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:DescribeTags",
"rds:DescribeDBInstances",
"rds:ListTagsForResource",
"lambda:ListFunctions",
"lambda:GetFunction",
"lambda:ListTags",
"ecs:DescribeServices",
"ecs:ListServices",
"ecs:ListClusters",
"ec2:DescribeVpcEndpoints",
"ec2:DescribeNatGateways"
],
"Resource": "*"
}
| Action | What it does |
|---|---|
tag:GetResources | Search resources by tags (used for Target filtering) |
ec2:Describe* | Read EC2 instance, VPC endpoint, and NAT gateway info |
rds:Describe*, rds:ListTagsForResource | Read RDS instance info and tags |
lambda:List*, lambda:GetFunction | Read Lambda function info and tags |
ecs:Describe*, ecs:List* | Read ECS service, cluster info |
EC2 actions
{
"Sid": "FrugallyEC2Actions",
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:ModifyInstanceAttribute"
],
"Resource": "*"
}
| Action | What it does |
|---|---|
ec2:StartInstances | Start stopped EC2 instances |
ec2:StopInstances | Stop running EC2 instances |
ec2:ModifyInstanceAttribute | Resize instances (change instance type) |
RDS actions
{
"Sid": "FrugallyRDSActions",
"Effect": "Allow",
"Action": [
"rds:StartDBInstance",
"rds:StopDBInstance",
"rds:ModifyDBInstance"
],
"Resource": "*"
}
Lambda actions
{
"Sid": "FrugallyLambdaActions",
"Effect": "Allow",
"Action": [
"lambda:PutFunctionConcurrency",
"lambda:DeleteFunctionConcurrency"
],
"Resource": "*"
}
frugally.app manages Lambda functions by setting reserved concurrency to 0 (effectively disabling the function) or removing the concurrency limit (re-enabling it).
ECS actions
{
"Sid": "FrugallyECSActions",
"Effect": "Allow",
"Action": [
"ecs:UpdateService"
],
"Resource": "*"
}
frugally.app manages ECS services by setting the desired count to 0 (stop) or restoring it to the previous value (start).
VPC Endpoint actions
{
"Sid": "FrugallyVpcEndpointActions",
"Effect": "Allow",
"Action": [
"ec2:CreateVpcEndpoint",
"ec2:DeleteVpcEndpoints",
"ec2:ModifyVpcEndpoint"
],
"Resource": "*"
}
NAT Gateway actions
{
"Sid": "FrugallyNatGatewayActions",
"Effect": "Allow",
"Action": [
"ec2:CreateNatGateway",
"ec2:DeleteNatGateway",
"ec2:DescribeAddresses"
],
"Resource": "*"
}
ec2:DescribeAddresses is needed to read Elastic IP information when recreating NAT Gateways.
Billing policy
Required when Cost Explorer or CUR features are enabled.
Cost Explorer
{
"Sid": "FrugallyCostExplorerReadAccess",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetCostForecast",
"ce:GetDimensionValues",
"ce:GetTags",
"ce:GetReservationUtilization",
"ce:GetSavingsPlansUtilization"
],
"Resource": "*"
}
| Action | What it does |
|---|---|
ce:GetCostAndUsage | Retrieve cost and usage data |
ce:GetCostForecast | Retrieve cost forecasts |
ce:GetDimensionValues | List available cost dimensions (services, regions, etc.) |
ce:GetTags | List cost allocation tags |
ce:GetReservationUtilization | Check Reserved Instance utilisation |
ce:GetSavingsPlansUtilization | Check Savings Plans utilisation |
Cost and Usage Reports (CUR)
{
"Sid": "FrugallyCURReadAccess",
"Effect": "Allow",
"Action": [
"cur:DescribeReportDefinitions"
],
"Resource": "*"
}
{
"Sid": "FrugallyCURS3ReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::*-cur-*",
"arn:aws:s3:::*-cur-*/*"
]
}
The S3 permissions are scoped to buckets matching *-cur-*. If your CUR bucket has a different naming convention, replace the resource ARNs with your specific bucket:
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
]
CloudTrail policy
Required when the CloudTrail feature is enabled.
{
"Sid": "FrugallyCloudTrailReadAccess",
"Effect": "Allow",
"Action": [
"cloudtrail:DescribeTrails",
"cloudtrail:GetTrailStatus",
"cloudtrail:LookupEvents",
"cloudtrail:GetEventSelectors"
],
"Resource": "*"
}
| Action | What it does |
|---|---|
cloudtrail:DescribeTrails | List trails and detect organisation-wide trails |
cloudtrail:GetTrailStatus | Check if a trail is actively logging |
cloudtrail:LookupEvents | Query audit events |
cloudtrail:GetEventSelectors | Read trail event filtering configuration |
Organisations policy
Required on the management account role when using the Organisation setup. This lets frugally.app discover and list member accounts.
{
"Sid": "FrugallyOrganizationsReadAccess",
"Effect": "Allow",
"Action": [
"organizations:DescribeOrganization",
"organizations:ListAccounts",
"organizations:DescribeAccount",
"organizations:ListTagsForResource"
],
"Resource": "*"
}
| Action | What it does |
|---|---|
organizations:DescribeOrganization | Read organisation metadata and verify management account |
organizations:ListAccounts | List all member accounts for discovery |
organizations:DescribeAccount | Read individual account details |
organizations:ListTagsForResource | Read tags on organisation resources |
Combined policies by setup type
Standalone Connection — full policy
A standalone Connection with all features enabled:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FrugallyResourceDiscovery",
"Effect": "Allow",
"Action": [
"tag:GetResources",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:DescribeTags",
"rds:DescribeDBInstances",
"rds:ListTagsForResource",
"lambda:ListFunctions",
"lambda:GetFunction",
"lambda:ListTags",
"ecs:DescribeServices",
"ecs:ListServices",
"ecs:ListClusters",
"ec2:DescribeVpcEndpoints",
"ec2:DescribeNatGateways"
],
"Resource": "*"
},
{
"Sid": "FrugallyEC2Actions",
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:ModifyInstanceAttribute"
],
"Resource": "*"
},
{
"Sid": "FrugallyRDSActions",
"Effect": "Allow",
"Action": [
"rds:StartDBInstance",
"rds:StopDBInstance",
"rds:ModifyDBInstance"
],
"Resource": "*"
},
{
"Sid": "FrugallyLambdaActions",
"Effect": "Allow",
"Action": [
"lambda:PutFunctionConcurrency",
"lambda:DeleteFunctionConcurrency"
],
"Resource": "*"
},
{
"Sid": "FrugallyECSActions",
"Effect": "Allow",
"Action": [
"ecs:UpdateService"
],
"Resource": "*"
},
{
"Sid": "FrugallyVpcEndpointActions",
"Effect": "Allow",
"Action": [
"ec2:CreateVpcEndpoint",
"ec2:DeleteVpcEndpoints",
"ec2:ModifyVpcEndpoint"
],
"Resource": "*"
},
{
"Sid": "FrugallyNatGatewayActions",
"Effect": "Allow",
"Action": [
"ec2:CreateNatGateway",
"ec2:DeleteNatGateway",
"ec2:DescribeAddresses"
],
"Resource": "*"
},
{
"Sid": "FrugallyCostExplorerReadAccess",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetCostForecast",
"ce:GetDimensionValues",
"ce:GetTags",
"ce:GetReservationUtilization",
"ce:GetSavingsPlansUtilization"
],
"Resource": "*"
},
{
"Sid": "FrugallyCloudTrailReadAccess",
"Effect": "Allow",
"Action": [
"cloudtrail:DescribeTrails",
"cloudtrail:GetTrailStatus",
"cloudtrail:LookupEvents",
"cloudtrail:GetEventSelectors"
],
"Resource": "*"
},
{
"Sid": "FrugallyCURReadAccess",
"Effect": "Allow",
"Action": [
"cur:DescribeReportDefinitions"
],
"Resource": "*"
},
{
"Sid": "FrugallyCURS3ReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::*-cur-*",
"arn:aws:s3:::*-cur-*/*"
]
}
]
}
Management account — full policy
The management account role in an Organisation setup with all features enabled:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FrugallyOrganizationsReadAccess",
"Effect": "Allow",
"Action": [
"organizations:DescribeOrganization",
"organizations:ListAccounts",
"organizations:DescribeAccount",
"organizations:ListTagsForResource"
],
"Resource": "*"
},
{
"Sid": "FrugallyCloudTrailReadAccess",
"Effect": "Allow",
"Action": [
"cloudtrail:DescribeTrails",
"cloudtrail:GetTrailStatus",
"cloudtrail:LookupEvents",
"cloudtrail:GetEventSelectors"
],
"Resource": "*"
},
{
"Sid": "FrugallyCostExplorerReadAccess",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetCostForecast",
"ce:GetDimensionValues",
"ce:GetTags",
"ce:GetReservationUtilization",
"ce:GetSavingsPlansUtilization"
],
"Resource": "*"
},
{
"Sid": "FrugallyCURReadAccess",
"Effect": "Allow",
"Action": [
"cur:DescribeReportDefinitions"
],
"Resource": "*"
},
{
"Sid": "FrugallyCURS3ReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::*-cur-*",
"arn:aws:s3:::*-cur-*/*"
]
}
]
}
The management account policy does not include resource execution actions (EC2, RDS, Lambda, etc.) because scheduling actions are performed via member account roles, not the management account.
Tag-based restrictions
For extra security, you can add IAM conditions to restrict frugally.app to only manage resources with specific tags. For example, to limit actions to resources tagged managed-by: frugally:
{
"Sid": "FrugallyEC2Actions",
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:ModifyInstanceAttribute"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/managed-by": "frugally"
}
}
}
Apply the same Condition block to any action statement you want to restrict. The tag key and value must exactly match the tags on your AWS resources.
Tag conditions on Describe* and List* actions may prevent frugally.app from discovering resources. Only apply tag restrictions to action statements (Start, Stop, Modify, etc.), not discovery statements.
For more on tag filtering in frugally.app, see Resource Tag Filtering.
Export formats
The Organisation Setup Wizard can export policies in three formats.
CloudFormation
AWSTemplateFormatVersion: '2010-09-09'
Description: 'IAM Role for Frugally cross-account access'
Parameters:
ExternalId:
Type: String
Description: External ID for Frugally cross-account access
Default: 'YOUR_EXTERNAL_ID'
NoEcho: true
Resources:
FrugallyAccessRole:
Type: AWS::IAM::Role
Properties:
RoleName: FrugallyAccessRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: 'arn:aws:iam::829513654501:root'
Action: 'sts:AssumeRole'
Condition:
StringEquals:
'sts:ExternalId': !Ref ExternalId
Policies:
- PolicyName: FrugallyAccessRole-policy
PolicyDocument:
... (generated execution policy)
Outputs:
RoleArn:
Description: ARN of the created IAM role
Value: !GetAtt FrugallyAccessRole.Arn
Deploy with the AWS CLI:
aws cloudformation create-stack \
--stack-name frugally-access \
--template-body file://frugally-role.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameters ParameterKey=ExternalId,ParameterValue=YOUR_EXTERNAL_ID
For multi-account deployment, use CloudFormation StackSets.
Terraform
resource "aws_iam_role" "FrugallyAccessRole" {
name = "FrugallyAccessRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::829513654501:root"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"sts:ExternalId" = var.frugally_external_id
}
}
}
]
})
}
resource "aws_iam_role_policy" "FrugallyAccessRole_policy" {
name = "FrugallyAccessRole-policy"
role = aws_iam_role.FrugallyAccessRole.id
policy = <<EOF
... (generated execution policy JSON)
EOF
}
variable "frugally_external_id" {
description = "External ID for Frugally cross-account access"
type = string
sensitive = true
}
JSON
Plain JSON can be copied directly into the IAM console when creating a policy manually.