Skip to content

fix(CAD-1836): target the caller's tenant when generating AWS org Config - #1860

Merged
PengyuanZhao merged 1 commit into
mainfrom
pzhao/CAD-1836
Sep 3, 2026
Merged

PengyuanZhao merged 1 commit into
mainfrom
pzhao/CAD-1836

Conversation

@PengyuanZhao

Copy link
Copy Markdown
Contributor

Jira/Github ticket

https://lacework.atlassian.net/browse/CAD-1836

Note the ticket title — "AWS Config integration POST endpoint not respect subaccount API key" — is a misdiagnosis. The API is correct; see Summary. The ticket covers two independent client-side defects; the other is fixed in lacework/services#34638.

Summary

A user ran lacework generate cloud-account aws with an API key belonging to sub-account TECH_ALLY, leaving the sub-account prompt blank. Their CloudTrail integration landed in TECH_ALLY (correct), but their AWS Config integrations landed in the parent account customerdemo (wrong), one per AWS member account.

The two take different routes. CloudTrail is created by lacework_integration_aws_ct through the Terraform Lacework provider, which picks up the sub-account from ~/.lacework.toml or LW_*. AWS-organization Config is not — createConfig diverts it to lacework/org-configuration/aws, a CloudFormation StackSet whose Lambda calls the Lacework API itself with the target tenant passed in as module attributes, then early-returns without emitting a lacework_integration_aws_cfg resource at all:

if args.AwsOrganization {
    block, err := lwgenerate.NewModule(
        "aws_org_configuration",
        lwgenerate.AwsConfigOrgSource,
        lwgenerate.HclModuleWithProviderDetails(map[string]string{"aws": "aws.main"}), // no lacework provider
        lwgenerate.HclModuleWithAttributes(map[string]interface{}{
            "lacework_account":    args.ConfigOrgLWAccount,
            "lacework_subaccount": args.ConfigOrgLWSubaccount,  // the ONLY tenant pointer
            ...

It never sees the ambient profile or environment, so lacework_subaccount is the only thing pointing it at a tenant. That value came from a prompt labelled "Lacework subaccount (optional):", with no Required flag and a default of its own zero value — never seeded from the account the CLI had already authenticated as (grepping generate_aws.go for cli.Subaccount/cli.Account returned nothing). Pressing Enter, the frictionless path, meant "parent tenant".

The fix defaults both the account and sub-account from the CLI's own state, leaving any explicitly supplied value alone. Placement matters in two ways:

  • Before the prompts, so the values render as their defaults and can still be cleared to deliberately target the parent.
  • Outside the cli.InteractiveMode() branch, because promptAwsGenerate only runs interactively — seeding inside it would leave --noninteractive broken.

The server is not at fault. With no Account-Name header, TokenAuthenticator leaves custGuid as the key's own account; the integration write uses sessionProps.getCustGuid() verbatim with no primary-account substitution on create (that exists only on read); and AwsCfg is not in CloudAccounts.orgSubType, so the one org-access swap path returns 400 rather than writing to the parent.

How did you test this change?

  • go build ./... clean; go test ./cli/cmd/ (full package) passes.
  • I originally added four unit cases covering seed-both / seed-sub-account-only / don't-override-explicit / no-sub-account-configured, but the logic was inlined into the RunE closure on review, which isn't unit-testable without driving the whole command. Suggested follow-up: assert on generated HCL in lwgenerate/aws — that package already compares emitted Terraform, so a case there could assert lacework_subaccount appears in the aws_org_configuration block.
  • go vet reports pre-existing non-constant-format-string findings across ~20 unrelated files (including generate_aws.go:276, which this PR does not touch — zero OutputHuman lines in the diff). Tests were run with -vet=off for that reason.

Manual verification for QA: with a sub-account API key configured and the sub-account prompt left at its default, run generate cloud-account aws with --aws_organization and Config enabled; confirm the generated main.tf carries lacework_subaccount = "<sub-account>" in the aws_org_configuration module and that the resulting Config integrations land in that sub-account.

Existing remediation: lacework cloud-account repair (v2.15.0) covers org-onboarded AwsCfg accounts already in the wrong tenant, but it reads only the LaceworkAccount stack parameter and never LaceworkSubaccount — worth a follow-up so affected customers can fully self-remediate.

🤖 Generated with Claude Code

A user running `generate cloud-account aws` with an API key belonging to sub-account
TECH_ALLY, leaving the sub-account prompt blank, got their CloudTrail integration in
TECH_ALLY but their AWS Config integrations in the parent account customerdemo.

The two take different routes. CloudTrail is created by lacework_integration_aws_ct through
the Terraform Lacework provider, which picks up the sub-account from ~/.lacework.toml or
LW_*. AWS-organization Config is not: createConfig diverts it to
lacework/org-configuration/aws, a CloudFormation StackSet whose Lambda calls the Lacework
API itself with the target tenant passed in as module attributes. It never sees the ambient
profile or environment, so the only thing pointing it at a tenant is lacework_subaccount.

That value came from a prompt labelled "Lacework subaccount (optional):" with no Required
flag and a default of its own zero value -- it was never seeded from the account the CLI had
already authenticated as. Pressing Enter, the frictionless path, meant "parent tenant", and
the Lambda then registered one Config integration per AWS member account there.

Default both the account and sub-account from the CLI's own state, leaving any explicitly
supplied value alone. Seeding happens before the prompts so the values appear as their
defaults and can still be cleared to deliberately target the parent, and outside the
interactive branch because promptAwsGenerate only runs interactively -- otherwise
--noninteractive would stay broken.

Note the server is not at fault: with no Account-Name header the API leaves custGuid as the
key's own account, the integration write uses it verbatim, and AwsCfg is not an org subtype
so the one org-access path would 400 rather than write to the parent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PengyuanZhao
PengyuanZhao requested a review from a team as a code owner September 3, 2026 20:34
@PengyuanZhao
PengyuanZhao enabled auto-merge (squash) September 3, 2026 20:38

@lokesh-vadlamudi lokesh-vadlamudi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Lgtm

@PengyuanZhao
PengyuanZhao merged commit 5d03eff into main Sep 3, 2026
13 checks passed
@PengyuanZhao
PengyuanZhao deleted the pzhao/CAD-1836 branch September 3, 2026 20:43
@lacework-releng lacework-releng mentioned this pull request Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants