Skip to content

Commit

Permalink
Merge pull request #38 from dannysteenman/feat/refactor-oidc
Browse files Browse the repository at this point in the history
feat(stacks): refactor project structure and enhance resource naming
  • Loading branch information
dannysteenman authored Jan 14, 2025
2 parents 546109b + eb08efb commit 4b0c205
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 144 deletions.
10 changes: 5 additions & 5 deletions src/bin/env-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ export function extractCleanedBranchName(gitBranchRef: string | undefined): stri
}

/**
* Generates a unique resource name that includes a branch suffix for branch-based deployments,
* or an environment suffix when no branch is specified.
* The generated name has a maximum length of 64 characters.
* Creates an environment-aware resource name with a branch or environment suffix.
* The name is truncated to a maximum of 64 characters to comply with AWS naming constraints.
*
* @param baseName - The base name for the resource.
* @returns A unique resource name with a branch or environment suffix.
* @returns A resource name with an environment or branch suffix.
* @throws Error if GIT_BRANCH_REF is "main".
*/
export function generateUniqueResourceName(baseName: string): string {
export function createEnvResourceName(baseName: string): string {
const branchName = process.env.GIT_BRANCH_REF;
const environment = process.env.ENVIRONMENT || 'dev';

Expand Down
6 changes: 3 additions & 3 deletions src/constructs/base-construct.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { extractCleanedBranchName, generateUniqueResourceName } from '../bin/env-helper';
import { createEnvResourceName, extractCleanedBranchName } from '../bin/env-helper';

export class BaseConstruct extends Construct {
/**
Expand Down Expand Up @@ -32,8 +32,8 @@ export class BaseConstruct extends Construct {
super(scope, id);

this.branch = extractCleanedBranchName(process.env.GIT_BRANCH_REF);
this.environment = process.env.ENVIRONMENT || 'dev';
this.account = cdk.Stack.of(this).account;
this.environment = process.env.ENVIRONMENT || 'dev';
this.region = cdk.Stack.of(this).region;
}

Expand All @@ -44,6 +44,6 @@ export class BaseConstruct extends Construct {
* @returns A unique resource name with an optional branch suffix.
*/
unique(name: string): string {
return generateUniqueResourceName(name);
return createEnvResourceName(name);
}
}
14 changes: 9 additions & 5 deletions src/constructs/network-construct.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CfnOutput, RemovalPolicy } from 'aws-cdk-lib';
import { RemovalPolicy } from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as s3 from 'aws-cdk-lib/aws-s3';
import type { Construct } from 'constructs';
Expand Down Expand Up @@ -31,6 +31,14 @@ export class NetworkConstruct extends BaseConstruct {
),
natGateways: this.environment === 'production' ? 3 : 1,
maxAzs: 3,
gatewayEndpoints: {
s3Endpoint: {
service: ec2.GatewayVpcEndpointAwsService.S3,
},
dynamoDBEndpoint: {
service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
},
},
flowLogs: {
s3: {
destination: ec2.FlowLogDestination.toS3(
Expand Down Expand Up @@ -63,9 +71,5 @@ export class NetworkConstruct extends BaseConstruct {
},
],
});

new CfnOutput(this, 'VpcId', {
value: this.vpc.vpcId,
});
}
}
39 changes: 17 additions & 22 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as cdk from 'aws-cdk-lib';
import { generateUniqueResourceName } from './bin/env-helper';
import { BaseStack, GitHubOIDCStack, ToolkitCleanerStack } from './stacks';
import { createEnvResourceName } from './bin/env-helper';
import { FoundationStack, StarterStack } from './stacks';

