Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ When access-key activity is available, an IAM user's Last Login is the most rece

Omitting `UserTrait.LastLogin` keeps the connector from publishing an incomplete timestamp as authoritative, but it does not clear a Last Login that C1 already stored. C1's current ingestion skips users whose incoming `UserTrait.LastLogin` is nil and only advances a stored timestamp when the incoming value is newer, so a user synced first with readable access-key activity and then with the lookup denied keeps the previously ingested value in C1. Use `access_key_activity_status` to tell whether the sync that produced a profile could read current access-key activity; clearing or propagating an unavailable Last Login requires platform-side support.

Set `--sync-resource-tags` to publish AWS resource tags on accounts, IAM users, and IAM roles as an `aws_tags` profile field (a nested map of tag key to tag value). None of the `List*` APIs return tags — `organizations.Account` has no `Tags` field at all, and `iam:ListUsers` / `iam:ListRoles` always return an empty `Tags` slice — so each resource costs at least one extra call (`organizations:ListTagsForResource`, `iam:ListUserTags`, `iam:ListRoleTags`). The documented 50-tag quota counts only user-created tags; AWS-reserved `aws:`-prefixed system tags are additional, so the connector reads tags across pages rather than assuming one response covers them. The flag is off by default because `organizations:ListTagsForResource` is throttled at 10 requests/second (burst 15) per account, so a 1,000-account organization spends roughly 100 seconds on tag reads alone. A missing tag permission fails the sync with a `PermissionDenied` naming the action to grant, rather than quietly syncing untagged resources — enabling the flag is an explicit request for tags, and C1 policy rules that read them would otherwise evaluate against tags that silently are not there.

Identity Center user Last Login uses a separate CloudTrail event feed. Enable Organizations support, Identity Center support, and `--sync-sso-user-last-login`, and grant `cloudtrail:LookupEvents` to report those sign-ins.

