Single Account Setup
Connect one AWS account to KubeSense for read-only inventory and CloudWatch metrics — by one-click CloudFormation, an IAM role you create yourself, or the controller's own identity.
Overview
KubeSense observes an AWS account using read-only credentials, discovering your
resources and pulling their metrics from CloudWatch so the cloud estate appears alongside
the Kubernetes telemetry KubeSense already collects. Nothing is created, changed or
deleted in the account — every call is a Describe, List, Get or GetMetricData.
This page covers connecting one account. For an estate spread across an AWS Organization, see Multi-Account Setup, which onboards them all in one rollout.
Two ways to connect a single account, offered as cards under Settings → Integrations → AWS → Add New AWS Account → Single Account:
| CloudFormation | Manual | |
|---|---|---|
| What it does | Launches a pre-filled stack that creates the read-only role and registers the account itself | You create the credential, then enter it in the form |
| Steps in AWS | Acknowledge the IAM capability and click Create | Create a role, user, or IRSA binding by hand |
| Requires KubeSense to be reachable from AWS | Yes — the stack calls back over public HTTPS | No |
| Use it when | The normal case | CloudFormation cannot reach KubeSense, or the credential already exists |
Prerequisites
- A KubeSense controller running with an identity of its own — an EKS IRSA role or an EC2 instance profile. Both paths reference it, and the Manual path's role mode is built on it
- Permission to create IAM roles and policies in the account you are connecting
- For the CloudFormation path, a KubeSense endpoint reachable over public HTTPS from AWS, since the stack registers the account by calling back
- Administrator access to the KubeSense dashboard
Find the controller's identity first: Both paths name it. Run kubectl exec -n kubesense CONTROLLER-POD -- aws sts get-caller-identity --query Arn --output text. If it prints an assumed-role ARN, convert it to the plain IAM role form — arn:aws:iam::111111111111:role/kubesense-controller — and use that. The Manual path's role form also resolves and displays this principal for you, ready to copy into a trust policy.
Path A — CloudFormation (recommended)
The stack creates the read-only collector role in the target account and registers that account with KubeSense itself, so there is nothing to copy back.
Step 1 — Fill in the setup screen
Settings → Integrations → AWS → Add New AWS Account → Single Account → CloudFormation:
| Field | What to enter |
|---|---|
| Controller Role ARN | The role this KubeSense controller runs as, from the prerequisite above. The enrolled account trusts it to assume the collector role. |
| Region | Where the stack is created, and the region used for API calls from the account. |
| Resource Types | What to collect from the account. An account registered with none selected authenticates correctly and then collects nothing. |
Continue — that saves the configuration and moves to the launch step.
Step 2 — Launch the stack
Select Launch stack in AWS. KubeSense generates a CloudFormation quick-create link with every stack parameter pre-filled and opens the AWS console in a new tab. Acknowledge the IAM capability and select Create stack. The account appears in KubeSense once the stack completes.
The generated link is also shown on the screen with a copy button. If the console opened signed in to the wrong AWS account, sign in to the account you are enrolling and use that link — it stays valid.
The launch link is a credential, and issuing one rotates the token: The URL embeds a registration token, so treat it like a password — anyone holding it can register an account against your tenant. Generating a launch link issues a fresh token and invalidates the previous one, which means any earlier link stops working and, importantly, a CloudFormation StackSet deployed from the Multi-Account path stops being able to register new accounts until it is updated with the new token. If you run a StackSet rollout, do not use this path afterwards without redeploying it.
What the stack creates
Two resources, no agents and no persistent compute: the read-only collector role
(KubeSenseCollectorRole, with a least-privilege inline policy listing only the API calls
KubeSense makes), and a registration function that runs once at stack create and once at
stack delete. On create it tells KubeSense the account is available, supplying nothing but
its own account ID; on delete it says the reverse and collection stops, with everything
already collected retained.
Deleting the stack is therefore how you disconnect an account connected this way.
Path B — Manual
Use this when CloudFormation cannot reach KubeSense, or when the credential already exists. Choose an Authentication Mode on the form:
| Mode | What it uses | Use it when |
|---|---|---|
| Role Access (IAM Role) | An IAM role in the target account that trusts the controller, assumed via STS | The general case, including any account other than the controller's own |
| Service Account | The controller's own identity — IRSA on EKS, or an EC2 instance profile | You are connecting the account the controller itself runs in |
| Credentials (Access Key) | A static IAM access key pair | Last resort — long-lived keys have to be stored and rotated |
Step 1 — Create the credential in AWS
For Role Access. Create a read-only role in the target account, named
KubeSenseCollectorRole by convention, with a trust policy naming the controller role
exactly:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/kubesense-controller"
},
"Action": "sts:AssumeRole"
}]
}Attach read-only permissions: either the AWS-managed ReadOnlyAccess policy, or the
read-only permissions policy below if your security review
requires least privilege. ReadOnlyAccess is simpler but broader — it also grants read
access to data, such as the contents of S3 objects, which KubeSense never reads.
Read-only permissions policy
This policy grants only the API calls KubeSense makes, and every one is read-only:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "KubeSenseCollectorReadOnly",
"Effect": "Allow",
"Resource": "*",
"Action": [
"ec2:Describe*",
"autoscaling:DescribeAutoScalingGroups",
"eks:ListClusters",
"eks:DescribeCluster",
"ecs:List*",
"ecs:Describe*",
"rds:Describe*",
"elasticache:Describe*",
"elasticloadbalancing:Describe*",
"lambda:ListFunctions",
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"sns:ListTopics",
"sns:GetTopicAttributes",
"sqs:ListQueues",
"kinesis:ListStreams",
"firehose:ListDeliveryStreams",
"kafka:ListClustersV2",
"lex:ListBots",
"wafv2:ListWebACLs",
"apigateway:GET",
"cloudfront:ListDistributions",
"route53:ListHostedZones",
"route53:ListHealthChecks",
"dynamodb:ListTables",
"dynamodb:DescribeTable",
"states:ListStateMachines",
"events:ListEventBuses",
"events:ListRules",
"redshift:DescribeClusters",
"es:ListDomainNames",
"es:DescribeDomain",
"sagemaker:ListEndpoints",
"sagemaker:DescribeEndpoint",
"sagemaker:DescribeEndpointConfig",
"cloudwatch:GetMetricData",
"cloudwatch:ListMetrics",
"pi:DescribeDimensionKeys",
"pi:ListAvailableResourceMetrics",
"pi:GetResourceMetrics",
"tag:GetResources",
"organizations:DescribeAccount"
]
}
]
}Leaving an action out does not stop collection — the feature it serves just stays empty, which is easy to mistake for a KubeSense problem. These are the ones most often trimmed:
| Permission | What it enables |
|---|---|
tag:GetResources | Resource tags in Cloud Explorer, for every resource type, in one call per region |
cloudwatch:ListMetrics | Finding which S3 and ECS container metrics exist |
pi:DescribeDimensionKeys | RDS top queries (Performance Insights) |
pi:ListAvailableResourceMetrics, pi:GetResourceMetrics | RDS Database Insights metrics |
autoscaling:DescribeAutoScalingGroups | Auto Scaling groups, and linking EC2 instances to their group |
events:ListRules | EventBridge rules — EventBridge metrics are reported per rule |
dynamodb:DescribeTable | DynamoDB table size, item count and provisioned capacity |
organizations:DescribeAccount | Optional. The account's name, which resolves only when the role is in the management account |
DocumentDB and Neptune are covered by rds:Describe*, and API Gateway by apigateway:GET.
The policy needs no s3:ListBucket and no sts: permission.
Then allow the controller to assume the role, once, in the controller account:
aws iam put-role-policy \
--role-name kubesense-controller \
--policy-name KubeSenseAssumeCollectorRoles \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::*:role/KubeSenseCollectorRole"
}]
}'Send no external ID: KubeSense sends none — the assuming principal is your own controller, not a shared vendor account, so there is no confused deputy to guard against. A trust policy carrying an sts:ExternalId condition rejects every AssumeRole with AccessDenied. Leave the condition out; if you want to narrow the trust, use a condition on aws:PrincipalOrgID instead.
For Service Account (IRSA). Attach the read-only policy directly to the controller's own role, and confirm the identity resolves to the account you are connecting:
kubectl exec -n kubesense <controller-pod> -- env | grep AWS_
# AWS_ROLE_ARN=arn:aws:iam::123456789012:role/kubesense-controller
# AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
kubectl exec -n kubesense <controller-pod> -- aws sts get-caller-identityThe Account value must match the account ID you register below. If the AWS_* variables
are absent, the pod started before the ServiceAccount annotation was applied — re-run
kubectl rollout restart and check again.
For Credentials (Access Key). Create an IAM user with the same read-only policy and generate an access key pair. Prefer either of the other two modes: a static key is stored, never expires on its own, and has to be rotated by hand.
Step 2 — Connect in KubeSense
Settings → Integrations → AWS → Add New AWS Account → Single Account → Manual:
| Field | What to enter |
|---|---|
| Name | A friendly label for this account, e.g. aws-prod. |
| AWS Account ID | The 12-digit account ID, no hyphens. |
| Authentication Mode | One of the three modes above. |
| IAM Role ARN | Role Access only. The role from Step 1. Entering just the role name also works — it is combined with the account ID above. The form shows the controller principal your trust policy must name. |
| AWS Access Key ID / Secret Access Key | Credentials mode only. Stored encrypted and never displayed again. |
| Collection Settings | Which resource types to collect. |
Select Connect. KubeSense verifies the credential immediately and the next screen reports success, or the exact error AWS returned.
Every resource type starts disabled: An account connected with nothing selected under Collection Settings authenticates correctly and then collects nothing — which reads as broken rather than unconfigured. Choose your resource types on this screen; you can change them at any time from the integration's settings, and a change takes effect on the next collection cycle.
Verify
- The credential resolves. The screen after Connect reports this. On the
CloudFormation path, the stack reaching
CREATE_COMPLETEis the equivalent. - Resources appear. Open the integration from Settings → Integrations → AWS. Within a minute or two it shows a count per resource type. A type showing zero either has no resources or is not selected under Collection Settings.
- Metrics arrive. Allow up to 10 minutes for the first metric cycle, then open any discovered resource and confirm its charts are populated.
KubeSense checks, on every collection cycle, that the assumed identity belongs to the
account_id you registered. A mismatched or wrong role ARN is rejected automatically — it
will never collect the wrong account's data.
Troubleshooting
| Symptom | Likely cause | Action |
|---|---|---|
AccessDenied when assuming the role | The trust policy does not name the controller, or the controller lacks sts:AssumeRole | Read the identity in the error — it is the principal actually used. Fix whichever half is missing: the controller's identity policy, or the role's trust policy |
AccessDenied and the trust policy looks right | The trust policy carries an sts:ExternalId condition | KubeSense sends no external ID. Remove the condition |
| The account connects but collects nothing | No resource types selected | Open the integration's settings and enable the types you want |
| Connected, but data is attributed to the wrong account | The role ARN belongs to a different account than the registered account ID | KubeSense rejects this by design. Correct the account ID or the role ARN |
Stack fails with KubeSenseCollectorRole already exists | The account already has the role from an earlier attempt | IAM role names are unique per account and CloudFormation will not adopt a resource it did not create. Delete the stack that owns it, or the role, and retry |
| Stack create fails with an HTTP error from the registration function | KubeSense was not reachable from AWS, or the token was rotated | Confirm the endpoint is publicly reachable over HTTPS, then generate a fresh launch link and retry |
| Service Account mode collects the controller's own account | No role ARN was set, so the controller's default credential chain was used | That is the intended behaviour of the mode. To read a different account, use Role Access |
env | grep AWS_ shows no AWS_* variables | The pod started before the ServiceAccount annotation was applied | Re-run kubectl rollout restart for the controller deployment |