Skip to content

Commit af9e51f

Browse files
committed
Moved cert-discovery to a common function. Addressed PR comments.
1 parent 48c3b59 commit af9e51f

3 files changed

Lines changed: 144 additions & 55 deletions

File tree

google-auth-library-java/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java

Lines changed: 118 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,19 @@
3131
package com.google.auth.mtls;
3232

3333
import com.google.api.core.InternalApi;
34+
import com.google.auth.http.HttpTransportFactory;
3435
import com.google.auth.oauth2.EnvironmentProvider;
36+
import com.google.auth.oauth2.OAuth2Utils;
3537
import com.google.auth.oauth2.PropertyProvider;
3638
import com.google.common.base.Strings;
3739
import java.io.File;
3840
import java.io.FileInputStream;
3941
import java.io.IOException;
4042
import java.io.InputStream;
43+
import java.security.KeyStore;
4144
import java.util.Locale;
45+
import java.util.logging.Logger;
46+
import javax.annotation.Nullable;
4247

4348
/**
4449
* Utility class for mTLS related operations.
@@ -47,6 +52,8 @@
4752
*/
4853
@InternalApi
4954
public class MtlsUtils {
55+
private static final Logger LOGGER = Logger.getLogger(MtlsUtils.class.getName());
56+
5057
static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG";
5158
static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json";
5259
static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud";
@@ -91,38 +98,27 @@ public static String getCertificatePath(
9198
}
9299

93100
/**
94-
* Resolves and loads the workload certificate configuration.
101+
* Resolves and parses the workload certificate configuration.
95102
*
96-
* <p>The configuration file is resolved in the following order of precedence: 1. The provided
97-
* certConfigPathOverride (if not null). 2. The path specified by the
98-
* GOOGLE_API_CERTIFICATE_CONFIG environment variable. 3. The well-known certificate configuration
99-
* file in the gcloud config directory.
103+
* <p>This locates the certificate configuration file via {@link #resolveCertificateConfigFile}
104+
* and parses its contents into a {@link WorkloadCertificateConfiguration}.
100105
*
101106
* @param envProvider the environment provider to use for resolving environment variables
102107
* @param propProvider the property provider to use for resolving system properties
103108
* @param certConfigPathOverride optional override path for the configuration file
104109
* @return the loaded WorkloadCertificateConfiguration
105-
* @throws IOException if the configuration file cannot be found, read, or parsed
110+
* @throws IOException if the configuration file cannot be resolved, read, or parsed
106111
*/
107112
static WorkloadCertificateConfiguration getWorkloadCertificateConfiguration(
108113
EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride)
109114
throws IOException {
110-
File certConfig;
111-
if (certConfigPathOverride != null) {
112-
certConfig = new File(certConfigPathOverride);
113-
} else {
114-
String envCredentialsPath = envProvider.getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE);
115-
if (!Strings.isNullOrEmpty(envCredentialsPath)) {
116-
certConfig = new File(envCredentialsPath);
117-
} else {
118-
certConfig = getWellKnownCertificateConfigFile(envProvider, propProvider);
119-
}
120-
}
121-
122-
if (!certConfig.isFile()) {
115+
File certConfig =
116+
resolveCertificateConfigFile(envProvider, propProvider, certConfigPathOverride);
117+
if (certConfig == null) {
118+
File wellKnownConfig = getWellKnownCertificateConfigFile(envProvider, propProvider);
123119
throw new CertificateSourceUnavailableException(
124120
"Certificate configuration file does not exist or is not a file: "
125-
+ certConfig.getAbsolutePath());
121+
+ wellKnownConfig.getAbsolutePath());
126122
}
127123
try (InputStream certConfigStream = new FileInputStream(certConfig)) {
128124
return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream);
@@ -181,33 +177,76 @@ public static boolean canMtlsBeEnabled(
181177
if (policy == MtlsEndpointUsagePolicy.NEVER) {
182178
return false;
183179
}
180+
184181
if (policy == MtlsEndpointUsagePolicy.ALWAYS) {
185182
return true;
186183
}
187184

188-
// Locate and process the certificate configuration file
185+
File certConfigFile =
186+
resolveCertificateConfigFile(envProvider, propProvider, certConfigPathOverride);
187+
return certConfigFile != null;
188+
}
189+
190+
/**
191+
* Resolves the mutual TLS (mTLS) certificate configuration file.
192+
*
193+
* <p>The configuration file is resolved in the following order of precedence:
194+
* <ol>
195+
* <li>The developer-provided {@code certConfigPathOverride} (if not null).
196+
* <li>The path specified by the {@code GOOGLE_API_CERTIFICATE_CONFIG} environment variable.
197+
* <li>The well-known automatic gcloud workload identity provisioning location (via {@link
198+
* #getWellKnownCertificateConfigFile}).
199+
* </ol>
200+
*
201+
* <p>If an explicit configuration file is specified (via override or environment variable) and it
202+
* is missing or invalid, an exception is thrown. If no explicit file is specified and the default
203+
* well-known file is missing, {@code null} is returned.
204+
*
205+
* @param envProvider the environment provider to use for resolving environment variables
206+
* @param propProvider the property provider to use for resolving system properties
207+
* @param certConfigPathOverride optional override path for the configuration file
208+
* @return the resolved File object, or null if no configuration was found
209+
* @throws IOException if an explicit configuration file is missing or malformed
210+
*/
211+
@Nullable
212+
static File resolveCertificateConfigFile(
213+
EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride)
214+
throws IOException {
215+
// 1. Check explicit developer override
216+
if (certConfigPathOverride != null) {
217+
File certConfigFile = new File(certConfigPathOverride);
218+
if (!certConfigFile.isFile()) {
219+
throw new CertificateSourceUnavailableException(
220+
"Certificate configuration file does not exist or is not a file: "
221+
+ certConfigFile.getAbsolutePath());
222+
}
223+
return certConfigFile;
224+
}
225+
226+
// 2. Check explicit environment variable
189227
String envPath = envProvider.getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE);
190-
if (certConfigPathOverride != null || !Strings.isNullOrEmpty(envPath)) {
191-
File certConfigFile =
192-
new File(certConfigPathOverride != null ? certConfigPathOverride : envPath);
228+
if (!Strings.isNullOrEmpty(envPath)) {
229+
File certConfigFile = new File(envPath);
193230
if (!certConfigFile.isFile()) {
194231
throw new CertificateSourceUnavailableException(
195232
"Certificate configuration file does not exist or is not a file: "
196233
+ certConfigFile.getAbsolutePath());
197234
}
198-
return true;
235+
return certConfigFile;
199236
}
200237

238+
// 3. Check optional well-known automatic provisioning location
201239
try {
202240
File wellKnownConfig = getWellKnownCertificateConfigFile(envProvider, propProvider);
203241
if (wellKnownConfig.isFile()) {
204-
return true;
242+
return wellKnownConfig;
205243
}
206244
} catch (IOException e) {
207-
// ignore if well-known directory resolution fails (e.g. APPDATA not set on Windows)
245+
LOGGER.info(
246+
"Could not get the mutual TLS (mTLS) client certificate configuration. The library will fall back to making standard non-mTLS requests.");
208247
}
209248

210-
return false;
249+
return null;
211250
}
212251

213252
/**
@@ -226,4 +265,55 @@ public static MtlsEndpointUsagePolicy getMtlsEndpointUsagePolicy(
226265
}
227266
return MtlsEndpointUsagePolicy.AUTO;
228267
}
268+
269+
/**
270+
* Prepares and upgrades the HTTP transport factory for mutual TLS (mTLS) if applicable.
271+
*
272+
* @param baseTransportFactory the base HTTP transport factory to upgrade
273+
* @param envProvider the environment provider to use for resolving environment variables
274+
* @param propProvider the property provider to use for resolving system properties
275+
* @param certConfigPathOverride optional override path for the configuration file
276+
* @return the mTLS-configured HTTP transport factory, or the base factory if mTLS is not enabled
277+
* @throws IOException if mTLS is required/enabled but certificate initialization fails or an
278+
* incompatible transport factory was provided
279+
*/
280+
public static HttpTransportFactory prepareTransportFactoryIfMtlsEnabled(
281+
HttpTransportFactory baseTransportFactory,
282+
EnvironmentProvider envProvider,
283+
PropertyProvider propProvider,
284+
String certConfigPathOverride)
285+
throws IOException {
286+
287+
MtlsEndpointUsagePolicy mtlsPolicy = getMtlsEndpointUsagePolicy(envProvider);
288+
try {
289+
boolean canMtls = canMtlsBeEnabled(envProvider, propProvider, certConfigPathOverride);
290+
if (canMtls) {
291+
if (baseTransportFactory instanceof MtlsHttpTransportFactory) {
292+
// A custom MtlsHttpTransportFactory was already pre-configured by the user.
293+
// Keep using it as-is without re-initializing.
294+
return baseTransportFactory;
295+
} else if (baseTransportFactory == OAuth2Utils.HTTP_TRANSPORT_FACTORY) {
296+
// This is the default HttpTransportFactory assigned by credentials.
297+
// Automatically discover and load client certificates to construct an mTLS factory.
298+
X509Provider x509Provider =
299+
new X509Provider(envProvider, propProvider, certConfigPathOverride);
300+
KeyStore mtlsKeyStore = x509Provider.getKeyStore();
301+
return new MtlsHttpTransportFactory(mtlsKeyStore);
302+
} else {
303+
// A user configured non-mTLS HttpTransportFactory was explicitly injected.
304+
// Reject it to avoid bypassing mTLS enforcement or overriding the user's factory.
305+
throw new IOException(
306+
"mTLS is enabled on the system, but a user configured non-mTLS HttpTransportFactory was provided: "
307+
+ baseTransportFactory.getClass().getName());
308+
}
309+
}
310+
} catch (Exception e) {
311+
if (mtlsPolicy == MtlsEndpointUsagePolicy.ALWAYS) {
312+
throw new IOException(
313+
"mTLS is configured to ALWAYS, but initialization failed: " + e.getMessage(), e);
314+
}
315+
// Graceful fallback to standard transport if mTLS initialization fails under AUTO policy
316+
}
317+
return baseTransportFactory;
318+
}
229319
}