`baton-aws` also supports account provisioning and deprovisioning for AWS IAM Identity Center (SSO) users via the Identity Store API. See the "Syncing and Provisioning all supported objects" IAM policy below for the required permissions.
Expand Down Expand Up @@ -159,6 +161,7 @@ Flags:
--storage-engine string The storage engine to use when opening the sync c1z file: sqlite or pebble. Defaults to pebble when unset. ($BATON_STORAGE_ENGINE)
--sync-iam-user-console-access Enable fetching IAM user console login profiles via iam:GetLoginProfile (one API call per user). Disabled by default. ($BATON_SYNC_IAM_USER_CONSOLE_ACCESS)
--sync-only-attached-policies Only sync IAM managed policies that are attached to at least one user, role, or group ($BATON_SYNC_ONLY_ATTACHED_POLICIES)
--sync-resource-tags Sync AWS resource tags onto accounts, IAM users, and IAM roles as the aws_tags profile field. Tags are not returned by the List APIs, so this costs at least one extra API call per resource. ($BATON_SYNC_RESOURCE_TAGS)
--sync-resource-types strings The resource type IDs to sync ($BATON_SYNC_RESOURCE_TYPES)
--sync-resources strings The resource IDs to sync ($BATON_SYNC_RESOURCES)
--sync-secrets Whether to sync secrets or not ($BATON_SYNC_SECRETS)
Expand Down Expand Up @@ -198,6 +201,9 @@ _These policies have comments prefixed with // that need to be removed before us
"iam:GetAccessKeyLastUsed",
// Optional: only used with --sync-iam-user-console-access.
"iam:GetLoginProfile",
// Optional: only used with --sync-resource-tags.
"iam:ListUserTags",
"iam:ListRoleTags",
"iam:ListSigningCertificates",
"iam:ListSSHPublicKeys",
"iam:ListServiceSpecificCredentials",
Expand Down Expand Up @@ -239,6 +245,8 @@ _These policies have comments prefixed with // that need to be removed before us
"organizations:ListParents",
"organizations:ListRoots",
"organizations:ListOrganizationalUnitsForParent",
// Optional: only used with --sync-resource-tags.
"organizations:ListTagsForResource",
"sso:ListInstances",
"sso:ListPermissionSets",
"sso:DescribePermissionSet",
Expand Down Expand Up @@ -306,6 +314,9 @@ _These policies have comments prefixed with // that need to be removed before us
"iam:GetAccessKeyLastUsed",
// Optional: only used with --sync-iam-user-console-access.
"iam:GetLoginProfile",
// Optional: only used with --sync-resource-tags.
"iam:ListUserTags",
"iam:ListRoleTags",
"iam:ListSigningCertificates",
"iam:ListSSHPublicKeys",
"iam:ListServiceSpecificCredentials",
Expand Down Expand Up @@ -347,6 +358,8 @@ _These policies have comments prefixed with // that need to be removed before us
"organizations:ListParents",
"organizations:ListRoots",
"organizations:ListOrganizationalUnitsForParent",
// Optional: only used with --sync-resource-tags.
"organizations:ListTagsForResource",
"sso:ListInstances",
"sso:ListPermissionSets",
"sso:DescribePermissionSet",
Expand Down Expand Up @@ -547,6 +560,9 @@ Each sub-account will need to have the following policy attached to the role tha
"iam:GetAccessKeyLastUsed",
// Optional: only used with --sync-iam-user-console-access.
"iam:GetLoginProfile",
// Optional: only used with --sync-resource-tags.
"iam:ListUserTags",
"iam:ListRoleTags",
"iam:ListSigningCertificates",
"iam:ListSSHPublicKeys",
"iam:ListServiceSpecificCredentials",
Expand Down
24 changes: 24 additions & 0 deletions baton_capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
{
"permission": "organizations:ListParents"
},
{
"permission": "organizations:ListTagsForResource"
},
{
"permission": "sso:ListPermissionSets"
},
Expand Down Expand Up @@ -152,6 +155,9 @@
{
"permission": "organizations:ListParents"
},
{
"permission": "organizations:ListTagsForResource"
},
{
"permission": "sso:ListPermissionSets"
},
Expand Down Expand Up @@ -241,6 +247,9 @@
"permissions": [
{
"permission": "iam:ListAccountAliases"
},
{
"permission": "organizations:ListTagsForResource"
}
]
}
Expand All @@ -253,6 +262,9 @@
"permissions": [
{
"permission": "iam:ListAccountAliases"
},
{
"permission": "organizations:ListTagsForResource"
}
]
}
Expand Down Expand Up @@ -448,6 +460,9 @@
{
"permission": "iam:ListGroupsForUser"
},
{
"permission": "iam:ListUserTags"
},
{
"permission": "iam:CreateUser"
},
Expand Down Expand Up @@ -525,6 +540,9 @@
{
"permission": "iam:ListGroupsForUser"
},
{
"permission": "iam:ListUserTags"
},
{
"permission": "iam:CreateUser"
},
Expand Down Expand Up @@ -900,6 +918,9 @@
},
{
"permission": "iam:ListAttachedRolePolicies"
},
{
"permission": "iam:ListRoleTags"
}
]
}
Expand All @@ -918,6 +939,9 @@
},
{
"permission": "iam:ListAttachedRolePolicies"
},
{
"permission": "iam:ListRoleTags"
}
]
}
Expand Down
6 changes: 6 additions & 0 deletions config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@
"description": "Only sync IAM managed policies that are attached to at least one user, role, or group",
"boolField": {}
},
{
"name": "sync-resource-tags",
"displayName": "Sync Resource Tags",
"description": "Sync AWS resource tags onto accounts, IAM users, and IAM roles as the aws_tags profile field. Tags are not returned by the List APIs, so this costs at least one extra API call per resource.",
"boolField": {}
},
{
"name": "create-account-resource-type",
"displayName": "Account Provisioning Target",
Expand Down
28 changes: 26 additions & 2 deletions docs/connector.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ Two optional settings gate parts of this, and both are off by default:
- **Sync secrets** — without it no access keys are synced, so none of the per-key detail above appears.
- **Sync IAM User Console Access** (`BATON_SYNC_IAM_USER_CONSOLE_ACCESS`) — reports `console_access_status` as `enabled` when AWS returns a login profile, `disabled` when no login profile exists, or `unavailable` when AWS denies the lookup. The connector includes `console_access_enabled`, `password_reset_required`, and `login_profile_created_at` only when the state is known. It is off by default because it costs one `iam:GetLoginProfile` call per IAM user and requires `iam:GetLoginProfile` on the connector role. This setting detects an IAM console password; it does not detect access through Identity Center or an assumed role.

- **Sync Resource Tags** (`BATON_SYNC_RESOURCE_TAGS`) — without it the `aws_tags` profile field on accounts, IAM users, and IAM roles is empty. It is off by default because tags are not returned by any `List*` call, so it costs at least one extra call per resource (`organizations:ListTagsForResource`, `iam:ListUserTags`, `iam:ListRoleTags`). The 50-tag quota covers only user-created tags — AWS-reserved `aws:` system tags are additional — so tags are read across pages. `organizations:ListTagsForResource` is throttled at 10 requests/second per account, which is the practical cost in a large organization. A missing tag permission fails the sync with a `PermissionDenied` naming the action to grant, rather than quietly syncing untagged resources: enabling this setting is an explicit request for tags, so losing them silently would leave policy rules evaluating against tags that are not there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: three places that document every other optional sync flag were not given the tags equivalent — the self-hosted env-var example (~line 789, which lists BATON_SYNC_SECRETS, BATON_SYNC_IAM_USER_CONSOLE_ACCESS, BATON_SYNC_SSO_USER_LAST_LOGIN but not BATON_SYNC_RESOURCE_TAGS), the cloud-hosted setup Steps (~line 698, which has an "Optional. Enable Sync IAM User Console Access" step), and the "Section 4: Other permissions" bullet list (~line 647) that explains each optional permission in the policy JSON this PR edited at lines 499-501. Following the iam:GetLoginProfile precedent in each spot would keep the flag discoverable from both install paths.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 06a9318.


IAM user **Last Login** does not depend on either optional setting.

Identity Center user Last Login uses a separate CloudTrail event feed. Enable Organizations support, Identity Center support, and **Sync SSO User Last Login** (`BATON_SYNC_SSO_USER_LAST_LOGIN`), and grant `cloudtrail:LookupEvents` to report Identity Center sign-ins. This setting is also off by default and does not affect IAM user Last Login or access key activity.
Expand Down Expand Up @@ -268,7 +270,9 @@ Next, you will create an inline policy to define the specific data this role can
"iam:GetRole",
"iam:ListAccessKeys",
"iam:GetAccessKeyLastUsed",
"iam:GetLoginProfile"
"iam:GetLoginProfile",
"iam:ListUserTags",
"iam:ListRoleTags"
],
"Resource": "*"
},
Expand All @@ -277,7 +281,8 @@ Next, you will create an inline policy to define the specific data this role can
"Effect": "Allow",
"Action": [
"organizations:ListAccounts",
"organizations:DescribeOrganization"
"organizations:DescribeOrganization",
"organizations:ListTagsForResource"
],
"Resource": "*"
},
Expand Down Expand Up @@ -332,6 +337,11 @@ Next, you will create an inline policy to define the specific data this role can

