DEV Community

Paul Eggerling-Boeck
Paul Eggerling-Boeck

Posted on • Originally published at awstip.com on

AWS Lambda Tutorial: Using Secrets Manager and Java SDK to Connect to MySQL

In previous articles, I showed you how to store a password in AWS Secrets Manager and how to deploy an AWS Lambda function with SAM. This tutorial is going to build on both of those articles so if you haven’t read them, you might want to start there.

Now that you know how to deploy a Lambda function and securely store your database credentials, it’s time to learn how to access those credentials and use them to connect to your database from a Lambda function written in Java and using the AWS SDK for Java.

For the purposes of this tutorial, I’m going to keep it simple and old school by showing you how to connect to MySQL using straight JDBC code. In a future article, we’ll build on some of the foundation shown here and use Spring Data JPA for database access. You’ll need to implement the following steps in order to connect to MySQL using JDBC with credentials retrieved from Secrets Manager.

  1. Add the MySQL driver and AWS Secrets Manager SDK as dependencies of your project. I’ll illustrate how to do this with Gradle.
  2. Write the JDBC code to create a database connection.
  3. Ensure that your Lambda function has permission to read from Secrets Manager. I’ll show an example of how to set this up in the SAM configuration for your Lambda function.
  4. Deploy and test your Lambda function.

If you don’t want to create everything from scratch, you can fork the GitHub repository I used to get this example working. That repository has all the Terraform and AWS SAM configuration you need for this tutorial which will allow you to focus on the details I’m presenting below.

Before you get started on the details of this tutorial, you’ll want to use Terraform as described in the article I linked above in order to create your RDS instance and Secrets Manager secret. You can find the specifics in the README.md file in the root of the repository.

The first thing you’ll need to do is to declare the needed dependencies for working with the Secrets Manager SDK and MySQL. Here’s what that would look like if you’re using Gradle. Note: the existing aws-lambda-java-core dependency is from the previous article.

dependencies {
    // AWS dependencies
    implementation 'com.amazonaws:aws-lambda-java-core:1.2.2'
    implementation 'com.amazonaws:aws-java-sdk-secretsmanager:1.12.449'

    // MySQL dependencies
    implementation 'com.mysql:mysql-connector-j:8.0.32'
}
Enter fullscreen mode Exit fullscreen mode

And here’s the Java class. You’ll see that the static initializer block is responsible for calling getSecret() and creating the database connection which is then stored in a static variable. This is a best practice when writing Java-based Lambda functions. You want to be re-using things like database connections and not creating them every time your Lambda function is executed. You’ll find a handleRequest() method which is called when the Lambda function is invoked. The handler method calls getString() which runs a query, and returns the result. This pattern is also a Lambda best practice. Your handler method should delegate all functionality to additional methods. Easy peasy.

NOTE: you’ll need to update the RDS host name and db username variables before deploying this class. The RDS host name can be found as the ‘endpoint’ listed for your RDS instance in the AWS Management Console. The db username should be the same value you provided to Terraform when you used it to create your AWS resources.

package org.example;

import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClient;
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * This class implements a simple Lambda function that connects to an RDS database
 */
public class App {
    // UPDATE the HOST string to match your RDS endpoint
    static final String HOST = "<your rds endpoint>";
    static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    static final String DB_URL = String.format("jdbc:mysql://%s/example", HOST);
    // Update the USER string to match the username you supplied when
    // creating your database
    static final String USER = "<your db username>";
    static final String SECRET_NAME = "rds-password";

    static final Connection conn;

    static {
        // Get the password from AWS Secrets Manager
        String password = getSecret(SECRET_NAME);

        // Initialize the dataabase driver
        try {
            Class.forName(JDBC_DRIVER);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        try {
            conn = DriverManager.getConnection(DB_URL,USER,password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * Lambda handler method
     * @param context the Lambda request context
     * @return a string containing a message
     */
    public String handleRequest(Context context) {
        String ret = getString();
        if (ret == null) {
            return ("ERROR");
        }
        return (ret);
    }

    /**
     * Run a simple query to test the connection
     * @return the result of the query
     */
    private String getString() {
        String ret = "Could not connect to database";

        // Run a simple query to test the connection
        try (Statement stmt = conn.createStatement(); ) {
            try( ResultSet rs = stmt.executeQuery("SELECT 'Hello World!' FROM (SELECT 1) AS dummy")) {
                while(rs.next()) {
                    ret = rs.getString(1);
                }
            } catch(Exception e) {
                e.printStackTrace();
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return ret;
    }

    /**
     * Get the secret from AWS Secrets Manager
     * @return the secret
     */
    public static String getSecret(String secretName) {
        Region region = Regions.getCurrentRegion();

        // Create a Secrets Manager client in US East 2
        AWSSecretsManager client = AWSSecretsManagerClient.builder()
            .withRegion(Regions.US_EAST_2)
            .build();

        // Create the request used to retrieve the secret
        GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest()
            .withSecretId(secretName);

        GetSecretValueResult getSecretValueResult;

        try {
            getSecretValueResult = client.getSecretValue(getSecretValueRequest);
        } catch (Exception e) {
            // For a list of exceptions thrown, see
            // https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
            throw e;
        }

        // Get the secret value and return it
        return getSecretValueResult.getSecretString();
    }
}
Enter fullscreen mode Exit fullscreen mode

If you’ve forked the repository I mentioned at the beginning, you have everything you need to package and deploy this Lambda function to AWS and test it out. Since you added the dependencies on the Secrets Manager SDK and MySQL driver, those will be packaged into the bundle deployed by AWS SAM. You can find details in the README.md file in the root of the repository. Go ahead and use the built-in test feature in the AWS Lambda console to see it if it works.

I’m betting you got an error similar to the one below. Why? because you have not given your Lambda function permission to access your database password in Secrets Manager. The ARN of your IAM role and your request ID will be different, but the error is the same.

User: arn:aws:sts::873617326850:assumed-role/simple-java-lambda-SimpleJavaFunctionRole-P8CLE0MM2DSX/simple-java-lambda is not authorized to perform: secretsmanager:GetSecretValue on resource: weather-tracker-rds-password because no identity-based policy allows the secretsmanager:GetSecretValue action (Service: AWSSecretsManager; Status Code: 400; Error Code: AccessDeniedException; Request ID: e7f541a8-128e-4809-9825-3a0bdda806e5; Proxy: null)
Enter fullscreen mode Exit fullscreen mode

In order to give your Lambda function permission to access secrets manager, you’ll need to modify the SAM template (sam.template.yaml) used to deploy the function so that the IAM role created by SAM will have the policy SecretsManagerReadWrite . Luckily it only takes two lines of code to accomplish this. See the last two lines in the listing below. I’ve added these lines to the template file, you just need to uncomment them :)

.AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: Simple Java Lambda Functions
Resources:
  SimpleJavaFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      FunctionName: simple-java-lambda
      Handler: org.example.App::handleRequest
      Runtime: java8
      Description: Simple Java Lambda Function
      MemorySize: 512
      Timeout: 60
      PackageType: Zip
      CodeUri: '.'
      Policies:
        - SecretsManagerReadWrite
Enter fullscreen mode Exit fullscreen mode

Those two lines cause AWS SAM to attach the SecretsManagerReadWrite policy to the default IAM role created for the Lambda function. Once you have updated the SAM template file, just rebuild and re-deploy the Lambda function and give it another test. This time, you’ll see it succeed and return the value ‘Hello World!’ which it got from executing the SQL query after it connected to your MySQL database. Congratulations!

Even though the resources you’ve created in this tutorial should be using the AWS free tier pricing model, I would advise you to run terraform destroy and sam deletewhen you’re done so you don’t end up with an unexpected AWS bill at the end of the month!

Was this article helpful? Did you learn something? Was it worth your time, or a waste of time? I’d love to hear from you if you have questions or feedback on this article and/or if I can help you get past any stumbling blocks you may have encountered along the way!


Top comments (0)