Skip to content

Commit

Permalink
merge ssm secrets branch
Browse files Browse the repository at this point in the history
  • Loading branch information
bobivk committed Jun 23, 2024
2 parents 3d70c00 + cae132f commit c7b56f2
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 15 deletions.
44 changes: 35 additions & 9 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<description>Open Repository with Playgrounds for Children</description>
<properties>
<java.version>17</java.version>
<aws.java.sdk.version>2.26.3</aws.java.sdk.version>
</properties>

<dependencies>
Expand All @@ -27,12 +28,19 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
<scope>runtime</scope>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.java.sdk.version}</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>


<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down Expand Up @@ -63,19 +71,37 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.7.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.7.3</version>
<groupId>software.amazon.awssdk</groupId>
<artifactId>auth</artifactId>
<version>${aws.java.sdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>http-auth</artifactId>
<version>${aws.java.sdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ssm</artifactId>
<version>${aws.java.sdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>regions</artifactId>
<version>${aws.java.sdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>url-connection-client</artifactId>
<version>${aws.java.sdk.version}</version>
</dependency>
</dependencies>

Expand Down
56 changes: 56 additions & 0 deletions backend/src/main/java/bg/kidsground/config/AwsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package bg.kidsground.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ssm.SsmClient;


@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
public class AwsConfig {

private final String appName = "kidsground";

@Lazy(false)
@Bean
public AwsParameterBeanPostProcessor setupAwsParamBeanPostProcessor(SsmClient ssmClient) {
AwsParameterBeanPostProcessor beanPostProcessor = new AwsParameterBeanPostProcessor(ssmClient, appName);
return beanPostProcessor;
}

@Bean
public AwsCredentialsProvider awsCredentialsProvider(@Value("${aws.app.name:}") String awsCredentialProfileName) {
if (awsCredentialProfileName != null && !awsCredentialProfileName.isBlank()) {
final ProfileCredentialsProvider create = ProfileCredentialsProvider.create(awsCredentialProfileName);
return AwsCredentialsProviderChain.builder()
.addCredentialsProvider(create)
.addCredentialsProvider(InstanceProfileCredentialsProvider.create())
.build();
}
return AwsCredentialsProviderChain.builder()
.addCredentialsProvider(InstanceProfileCredentialsProvider.create())
.build();
}

@Lazy(false)
@Bean
@ConditionalOnBean(value = AwsCredentialsProvider.class)
public SsmClient ssmClient(AwsCredentialsProvider awsCredentialsProvider) {
return SsmClient.builder()
.credentialsProvider(awsCredentialsProvider)
.region(Region.EU_CENTRAL_1)
.httpClient(UrlConnectionHttpClient.builder().build())
.build();
}
}
15 changes: 15 additions & 0 deletions backend/src/main/java/bg/kidsground/config/AwsParameter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package bg.kidsground.config;


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AwsParameter {
String value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package bg.kidsground.config;

import jakarta.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.ReflectionUtils;
import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest;
import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse;
import software.amazon.awssdk.services.ssm.model.Parameter;

public class AwsParameterBeanPostProcessor implements BeanPostProcessor, EnvironmentAware {

private static final Logger LOG = LoggerFactory.getLogger(AwsParameterBeanPostProcessor.class);
private final String appName;
private Environment environment;
private SsmClient ssmClient;
private GetParametersByPathResponse applicationParams;

public AwsParameterBeanPostProcessor(SsmClient ssmClient, String appName) {
this.ssmClient = ssmClient;
this.appName = appName;
}

@PostConstruct
public void init() {
if (ssmClient == null) {
LOG.warn("application cannot be injected with aws ssm parameters without ssmClient.");
}
if (appName == null || appName.isBlank()) {
LOG.warn("application cannot be injected with aws ssm parameters without setting the 'aws.app.name' property.");
}
if (ssmClient != null) {
try {
GetParametersByPathRequest byPathRequest = GetParametersByPathRequest.builder().path( "/config/" + appName + "/").build();
applicationParams = ssmClient.getParametersByPath(byPathRequest);
} catch (SdkException e) {
LOG.warn("Unable to retrieve parameters from SSM due to exception: {}", e.getMessage());
LOG.warn("Parameter injection from SSM will not be possible due to an error communicating with the service. Please check permissions.");
LOG.warn("Parameter injection will only occur through properties and environment variables");
ssmClient = null;
}
}
}

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// instrospec the bean for fields with AwsParameter annotation
Class<?> clazz = bean.getClass();
// only scan beans in this package
if (clazz.getName().startsWith("bg.kidsground")) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(AwsParameter.class)) {
ReflectionUtils.makeAccessible(field);
AwsParameter awsParameter = field.getAnnotation(AwsParameter.class);
String annotationValue = awsParameter.value();
String[] split = annotationValue.split(":");
String paramName = split[0];
String defaultValue = split.length > 1 ? split[1] : null;

if (applicationParams != null) {
Optional<Parameter> valueForField = applicationParams.parameters().stream().filter(param -> param.name().contains(paramName)).findFirst();
if (valueForField.isPresent()) {
LOG.info("Injecting value for field {} into bean {}", paramName, bean.getClass().getCanonicalName());
final String value = valueForField.get().value();
setField(field, bean, value);
continue;
}
}
Object property = environment.getProperty(paramName, field.getType());
if (property != null) {
setField(field, bean, property);
} else if (defaultValue != null) {
setField(field, bean, defaultValue);
}
}
}
}
return bean;
}

private void setField(Field field, Object bean, Object value) {
try {
field.set(bean, value);
} catch (IllegalArgumentException | IllegalAccessException ex) {
LOG.error(ex.getMessage(), ex);
}
}

}
33 changes: 33 additions & 0 deletions backend/src/main/java/bg/kidsground/config/DataSourceConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package bg.kidsground.config;

import bg.kidsground.service.SecretsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

final SecretsService secretsService;

@Autowired
public DataSourceConfig(SecretsService secretsService) {
this.secretsService = secretsService;
}

@Primary
@Bean
public DataSource dataSource() {

DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(this.secretsService.getSecret("db.url"));
dataSource.setUsername(this.secretsService.getSecret("db.username"));
dataSource.setPassword(this.secretsService.getSecret("db.password"));

return dataSource;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ public class By {
public static final String ID = PLAYGROUND_ROOT + "/{id}";
}
}

public class Secrets {
public static final String SECRETS_ROOT = V1_ROOT + "/secrets";

public static final String MAPS_API_KEY = SECRETS_ROOT + "/mapsApiKey";
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package bg.kidsground.controller;

import bg.kidsground.constants.AppRestEndpoints;
import bg.kidsground.service.SecretsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SecretsController {
@Autowired
private SecretsService secretsService;

@GetMapping(AppRestEndpoints.V1.Secrets.MAPS_API_KEY)
public ResponseEntity<String> getMapsApiKey() {
return ResponseEntity.ok(this.secretsService.getSecret("maps.apiKey"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package bg.kidsground.service;

public interface SecretsService {
String getSecret(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package bg.kidsground.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.ssm.model.GetParameterRequest;

@Service
@Slf4j
public class SecretsServiceImpl implements SecretsService {

@Autowired
private SsmClient ssmClient;

public String getSecret(String secretName) {
String secret = this.ssmClient.getParameter(
GetParameterRequest.builder()
.name("/config/kidsground/" + secretName)
.withDecryption(true)
.build())
.parameter()
.value();
log.info("got secret from param store with name {} and value {}", secretName, secret);
return secret;
}
}
14 changes: 8 additions & 6 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
server.port=8009
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=admin
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

security.basic.enable=false
security.ignored=/**
security.ignored=/**

spring.application.name=kidsground
cloud.aws.region.static=eu-central-1

aws.app.name=kidsground

0 comments on commit c7b56f2

Please sign in to comment.