* iam:GetLoginProfile: Allows C1 to see whether each IAM user has a console login profile, and whether a password reset is required.

**Optional: Resource Tags** The JSON above includes these permissions. They are used only when **Sync Resource Tags** (`BATON_SYNC_RESOURCE_TAGS`) is enabled. The flag is off by default because it makes at least one extra call per account, IAM user, and IAM role. If you enable it, these permissions are required — the sync fails without them rather than silently omitting tags.

* iam:ListUserTags and iam:ListRoleTags: Allow C1 to read the tags on each IAM user and role. The `ListUsers` and `ListRoles` responses do not include tags, so these per-resource calls are the only source.
* organizations:ListTagsForResource: Allows C1 to read the tags on each AWS account. The `ListAccounts` response has no tags field at all.

**Optional: AWS Organizations Support** Include these permissions if you enable the "Enable support for AWS Organizations" checkbox in the C1 UI.

* organizations:ListAccounts: Allows the connector to discover all accounts within your AWS Organization.
Expand Down Expand Up @@ -492,6 +502,9 @@ The permissions policy below is broken into several sections to align with these
"sso:ListPermissionSetsProvisionedToAccount",
"organizations:ListRoots",
"organizations:ListOrganizationalUnitsForParent",
"organizations:ListTagsForResource",
"iam:ListUserTags",
"iam:ListRoleTags",
"iam:GetUser",
"iam:ListAccessKeys",
"iam:ListSigningCertificates",
Expand Down Expand Up @@ -638,6 +651,7 @@ The permissions policy below is broken into several sections to align with these
- The permissions listed in the `"Sid": "IAMListPermissions"` and `"Sid": "AccessToSSOProvisiondRoles"` sections are required only if you want to use C1 to create assignments in the AWS Organization’s management account. In certain cases, you may also need to add `iam:UpdateSAMLProvider` to these sections.
- `iam:ListAccessKeys` and `iam:GetAccessKeyLastUsed` let C1 report the most recent access key activity on each IAM user. Enable **Sync secrets** to also sync each key as its own secret resource with per-key activity. When cross-account IAM sync is active, grant both actions to the assumed role in every member account.
- `iam:GetLoginProfile` is only needed when **Sync IAM User Console Access** is enabled. It lets C1 report whether an IAM user has a console password.
- `organizations:ListTagsForResource`, `iam:ListUserTags` and `iam:ListRoleTags` are only needed when **Sync Resource Tags** is enabled. They let C1 read the tags on each account, IAM user and IAM role. None of the `List*` calls return tags, so these are the only source. If the setting is on and a permission is missing, the sync fails rather than silently omitting tags.
</Step>
<Step>
Click **Review Policy**.
Expand Down Expand Up @@ -694,6 +708,9 @@ The permissions policy below is broken into several sections to align with these
**Optional.** Enable **Sync IAM User Console Access** to report whether each IAM user has a console password. The connector role must include `iam:GetLoginProfile`.
</Step>
<Step>
**Optional.** Enable **Sync Resource Tags** to publish AWS tags on accounts, IAM users and IAM roles as an `aws_tags` profile field, usable in policy rules. The connector role must include `organizations:ListTagsForResource`, `iam:ListUserTags` and `iam:ListRoleTags`. This adds at least one API call per account, user and role.
</Step>
<Step>
**Optional.** If both Organizations support and Identity Center support are enabled, enable **Sync SSO User Last Login** to report Identity Center sign-ins. The connector role must include `cloudtrail:LookupEvents`.
</Step>
<Step>
Expand Down Expand Up @@ -797,6 +814,10 @@ stringData:
# Requires Organizations and Identity Center support plus cloudtrail:LookupEvents
BATON_SYNC_SSO_USER_LAST_LOGIN: true

