IAM permissions
The agent has two distinct AWS identities depending on the deployment path:
- The task role (Fargate, ECS task JSON), instance profile (EC2), or assumed role (Local) is the identity the agent process runs under at scan time. It needs the discovery policy.
- The execution role is used only on Fargate and ECS task JSON paths. It is what ECS itself uses at task launch to pull the image, fetch the enrollment token from Secrets Manager, and write to CloudWatch Logs. The agent process never assumes it. It needs the execution-role policy.
Cloud (managed) Fargate and EC2 templates create both roles for you. Cloud (DIY) Terraform creates them via the deploy-argus module. Cloud (DIY) ECS task JSON and Local paths require you to create the roles by hand using the JSON below.
Discovery policy (task role / instance profile)
Section titled “Discovery policy (task role / instance profile)”The policy the agent process uses at scan time. Every statement below is read-only: nothing in it can modify anything in your account. Remediation is a separate, optional policy, covered in Remediation policy.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ArgusS3BucketPosture", "Effect": "Allow", "Action": [ "s3:GetBucketAcl", "s3:GetBucketLocation", "s3:GetBucketLogging", "s3:GetBucketPolicy", "s3:GetBucketPolicyStatus", "s3:GetBucketPublicAccessBlock", "s3:GetBucketTagging", "s3:GetBucketVersioning", "s3:GetEncryptionConfiguration", "s3:ListAllMyBuckets", "s3:ListBucket" ], "Resource": "arn:aws:s3:::*" }, { "Sid": "ArgusS3ObjectSampling", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:GetObjectTagging", "s3:GetObjectVersion" ], "Resource": "arn:aws:s3:::*/*" }, { "Sid": "ArgusDatabasePosture", "Effect": "Allow", "Action": [ "dynamodb:DescribeTable", "dynamodb:GetResourcePolicy", "dynamodb:ListTables", "dynamodb:ListTagsOfResource", "dynamodb:Scan", "rds:DescribeDBClusters", "rds:DescribeDBInstances", "rds:ListTagsForResource", "redshift-serverless:ListNamespaces", "redshift-serverless:ListWorkgroups", "redshift:DescribeClusters", "redshift:GetClusterCredentials" ], "Resource": "*" }, { "Sid": "ArgusIdentityRead", "Effect": "Allow", "Action": [ "iam:GenerateCredentialReport", "iam:GenerateServiceLastAccessedDetails", "iam:GetAccessKeyLastUsed", "iam:GetCredentialReport", "iam:GetGroupPolicy", "iam:GetPolicy", "iam:GetPolicyVersion", "iam:GetRole", "iam:GetRolePolicy", "iam:GetServiceLastAccessedDetails", "iam:GetUser", "iam:GetUserPolicy", "iam:ListAccessKeys", "iam:ListAttachedGroupPolicies", "iam:ListAttachedRolePolicies", "iam:ListAttachedUserPolicies", "iam:ListGroupPolicies", "iam:ListGroups", "iam:ListGroupsForUser", "iam:ListMFADevices", "iam:ListRolePolicies", "iam:ListRoles", "iam:ListUserPolicies", "iam:ListUserTags", "iam:ListUsers" ], "Resource": "*" }, { "Sid": "ArgusAccountContext", "Effect": "Allow", "Action": [ "cloudwatch:GetMetricStatistics", "ec2:DescribeRegions", "iam:SimulatePrincipalPolicy", "kms:DescribeKey", "sts:GetCallerIdentity" ], "Resource": "*" }, { "Sid": "ArgusKmsDecrypt", "Effect": "Allow", "Action": [ "kms:Decrypt" ], "Resource": "*", "Condition": { "StringLike": { "kms:ViaService": [ "s3.*.amazonaws.com", "dynamodb.*.amazonaws.com" ] } } } ]}Machine-readable version (for programmatic diffing / policy review): verification-permissions.json.
Why the full set matters
Section titled “Why the full set matters”Argus reports a control as Not Verified only when it genuinely could not read the evidence. A missing permission is our problem to prevent, not a state you should have to diagnose: if the agent cannot read a signal because a grant is absent, the control reads “not verified” and your compliance score is understated for no real reason. The set above is generated from the same registry the agent uses, so it covers every signal Argus checks.
The ArgusIdentityRead statement powers identities, the access graph, blast radius,
credential hygiene and IAM recommendations. Without it those pages are empty.
iam:SimulatePrincipalPolicy is how the agent reports whether it is able to remediate. It
is read-only, and it is granted here rather than with the remediation policy on purpose: a
read-only deployment still needs to be able to tell you that it is read-only.
Resource: "*" is the recommended starting point for first-deploy validation. For least-privilege at scale, narrow the Resource field on per-service Sids (for example, restrict s3:GetObject and s3:ListBucket to specific bucket ARNs).
The sts:GetCallerIdentity call is what the agent uses to assert it is running inside the AWS account you registered (the “cross account guard”). Without it, the agent refuses to persist scan results.
What breaks if you omit a permission
Section titled “What breaks if you omit a permission”If your security review asks what a specific grant buys, this is the answer, signal by signal. Every row is generated from the same registry the agent reads at scan time, so it cannot drift from what the code actually does.
Nothing here fails loudly. A denied read does not stop a scan: the affected control reads Not Verified, or the finding is simply absent. That is the reason to grant the full set rather than trim it and discover the gap during an audit.
| What it reads | Permissions | Without it |
|---|---|---|
| Object content sampling | s3:GetObject, s3:GetObjectVersion, s3:GetObjectTagging, kms:Decrypt |
Contents are not sampled. The store reports no findings, which reads as clean rather than unread. |
| Encryption posture | s3:GetEncryptionConfiguration |
Encryption controls read Not Verified and risk assumes unencrypted. |
| Bucket inventory | s3:ListAllMyBuckets, s3:ListBucket, s3:GetBucketLocation |
The resource is never discovered, so it is absent from every count, score and report. |
| Access-logging posture | s3:GetBucketLogging |
Access-logging controls read Not Verified. |
| Public-access exposure | s3:GetBucketPolicyStatus, s3:GetBucketAcl, s3:GetBucketPublicAccessBlock, s3:GetBucketPolicy |
Exposure cannot be confirmed. Risk assumes the worst case and flags the score unverified. |
| Bucket tags | s3:GetBucketTagging |
Tags are unavailable, so tag-based policy scope and filtering silently match nothing. |
| Versioning / recovery posture | s3:GetBucketVersioning |
Recovery-posture controls read Not Verified. |
| What it reads | Permissions | Without it |
|---|---|---|
| Backup / retention posture | rds:DescribeDBInstances, rds:DescribeDBClusters |
Backup and retention controls read Not Verified. |
| Database content sampling | Contents are not sampled. The store reports no findings, which reads as clean rather than unread. | |
| Encryption posture | rds:DescribeDBInstances, rds:DescribeDBClusters |
Encryption controls read Not Verified and risk assumes unencrypted. |
| Database inventory | rds:DescribeDBInstances, rds:DescribeDBClusters |
The resource is never discovered, so it is absent from every count, score and report. |
| Public-access exposure | rds:DescribeDBInstances, rds:DescribeDBClusters |
Exposure cannot be confirmed. Risk assumes the worst case and flags the score unverified. |
| Database tags | rds:ListTagsForResource |
Tags are unavailable, so tag-based policy scope and filtering silently match nothing. |
Redshift
Section titled “Redshift”| What it reads | Permissions | Without it |
|---|---|---|
| Cluster content sampling | redshift:GetClusterCredentials |
Contents are not sampled. The store reports no findings, which reads as clean rather than unread. |
| Encryption posture | redshift:DescribeClusters, redshift-serverless:ListWorkgroups |
Encryption controls read Not Verified and risk assumes unencrypted. |
| Cluster inventory | redshift:DescribeClusters, redshift-serverless:ListWorkgroups, redshift-serverless:ListNamespaces |
The resource is never discovered, so it is absent from every count, score and report. |
| Public-access exposure | redshift:DescribeClusters, redshift-serverless:ListWorkgroups |
Exposure cannot be confirmed. Risk assumes the worst case and flags the score unverified. |
DynamoDB
Section titled “DynamoDB”| What it reads | Permissions | Without it |
|---|---|---|
| Table content sampling | dynamodb:Scan, kms:Decrypt |
Contents are not sampled. The store reports no findings, which reads as clean rather than unread. |
| Encryption posture | dynamodb:DescribeTable |
Encryption controls read Not Verified and risk assumes unencrypted. |
| Table inventory | dynamodb:ListTables, dynamodb:DescribeTable |
The resource is never discovered, so it is absent from every count, score and report. |
| Resource-policy exposure | dynamodb:GetResourcePolicy |
Exposure cannot be confirmed. Risk assumes the worst case and flags the score unverified. |
| Table tags | dynamodb:ListTagsOfResource |
Tags are unavailable, so tag-based policy scope and filtering silently match nothing. |
Identity (IAM)
Section titled “Identity (IAM)”| What it reads | Permissions | Without it |
|---|---|---|
| Permissions boundaries (caps on effective access) | iam:GetUser, iam:GetRole |
Permissions boundaries are invisible, so a principal already capped by one is still reported as an unrestricted admin. |
| Credential hygiene | iam:ListAccessKeys, iam:GetAccessKeyLastUsed, iam:ListMFADevices, iam:GetCredentialReport, iam:GenerateCredentialReport |
Credential hygiene (MFA, key age, stale passwords) cannot be assessed. |
| Identity inventory | iam:ListUsers, iam:ListRoles, iam:ListGroups |
The resource is never discovered, so it is absent from every count, score and report. |
| Group membership | iam:ListGroupsForUser |
Group-derived permissions are invisible to the access graph, understating blast radius. |
| Attached permissions | iam:ListAttachedUserPolicies, iam:ListAttachedRolePolicies, iam:ListAttachedGroupPolicies, iam:ListUserPolicies, iam:ListRolePolicies, iam:ListGroupPolicies, iam:GetUserPolicy, iam:GetRolePolicy, iam:GetGroupPolicy, iam:GetPolicy, iam:GetPolicyVersion |
Attached permissions cannot be read, so over-privilege detection misses custom policies. |
| Identity tags | iam:ListUserTags |
Tags are unavailable, so tag-based policy scope and filtering silently match nothing. |
| Permission usage history | iam:GetServiceLastAccessedDetails, iam:GenerateServiceLastAccessedDetails |
Granted-versus-used comparison is unavailable, so over-provisioning cannot be evidenced. |
Account context
Section titled “Account context”| What it reads | Permissions | Without it |
|---|---|---|
| Remediation capability check | iam:SimulatePrincipalPolicy |
Remediation cannot pre-check whether it is permitted before attempting a fix. |
| Account identity | sts:GetCallerIdentity |
The agent cannot prove it is running in the registered account and refuses to persist results. |
| Encryption key metadata | kms:DescribeKey |
A customer-managed key cannot be distinguished from an AWS-managed one. |
| Utilization metrics | cloudwatch:GetMetricStatistics |
Bucket size and object counts fall back to a full listing, which is far slower on large buckets. |
| Region enumeration | ec2:DescribeRegions |
Only the agent’s own region is scanned. |
Remediation policy (optional)
Section titled “Remediation policy (optional)”Everything Argus needs to fix what it finds, and nothing else. It is a separate policy, not a statement inside the discovery policy above, because it is separately grantable and separately revocable.
You do not need it to use Argus. Without this policy the agent discovers, classifies and scores exactly as it does with it; the remediation engine simply reports that it cannot execute, and the Remediation page tells you so up front rather than failing at the moment you approve a fix.
On the managed CloudFormation templates you do not attach this by hand: set the
EnableRemediation stack parameter to true and CloudFormation creates and attaches it
for you. The parameter defaults to false, so a default managed deployment cannot modify
anything in your account. Attaching or detaching it is a stack update, not a redeploy: the
running agent is not replaced and does not need re-enrolling.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ArgusRemediationWrite", "Effect": "Allow", "Action": [ "iam:AttachRolePolicy", "iam:AttachUserPolicy", "iam:DeleteUserPolicy", "iam:DetachRolePolicy", "iam:DetachUserPolicy", "iam:PutUserPolicy", "iam:UpdateAccessKey", "s3:DeleteBucketPolicy", "s3:PutBucketPolicy", "s3:PutBucketPublicAccessBlock", "s3:PutBucketVersioning", "s3:PutEncryptionConfiguration" ], "Resource": "*" } ]}Every action here is one the remediation engine actually calls, including the ones it needs to roll a change back. Granting a subset produces a worse outcome than granting none: a workflow can apply its first step, fail authorization on the second, and then fail to undo the first.
Enabling it after you have already deployed
Section titled “Enabling it after you have already deployed”You do not need to redeploy, and you will not lose the agent’s identity.
Cloud (managed), Fargate or EC2:
- Open the CloudFormation stack in the AWS console and choose Update.
- Keep Use existing template, then set
EnableRemediationtotrue. Keeping it matters: it reuses the template CloudFormation stored with your stack. Switching to the current template URL would move an existing stack onto the v2 IAM naming, which replaces the agent’s role and breaks its credentials. - Review the change set. It creates exactly one resource,
ArgusRemediationWrite-<stack-name>, and modifies nothing else. - Apply.
The agent keeps running throughout: no task is replaced, no instance is restarted, and the enrollment token is untouched. Task and instance credentials re-evaluate policy at call time, so the new permission takes effect on the agent’s next remediation attempt without any action on your side.
Then tell Argus to look again. Because the agent is never restarted by this change, nothing on our side observes it automatically: press Re-check on the account row in Settings > Remediation and it flips to Remediation available. Restarting the agent has the same effect, since Argus verifies permissions whenever an agent starts up.
Cloud (DIY): attach the policy above to the task role or instance profile the agent runs as, then re-check the account.
Deploying a new agent needs none of this: Argus verifies what it is permitted to do as
soon as the agent enrols, so an account deployed with EnableRemediation=true reports
itself available without anyone pressing anything.
Revoking it
Section titled “Revoking it”Same routes, in reverse: set EnableRemediation back to false and update the stack, or
detach the policy. CloudFormation deletes the managed policy, and the agent loses write
access immediately - there is no cached grant and no restart needed.
What happens to work already in flight:
- Queued approvals stop at the readiness gate. They are refused before anything is applied, with a message naming the read-only deployment, rather than failing halfway through.
- Rollback is deliberately not blocked. Revoking must never trap you mid-change with no way back, so if a rollback is still available for a completed run, it can still be used.
- Discovery, classification and compliance scoring are unaffected.
If you want to stop remediation immediately without touching your cloud, use the tenant-wide pause in Settings > Remediation instead. It is one click, instantly reversible, and changes nothing in your AWS account - but it is enforced by Argus rather than by IAM, so it is the weaker of the two. Revoking the IAM grant is what survives Argus itself being compromised.
Execution-role policy (Fargate / ECS task JSON only)
Section titled “Execution-role policy (Fargate / ECS task JSON only)”Attached in addition to the AWS-managed AmazonECSTaskExecutionRolePolicy (which lets ECS pull the image and emit log events).
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ArgusReadEnrollmentTokenSecret", "Effect": "Allow", "Action": "secretsmanager:GetSecretValue", "Resource": "<TOKEN_SECRET_ARN>" }, { "Sid": "ArgusCloudWatchLogs", "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "<LOG_GROUP_ARN_PATTERN>" } ]}Substitute:
<TOKEN_SECRET_ARN>with the ARN returned byaws secretsmanager create-secret(looks likearn:aws:secretsmanager:us-east-2:111122223333:secret:argus-agent/enrollment-token-xxxxxx).<LOG_GROUP_ARN_PATTERN>with the ARN of the log group your task definition’sawslogs-groupreferences. Use a literal ARN (arn:aws:logs:us-east-2:111122223333:log-group:/ecs/argus-agent:*) or a wildcard prefix (arn:aws:logs:us-east-2:111122223333:log-group:/ecs/*).
Trust policies
Section titled “Trust policies”Both roles need a trust policy declaring which AWS principal can assume them.
ECS task role and execution role (Fargate, ECS task JSON):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" } ]}EC2 instance profile role (Cloud managed EC2):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ]}Local-path assumed role (agent on a laptop / on-prem VM assuming a role inside the target account):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::<source-account-id>:user/<source-iam-user>" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "<your-external-id>" } } } ]}Generate a random external ID per role you create. The agent passes whatever you set as AWS_EXTERNAL_ID in the AssumeRole call, and this Condition ensures only callers with the matching value can assume the role.
Using the Cloud (DIY) IAM policy tab
Section titled “Using the Cloud (DIY) IAM policy tab”The dashboard’s Cloud (DIY) > IAM policy tab is reference content, not a deploy. It emits the same two policy documents shown above. Use it when your platform team needs the JSON for security review, when you are writing your own IaC (CDK, Pulumi, CloudFormation), or when you are auditing what Argus needs in your account.