r2dbc-spi
r2dbc-spi copied to clipboard
Introduce support for dynamic credentials
Right now, we support static credentials provided through ConnectionFactoryOptions
. It would be neat to support credential rotation (or dynamic credentials) through a Publisher<? extends Credential>
. Credential
could be a marker interface with a concrete UserPasswordCredential
implementation as a general case. Driver implementations could ship with their own extensions for other authentication types.
Dynamic credentials require programmatic configuration and cannot be parsed from a connection URL.
This would greatly help us, we're unable to extend the underlying oracle connection factory we're using to allow for new credential retrieval. Our current work around is setting the connection pool to be of fixed size and hope we don't lose connections...
One idea is to specify that a Credential is mutable, as this would allow security sensitive values can be cleared from memory. I've explored with this idea in the code that follows.
/**
* Credentials used to authenticate with a database. Credential objects are
* mutable. Mutability allows any security sensitive values retained by a
* {@code Credential} to be cleared from memory. Drivers MUST NOT retain a
* reference to a {@code Credential} object after consuming it for database
* authentication.
*/
interface Credential {
}
Consistent with the existing PASSWORD option, CharSequence might work well for storing a password as a UserPasswordCredential:
interface UserPasswordCredential extends Credential {
String user();
CharSequence password();
}
Would we want to have a factory for creating Credential objects?
/** Factory for creating {@link Credential} objects */
public static class Credentials {
/**
* Returns a new {@link AsyncLock2.UserPasswordCredential}
* @param user Username. Not null.
* @param password Password. Not null.
*/
public static UserPasswordCredential createUserPasswordCredential(
String user, CharSequence password) {
record UserPasswordCredentialImpl(String user, CharSequence password)
implements UserPasswordCredential { }
return new UserPasswordCredentialImpl(user, password);
}
}
With an Option constant declared as ...
public static final Option<Publisher<? extends Credential>> CREDENTIAL_PUBLISHER =
Option.valueOf("credentialPublisher");
... we can have user code like this:
static char[] getPassword() {
// Example code below. Real code could obtain a password from the file
// system, user input, etc
return "the password".toCharArray();
}
/**
* Publishes the password and clears it from memory after
* consumption or cancellation by a downstream subscriber.
*/
static Publisher<CharSequence> createPasswordPublisher() {
return Mono.using(
// On each subscribe, return a password char array
() -> getPassword(),
// Map the char[] to Publisher<CharSequence>
password -> Mono.just(CharBuffer.wrap(password)),
// Clear the char[] after emitting onComplete/onError
password -> Arrays.fill(password, (char)0));
}
/**
* Publishes a database connection authenticated by a
* {@link UserPasswordCredential}
*/
public static Publisher<? extends Connection> connect() {
return ConnectionFactories.get(ConnectionFactoryOptions.parse(
"r2dbc:driver:@host:port/database")
.mutate()
.option(
CREDENTIAL_PUBLISHER,
Mono.from(createPasswordPublisher())
.map(password ->
Credentials.createUserPasswordCredential("TheUser", password)))
.build())
.create();
}