# Optional: Publish AWS resource tags as the aws_tags profile field
# Requires organizations:ListTagsForResource, iam:ListUserTags, iam:ListRoleTags
BATON_SYNC_RESOURCE_TAGS: true

# Optional: Choose which AWS user type C1 should create when provisioning accounts.
# "iam_user" (default) creates IAM users. "sso_user" creates AWS Identity Center
# (SSO) users via the Identity Store API. Only one path is active at a time per
Expand Down Expand Up @@ -991,6 +1012,9 @@ resource "aws_iam_role" "ConductorOneIntegration" {
"organizations:ListOrganizationalUnitsForParent",
"organizations:ListParents",
"organizations:ListRoots",
"organizations:ListTagsForResource",
"iam:ListUserTags",
"iam:ListRoleTags",
"sso:DescribePermissionSet",
"sso:GetInlinePolicyForPermissionSet",
"sso:ListAccountAssignments",
Expand Down
1 change: 1 addition & 0 deletions pkg/config/conf.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ var (
field.WithDescription("Only sync IAM managed policies that are attached to at least one user, role, or group"),
field.WithDefaultValue(false),
)
SyncResourceTags = field.BoolField(
"sync-resource-tags",
field.WithDisplayName("Sync Resource Tags"),
field.WithDescription(
"Sync AWS resource tags onto accounts, IAM users, and IAM roles as the aws_tags profile field. "+
"Tags are not returned by the List APIs, so this costs at least one extra API call per resource.",
),
field.WithDefaultValue(false),
)
GlobalAwsAccountProvisioningTargetField = field.SelectField(
"create-account-resource-type",
[]string{"iam_user", "sso_user"},
Expand Down Expand Up @@ -176,6 +185,7 @@ var Config = field.NewConfiguration(
SyncSSOUserLastLogin,
SyncIAMUserConsoleAccess,
SyncOnlyAttachedPolicies,
SyncResourceTags,
GlobalAwsAccountProvisioningTargetField,
},
field.WithConstraints(
Expand Down
15 changes: 15 additions & 0 deletions pkg/connector/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ type accountResourceType struct {
// this so accounts never point at a Root/OU resource that this run never syncs, which
// would otherwise leave a dangling "MISSING RESOURCE" parent.
hierarchySync HierarchySyncFlags

// syncResourceTags gates the per-account organizations:ListTagsForResource call.
// See tags.go for why this is opt-in.
syncResourceTags bool
}

func (o *accountResourceType) ResourceType(_ context.Context) *v2.ResourceType {
Expand Down Expand Up @@ -194,6 +198,15 @@ func (o *accountResourceType) List(ctx context.Context, _ *v2.ResourceId, opts r
l.Debug("baton-aws: account found", zap.String("name", name), zap.String("account_id", accountId), zap.String("account_status", string(status)))

profile := accountProfile(ctx, account)

if o.syncResourceTags {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: aws_tags is absent from the account profile when the flag is off, but IAM users/roles always publish it (roleProfile/iamUserProfile set it to an empty map unconditionally). That inconsistency matters for the CEL contract documented in tags.go: "Owner" in resource.profile.aws_tags is itself an eval error when the aws_tags key does not exist, so a rule written against accounts breaks differently depending on the flag. docs/connector.mdx:120 also states the field "is empty" without the flag, which is only true for users and roles. Consider setting profile[tagsProfileField] = map[string]interface{}{} in accountProfile so the key is always present.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't fix. We avoid changing existing profile fields, so aws_tags stays unconditional on users/roles. CEL guards absence fine: has(resource.profile.aws_tags) returns false without erroring.

tags, err := fetchAccountTags(ctx, o.orgClient, accountId)
if err != nil {
return nil, nil, err
}
profile[tagsProfileField] = tags
}

resourceOpts := []resourceSdk.ResourceOption{
resourceSdk.WithAnnotation(annos),
// Sparse ACLs: advertise the scope-binding type as a child so the SDK
Expand Down Expand Up @@ -973,6 +986,7 @@ func accountBuilder(
region string,
identityClient client.IdentityStoreClient,
hierarchySync HierarchySyncFlags,
syncResourceTags bool,
) *accountResourceType {
return &accountResourceType{
resourceType: resourceTypeAccount,
Expand All @@ -983,6 +997,7 @@ func accountBuilder(
identityInstance: identityInstance,
region: region,
hierarchySync: hierarchySync,
syncResourceTags: syncResourceTags,
}
}

Expand Down
9 changes: 9 additions & 0 deletions pkg/connector/account_iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ func (o *accountIAMResourceType) List(ctx context.Context, _ *v2.ResourceId, opt
Id: awsSdk.ToString(account.Id),
}
profile := accountProfile(ctx, account)

if o.aws != nil && o.aws.syncResourceTags {
tags, err := fetchAccountTags(ctx, o.orgClient, awsSdk.ToString(account.Id))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Unlike account.go:194, this loop never filters on account.Status, so fetchAccountTags now fires for SUSPENDED / PENDING_CLOSURE accounts too — and every error here is fatal, so one bad account aborts the whole account_iam sync. It also burns organizations:ListTagsForResource calls (10 req/s per account) on accounts the org account syncer deliberately skips. Consider skipping non-ACTIVE accounts before the tag fetch, mirroring account.go.

if err != nil {
return nil, nil, err
}
profile[tagsProfileField] = tags
}

userResource, err := resourceSdk.NewAppResource(
awsSdk.ToString(account.Name),
resourceTypeAccountIam,
Expand Down
Loading
Loading