Skip to main content

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"
}
}
}
]
}
ElementPurpose
Principal.AWSThe frugally.app AWS account (829513654501). Only this account can assume the role.
sts:ExternalIdA 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": "*"
}
ActionWhat it does
tag:GetResourcesSearch resources by tags (used for Target filtering)
ec2:Describe*Read EC2 instance, VPC endpoint, and NAT gateway info
rds:Describe*, rds:ListTagsForResourceRead RDS instance info and tags
lambda:List*, lambda:GetFunctionRead 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": "*"
}
ActionWhat it does
ec2:StartInstancesStart stopped EC2 instances
ec2:StopInstancesStop running EC2 instances
ec2:ModifyInstanceAttributeResize 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": "*"
}
ActionWhat it does
ce:GetCostAndUsageRetrieve cost and usage data
ce:GetCostForecastRetrieve cost forecasts
ce:GetDimensionValuesList available cost dimensions (services, regions, etc.)
ce:GetTagsList cost allocation tags
ce:GetReservationUtilizationCheck Reserved Instance utilisation
ce:GetSavingsPlansUtilizationCheck 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": "*"
}
ActionWhat it does
cloudtrail:DescribeTrailsList trails and detect organisation-wide trails
cloudtrail:GetTrailStatusCheck if a trail is actively logging
cloudtrail:LookupEventsQuery audit events
cloudtrail:GetEventSelectorsRead 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": "*"
}
ActionWhat it does
organizations:DescribeOrganizationRead organisation metadata and verify management account
organizations:ListAccountsList all member accounts for discovery
organizations:DescribeAccountRead individual account details
organizations:ListTagsForResourceRead 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-*/*"
]
}
]
}
note

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.

caution

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.


Next steps