Kubesense

Multi-Account Setup

Onboard every account in an AWS Organization at once — by Organization Discovery or a CloudFormation StackSet — using read-only cross-account roles and no long-lived keys.

Overview

KubeSense collects inventory and CloudWatch metrics from an AWS account by assuming a read-only IAM role inside it. For one or two accounts you add each by hand — see Single Account Setup. When your estate spans an AWS Organization, multi-account setup onboards them all in a single rollout — and keeps up with accounts that join, get suspended, or leave later.

There are two ways to run it. Pick based on one question: will you let KubeSense list your organization's accounts?

Organization DiscoveryCloudFormation StackSet
How accounts are foundKubeSense lists the organization and enrolls what it findsEach account registers itself as its stack is created
Extra permission neededA read-only role that can call organizations:ListAccountsNone
Reviewable preview before anything is enrolledYesNo
Scope by Organizational UnitYes, in KubeSenseYes, via StackSet deployment targets
Accounts that leave or are suspendedPaused automatically on the next cycleDe-registered when the stack instance is deleted
Choose it whenYou can grant organization read access — recommendedListing the organization is off the table

Both use read-only cross-account roles and store no long-lived AWS keys. Every account is verified against its own identity before any data is ingested, so a misconfigured role fails closed rather than silently attributing one account's resources to another.

Architecture

 Controller account  (the KubeSense controller runs here)
        │  sts:AssumeRole — read-only, nothing stored
        │
        ├── KubeSenseOrgListerRole   (management / delegated-admin account)
        │       └─ organizations:ListAccounts → the account list   [Approach A only]
        │
        ├── KubeSenseCollectorRole → Account A ──┐
        ├── KubeSenseCollectorRole → Account B   ├─ inventory + CloudWatch metrics
        └── KubeSenseCollectorRole → Account C ──┘

Controller account — the AWS account your KubeSense controller runs in. It holds one IAM identity, the controller role, named kubesense-controller by default, and that identity is what reaches into every other account.

Monitored account — any account you want observed. Each contains an identical read-only role, KubeSenseCollectorRole by default, whose trust policy names your controller role. Nothing else is installed in a monitored account.

Management / delegated-administrator account — organizations:ListAccounts only succeeds from one of these. Approach A needs one; a delegated administrator is strongly preferred, since it keeps the lister role out of your most sensitive account.

The collector role name must be identical in every account: KubeSense never stores a per-account role ARN. It derives one for each account as arn:aws:iam::ACCOUNT-ID:role/KubeSenseCollectorRole, and that single convention is what makes enrollment work with no per-account configuration. You may use a different name, but change it in every account and tell KubeSense the new name — otherwise the derived ARN is wrong and that account collects nothing.

Prerequisites

Before you begin, ensure you have:

  1. A KubeSense controller running in your controller account with an identity of its own — an EKS IRSA role or an EC2 instance profile
  2. Permission to create IAM roles in the controller account and in your management account
  3. Permission to create CloudFormation StackSets from your management or delegated-administrator account
  4. Trusted access enabled between CloudFormation and AWS Organizations — a one-time action under CloudFormation → StackSets → Enable trusted access. Without it the service-managed permission model is unavailable
  5. Administrator access to the KubeSense dashboard

Step 1 — Prepare the controller account

Do this once, in the controller account, whichever approach you choose.

1.1 Give the controller its own IAM identity

Skip this if it already has one. A pod with no identity falls back to whatever the node has, which is not something to build organization-wide access on.

Name the role kubesense-controller. Do not name it KubeSenseCollectorRole — that name is reserved for the collector role created in every account, possibly including the controller account, and two roles cannot share a name.

The controller role needs no AWS read permissions of its own. All collection happens through the collector roles it assumes, so the only permission it ever gets is the sts:AssumeRole grant in Step 1.3.

On EKS, confirm the cluster has an IAM OIDC provider — IRSA does not work without one:

aws eks describe-cluster --name <cluster> \
  --query identity.oidc.issuer --output text

# if that printed None, associate one (once per cluster)
eksctl utils associate-iam-oidc-provider --cluster <cluster> --approve

Then create the role and bind it to the controller's ServiceAccount:

kubectl get pod <controller-pod> -n kubesense \
  -o jsonpath='{.spec.serviceAccountName}'

eksctl create iamserviceaccount \
  --cluster <cluster> \
  --namespace kubesense \
  --name <controller-serviceaccount> \
  --role-name kubesense-controller \
  --approve \
  --override-existing-serviceaccounts

kubectl rollout restart deployment/<controller-deployment> -n kubesense

The restart matters: EKS injects credentials at pod startup, so an annotation added to a running pod has no effect until it is replaced. If the ServiceAccount is managed by Helm, add the annotation to your values file too, or the next upgrade removes it.

On EC2, create the role with an EC2 trust policy instead and attach it to the instance as an instance profile. Its ARN is what the rest of this page calls the controller role.

1.2 Confirm the identity the controller actually uses

Every template and trust policy below names this identity, so getting it right here saves the most common class of failure:

kubectl exec -n kubesense <controller-pod> -- \
  aws sts get-caller-identity --query Arn --output text

If it prints an assumed-role ARN such as arn:aws:sts::111111111111:assumed-role/kubesense-controller/..., convert it to the plain IAM role form — arn:aws:iam::111111111111:role/kubesense-controller — and use that everywhere below.

Check this even if you are sure: If the command returns something like assumed-role/CLUSTER-node-role/i-0abc..., the controller is falling back to the EC2 node instance role because its own identity is not reaching the pod. Do not use the node role in your trust policies — it is shared by every pod on that node, so trusting it grants organization-wide read access to anything scheduled there. Fix the controller's identity instead, then restart the pod so the credentials are injected.

1.3 Allow the controller to assume the collector roles

Attach an inline policy to the controller's role. In the console: IAM → Roles → the controller's role → Add permissions → Create inline policy → JSON, paste the policy, and name it KubeSenseAssumeCollectorRoles.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "sts:AssumeRole",
    "Resource": [
      "arn:aws:iam::*:role/KubeSenseCollectorRole",
      "arn:aws:iam::*:role/KubeSenseOrgListerRole"
    ]
  }]
}

Or with the CLI:

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",
        "arn:aws:iam::*:role/KubeSenseOrgListerRole"
      ]
    }]
  }'

The second ARN is only used by Approach A. Leaving it in costs nothing — it permits assuming a role that does not exist unless you create it — and saves editing this policy if you switch approaches. Narrow the * to explicit account IDs if you prefer; the grant is already bounded by role name.

Cross-account access needs both halves: Assuming a role across accounts requires two permissions granted in different places — this identity policy on the controller, and a trust policy on the target role naming the controller. Miss either and the assume fails with AccessDenied. The templates below write the trust side for you; this step is the half you own.

Approach A — Organization Discovery

You create one extra read-only role that permits organizations:ListAccounts. KubeSense assumes it, enumerates the organization, shows you a plan, and enrolls the accounts you approve. Five steps: roll out the collector role, create the lister role, configure, review, activate.

Step A1 — Roll the collector role out to every account

In the KubeSense dashboard, go to Settings → Integrations → AWS → Add New AWS Account, open the Multi Account tab and choose Organization Discovery. Under Deploy the collector role, click View setup instructions — the modal offers the CloudFormation template (kubesense-monitored-account-role.yaml) as a download and shows the four parameter values ready to copy.

Then, signed in to your management or delegated-administrator account:

  1. Go to CloudFormation → StackSets → Create StackSet.
  2. Under Permissions, keep the default Service-managed permissions. This lets CloudFormation create roles across the organization without pre-provisioning anything per account.
  3. Choose Upload a template file and select the downloaded template.
  4. Name the StackSet — for example kubesense-collector-role — and fill in the parameters:
