Scan your AWS and Azure infrastructure for cost optimization opportunities using Cloud Custodian. Run scans directly in GitHub Actions - no infrastructure required from your side.
- 🔍 Safe Scanning - 100% read-only operations, no changes to your infrastructure
- ☁️ Multi-Cloud - Supports Azure and AWS
- 🚀 Zero Config - Works out-of-the-box with sensible defaults
- 🔐 Secure - Uses OIDC for authentication, no secrets storage needed
- 📊 Actionable - Findings submitted to LeftSize dashboard for tracking
- 🎯 Customizable - Filter policies, add custom rules
- Install the LeftSize GitHub App
- Complete onboarding to get your
installation-idandrepository-token - Configure cloud provider authentication (Azure or AWS)
name: LeftSize Cost Optimization Scan
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight
workflow_dispatch: # Manual trigger
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
leftsize-scan:
runs-on: ubuntu-latest
steps:
- name: Azure Login (OIDC)
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Run LeftSize Scan
uses: leftsize/leftsize-action@v1
with:
installation-id: ${{ secrets.LEFTSIZE_INSTALLATION_ID }}
repository-token: ${{ secrets.LEFTSIZE_REPOSITORY_TOKEN }}permissions:
id-token: write # Required for OIDC
contents: read
jobs:
leftsize-scan:
runs-on: ubuntu-latest
steps:
- name: AWS Login (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Run LeftSize Scan
uses: leftsize/leftsize-action@v1
with:
installation-id: ${{ secrets.LEFTSIZE_INSTALLATION_ID }}
repository-token: ${{ secrets.LEFTSIZE_REPOSITORY_TOKEN }}
cloud-provider: aws| Input | Required | Default | Description |
|---|---|---|---|
installation-id |
✅ Yes | - | GitHub App installation ID from LeftSize onboarding |
repository-token |
✅ Yes | - | Secure repository token from LeftSize onboarding |
backend-url |
No | https://api.leftsize.com |
LeftSize backend API URL |
cloud-provider |
No | azure |
Cloud provider: azure or aws |
azure-subscription-ids |
No | All accessible | Comma-separated Azure subscription IDs to scan |
aws-regions |
No | All accessible | Comma-separated AWS regions to scan |
include-policies |
No | All | Policy categories: cost-optimization, governance, security |
exclude-policies |
No | None | Specific policy names to skip (comma-separated) |
verbose |
No | false |
Enable verbose logging |
| Output | Description |
|---|---|
findings-count |
Total number of findings detected |
findings-submitted |
Whether findings were successfully submitted (true/false) |
findings-json |
JSON string of all findings for custom processing |
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
leftsize-scan:
runs-on: ubuntu-latest
steps:
- uses: leftsize/leftsize-action@v1
with:
installation-id: ${{ secrets.LEFTSIZE_INSTALLATION_ID }}
repository-token: ${{ secrets.LEFTSIZE_REPOSITORY_TOKEN }}
azure-subscription-ids: "sub-id-1,sub-id-2,sub-id-3"permissions:
id-token: write # Required for OIDC
contents: read
jobs:
leftsize-scan:
runs-on: ubuntu-latest
steps:
- uses: leftsize/leftsize-action@v1
with:
installation-id: ${{ secrets.LEFTSIZE_INSTALLATION_ID }}
repository-token: ${{ secrets.LEFTSIZE_REPOSITORY_TOKEN }}
cloud-provider: aws
aws-regions: "us-east-1,eu-west-1,ap-southeast-1"permissions:
id-token: write # Required for OIDC
contents: read
jobs:
leftsize-scan:
runs-on: ubuntu-latest
steps:
- uses: leftsize/leftsize-action@v1
with:
installation-id: ${{ secrets.LEFTSIZE_INSTALLATION_ID }}
repository-token: ${{ secrets.LEFTSIZE_REPOSITORY_TOKEN }}
include-policies: cost-optimization,governance # Only cost and governance policies
exclude-policies: leftsize-idle-vm # Skip specific rulepermissions:
id-token: write # Required for OIDC
contents: read
jobs:
leftsize-scan:
runs-on: ubuntu-latest
steps:
- name: Run LeftSize Scan
id: leftsize
uses: leftsize/leftsize-action@v1
with:
installation-id: ${{ secrets.LEFTSIZE_INSTALLATION_ID }}
repository-token: ${{ secrets.LEFTSIZE_REPOSITORY_TOKEN }}
- name: Process Findings
run: |
echo "Found ${{ steps.leftsize.outputs.findings-count }} findings"
echo "Submitted: ${{ steps.leftsize.outputs.findings-submitted }}"
# Custom processing
echo '${{ steps.leftsize.outputs.findings-json }}' | jq '.[] | select(.severity == "high")'# Create app
az ad app create --display-name "LeftSize-GitHub-Action"
# Get app ID
APP_ID=$(az ad app list --display-name "LeftSize-GitHub-Action" --query "[0].appId" -o tsv)
# Create service principal
az ad sp create --id $APP_ID
# Get object ID
OBJECT_ID=$(az ad sp list --display-name "LeftSize-GitHub-Action" --query "[0].id" -o tsv)# Create credential configuration
cat > credential.json << EOF
{
"name": "github-actions",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:YOUR-ORG/YOUR-REPO:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}
EOF
# Add federated credential
az ad app federated-credential create --id $APP_ID --parameters credential.json# Assign Reader role (minimum required)
az role assignment create \
--assignee $OBJECT_ID \
--role "Reader" \
--scope /subscriptions/YOUR-SUBSCRIPTION-ID
# Assign Monitoring Reader (for metrics)
az role assignment create \
--assignee $OBJECT_ID \
--role "Monitoring Reader" \
--scope /subscriptions/YOUR-SUBSCRIPTION-IDAdd these secrets to your repository:
AZURE_CLIENT_ID: App ID from step 1AZURE_TENANT_ID: Your Azure AD tenant IDAZURE_SUBSCRIPTION_ID: Your subscription IDLEFTSIZE_INSTALLATION_ID: From LeftSize onboardingLEFTSIZE_REPOSITORY_TOKEN: From LeftSize onboarding
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT-ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:YOUR-ORG/YOUR-REPO:ref:refs/heads/main"
}
}
}
]
}aws iam attach-role-policy \
--role-name LeftSize-GitHub-Action \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccessAWS_ROLE_ARN: ARN of the role created aboveLEFTSIZE_INSTALLATION_ID: From LeftSize onboardingLEFTSIZE_REPOSITORY_TOKEN: From LeftSize onboarding
The action includes curated Cloud Custodian policies:
leftsize-idle-vm- Idle virtual machinesleftsize-unattached-disk- Unattached managed disksleftsize-unused-public-ip- Unused public IP addressesleftsize-idle-app-service-plan- Underutilized App Service Plansleftsize-idle-app-service-plan-no-apps- Empty App Service Plansleftsize-inactive-azure-load-balancer- Inactive load balancers
leftsize-idle-ecs-instances- Idle ECS container instancesleftsize-unattached-ebs-volumes- Unattached EBS volumesleftsize-unused-elastic-ips- Unused Elastic IPsleftsize-old-snapshots- Old EBS snapshots- And more...
- Security group rules
- Unencrypted resources
- Missing tags
- And more...
ubuntu-latest, ubuntu-20.04, or ubuntu-22.04). It does not work on Windows or macOS runners due to Docker-based action limitations.
jobs:
leftsize-scan:
runs-on: ubuntu-latest # RequiredLeftSize runs on your GitHub Actions runners, not our infrastructure:
- Free tier: 2000 minutes/month (GitHub Free)
- Typical scan: 3-5 minutes
- Result: ~400 scans/month on free tier
For larger organizations, use self-hosted runners for unlimited usage.
- ✅ No Cloud Credentials Stored - Uses OIDC (keyless authentication)
- ✅ Read-Only Operations - All policies are 100% safe, no destructive actions
- ✅ Multi-Tenant Secure - Repository tokens prevent cross-tenant attacks
- ✅ Minimal Permissions - Only requires Reader and Monitoring Reader roles
Azure:
# Test authentication locally
az login
az account show
# Verify service principal
az ad sp show --id $AZURE_CLIENT_IDAWS:
# Test authentication locally
aws sts get-caller-identity
# Verify role trust policy
aws iam get-role --role-name LeftSize-GitHub-ActionThis is normal! It means your infrastructure is well-optimized. The action will still submit a report with zero findings.
Check:
LEFTSIZE_INSTALLATION_IDis correctLEFTSIZE_REPOSITORY_TOKENmatches your repository- Backend URL is reachable (
https://api.leftsize.com)
Findings are saved as artifacts even if submission fails.
- 📚 Documentation
- 💬 GitHub Discussions
- 🐛 Report Issues
- 📧 Email: support@leftsize.io
MIT License - See LICENSE file for details.
Contributions welcome! Please read CONTRIBUTING.md for guidelines.
Made with ❤️ by the LeftSize team