Skip to content

feat(cli): add cloud-account repair command for org-onboarded AwsCfg accounts - #1845

Merged
lokesh-vadlamudi merged 3 commits into
mainfrom
lvadlamudi/link-4430-config-repair
Jul 16, 2026
Merged

feat(cli): add cloud-account repair command for org-onboarded AwsCfg accounts#1845
lokesh-vadlamudi merged 3 commits into
mainfrom
lvadlamudi/link-4430-config-repair

Conversation

@lokesh-vadlamudi

@lokesh-vadlamudi lokesh-vadlamudi commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Jira/Github ticket

https://lacework.atlassian.net/browse/LINK-4430

Summary

Adds lacework cloud-account repair to re-register a missing cloud-account integration for an account onboarded through a Lacework onboarding template — one account with --account-id, or every broken account in the fleet with --all.

The onboarding template is selected with --method, named <cloud>-<template>. Only aws-cfg-org (AWS Config organization onboarding) is implemented today; more AWS templates and other clouds (gcp, azure) are planned as additional --method values. The code is modular: each method lives in its own file (cloud_account_repair_aws_cfg_org.go) behind a small dispatch registry in cloud_account_repair.go, so adding a method is a new file plus one registry entry.

For aws-cfg-org: the org-config setup Lambda registers each member account with Lacework but does not reconcile — if a registration (or the member stack) is dropped, replaying is the only recovery. This command does the recovery synchronously and is state-aware:

  • member stack instance present, integration missing → re-register the integration only
  • member stack instance missing (role gone) but the account is in a targeted OU → re-create the stack instance (rebuilds the IAM role), wait for it, then register
  • integration already present → nothing to do
  • account not in any targeted OU, or the management StackSet is gone → report and do nothing

It derives the member role ARN and external id from the StackSet instance's stack id (matching how the config-org member template builds them), so it registers directly against the Lacework API. Supports --dry-run and --json.

With --all (mutually exclusive with --account-id), the command sweeps the whole fleet instead: it enumerates every ACTIVE account under the StackSet's targeted OUs (recursively, via AWS Organizations; the org management account is excluded since service-managed StackSets never deploy to it), diffs them against the existing stack instances and AwsCfg integrations, rebuilds all missing member instances in a single StackSet operation, then registers every unregistered account. Per-account registration failures are reported in the summary and exit non-zero rather than aborting the sweep. --dry-run reports the classified diff without changing anything.

Output contract (--json)

Per-account results use one shape in both modes: {"accountId", "action"} plus name/roleArn/externalId/intgGuid when a registration happened, or error when it failed. action is one of register (dry-run), registered, already-onboarded, create-stack-instance-and-register (dry-run, rebuild pending), error.

--all always emits a single envelope regardless of outcome — healthy, dry-run, or repaired — so consumers parse one shape:

{
  "targetOUs": ["..."],
  "expectedAccounts": 2,
  "missingStackInstance": ["..."],
  "missingIntegration": [],
  "repaired": [ { "accountId": "...", "action": "registered", "intgGuid": "..." } ],
  "dryRun": true
}

(dryRun present only on dry-runs; repaired empty on dry-run and healthy outcomes.)

lacework cloud-account repair --help (byte-exact, from the committed help fixture CI verifies)
Re-register a missing Lacework cloud-account integration for one account onboarded through a
Lacework onboarding template, when the integration is missing - whether the underlying IAM role is
still healthy or was dropped (in which case it is rebuilt first).

The template is selected with --method, named <cloud>-<template>. Only "aws-cfg-org" (AWS Config
organization onboarding) is implemented today; more AWS templates and other clouds (gcp, azure) are
planned and will be added as additional --method values. The rest of this help describes the
aws-cfg-org method.

The org-config setup Lambda registers each member account with Lacework but does NOT reconcile: if a
registration (or the member stack) is dropped, replaying is the only recovery. For aws-cfg-org this
command is state-aware and does the minimum needed for the account:

  - member stack instance present, integration missing -> re-register the integration only
  - member stack instance missing (role gone) but the account is in a targeted OU -> re-create the
    stack instance (which rebuilds the IAM role), wait for it, then register the integration
  - integration already present -> nothing to do
  - account not in any targeted OU, or the management StackSet is gone -> report and do nothing

It registers directly against the Lacework API (deriving the member role ARN and external id from
the stack instance), so you get the result synchronously instead of the setup Lambda's 5-minute
fire-and-forget. When it registers directly the integration is named "<LaceworkAccount>-Config".

Naming note: in the rebuild path (member stack instance missing), re-creating the instance fires the
template's own setup Lambda, which registers the integration first. In that case this command reports
"already onboarded" and the integration keeps the Lambda's name - the account is still fully
recovered, just registered by the Lambda rather than directly.

With --all instead of --account-id, the command sweeps every ACTIVE account under the StackSet's
targeted OUs (recursively, excluding the organization management account), finds the ones missing
their member stack and/or integration, rebuilds all missing stack instances in one StackSet
operation, and registers every unregistered account. With --dry-run it only reports the diff.

Run it with credentials for your AWS Organizations management account (the account that owns the
management stack):

    lacework cloud-account repair --account-id 123456789012 --stack-name lacework-aws-cfg-org
    lacework cloud-account repair --account-id 123456789012 --stack-name lacework-aws-cfg-org --dry-run
    lacework cloud-account repair --all --stack-name lacework-aws-cfg-org --dry-run

Creating a stack instance only works when the account already belongs to an OU the StackSet targets.
If it does not, add the account to a targeted OU (or add its OU to the stack's OrganizationalUnits
parameter) first - this command reports that case and does nothing.

Usage:
  lacework cloud-account repair [flags]

Flags:
      --account-id string    AWS account id whose AwsCfg integration is missing (required unless --all)
      --all                  find and repair every account in the targeted OUs that is missing its member stack or integration
      --aws-profile string   AWS profile for the org management account credentials
      --aws-region string    AWS region the management stack/stackset live in (default us-east-1)
      --dry-run              show what would be re-registered without calling the Lacework API
  -h, --help                 help for repair
      --method string        onboarding template to repair (supported: aws-cfg-org) (default "aws-cfg-org")
      --stack-name string    name of the Lacework AWS Config org management CloudFormation stack (required)

Global Flags:
  -a, --account string      account subdomain of URL (i.e. <ACCOUNT>.lacework.net)
  -k, --api_key string      access key id
  -s, --api_secret string   secret access key
      --api_token string    access token (replaces the use of api_key and api_secret)
      --debug               turn on debug logging
      --json                switch commands output from human-readable to json format
      --nocache             turn off caching
      --nocolor             turn off colors
      --noninteractive      turn off interactive mode (disable spinners, prompts, etc.)
      --organization        access organization level data sets (org admins only)
  -p, --profile string      switch between profiles configured at ~/.lacework.toml
      --subaccount string   sub-account name inside your organization (org admins only)

How did you test this change?

Unit tests (TestStackUID, TestRepairDerivationMatchesTemplate, TestRepairRegisterResult, TestDiffRepairTargets) plus flag/dispatch checks — --method defaults to aws-cfg-org, and unsupported values fail fast listing what's supported:

$ lacework cloud-account repair --method aws-ct-org --account-id <member> --stack-name <mgmt-stack>
ERROR unsupported --method "aws-ct-org"; supported: aws-cfg-org

Verified end-to-end against a real AWS Config organization onboarding (twice — before and after the modular refactor, identical behavior): deployed the config-org template into an AWS Organizations management account, onboarding two member accounts to a Lacework tenant, then exercised both recovery paths.

Scenario 1 — instance present, integration missing (deleted the integration, left the IAM role):

$ lacework cloud-account repair --account-id <member> --stack-name <mgmt-stack> --aws-region us-west-2 --dry-run
Re-registering AwsCfg integration:
  account-id:  <member>
  name:        <tenant>-Config
  role-arn:    arn:aws:iam::<member>:role/lacework-config-role-d051baf0
  external-id: lweid:aws:v2:<tenant>:<member>:LWd051baf0
Dry-run: nothing was created. Re-run without --dry-run to register.

$ lacework --json cloud-account repair --account-id <member> --stack-name <mgmt-stack> --aws-region us-west-2
{ "accountId": "<member>", "action": "registered", "intgGuid": "...", "roleArn": "...", "externalId": "..." }

Derived role/external id matched the original onboarding exactly; integration came back state: Ok. Re-running is idempotent (action: already-onboarded).

Scenario 2 — member stack instance + role gone (deleted the StackSet instance with --no-retain-stacks; this also deregisters the integration via the member stack's custom resource):

$ lacework --json cloud-account repair --account-id <member> --stack-name <mgmt-stack> --aws-region us-west-2 --dry-run
{ "accountId": "<member>", "action": "create-stack-instance-and-register", "dryRun": true, "stackSet": "...", "targetOUs": ["..."] }

The real run rebuilt the member StackSet instance and the account recovered healthy (stack instance CURRENT, integration state: Ok). Note: re-creating the instance fires the template's own setup Lambda, which registers first — so repair reports already-onboarded in this path (the account is still fully recovered, just registered by the Lambda rather than directly). This is documented in the command help.

--all sweep mode

Verified end-to-end against a fresh config-org deployment (management account, two member accounts under the targeted OUs — one targeted OU intentionally left empty to exercise the recursive enumeration — onboarding to a Lacework tenant). Unit test TestDiffRepairTargets covers the diff classification.

Healthy baseline — enumeration, management-account exclusion, and diff report nothing to fix; the --json envelope carries the same keys as every other outcome, and the single-account mode reports the shared already-onboarded shape:

$ lacework cloud-account repair --all --stack-name <mgmt-stack> --aws-region us-west-2 --dry-run
All 2 accounts in the targeted OUs are onboarded; nothing to do.

$ lacework --json cloud-account repair --all --stack-name <mgmt-stack> --aws-region us-west-2 --dry-run
{
  "dryRun": true,
  "expectedAccounts": 2,
  "missingIntegration": [],
  "missingStackInstance": [],
  "repaired": [],
  "targetOUs": [ "<ou-1>", "<ou-2>" ]
}

$ lacework --json cloud-account repair --account-id <member-1> --stack-name <mgmt-stack> --aws-region us-west-2
{
  "accountId": "<member-1>",
  "action": "already-onboarded"
}

Register-only path — deleted one integration (instances left intact); the diff classifies it and the sweep registers it directly, synchronously:

$ lacework --json cloud-account repair --all --stack-name <mgmt-stack> --aws-region us-west-2 --dry-run
{
  "dryRun": true,
  "expectedAccounts": 2,
  "missingIntegration": [ "<member-1>" ],
  "missingStackInstance": [],
  "repaired": [],
  "targetOUs": [ "<ou-1>", "<ou-2>" ]
}

$ lacework --json cloud-account repair --all --stack-name <mgmt-stack> --aws-region us-west-2
{
  "expectedAccounts": 2,
  "missingIntegration": [ "<member-1>" ],
  "missingStackInstance": [],
  "repaired": [
    {
      "accountId": "<member-1>",
      "action": "registered",
      "externalId": "lweid:aws:v2:<tenant>:<member-1>:LW08b703c0",
      "intgGuid": "<tenant-guid>_B34F4E2E...",
      "name": "<tenant>-Config",
      "roleArn": "arn:aws:iam::<member-1>:role/lacework-config-role-08b703c0"
    }
  ],
  "targetOUs": [ "<ou-1>", "<ou-2>" ]
}

The integration came up Enabled/Ok — the role ARN and external id derived from the existing stack instance matched what the member template built.

Broken fleet / batch rebuild — deleted both member stack instances (--no-retain-stacks, which also deregisters both integrations via the member stacks' custom resource), then:

$ lacework --json cloud-account repair --all --stack-name <mgmt-stack> --aws-region us-west-2 --dry-run
{
  "dryRun": true,
  "expectedAccounts": 2,
  "missingIntegration": [],
  "missingStackInstance": [ "<member-1>", "<member-2>" ],
  "repaired": [],
  "targetOUs": [ "<ou-1>", "<ou-2>" ]
}

$ lacework cloud-account repair --all --stack-name <mgmt-stack> --aws-region us-west-2
Rebuilding 2 member stack instance(s): <member-1>, <member-2>

  <member-1>: already onboarded
  <member-2>: already onboarded

Repaired 2 of 2 account(s).

Both instances were created in one StackSet operation and came back CURRENT; both integrations recovered Enabled/Ok ("already onboarded" because re-creating the member stacks fires the template's setup Lambda, which registers first — the documented race). A follow-up --all run converged to "All 2 accounts in the targeted OUs are onboarded; nothing to do" (idempotent). Note the rebuild is a long operation (~10+ minutes for two accounts — each member deploy provisions the role and blocks on the Lambda registration round-trip, serialized by MaxConcurrentCount: 1).

Field note: the config-org template ships its StackSet with AutoDeployment: Enabled, so an instance deleted while the account is still in a targeted OU is re-created by AWS reconciliation within minutes (and the Lambda re-registers it). The --all rebuild path matters for the cases auto-deployment can't see: integration deleted on the Lacework side only, the account was outside the OU when the instance was lost, or the StackSet/management stack was replaced. (For the E2E rebuild test above, auto-deployment was temporarily disabled on the test StackSet to keep it from racing the repair.)

@lokesh-vadlamudi lokesh-vadlamudi self-assigned this Jul 15, 2026
@lokesh-vadlamudi
lokesh-vadlamudi force-pushed the lvadlamudi/link-4430-config-repair branch 5 times, most recently from e023c55 to 990b3f6 Compare July 15, 2026 20:30
Re-registers a missing AwsCfg cloud-account integration for an AWS account
onboarded through the AWS Config organization CloudFormation template.
State-aware: re-registers when the member stack is healthy, or rebuilds the
member StackSet instance first when the IAM role was dropped. The onboarding
method is selected with --method (aws-cfg-org today; more templates and
clouds planned). Supports --dry-run and --json.
@lokesh-vadlamudi
lokesh-vadlamudi force-pushed the lvadlamudi/link-4430-config-repair branch from 990b3f6 to 5b3489f Compare July 15, 2026 20:40
With --all instead of --account-id, repair enumerates every ACTIVE
account under the StackSet's targeted OUs (recursively, excluding the
org management account), diffs them against existing stack instances
and AwsCfg integrations, rebuilds all missing member instances in one
StackSet operation, and registers every unregistered account.
Per-account registration failures are reported and exit non-zero
instead of aborting the sweep. --dry-run reports the classified diff;
--json emits it structured.
Single-account and --all modes now report already-onboarded with the
same {accountId, action} shape, and --all emits one JSON envelope for
every outcome (healthy, dry-run, repaired) with stable keys and
non-null arrays. stackParameter gained a found flag so targetOUsParam
can distinguish an absent OrganizationalUnits parameter from a
DescribeStacks API failure instead of swallowing the error.
@lokesh-vadlamudi
lokesh-vadlamudi marked this pull request as ready for review July 16, 2026 20:04
@lokesh-vadlamudi
lokesh-vadlamudi requested a review from a team as a code owner July 16, 2026 20:04
@lokesh-vadlamudi
lokesh-vadlamudi enabled auto-merge (squash) July 16, 2026 20:44

@PengyuanZhao PengyuanZhao left a comment

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.

LGTM!

@lokesh-vadlamudi
lokesh-vadlamudi merged commit 3e0e003 into main Jul 16, 2026
13 checks passed
@lokesh-vadlamudi
lokesh-vadlamudi deleted the lvadlamudi/link-4430-config-repair branch July 16, 2026 21:11
@lacework-releng lacework-releng mentioned this pull request Jul 16, 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