ParameterValue
ControllerAccountIdYour 12-digit controller account ID, from the ARN confirmed in Step 1.2.
ControllerRoleNameThe controller role's name only, not its ARN — the part after role/. Default kubesense-controller.
CollectorRoleNameLeave as KubeSenseCollectorRole unless you have a naming standard. It must match what you tell KubeSense in Step A3.
OrgIdOptional but recommended: your o-xxxx id. The trust policy then additionally requires the calling principal to belong to this organization, so a leaked controller ARN alone is not enough to assume the role.
  1. On Configure StackSet options, tick "I acknowledge that AWS CloudFormation might create IAM resources with custom names" (CAPABILITY_NAMED_IAM). It is required because the template pins the role name, which is deliberate.
  2. Under Deployment targets, choose Deploy to organizational units and select the OUs holding the accounts you want observed. Select the organization root to cover everything.
  3. Enable automatic deployment. This is what gives accounts added to those OUs later the role with no action from you, and removes it cleanly when an account leaves. Skipping it is the single most common reason a later-created account never collects.
  4. Choose a single region. IAM is global, so extra regions only create redundant stack instances.
  5. Submit, and wait for every stack instance to reach CURRENT / SUCCEEDED.

Count the stack instances before moving on: A service-managed StackSet never deploys to your organization's management account, even when you target the organization root. AWS excludes it silently — no stack instance and no error — so an organization of N accounts produces N−1 instances when the management account is one of them. That is usually fine, because "Skip management account" is on by default. If you do want that account observed, deploy the same template into it as a plain stack (CloudFormation → Create stack, not Create StackSet) with the same parameters, and turn "Skip management account" off in Step A3. Turning the setting off without deploying the role leaves the account enrolled with no role to assume, failing every collection cycle.

About the role's permissions: This template attaches the AWS-managed ReadOnlyAccess policy, which is broad — it includes read access to object contents in S3, which KubeSense never uses. If your security review requires least privilege, replace it with the read-only permissions policy on the Single Account Setup page, which grants only the API calls KubeSense actually makes. The StackSet template used by Approach B already attaches a least-privilege policy inline.If you built a least-privilege policy from an earlier version, check it against that page. Recent additions are tag:GetResources for resource tags, pi:ListAvailableResourceMetrics and pi:GetResourceMetrics for RDS Database Insights metrics, and dynamodb:DescribeTable for DynamoDB table size and capacity. Leaving one out does not stop collection; that feature just stays empty.

Step A2 — Create the organization lister role

This is the one role Approach B does not need. It lets KubeSense enumerate the organization, and it lives in your management or delegated-administrator account — nowhere else, and only once.

Create a role there named KubeSenseOrgListerRole with this trust policy, using the exact controller ARN from Step 1.2:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::<CONTROLLER-ACCOUNT-ID>:role/kubesense-controller" },
    "Action": "sts:AssumeRole"
  }]
}

and this permissions policy — read-only organization metadata, nothing else:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "organizations:ListAccounts",
      "organizations:ListAccountsForParent",
      "organizations:ListParents",
      "organizations:ListOrganizationalUnitsForParent",
      "organizations:DescribeOrganization"
    ],
    "Resource": "*"
  }]
}

These five actions are exactly what discovery calls, and no more. There is no write permission and no access to account contents. Note the role's ARN — KubeSense asks for it next.

Why a second role at all: Your controller runs in an ordinary member account, where ListAccounts is refused no matter what permissions you grant locally — the call only succeeds from the management or delegated-admin account. So KubeSense makes two hops: it assumes this lister role to find the accounts, then assumes each monitored account's collector role to collect from them.

Step A3 — Configure the enrollment in KubeSense

Back on the Organization Setup screen, fill in:

FieldWhat to enter
Organization Lister Role ARNThe ARN from Step A2.
RegionsThe regions each enrolled account is queried in. An account collects nothing from a region that is not selected. The first is also used to call the Organizations API.
Include Organization Unit IDsOptional. Restrict enrollment to specific OUs (ou-xxxx-xxxxxxxx); their descendants are included. Leave empty to cover the whole organization.
Exclude Account IDsOptional. Accounts to always skip. This wins over the OUs above.
Skip management accountOn by default. Leave it on unless you run workloads in the management account.
Collector Role NameFixed to KubeSenseCollectorRole — it has to match the role deployed in Step A1.
Resource TypesWhich services each newly enrolled account collects. An account enrolled with none selected is created and then collects nothing.

Save the configuration. Nothing is collected yet — a new enrollment starts in preview mode.

Resource types apply at first enrollment only: These defaults apply at the moment an account is first enrolled. Changing an individual account's settings afterwards is safe — enrollment never overwrites your per-account choices on a later cycle. Equally, editing these defaults later affects only accounts enrolled from that point on.

Step A4 — Review the preview

KubeSense lists your organization and shows what it would do with each account, without creating anything. Discovery takes 10–30 seconds.

OutcomeMeaning
Would enrollThe account will be onboarded, using the derived collector role ARN.
Skipped — management accountIt is the organization's management account and skip management account is on.
Skipped — excludedYou listed it under excluded accounts.
Skipped — inactiveAWS reports it as suspended or closed.

Read the list carefully — it is the last checkpoint before collection starts, and the only place a misconfiguration is cheap to fix:

  • An account you expected is missing entirely — it is outside the OUs you selected. Check the OU list.
  • Nothing is listed at all, or the preview errors — almost always the lister role: either the controller cannot assume it, or it does not live in the management or delegated-administrator account.
  • An account is listed that should not be — add it to excluded accounts and review again.

What the preview cannot tell you: It confirms which accounts KubeSense will enroll — not that each one's collector role actually exists. The role ARN is derived by convention and never called at this stage, so an account whose Step A1 stack instance failed still shows as "Would enroll" and then fails once collection begins. If Step A1 reported any failed stack instances, resolve those first.

Step A5 — Activate

Choose Enable Enrollment. KubeSense creates one integration per approved account and begins collecting within a few minutes. (Finish without enrolling leaves it in preview — nothing is created, and you can return and enable it later.)

From here the enrollment maintains itself. On a recurring cycle — roughly every 15 minutes — it re-reads your organization and reconciles:

  • New accounts in the covered OUs are enrolled automatically, with your configured defaults.
  • Accounts that leave the organization or the selected OUs, become suspended, or are added to your exclude list are paused. Pausing stops collection and retains everything already collected.
  • Accounts that return are resumed automatically.
  • Accounts you added by hand are never touched, whatever else changes.

Approach B — CloudFormation StackSet

Use this when you will not grant organizations:ListAccounts. Each account registers itself as its stack deploys, so KubeSense never enumerates your organization.

Do these steps in order. Each account calls KubeSense the moment its stack is created, so the KubeSense side has to be ready before the StackSet rolls out — otherwise every stack instance fails and must be retried.

Step B1 — Prepare KubeSense

Go to Settings → Integrations → AWS → Add New AWS Account, open the Multi Account tab and choose CloudFormation StackSet. On the Setup step provide the Controller Role ARN from Step 1.2, the regions to collect from, any OU or account filters, and the resource types to enable on each account. No lister role is required. Save to continue.

On the Deploy step, copy the Template URL and click Generate Token. That completes the parameter set: ControllerRoleArn, KubeSenseEndpoint, PushToken, PushPath and CollectorRoleName, each with a copy button.

The push token is shown once: Copy it before you leave the screen — it cannot be recovered afterwards, only replaced. Generating a new token stops any StackSet still carrying the previous one from registering, until that StackSet is updated with the new value.

Step B2 — Create the StackSet

