diff --git a/app/src/main/java/com/renaghan/todo/config/WebSocketConfig.java b/app/src/main/java/com/renaghan/todo/config/WebSocketConfig.java index e9734c2..4cb046c 100644 --- a/app/src/main/java/com/renaghan/todo/config/WebSocketConfig.java +++ b/app/src/main/java/com/renaghan/todo/config/WebSocketConfig.java @@ -25,23 +25,23 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { private final Endpoint messageBrokerEndpoint; private final String messageBrokerUser; private final String messageBrokerPassword; - private final boolean messageBrokerUseSssl; + private final boolean messageBrokerUseSSL; public WebSocketConfig( @Value("${spring.activemq.broker-url}") String websocketRelayEndpoint, @Value("${spring.activemq.user}") String messageBrokerUser, @Value("${spring.activemq.password}") String messageBrokerPassword, - @Value("${custom.web-socket-relay-use-ssl:#{false}}") boolean messageBrokerUseSssl) { + @Value("${custom.web-socket-relay-use-ssl:#{false}}") boolean messageBrokerUseSSL) { this.messageBrokerEndpoint = Endpoint.fromEndpointString(websocketRelayEndpoint); this.messageBrokerUser = messageBrokerUser; this.messageBrokerPassword = messageBrokerPassword; - this.messageBrokerUseSssl = messageBrokerUseSssl; + this.messageBrokerUseSSL = messageBrokerUseSSL; } @Override public void configureMessageBroker(@NonNull MessageBrokerRegistry registry) { ReactorNettyTcpClient customTcpClient = - this.messageBrokerUseSssl + this.messageBrokerUseSSL ? getCustomTcpClientWithSSLSupport() : getCustomTcpClientWithoutSSLSupport(); diff --git a/app/src/main/resources/templates/layout/layout.html b/app/src/main/resources/templates/layout/layout.html index 798ddc3..ca9776b 100644 --- a/app/src/main/resources/templates/layout/layout.html +++ b/app/src/main/resources/templates/layout/layout.html @@ -44,7 +44,7 @@

diff --git a/cdk/build.gradle.kts b/cdk/build.gradle.kts index b469aa1..9f0202f 100644 --- a/cdk/build.gradle.kts +++ b/cdk/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation("software.amazon.awscdk:aws-cdk-lib:2.151.1") implementation("software.constructs:constructs:10.3.0") implementation("dev.stratospheric:cdk-constructs:0.1.15") + implementation("org.passay:passay:1.6.4") } tasks.register("infra") { diff --git a/cdk/src/main/java/com/renaghan/todo/cdk/ActiveMQ.java b/cdk/src/main/java/com/renaghan/todo/cdk/ActiveMQ.java new file mode 100644 index 0000000..986ff2c --- /dev/null +++ b/cdk/src/main/java/com/renaghan/todo/cdk/ActiveMQ.java @@ -0,0 +1,218 @@ +package com.renaghan.todo.cdk; + +import dev.stratospheric.cdk.ApplicationEnvironment; +import dev.stratospheric.cdk.Network; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.passay.CharacterData; +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.PasswordGenerator; +import software.amazon.awscdk.Fn; +import software.amazon.awscdk.Stack; +import software.amazon.awscdk.services.amazonmq.CfnBroker; +import software.amazon.awscdk.services.ec2.CfnSecurityGroup; +import software.amazon.awscdk.services.ssm.StringParameter; +import software.constructs.Construct; + +class ActiveMQ { + + private static final String PARAMETER_USERNAME = "activeMqUsername"; + private static final String PARAMETER_PASSWORD = "activeMqPassword"; + private static final String PARAMETER_AMQP_ENDPOINT = "amqpEndpoint"; + private static final String PARAMETER_STOMP_ENDPOINT = "stompEndpoint"; + private static final String PARAMETER_SECURITY_GROUP_ID = "activeMqSecurityGroupId"; + + private final CDKApp app; + private final Stack stack; + private final CfnBroker broker; + private final String username; + private final String password; + private final String securityGroupId; + + ActiveMQ(CDKApp app, Stack stack, Network network) { + this.app = app; + this.stack = stack; + + this.username = app.getContext("activeMqUsername"); + this.password = generatePassword(); + + List userList = new ArrayList<>(); + userList.add(new User(username, password)); + + CfnSecurityGroup amqSecurityGroup = + CfnSecurityGroup.Builder.create(stack, "amqSecurityGroup") + .vpcId(network.getVpc().getVpcId()) + .groupDescription("Security Group for the Amazon MQ instance") + .groupName(app.appEnv().prefix("amqSecurityGroup")) + .build(); + + this.securityGroupId = amqSecurityGroup.getAttrGroupId(); + + this.broker = + CfnBroker.Builder.create(stack, "amqBroker") + .brokerName(app.appEnv().prefix("stratospheric-amq-message-broker")) + .securityGroups(Collections.singletonList(this.securityGroupId)) + .subnetIds( + Collections.singletonList( + network.getVpc().getIsolatedSubnets().get(0).getSubnetId())) + .hostInstanceType("mq.t3.micro") + .engineType("ACTIVEMQ") + .engineVersion("5.18") + .authenticationStrategy("SIMPLE") + .encryptionOptions( + CfnBroker.EncryptionOptionsProperty.builder().useAwsOwnedKey(true).build()) + .users(userList) + .publiclyAccessible(false) + .autoMinorVersionUpgrade(true) + .deploymentMode("SINGLE_INSTANCE") + .logs(CfnBroker.LogListProperty.builder().general(true).build()) + .build(); + + createOutputParameters(); + } + + public static ActiveMqOutputParameters getOutputParametersFromParameterStore( + Construct scope, ApplicationEnvironment applicationEnvironment) { + return new ActiveMqOutputParameters( + getParameterUsername(scope, applicationEnvironment), + getParameterPassword(scope, applicationEnvironment), + getParameterAmqpEndpoint(scope, applicationEnvironment), + getParameterStompEndpoint(scope, applicationEnvironment), + getParameterSecurityGroupId(scope, applicationEnvironment)); + } + + private static String getParameterUsername( + Construct scope, ApplicationEnvironment applicationEnvironment) { + return StringParameter.fromStringParameterName( + scope, + PARAMETER_USERNAME, + createParameterName(applicationEnvironment, PARAMETER_USERNAME)) + .getStringValue(); + } + + private static String getParameterPassword( + Construct scope, ApplicationEnvironment applicationEnvironment) { + return StringParameter.fromStringParameterName( + scope, + PARAMETER_PASSWORD, + createParameterName(applicationEnvironment, PARAMETER_PASSWORD)) + .getStringValue(); + } + + private static String getParameterAmqpEndpoint( + Construct scope, ApplicationEnvironment applicationEnvironment) { + return StringParameter.fromStringParameterName( + scope, + PARAMETER_AMQP_ENDPOINT, + createParameterName(applicationEnvironment, PARAMETER_AMQP_ENDPOINT)) + .getStringValue(); + } + + private static String getParameterStompEndpoint( + Construct scope, ApplicationEnvironment applicationEnvironment) { + return StringParameter.fromStringParameterName( + scope, + PARAMETER_STOMP_ENDPOINT, + createParameterName(applicationEnvironment, PARAMETER_STOMP_ENDPOINT)) + .getStringValue(); + } + + private static String getParameterSecurityGroupId( + Construct scope, ApplicationEnvironment applicationEnvironment) { + return StringParameter.fromStringParameterName( + scope, + PARAMETER_SECURITY_GROUP_ID, + createParameterName(applicationEnvironment, PARAMETER_SECURITY_GROUP_ID)) + .getStringValue(); + } + + private String generatePassword() { + PasswordGenerator passwordGenerator = new PasswordGenerator(); + CharacterData lowerCaseChars = EnglishCharacterData.LowerCase; + CharacterRule lowerCaseRule = new CharacterRule(lowerCaseChars); + lowerCaseRule.setNumberOfCharacters(5); + CharacterData upperCaseChars = EnglishCharacterData.UpperCase; + CharacterRule upperCaseRule = new CharacterRule(upperCaseChars); + upperCaseRule.setNumberOfCharacters(5); + CharacterData digitChars = EnglishCharacterData.Digit; + CharacterRule digitRule = new CharacterRule(digitChars); + digitRule.setNumberOfCharacters(5); + return passwordGenerator.generatePassword(32, lowerCaseRule, upperCaseRule, digitRule); + } + + private void createOutputParameters() { + StringParameter.Builder.create(stack, PARAMETER_USERNAME) + .parameterName(createParameterName(app.appEnv(), PARAMETER_USERNAME)) + .stringValue(username) + .build(); + + StringParameter.Builder.create(stack, PARAMETER_PASSWORD) + .parameterName(createParameterName(app.appEnv(), PARAMETER_PASSWORD)) + .stringValue(password) + .build(); + + StringParameter.Builder.create(stack, PARAMETER_AMQP_ENDPOINT) + .parameterName(createParameterName(app.appEnv(), PARAMETER_AMQP_ENDPOINT)) + .stringValue(Fn.select(0, this.broker.getAttrAmqpEndpoints())) + .build(); + + StringParameter.Builder.create(stack, PARAMETER_STOMP_ENDPOINT) + .parameterName(createParameterName(app.appEnv(), PARAMETER_STOMP_ENDPOINT)) + .stringValue(Fn.select(0, this.broker.getAttrStompEndpoints())) + .build(); + + StringParameter.Builder.create(stack, PARAMETER_SECURITY_GROUP_ID) + .parameterName(createParameterName(app.appEnv(), PARAMETER_SECURITY_GROUP_ID)) + .stringValue(this.securityGroupId) + .build(); + } + + private static String createParameterName( + ApplicationEnvironment applicationEnvironment, String parameterName) { + return applicationEnvironment.getEnvironmentName() + + "-" + + applicationEnvironment.getApplicationName() + + "-ActiveMq-" + + parameterName; + } + + public record ActiveMqOutputParameters( + String activeMqUsername, + String activeMqPassword, + String amqpEndpoint, + String stompEndpoint, + String activeMqSecurityGroupId) {} + + @SuppressWarnings("unused") + static class User { + + String username; + + String password; + + public User() {} + + public User(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + } +} diff --git a/cdk/src/main/java/com/renaghan/todo/cdk/Database.java b/cdk/src/main/java/com/renaghan/todo/cdk/Database.java index 7692432..035f448 100644 --- a/cdk/src/main/java/com/renaghan/todo/cdk/Database.java +++ b/cdk/src/main/java/com/renaghan/todo/cdk/Database.java @@ -313,57 +313,11 @@ public DatabaseInputParameters withPostgresVersion(String postgresVersion) { } } - public static class DatabaseOutputParameters { - private final String endpointAddress; - private final String endpointPort; - private final String dbName; - private final String databaseSecretArn; - private final String databaseSecurityGroupId; - private final String instanceId; - - public DatabaseOutputParameters( - String endpointAddress, - String endpointPort, - String dbName, - String databaseSecretArn, - String databaseSecurityGroupId, - String instanceId) { - this.endpointAddress = endpointAddress; - this.endpointPort = endpointPort; - this.dbName = dbName; - this.databaseSecretArn = databaseSecretArn; - this.databaseSecurityGroupId = databaseSecurityGroupId; - this.instanceId = instanceId; - } - - /** The URL of the Postgres instance. */ - public String getEndpointAddress() { - return endpointAddress; - } - - /** The port of the Postgres instance. */ - public String getEndpointPort() { - return endpointPort; - } - - /** The database name of the Postgres instance. */ - public String getDbName() { - return dbName; - } - - /** The secret containing username and password. */ - public String getDatabaseSecretArn() { - return databaseSecretArn; - } - - /** The database's security group. */ - public String getDatabaseSecurityGroupId() { - return databaseSecurityGroupId; - } - - /** The database's identifier. */ - public String getInstanceId() { - return instanceId; - } - } + public record DatabaseOutputParameters( + String endpointAddress, + String endpointPort, + String dbName, + String databaseSecretArn, + String databaseSecurityGroupId, + String instanceId) {} } diff --git a/cdk/src/main/java/com/renaghan/todo/cdk/Infrastructure.java b/cdk/src/main/java/com/renaghan/todo/cdk/Infrastructure.java index 1130fbe..0526e56 100644 --- a/cdk/src/main/java/com/renaghan/todo/cdk/Infrastructure.java +++ b/cdk/src/main/java/com/renaghan/todo/cdk/Infrastructure.java @@ -115,6 +115,10 @@ private void messaging() { .build(); } + private void activeMQ() { + new ActiveMQ(app, stack, network); + } + private void generate() { dockerRepo(); cert(); @@ -123,6 +127,7 @@ private void generate() { cognito(); messaging(); database(); + activeMQ(); app.appEnv().tag(stack); app.synth(); } diff --git a/cdk/src/main/java/com/renaghan/todo/cdk/ServiceApp.java b/cdk/src/main/java/com/renaghan/todo/cdk/ServiceApp.java index d75ea55..fecd7f9 100644 --- a/cdk/src/main/java/com/renaghan/todo/cdk/ServiceApp.java +++ b/cdk/src/main/java/com/renaghan/todo/cdk/ServiceApp.java @@ -52,33 +52,31 @@ public static void main(String[] args) { Database.getOutputParametersFromParameterStore(serviceStack, app.appEnv()); ISecret databaseSecret = Secret.fromSecretCompleteArn( - serviceStack, "databaseSecret", databaseOutputParameters.getDatabaseSecretArn()); + serviceStack, "databaseSecret", databaseOutputParameters.databaseSecretArn()); vars.put( "SPRING_DATASOURCE_URL", String.format( "jdbc:postgresql://%s:%s/%s", - databaseOutputParameters.getEndpointAddress(), - databaseOutputParameters.getEndpointPort(), - databaseOutputParameters.getDbName())); + databaseOutputParameters.endpointAddress(), + databaseOutputParameters.endpointPort(), + databaseOutputParameters.dbName())); vars.put( "SPRING_DATASOURCE_USERNAME", databaseSecret.secretValueFromJson("username").toString()); vars.put( "SPRING_DATASOURCE_PASSWORD", databaseSecret.secretValueFromJson("password").toString()); - /* - ActiveMqStack.ActiveMqOutputParameters activeMqOutputParameters = - ActiveMqStack.getOutputParametersFromParameterStore(parametersStack, applicationEnvironment); - vars.put("WEB_SOCKET_RELAY_ENDPOINT", activeMqOutputParameters.getStompEndpoint()); - vars.put("WEB_SOCKET_RELAY_USERNAME", activeMqOutputParameters.getActiveMqUsername()); - vars.put("WEB_SOCKET_RELAY_PASSWORD", activeMqOutputParameters.getActiveMqPassword()); - */ + ActiveMQ.ActiveMqOutputParameters activeMqOutputParameters = + ActiveMQ.getOutputParametersFromParameterStore(serviceStack, app.appEnv()); + + vars.put("WEB_SOCKET_RELAY_ENDPOINT", activeMqOutputParameters.stompEndpoint()); + vars.put("WEB_SOCKET_RELAY_USERNAME", activeMqOutputParameters.activeMqUsername()); + vars.put("WEB_SOCKET_RELAY_PASSWORD", activeMqOutputParameters.activeMqPassword()); List securityGroupIdsToGrantIngressFromEcs = Arrays.asList( - databaseOutputParameters.getDatabaseSecurityGroupId() - // ,activeMqOutputParameters.getActiveMqSecurityGroupId() - ); + databaseOutputParameters.databaseSecurityGroupId(), + activeMqOutputParameters.activeMqSecurityGroupId()); Service.ServiceInputParameters inputParameters = new Service.ServiceInputParameters(