使用 IAM 身份验证和 Spring JDBC(DataSource 和 JdbcTemplate)访问 AWS RDS

问题描述 投票:0回答:3

我不知道如何实现这一点。任何帮助和/或指示将不胜感激。

目前,我的 Java/Spring 应用程序后端部署在 EC2 上,并使用常规 Spring JDBC 设置成功访问 RDS 上的 MySQL。也就是说,将数据库信息存储在

application.properties
中,并在
@Configuration
类中配置
DataSource
JdbcTemplate。一切正常。

现在,我需要安全地访问 RDS 上的 MySQL。 RDS 实例启用了 IAM 身份验证。我还成功创建了IAM角色并应用了内联策略。然后,按照此链接上的 AWS RDS 文档和 Java 示例,我可以使用 身份验证令牌和我创建的用户(而不是常规数据库用户名和密码)从独立 Java 类成功访问数据库。这个独立的 Java 类直接处理“Connection”对象。

我陷入困境的地方是如何将其转换为 Spring JDBC 配置。也就是说,在我的 @Configuration 类中为此设置

DataSource
JdbcTemplate
beans。

实现这一点的正确/正确方法是什么?

----- 编辑 - 开始 -----

我正在尝试将其实现为一个可用于多个项目的库。也就是说,它将用作 JAR 并在项目的 POM 文件中声明为依赖项。该库将包括可配置的 AWS 服务,例如使用通用数据库用户名和密码的 RDS 访问、使用 IAM 身份验证的 RDS 访问、用于数据加密的 KMS(CMK/数据密钥)等。

想法是根据项目在任何 Web/应用程序服务器上使用此库。

希望这能进一步澄清我的需求。

----- 编辑 - 结束 -----

DataSource 内部有 getConnection() 所以我基本上可以创建自己的 DataSource 实现来实现我想要的。但这是个好方法吗?

类似:

public class MyDataSource implements DataSource {
    @Override
    public Connection getConnection() throws SQLException {
        Connection conn = null;
        // get a connection using IAM Authentication Token for accessing AWS RDS, etc. as in the AWS docs
        return conn;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return getConnection();
    }

    //other methods
} 
java architecture amazon-rds spring-jdbc amazon-iam
3个回答
13
投票

您可以使用以下代码片段替换 SpringBoot/Tomcat 提供的默认连接池。由于令牌的有效期为 15 分钟,因此它将每 10 分钟刷新一次令牌密码。此外,它还假设可以从 DNS 主机名中提取区域。如果不是这种情况,您需要指定要使用的区域。

public class RdsIamAuthDataSource extends org.apache.tomcat.jdbc.pool.DataSource {

private static final Logger LOG = LoggerFactory.getLogger(RdsIamAuthDataSource.class);

/**
 * The Java KeyStore (JKS) file that contains the Amazon root CAs
 */
public static final String RDS_CACERTS = "/rds-cacerts";
/**
 * Password for the ca-certs file.
 */
public static final String PASSWORD = "changeit";
public static final int DEFAULT_PORT = 3306;

@Override
public ConnectionPool createPool() throws SQLException {
    return pool != null ? pool : createPoolImpl();
}

protected synchronized ConnectionPool createPoolImpl() throws SQLException {
    return pool = new RdsIamAuthConnectionPool(poolProperties);
}

public static class RdsIamAuthConnectionPool extends ConnectionPool implements Runnable {

    private RdsIamAuthTokenGenerator rdsIamAuthTokenGenerator;
    private String host;
    private String region;
    private int port;
    private String username;
    private Thread tokenThread;

    public RdsIamAuthConnectionPool(PoolConfiguration prop) throws SQLException {
        super(prop);
    }

