mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 08:03:19 +00:00
feat(iam): add platform permission set and org-admin assume alarm (SEC-37) (#161)
* feat(iam): add platform permission set and org-admin assume alarm (SEC-37) Adds an Identity Center platform group and Platform permission set assigned to the management account, and a CloudTrail alarm on AssumeRole of OrganizationAccountAccessRole. Scripts still assume that role. This change is not deployed. Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com> * fix(iam): skip sanctioned org-admin assumes in the alarm (SEC-37) Count failed assumes for every principal. Do not page on a successful assume by the Platform permission set or the two repo script sessions. Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
This commit is contained in:
parent
8cbc98d927
commit
ff492de58d
4 changed files with 119 additions and 1 deletions
2
.github/workflows/deploy.yaml
vendored
2
.github/workflows/deploy.yaml
vendored
|
|
@ -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"
|
||||
stacks: "account-baseline dynamodb-cmk regional-baseline-us-west-2 regional-baseline-us-east-2 backup-offsite backup org-governance platform-access"
|
||||
secrets:
|
||||
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { AppWebAclStack } from "../lib/app-web-acl-stack";
|
|||
import { SeahavenSiteHcptfStack } from "../lib/seahaven-site-hcptf-stack";
|
||||
import { MemberBaselineStack } from "../lib/member-baseline-stack";
|
||||
import { OrgGovernanceStack } from "../lib/org-governance-stack";
|
||||
import { PlatformAccessStack } from "../lib/platform-access-stack";
|
||||
|
||||
const ACCOUNT = "328440206208";
|
||||
const EXTERNAL_DEV_ACCOUNT = "396287094661";
|
||||
|
|
@ -79,6 +80,12 @@ new OrgGovernanceStack(app, "org-governance", {
|
|||
env: { account: ACCOUNT, region: "us-east-1" },
|
||||
});
|
||||
|
||||
new PlatformAccessStack(app, "platform-access", {
|
||||
stackName: "seahaven-platform-access",
|
||||
// Same management-account region as org-governance above.
|
||||
env: { account: ACCOUNT, region: "us-east-1" }, // pragma: allowlist secret
|
||||
});
|
||||
|
||||
new MemberBaselineStack(app, "external-dev-baseline", {
|
||||
stackName: "seahaven-external-dev-baseline",
|
||||
env: { account: EXTERNAL_DEV_ACCOUNT, region: "us-east-1" },
|
||||
|
|
|
|||
|
|
@ -264,6 +264,53 @@ export class CisMonitoring extends Construct {
|
|||
alarm.addAlarmAction(new cwactions.SnsAction(topic));
|
||||
}
|
||||
|
||||
// SEC-37. Not a CIS control. Same topic as the CIS alarms.
|
||||
// SCP exemptions for OrganizationAccountAccessRole stay in place.
|
||||
//
|
||||
// A 1/1 alarm on every assume would page for each Platform operator
|
||||
// session and each run of the bootstrap and substrate scripts. CIS 4.1
|
||||
// treats that single-event paging on a routine path as alert fatigue.
|
||||
// Successful assumes are excluded only for those callers:
|
||||
// * the Platform permission set (AWSReservedSSO_Platform_*)
|
||||
// * session plat-145-hcptf-bootstrap (create-hcptf-bootstrap-roles.sh)
|
||||
// * session plat-147-tf-substrate (delete-terraform-substrate-prod-dev.sh)
|
||||
// Any errorCode still counts, including a failed call from those callers.
|
||||
//
|
||||
// Residual: roleSessionName is chosen by the caller, so a successful
|
||||
// assume that copies one of those two session names is not counted.
|
||||
// The scripts stay on OrganizationAccountAccessRole until the permission
|
||||
// set is deployed and a real sign-in has assumed the member role.
|
||||
const orgAdminAssume = new logs.MetricFilter(
|
||||
this,
|
||||
"OrganizationAccountAccessRoleAssumeFilter",
|
||||
{
|
||||
logGroup,
|
||||
filterPattern: logs.FilterPattern.literal(
|
||||
'{ ($.eventName = "AssumeRole") && ($.requestParameters.roleArn = "*OrganizationAccountAccessRole") && (($.errorCode = "*") || (($.userIdentity.arn != "*AWSReservedSSO_Platform_*") && ($.requestParameters.roleSessionName != "plat-145-hcptf-bootstrap") && ($.requestParameters.roleSessionName != "plat-147-tf-substrate"))) }',
|
||||
),
|
||||
metricNamespace: "SeahavenSecurity",
|
||||
metricName: "OrganizationAccountAccessRoleAssume",
|
||||
metricValue: "1",
|
||||
defaultValue: 0,
|
||||
},
|
||||
);
|
||||
const orgAdminAlarm = orgAdminAssume
|
||||
.metric({
|
||||
statistic: "Sum",
|
||||
period: cdk.Duration.minutes(5),
|
||||
})
|
||||
.createAlarm(this, "OrganizationAccountAccessRoleAssumeAlarm", {
|
||||
alarmName: "organization-account-access-role-assume",
|
||||
alarmDescription:
|
||||
"AssumeRole of OrganizationAccountAccessRole outside the Platform permission set and the two repo script sessions. Failed attempts count for every principal.",
|
||||
threshold: 1,
|
||||
comparisonOperator:
|
||||
cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
||||
evaluationPeriods: 1,
|
||||
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
|
||||
});
|
||||
orgAdminAlarm.addAlarmAction(new cwactions.SnsAction(topic));
|
||||
|
||||
new cdk.CfnOutput(this, "CisAlarmTopicArn", { value: topic.topicArn });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
64
lib/platform-access-stack.ts
Normal file
64
lib/platform-access-stack.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
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 MANAGEMENT_ACCOUNT_ID = "328440206208";
|
||||
|
||||
/**
|
||||
* Identity Center group and permission set for platform operators (SEC-37).
|
||||
*
|
||||
* Assigned only to the management account. ReadOnlyAccess plus
|
||||
* sts:AssumeRole on OrganizationAccountAccessRole, so the existing
|
||||
* bootstrap and teardown scripts keep working after a person uses this
|
||||
* set. Those scripts still assume OrganizationAccountAccessRole directly.
|
||||
* Retarget them only after this set is deployed and a real sign-in has
|
||||
* assumed the member role.
|
||||
*
|
||||
* This is not an SCP exemption. /platform/ path denies exempt the
|
||||
* reserved SSO role name AWSReservedSSO_Platform_* once the set exists.
|
||||
*/
|
||||
export class PlatformAccessStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const group = new identitystore.CfnGroup(this, "PlatformGroup", {
|
||||
identityStoreId: IDENTITY_STORE_ID,
|
||||
displayName: "platform",
|
||||
description:
|
||||
"Platform operators. Management account only. Assumes OrganizationAccountAccessRole for bootstrap.",
|
||||
});
|
||||
|
||||
const permissionSet = new sso.CfnPermissionSet(this, "PlatformPermissionSet", {
|
||||
instanceArn: IDENTITY_CENTER_INSTANCE_ARN,
|
||||
name: "Platform",
|
||||
description:
|
||||
"Read-only in the management account, plus assume OrganizationAccountAccessRole.",
|
||||
sessionDuration: "PT8H",
|
||||
managedPolicies: ["arn:aws:iam::aws:policy/ReadOnlyAccess"],
|
||||
inlinePolicy: {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Sid: "AssumeOrganizationAccountAccessRole",
|
||||
Effect: "Allow",
|
||||
Action: "sts:AssumeRole",
|
||||
Resource: "arn:aws:iam::*:role/OrganizationAccountAccessRole",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
new sso.CfnAssignment(this, "PlatformManagementAssignment", {
|
||||
instanceArn: IDENTITY_CENTER_INSTANCE_ARN,
|
||||
permissionSetArn: permissionSet.attrPermissionSetArn,
|
||||
principalId: group.attrGroupId,
|
||||
principalType: "GROUP",
|
||||
targetId: MANAGEMENT_ACCOUNT_ID,
|
||||
targetType: "AWS_ACCOUNT",
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue