harsh
harsh

Reputation: 1551

Iam authentication with r2dbc-postgresql

For working with IAM authentication for a DB, a dynamic password is required to refresh the connection pool with new credentials. in r2dbc-postgresql I could not find any possibility of a custom datasource like the one in HikariCP. Also there is no option to supply a dynamic password as concrete final class is used for config (PostgresConnectionConfiguration)

Any hints for a workaround?

Upvotes: 2

Views: 911

Answers (1)

I was just deep diving into this and trying to find a solution and I think I have it.

Although there is not any current support as mentioned by Anubis, you could definitely implement not a *ConnectionConfiguration, but a ConnectionFactory.

This approach should work for any need you have for rotating credentials, IAM Auth, rotating credentials via AWS Secrets Manager and any others. Also, I suppose it will work with any driver, as it is constructed with generic r2dbc-spi.

Below, a simple implementation for IAM Auth in Kotlin

fun interface ConnectionFactoryGenerator {
  // user and pass just for generic usage
  fun generate(username: String, password: String): ConnectionFactory
}

class RdsIamConnectionFactory(
    private val client: RdsAsyncClient,
    private val host: String,
    private val port: Int,
    private val username: String,
    private val generator: ConnectionFactoryGenerator
) : ConnectionFactory {

  // ugly, I know, but you need this to be the same metadata as underlying connection
  private val metadata = generator.generate(username, "fake").metadata

  override fun create(): Publisher<Connection> =
      Mono.defer {
          GenerateAuthenticationTokenRequest.builder()
              .username(username)
              .port(port)
              .hostname(host)
              .build()
              .let(client.utilities()::generateAuthenticationToken)
              .let { generator.generate(username, it) }
              .create()
              .let { Mono.from(it) }
      }

    override fun getMetadata(): ConnectionFactoryMetadata {
      return metadata
    }
}

You can use this like:

RdsIamConnectionFactory(
    rdsAsyncClient,
    databaseEnv.host,
    databaseEnv.port,
    databaseEnv.username,
) { _, pass ->
      ... setup your specific connection factory here ...
}

Now, this code is just good enough for me, but if you are worried that each connection is creating almost equal instances of ConnectionConfiguration / ConnectionFactory, you can always improve it with your own code on top of it.

In case you have any trouble testing this code, make sure you have:

  1. Added RDS certificates to Java certificate chain (both main and intermediate certificates)
  2. Created a user that is enabled to IAM authentication (role rds_iam in Postgres for example)
  3. Correctly configured your connection pool (max idle and life time, according to your DB configuration)

Hope this helps, as I was trying to figure this out for a long time now and just decided to solve this.

Upvotes: 0

Related Questions