Blog AWS Spring Boot

Securing Database Credentials with AWS Secrets Manager in a Spring Boot Application

Introduction

In modern application development, handling sensitive information like database credentials securely is of utmost importance. Hardcoding these credentials in your application code or configuration files is a serious security risk, as they can be easily exposed or accidentally committed to version control systems.

AWS Secrets Manager provides a secure and scalable solution for managing secrets, including database credentials, API keys, and other sensitive data.

In this blog post, we’ll explore how to integrate AWS Secrets Manager with a Spring Boot application to securely retrieve and use database credentials. We’ll also discuss best practices for handling AWS credentials in a production environment.

How To Create AWS Secret Manager in AWS

Before we dive into the code, let’s set up AWS Secrets Manager and create a secret for our database credentials.

  1. Create an AWS Account: If you haven’t already, create an AWS account and log in to the AWS Management Console.
  2. Navigate to AWS Secrets Manager: In the AWS Management Console, search for the Secrets Manager service and navigate to it.
  3. Create a New Secret: Click on “Store a new secret” and select the “Credential pair” option. Provide a name for your secret (e.g., “my-db-credential”) and enter your database username and password in the respective fields.
  4. Copy the Secret ARN: After creating the secret, copy the Amazon Resource Name (ARN) of the secret, as you’ll need it later in your application code.

Integrating AWS Secrets Manager with Spring Boot

Now that we have our database credentials stored securely in AWS Secrets Manager, let’s integrate it with our Spring Boot application.

  1. Add AWS SDK Dependencies: In your Spring Boot application, add the required AWS SDK dependencies to your project’s build file (e.g., pom.xml for Maven or build.gradle for Gradle).
XML
<dependencies>
    <!-- Spring Boot dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- AWS SDK for Java -->
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-secretsmanager</artifactId>
        <version>1.12.300</version>
    </dependency>

    <!-- Gson for JSON parsing -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.9.1</version>
    </dependency>

    <!-- Lombok for boilerplate code reduction -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
  1. Create a Configuration Class: Create a new class (e.g., ApplicationConfig.java) and annotate it with @Configuration.
Java
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder;
import com.amazonaws.services.secretsmanager.model.*;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class ApplicationConfig {
    // ...
}
  1. Retrieve AWS Credentials: In the ApplicationConfig class, inject the AWS credentials using @Value annotations. These credentials can be provided as environment variables or read from a credentials file.
XML
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${cloud.aws.credentials.secret-key}")
private String secretkey;
  1. Create a DataSource Bean: Define a @Bean method to create a DataSource object using the database credentials retrieved from AWS Secrets Manager.
Java
@Bean
public DataSource dataSource() {
    AwsSecrets secrets = getSecret();
    return DataSourceBuilder
            .create()
            .url("jdbc:" + secrets.getEngine() + "://" + secrets.getHost() + ":" + secrets.getPort() + "/mydb")
            .username(secrets.getUsername())
            .password(secrets.getPassword())
            .build();
}
  1. Retrieve Secrets from AWS Secrets Manager: Create a private method getSecret() to retrieve the database credentials from AWS Secrets Manager using the AWS SDK for Java.
Java
private AwsSecrets getSecret() {
    String secretName = "mydb-db-credential";
    String region = "us-east-2";

    AWSSecretsManager client = AWSSecretsManagerClientBuilder.standard()
            .withRegion(region)
            .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretkey)))
            .build();

    GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest()
            .withSecretId(secretName);

    GetSecretValueResult getSecretValueResult = null;
    try {
        getSecretValueResult = client.getSecretValue(getSecretValueRequest);
    } catch (Exception e) {
        throw e;
    }

    if (getSecretValueResult.getSecretString() != null) {
        String secret = getSecretValueResult.getSecretString();
        return new Gson().fromJson(secret, AwsSecrets.class);
    }

    return null;
}

