Connecting to your DB instance using IAM authentication and the AWS SDK for Java - Amazon Relational Database Service

Connecting to your DB instance using IAM authentication and the AWS SDK for Java

You can connect to an RDS for MariaDB, MySQL, or PostgreSQL DB instance with the AWS SDK for Java as described following.

Prerequisites

The following are prerequisites for connecting to your DB instance using IAM authentication:

For examples on how to use the SDK for Java 2.x, see Amazon RDS examples using SDK for Java 2.x.

Generating an IAM authentication token

If you are writing programs using the AWS SDK for Java, you can get a signed authentication token using the RdsIamAuthTokenGenerator class. Using this class requires that you provide AWS credentials. To do this, you create an instance of the DefaultAWSCredentialsProviderChain class. DefaultAWSCredentialsProviderChain uses the first AWS access key and secret key that it finds in the default credential provider chain. For more information about AWS access keys, see Managing access keys for users.

Note

You cannot use a custom Route 53 DNS record instead of the DB instance endpoint to generate the authentication token.

After you create an instance of RdsIamAuthTokenGenerator, you can call the getAuthToken method to obtain a signed token. Provide the AWS Region, host name, port number, and user name. The following code example illustrates how to do this.

package com.amazonaws.codesamples; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.services.rds.auth.GetIamAuthTokenRequest; import com.amazonaws.services.rds.auth.RdsIamAuthTokenGenerator; public class GenerateRDSAuthToken { public static void main(String[] args) { String region = "us-west-2"; String hostname = "rdsmysql.123456789012.us-west-2.rds.amazonaws.com"; String port = "3306"; String username = "jane_doe"; System.out.println(generateAuthToken(region, hostname, port, username)); } static String generateAuthToken(String region, String hostName, String port, String username) { RdsIamAuthTokenGenerator generator = RdsIamAuthTokenGenerator.builder() .credentials(new DefaultAWSCredentialsProviderChain()) .region(region) .build(); String authToken = generator.getAuthToken( GetIamAuthTokenRequest.builder() .hostname(hostName) .port(Integer.parseInt(port)) .userName(username) .build()); return authToken; } }

Manually constructing an IAM authentication token

In Java, the easiest way to generate an authentication token is to use RdsIamAuthTokenGenerator. This class creates an authentication token for you, and then signs it using AWS signature version 4. For more information, see Signature version 4 signing process in the AWS General Reference.

However, you can also construct and sign an authentication token manually, as shown in the following code example.

package com.amazonaws.codesamples; import com.amazonaws.SdkClientException; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.auth.SigningAlgorithm; import com.amazonaws.util.BinaryUtils; import org.apache.commons.lang3.StringUtils; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.Date; import java.util.SortedMap; import java.util.TreeMap; import static com.amazonaws.auth.internal.SignerConstants.AWS4_TERMINATOR; import static com.amazonaws.util.StringUtils.UTF8; public class CreateRDSAuthTokenManually { public static String httpMethod = "GET"; public static String action = "connect"; public static String canonicalURIParameter = "/"; public static SortedMap<String, String> canonicalQueryParameters = new TreeMap(); public static String payload = StringUtils.EMPTY; public static String signedHeader = "host"; public static String algorithm = "AWS4-HMAC-SHA256"; public static String serviceName = "rds-db"; public static String requestWithoutSignature; public static void main(String[] args) throws Exception { String region = "us-west-2"; String instanceName = "rdsmysql.123456789012.us-west-2.rds.amazonaws.com"; String port = "3306"; String username = "jane_doe"; Date now = new Date(); String date = new SimpleDateFormat("yyyyMMdd").format(now); String dateTimeStamp = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'").format(now); DefaultAWSCredentialsProviderChain creds = new DefaultAWSCredentialsProviderChain(); String awsAccessKey = creds.getCredentials().getAWSAccessKeyId(); String awsSecretKey = creds.getCredentials().getAWSSecretKey(); String expiryMinutes = "900"; System.out.println("Step 1: Create a canonical request:"); String canonicalString = createCanonicalString(username, awsAccessKey, date, dateTimeStamp, region, expiryMinutes, instanceName, port); System.out.println(canonicalString); System.out.println(); System.out.println("Step 2: Create a string to sign:"); String stringToSign = createStringToSign(dateTimeStamp, canonicalString, awsAccessKey, date, region); System.out.println(stringToSign); System.out.println(); System.out.println("Step 3: Calculate the signature:"); String signature = BinaryUtils.toHex(calculateSignature(stringToSign, newSigningKey(awsSecretKey, date, region, serviceName))); System.out.println(signature); System.out.println(); System.out.println("Step 4: Add the signing info to the request"); System.out.println(appendSignature(signature)); System.out.println(); } //Step 1: Create a canonical request date should be in format YYYYMMDD and dateTime should be in format YYYYMMDDTHHMMSSZ public static String createCanonicalString(String user, String accessKey, String date, String dateTime, String region, String expiryPeriod, String hostName, String port) throws Exception { canonicalQueryParameters.put("Action", action); canonicalQueryParameters.put("DBUser", user); canonicalQueryParameters.put("X-Amz-Algorithm", "AWS4-HMAC-SHA256"); canonicalQueryParameters.put("X-Amz-Credential", accessKey + "%2F" + date + "%2F" + region + "%2F" + serviceName + "%2Faws4_request"); canonicalQueryParameters.put("X-Amz-Date", dateTime); canonicalQueryParameters.put("X-Amz-Expires", expiryPeriod); canonicalQueryParameters.put("X-Amz-SignedHeaders", signedHeader); String canonicalQueryString = ""; while(!canonicalQueryParameters.isEmpty()) { String currentQueryParameter = canonicalQueryParameters.firstKey(); String currentQueryParameterValue = canonicalQueryParameters.remove(currentQueryParameter); canonicalQueryString = canonicalQueryString + currentQueryParameter + "=" + currentQueryParameterValue; if (!currentQueryParameter.equals("X-Amz-SignedHeaders")) { canonicalQueryString += "&"; } } String canonicalHeaders = "host:" + hostName + ":" + port + '\n'; requestWithoutSignature = hostName + ":" + port + "/?" + canonicalQueryString; String hashedPayload = BinaryUtils.toHex(hash(payload)); return httpMethod + '\n' + canonicalURIParameter + '\n' + canonicalQueryString + '\n' + canonicalHeaders + '\n' + signedHeader + '\n' + hashedPayload; } //Step 2: Create a string to sign using sig v4 public static String createStringToSign(String dateTime, String canonicalRequest, String accessKey, String date, String region) throws Exception { String credentialScope = date + "/" + region + "/" + serviceName + "/aws4_request"; return algorithm + '\n' + dateTime + '\n' + credentialScope + '\n' + BinaryUtils.toHex(hash(canonicalRequest)); } //Step 3: Calculate signature /** * Step 3 of the &AWS; Signature version 4 calculation. It involves deriving * the signing key and computing the signature. Refer to * http://docs.aws.amazon * .com/general/latest/gr/sigv4-calculate-signature.html */ public static byte[] calculateSignature(String stringToSign, byte[] signingKey) { return sign(stringToSign.getBytes(Charset.forName("UTF-8")), signingKey, SigningAlgorithm.HmacSHA256); } public static byte[] sign(byte[] data, byte[] key, SigningAlgorithm algorithm) throws SdkClientException { try { Mac mac = algorithm.getMac(); mac.init(new SecretKeySpec(key, algorithm.toString())); return mac.doFinal(data); } catch (Exception e) { throw new SdkClientException( "Unable to calculate a request signature: " + e.getMessage(), e); } } public static byte[] newSigningKey(String secretKey, String dateStamp, String regionName, String serviceName) { byte[] kSecret = ("AWS4" + secretKey).getBytes(Charset.forName("UTF-8")); byte[] kDate = sign(dateStamp, kSecret, SigningAlgorithm.HmacSHA256); byte[] kRegion = sign(regionName, kDate, SigningAlgorithm.HmacSHA256); byte[] kService = sign(serviceName, kRegion, SigningAlgorithm.HmacSHA256); return sign(AWS4_TERMINATOR, kService, SigningAlgorithm.HmacSHA256); } public static byte[] sign(String stringData, byte[] key, SigningAlgorithm algorithm) throws SdkClientException { try { byte[] data = stringData.getBytes(UTF8); return sign(data, key, algorithm); } catch (Exception e) { throw new SdkClientException( "Unable to calculate a request signature: " + e.getMessage(), e); } } //Step 4: append the signature public static String appendSignature(String signature) { return requestWithoutSignature + "&X-Amz-Signature=" + signature; } public static byte[] hash(String s) throws Exception { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(s.getBytes(UTF8)); return md.digest(); } catch (Exception e) { throw new SdkClientException( "Unable to compute hash while signing request: " + e.getMessage(), e); } } }

Connecting to a DB instance

The following code example shows how to generate an authentication token, and then use it to connect to an instance running MariaDB or MySQL.

To run this code example, you need the AWS SDK for Java, found on the AWS site. In addition, you need the following:

  • MySQL Connector/J. This code example was tested with mysql-connector-java-5.1.33-bin.jar.

  • An intermediate certificate for Amazon RDS that is specific to an AWS Region. (For more information, see Using SSL/TLS to encrypt a connection to a DB instance or cluster.) At runtime, the class loader looks for the certificate in the same directory as this Java code example, so that the class loader can find it.

  • Modify the values of the following variables as needed:

    • RDS_INSTANCE_HOSTNAME – The host name of the DB instance that you want to access.

    • RDS_INSTANCE_PORT – The port number used for connecting to your PostgreSQL DB instance.

    • REGION_NAME – The AWS Region where the DB instance is running.

    • DB_USER – The database account that you want to access.

    • SSL_CERTIFICATE – An SSL certificate for Amazon RDS that is specific to an AWS Region.

      To download a certificate for your AWS Region, see Using SSL/TLS to encrypt a connection to a DB instance or cluster. Place the SSL certificate in the same directory as this Java program file, so that the class loader can find the certificate at runtime.

This code example obtains AWS credentials from the default credential provider chain.

Note

Specify a password for DEFAULT_KEY_STORE_PASSWORD other than the prompt shown here as a security best practice.

package com.amazonaws.samples; import com.amazonaws.services.rds.auth.RdsIamAuthTokenGenerator; import com.amazonaws.services.rds.auth.GetIamAuthTokenRequest; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.auth.AWSStaticCredentialsProvider; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.security.KeyStore; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; import java.net.URL; public class IAMDatabaseAuthenticationTester { //&AWS; Credentials of the IAM user with policy enabling IAM Database Authenticated access to the db by the db user. private static final DefaultAWSCredentialsProviderChain creds = new DefaultAWSCredentialsProviderChain(); private static final String AWS_ACCESS_KEY = creds.getCredentials().getAWSAccessKeyId(); private static final String AWS_SECRET_KEY = creds.getCredentials().getAWSSecretKey(); //Configuration parameters for the generation of the IAM Database Authentication token private static final String RDS_INSTANCE_HOSTNAME = "rdsmysql.123456789012.us-west-2.rds.amazonaws.com"; private static final int RDS_INSTANCE_PORT = 3306; private static final String REGION_NAME = "us-west-2"; private static final String DB_USER = "jane_doe"; private static final String JDBC_URL = "jdbc:mysql://" + RDS_INSTANCE_HOSTNAME + ":" + RDS_INSTANCE_PORT; private static final String SSL_CERTIFICATE = "rds-ca-2019-us-west-2.pem"; private static final String KEY_STORE_TYPE = "JKS"; private static final String KEY_STORE_PROVIDER = "SUN"; private static final String KEY_STORE_FILE_PREFIX = "sys-connect-via-ssl-test-cacerts"; private static final String KEY_STORE_FILE_SUFFIX = ".jks"; private static final String DEFAULT_KEY_STORE_PASSWORD = "changeit"; public static void main(String[] args) throws Exception { //get the connection Connection connection = getDBConnectionUsingIam(); //verify the connection is successful Statement stmt= connection.createStatement(); ResultSet rs=stmt.executeQuery("SELECT 'Success!' FROM DUAL;"); while (rs.next()) { String id = rs.getString(1); System.out.println(id); //Should print "Success!" } //close the connection stmt.close(); connection.close(); clearSslProperties(); } /** * This method returns a connection to the db instance authenticated using IAM Database Authentication * @return * @throws Exception */ private static Connection getDBConnectionUsingIam() throws Exception { setSslProperties(); return DriverManager.getConnection(JDBC_URL, setMySqlConnectionProperties()); } /** * This method sets the mysql connection properties which includes the IAM Database Authentication token * as the password. It also specifies that SSL verification is required. * @return */ private static Properties setMySqlConnectionProperties() { Properties mysqlConnectionProperties = new Properties(); mysqlConnectionProperties.setProperty("verifyServerCertificate","true"); mysqlConnectionProperties.setProperty("useSSL", "true"); mysqlConnectionProperties.setProperty("user",DB_USER); mysqlConnectionProperties.setProperty("password",generateAuthToken()); return mysqlConnectionProperties; } /** * This method generates the IAM Auth Token. * An example IAM Auth Token would look like follows: * btusi123---cmz7kenwo2ye---rds---cn-north-1.amazonaws.com.rproxy.goskope.com.cn:3306/?Action=connect&DBUser=iamtestuser&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20171003T010726Z&X-Amz-SignedHeaders=host&X-Amz-Expires=899&X-Amz-Credential=AKIAPFXHGVDI5RNFO4AQ%2F20171003%2Fcn-north-1%2Frds-db%2Faws4_request&X-Amz-Signature=f9f45ef96c1f770cdad11a53e33ffa4c3730bc03fdee820cfdf1322eed15483b * @return */ private static String generateAuthToken() { BasicAWSCredentials awsCredentials = new BasicAWSCredentials(AWS_ACCESS_KEY, AWS_SECRET_KEY); RdsIamAuthTokenGenerator generator = RdsIamAuthTokenGenerator.builder() .credentials(new AWSStaticCredentialsProvider(awsCredentials)).region(REGION_NAME).build(); return generator.getAuthToken(GetIamAuthTokenRequest.builder() .hostname(RDS_INSTANCE_HOSTNAME).port(RDS_INSTANCE_PORT).userName(DB_USER).build()); } /** * This method sets the SSL properties which specify the key store file, its type and password: * @throws Exception */ private static void setSslProperties() throws Exception { System.setProperty("javax.net.ssl.trustStore", createKeyStoreFile()); System.setProperty("javax.net.ssl.trustStoreType", KEY_STORE_TYPE); System.setProperty("javax.net.ssl.trustStorePassword", DEFAULT_KEY_STORE_PASSWORD); } /** * This method returns the path of the Key Store File needed for the SSL verification during the IAM Database Authentication to * the db instance. * @return * @throws Exception */ private static String createKeyStoreFile() throws Exception { return createKeyStoreFile(createCertificate()).getPath(); } /** * This method generates the SSL certificate * @return * @throws Exception */ private static X509Certificate createCertificate() throws Exception { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); URL url = new File(SSL_CERTIFICATE).toURI().toURL(); if (url == null) { throw new Exception(); } try (InputStream certInputStream = url.openStream()) { return (X509Certificate) certFactory.generateCertificate(certInputStream); } } /** * This method creates the Key Store File * @param rootX509Certificate - the SSL certificate to be stored in the KeyStore * @return * @throws Exception */ private static File createKeyStoreFile(X509Certificate rootX509Certificate) throws Exception { File keyStoreFile = File.createTempFile(KEY_STORE_FILE_PREFIX, KEY_STORE_FILE_SUFFIX); try (FileOutputStream fos = new FileOutputStream(keyStoreFile.getPath())) { KeyStore ks = KeyStore.getInstance(KEY_STORE_TYPE, KEY_STORE_PROVIDER); ks.load(null); ks.setCertificateEntry("rootCaCertificate", rootX509Certificate); ks.store(fos, DEFAULT_KEY_STORE_PASSWORD.toCharArray()); } return keyStoreFile; } /** * This method clears the SSL properties. * @throws Exception */ private static void clearSslProperties() throws Exception { System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStoreType"); System.clearProperty("javax.net.ssl.trustStorePassword"); } }

If you want to connect to a DB instance through a proxy, see Connecting to a proxy using IAM authentication.