Aws

Least-Privilege AWS Account Setup for Harness in a SaaS Environment

Contents

When deploying a SaaS service on AWS and connecting Harness for CI/CD, one of the first questions is how Harness should access the AWS account. The easiest option is to create an Access Key and attach AdministratorAccess, but that creates an unnecessarily large blast radius if the credential is ever compromised.

This post describes a setup that allows Harness to deploy AWS resources while granting only the permissions it actually needs.

Assumption: an AWS Connector is configured through Harness Delegate or Harness Platform, and a restricted IAM Role is provided in the target SaaS deployment account.

The short answer

Use the following approach where possible.

  1. Do not use an IAM User Access Key as the default authentication method.
  2. Prefer IAM Role + STS AssumeRole for Harness access to AWS.
  3. When the external Harness SaaS assumes the role, include an External ID in the trust policy.
  4. Separate roles by deployment environment.
    • harness-dev-deploy-role
    • harness-stg-deploy-role
    • harness-prod-deploy-role
  5. Do not try to produce a perfect least-privilege policy immediately. Start small, observe Access Analyzer and CloudTrail, then refine the policy.

AWS recommends temporary credentials and roles over long-lived IAM user Access Keys. IAM policies should also follow the least-privilege principle by allowing only the actions required for the workload.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Harness
  └─ AWS Connector
      ├─ Harness Delegate or Harness Platform credential
      └─ STS AssumeRole
          └─ AWS target account
              └─ harness-*-deploy-role
                  ├─ ECR push/pull
                  ├─ ECS or EKS deployment
                  ├─ S3 artifact access
                  ├─ CloudWatch Logs read access
                  └─ Only the required IAM PassRole permissions

Why use a role?

An Access Key is a long-lived credential that remains usable until it is revoked. STS authentication through an IAM Role issues temporary credentials with a limited session duration.

The target account can also separate two concerns:

  • the trust policy controls who can assume the role
  • the permission policy controls what the assumed role can do

Harness AWS Connector supports a cross-account role ARN and External ID. The connector’s base credential assumes a role in the target account, and that assumed role receives only the permissions required for services such as S3, ECS, EC2, or EKS.

Account and role design

For a SaaS environment, I prefer at least the following separation.

EnvironmentPurposeHarness permissions
sharedECR, shared artifacts, CI infrastructureImage push/pull and artifact read/write
devDevelopment deploymentsCan be broader, but avoid administrator access
stgPre-production validationRestricted similarly to production
prodLive customer trafficNarrowest permissions and mandatory approval

Even before adopting AWS Organizations, keep the dev, staging, and production roles separate by name and policy. This reduces migration cost later when accounts are split or Control Tower is introduced.

Trust policy example

A role assumed by Harness from outside the target account can begin with a trust policy like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<HARNESS_OR_DELEGATE_ACCOUNT_ID>:role/<HARNESS_BASE_ROLE>"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "harness:<HARNESS_ACCOUNT_ID>:<SERVICE_NAME>:prod"
        }
      }
    }
  ]
}

Two points matter here.

  • Restrict Principal to a specific AWS account and role whenever possible.
  • Use sts:ExternalId to reduce the confused deputy risk.

An External ID is not a secret like a password. It is an additional control that helps distinguish whether an AssumeRole request made by a third-party service is intended for this specific customer account.

Permission policy example: ECS deployment

For an ECS/Fargate deployment, the Harness deployment role can start with permissions similar to the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EcrReadWriteForServiceRepositories",
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:CompleteLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:InitiateLayerUpload",
        "ecr:PutImage",
        "ecr:BatchGetImage",
        "ecr:DescribeRepositories"
      ],
      "Resource": "*"
    },
    {
      "Sid": "EcsDeploy",
      "Effect": "Allow",
      "Action": [
        "ecs:DescribeClusters",
        "ecs:DescribeServices",
        "ecs:DescribeTaskDefinition",
        "ecs:RegisterTaskDefinition",
        "ecs:UpdateService",
        "ecs:ListTasks",
        "ecs:DescribeTasks"
      ],
      "Resource": "*"
    },
    {
      "Sid": "PassOnlyTaskRoles",
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": [
        "arn:aws:iam::<ACCOUNT_ID>:role/<SERVICE_TASK_ROLE>",
        "arn:aws:iam::<ACCOUNT_ID>:role/<SERVICE_TASK_EXECUTION_ROLE>"
      ],
      "Condition": {
        "StringEquals": {
          "iam:PassedToService": "ecs-tasks.amazonaws.com"
        }
      }
    },
    {
      "Sid": "CloudWatchLogsRead",
      "Effect": "Allow",
      "Action": [
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams",
        "logs:GetLogEvents",
        "logs:FilterLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

In production, reduce Resource: "*" wherever possible. Some actions, such as ECR GetAuthorizationToken, cannot be restricted to a repository resource. A practical approach is to verify the deployment first and then narrow repository, cluster, and service ARNs incrementally.

Permission policy example: Terraform and IaC

When Harness uses Terraform to create VPC, ECS, RDS, and other infrastructure, the required permission scope becomes much larger. Do not place every permission into one role. Split the responsibilities instead.

RoleResponsibility
harness-iac-plan-roleRead-oriented permissions for terraform plan
harness-iac-apply-rolePermissions for terraform apply, used only after approval
harness-app-deploy-roleApplication image and service deployment

Avoid granting broad permissions such as iam:*, organizations:*, account:*, or kms:* from the beginning. When they are required, constrain them with specific paths, prefixes, resources, and tag conditions.

Harness Connector setup

  1. Create harness-prod-deploy-role in the target AWS account.
  2. Add the Harness-side principal and External ID to the role trust policy.
  3. Attach a least-privilege permission policy for the deployment type, such as ECS, EKS, Lambda, or Terraform.
  4. Create an AWS Connector in Harness.
  5. Select a cross-account role ARN or a Delegate-based AssumeRole authentication method.
  6. Enter the External ID.
  7. Run Test connection.
  8. Review only the failed actions in CloudTrail or Harness logs and add permissions as needed.

Harness documentation notes that the AWS Connector connection test may require ec2:DescribeRegions. When the test fails even though the actual deployment permissions look correct, check whether this action is missing.

Operations checklist

  • Enable MFA for the root account
  • Define expiration and rotation rules for any long-lived Access Key used by Harness
  • Allow only deployment-related actions in the production role
  • Restrict iam:PassRole to specific task and task execution roles
  • Use an External ID
  • Enable CloudTrail
  • Use IAM Access Analyzer to inspect external access and unused permissions
  • Add Harness approval or Change Management stages to production pipelines
  • Separate connectors for dev, staging, and production
  • Keep the break-glass administrator account separate and unused during normal operations

The rules I use

CI/CD permissions for a SaaS service need to balance deployment convenience with a small blast radius. I start with these rules.

  • Do not give Harness administrator access.
  • Separate human identities from automation roles.
  • Separate deployment roles by environment.
  • Treat PassRole and KMS permissions conservatively.
  • First make the deployment work, then immediately reduce permissions using Access Analyzer and CloudTrail evidence.

References