mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-10-07 13:48:56 +00:00
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>
This commit is contained in:
parent
d80295c005
commit
f38ed7357b
4 changed files with 106 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
|
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@e5691d8a7f96ac4d5a841a82975ff0a4354d53ac # v1.0.7
|
||||||
with:
|
with:
|
||||||
node-version: "24"
|
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:
|
secrets:
|
||||||
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
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 { SeahavenSiteHcptfStack } from "../lib/seahaven-site-hcptf-stack";
|
||||||
import { MemberBaselineStack } from "../lib/member-baseline-stack";
|
import { MemberBaselineStack } from "../lib/member-baseline-stack";
|
||||||
import { OrgGovernanceStack } from "../lib/org-governance-stack";
|
import { OrgGovernanceStack } from "../lib/org-governance-stack";
|
||||||
|
import { PlatformAccessStack } from "../lib/platform-access-stack";
|
||||||
|
|
||||||
const ACCOUNT = "328440206208";
|
const ACCOUNT = "328440206208";
|
||||||
const EXTERNAL_DEV_ACCOUNT = "396287094661";
|
const EXTERNAL_DEV_ACCOUNT = "396287094661";
|
||||||
|
|
@ -79,6 +80,12 @@ new OrgGovernanceStack(app, "org-governance", {
|
||||||
env: { account: ACCOUNT, region: "us-east-1" },
|
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", {
|
new MemberBaselineStack(app, "external-dev-baseline", {
|
||||||
stackName: "seahaven-external-dev-baseline",
|
stackName: "seahaven-external-dev-baseline",
|
||||||
env: { account: EXTERNAL_DEV_ACCOUNT, region: "us-east-1" },
|
env: { account: EXTERNAL_DEV_ACCOUNT, region: "us-east-1" },
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,40 @@ export class CisMonitoring extends Construct {
|
||||||
alarm.addAlarmAction(new cwactions.SnsAction(topic));
|
alarm.addAlarmAction(new cwactions.SnsAction(topic));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SEC-37. Not a CIS control. Counts every AssumeRole of
|
||||||
|
// OrganizationAccountAccessRole, including failures. Same topic as the
|
||||||
|
// CIS alarms. SCP exemptions for that role stay in place.
|
||||||
|
const orgAdminAssume = new logs.MetricFilter(
|
||||||
|
this,
|
||||||
|
"OrganizationAccountAccessRoleAssumeFilter",
|
||||||
|
{
|
||||||
|
logGroup,
|
||||||
|
filterPattern: logs.FilterPattern.literal(
|
||||||
|
'{ ($.eventName = "AssumeRole") && ($.requestParameters.roleArn = "*OrganizationAccountAccessRole") }',
|
||||||
|
),
|
||||||
|
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, including failed attempts",
|
||||||
|
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 });
|
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