Skip to content

Commit 8afa542

Browse files
committed
JVMCBC-1679 Support short lived mTLS certs refresh without application restart
Motivation ---------- Support credential rotation. Keep the code that load new credentials off the hot path; discourage blocking low-level async I/O threads. Modifications ------------- Add DelegatingAuthenticator which delegates to another authenticator the user can swap at runtime. Change-Id: I53bbcfde090b8d50cf2e57e76601fc1b39459c6d Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/233162 Tested-by: Build Bot <[email protected]> Reviewed-by: Michael Reiche <[email protected]>
1 parent 26eea73 commit 8afa542

File tree

4 files changed

+139
-5
lines changed

4 files changed

+139
-5
lines changed

core-io/src/main/java/com/couchbase/client/core/env/Authenticator.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@
2626
import reactor.util.annotation.Nullable;
2727

2828
/**
29-
* The {@link Authenticator} encapsulates authentication strategies.
29+
* An authentication strategy.
30+
* <p>
31+
* <b>Note:</b> The methods of this interface are part of the SDK's internal API, and may change at any time.
32+
* Implementing this interface yourself is not recommended. Please use one of the provided implementations.
3033
*
31-
* <p>Please only use the implementations of this class, since the actual interfaces are unstable, internal
32-
* and may change at any time!</p>
34+
* @see PasswordAuthenticator
35+
* @see CertificateAuthenticator
36+
* @see DelegatingAuthenticator
3337
*
3438
* @since 2.0.0
3539
*/
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2025 Couchbase, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.couchbase.client.core.env;
18+
19+
import com.couchbase.client.core.annotation.Stability;
20+
import com.couchbase.client.core.deps.io.grpc.CallCredentials;
21+
import com.couchbase.client.core.deps.io.netty.channel.ChannelPipeline;
22+
import com.couchbase.client.core.deps.io.netty.handler.codec.http.HttpRequest;
23+
import com.couchbase.client.core.deps.io.netty.handler.ssl.SslContextBuilder;
24+
import com.couchbase.client.core.endpoint.EndpointContext;
25+
import com.couchbase.client.core.service.ServiceType;
26+
import org.jspecify.annotations.NullMarked;
27+
import org.jspecify.annotations.Nullable;
28+
29+
@NullMarked
30+
@Stability.Internal
31+
abstract class AuthenticatorWrapper implements Authenticator {
32+
33+
abstract Authenticator wrapped();
34+
35+
@Override
36+
public void authKeyValueConnection(final EndpointContext endpointContext, final ChannelPipeline pipeline) {
37+
wrapped().authKeyValueConnection(endpointContext, pipeline);
38+
}
39+
40+
@Override
41+
public void authHttpRequest(final ServiceType serviceType, final HttpRequest request) {
42+
wrapped().authHttpRequest(serviceType, request);
43+
}
44+
45+
@Override
46+
public @Nullable CallCredentials protostellarCallCredentials() {
47+
return wrapped().protostellarCallCredentials();
48+
}
49+
50+
@Override
51+
public void applyTlsProperties(final SslContextBuilder sslContextBuilder) {
52+
wrapped().applyTlsProperties(sslContextBuilder);
53+
}
54+
55+
@Override
56+
public boolean supportsTls() {
57+
return wrapped().supportsTls();
58+
}
59+
60+
@Override
61+
public boolean supportsNonTls() {
62+
return wrapped().supportsNonTls();
63+
}
64+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2025 Couchbase, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.couchbase.client.core.env;
18+
19+
import org.jspecify.annotations.NullMarked;
20+
21+
import static java.util.Objects.requireNonNull;
22+
23+
/**
24+
* Delegates authentication to another authenticator.
25+
* <p>
26+
* The other authenticator can be swapped at runtime
27+
* to support credential rotation.
28+
*
29+
* @see #create(Authenticator)
30+
*/
31+
@NullMarked
32+
public class DelegatingAuthenticator extends AuthenticatorWrapper {
33+
private volatile Authenticator delegate;
34+
35+
/**
36+
* Returns a new authenticator that delegates to the given authenticator.
37+
* <p>
38+
* The delegate may be updated later by calling {@link #setDelegate(Authenticator)}.
39+
*/
40+
public static DelegatingAuthenticator create(Authenticator delegate) {
41+
return new DelegatingAuthenticator(delegate);
42+
}
43+
44+
public void setDelegate(Authenticator delegate) {
45+
this.delegate = requireNonNull(delegate);
46+
}
47+
48+
DelegatingAuthenticator(Authenticator delegate) {
49+
setDelegate(delegate);
50+
}
51+
52+
Authenticator wrapped() {
53+
return delegate;
54+
}
55+
56+
@Override
57+
public String toString() {
58+
return "DelegatingAuthenticator{" +
59+
"delegate=" + delegate +
60+
'}';
61+
}
62+
}

core-io/src/main/java/com/couchbase/client/core/env/PasswordAuthenticator.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ public static PasswordAuthenticator.Builder builder(String username, String pass
9292
* One way to keep the supplier's value up to date is to schedule a recurring task
9393
* that reads new credentials from an external source and stores them
9494
* in the volatile field.
95+
*
96+
* @deprecated This method is difficult to use safely, because it's easy to accidentally
97+
* do blocking IO inside the supplier. Please use {@link DelegatingAuthenticator} instead.
9598
*/
99+
@Deprecated
96100
public static PasswordAuthenticator.Builder builder(Supplier<UsernameAndPassword> supplier) {
97101
return new Builder(supplier);
98102
}
@@ -262,7 +266,7 @@ public Builder username(final String username) {
262266
* @param username A supplier that returns the username to use.
263267
* @return this builder for chaining purposes.
264268
* @deprecated This method does not support returning username and password as an atomic unit.
265-
* Please use {@link PasswordAuthenticator#builder(Supplier)} instead.
269+
* Please use {@link DelegatingAuthenticator} instead.
266270
*/
267271
@Deprecated
268272
public Builder username(final Supplier<String> username) {
@@ -304,7 +308,7 @@ public Builder password(final String password) {
304308
* @param password the password to alongside for the username provided.
305309
* @return this builder for chaining purposes.
306310
* @deprecated This method does not support returning username and password as an atomic unit.
307-
* Please use {@link PasswordAuthenticator#builder(Supplier)} instead.
311+
* Please use {@link DelegatingAuthenticator} instead.
308312
*/
309313
@Deprecated
310314
public Builder password(final Supplier<String> password) {

0 commit comments

Comments
 (0)