google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@
3939
import com.google.auth.RequestMetadataCallback;
4040
import com.google.auth.http.AuthHttpConstants;
4141
import com.google.auth.http.HttpTransportFactory;
42-
import com.google.auth.mtls.MtlsHttpTransportFactory;
4342
import com.google.auth.mtls.MtlsUtils;
44-
import com.google.auth.mtls.X509Provider;
4543
import com.google.common.annotations.VisibleForTesting;
4644
import com.google.common.base.MoreObjects;
4745
import com.google.common.base.MoreObjects.ToStringHelper;
@@ -54,7 +52,6 @@
5452
import java.io.ObjectInputStream;
5553
import java.net.URI;
5654
import java.nio.charset.StandardCharsets;
57-
import java.security.KeyStore;
5855
import java.time.Duration;
5956
import java.util.Collection;
6057
import java.util.Collections;
@@ -401,29 +398,10 @@ void refreshRegionalAccessBoundaryIfExpired(@Nullable URI uri, @Nullable AccessT
401398
return;
402399
}
403400

404-
MtlsUtils.MtlsEndpointUsagePolicy mtlsPolicy =
405-
MtlsUtils.getMtlsEndpointUsagePolicy(getEnvironmentProvider());
406-
try {
407-
boolean canMtls =
408-
MtlsUtils.canMtlsBeEnabled(getEnvironmentProvider(), getPropertyProvider(), null);
409-
// Initialize mTLS transport factory if mTLS can be enabled and the user hasn't already
410-
// configured a custom MtlsHttpTransportFactory.
411-
if (!(transportFactory instanceof MtlsHttpTransportFactory) && canMtls) {
412-
X509Provider x509Provider =
413-
new X509Provider(getEnvironmentProvider(), getPropertyProvider(), null);
414-
KeyStore mtlsKeyStore = x509Provider.getKeyStore();
415-
transportFactory = new MtlsHttpTransportFactory(mtlsKeyStore);
416-
}
417-
} catch (Exception e) {
418-
if (mtlsPolicy == MtlsUtils.MtlsEndpointUsagePolicy.ALWAYS) {
419-
if (e instanceof IOException) {
420-
throw (IOException) e;
421-
}
422-
throw new IOException(
423-
"mTLS is configured to ALWAYS, but initialization failed: " + e.getMessage(), e);
424-
}
425-
// Graceful fallback to standard transport if mTLS initialization fails
426-
}
401+
// Automatically discover certificates or enforce mTLS policy if applicable
402+
transportFactory =
403+
MtlsUtils.prepareTransportFactoryIfMtlsEnabled(
404+
transportFactory, getEnvironmentProvider(), getPropertyProvider(), null);
427405

