Connecting to AWS IAM (NHI)

Last updated: September 22, 2026

What You're Setting Up

You're creating a read-only IAM role in your AWS account that Lumos assumes via AWS STS using an External-ID-gated trust policy. Lumos uses this role to inventory non-human identities in your tenant and surface non-human identity risk.

The policy is organized so that statements line up one-to-one with a setting in the integration UI: switch a workload off and you can delete its statement. The six statements prefixed NHIAgent have no setting — those are read by the NHI security agent and it produces findings without installing the data.

What Lumos does NOT do:

  • No writes — every action is read-only.

  • No data-plane access — we never read secret values, S3 objects, KMS encryption, DynamoDB rows, or log-stream contents.

  • No CloudTrail log-file access — we read management-event history through the CloudTrail API, not from your CloudTrail S3 bucket, and only to establish when a workload last ran.

  • No role assumption beyond this scoped role.

Every call Lumos makes lands in your CloudTrail so you can audit exactly what ran.


Prerequisites

Before you start, make sure you have:

  • AWS IAM admin permissions in the account you want to connect (or a role that can create IAM roles and policies).

  • The Service Role External ID shown on your Lumos integration page (used in Step 2). Do not generate your own — Lumos issues a per-tenant value.

  • For full finding coverage, enable these in every region you want scanned:

    • IAM Access Analyzer with both analyzer types configured: an External Access analyzer (Type ACCOUNT or ORGANIZATION) powers externally-shared-resource and public-resource findings; an Unused Access analyzer powers stale-access-key and abandoned-user findings.

    • Amazon GuardDuty for active-compromise and lateral-movement detection.

      Neither is required to connect or to sync. Leaving them off costs you the findings listed under the NHIAgent statements in the policy and nothing else.

  • If you're connecting multiple AWS accounts: each needs its own role and connection. See the "Connecting many accounts at once" section for StackSet instructions. Include your Organization management or delegated-admin account so Lumos can tell org-sibling trust relationships apart from external ones. One connection covers every region in an account, so you don't need a separate connection per region.


Step 1 — Create the IAM policy

In the AWS console: IAM → Policies → Create policy → JSON. Paste the policy below and name it LumosNHIReadOnly-policy (any name works).