In this method, we’re using the AWSSecretsManager client to retrieve the secret value from AWS Secrets Manager. We’re also using the Gson library to deserialize the secret string into an AwsSecrets object.

Final Class of ApplicationConfig.java

Java
package com.example.awssecretsmanager;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder;
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class ApplicationConfig {
    @Value("${cloud.aws.credentials.access-key}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secret-key}")
    private String secretKey;

    private Gson gson = new Gson();

    @Bean
    public DataSource dataSource() {
        AwsSecrets secrets = getSecret();
        return DataSourceBuilder
                .create()
                .url("jdbc:" + secrets.getEngine() + "://" + secrets.getHost() + ":" + secrets.getPort() + "/mydb")
                .username(secrets.getUsername())
                .password(secrets.getPassword())
                .build();
    }

    private AwsSecrets getSecret() {
        String secretName = "mydb-db-credential";
        String region = "us-east-2";

        AWSSecretsManager client = AWSSecretsManagerClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
                .build();

        GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest()
                .withSecretId(secretName);

        GetSecretValueResult getSecretValueResult = null;
        try {
            getSecretValueResult = client.getSecretValue(getSecretValueRequest);
        } catch (Exception e) {
            throw e;
        }

        if (getSecretValueResult.getSecretString() != null) {
            String secret = getSecretValueResult.getSecretString();
            return gson.fromJson(secret, AwsSecrets.class);
        }

        return null;
    }
}
  1. Create an AwsSecrets POJO: Create a simple POJO (Plain Old Java Object) to hold the database credentials retrieved from AWS Secrets Manager.
Java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AwsSecrets {
    private String username;
    private String password;
    private String host;
    private String engine;
    private String port;
    private String dbInstanceIdentifier;
}
  1. Configure Application Properties: In your application.properties or application.yml file, provide the necessary configuration for your Spring Boot application, including the AWS credentials.
Java
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
server.port=5000
spring.jpa.properties.max_allowed_packet=2000

cloud:
  aws:
    region: static
    auto: false
    credentials:
      access-key: YOUR_AWS_ACCESS_KEY
      secret-key: YOUR_AWS_SECRET_KEY

Replace YOUR_AWS_ACCESS_KEY and YOUR_AWS_SECRET_KEY with your actual AWS credentials.

Handling AWS Credentials in a Production Environment

While we’ve used hardcoded AWS credentials in the example for simplicity, it’s essential to handle these credentials securely in a production environment. Here are some best practices:

  1. Use AWS Identity and Access Management (IAM) Roles: Instead of hardcoding AWS credentials in your application, you can assign an IAM role with the necessary permissions to your application’s compute resource (e.g., EC2 instance, ECS task, or Lambda function). The AWS SDK will automatically retrieve the credentials from the assigned IAM role.
  2. Use AWS Secrets Manager for AWS Credentials: If you can’t use IAM roles, you can store your AWS credentials as a secret in AWS Secrets Manager and retrieve them programmatically in your application.
  3. Limit Access to AWS Credentials: Ensure that your AWS credentials have the minimum required permissions to access the necessary resources. Follow the principle of least privilege when assigning permissions.
  4. Rotate AWS Credentials Regularly: Rotate your AWS credentials periodically to mitigate the risk of credential exposure or misuse.
  5. Use Environment Variables or Credentials File: If you need to provide AWS credentials directly to your application, use environment variables or a credentials file instead of hardcoding them in your application code or configuration files.

Conclusion

In this blog post, we’ve explored how to integrate AWS Secrets Manager with a Spring Boot application to securely retrieve and use database credentials. We’ve also discussed best practices for handling AWS credentials in a production environment.

By leveraging AWS Secrets Manager, you can centralize the management of sensitive data, rotate secrets automatically, and improve your application’s security posture by eliminating hardcoded credentials. The provided code examples demonstrate how to retrieve database credentials from AWS Secrets Manager and create a DataSource.

Ref: https://github.com/Java-Techie-jt/springboot-aws-secrets-manager

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over