Skip to content

Commit 2519404

Browse files
committed
feat: Refactor X509Provider
1 parent 90b61a9 commit 2519404

16 files changed

Lines changed: 312 additions & 395 deletions
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.google.auth.mtls;
2+
3+
import com.google.auth.oauth2.EnvironmentProvider;
4+
import com.google.auth.oauth2.PropertyProvider;
5+
import com.google.common.base.Strings;
6+
import java.io.File;
7+
import java.io.FileInputStream;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.util.Locale;
11+
12+
/**
13+
* Utility class for mTLS related operations.
14+
*
15+
* <p>For internal use only.
16+
*/
17+
public class MtlsUtils {
18+
static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG";
19+
static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json";
20+
static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud";
21+
22+
private MtlsUtils() {
23+
// Prevent instantiation for Utility class
24+
}
25+
26+
/**
27+
* Returns the path to the client certificate file specified by the loaded workload certificate
28+
* configuration.
29+
*
30+
* @return The path to the certificate file.
31+
* @throws IOException if the certificate configuration cannot be found or loaded.
32+
*/
33+
public static String getCertificatePath(
34+
EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride)
35+
throws IOException {
36+
String certPath =
37+
getWorkloadCertificateConfiguration(envProvider, propProvider, certConfigPathOverride)
38+
.getCertPath();
39+
if (Strings.isNullOrEmpty(certPath)) {
40+
throw new CertificateSourceUnavailableException(
41+
"Certificate configuration loaded successfully, but does not contain a 'certificate_file' path.");
42+
}
43+
return certPath;
44+
}
45+
46+
public static WorkloadCertificateConfiguration getWorkloadCertificateConfiguration(
47+
EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride)
48+
throws IOException {
49+
File certConfig;
50+
if (certConfigPathOverride != null) {
51+
certConfig = new File(certConfigPathOverride);
52+
} else {
53+
String envCredentialsPath = envProvider.getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE);
54+
if (!Strings.isNullOrEmpty(envCredentialsPath)) {
55+
certConfig = new File(envCredentialsPath);
56+
} else {
57+
certConfig = getWellKnownCertificateConfigFile(envProvider, propProvider);
58+
}
59+
}
60+
61+
if (!certConfig.isFile()) {
62+
throw new CertificateSourceUnavailableException("File does not exist.");
63+
}
64+
try (InputStream certConfigStream = new FileInputStream(certConfig)) {
65+
return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream);
66+
}
67+
}
68+
69+
private static File getWellKnownCertificateConfigFile(
70+
EnvironmentProvider envProvider, PropertyProvider propProvider) {
71+
File cloudConfigPath;
72+
String envPath = envProvider.getEnv("CLOUDSDK_CONFIG");
73+
if (envPath != null) {
74+
cloudConfigPath = new File(envPath);
75+
} else {
76+
String osName = propProvider.getProperty("os.name", "").toLowerCase(Locale.US);
77+
if (osName.indexOf("windows") >= 0) {
78+
File appDataPath = new File(envProvider.getEnv("APPDATA"));
79+
cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY);
80+
} else {
81+
File configPath = new File(propProvider.getProperty("user.home", ""), ".config");
82+
cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY);
83+
}
84+
}
85+
return new File(cloudConfigPath, WELL_KNOWN_CERTIFICATE_CONFIG_FILE);
86+
}
87+
}

oauth2_http/java/com/google/auth/mtls/X509Provider.java

Lines changed: 34 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@
3131
package com.google.auth.mtls;
3232

3333
import com.google.api.client.util.SecurityUtils;
34+
import com.google.auth.oauth2.EnvironmentProvider;
35+
import com.google.auth.oauth2.PropertyProvider;
36+
import com.google.auth.oauth2.SystemEnvironmentProvider;
37+
import com.google.auth.oauth2.SystemPropertyProvider;
3438
import com.google.common.base.Strings;
3539
import java.io.File;
3640
import java.io.FileInputStream;
37-
import java.io.FileNotFoundException;
3841
import java.io.IOException;
3942
import java.io.InputStream;
4043
import java.io.SequenceInputStream;
4144
import java.security.KeyStore;
42-
import java.util.Locale;
4345