428406
regionalAccessBoundaryManager.triggerAsyncRefresh(
429407
transportFactory, (RegionalAccessBoundaryProvider) this, token);

google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1231,6 +1231,18 @@ public void regionalAccessBoundary_alwaysPolicy_missingCertConfig_throwsExceptio
12311231
assertThrows(IOException.class, () -> credentials.getRequestMetadata());
12321232
}
12331233

1234+
@Test
1235+
public void regionalAccessBoundary_alwaysPolicy_userConfiguredNonMtlsFactory_throwsException() {
1236+
TestEnvironmentProvider envProvider = new TestEnvironmentProvider();
1237+
envProvider.setEnv("GOOGLE_API_USE_MTLS_ENDPOINT", "always");
1238+
1239+
GoogleCredentials credentials =
1240+
new TestRegionalCredentials(
1241+
new AccessToken("some-token", null), envProvider, DUMMY_TRANSPORT_FACTORY);
1242+
1243+
assertThrows(IOException.class, () -> credentials.getRequestMetadata());
1244+
}
1245+
12341246
@Test
12351247
public void regionalAccessBoundary_deduplicationOfConcurrentRefreshes()
12361248
throws IOException, InterruptedException {
@@ -1371,10 +1383,19 @@ public void advanceTime(long millis) {
13711383
private static class TestRegionalCredentials extends GoogleCredentials
13721384
implements RegionalAccessBoundaryProvider {
13731385
private final EnvironmentProvider envProvider;
1386+
private final HttpTransportFactory transportFactory;
13741387

13751388
TestRegionalCredentials(AccessToken token, EnvironmentProvider envProvider) {
1389+
this(token, envProvider, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
1390+
}
1391+
1392+
TestRegionalCredentials(
1393+
AccessToken token,
1394+
EnvironmentProvider envProvider,
1395+
HttpTransportFactory transportFactory) {
13761396
super(GoogleCredentials.newBuilder().setAccessToken(token));
13771397
this.envProvider = envProvider;
1398+
this.transportFactory = transportFactory;
13781399
}
13791400

13801401
@Override
@@ -1384,7 +1405,7 @@ EnvironmentProvider getEnvironmentProvider() {
13841405

13851406
@Override
13861407
HttpTransportFactory getTransportFactory() {
1387-
return DUMMY_TRANSPORT_FACTORY;
1408+
return transportFactory;
13881409
}
13891410

13901411
@Override

0 commit comments

Comments
 (0)