Kubesense

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:

CloudFormationManual
What it doesLaunches a pre-filled stack that creates the read-only role and registers the account itselfYou create the credential, then enter it in the form
Steps in AWSAcknowledge the IAM capability and click CreateCreate a role, user, or IRSA binding by hand
Requires KubeSense to be reachable from AWSYes — the stack calls back over public HTTPSNo
Use it whenThe normal caseCloudFormation cannot reach KubeSense, or the credential already exists

Prerequisites

  1. 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
  2. Permission to create IAM roles and policies in the account you are connecting
  3. For the CloudFormation path, a KubeSense endpoint reachable over public HTTPS from AWS, since the stack registers the account by calling back
  4. 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.

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:

FieldWhat to enter
Controller Role ARNThe role this KubeSense controller runs as, from the prerequisite above. The enrolled account trusts it to assume the collector role.
RegionWhere the stack is created, and the region used for API calls from the account.
Resource TypesWhat 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:

ModeWhat it usesUse it when
Role Access (IAM Role)An IAM role in the target account that trusts the controller, assumed via STSThe general case, including any account other than the controller's own
Service AccountThe controller's own identity — IRSA on EKS, or an EC2 instance profileYou are connecting the account the controller itself runs in
Credentials (Access Key)A static IAM access key pairLast 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:

PermissionWhat it enables
tag:GetResourcesResource tags in Cloud Explorer, for every resource type, in one call per region
cloudwatch:ListMetricsFinding which S3 and ECS container metrics exist
pi:DescribeDimensionKeysRDS top queries (Performance Insights)
pi:ListAvailableResourceMetrics, pi:GetResourceMetricsRDS Database Insights metrics
autoscaling:DescribeAutoScalingGroupsAuto Scaling groups, and linking EC2 instances to their group
events:ListRulesEventBridge rules — EventBridge metrics are reported per rule
dynamodb:DescribeTableDynamoDB table size, item count and provisioned capacity
organizations:DescribeAccountOptional. 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-identity

The 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:

FieldWhat to enter
NameA friendly label for this account, e.g. aws-prod.
AWS Account IDThe 12-digit account ID, no hyphens.
Authentication ModeOne of the three modes above.
IAM Role ARNRole 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 KeyCredentials mode only. Stored encrypted and never displayed again.
Collection SettingsWhich 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

  1. The credential resolves. The screen after Connect reports this. On the CloudFormation path, the stack reaching CREATE_COMPLETE is the equivalent.
  2. 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.
  3. 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

SymptomLikely causeAction
AccessDenied when assuming the roleThe trust policy does not name the controller, or the controller lacks sts:AssumeRoleRead 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 rightThe trust policy carries an sts:ExternalId conditionKubeSense sends no external ID. Remove the condition
The account connects but collects nothingNo resource types selectedOpen the integration's settings and enable the types you want
Connected, but data is attributed to the wrong accountThe role ARN belongs to a different account than the registered account IDKubeSense rejects this by design. Correct the account ID or the role ARN
Stack fails with KubeSenseCollectorRole already existsThe account already has the role from an earlier attemptIAM 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 functionKubeSense was not reachable from AWS, or the token was rotatedConfirm the endpoint is publicly reachable over HTTPS, then generate a fresh launch link and retry
Service Account mode collects the controller's own accountNo role ARN was set, so the controller's default credential chain was usedThat is the intended behaviour of the mode. To read a different account, use Role Access
env | grep AWS_ shows no AWS_* variablesThe pod started before the ServiceAccount annotation was appliedRe-run kubectl rollout restart for the controller deployment