Skip to content

feat(mtls): Add support for X.509-based mTLS-transport in Java GAX lib #3852

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

andyrzhao
Copy link

Fixes #3851

  • Refactors mTLS code path to use new CertificateBasedAccess class to determine mTLS behavior based on env vars.
  • Refactors mTLS code path to use DefaultMtlsProviderFactory from Java auth lib for creating a default mTLS provider using either the legacy SecureConnect mtls provider or the newer X.509 mtls provider, depending on availability.

@product-auto-label product-auto-label bot added the size: l Pull request size is large. label Jun 25, 2025
@@ -150,6 +153,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
@Nullable private final Boolean allowNonDefaultServiceAccount;
@VisibleForTesting final ImmutableMap<String, ?> directPathServiceConfig;
@Nullable private final MtlsProvider mtlsProvider;
@Nullable private final CertificateBasedAccess certificateBasedAccess;
Copy link
Contributor

Choose a reason for hiding this comment

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

qq, I believe below has a default value for the certificateBasedAccess. What's the reason this should be Nullable?

Copy link
Author

@andyrzhao andyrzhao Jul 2, 2025

Choose a reason for hiding this comment

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

So the "CertificateBasedAccess" is essentially a couple of helper functions that was moved out of the original MtlsProvider implementation, and since the previous MtlsProvider was Nullable, this was also marked Nullable to retain the same semantics - my hunch is that we should keep it Nullable for maximum compatibility/flexibility.

Copy link
Contributor

@lqiu96 lqiu96 Jul 11, 2025

Choose a reason for hiding this comment

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

I see. The old MtlsProvider was public in name, but we didn't allow users to set it into the client. I don't think we have direct use cases of users using it and I don't think we need to maintain compatibility for it.

If possible, I would rather have this be non-null so we don't need the null checks. I think this could also be the same with the new MtlsProvider above since there is a default one created below.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh wait, I think the MtlsProvider must remain Nullable given the possibility of CertificateSourceUnavailableException.

Copy link
Author

Choose a reason for hiding this comment

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

So yes, I think MtlsProvider should remain Nullable. Would you like to keep CertificateBasedAccess Nullable as well or change it to non-nullable? I don't have a strong preference either way. LMK Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

I think MtlsProvider must remain Nullable and CertificateBasedAccess can be non-Nullable. Preference for CertificateBasedAccess to be non-nullable so that we don't need so many null checks in the logic below and in other files.

Copy link
Author

Choose a reason for hiding this comment

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

I updated the latest revision to mark MtlsProvider Nullable and CertificateBasedAccess NonNullable in the HTTP/gRPC channel providers for consistency. However, CertificateBasedAccess is left as @nullable in EndpointContext, since removing it broke compilation for a ton of tests... (will revisit if needed.)

@andyrzhao andyrzhao requested a review from lqiu96 July 7, 2025 19:33
Comment on lines +1288 to +1305
if (certificateBasedAccess == null) {
certificateBasedAccess = CertificateBasedAccess.createWithSystemEnv();
}
if (certificateBasedAccess.useMtlsClientCertificate()) {
if (mtlsProvider == null) {
// Attempt to create default MtlsProvider from environment.
try {
mtlsProvider = DefaultMtlsProviderFactory.create();
} catch (CertificateSourceUnavailableException e) {
// This is okay. Leave mtlsProvider as null so that we will not auto-upgrade
// to mTLS endpoints. See https://google.aip.dev/auth/4114.
} catch (IOException e) {
LOG.log(
Level.WARNING,
"DefaultMtlsProviderFactory encountered unexpected IOException: " + e.getMessage());
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

qq, I noticed that this rough logic also exists here and in the EndpointContext. What's the reason that it needs to exist in the gRPC Channel Provider as well as EndpointContext?

Copy link
Author

Choose a reason for hiding this comment

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

Yes good question. The issue is that EndpointContext is responsible for "endpoint resolution", which takes a dependency on the availability of mTLS (i.e. use mTLS endpoint only if mTLS support is available) - see the complicated "determineEndpoint" function. In the other 2 locations (gRPC/HTTP channel provider), they are used for configuring the TLS settings of the Channel themselves.

Copy link
Contributor

Choose a reason for hiding this comment

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

In the other 2 locations (gRPC/HTTP channel provider), they are used for configuring the TLS settings of the Channel themselves.

Sorry, can you elaborate on this point? I wasn't aware there was settings that MtlsProvider itself was configuring anything. Can you link me to how it's configure TLS settings on the channel?

My assumption was that it was only using the MtlsProvider logic (i.e. checking for env var) to see if Mtls was to be enabled and not touching anything on the channel.

Copy link
Author

Choose a reason for hiding this comment

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

Let me clarify, for gRPC/HTTP channel provider, the "mtlsKeyStore" is used for creating the mTLS-enabled transport as seen in the code snippet below:

HttpTransport createHttpTransport() throws IOException, GeneralSecurityException {
if (certificateBasedAccess == null || mtlsProvider == null) {
return null;
}
if (certificateBasedAccess.useMtlsClientCertificate()) {
KeyStore mtlsKeyStore = mtlsProvider.getKeyStore();
if (mtlsKeyStore != null) {
return new NetHttpTransport.Builder().trustCertificates(null, mtlsKeyStore, "").build();
}
}
return null;
}

The channel providers have no reference to the EndpointContext, so need to independently calculate and bootstrap the mTLS provider.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, thanks for the explanation.

In this case, I think what we have previously configured is probably a mistake (i.e. configuring multiple separate MtlsProviders). I think realistically the flow should be

  1. EndpointContext creates the MtlsProvider and CertificateBasedAccess class that is used to compute the endpoint
  2. gRPC and HttpJson channel providers get the MtlsProvider and CertificateBasedAccess classes that were created in the endpointcontext

However, I think in order for that to be done, we'll need to create public methods inside TransportChannelProvider to allow access to them. It is possible with adding InternalApi and we have done previously to access mtlsEndpoint.

Let me think about the options a bit more. I think what you have is based on the existing code and should work, but I think it previously wasn't configured the best/ correctly and would love to try and fix it if possible.

@lqiu96
Copy link
Contributor

lqiu96 commented Jul 16, 2025

/gcbrun

void testUseMtlsClientCertificateTrue() {
CertificateBasedAccess cba =
new CertificateBasedAccess(
name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true");
Copy link
Contributor

Choose a reason for hiding this comment

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

should this be GOOGLE_API_USE_CLIENT_CERTIFICATE ? true : false

Copy link
Author

Choose a reason for hiding this comment

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

I can update this to the below equivalent for clarity:

name.equals("GOOGLE_API_USE_CLIENT_CERTIFICATE") ? "true" : "auto")

void testUseMtlsClientCertificateFalse() {
CertificateBasedAccess cba =
new CertificateBasedAccess(
name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false");
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above GOOGLE_API_USE_CLIENT_CERTIFICATE ? false : true?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size: l Pull request size is large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement X.509 WIF mTLS-transport support in Java Gax lib
2 participants