Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,9 @@ jobs:
- name: CDK Synth
run: cdk synth
working-directory: ./labs/unicorn-store/infrastructure/cdk/
- name: Build CDK Immersion Day infrastructure
run: mvn clean package
working-directory: ./infrastructure/cdk/
- name: CDK Synth Immersion Day infrastructure
run: cdk synth
working-directory: ./infrastructure/cdk/
14 changes: 14 additions & 0 deletions infrastructure/cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.classpath.txt
target
.classpath
.project
.idea
.settings
.vscode
*.iml

# CDK asset staging directory
.cdk.staging
cdk.out
cdk.context.json

19 changes: 19 additions & 0 deletions infrastructure/cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Welcome to the Unicorn Store CDK Java project!

This project is used to deploy the needed infrastructure resources to run the Unicorn Store.
Please follow the workshop instructions on how to properly configure the environment.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

It is a [Maven](https://maven.apache.org/) based project, so you can open this project with any Maven compatible Java IDE to build and run tests.

## Useful commands

* `mvn package` compile and run tests
* `cdk ls` list all stacks in the app
* `cdk synth` emits the synthesized CloudFormation template
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk docs` open CDK documentation

Enjoy!
27 changes: 27 additions & 0 deletions infrastructure/cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"app": "mvn -e -q compile exec:java",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"target",
"pom.xml",
"src/test"
]
},
"context": {
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
"aws-cdk:enableDiffNoFail": true,
"@aws-cdk/core:stackRelativeExports": true,
"@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
"@aws-cdk/aws-kms:defaultKeyPolicies": true,
"@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true,
"@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
"@aws-cdk/aws-efs:defaultEncryptionAtRest": true,
"@aws-cdk/aws-lambda:recognizeVersionProps": true,
"@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true
}
}
74 changes: 74 additions & 0 deletions infrastructure/cdk/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.amazon.aws.example</groupId>
<artifactId>unicorn-cdk-setup</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>AWS CDK Stack to setup the workshop infrastructure</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<cdk.version>2.173.2</cdk.version>
<junit.version>5.11.4</junit.version>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<mainClass>com.unicorn.UnicornStoreApp</mainClass>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<!-- AWS Cloud Development Kit -->
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>aws-cdk-lib</artifactId>
<version>${cdk.version}</version>
</dependency>

<!-- <dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>apprunner-alpha</artifactId>
<version>2.173.2-alpha.0</version>
</dependency> -->

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>

<dependency>
<groupId>io.github.cdklabs</groupId>
<artifactId>cdknag</artifactId>
<version>2.34.23</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
87 changes: 87 additions & 0 deletions infrastructure/cdk/src/main/java/com/unicorn/UnicornStoreApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.unicorn;

import java.util.List;

// import com.unicorn.alternatives.UnicornAuditService;
// import com.unicorn.alternatives.UnicornStoreMicronaut;
// import com.unicorn.alternatives.UnicornStoreQuarkus;
// import com.unicorn.alternatives.UnicornStoreSpringGraalVM;
// import com.unicorn.core.InfrastructureStack;
import io.github.cdklabs.cdknag.AwsSolutionsChecks;
import io.github.cdklabs.cdknag.NagPackSuppression;
import io.github.cdklabs.cdknag.NagSuppressions;
import software.amazon.awscdk.App;
import software.amazon.awscdk.Aspects;
// import software.amazon.awscdk.StackProps;

public class UnicornStoreApp {

public static void main(final String[] args) {
App app = new App();

var unicornStoreStack = new UnicornStoreStack(app, "unicornstore-stack");

// var infrastructureStack = new InfrastructureStack(app, "UnicornStoreInfrastructure", StackProps.builder()
// .build());

// var unicornStoreSpring = new UnicornStoreStack(app, "UnicornStoreSpringApp", StackProps.builder()
// .build(), infrastructureStack);

// var unicornStoreMicronaut = new UnicornStoreMicronaut(app, "UnicornStoreMicronautApp", StackProps.builder()
// .build(), infrastructureStack);

// var unicornStoreSpringGraalVM = new UnicornStoreSpringGraalVM(app, "UnicornStoreSpringGraalVMApp", StackProps.builder()
// .build(), infrastructureStack);

// var unicornStoreQuarkus = new UnicornStoreQuarkus(app, "UnicornStoreQuarkusApp", StackProps.builder()
// .build(), infrastructureStack);

// var unicornAuditService = new UnicornAuditService(app, "UnicornAuditServiceApp", StackProps.builder()
// .build(), infrastructureStack);


//Add CDK-NAG checks: https://github.com/cdklabs/cdk-nag
//Add suppression to exclude certain findings that are not needed for Workshop environment
Aspects.of(app).add(new AwsSolutionsChecks());
var suppression = List.of(
new NagPackSuppression.Builder().id("AwsSolutions-APIG4").reason("The workshop environment does not require API-Gateway authorization").build(),
new NagPackSuppression.Builder().id("AwsSolutions-COG4").reason("The workshop environment does not require Cognito User Pool authorization").build(),
new NagPackSuppression.Builder().id("AwsSolutions-RDS3").reason("Workshop environment does not need a Multi-AZ setup to reduce cost").build(),
new NagPackSuppression.Builder().id("AwsSolutions-IAM4").reason("AWS Managed policies are acceptable for the workshop").build(),
new NagPackSuppression.Builder().id("AwsSolutions-IAM5").reason("A wildcard is acceptable for this workshop to allow parallel creation of resources").build(),
new NagPackSuppression.Builder().id("AwsSolutions-RDS10").reason("Workshop environment is ephemeral and the database should be deleted by the end of the workshop").build(),
new NagPackSuppression.Builder().id("AwsSolutions-RDS11").reason("Database is in a private subnet and can use the default port").build(),
new NagPackSuppression.Builder().id("AwsSolutions-APIG2").reason("API Gateway request validation is not needed for workshop").build(),
new NagPackSuppression.Builder().id("AwsSolutions-APIG1").reason("API Gateway access logging not needed for workshop setup").build(),
new NagPackSuppression.Builder().id("AwsSolutions-APIG6").reason("API Gateway access logging not needed for workshop setup").build(),
new NagPackSuppression.Builder().id("AwsSolutions-VPC7").reason("Workshop environment does not need VPC flow logs").build(),
new NagPackSuppression.Builder().id("AwsSolutions-SMG4").reason("Ephemeral workshop environment does not need to rotate secrets").build(),
new NagPackSuppression.Builder().id("AwsSolutions-RDS2").reason("Workshop non-sensitive test database does not need encryption at rest").build(),
new NagPackSuppression.Builder().id("AwsSolutions-APIG3").reason("Workshop API Gateways do not need AWS WAF assigned").build(),
new NagPackSuppression.Builder().id("AwsSolutions-EC23").reason("Not needed").build(),
new NagPackSuppression.Builder().id("AwsSolutions-RDS13").reason("Workshop Database does not need backups").build(),
new NagPackSuppression.Builder().id("AwsSolutions-S1").reason("Workshop S3 bucket does not need Access Logs").build(),
new NagPackSuppression.Builder().id("AwsSolutions-L1").reason("Workshop environment use CDK default Lambdas" ).build(),
new NagPackSuppression.Builder().id("AwsSolutions-RDS6").reason("Workshop environment uses user/password authentication").build(),
new NagPackSuppression.Builder().id("AwsSolutions-EC28").reason("Workshop instance doesn't need autoscaling").build(),
new NagPackSuppression.Builder().id("AwsSolutions-EC29").reason("Workshop instance doesn't need autoscaling").build(),
new NagPackSuppression.Builder().id("AwsSolutions-CFR1").reason("Workshop environment should be accessible from any Geo").build(),
new NagPackSuppression.Builder().id("AwsSolutions-CFR2").reason("Ephemeral workshop environment does not need WAF").build(),
new NagPackSuppression.Builder().id("AwsSolutions-CFR3").reason("Ephemeral workshop environment does not need logging").build(),
new NagPackSuppression.Builder().id("AwsSolutions-CFR4").reason("Workshop instance uses http").build(),
new NagPackSuppression.Builder().id("AwsSolutions-CFR5").reason("Workshop instance uses http").build(),
new NagPackSuppression.Builder().id("AwsSolutions-EKS1").reason("Workshop non-sensitive EKS cluster uses public access" ).build(),
new NagPackSuppression.Builder().id("CdkNagValidationFailure").reason("Suppress warnings see: https://github.com/cdklabs/cdk-nag/issues/817").build()
);

NagSuppressions.addStackSuppressions(unicornStoreStack, suppression);
// NagSuppressions.addStackSuppressions(infrastructureStack, suppression);
// NagSuppressions.addStackSuppressions(unicornStoreSpring, suppression);
// NagSuppressions.addStackSuppressions(unicornStoreMicronaut, suppression);
// NagSuppressions.addStackSuppressions(unicornStoreSpringGraalVM, suppression);
// NagSuppressions.addStackSuppressions(unicornStoreQuarkus, suppression);
// NagSuppressions.addStackSuppressions(unicornAuditService, suppression);

app.synth();
}
}
133 changes: 133 additions & 0 deletions infrastructure/cdk/src/main/java/com/unicorn/UnicornStoreStack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.unicorn;

import com.unicorn.constructs.InfrastructureCore;
import com.unicorn.constructs.InfrastructureImmDay;
import com.unicorn.constructs.DatabaseSetup;
import com.unicorn.constructs.WorkshopIde;
import com.unicorn.constructs.EksCluster;
import com.unicorn.constructs.UnicornStoreLambda;
import software.amazon.awscdk.CfnParameter;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.ec2.Port;
import software.amazon.awscdk.services.ec2.SecurityGroup;
import software.amazon.awscdk.services.ec2.SecurityGroupProps;
import software.constructs.Construct;

import software.amazon.awscdk.DefaultStackSynthesizer;
import software.amazon.awscdk.DefaultStackSynthesizerProps;

public class UnicornStoreStack extends Stack {

private final InfrastructureCore infrastructureCore;

public UnicornStoreStack(final Construct scope, final String id) {
// super(scope, id, props);
super(scope, id, StackProps.builder()
.synthesizer(new DefaultStackSynthesizer(DefaultStackSynthesizerProps.builder()
.generateBootstrapVersionRule(false) // This disables the bootstrap version parameter
.build()))
.build());

// Create Core infrastructure
this.infrastructureCore = new InfrastructureCore(this, "InfrastructureCore");
var accountId = Stack.of(this).getAccount();

// Execute Database setup
var databaseSetup = new DatabaseSetup(this, "UnicornDatabaseSetup", infrastructureCore);
databaseSetup.getNode().addDependency(infrastructureCore.getDatabase());

// Create security group for IDE to talk to EKS Cluster
var eksIdeSecurityGroup = new SecurityGroup(this, "IdeEksSecurityGroup",
SecurityGroupProps
.builder()
.securityGroupName("unicornstore-ide-eks-sg")
.vpc(infrastructureCore.getVpc())
.allowAllOutbound(false)
.build());
// Add ingress rule to allow all traffic from within the same security group
eksIdeSecurityGroup.getConnections().allowInternally(
Port.allTraffic(),
"Allow all internal traffic"
);

// Create Workshop IDE
var workshopIde = new WorkshopIde(this, "WorkshopIde", "unicornstore-ide", infrastructureCore, eksIdeSecurityGroup);
var ideRole = workshopIde.getIdeRole();

// Create UnicornStoreLambda
new UnicornStoreLambda(this, "UnicornStoreLambda", infrastructureCore);

// Create Immersion Day additional infrastructure
new InfrastructureImmDay(this, "InfrastructureImmDay", infrastructureCore);

// Create EKS cluster for the workshop
var unicornStoreEksCluster = new EksCluster(this, "UnicornStoreEksCluster", "unicorn-store", "1.31",
infrastructureCore.getVpc(), eksIdeSecurityGroup);
unicornStoreEksCluster.createAccessEntry(ideRole.getRoleArn());
var isWorkshopStudioAccount = CfnParameter.Builder.create(this, "IsWorkshopStudioAccount")
.type("String")
.defaultValue("no")
.build();
if ("yes".equals(isWorkshopStudioAccount.getValueAsString())) {
unicornStoreEksCluster.createAccessEntry("arn:aws:iam::" + accountId + ":role/WSParticipantRole");
}


// var eventBridge = infrastructureConstruct.getEventBridge();

// //Create Spring Lambda function
// var unicornStoreSpringLambda = createUnicornLambdaFunction();

// //Permission for Spring Boot Lambda Function
// eventBridge.grantPutEventsTo(unicornStoreSpringLambda);

// //Setup a Proxy-Rest API to access the Spring Lambda function
// var restApi = setupRestApi(unicornStoreSpringLambda);

// //Create output values for later reference
// new CfnOutput(this, "unicorn-store-spring-function-arn", CfnOutputProps.builder()
// .value(unicornStoreSpringLambda.getFunctionArn())
// .build());

// new CfnOutput(this, "ApiEndpointSpring", CfnOutputProps.builder()
// .value(restApi.getUrl())
// .build());
}

// private RestApi setupRestApi(Alias unicornStoreSpringLambdaAlias) {
// return LambdaRestApi.Builder.create(this, "UnicornStoreSpringApi")
// .restApiName("UnicornStoreSpringApi")
// .handler(unicornStoreSpringLambdaAlias)
// .build();
// }

// private Alias createUnicornLambdaFunction() {
// var lambda = Function.Builder.create(this, "UnicornStoreSpringFunction")
// .runtime(Runtime.JAVA_21)
// .functionName("unicorn-store-spring")
// .memorySize(512)
// .timeout(Duration.seconds(29))
// .code(Code.fromAsset("../../software/unicorn-store-spring/target/store-spring-1.0.0.jar"))
// .handler("com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler")
// .vpc(infrastructureConstruct.getVpc())
// .securityGroups(List.of(infrastructureConstruct.getApplicationSecurityGroup()))
// .environment(Map.of(
// "MAIN_CLASS", "com.unicorn.store.StoreApplication",
// "SPRING_DATASOURCE_PASSWORD", infrastructureConstruct.getDatabaseSecretString(),
// "SPRING_DATASOURCE_URL", infrastructureConstruct.getDatabaseJDBCConnectionString(),
// "SPRING_DATASOURCE_HIKARI_maximumPoolSize", "1",
// "AWS_SERVERLESS_JAVA_CONTAINER_INIT_GRACE_TIME", "500"
// ))
// .build();

// // Create an alias for the latest version
// var alias = Alias.Builder.create(this, "UnicornStoreSpringFunctionAlias")
// .aliasName("live")
// .version(lambda.getLatestVersion())
// .build();

// return alias;
// }

}
Loading
Loading