The policy is split into one statement per integration setting so you can remove the services you don't want scanned. If you remove a service, make sure the relevant setting is toggled off in the integration UI as well or the sync will fail with an AccessDenied error. See the "Tailoring the policy" section below for what each statement covers and what you give up by dropping it.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AlwaysRequired",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "sts:GetCallerIdentity",
        "account:ListRegions"
      ]
    },
    {
      "Sid": "FetchIamUsers",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "iam:ListUsers",
        "iam:GetUser",
        "iam:ListUserPolicies",
        "iam:GetUserPolicy",
        "iam:ListAttachedUserPolicies",
        "iam:ListGroupsForUser",
        "iam:ListAccessKeys",
        "iam:GetAccessKeyLastUsed",
        "iam:GetLoginProfile",
        "iam:ListMFADevices",
        "iam:GetPolicy",
        "iam:GetPolicyVersion"
      ]
    },
    {
      "Sid": "FetchFederatedPrincipals",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "iam:ListOpenIDConnectProviders",
        "iam:GetOpenIDConnectProvider",
        "iam:ListSAMLProviders",
        "iam:GetSAMLProvider",
        "iam:ListRoles"
      ]
    },
    {
      "Sid": "FetchIamRoleEntitlements",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "iam:ListRoles",
        "iam:GetRole",
        "iam:ListRolePolicies",
        "iam:GetRolePolicy",
        "iam:ListAttachedRolePolicies",
        "iam:GetPolicy",
        "iam:GetPolicyVersion"
      ]
    },
    {
      "Sid": "FetchIamGroupEntitlements",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "iam:ListGroups",
        "iam:GetGroup",
        "iam:ListGroupPolicies",
        "iam:GetGroupPolicy",
        "iam:ListAttachedGroupPolicies",
        "iam:GetPolicy",
        "iam:GetPolicyVersion"
      ]
    },
    {
      "Sid": "FetchIamPolicyTerminalResources",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "iam:ListPolicies",
        "iam:GetPolicy",
        "iam:ListPolicyVersions",
        "iam:GetPolicyVersion"
      ]
    },
    {
      "Sid": "FetchLambdaFunctions",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "lambda:ListFunctions",
        "lambda:GetFunctionConfiguration",
        "lambda:ListFunctionUrlConfigs",
        "lambda:GetPolicy"
      ]
    },
    {
      "Sid": "FetchEcsTasksAndServices",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "ecs:List*",
        "ecs:Describe*"
      ]
    },
    {
      "Sid": "FetchEc2InstanceProfiles",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeSecurityGroups",
        "autoscaling:Describe*",
        "iam:ListInstanceProfiles"
      ]
    },
    {
      "Sid": "FetchEksWorkloads",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "eks:List*",
        "eks:Describe*",
        "iam:ListRoles"
      ]
    },
    {
      "Sid": "FetchGlueJobs",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "glue:GetJobs",
        "glue:GetCrawlers",
        "glue:GetJobRuns"
      ]
    },
    {
      "Sid": "FetchStepFunctions",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "states:List*",
        "states:Describe*"
      ]
    },
    {
      "Sid": "FetchKinesisFirehoseStreams",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "firehose:List*",
        "firehose:Describe*"
      ]
    },
    {
      "Sid": "FetchCodePipelinePipelines",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "codepipeline:List*",
        "codepipeline:Get*"
      ]
    },
    {
      "Sid": "FetchSageMakerJobs",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "sagemaker:List*",
        "sagemaker:Describe*"
      ]
    },
    {
      "Sid": "FetchEventBridgeRulesAndPipes",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "events:ListRules",
        "events:ListTargetsByRule",
        "pipes:ListPipes",
        "pipes:DescribePipe"
      ]
    },
    {
      "Sid": "FetchMskConnectConnectors",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "kafkaconnect:ListConnectors"
      ]
    },
    {
      "Sid": "FetchCodeBuildProjects",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "codebuild:List*",
        "codebuild:BatchGet*"
      ]
    },
    {
      "Sid": "FetchBatchJobDefinitions",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "batch:Describe*",
        "batch:List*"
      ]
    },
    {
      "Sid": "FetchAppRunnerServices",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "apprunner:List*",
        "apprunner:Describe*"
      ]
    },
    {
      "Sid": "FetchBedrockAgents",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "bedrock:List*",
        "bedrock:Get*"
      ]
    },
    {
      "Sid": "FetchIotRoleAliases",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "iot:List*",
        "iot:Describe*"
      ]
    },
    {
      "Sid": "FetchIamRolesAnywhereProfiles",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "rolesanywhere:ListProfiles"
      ]
    },
    {
      "Sid": "FetchSecretsManagerSecrets",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "secretsmanager:ListSecrets"
      ]
    },
    {
      "Sid": "FetchSsmSecureStringParameters",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "ssm:DescribeParameters"
      ]
    },
    {
      "Sid": "FetchNhaLastActivity",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "cloudwatch:GetMetricData",
        "cloudwatch:ListMetrics"
      ]
    },
    {
      "Sid": "FetchLastActivityViaCloudTrail",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "cloudtrail:LookupEvents"
      ]
    },
    {
      "Sid": "NHIAgentIamAuthorizationGraph",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "iam:GetAccountAuthorizationDetails"
      ]
    },
    {
      "Sid": "NHIAgentAccessAnalyzerFindings",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "access-analyzer:ListAnalyzers",
        "access-analyzer:ListFindings",
        "access-analyzer:GetFinding"
      ]
    },
    {
      "Sid": "NHIAgentGuardDutyFindings",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "guardduty:ListDetectors",
        "guardduty:ListFindings",
        "guardduty:GetFindings"
      ]
    },
    {
      "Sid": "NHIAgentBucketExposure",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "s3:ListAllMyBuckets",
        "s3:GetBucketLocation",
        "s3:GetBucketPolicy",
        "s3:GetBucketPolicyStatus",
        "s3:GetBucketAcl",
        "s3:GetBucketPublicAccessBlock",
        "s3:GetAccountPublicAccessBlock"
      ]
    },
    {
      "Sid": "NHIAgentKeyPolicyExposure",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "kms:ListKeys",
        "kms:DescribeKey",
        "kms:GetKeyPolicy"
      ]
    },
    {
      "Sid": "NHIAgentIdentityCenter",
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "sso:List*",
        "sso:Describe*",
        "sso:Get*",
        "identitystore:List*",
        "identitystore:Describe*",
        "organizations:ListAccounts",
        "organizations:DescribeOrganization"
      ]
    },
    {
      "Sid": "DenyDataPlaneReads",
      "Effect": "Deny",
      "Resource": "*",
      "Action": [
        "s3:GetObject",
        "s3:GetObjectVersion",
        "s3:GetObjectAttributes",
        "s3:ListBucket",
        "s3:ListBucketVersions",
        "secretsmanager:GetSecretValue",
        "ssm:GetParameter",
        "ssm:GetParameters",
        "ssm:GetParametersByPath",
        "kms:Decrypt",
        "dynamodb:GetItem",
        "dynamodb:Query",
        "dynamodb:Scan",
        "logs:GetLogEvents",
        "logs:FilterLogEvents"
      ]
    }
  ]
}

ℹ A note on repeated actions: a few IAM actions appear in more than one statement — iam:ListRoles in three, iam:GetPolicy and iam:GetPolicyVersion in four. That is deliberate. Those calls are shared by several settings: resolving EKS IRSA bindings and federated trust policies both walk the full role list, and attached managed policies are resolved for users, roles and groups alike. Repeating them means each statement is independently deletable. IAM combines the grants automatically, so there is no need to consolidate.


Step 2 — Create the IAM role

  1. Go to IAM → Roles → Create role.

  2. For Trusted entity type, choose AWS account.

  3. Under An AWS account, select Another AWS account and enter the Lumos account ID: 349123981986

  4. Check Require external ID and enter the Service Role External ID shown in the Lumos connector setup form.

  5. Click Next.

  6. On the Add permissions screen, search for the policy you created in Step 1, select it, and click Next.

  7. Give the role a name (e.g. LumosNHIReadOnly) and click Create role.


Step 3 — Note the role ARN

After the role is created, copy its ARN (format: arn:aws:iam::<your-account-id>:role/LumosNHIReadOnly). You'll paste it into Lumos in the next step.


Step 4 — Update the trust policy

  1. Open the role you just created (click View role from the success banner, or find it under IAM → Roles).

  2. Go to the Trust relationships tab and click Edit trust policy.

  3. In the JSON document, set the AWS value inside the Principal block to the Lumos Customer Integrator Role ARN shown in the Lumos connector setup form.

  4. Click Save changes.


Step 5 — Connect in Lumos

  1. In the Lumos app, go to Integrations → Add Integration and search for "AWS IAM Identity Center (NHI)".

Fill in the connector settings in Lumos with the values you collected:

  • Regions - one or more AWS region codes. Leave blank to scan every region in the account.

  • IAM Identity Center ARN - (Optional) Instance ARN from IAM Identity Center Settings

  • Service Role ARN - ARN of the IAM role created in Step 3

  • SCIM Endpoint or Tenant ID - (Optional) SCIM endpoint URL or tenant ID


Connecting many accounts at once

The steps above connect one AWS account. To onboard an entire AWS Organization, Lumos publishes a CloudFormation StackSet template and an onboarding script that deploy the role across every account you select and connect them in a single run.

Follow AWS_SETUP.md in our connector scripts repo.


Scan behavior

  • Cadence: Follow the NHI security agent configuration. We don’t pull AWS IAM data on a regular basis by default unless the agent is configured to run on a schedule.

  • Session lifetime: each scan opens a short-lived STS session (5-10 min). Lumos never holds long-lived credentials.

  • Regions: scanning more regions takes longer, but not proportionally. Lumos queries regions in parallel, and the largest data sets — IAM users, roles, groups and policies — are global and fetched once regardless of how many regions you scan.


Tailoring the policy

Statement names match the settings in the integration UI. Every statement aside from AlwaysRequired can be deleted before you create the policy — Lumos skips that service and the rest of the scan is unaffected. For any services removed from the policy, make sure that the corresponding setting in the Integration UI is toggled off. Statements prefixed with NHIAgent  have no integration setting at all as they power agent findings rather than data syncs.

Statement

Covers

Impact if removed

AlwaysRequired

The account ID Lumos runs as, and the regions enabled on the account

Required — every capability derives resource ARNs from the account ID

FetchIamUsers

IAM users, their policies, access keys, MFA devices and login profiles

IAM users as non-human accounts; unused access key and abandoned user findings

FetchFederatedPrincipals

OIDC and SAML identity providers

Federated principals; OIDC sub/aud and SAML certificate findings

FetchIamRoleEntitlements

IAM roles and their inline and attached policies

Role entitlements — the permissions each workload actually holds

FetchIamGroupEntitlements

IAM groups and their policies

Group entitlements and group membership in the access graph

FetchIamPolicyTerminalResources

Managed policies and their versions

Resources referenced by policy Allow statements

FetchLambdaFunctions

Lambda functions, URL configs and resource policies

Lambda execution role attribution; publicly-invokable Function URL findings

FetchEcsTasksAndServices

ECS clusters, services, tasks and task definitions

ECS task and execution role attribution

FetchEc2InstanceProfiles

EC2 instances, security groups, Auto Scaling groups and instance profiles

Which instance or ASG uses a given role

FetchEksWorkloads

EKS clusters, node groups, Fargate profiles, Pod Identity associations

Pod, node and Fargate role attribution

FetchGlueJobs

Glue jobs, crawlers and job runs

Glue job role attribution and last-run times

FetchStepFunctions

Step Functions state machines

State machine role attribution

