Conexión al clúster de base de datos mediante la autenticación de IAM y el AWS SDK for Java - Amazon Aurora

Conexión al clúster de base de datos mediante la autenticación de IAM y el AWS SDK for Java

Puede conectarse a un clúster de bases de datos de Aurora MySQL o Aurora PostgreSQL con el AWS SDK for Java como se describe a continuación.

Requisitos previos

A continuación, se muestran requisitos previos para conectarse al clúster de de base de datos mediante la autenticación de IAM:

Para ver ejemplos de cómo usar el SDK para Java 2.x, consulte Ejemplos de Amazon RDS que utilizan SDK para Java 2.x.

Generación de un token de autenticación de IAM

Si escribe programas mediante AWS SDK for Java, puede obtener un token de autenticación firmado mediante la clase RdsIamAuthTokenGenerator. El uso de esta clase requiere que proporcione las credenciales de AWS. Para hacer esto, puede crear una instancia de la clase DefaultAWSCredentialsProviderChain. DefaultAWSCredentialsProviderChain usa la primera clave de acceso y clave secreta de AWS que encuentra en la cadena predeterminada de proveedores de credenciales. A fin de obtener más información acerca de las claves de acceso de AWS, consulte Administración de claves de acceso para usuarios.

nota

No puede utilizar un registro DNS personalizado de Route 53 ni un punto de conexión personalizado de Aurora en lugar del punto de conexión de el clúster de base de datos para generar el token de autenticación.

Tras crear una instancia de RdsIamAuthTokenGenerator, puede llamar al método getAuthToken para obtener un token firmado. Proporcione la región de AWS el nombre de host, el número de puerto y el nombre de usuario. En el siguiente ejemplo de código se ilustra cómo hacerlo.

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; } }

Creación manual de un token de autenticación de IAM

En Java, la forma más sencilla de generar un token de autenticación es usar RdsIamAuthTokenGenerator. Esta clase crea automáticamente un token de autenticación y, a continuación, lo firma mediante AWS Signature versión 4. Para obtener más información, consulte Proceso de firma Signature Version 4 en la Referencia general de AWS.

Sin embargo, también puede crear y firmar un token de autenticación manualmente, como se muestra en el siguiente ejemplo de código.

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); } } }

Conexión a su clúster de bases de datos

El siguiente ejemplo de código muestra cómo generar un token de autenticación y, a continuación, usarlo para conectarse a un clúster que ejecuta Aurora MySQL.

Para ejecutar este ejemplo de código, necesita AWS SDK for Java, que se encuentra en el sitio de AWS. Además, necesitará lo siguiente:

  • MySQL Connector/J. Este ejemplo de código se ha probado con mysql-connector-java-5.1.33-bin.jar.

  • Un certificado intermedio para Amazon Aurora que es específico de una región de AWS. (Para obtener más información, consulte Uso de SSL/TLS para cifrar una conexión a un clúster de base de datos.) En tiempo de ejecución, el cargador de clases busca el certificado en el mismo directorio que este ejemplo de código Java, de modo que el cargador de clases pueda encontrarlo.

  • Modifique los valores de las siguientes variables según sea necesario:

    • RDS_INSTANCE_HOSTNAME: el nombre de anfitrión del clúster de bases de datos que desea acceder.

    • RDS_INSTANCE_PORT: el número de puerto usado para conectarse al clúster de bases de datos de PostgreSQL.

    • REGION_NAME: la región de AWS en la que se ejecuta el clúster de bases de datos.

    • DB_USER: la cuenta de base de datos a la que desea acceder.

    • SSL_CERTIFICATE: un certificado de SSL para Amazon Aurora que es específico de una región de AWS.

      Para descargar un certificado para su región de AWS, consulte Uso de SSL/TLS para cifrar una conexión a un clúster de base de datos. Coloque el certificado SSL en el mismo directorio que este archivo de programa Java, de modo que el cargador de clases pueda encontrar el certificado en tiempo de ejecución.

En este ejemplo de código, se obtienen las credenciales de AWS de la cadena predeterminada de proveedores de credenciales.

nota

Especifique una contraseña para DEFAULT_KEY_STORE_PASSWORD que no sea la que se muestra aquí como práctica recomendada de seguridad.

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.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"); } }

Si desea conectarse a un clúster de base de datos a través de un proxy, consulte Conexión a un proxy mediante autenticación de IAM.