4446
/**
4547
* This class implements {@link MtlsProvider} for the Google Auth library transport layer via {@link
@@ -48,23 +50,40 @@
4850
* backwards compatibility.
4951
*/
5052
public class X509Provider implements MtlsProvider {
51-
static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG";
52-
static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json";
53-
static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud";
54-
53+
private final EnvironmentProvider envProvider;
54+
private final PropertyProvider propProvider;
5555
private final String certConfigPathOverride;
5656

5757
/**
5858
* Creates an X509 provider with an override path for the certificate configuration, bypassing the
5959
* normal checks for the well known certificate configuration file path and environment variable.
6060
* This is meant for internal Google Cloud usage and behavior may be changed without warning.
6161
*
62+
* @param envProvider environment provider used for environment variables
63+
* @param propProvider property provider used for system properties
6264
* @param certConfigPathOverride the path to read the certificate configuration from.
6365
*/
64-
public X509Provider(String certConfigPathOverride) {
66+
public X509Provider(
67+
EnvironmentProvider envProvider,
68+
PropertyProvider propProvider,
69+
String certConfigPathOverride) {
70+
this.envProvider = envProvider;
71+
this.propProvider = propProvider;
6572
this.certConfigPathOverride = certConfigPathOverride;
6673
}
6774

75+
/**
76+
* Creates an X509 provider with an override path for the certificate configuration.
77+
*
78+
* @param certConfigPathOverride the path to read the certificate configuration from.
79+
*/
80+
public X509Provider(String certConfigPathOverride) {
81+
this(
82+
SystemEnvironmentProvider.getInstance(),
83+
SystemPropertyProvider.getInstance(),
84+
certConfigPathOverride);
85+
}
86+
6887
/**
6988
* Creates a new X.509 provider that will check the environment variable path and the well known
7089
* Gcloud certificate configuration location. This is meant for internal Google Cloud usage and
@@ -78,23 +97,15 @@ public X509Provider() {
7897
* Returns the path to the client certificate file specified by the loaded workload certificate
7998
* configuration.
8099
*
81-
* <p>If the configuration has not been loaded yet (e.g., if {@link #getKeyStore()} has not been
82-
* called), this method will attempt to load it first by searching the override path, environment
83-
* variable, and well-known locations.
100+
* <p>If the configuration has not been loaded yet, this method will attempt to load it first by
101+
* searching the override path, environment variable, and well-known locations.
84102
*
85103
* @return The path to the certificate file.
86104
* @throws IOException if the certificate configuration cannot be found or loaded, or if the
87105
* configuration file does not specify a certificate path.
88-
* @throws CertificateSourceUnavailableException if the configuration file is not found.
89106
*/
90107
public String getCertificatePath() throws IOException {
91-
String certPath = getWorkloadCertificateConfiguration().getCertPath();
92-
if (Strings.isNullOrEmpty(certPath)) {
93-
// Ensure the loaded configuration actually contains the required path.
94-
throw new CertificateSourceUnavailableException(
95-
"Certificate configuration loaded successfully, but does not contain a 'certificate_file' path.");
96-
}
97-
return certPath;
108+
return MtlsUtils.getCertificatePath(envProvider, propProvider, certConfigPathOverride);
98109
}
99110

100111
/**
@@ -115,12 +126,14 @@ public String getCertificatePath() throws IOException {
115126
*/
116127
@Override
117128
public KeyStore getKeyStore() throws CertificateSourceUnavailableException, IOException {
118-
WorkloadCertificateConfiguration workloadCertConfig = getWorkloadCertificateConfiguration();
129+
WorkloadCertificateConfiguration workloadCertConfig =
130+
MtlsUtils.getWorkloadCertificateConfiguration(
131+
envProvider, propProvider, certConfigPathOverride);
119132

120133
// Read the certificate and private key file paths into streams.
121-
try (InputStream certStream = createInputStream(new File(workloadCertConfig.getCertPath()));
134+
try (InputStream certStream = new FileInputStream(new File(workloadCertConfig.getCertPath()));
122135
InputStream privateKeyStream =
123-
createInputStream(new File(workloadCertConfig.getPrivateKeyPath()));
136+
new FileInputStream(new File(workloadCertConfig.getPrivateKeyPath()));
124137
SequenceInputStream certAndPrivateKeyStream =
125138
new SequenceInputStream(certStream, privateKeyStream)) {
126139

@@ -150,73 +163,5 @@ public boolean isAvailable() throws IOException {
150163
return true;
151164
}
152165

153-
private WorkloadCertificateConfiguration getWorkloadCertificateConfiguration()
154-
throws IOException {
155-
File certConfig;
156-
if (this.certConfigPathOverride != null) {
157-
certConfig = new File(certConfigPathOverride);
158-
} else {
159-
String envCredentialsPath = getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE);
160-
if (!Strings.isNullOrEmpty(envCredentialsPath)) {
161-
certConfig = new File(envCredentialsPath);
162-
} else {
163-
certConfig = getWellKnownCertificateConfigFile();
164-
}
165-
}
166-
InputStream certConfigStream = null;
167-
try {
168-
if (!isFile(certConfig)) {
169-
// Path will be put in the message from the catch block below
170-
throw new CertificateSourceUnavailableException("File does not exist.");
171-
}
172-
certConfigStream = createInputStream(certConfig);
173-
return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream);
174-
} finally {
175-
if (certConfigStream != null) {
176-
certConfigStream.close();
177-
}
178-
}
179-
}
180166

181-
/*
182-
* Start of methods to allow overriding in the test code to isolate from the environment.
183-
*/
184-
boolean isFile(File file) {
185-
return file.isFile();
186-
}
187-
188-
InputStream createInputStream(File file) throws FileNotFoundException {
189-
return new FileInputStream(file);
190-
}
191-
192-
String getEnv(String name) {
193-
return System.getenv(name);
194-
}
195-
196-
String getOsName() {
197-
return getProperty("os.name", "").toLowerCase(Locale.US);
198-
}
199-
200-
String getProperty(String property, String def) {
201-
return System.getProperty(property, def);
202-
}
203-
204-
/*
205-
* End of methods to allow overriding in the test code to isolate from the environment.
206-
*/
207-
208-
private File getWellKnownCertificateConfigFile() {
209-
File cloudConfigPath;
210-
String envPath = getEnv("CLOUDSDK_CONFIG");
211-
if (envPath != null) {
212-
cloudConfigPath = new File(envPath);
213-
} else if (getOsName().indexOf("windows") >= 0) {
214-
File appDataPath = new File(getEnv("APPDATA"));
215-
cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY);
216-
} else {
217-
File configPath = new File(getProperty("user.home", ""), ".config");
218-
cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY);
219-
}
220-
return new File(cloudConfigPath, WELL_KNOWN_CERTIFICATE_CONFIG_FILE);
221-
}
222167
}

oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,6 @@ String getRegionalCredentialVerificationUrl() {
196196
return this.regionalCredentialVerificationUrl;
197197
}
198198

199-
@VisibleForTesting
200-
String getEnv(String name) {
201-
return System.getenv(name);
202-
}
203199

204200
@VisibleForTesting
205201
AwsSecurityCredentialsSupplier getAwsSecurityCredentialsSupplier() {
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.google.auth.oauth2;
22

3-
/** Interface for an environment provider. */
4-
interface EnvironmentProvider {
3+
/**
4+
* Interface for an environment provider.
5+
*
6+
* <p>For internal use only.
7+
*/
8+
public interface EnvironmentProvider {
59
String getEnv(String name);
610
}

oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials {
9797
@Nullable protected ImpersonatedCredentials impersonatedCredentials;
9898

9999
private EnvironmentProvider environmentProvider;
100+
private PropertyProvider propertyProvider;
100101

101102
private int connectTimeout;
102103
private int readTimeout;
@@ -205,6 +206,7 @@ protected ExternalAccountCredentials(
205206
: scopes;
206207
this.environmentProvider =
207208
environmentProvider == null ? SystemEnvironmentProvider.getInstance() : environmentProvider;
209+
this.propertyProvider = SystemPropertyProvider.getInstance();
208210
this.workforcePoolUserProject = null;
209211
this.serviceAccountImpersonationOptions =
210212
new ServiceAccountImpersonationOptions(new HashMap<String, Object>());
@@ -253,6 +255,10 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder)
253255
builder.environmentProvider == null
254256
? SystemEnvironmentProvider.getInstance()
255257
: builder.environmentProvider;
258+
this.propertyProvider =
259+
builder.propertyProvider == null
260+
? SystemPropertyProvider.getInstance()
261+
: builder.propertyProvider;
256262
this.serviceAccountImpersonationOptions =
257263
builder.serviceAccountImpersonationOptions == null
258264
? new ServiceAccountImpersonationOptions(new HashMap<String, Object>())
@@ -657,6 +663,10 @@ EnvironmentProvider getEnvironmentProvider() {
657663
return environmentProvider;
658664
}
659665

666+
PropertyProvider getPropertyProvider() {
667+
return propertyProvider;
668+
}
669+
660670
/**
661671
* @return whether the current configuration is for Workforce Pools (which enable 3p user
662672
* identities, rather than workloads)
@@ -772,6 +782,7 @@ public abstract static class Builder extends GoogleCredentials.Builder {
772782
protected String tokenInfoUrl;
773783
protected CredentialSource credentialSource;
774784
protected EnvironmentProvider environmentProvider;
785+
protected PropertyProvider propertyProvider;
775786
protected HttpTransportFactory transportFactory;
776787

777788
@Nullable protected String serviceAccountImpersonationUrl;
@@ -806,6 +817,7 @@ protected Builder(ExternalAccountCredentials credentials) {
806817
this.clientSecret = credentials.clientSecret;
807818
this.scopes = credentials.scopes;
808819
this.environmentProvider = credentials.environmentProvider;
820+
this.propertyProvider = credentials.propertyProvider;
809821
this.workforcePoolUserProject = credentials.workforcePoolUserProject;
810822
this.serviceAccountImpersonationOptions = credentials.serviceAccountImpersonationOptions;
811823
this.metricsHandler = credentials.metricsHandler;
@@ -1029,6 +1041,18 @@ Builder setEnvironmentProvider(EnvironmentProvider environmentProvider) {
10291041
return this;
10301042
}
10311043

1044+
/**
1045+
* Sets the optional Property Provider.
1046+
*
1047+
* @param propertyProvider the {@code PropertyProvider} to set
1048+
* @return this {@code Builder} object
1049+
*/
1050+
@CanIgnoreReturnValue
1051+
Builder setPropertyProvider(PropertyProvider propertyProvider) {
1052+
this.propertyProvider = propertyProvider;
1053+
return this;
1054+
}
1055+
10321056
@Override
10331057
public abstract ExternalAccountCredentials build();
10341058
}

0 commit comments

Comments
 (0)