// Inherit environment variables from npm run commands (displayed in .projen/tasks.json)
const environment = process.env.ENVIRONMENT || 'dev';
const awsEnvironment = {
const awsAccountConfig = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};
Expand All @@ -13,32 +13,27 @@ const awsEnvironment = {
const app = new cdk.App();

/**
* These stacks should only be deployed a single time for each AWS account,
* and never deployed via branch-based deployments.
* Conditional deployment of the FoundationStack.
*
* This section of code is responsible for deploying the FoundationStack, which sets up
* crucial infrastructure components like GitHub OpenID Connect (OIDC) support and IAM roles
* for GitHub Actions deployments.
*
* @remarks
* - This stack should be deployed only once per AWS account.
* - It should not be part of branch-based deployments.
* - The deployment is conditional, based on the absence of the GIT_BRANCH_REF environment variable.
*/
if (!process.env.GIT_BRANCH_REF) {
/**
* The ToolkitCleaner construct creates a state machine that runs every day
* and removes unused S3 and ECR assets from your CDK Toolkit.
* The state machine outputs the number of deleted assets and the total reclaimed size in bytes.
*/
new ToolkitCleanerStack(app, generateUniqueResourceName('ToolkitCleanerStack'), {
env: awsEnvironment,
});
/**
* Add GitHub OpenID Connect support and create an IAM role for GitHub Actions deployments.
* This stack is only created once per AWS environment, as the GitHub OIDC provider and
* deployment role can be reused across all deployments for that environment.
*/
new GitHubOIDCStack(app, generateUniqueResourceName('GitHubOIDCStack'), {
env: awsEnvironment,
new FoundationStack(app, createEnvResourceName('FoundationStack'), {
env: awsAccountConfig,
environment: environment,
});
}

// Create a new stack with your resources
new BaseStack(app, generateUniqueResourceName('BaseStack'), {
env: awsEnvironment,
new StarterStack(app, createEnvResourceName('StarterStack'), {
env: awsAccountConfig,
environment: environment,
});

Expand Down
74 changes: 53 additions & 21 deletions src/stacks/README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,101 @@
# AWS CDK Stacks: BaseStack and GitHubOIDCStack
# AWS CDK Stacks: StarterStack and FoundationStack

This documentation details the structure and functionality of two pivotal stacks within our AWS CDK TypeScript project: `BaseStack` and `GitHubOIDCStack`. These stacks lay the groundwork for deploying AWS resources with specific configurations and capabilities, tailored to different deployment stages and integration with GitHub Actions for CI/CD processes.
This documentation details the structure and functionality of three pivotal stacks within our AWS CDK TypeScript project: `StarterStack` and `FoundationStack`. These stacks lay the groundwork for deploying AWS resources with specific configurations and capabilities, tailored to different deployment stages and integration with GitHub Actions for CI/CD processes.

## BaseStack
## StarterStack

The `BaseStack` serves as a foundational stack that you can use to start instantiation your custom and cdk-lib constructs.
The `StarterStack` serves as a foundation for building your AWS infrastructure using CDK. It provides a starting point for adding and organizing your AWS resources and constructs.

### Properties

- `environment`: Optional. Specifies the deployment stage (e.g., `dev`, `test`, `staging`, `production`). It's crucial for tailoring the stack configuration to the target environment.
- `environment`: Optional. Specifies the deployment stage (e.g., `dev`, `test`, `staging`, `production`). If not provided, an error will be thrown.

### Features

- Includes a `NetworkConstruct` that creates a secure VPC.
- Designed to be easily extendable with additional AWS resources and constructs.

### Usage

The `StarterStack` is intended to be customized and extended based on your specific infrastructure needs. You can add new constructs and resources within the constructor of this class.

### Example Usage

```typescript
import * as cdk from 'aws-cdk-lib';
import { BaseStack } from './stacks';
import { StarterStack } from './stacks';

// Inherit environment variables from npm run commands (displayed in .projen/tasks.json)
const environment = process.env.ENVIRONMENT || 'dev';
const awsEnvironment = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};

// Instantiate the CDK app
const app = new cdk.App();

// Create a new stack with your resources
new BaseStack(app, `BaseStack-${environment}`, {
new StarterStack(app, `StarterStack-${environment}`, {
env: awsEnvironment,
environment: environment,
});
```

## GitHubOIDCStack
### Extending the Stack

The GitHubOIDCStack is designed to facilitate secure CI/CD workflows by integrating AWS resources with GitHub Actions via OpenID Connect (OIDC). This allows for a more secure and streamlined deployment process directly from GitHub Actions.
To add new resources to the `StarterStack`, you can modify the constructor. For example, to add a new S3 bucket:

```typescript
import * as s3 from 'aws-cdk-lib/aws-s3';

// In the constructor:
new s3.Bucket(this, 'MyBucket', {
bucketName: 'my-unique-bucket-name',
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
});
```

### Best Practices

- Organize your constructs logically within this stack.
- For complex infrastructures, consider creating additional stacks for different components.
- Remember to import necessary modules at the top of the file.
- Customize and extend the stack based on your specific infrastructure requirements.

The `StarterStack` provides a flexible foundation for your AWS CDK project, allowing you to incrementally build and organize your infrastructure as code.


## FoundationStack

The `FoundationStack` sets up fundamental infrastructure components for AWS deployments via GitHub Actions. It combines the functionality of the GitHubOIDCStack with additional features.

### Features

- GitHub Actions OIDC Provider: Sets up the OIDC provider for GitHub Actions within the AWS account, enabling trust relationships between GitHub and AWS.
- GitHub Deploy Role: Creates an IAM role with AdministratorAccess managed policy. This role is assumable by GitHub Actions workflows, granting them the permissions needed to deploy resources.
- GitHub Actions OIDC Provider: Sets up the OIDC provider for GitHub Actions within the AWS account.
- GitHub Deploy Role: Creates an IAM role with AdministratorAccess managed policy, assumable by GitHub Actions workflows.
- CDK Toolkit Cleaner: Implements a ToolkitCleaner for managing CDK toolkit resources.

### Configuration
### Properties

The stack automatically retrieves GitHub repository details (owner and repository name) using a helper function, ensuring that the OIDC provider and IAM roles are correctly configured for the specific GitHub repository.
- `environment`: Required. Specifies the deployment stage (e.g., `dev`, `test`, `staging`, `production`).

### Example Usage

```typescript
import * as cdk from 'aws-cdk-lib';
import { GitHubOIDCStack } from './stacks';
import { FoundationStack } from './stacks';

// Inherit environment variables from npm run commands (displayed in .projen/tasks.json)
const environment = process.env.ENVIRONMENT || 'dev';
const awsEnvironment = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};

// Instantiate the CDK app
const app = new cdk.App();

// Add GitHub OpenID Connect support and create an IAM role for GitHub
new GitHubOIDCStack(app, `GitHubOIDCStack-${environment}`, {
new FoundationStack(app, `FoundationStack-${environment}`, {
env: awsEnvironment,
environment: environment,
});
```

The `FoundationStack` provides a more comprehensive setup for GitHub Actions integration and CDK resource management, making it suitable for projects that require both OIDC-based deployments and CDK toolkit cleaning.
22 changes: 0 additions & 22 deletions src/stacks/base-stack.ts

This file was deleted.

72 changes: 72 additions & 0 deletions src/stacks/foundation-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import { ToolkitCleaner } from 'cloudstructs/lib/toolkit-cleaner';
import type { Construct } from 'constructs';
import { getGitRepositoryDetails } from '../bin/git-helper';

export interface FoundationStackProps extends cdk.StackProps {
/**
* Determine the stage to which you want to deploy the stack
*
* @default - If not given, it will throw out an error
*/
readonly environment: string;
}

/**
* FoundationStack
*
* This stack sets up fundamental infrastructure components for AWS deployments via GitHub Actions.
* It includes the creation of an OpenID Connect (OIDC) provider for GitHub and an IAM role for
* GitHub Actions deployments.
*
* @extends cdk.Stack
*
* @remarks
* - Creates a GitHub OIDC provider
* - Sets up an IAM role for GitHub Actions with necessary permissions
* - Implements a ToolkitCleaner for managing CDK toolkit resources
*
* @param scope - The scope in which to define this construct
* @param id - The scoped construct ID
* @param props - Stack properties
*/
export class FoundationStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: FoundationStackProps) {
super(scope, id, props);

////////////////////////////////
// Setup GitHub OIDC support //
//////////////////////////////
const { gitOwner, gitRepoName } = getGitRepositoryDetails();

const githubDomain = 'token.actions.githubusercontent.com';

const openIdConnectProvider = new iam.OpenIdConnectProvider(this, 'GithubProvider', {
url: `https://${githubDomain}`,
clientIds: ['sts.amazonaws.com'],
});

const conditions: iam.Conditions = {
StringLike: {
[`${githubDomain}:sub`]: `repo:${gitOwner}/${gitRepoName}:environment:${props.environment}`,
},
StringEquals: {
[`${githubDomain}:aud`]: 'sts.amazonaws.com',
},
};

new iam.Role(this, 'GitHubActionsServiceRole', {
assumedBy: new iam.WebIdentityPrincipal(openIdConnectProvider.openIdConnectProviderArn, conditions),
description: 'This role is used via GitHub Actions to deploy with AWS CDK or Terraform on the target AWS account',
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')],
maxSessionDuration: cdk.Duration.hours(2),
roleName: process.env.GITHUB_DEPLOY_ROLE ?? 'GitHubActionsServiceRole',
});

////////////////////////////////
// Setup CDK Toolkit Cleaner //
//////////////////////////////
new ToolkitCleaner(this, 'ToolkitCleaner');
}
}
Loading

0 comments on commit 4b0c205

Please sign in to comment.