r2dbc / r2dbc-spi

Service Provider Interface for R2DBC Implementations

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Introduce support for dynamic credentials

mp911de opened this issue · comments

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.

commented

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

@calebcodesgud , could you please help me with user code for this? Should I have a class extending Subscriber ? What would be my logic on overriding subscribe method?

commented

@barathnagarajan It's not implemented yet, watch the status of this PR
#274