FinOps Account Hardening
When org.ous.sharedServices.accounts.finOps is set, ScpFinOpsAccountBaseline is
auto-attached to the FinOps account. No extra config required.
The FinOps account exists for one purpose — hold CUR Parquet data, the Glue catalog, and Athena queries. The SCP enforces that scope.
What it denies
| Category | Examples |
|---|---|
| Compute / data services | ec2:RunInstances + ec2:StartInstances, RDS, SageMaker, Bedrock, ECS, EKS, EMR, Redshift / Redshift Serverless, OpenSearch / ES, Kinesis, Firehose, MSK, Step Functions, AppRunner, Lightsail, WorkSpaces, AppStream, Code* |
| Network primitives | VPC / IGW / NAT / TGW / VPC peering / Client VPN creation |
| IAM identity creation | CreateUser, CreateAccessKey, CreateLoginProfile, CreateServiceSpecificCredential (humans use SSO) |
| Org integrity | LeaveOrganization, CloseAccount, disabling CloudTrail / GuardDuty / Macie / Config |
AWSControlTowerExecution is exempted everywhere so Control Tower keeps managing the account.
EC2 is only denied at the instance launch level (RunInstances / StartInstances).
Describe calls, EBS, and VPC endpoint creation remain available — Glue and Athena don’t
need a VPC, but ec2:CreateVpcEndpoint to S3/Glue is still possible if you want it.
Lambda is intentionally allowed. CDK custom resources rely on it. Tighten with permission boundaries, not SCPs, if you need to lock Lambda down further.
Adding more deny statements
Use scpStatements on the FinOps account record. Additive only — you can’t override a
baseline Deny with an Allow.
sharedServices: { ouId: 'ou-xxxx', accounts: { finOps: { accountId: '123...', scpStatements: [ new iam.PolicyStatement({ effect: iam.Effect.DENY, actions: ['s3:PutBucketReplication'], resources: ['*'], }), ], }, },}shared_services=dlz.OrgOuSharedServices( ou_id="ou-xxxx", accounts=dlz.OrgOuSharedServicesAccounts( fin_ops=dlz.DLzFinOpsAccount( account_id="123...", scp_statements=[ iam.PolicyStatement( effect=iam.Effect.DENY, actions=["s3:PutBucketReplication"], resources=["*"], ), ], ), ),)To replace the default service deny list entirely (e.g. drop bedrock:* so the FinOps
account can call Bedrock for an internal cost-analysis assistant), use
ScpFinOpsAccountBaseline.statements({ deniedServices: [...] }). This only swaps the
compute / data services list; the network, IAM-identity, and org-integrity statements
still apply.