diff --git a/.github/workflows/cdk_deploy.yml b/.github/workflows/cdk_deploy.yml new file mode 100644 index 0000000..be52fe1 --- /dev/null +++ b/.github/workflows/cdk_deploy.yml @@ -0,0 +1,57 @@ +on: + push: + branches: + - develop + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" + - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: "20" + - name: Install Python dependencies and CDK + run: | + # install your Python dependencies here + npm install -g aws-cdk + python -m pip install --use-feature=fast-deps --upgrade pip + pip install -r cdk/requirements.txt + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@master + with: + aws-access-key-id: ${{ secrets.SA_CDK_DEPLOYUSER_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.SA_CDK_DEPLOYUSER_AWS_SECRET_ACCESS_KEY }} + aws-region: "us-west-2" + - name: Set Environment Name + run: | + if [ "$GITHUB_REF" == "refs/heads/develop" ]; then + AWS_ENV="devA" + elif [ "$GITHUB_REF" == "refs/heads/main" ]; then + AWS_ENV="productionA" + else + echo "Skipping workflow for branch $GITHUB_REF" + exit 0 + fi + echo "Setting stack name to $AWS_ENV" + echo "AWS_ENV=$AWS_ENV" >> $GITHUB_ENV + - name: CDK synth + if: ${{ env.AWS_ENV == 'devA' || env.AWS_ENV == 'productionA' }} + run: npx cdk synth ${{ env.AWS_ENV }} --require-approval=never + - name: CDK diff + if: ${{ env.AWS_ENV == 'devA' || env.AWS_ENV == 'productionA' }} + run: npx cdk diff ${{ env.AWS_ENV }} --require-approval=never + - name: CDK cdk deploy + if: ${{ env.AWS_ENV == 'devA' || env.AWS_ENV == 'productionA' }} + run: npx cdk deploy ${{ env.AWS_ENV }} --require-approval=never --all diff --git a/.github/workflows/cdk_review.yml b/.github/workflows/cdk_review.yml new file mode 100644 index 0000000..09e2013 --- /dev/null +++ b/.github/workflows/cdk_review.yml @@ -0,0 +1,47 @@ +on: + pull_request: + branches: [develop] + paths: + - cdk/** + types: [opened, reopened, synchronize] + + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" + - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: "20" + + - name: Install Python dependencies and CDK + run: | + # install your Python dependencies here + npm install -g aws-cdk + python -m pip install --use-feature=fast-deps --upgrade pip + pip install -r cdk/requirements.txt + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@master + with: + aws-access-key-id: ${{ secrets.SA_CDK_DEPLOYUSER_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.SA_CDK_DEPLOYUSER_AWS_SECRET_ACCESS_KEY }} + aws-region: "us-east-2" + + - name: CDK synth + run: npx cdk synth --require-approval=never + + - name: CDK diff + run: npx cdk synth --require-approval=never diff --git a/README.md b/README.md index 8a936fa..143305e 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,25 @@ -# Welcome to your CDK TypeScript project +# wmaug-management-infrastructure -This is a blank project for CDK development with TypeScript. +TypeScript multi-stack AWS CDK app for managing the AWS Management account for the West Michigan AWS Users Group. -The `cdk.json` file tells the CDK Toolkit how to execute your app. -## Useful commands +## Stack information +- Sso + - Creates the AWS SSO resources for the WMAUG Management account. + - Creates and defines permissions for groups. + - Assigns groups to permission sets +- Scp + - Stack containing SCPs for the WMAUG org. + - Deny the creation of access keys + - Deny the deployment of resources in any region other than us-east-1 and us-east-2 -* `npm run build` compile typescript to js -* `npm run watch` watch for changes and compile -* `npm run test` perform the jest unit tests -* `npx cdk deploy` deploy this stack to your default AWS account/region -* `npx cdk diff` compare deployed stack with current state -* `npx cdk synth` emits the synthesized CloudFormation template +## Manually deploying the Sso stack +npx cdk deploy Sso --parameters instanceArnParam="arn:aws:sso:::instance/ssoins-123456789abcdefg" \ +--parameters wmaugManagementAccountNumberParam="123456789abcd" \ +--parameters wmaugModeratorAccountNumberParam="123456789abcd" \ +--parameters wmaugModeratorAdminGroupId="12345678-1234-1234-1234-abcdefghijkl" \ +--parameters wmaugFullAdminGroupId="12345678-1234-1234-1234-abcdefghijkl" +## Manually deploying the Scp stack +npx cdk deploy Scp -## Deploying and using this stack -AWS_DEFAULT_PROFILE=profile-name npx cdk deploy --parameters instanceArnParam="arn:aws:sso:::instance/ssoins-123456789abc" \ No newline at end of file diff --git a/bin/wmaug-management-infrastructure.ts b/bin/wmaug-management-infrastructure.ts index 91399cc..87413df 100644 --- a/bin/wmaug-management-infrastructure.ts +++ b/bin/wmaug-management-infrastructure.ts @@ -1,21 +1,14 @@ #!/usr/bin/env node import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; -import { WmaugManagementInfrastructureStack } from '../lib/wmaug-management-infrastructure-stack'; +import { Sso } from '../lib/wmaug-management-infrastructure-sso'; +import { Scp } from '../lib/wmaug-management-infrastructure-scp'; const app = new cdk.App(); -new WmaugManagementInfrastructureStack(app, 'WmaugManagementInfrastructureStack', { - /* If you don't specify 'env', this stack will be environment-agnostic. - * Account/Region-dependent features and context lookups will not work, - * but a single synthesized template can be deployed anywhere. */ +new Sso(app, 'Sso', { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-2' }, +}); - /* Uncomment the next line to specialize this stack for the AWS Account - * and Region that are implied by the current CLI configuration. */ - // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, - - /* Uncomment the next line if you know exactly what Account and Region you - * want to deploy the stack to. */ - // env: { account: '123456789012', region: 'us-east-1' }, - - /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +new Scp(app, 'Scp', { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-2' }, }); \ No newline at end of file diff --git a/lib/wmaug-management-infrastructure-scp.ts b/lib/wmaug-management-infrastructure-scp.ts new file mode 100644 index 0000000..8e38cdf --- /dev/null +++ b/lib/wmaug-management-infrastructure-scp.ts @@ -0,0 +1,56 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as orgs from 'aws-cdk-lib/aws-organizations'; + + +export class Scp extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + new orgs.CfnPolicy(this, 'denyIamAccessKeyCreation', { + name: 'denyIamAccessKeyCreation', + description: 'Deny IAM access key creation', + type: 'SERVICE_CONTROL_POLICY', + content:{ + "Version": "2012-10-17", + "Statement": { + "Effect": "Deny", + "Action": [ + "iam:CreateAccessKey", + "iam:CreateUser", + "iam:CreateLoginProfile", + "iam:UpdateLoginProfile", + "iam:DeleteAccessKey", + "iam:DeleteUser", + "iam:DeleteLoginProfile" + ], + "Resource": "*" + } + }, + }); + + // create SCP blocking access to all regions except us-east-1 and us-east-2 + new orgs.CfnPolicy(this, 'denyAllRegionsExceptUSEast', { + name: 'denyAllRegionsExceptUSEast', + description: 'Deny all regions except us-east-1 and us-east-2', + type: 'SERVICE_CONTROL_POLICY', + content:{ + "Version": "2012-10-17", + "Statement": { + "Effect": "Deny", + "Action": "*", + "Resource": "*", + "Condition": { + "StringNotEquals": { + "aws:RequestedRegion": [ + "us-east-1", + "us-east-2" + ] + } + } + } + }, + }); + + } +} diff --git a/lib/wmaug-management-infrastructure-sso.ts b/lib/wmaug-management-infrastructure-sso.ts new file mode 100644 index 0000000..d11b81d --- /dev/null +++ b/lib/wmaug-management-infrastructure-sso.ts @@ -0,0 +1,86 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as sso from 'aws-cdk-lib/aws-sso'; + +export class Sso extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const instanceArnParam = new cdk.CfnParameter(this, 'instanceArnParam', { + type: 'String', + description: 'The ARN of the SSO instance', + }); + + // start account number parameters + const wmaugManagementAccountNumberParam = new cdk.CfnParameter(this, 'wmaugManagementAccountNumberParam', { + type: 'String', + description: 'The account number of the WMAUG management account', + }); + + const wmaugModeratorAccountNumberParam = new cdk.CfnParameter(this, 'wmaugModeratorAccountNumberParam', { + type: 'String', + description: 'The account number of the WMAUG moderator account', + }); + + // start group GUID parameters + + const wmaugModeratorAdminGroupId = new cdk.CfnParameter(this, 'wmaugModeratorAdminGroupId', { + type: 'String', + description: 'The GUID of the wmaugModeratorAdmin SSO group', + }); + + const wmaugFullAdminGroupId = new cdk.CfnParameter(this, 'wmaugFullAdminGroupId', { + type: 'String', + description: 'The GUID of the wmaugFullAdmin SSO group', + }); + + // Start permission set policy creation + const wmaugModeratorAdminPermissionSet = new sso.CfnPermissionSet(this, 'wmaugModeratorAdminPermissionSet', { + // Use the value of the CFN parameter + instanceArn: instanceArnParam.valueAsString, + name: 'wmaugModeratorAdminPermissionSet', + description: 'Permission set WMAUG moderators and administrators will use', + managedPolicies: ['arn:aws:iam::aws:policy/AdministratorAccess'], + }); + + const wmaugFullAdminPermissionSet = new sso.CfnPermissionSet(this, 'wmaugFullAdminPermissionSet', { + // Use the value of the CFN parameter + instanceArn: instanceArnParam.valueAsString, + name: 'wmaugFullAdminPermissionSet', + description: 'Permission set WMAUG owners will use', + managedPolicies: ['arn:aws:iam::aws:policy/AdministratorAccess'], + }); + + // Assign moderator admin to moderator account + new sso.CfnAssignment(this, 'wmaugModeratorAdminModeratorAssignment', { + instanceArn: instanceArnParam.valueAsString, + permissionSetArn: wmaugModeratorAdminPermissionSet.attrPermissionSetArn, + principalType: 'GROUP', + principalId: wmaugModeratorAdminGroupId.valueAsString, + targetId: wmaugModeratorAccountNumberParam.valueAsString, + targetType: 'AWS_ACCOUNT', + }); + + // Assign full admin to management account + new sso.CfnAssignment(this, 'wmaugFullAdminManagementAssignment', { + instanceArn: instanceArnParam.valueAsString, + permissionSetArn: wmaugFullAdminPermissionSet.attrPermissionSetArn, + principalType: 'GROUP', + principalId: wmaugFullAdminGroupId.valueAsString, + targetId: wmaugManagementAccountNumberParam.valueAsString, + targetType: 'AWS_ACCOUNT', + }); + + // Assign full admin to moderator account + new sso.CfnAssignment(this, 'wmaugFullAdminModeratorAssignment', { + instanceArn: instanceArnParam.valueAsString, + permissionSetArn: wmaugFullAdminPermissionSet.attrPermissionSetArn, + principalType: 'GROUP', + principalId: wmaugFullAdminGroupId.valueAsString, + targetId: wmaugModeratorAccountNumberParam.valueAsString, + targetType: 'AWS_ACCOUNT', + }); + } + + +} \ No newline at end of file diff --git a/lib/wmaug-management-infrastructure-stack.ts b/lib/wmaug-management-infrastructure-stack.ts deleted file mode 100644 index 0167e67..0000000 --- a/lib/wmaug-management-infrastructure-stack.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as sso from 'aws-cdk-lib/aws-sso'; - -export class WmaugManagementInfrastructureStack extends cdk.Stack { - constructor(scope: Construct, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - // Define the CFN parameter - const instanceArnParam = new cdk.CfnParameter(this, 'instanceArnParam', { - type: 'String', - description: 'The ARN of the SSO instance', - }); - - new sso.CfnPermissionSet(this, 'adminPermissionSet', { - // Use the value of the CFN parameter - instanceArn: instanceArnParam.valueAsString, - name: 'adminPermissionSet', - description: 'Permission set for admin', - managedPolicies: ['arn:aws:iam::aws:policy/AdministratorAccess'], - }); - } -} \ No newline at end of file diff --git a/test/wmaug-management-infrastructure.test.ts b/test/wmaug-management-infrastructure.test.ts index 6205d0a..ac8320e 100644 --- a/test/wmaug-management-infrastructure.test.ts +++ b/test/wmaug-management-infrastructure.test.ts @@ -3,7 +3,7 @@ // import * as WmaugManagementInfrastructure from '../lib/wmaug-management-infrastructure-stack'; // example test. To run these tests, uncomment this file along with the -// example resource in lib/wmaug-management-infrastructure-stack.ts +// example resource in lib/wmaug-management-infrastructure-sso.ts test('SQS Queue Created', () => { // const app = new cdk.App(); // // WHEN