Signed in to your management or delegated-administrator account, go to CloudFormation → StackSets → Create StackSet, keep Service-managed permissions, paste the template URL from Step B1, and fill in:

ParameterValue
ControllerRoleArnThe full ARN from Step 1.2. Note this template takes the ARN, unlike Approach A's which takes an account id and role name separately.
KubeSenseEndpointThe HTTPS base URL of KubeSense, from Step B1. Must be reachable from AWS.
PushTokenThe registration token from Step B1. Marked no-echo, so it does not appear in stack events or the console.
PushPathThe registration path on that endpoint. The default /api/cloud-org-enrollments/push is correct when KubeSenseEndpoint is your dashboard, which is the usual case. Enter it with no trailing slash — a wrong value makes every stack instance fail with HTTP 404 or 405.
CollectorRoleNameMust match what you configured in KubeSense.
CaBundleOnly if your endpoint's certificate is issued by a private CA, or its chain omits an intermediate.
InsecureSkipTlsVerifyDefaults to true so enrollment works before the certificate chain is sorted out. Set it to false once the chain verifies.

Acknowledge CAPABILITY_NAMED_IAM as in Step A1, target your OUs, and enable automatic deployment — that is what onboards future accounts and cleanly removes departing ones.

Network requirement: The registration function runs in AWS, outside your VPCs, so the KubeSense endpoint must be reachable over public HTTPS from the accounts you deploy to. If KubeSense is only reachable internally, expose the registration path through a public endpoint. Only that one path needs to be reachable — it authenticates its own token and ignores any session headers.

Step B3 — Deploy the management account as a plain stack

Skip this only if you do not want your organization's management account observed. A service-managed StackSet never deploys to it, silently, so it registers nothing.

  1. Sign in to the management account and go to CloudFormation → Create stack → With new resources. This is Create stack, not Create StackSet.
  2. Use the same template URL.
  3. Enter the same parameter values as the StackSet — particularly CollectorRoleName, PushPath and PushToken, since KubeSense derives this account's role ARN by the same convention as every other. Reuse the same push token; generating a new one invalidates the StackSet's.
  4. Acknowledge CAPABILITY_NAMED_IAM and create the stack.

Because this is a plain stack it is not covered by the StackSet's automatic deployment. It is the one account you maintain by hand: if you later update the StackSet's parameters, update this stack too.

Step B4 — Verify the rollout

Watch the StackSet's Stack instances tab until every instance reports SUCCEEDED. Each successful instance means that account has created its read-only role and registered itself with KubeSense. A failed instance carries the HTTP status in its status reason — see Troubleshooting. Registration is idempotent, so failed instances can be retried safely once the cause is fixed.

Then confirm from the KubeSense side: the enrolled account count should be your successful stack instances, plus one if you deployed the management account in Step B3. If the dashboard is one short and every instance succeeded, the missing account is almost always the management account.

What the StackSet deploys

Two resources per account — no agents, no persistent compute:

  1. The read-only collector role, trusting your controller. Unlike Approach A's template, this one uses a least-privilege inline policy listing only the API calls KubeSense makes, rather than the broad AWS-managed ReadOnlyAccess.
  2. A registration function, which runs only at stack create and stack delete. On create it tells KubeSense "this account is now available", supplying nothing but its own account ID; on delete it says the reverse. It also calls iam:ListAccountAliases in its own account so the account appears under its alias rather than a bare twelve-digit number — best-effort, and an account that denies it still registers.

The function cannot choose which role KubeSense uses — KubeSense derives that itself from the account ID and your configured role name, so a registration request cannot point KubeSense at an arbitrary role even if the token leaks. Deleting a stack instance de-registers that account and stops collection; data already collected is retained, and a KubeSense outage never blocks a stack teardown.

Managing enrolled accounts

Seeing what is enrolled. The AWS integration page lists every automatically enrolled account, whether it is collecting or paused, and how it was registered. Manually added integrations are listed separately — enrollment never claims them.

Changing what one account collects. Adjust that account's resource types as usual. Enrollment will not overwrite the change on a later cycle.

Changing the defaults for future accounts. Edit the resource types on the enrollment. Existing accounts keep their current settings; only accounts enrolled from that point on pick up the new defaults.

Stopping collection for one account. With Organization Discovery, add it to Exclude Account IDs — it is paused on the next cycle and its data is retained; removing it from the list resumes it. With the StackSet, delete that account's stack instance; redeploying re-registers it.

Pausing enrollment entirely. With Organization Discovery, choose Return to preview on the Organization Setup screen and save: discovery keeps running so the plan stays current, but no account is enrolled, paused, or resumed automatically. Choose Enable Enrollment on the Review screen to resume. With the StackSet, generate a new push token without redeploying — accounts whose stacks are created after that cannot register until the StackSet is updated.

Accounts already enrolled keep collecting either way; tearing those down is a separate, deliberate action, so pausing can never silently stop collection.

Troubleshooting

SymptomLikely causeAction
An account is listed but is not collectingKubeSenseCollectorRole missing in that account, or named differentlyConfirm the role exists with the exact configured name. If the logs report the account resolving to your controller account id, that account has no role of its own and the controller fell back to its own identity — re-run its stack instance
Preview is empty or errorsThe lister role is not in the management or delegated-admin account, or lacks the five organizations:* permissionsListAccounts returns nothing from an ordinary member account however it is permissioned. Recheck Step A2
Preview reports AccessDenied on sts:AssumeRoleThe identity in the message is the principal actually usedIf it is not the controller role from Step 1.2, the controller is not running as the identity you configured. If it is, one half is missing: the identity policy (Step 1.3) or the lister role's trust policy (Step A2)
Push registration fails with 404 or 405PushPath does not match where your endpoint serves registrationFix PushPath, with no trailing slash. A 405 returned by nginx as an HTML page means the request fell through to the dashboard's static file handler
Push registration fails with 403The push token is wrong or has been replacedRedeploy the StackSet with the current token
Push registration fails with 409The enrollment is disabled in KubeSenseEnable it and retry the failed instances
Push registration fails with 401A gateway in front of KubeSense rejected the callAllow-list the registration path at your gateway
CERTIFICATE_VERIFY_FAILED on stack instancesAWS could not build a trust chain to your endpoint's certificateRun openssl s_client -connect <host>:443 -showcerts. Private CA → paste its PEM into CaBundle. Public CA but only one certificate returned → your server is not sending its intermediate; reinstall the certificate with the full chain
A new account was not onboardedAutomatic deployment is off on the StackSet, or the account was created outside the targeted OUsTurn automatic deployment on, or move the account into a covered OU
One account is missing and every stack instance succeededIt is the management account, which service-managed StackSets skip by designDeploy to it as a plain stack (Step B3). Confirm which account it is with aws organizations describe-organization --query Organization.MasterAccountId
An account still shows after its stack was deletedDe-registration is best-effort so a KubeSense outage cannot block a stack deletePause the account from the dashboard
KubeSenseCollectorRole already exists on stack createThat 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

Best practices

  • Use a delegated administrator for the lister role rather than the management account, so organization read access lives outside your most sensitive account.
  • Set OrgId on the collector role template — the trust policy then also requires the caller to belong to your organization, so a leaked controller ARN is not enough on its own.
  • Always enable automatic deployment on the StackSet. It is what onboards accounts created later, and its absence is the commonest cause of a missing account.
  • Select regions deliberately. Every enrolled account is queried in every selected region on each cycle, so unused regions cost API calls for nothing.
  • Rotate the push token once a rollout finishes, and again before onboarding a new batch — updating the StackSet in the same change.
  • Prefer Organization Discovery unless organization listing is genuinely off the table: the preview, OU filtering and automatic pause/resume have no equivalent in the push path.