feat(iam): add view-only access across all five accounts (PLAT-237) (#176)
Some checks are pending
Deploy / deploy-management (push) Waiting to run
Deploy / deploy-external-dev (push) Waiting to run
Deploy / deploy-security (push) Waiting to run
Deploy / deploy-dev (push) Waiting to run
Deploy / deploy-prod (push) Waiting to run

Give the view group ViewOnlyAccess in every account without a data-plane read or an assume-role path, and include the stack on the management deploy job.
This commit is contained in:
Adam Moussa 2026-10-02 18:25:22 +00:00 • committed by GitHub
parent 031e1d1a3b
commit 5589c6d34c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 110 additions and 1 deletions

View file

@ -22,7 +22,7 @@ jobs:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@e5691d8a7f96ac4d5a841a82975ff0a4354d53ac # v1.0.7
with:
node-version: "24"
stacks: "account-baseline dynamodb-cmk regional-baseline-us-west-2 regional-baseline-us-east-2 backup-offsite backup org-governance platform-access engineering-access"
stacks: "account-baseline dynamodb-cmk regional-baseline-us-west-2 regional-baseline-us-east-2 backup-offsite backup org-governance platform-access engineering-access view-access"
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}

View file

@ -31,6 +31,7 @@ are noted):
| `seahaven-backup-offsite` | 328440206208 | us-west-2 | Governance-locked offsite copy vault (C-7) |
| `seahaven-org-governance` | 328440206208 | us-east-1 | AWS Organizations OU tree + SCPs (incl. the cdk-imported external-dev guardrails) |
| `seahaven-engineering-access` | 328440206208 | us-east-1 | Identity Center group `engineering`. `EngineeringProd` reads payments-dashboard configuration and seahaven-site in 011934824531. `EngineeringDev` has no allow (PLAT-235, PLAT-236). |
| `seahaven-view-access` | 328440206208 | us-east-1 | Identity Center group `view`. Permission set `View` is `ViewOnlyAccess` on all five accounts. No data-plane reads and no assume-role. Membership is outside this stack. |
| `seahaven-external-dev-baseline` | 396287094661 | us-east-1 | Member-account baseline: Config, GuardDuty, Security Hub (FSBP + CIS v3.0), Access Analyzer, flow logs, budget |
| `seahaven-terraform-substrate` | 011934824531, 710827005802 | us-east-1 | Removed from the CDK app and from CD (PLAT-147). Live stacks remain until `scripts/delete-terraform-substrate-prod-dev.sh`. The six imported prod pairs are already forgotten. |
| `seahaven-hcptf` | 011934824531, 710827005802 | us-east-1 | payments-dashboard HCP apply and plan roles, scoped policies, and the Lambda boundary in prod and dev. Prod also owns the imported `hcptf-seahaven-site` apply and plan roles (PLAT-225), with policies at `/tf-managed/`. Trust is pinned per workspace. On the dev and prod deploy jobs. Do not create the roles. |
@ -67,6 +68,7 @@ the TypeScript source — no separate compile step needed for `cdk synth` /
| `backup` | `seahaven-backup` | 328440206208 | us-east-1 | `lib/backup-stack.ts` |
| `org-governance` | `seahaven-org-governance` | 328440206208 | us-east-1 | `lib/org-governance-stack.ts` |
| `engineering-access` | `seahaven-engineering-access` | 328440206208 | us-east-1 | `lib/engineering-access-stack.ts` |
| `view-access` | `seahaven-view-access` | 328440206208 | us-east-1 | `lib/view-access-stack.ts` |
| `external-dev-baseline` | `seahaven-external-dev-baseline` | 396287094661 | us-east-1 | `lib/member-baseline-stack.ts` |
| `security-baseline` | `seahaven-security-baseline` | 001520130573 | us-east-1 | `lib/member-baseline-stack.ts` |
| `dev-baseline` | `seahaven-dev-baseline` | 710827005802 | us-east-1 | `lib/member-baseline-stack.ts` (orgManagedDetection) |

View file

@ -15,6 +15,7 @@ import { MemberBaselineStack } from "../lib/member-baseline-stack";
import { OrgGovernanceStack } from "../lib/org-governance-stack";
import { PlatformAccessStack } from "../lib/platform-access-stack";
import { EngineeringAccessStack } from "../lib/engineering-access-stack";
import { ViewAccessStack } from "../lib/view-access-stack";
const ACCOUNT = "328440206208";
const EXTERNAL_DEV_ACCOUNT = "396287094661";
@ -95,6 +96,16 @@ new EngineeringAccessStack(app, "engineering-access", {
prodAccountId: PROD_ACCOUNT,
});
new ViewAccessStack(app, "view-access", {
stackName: "seahaven-view-access",
env: { account: ACCOUNT, region: "us-east-1" }, // pragma: allowlist secret
managementAccountId: ACCOUNT,
securityAccountId: SECURITY_ACCOUNT,
externalDevAccountId: EXTERNAL_DEV_ACCOUNT,
devAccountId: DEV_ACCOUNT,
prodAccountId: PROD_ACCOUNT,
});
new MemberBaselineStack(app, "external-dev-baseline", {
stackName: "seahaven-external-dev-baseline",
env: { account: EXTERNAL_DEV_ACCOUNT, region: "us-east-1" },

96
lib/view-access-stack.ts Normal file
View file

@ -0,0 +1,96 @@
import * as cdk from "aws-cdk-lib";
import * as identitystore from "aws-cdk-lib/aws-identitystore";
import * as sso from "aws-cdk-lib/aws-sso";
import { Construct } from "constructs";
const IDENTITY_CENTER_INSTANCE_ARN =
"arn:aws:sso:::instance/ssoins-722321f42ca610e4";
const IDENTITY_STORE_ID = "d-9067ec8e26";
const VIEW_ONLY_POLICY = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess";
export interface ViewAccessStackProps extends cdk.StackProps {
managementAccountId: string;
securityAccountId: string;
externalDevAccountId: string;
devAccountId: string;
prodAccountId: string;
}
/**
* Identity Center group and permission set for view-only access across
* every organization account.
*
* ViewOnlyAccess lists and describes resources. It does not read S3
* objects, secret values, DynamoDB items, or log contents. There is no
* inline policy and no sts:AssumeRole on OrganizationAccountAccessRole.
*
* Group membership is outside this stack.
*/
export class ViewAccessStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: ViewAccessStackProps) {
super(scope, id, props);
const group = new identitystore.CfnGroup(this, "ViewGroup", {
identityStoreId: IDENTITY_STORE_ID,
displayName: "view",
description:
"View-only across all organization accounts. No data-plane reads.",
});
const permissionSet = new sso.CfnPermissionSet(this, "ViewPermissionSet", {
instanceArn: IDENTITY_CENTER_INSTANCE_ARN,
name: "View",
description:
"ViewOnlyAccess in every organization account. No assume-role.",
sessionDuration: "PT8H",
managedPolicies: [VIEW_ONLY_POLICY],
});
this.assignment(
"ViewManagementAssignment",
permissionSet,
group,
props.managementAccountId,
);
this.assignment(
"ViewSecurityAssignment",
permissionSet,
group,
props.securityAccountId,
);
this.assignment(
"ViewExternalDevAssignment",
permissionSet,
group,
props.externalDevAccountId,
);
this.assignment(
"ViewDevAssignment",
permissionSet,
group,
props.devAccountId,
);
this.assignment(
"ViewProdAssignment",
permissionSet,
group,
props.prodAccountId,
);
}
private assignment(
id: string,
permissionSet: sso.CfnPermissionSet,
group: identitystore.CfnGroup,
targetId: string,
): void {
new sso.CfnAssignment(this, id, {
instanceArn: IDENTITY_CENTER_INSTANCE_ARN,
permissionSetArn: permissionSet.attrPermissionSetArn,
principalId: group.attrGroupId,
principalType: "GROUP",
targetId,
targetType: "AWS_ACCOUNT",
});
}
}