FetchKinesisFirehoseStreams

Firehose delivery streams

Firehose delivery role attribution

FetchCodePipelinePipelines

CodePipeline pipelines and executions

Pipeline role attribution and last-run times

FetchSageMakerJobs

SageMaker notebook instances, endpoints and models

SageMaker execution role attribution

FetchEventBridgeRulesAndPipes

EventBridge rules, targets and Pipes

Rule and pipe role attribution

FetchMskConnectConnectors

MSK Connect connectors

Connector role attribution

FetchCodeBuildProjects

CodeBuild projects

Build project role attribution

FetchBatchJobDefinitions

Batch job definitions, compute environments, queues and jobs

Batch job role attribution and last-run times

FetchAppRunnerServices

App Runner services

App Runner instance role attribution

FetchBedrockAgents

Bedrock agents and aliases

Bedrock agent role attribution

FetchIotRoleAliases

IoT role aliases

IoT role alias attribution

FetchIamRolesAnywhereProfiles

IAM Roles Anywhere profiles

Roles Anywhere profile attribution

FetchSecretsManagerSecrets

Secret names and metadata — never secret values

Secrets as resources in the access graph

FetchSsmSecureStringParameters

Parameter metadata — never parameter values

SSM parameters as resources in the access graph

FetchNhaLastActivity

CloudWatch metrics for workload activity

Last-used timestamps for every workload type

FetchLastActivityViaCloudTrail

CloudTrail management-event lookups

Last-run times for Batch jobs and standalone ECS tasks that have aged out of their own APIs

DenyDataPlaneReads

Explicit deny on object, secret, parameter, row and log-content reads

Nothing — it grants no access. Keep it if your security review wants the read-only guarantee written into the policy

NHIAgentIamAuthorizationGraph

The full IAM authorization graph in one paginated call

All IAM-based findings — this is the agent's primary extraction

NHIAgentAccessAnalyzerFindings

IAM Access Analyzer External Access and Unused Access findings

Externally-shared S3, KMS, Secrets Manager and Lambda resources; long-unused access keys; abandoned IAM users

NHIAgentGuardDutyFindings

GuardDuty findings scoped to non-human principals

Active-compromise and lateral-movement detection

NHIAgentBucketExposure

Bucket inventory, policies, ACLs and public-access-block settings

Public and externally-shared bucket findings

NHIAgentKeyPolicyExposure

KMS keys, their metadata and key policies

Key-policy trust findings

NHIAgentIdentityCenter

IAM Identity Center permission sets, assignments, applications, Organizations

Workforce access mapping; org-sibling vs external trust classification


Security FAQ

Does Lumos access data inside my AWS account (S3 objects, secret values, log contents)?

No. The S3 permissions grant bucket-level metadata only — inventory, bucket policies, ACLs and public-access-block settings — never object contents.  DenyDataPlaneReads then explicitly blocks  s3:GetObject, s3:ListBucket, secretsmanager:GetSecretValue, SSM parameter reads, kms:Decrypt, DynamoDB item reads and CloudWatch Logs event reads. An explicit Deny in IAM overrides every Allow, so those calls fail for this role regardless of what else the policy grants.

Every call Lumos makes lands in your CloudTrail so you can audit exactly what ran.

Does Lumos read CloudTrail events?

Yes, in a narrow way. Lumos calls cloudtrail:LookupEvents to find when a workload last ran where the service's own API can't say — for example AWS Batch, whose job list only retains finished jobs for about seven days, so older runs are recovered from SubmitJob events. Lumos reads this through the CloudTrail API rather than from your log bucket. It only runs when the CloudTrail activity settings are on.

Why is iam:GetAccountAuthorizationDetails in the policy?

It's AWS's dedicated read-only API for retrieving the IAM authorization graph — users, roles, groups, their inline + managed policy documents, trust policies, and RoleLastUsed. Using it in one call (rather than ~20 per-entity reads) keeps your CloudTrail cleaner and makes scans predictable. It's IAM-metadata-only; there is no data-plane exposure.

How are credentials handled?

Lumos's service account in AWS account 349123981986 assumes your role via sts:AssumeRole with the External ID condition.

Can I scope the role down further?

Yes. The policy is split by integration setting so you can delete whole statements before creating it — see the "Tailoring the policy" section for what each statement covers and what dropping it costs you. Only AlwaysRequired  must stay. Your Lumos account team can walk through the trade-offs.

Can I remove the role later?

Yes. Delete the role in IAM; Lumos's next scan will fail with AccessDenied and we'll surface a "connection lost" banner in the integration UI. No data exfiltration — Lumos cannot assume the role once it's deleted.


Questions? Contact your Lumos account team or support@lumos.com.