    @Override
    protected void init(PoolConfiguration prop) throws SQLException {
        try {
            URI uri = new URI(prop.getUrl().substring(5));
            this.host = uri.getHost();
            this.port = uri.getPort();
            if (this.port < 0) {
                this.port = DEFAULT_PORT;
            }
            this.region = StringUtils.split(this.host,'.')[2]; // extract region from rds hostname
            this.username = prop.getUsername();
            this.rdsIamAuthTokenGenerator = RdsIamAuthTokenGenerator.builder().credentials(new DefaultAWSCredentialsProviderChain()).region(this.region).build();
            updatePassword(prop);
            final Properties props = prop.getDbProperties();
            props.setProperty("useSSL","true");
            props.setProperty("requireSSL","true");
            props.setProperty("trustCertificateKeyStoreUrl",getClass().getResource(RDS_CACERTS).toString());
            props.setProperty("trustCertificateKeyStorePassword", PASSWORD);
            super.init(prop);
            this.tokenThread = new Thread(this, "RdsIamAuthDataSourceTokenThread");
            this.tokenThread.setDaemon(true);
            this.tokenThread.start();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public void run() {
        try {
            while (this.tokenThread != null) {
                Thread.sleep(10 * 60 * 1000); // wait for 10 minutes, then recreate the token
                updatePassword(getPoolProperties());
            }
        } catch (InterruptedException e) {
            LOG.debug("Background token thread interrupted");
        }
    }

    @Override
    protected void close(boolean force) {
        super.close(force);
        Thread t = tokenThread;
        tokenThread = null;
        if (t != null) {
            t.interrupt();
        }
    }

    private void updatePassword(PoolConfiguration props) {
        String token = rdsIamAuthTokenGenerator.getAuthToken(GetIamAuthTokenRequest.builder().hostname(host).port(port).userName(this.username).build());
        LOG.debug("Updated IAM token for connection pool");
        props.setPassword(token);
    }
}
}

请注意,您需要导入亚马逊的根/中间证书才能建立可信连接。上面的示例代码假设证书已导入到名为“rds-cacert”的文件中,并且在类路径上可用。或者,您也可以将它们导入 JVM 'cacerts' 文件中。

要使用此数据源,您可以使用 Spring 的以下属性:

datasource:
  url: jdbc:mysql://dbhost.xyz123abc.us-east-1.rds.amazonaws.com/dbname
  username: iam_app_user
  driver-class-name: com.mysql.cj.jdbc.Driver
  type: com.mydomain.jdbc.RdsIamAuthDataSource

使用 Spring Java 配置:

@Bean public DataSource dataSource() { 
    PoolConfiguration props = new PoolProperties(); 
    props.setUrl("jdbc:mysql://dbname.abc123xyz.us-east-1.rds.amazonaws.com/dbschema"); 
    props.setUsername("iam_dbuser_app"); 
    props.setDriverClassName("com.mysql.jdbc.Driver"); 
    return new RdsIamAuthDataSource(props); 
}

更新:使用 MySQL 时,您还可以决定使用 MariaDB JDBC 驱动程序,它内置了对 IAM 身份验证的支持:

spring:
  datasource:
    host: dbhost.cluster-xxx.eu-west-1.rds.amazonaws.com
    url: jdbc:mariadb:aurora//${spring.datasource.host}/db?user=xxx&credentialType=AWS-IAM&useSsl&serverSslCert=classpath:rds-combined-ca-bundle.pem
    type: org.mariadb.jdbc.MariaDbPoolDataSource

上面需要 MariaDB 和 AWS SDK 库,并且需要类路径中的 CA-bundle


10
投票

2023 年更新!

现在可以使用AWS JDBC Driver for MySQL支持IAM数据库身份验证更轻松地完成此任务。我使用 Spring Boot 3 和 spring-boot-starter-jdbc 测试了此配置。

添加以下依赖项:

runtimeOnly 'software.aws.rds:aws-mysql-jdbc:1.1.6'
runtimeOnly 'software.amazon.awssdk:rds:2.20.57'

将以下内容添加到application.yml:

spring:
  datasource:
    jdbc-url: jdbc:mysql:aws://yourdbcluster-xxxx.cluster-xxxx.your-region.rds.amazonaws.com:3306/yourdb?useAwsIam=true
    username: iam_username
    #password: dont-need-this
    driver-class-name: software.aws.rds.jdbc.mysql.Driver

它应该可以工作!

驱动程序使用 AWS 默认凭证提供程序链,因此请确保您拥有带有策略的凭证,允许在任何运行应用程序的地方进行 IAM 数据库访问。如果您还使用故障转移支持,请务必阅读 github 上的所有驱动程序文档。希望此更新对其他人有帮助!


*这是使用 MariaDB 驱动程序的旧方法 - 请参阅上面的更新!

我知道这是一个较旧的问题,但经过一番搜索后,我发现了一种非常简单的方法,您现在可以使用 MariaDB 驱动程序来执行此操作。在 2.5 版本中,他们向驱动程序添加了一个 AWS IAM 凭证插件。它将自动处理令牌的生成、缓存和刷新。

我已经使用 Spring Boot 2.3 和默认的 HikariCP 连接池进行了测试,通过这些设置它对我来说工作得很好:

spring.datasource.url=jdbc:mariadb://host/db?credentialType=AWS-IAM&useSsl&serverSslCert=classpath:rds-combined-ca-bundle.pem
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=iam_username
#spring.datasource.password=dont-need-this
spring.datasource.hikari.maxLifetime=600000

下载 rds-combined-ca-bundle.pem 并将其放入

src/main/resources
,以便您可以通过 SSL 连接。
您还需要类路径上的这些依赖项:

runtime 'org.mariadb.jdbc:mariadb-java-client'
runtime 'com.amazonaws:aws-java-sdk-rds:1.11.880'

驱动程序使用标准

DefaultAWSCredentialsProviderChain
,因此请确保您拥有带有策略的凭证,允许在任何运行应用程序的地方进行 IAM 数据库访问。

希望这对其他人有帮助 - 我在网上找到的大多数示例都涉及自定义代码、后台线程等 - 但使用新的驱动程序功能要容易得多!


0
投票

有一个库可以让这变得简单。实际上,您只需重写 HikariDataSource 中的 getPassword() 方法即可。您使用 STS 承担该角色并发送该角色的“密码”。

<dependency>
  <groupId>io.volcanolabs</groupId>
  <artifactId>rds-iam-hikari-datasource</artifactId>
  <version>3.0.0</version>
</dependency> 
© www.soinside.com 2019 - 2024. All rights reserved.