Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plugin manager to allow dynamically supplying passwords #2369

Merged
merged 4 commits into from Dec 28, 2021

Conversation

sehrope
Copy link
Member

@sehrope sehrope commented Dec 20, 2021

This is a revised version of #2321. I'll close that one out in favor of this.

The first two commits do some refactoring to consolidate where username and passwords are pulled from the properties map and removing those args from the various internal creation methods. This simplifies the changeset for actual feature which adds a plugin hook for dynamically providing the password. The plugin callback also provides the authentication type so the caller has a bit more information to make a decision (e.g. the caller can ensure they are using SCRAM and not sending the password over plaintext).

Because of the message flow of the driver, if the server is configured with trust authentication then no callback will be invoked.

@davecramer While reviewing, in particular take a peek at the public interfaces to make sure it makes sense as we'll be stuck with them.

All Submissions:

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

New Feature Submissions:

  1. Does your submission pass tests?
  2. Does ./gradlew autostyleCheck checkstyleAll pass ?

…ties

Changes internal constructors for PgConnection and related classes to only accept the
connection properties object and remove the user and password arguments. Any locations
that required those fields can retrieve them from the properties map.

AuthenticationPlugin authPlugin;
try {
authPlugin = (AuthenticationPlugin) ObjectFactory.instantiate(authPluginClassName, info,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to cache the instance ? Is it possible that different connections would use different authentication plugins ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it should not be cached as connections should be isolated from each other. If a user needs to maintain state across multiple runtime instantiations then it would need to be done in the user's implementation through either static fields or some kind of proxying.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about that a bit more, perhaps we need a new opaque "arg" property that users can use to customize their implementation. The plugin constructor takes the connection properties but there's no specific property that user's could use for their own purposes. Something like authPluginArg (e.g. the plugin equivalent of socketFactoryArg).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fairly good chance that the user will only use one auth plugin. I wonder what the cost of instantiating a class really is (in other words is it worth worrying about)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I doubt it's an issue in practice and connections tend to be long lived anyway. The time spent opening the underlying socket and TLS handshake is going to be significantly more than any object creation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough.

@davecramer
Copy link
Member

I wonder why this is failing on windows ?

@sehrope
Copy link
Member Author

sehrope commented Dec 21, 2021

Looks like two separate issues failing the AppVeyor build but neither of them is actually Windows related.

The v9.6 environment is failing because the testing isn't correctly being skipped on older platforms. The v10 environment is random failures from gremlins race conditions that query timeout test.

I'm going to push a fix for the test skipping.

…t runtime

Adds authenticationPluginClassName connection property that allows end users to specify a class
that will provide the connection passwords at runtime. This allows configuring a connection with
a password that must be generated on the fly or periodically changes.

Custom implementations have access to the full connection properties and the type of authentication
that is being requested by the server, e.g. MD5 or SCRAM.
@sehrope sehrope force-pushed the pr-2321-add-plugin-manager-v4 branch from e8ec934 to b95ef95 Compare December 21, 2021 13:55
@davecramer
Copy link
Member

@sehrope I don't see any reason not to merge this.

@sehrope
Copy link
Member Author

sehrope commented Dec 28, 2021

Sounds good. I'll merge this in a bit.

@sehrope sehrope merged commit 473091a into pgjdbc:master Dec 28, 2021
Comment on lines +13 to +14
@Nullable
String getPassword(AuthenticationRequestType type) throws PSQLException;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security best practices suggest that passwords should be used as char[] so they can be nullified after use: https://www.baeldung.com/java-storing-passwords

@@ -142,6 +142,7 @@ In addition to the standard connection parameters the driver supports a number o
| adaptiveFetchMaximum | Integer | -1 | Specifies maximum number of rows, which can be calculated by adaptiveFetch. Number of rows used by adaptiveFetch cannot go above this value. Any negative number set as adaptiveFetchMaximum is used by adaptiveFetch as infinity number of rows.
| localSocketAddress | String | null | Hostname or IP address given to explicitly configure the interface that the driver will bind the client side of the TCP/IP connection to when connecting.
| quoteReturningIdentifiers | Boolean | true | By default we double quote returning identifiers. Some ORM's already quote them. Switch allows them to turn this off
| authenticationPluginClassName | String | null | Fully qualified class name of the class implementing the AuthenticationPlugin interface. If this is null, the password value in the connection properties will be used.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frankly speaking, I believe the naming is misleading. What the thing does it supplies passwords, so it is not clear why do we call it authenticationPlugin rather than passwordCallback or something like that.

There's a similar interface in Java: javax.security.auth.callback.PasswordCallback

@davecramer
Copy link
Member

@sehrope I think these suggestions make sense. Thoughts ?

Comment on lines +12 to +16
@RunWith(Suite.class)
@SuiteClasses({
AuthenticationPluginTest.class,
})
public class PluginTestSuite {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need suites? I think we no longer use them.

@sehrope
Copy link
Member Author

sehrope commented Dec 30, 2021

Security best practices suggest that passwords should be used as char[] so they can be nullified after use: https://www.baeldung.com/java-storing-passwords

I considered having the interface be char[] when I put this together but stuck with String as the rest of the driver deals with String passwords directly (e.g. receiving them via java.util.Properties, passing them as a String into the SASL functions). It'd have been a much larger change to cascade it through all the downstream call sites. I'm not against it, just concerned that the overall changes required to meaningfully support that are larger and more likely to break something.

Do we need suites? I think we no longer use them.

In that case we should get rid of all of the suites. It was added to be consistent with the existing code.

Frankly speaking, I believe the naming is misleading. What the thing does it supplies passwords, so it is not clear why do we call it authenticationPlugin rather than passwordCallback or something like that.

There's a similar interface in Java: javax.security.auth.callback.PasswordCallback

Well to be fair, I did specifically request feedback on the public interface naming...

I picked "Auth..." over simply "Password..." because it's meant to handle the authentication stage of the protocol. In most cases that involves a password but it might be extended to handle a non-password step in the future. The interface also includes the additional parameter for the authentication type (e.g. plain, MD5, or SASL) to allow the client further customize it's handling (e.g. reject sending things over in plaintext).

I'm fine with changing the name to something more appropriate but it's more than just a password callback.

@vlsi
Copy link
Member

vlsi commented Dec 30, 2021

I considered having the interface be char[] when I put this together but stuck with String as the rest of the driver deals with String passwords directly

I think we should fix that as much as we can.
I believe we just missed it.

I don't know what to do with Properties, however, we could probably refactor internals to char[] for better security.

Well to be fair, I did specifically request feedback on the public interface naming

Could you please clarify the use-case?
What would the end-to-end use of the feature?

@sehrope
Copy link
Member Author

sehrope commented Dec 30, 2021

The original user issue that lead to this and #2321 (replaced by this PR) is this: #2102

It's for connecting to systems like RDS that allow for passwords that are really short lived digital signatures. They can't be static values as they expire in a matter of minutes so a connection pool that would be periodically creating new connections would need to generate them on the fly. Rather than supporting N different mechanisms directly, the idea is to have the user supply a class that provides the password on the fly at connect time. That way the digital signature or whatever else is involved to generate the password can happen at the last possible moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants