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
ACCOUNTorORGANIZATION) 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
NHIAgentstatements 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
Go to IAM → Roles → Create role.
For Trusted entity type, choose AWS account.
Under An AWS account, select Another AWS account and enter the Lumos account ID:
349123981986Check Require external ID and enter the Service Role External ID shown in the Lumos connector setup form.
Click Next.
On the Add permissions screen, search for the policy you created in Step 1, select it, and click Next.
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
Open the role you just created (click View role from the success banner, or find it under IAM → Roles).
Go to the Trust relationships tab and click Edit trust policy.
In the JSON document, set the
AWSvalue inside thePrincipalblock to the Lumos Customer Integrator Role ARN shown in the Lumos connector setup form.Click Save changes.
Step 5 — Connect in Lumos
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 |
| The account ID Lumos runs as, and the regions enabled on the account | Required — every capability derives resource ARNs from the account ID |
| IAM users, their policies, access keys, MFA devices and login profiles | IAM users as non-human accounts; unused access key and abandoned user findings |
| OIDC and SAML identity providers | Federated principals; OIDC |
| IAM roles and their inline and attached policies | Role entitlements — the permissions each workload actually holds |
| IAM groups and their policies | Group entitlements and group membership in the access graph |
| Managed policies and their versions | Resources referenced by policy Allow statements |
| Lambda functions, URL configs and resource policies | Lambda execution role attribution; publicly-invokable Function URL findings |
| ECS clusters, services, tasks and task definitions | ECS task and execution role attribution |
| EC2 instances, security groups, Auto Scaling groups and instance profiles | Which instance or ASG uses a given role |
| EKS clusters, node groups, Fargate profiles, Pod Identity associations | Pod, node and Fargate role attribution |
| Glue jobs, crawlers and job runs | Glue job role attribution and last-run times |
| Step Functions state machines | State machine role attribution |
| Firehose delivery streams | Firehose delivery role attribution |
| CodePipeline pipelines and executions | Pipeline role attribution and last-run times |
| SageMaker notebook instances, endpoints and models | SageMaker execution role attribution |
| EventBridge rules, targets and Pipes | Rule and pipe role attribution |
| MSK Connect connectors | Connector role attribution |
| CodeBuild projects | Build project role attribution |
| Batch job definitions, compute environments, queues and jobs | Batch job role attribution and last-run times |
| App Runner services | App Runner instance role attribution |
| Bedrock agents and aliases | Bedrock agent role attribution |
| IoT role aliases | IoT role alias attribution |
| IAM Roles Anywhere profiles | Roles Anywhere profile attribution |
| Secret names and metadata — never secret values | Secrets as resources in the access graph |
| Parameter metadata — never parameter values | SSM parameters as resources in the access graph |
| CloudWatch metrics for workload activity | Last-used timestamps for every workload type |
| CloudTrail management-event lookups | Last-run times for Batch jobs and standalone ECS tasks that have aged out of their own APIs |
| 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 |
| The full IAM authorization graph in one paginated call | All IAM-based findings — this is the agent's primary extraction |
| 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 |
| GuardDuty findings scoped to non-human principals | Active-compromise and lateral-movement detection |
| Bucket inventory, policies, ACLs and public-access-block settings | Public and externally-shared bucket findings |
| KMS keys, their metadata and key policies | Key-policy trust findings |
| 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.