Skip to content

Configuring AWS Credentials

This guide covers how to securely configure AWS credentials for Controlinfra to scan your infrastructure.

Overview

Controlinfra needs AWS credentials to:

  1. Access your Terraform state (if using S3 backend)
  2. Run terraform plan to detect drift
  3. Query AWS APIs to compare actual vs. desired state

Credential Options

Option 1: IAM User Access Keys (Simple)

Create an IAM user with programmatic access:

  1. Go to AWS IAM Console
  2. Create a new user (e.g., controlinfra-scanner)
  3. Attach the required policy (see below)
  4. Generate access keys
  5. Enter keys in Controlinfra repository settings

For enhanced security, use a self-hosted runner:

  • Runner uses IAM role attached to the EC2 instance
  • No access keys stored in Controlinfra
  • Credentials never leave your infrastructure

Required IAM Permissions

Minimal Read-Only Policy

This policy provides read-only access to common AWS resources:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "TerraformStateAccess",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-terraform-state-bucket",
        "arn:aws:s3:::your-terraform-state-bucket/*"
      ]
    },
    {
      "Sid": "DynamoDBLocking",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:*:*:table/terraform-locks"
    },
    {
      "Sid": "ReadOnlyInfrastructure",
      "Effect": "Allow",
      "Action": [
        "ec2:Describe*",
        "s3:GetBucket*",
        "s3:GetObject*",
        "s3:ListBucket*",
        "rds:Describe*",
        "elasticloadbalancing:Describe*",
        "autoscaling:Describe*",
        "cloudwatch:Describe*",
        "cloudwatch:GetMetricStatistics",
        "iam:GetRole",
        "iam:GetPolicy",
        "iam:GetUser",
        "iam:ListAttachedRolePolicies",
        "iam:ListRolePolicies",
        "lambda:GetFunction",
        "lambda:ListFunctions",
        "sns:GetTopicAttributes",
        "sqs:GetQueueAttributes",
        "route53:GetHostedZone",
        "route53:ListResourceRecordSets",
        "cloudfront:GetDistribution",
        "acm:DescribeCertificate"
      ],
      "Resource": "*"
    }
  ]
}

Full Infrastructure Policy

For comprehensive coverage of all AWS services:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadOnlyAccess",
      "Effect": "Allow",
      "Action": [
        "acm:Describe*",
        "acm:Get*",
        "acm:List*",
        "apigateway:GET",
        "autoscaling:Describe*",
        "cloudfront:Get*",
        "cloudfront:List*",
        "cloudwatch:Describe*",
        "cloudwatch:Get*",
        "cloudwatch:List*",
        "cognito-idp:Describe*",
        "cognito-idp:List*",
        "dynamodb:Describe*",
        "dynamodb:List*",
        "ec2:Describe*",
        "ecr:Describe*",
        "ecr:Get*",
        "ecr:List*",
        "ecs:Describe*",
        "ecs:List*",
        "eks:Describe*",
        "eks:List*",
        "elasticache:Describe*",
        "elasticloadbalancing:Describe*",
        "es:Describe*",
        "es:List*",
        "events:Describe*",
        "events:List*",
        "iam:Get*",
        "iam:List*",
        "kms:Describe*",
        "kms:Get*",
        "kms:List*",
        "lambda:Get*",
        "lambda:List*",
        "logs:Describe*",
        "logs:Get*",
        "rds:Describe*",
        "rds:List*",
        "route53:Get*",
        "route53:List*",
        "s3:GetBucket*",
        "s3:GetObject*",
        "s3:ListBucket*",
        "secretsmanager:Describe*",
        "secretsmanager:List*",
        "sns:Get*",
        "sns:List*",
        "sqs:Get*",
        "sqs:List*",
        "ssm:Describe*",
        "ssm:Get*",
        "ssm:List*"
      ],
      "Resource": "*"
    },
    {
      "Sid": "TerraformState",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-terraform-state-bucket",
        "arn:aws:s3:::your-terraform-state-bucket/*"
      ]
    },
    {
      "Sid": "TerraformLocking",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:*:*:table/terraform-locks"
    }
  ]
}

Security Best Practices

1. Use Least Privilege

Only grant permissions for resources Terraform manages:

json
{
  "Effect": "Allow",
  "Action": ["ec2:Describe*"],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "ec2:ResourceTag/ManagedBy": "terraform"
    }
  }
}

2. Rotate Credentials Regularly

  • Set up a rotation schedule (e.g., every 90 days)
  • Use AWS Secrets Manager for automated rotation
  • Update Controlinfra settings after rotation

3. Monitor Credential Usage

Enable CloudTrail to track API calls:

json
{
  "eventSource": "sts.amazonaws.com",
  "eventName": "AssumeRole",
  "userIdentity": {
    "userName": "controlinfra-scanner"
  }
}

4. Use Self-Hosted Runners

For production environments:

  • Credentials stay in your AWS account
  • Use IAM roles instead of access keys
  • Better audit trail and compliance

Multiple AWS Accounts

If you manage infrastructure across multiple AWS accounts:

Option 1: Separate Credentials per Repository

Configure different AWS credentials for each repository in Controlinfra.

Option 2: Cross-Account Assume Role

  1. Create a role in each target account:
json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::SCANNER_ACCOUNT:user/controlinfra-scanner"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
  1. Grant assume role permission to the scanner user:
json
{
  "Effect": "Allow",
  "Action": "sts:AssumeRole",
  "Resource": [
    "arn:aws:iam::ACCOUNT_1:role/controlinfra-scanner",
    "arn:aws:iam::ACCOUNT_2:role/controlinfra-scanner"
  ]
}

Credential Storage

Controlinfra secures your credentials:

  • Encryption: All credentials are encrypted at rest using AES-256
  • Access Control: Credentials are only decrypted during scan execution
  • No Logging: Credentials are never written to logs
  • Isolation: Each user's credentials are isolated

Troubleshooting

"Access Denied" Errors

  1. Check IAM policy is attached to the user
  2. Verify the resource ARNs in the policy
  3. Check for Service Control Policies (SCPs) in AWS Organizations

"InvalidClientTokenId" Error

  • Access Key ID may be incorrect
  • User may have been deleted
  • Credentials may have been deactivated

"ExpiredToken" Error

  • If using temporary credentials, they may have expired
  • Generate new access keys

State File Access Issues

Error: Failed to load state: AccessDenied
  • Verify S3 bucket permissions
  • Check bucket policy allows the IAM user
  • Ensure the state file key path is correct

Next Steps

AI-powered infrastructure drift detection