From 1672482b628d0fd985bd27985b4b4a93d49c9a84 Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Thu, 10 Apr 2025 23:44:01 -0700 Subject: [PATCH 1/8] draft(mtls): Refactor MtlsProvider into Interface and create CertificateBasedAccess helper --- .../InstantiatingGrpcChannelProvider.java | 17 +- .../InstantiatingGrpcChannelProviderTest.java | 48 +++- .../com/google/api/gax/grpc/SettingsTest.java | 4 +- .../InstantiatingHttpJsonChannelProvider.java | 28 ++- ...tantiatingHttpJsonChannelProviderTest.java | 25 +- .../google/api/gax/rpc/EndpointContext.java | 38 +++- .../gax/rpc/mtls/CertificateBasedAccess.java | 75 ++++++ ...CertificateSourceUnavailableException.java | 73 ++++++ .../rpc/mtls/ContextAwareMetadataJson.java | 4 +- .../rpc/mtls/DefaultMtlsProviderFactory.java | 65 ++++++ .../gax/rpc/mtls/SecureConnectProvider.java | 149 ++++++++++++ .../WorkloadCertificateConfiguration.java | 100 ++++++++ .../google/api/gax/rpc/mtls/X509Provider.java | 199 ++++++++++++++++ .../api/gax/rpc/mtls/v2/MtlsProvider.java | 39 ++++ .../api/gax/rpc/EndpointContextTest.java | 129 ++++++----- .../AbstractMtlsTransportChannelTest.java | 42 ++-- .../rpc/mtls/CertificateBasedAccessTest.java | 83 +++++++ .../api/gax/rpc/mtls/MtlsProviderTest.java | 213 ------------------ .../api/gax/rpc/testing/FakeMtlsProvider.java | 24 +- 19 files changed, 1012 insertions(+), 343 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateSourceUnavailableException.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/DefaultMtlsProviderFactory.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/SecureConnectProvider.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/WorkloadCertificateConfiguration.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/X509Provider.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/v2/MtlsProvider.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/CertificateBasedAccessTest.java delete mode 100644 gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/MtlsProviderTest.java diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java index c62962f40c..8fd31fc370 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java @@ -42,7 +42,8 @@ import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.internal.EnvironmentProvider; -import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.api.gax.rpc.mtls.CertificateBasedAccess; +import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.ApiKeyCredentials; import com.google.auth.Credentials; import com.google.auth.oauth2.ComputeEngineCredentials; @@ -150,6 +151,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP @Nullable private final Boolean allowNonDefaultServiceAccount; @VisibleForTesting final ImmutableMap directPathServiceConfig; @Nullable private final MtlsProvider mtlsProvider; + @Nullable private final CertificateBasedAccess certificateBasedAccess; @Nullable private final SecureSessionAgent s2aConfigProvider; private final List allowedHardBoundTokenTypes; @VisibleForTesting final Map headersWithDuplicatesRemoved = new HashMap<>(); @@ -183,6 +185,7 @@ private InstantiatingGrpcChannelProvider(Builder builder) { this.mtlsEndpoint = builder.mtlsEndpoint; this.allowedHardBoundTokenTypes = builder.allowedHardBoundTokenTypes; this.mtlsProvider = builder.mtlsProvider; + this.certificateBasedAccess = builder.certificateBasedAccess; this.s2aConfigProvider = builder.s2aConfigProvider; this.envProvider = builder.envProvider; this.interceptorProvider = builder.interceptorProvider; @@ -484,7 +487,7 @@ boolean canUseDirectPathWithUniverseDomain() { @VisibleForTesting ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSecurityException { - if (mtlsProvider.useMtlsClientCertificate()) { + if (certificateBasedAccess.useMtlsClientCertificate()) { KeyStore mtlsKeyStore = mtlsProvider.getKeyStore(); if (mtlsKeyStore != null) { KeyManagerFactory factory = @@ -853,7 +856,8 @@ public static final class Builder { private boolean useS2A; private EnvironmentProvider envProvider; private SecureSessionAgent s2aConfigProvider = SecureSessionAgent.create(); - private MtlsProvider mtlsProvider = new MtlsProvider(); + private MtlsProvider mtlsProvider; + private CertificateBasedAccess certificateBasedAccess; @Nullable private GrpcInterceptorProvider interceptorProvider; @Nullable private Integer maxInboundMessageSize; @Nullable private Integer maxInboundMetadataSize; @@ -904,6 +908,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) { this.allowedHardBoundTokenTypes = provider.allowedHardBoundTokenTypes; this.directPathServiceConfig = provider.directPathServiceConfig; this.mtlsProvider = provider.mtlsProvider; + this.certificateBasedAccess = provider.certificateBasedAccess; this.s2aConfigProvider = provider.s2aConfigProvider; } @@ -994,6 +999,12 @@ Builder setMtlsProvider(MtlsProvider mtlsProvider) { return this; } + @VisibleForTesting + Builder setCertificateBasedAccess(CertificateBasedAccess certificateBasedAccess) { + this.certificateBasedAccess = certificateBasedAccess; + return this; + } + @VisibleForTesting Builder setS2AConfigProvider(SecureSessionAgent s2aConfigProvider) { this.s2aConfigProvider = s2aConfigProvider; diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java index 86203ce47d..f21a7da3b2 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java @@ -46,7 +46,8 @@ import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.internal.EnvironmentProvider; import com.google.api.gax.rpc.mtls.AbstractMtlsTransportChannelTest; -import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.api.gax.rpc.mtls.CertificateBasedAccess; +import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.ApiKeyCredentials; import com.google.auth.Credentials; import com.google.auth.http.AuthHttpConstants; @@ -92,6 +93,7 @@ class InstantiatingGrpcChannelProviderTest extends AbstractMtlsTransportChannelT private static final String API_KEY_AUTH_HEADER_KEY = "x-goog-api-key"; private static String originalOSName; private ComputeEngineCredentials computeEngineCredentials; + private CertificateBasedAccess cba; @BeforeAll public static void setupClass() { @@ -101,6 +103,9 @@ public static void setupClass() { @BeforeEach public void setup() throws IOException { computeEngineCredentials = Mockito.mock(ComputeEngineCredentials.class); + cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "never" : "false"); } @AfterEach @@ -208,6 +213,7 @@ void testWithPoolSize() throws IOException { TransportChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -277,6 +283,7 @@ private void testWithInterceptors(int numChannels) throws Exception { InstantiatingGrpcChannelProvider channelProvider = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setEndpoint("localhost:8080") .setPoolSize(numChannels) .setHeaderProvider(Mockito.mock(HeaderProvider.class)) @@ -310,6 +317,7 @@ void testChannelConfigurator() throws IOException { // Invoke the provider InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setEndpoint("localhost:8080") .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) @@ -331,6 +339,7 @@ void testWithGCECredentials() throws IOException { TransportChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .build() .withExecutor((Executor) executor) @@ -414,6 +423,7 @@ void testWithNonGCECredentials() throws IOException { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPath(true) .setChannelConfigurator(channelConfigurator) + .setCertificateBasedAccess(cba) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -442,6 +452,7 @@ void testWithDirectPathDisabled() throws IOException { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPath(false) .setChannelConfigurator(channelConfigurator) + .setCertificateBasedAccess(cba) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -469,6 +480,7 @@ void testWithNoDirectPathFlagSet() throws IOException { TransportChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() .setChannelConfigurator(channelConfigurator) + .setCertificateBasedAccess(cba) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -488,6 +500,7 @@ void testWithIPv6Address() throws IOException { TransportChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -513,6 +526,7 @@ void testWithPrimeChannel() throws IOException { .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .setChannelPrimer(mockChannelPrimer) + .setCertificateBasedAccess(cba) .build(); provider.getTransportChannel().shutdownNow(); @@ -526,7 +540,7 @@ void testWithPrimeChannel() throws IOException { @Test void testWithDefaultDirectPathServiceConfig() { InstantiatingGrpcChannelProvider provider = - InstantiatingGrpcChannelProvider.newBuilder().build(); + InstantiatingGrpcChannelProvider.newBuilder().setCertificateBasedAccess(cba).build(); ImmutableMap defaultServiceConfig = provider.directPathServiceConfig; @@ -591,6 +605,7 @@ void testWithCustomDirectPathServiceConfig() { InstantiatingGrpcChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() .setDirectPathServiceConfig(passedServiceConfig) + .setCertificateBasedAccess(cba) .build(); ImmutableMap defaultServiceConfig = provider.directPathServiceConfig; @@ -598,12 +613,14 @@ void testWithCustomDirectPathServiceConfig() { } @Override - protected Object getMtlsObjectFromTransportChannel(MtlsProvider provider) + protected Object getMtlsObjectFromTransportChannel( + MtlsProvider provider, CertificateBasedAccess cba) throws IOException, GeneralSecurityException { InstantiatingGrpcChannelProvider channelProvider = InstantiatingGrpcChannelProvider.newBuilder() .setEndpoint("localhost:8080") .setMtlsProvider(provider) + .setCertificateBasedAccess(cba) .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .build(); @@ -632,7 +649,10 @@ private void createAndCloseTransportChannel(InstantiatingGrpcChannelProvider pro FakeLogHandler logHandler = new FakeLogHandler(); InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler); InstantiatingGrpcChannelProvider provider = - createChannelProviderBuilderForDirectPathLogTests().setAttemptDirectPathXds().build(); + createChannelProviderBuilderForDirectPathLogTests() + .setAttemptDirectPathXds() + .setCertificateBasedAccess(cba) + .build(); createAndCloseTransportChannel(provider); assertThat(logHandler.getAllMessages()) .contains( @@ -647,7 +667,7 @@ void testLogDirectPathMisconfig_AttemptDirectPathNotSetAndAttemptDirectPathXdsSe InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler); InstantiatingGrpcChannelProvider provider = - createChannelProviderBuilderForDirectPathLogTests().build(); + createChannelProviderBuilderForDirectPathLogTests().setCertificateBasedAccess(cba).build(); createAndCloseTransportChannel(provider); assertThat(logHandler.getAllMessages()) .contains( @@ -663,6 +683,7 @@ void testLogDirectPathMisconfig_shouldNotLogInTheBuilder() { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPathXds() .setAttemptDirectPath(true) + .setCertificateBasedAccess(cba) .build(); assertThat(logHandler.getAllMessages()).isEmpty(); @@ -680,6 +701,7 @@ void testLogDirectPathMisconfigWrongCredential() throws Exception { .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .setEndpoint(DEFAULT_ENDPOINT) + .setCertificateBasedAccess(cba) .build(); TransportChannel transportChannel = provider.getTransportChannel(); @@ -706,6 +728,7 @@ void testLogDirectPathMisconfigNotOnGCE() throws Exception { .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .setEndpoint(DEFAULT_ENDPOINT) + .setCertificateBasedAccess(cba) .build(); TransportChannel transportChannel = provider.getTransportChannel(); @@ -731,6 +754,7 @@ public void canUseDirectPath_happyPath() throws IOException { .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -758,6 +782,7 @@ public void canUseDirectPath_boundTokenNotEnabledWithNonComputeCredentials() { .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .setAllowHardBoundTokenTypes(Collections.singletonList(HardBoundTokenTypes.ALTS)) .setCredentials(credentials) @@ -782,6 +807,7 @@ public void canUseDirectPath_happyPathWithBoundToken() throws IOException { .thenReturn(ComputeEngineCredentials.newBuilder()); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setAllowHardBoundTokenTypes(Collections.singletonList(HardBoundTokenTypes.ALTS)) @@ -809,6 +835,7 @@ public void canUseDirectPath_directPathEnvVarDisabled() throws IOException { .thenReturn("true"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -829,6 +856,7 @@ public void canUseDirectPath_directPathEnvVarNotSet_attemptDirectPathIsTrue() { System.setProperty("os.name", "Linux"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT); @@ -842,6 +870,7 @@ public void canUseDirectPath_directPathEnvVarNotSet_attemptDirectPathIsFalse() { System.setProperty("os.name", "Linux"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(false) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT); @@ -861,6 +890,7 @@ public void canUseDirectPath_nonComputeCredentials() { .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .setCredentials(credentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -880,6 +910,7 @@ public void canUseDirectPath_isNotOnComputeEngine_invalidOsNameSystemProperty() .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -899,6 +930,7 @@ public void canUseDirectPath_isNotOnComputeEngine_invalidSystemProductName() { .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -921,6 +953,7 @@ public void canUseDirectPath_isNotOnComputeEngine_unableToGetSystemProductName() .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) + .setCertificateBasedAccess(cba) .setEnvProvider(envProvider); InstantiatingGrpcChannelProvider provider = new InstantiatingGrpcChannelProvider(builder, ""); Truth.assertThat(provider.canUseDirectPath()).isFalse(); @@ -937,6 +970,7 @@ public void canUseDirectPath_nonGDUUniverseDomain() { String nonGDUEndpoint = "test.random.com:443"; InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(nonGDUEndpoint) @@ -966,6 +1000,7 @@ void providersInitializedWithConflictingApiKeyCredentialHeaders_removesDuplicate ApiKeyCredentials apiKeyCredentials = ApiKeyCredentials.create(correctApiKey); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setCredentials(apiKeyCredentials) .setHeaderProvider(getHeaderProviderWithApiKeyHeader()) .setEndpoint("test.random.com:443"); @@ -983,6 +1018,7 @@ void providersInitializedWithConflictingNonApiKeyCredentialHeaders_doesNotRemove header.put(AuthHttpConstants.AUTHORIZATION, authProvidedHeader); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setCredentials(computeEngineCredentials) .setHeaderProvider(FixedHeaderProvider.create(header)) .setEndpoint("test.random.com:443"); @@ -1011,6 +1047,7 @@ void buildProvider_handlesNullCredentialsMetadataRequest() throws IOException { Mockito.when(credentials.getRequestMetadata()).thenReturn(null); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setHeaderProvider(getHeaderProviderWithApiKeyHeader()) .setEndpoint("test.random.com:443"); @@ -1028,6 +1065,7 @@ void buildProvider_handlesErrorRetrievingCredentialsMetadataRequest() throws IOE .thenThrow(new IOException("Error getting request metadata")); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(cba) .setHeaderProvider(getHeaderProviderWithApiKeyHeader()) .setEndpoint("test.random.com:443"); InstantiatingGrpcChannelProvider provider = builder.build(); diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java index f08a3c3a89..9b8cc110e9 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java @@ -51,7 +51,7 @@ import com.google.api.gax.rpc.StubSettings; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.UnaryCallSettings; -import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.Credentials; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -83,7 +83,7 @@ private static class FakeStubSettings extends StubSettings { public static final int DEFAULT_SERVICE_PORT = 443; public static final String DEFAULT_SERVICE_ENDPOINT = DEFAULT_SERVICE_ADDRESS + ':' + DEFAULT_SERVICE_PORT; - public static final MtlsProvider DEFAULT_MTLS_PROVIDER = new MtlsProvider(); + public static final MtlsProvider DEFAULT_MTLS_PROVIDER = null; public static final ImmutableList DEFAULT_SERVICE_SCOPES = ImmutableList.builder() .add("https://www.googleapis.com/auth/pubsub") diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java index 1171ef1b59..54e3183cdd 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java @@ -36,7 +36,8 @@ import com.google.api.gax.rpc.FixedHeaderProvider; import com.google.api.gax.rpc.HeaderProvider; import com.google.api.gax.rpc.TransportChannelProvider; -import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.api.gax.rpc.mtls.CertificateBasedAccess; +import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.Credentials; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; @@ -67,6 +68,7 @@ public final class InstantiatingHttpJsonChannelProvider implements TransportChan private final String endpoint; private final HttpTransport httpTransport; private final MtlsProvider mtlsProvider; + private final CertificateBasedAccess certificateBasedAccess; private InstantiatingHttpJsonChannelProvider( Executor executor, @@ -74,13 +76,15 @@ private InstantiatingHttpJsonChannelProvider( HttpJsonInterceptorProvider interceptorProvider, String endpoint, HttpTransport httpTransport, - MtlsProvider mtlsProvider) { + MtlsProvider mtlsProvider, + CertificateBasedAccess certificateBasedAccess) { this.executor = executor; this.headerProvider = headerProvider; this.interceptorProvider = interceptorProvider; this.endpoint = endpoint; this.httpTransport = httpTransport; this.mtlsProvider = mtlsProvider; + this.certificateBasedAccess = certificateBasedAccess; } /** @@ -173,7 +177,7 @@ public TransportChannelProvider withCredentials(Credentials credentials) { } HttpTransport createHttpTransport() throws IOException, GeneralSecurityException { - if (mtlsProvider.useMtlsClientCertificate()) { + if (certificateBasedAccess.useMtlsClientCertificate()) { KeyStore mtlsKeyStore = mtlsProvider.getKeyStore(); if (mtlsKeyStore != null) { return new NetHttpTransport.Builder().trustCertificates(null, mtlsKeyStore, "").build(); @@ -237,7 +241,8 @@ public static final class Builder { private HttpJsonInterceptorProvider interceptorProvider; private String endpoint; private HttpTransport httpTransport; - private MtlsProvider mtlsProvider = new MtlsProvider(); + private MtlsProvider mtlsProvider = null; + private CertificateBasedAccess certificateBasedAccess; private Builder() {} @@ -247,6 +252,7 @@ private Builder(InstantiatingHttpJsonChannelProvider provider) { this.endpoint = provider.endpoint; this.httpTransport = provider.httpTransport; this.mtlsProvider = provider.mtlsProvider; + this.certificateBasedAccess = provider.certificateBasedAccess; this.interceptorProvider = provider.interceptorProvider; } @@ -317,9 +323,21 @@ Builder setMtlsProvider(MtlsProvider mtlsProvider) { return this; } + @VisibleForTesting + Builder setCertificateBasedAccess(CertificateBasedAccess certificateBasedAccess) { + this.certificateBasedAccess = certificateBasedAccess; + return this; + } + public InstantiatingHttpJsonChannelProvider build() { return new InstantiatingHttpJsonChannelProvider( - executor, headerProvider, interceptorProvider, endpoint, httpTransport, mtlsProvider); + executor, + headerProvider, + interceptorProvider, + endpoint, + httpTransport, + mtlsProvider, + certificateBasedAccess); } } } diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java index 3e6b2d56d1..7f70a8cedd 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java @@ -35,7 +35,8 @@ import com.google.api.gax.rpc.HeaderProvider; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.mtls.AbstractMtlsTransportChannelTest; -import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.api.gax.rpc.mtls.CertificateBasedAccess; +import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Collections; @@ -43,6 +44,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -50,13 +52,22 @@ class InstantiatingHttpJsonChannelProviderTest extends AbstractMtlsTransportChan private static final String DEFAULT_ENDPOINT = "localhost:8080"; private static final Map DEFAULT_HEADER_MAP = Collections.emptyMap(); + private CertificateBasedAccess cba; + + @BeforeEach + public void setup() throws IOException { + cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "never" : "false"); + } @Test void basicTest() throws IOException { ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1); executor.shutdown(); - TransportChannelProvider provider = InstantiatingHttpJsonChannelProvider.newBuilder().build(); + TransportChannelProvider provider = + InstantiatingHttpJsonChannelProvider.newBuilder().setCertificateBasedAccess(cba).build(); assertThat(provider.needsEndpoint()).isTrue(); provider = provider.withEndpoint(DEFAULT_ENDPOINT); @@ -108,7 +119,10 @@ void basicTest() throws IOException { @Test void managedChannelUsesDefaultChannelExecutor() throws IOException { InstantiatingHttpJsonChannelProvider instantiatingHttpJsonChannelProvider = - InstantiatingHttpJsonChannelProvider.newBuilder().setEndpoint(DEFAULT_ENDPOINT).build(); + InstantiatingHttpJsonChannelProvider.newBuilder() + .setEndpoint(DEFAULT_ENDPOINT) + .setCertificateBasedAccess(cba) + .build(); instantiatingHttpJsonChannelProvider = (InstantiatingHttpJsonChannelProvider) instantiatingHttpJsonChannelProvider.withHeaders(DEFAULT_HEADER_MAP); @@ -140,6 +154,7 @@ void managedChannelUsesCustomExecutor() throws IOException { InstantiatingHttpJsonChannelProvider.newBuilder() .setEndpoint(DEFAULT_ENDPOINT) .setExecutor(executor) + .setCertificateBasedAccess(cba) .build(); instantiatingHttpJsonChannelProvider = (InstantiatingHttpJsonChannelProvider) @@ -164,12 +179,14 @@ void managedChannelUsesCustomExecutor() throws IOException { } @Override - protected Object getMtlsObjectFromTransportChannel(MtlsProvider provider) + protected Object getMtlsObjectFromTransportChannel( + MtlsProvider provider, CertificateBasedAccess cba) throws IOException, GeneralSecurityException { InstantiatingHttpJsonChannelProvider channelProvider = InstantiatingHttpJsonChannelProvider.newBuilder() .setEndpoint("localhost:8080") .setMtlsProvider(provider) + .setCertificateBasedAccess(cba) .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .build(); diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index ce5cf8c786..1b64a76bd1 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -31,7 +31,9 @@ import com.google.api.core.InternalApi; import com.google.api.gax.rpc.internal.EnvironmentProvider; -import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.api.gax.rpc.mtls.CertificateBasedAccess; +import com.google.api.gax.rpc.mtls.DefaultMtlsProviderFactory; +import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.Credentials; import com.google.auth.oauth2.ComputeEngineCredentials; import com.google.auto.value.AutoValue; @@ -117,6 +119,9 @@ public static EndpointContext getDefaultInstance() { @Nullable public abstract MtlsProvider mtlsProvider(); + @Nullable + public abstract CertificateBasedAccess certificateBasedAccess(); + public abstract boolean usingGDCH(); abstract String resolvedUniverseDomain(); @@ -210,7 +215,11 @@ public abstract static class Builder { public abstract Builder setSwitchToMtlsEndpointAllowed(boolean switchToMtlsEndpointAllowed); - public abstract Builder setMtlsProvider(MtlsProvider mtlsProvider); + public abstract Builder setMtlsProvider( + com.google.api.gax.rpc.mtls.v2.MtlsProvider mtlsProvider); + + public abstract Builder setCertificateBasedAccess( + CertificateBasedAccess certificateBasedAccess); public abstract Builder setUsingGDCH(boolean usingGDCH); @@ -238,8 +247,11 @@ public abstract static class Builder { abstract boolean switchToMtlsEndpointAllowed(); + @Nullable abstract MtlsProvider mtlsProvider(); + abstract CertificateBasedAccess certificateBasedAccess(); + abstract boolean usingGDCH(); abstract String resolvedUniverseDomain(); @@ -272,7 +284,18 @@ private String determineUniverseDomain() { /** Determines the fully resolved endpoint and universe domain values */ private String determineEndpoint() throws IOException { - MtlsProvider mtlsProvider = mtlsProvider() == null ? new MtlsProvider() : mtlsProvider(); + MtlsProvider mtlsProvider = mtlsProvider(); + if (mtlsProvider == null) { + try { + mtlsProvider = DefaultMtlsProviderFactory.create(); + } catch (IOException e) { + // throw e; + } + } + CertificateBasedAccess cba = + certificateBasedAccess() == null + ? CertificateBasedAccess.createWithSystemEnv() + : certificateBasedAccess(); // TransportChannelProvider's endpoint will override the ClientSettings' endpoint String customEndpoint = transportChannelProviderEndpoint() == null @@ -294,7 +317,7 @@ private String determineEndpoint() throws IOException { String endpoint = mtlsEndpointResolver( - customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider); + customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider, cba); // Check if mTLS is configured with non-GDU if (endpoint.equals(mtlsEndpoint()) @@ -347,16 +370,17 @@ String mtlsEndpointResolver( String endpoint, String mtlsEndpoint, boolean switchToMtlsEndpointAllowed, - MtlsProvider mtlsProvider) + MtlsProvider mtlsProvider, + CertificateBasedAccess cba) throws IOException { if (switchToMtlsEndpointAllowed && mtlsProvider != null) { - switch (mtlsProvider.getMtlsEndpointUsagePolicy()) { + switch (cba.getMtlsEndpointUsagePolicy()) { case ALWAYS: return mtlsEndpoint; case NEVER: return endpoint; default: - if (mtlsProvider.useMtlsClientCertificate() && mtlsProvider.getKeyStore() != null) { + if (cba.useMtlsClientCertificate() && mtlsProvider.getKeyStore() != null) { return mtlsEndpoint; } return endpoint; diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java new file mode 100644 index 0000000000..004cf1aa70 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.rpc.mtls; + +import com.google.api.gax.rpc.internal.EnvironmentProvider; + +/** Utility class for handling certificate-based access configurations. */ +public class CertificateBasedAccess { + + private final EnvironmentProvider envProvider; + + public CertificateBasedAccess(EnvironmentProvider envProvider) { + this.envProvider = envProvider; + } + + public static CertificateBasedAccess createWithSystemEnv() { + return new CertificateBasedAccess(System::getenv); + } + + /** + * The policy for mutual TLS endpoint usage. NEVER means always use regular endpoint; ALWAYS means + * always use mTLS endpoint; AUTO means auto switch to mTLS endpoint if client certificate exists + * and should be used. + */ + public enum MtlsEndpointUsagePolicy { + NEVER, + AUTO, + ALWAYS; + } + + /** Returns if mutual TLS client certificate should be used. */ + public boolean useMtlsClientCertificate() { + String useClientCertificate = envProvider.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE"); + return "true".equals(useClientCertificate); + } + + /** Returns the current mutual TLS endpoint usage policy. */ + public MtlsEndpointUsagePolicy getMtlsEndpointUsagePolicy() { + String mtlsEndpointUsagePolicy = envProvider.getenv("GOOGLE_API_USE_MTLS_ENDPOINT"); + if ("never".equals(mtlsEndpointUsagePolicy)) { + return MtlsEndpointUsagePolicy.NEVER; + } else if ("always".equals(mtlsEndpointUsagePolicy)) { + return MtlsEndpointUsagePolicy.ALWAYS; + } + return MtlsEndpointUsagePolicy.AUTO; + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateSourceUnavailableException.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateSourceUnavailableException.java new file mode 100644 index 0000000000..5657dfb1f3 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateSourceUnavailableException.java @@ -0,0 +1,73 @@ +/* + * Copyright 2025 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.rpc.mtls; + +import java.io.IOException; + +/** + * This exception is thrown by certificate providers in the Google auth library when the certificate + * source is unavailable. This means that the transport layer should move on to the next certificate + * source provider type. + */ +public class CertificateSourceUnavailableException extends IOException { + + /** + * Constructor with a message and throwable cause. + * + * @param message The detail message (which is saved for later retrieval by the {@link + * #getMessage()} method) + * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). + * (A null value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + public CertificateSourceUnavailableException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructor with a throwable cause. + * + * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). + * (A null value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + public CertificateSourceUnavailableException(Throwable cause) { + super(cause); + } + + /** + * Constructor with a message. + * + * @param message The detail message (which is saved for later retrieval by the {@link + * #getMessage()} method) + */ + public CertificateSourceUnavailableException(String message) { + super(message); + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/ContextAwareMetadataJson.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/ContextAwareMetadataJson.java index 25d7d27de7..145799a3c0 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/ContextAwareMetadataJson.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/ContextAwareMetadataJson.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2025 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -32,12 +32,10 @@ import com.google.api.client.json.GenericJson; import com.google.api.client.util.Key; -import com.google.api.core.BetaApi; import com.google.common.collect.ImmutableList; import java.util.List; /** Data class representing context_aware_metadata.json file. */ -@BetaApi public class ContextAwareMetadataJson extends GenericJson { /** Cert provider command */ @Key("cert_provider_command") diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/DefaultMtlsProviderFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/DefaultMtlsProviderFactory.java new file mode 100644 index 0000000000..99b91ceecd --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/DefaultMtlsProviderFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright 2025 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.rpc.mtls; + +import java.io.IOException; + +public class DefaultMtlsProviderFactory { + + /** + * Creates an instance of {@link MtlsProvider}. It first attempts to create an {@link + * com.google.auth.mtls.X509Provider}. If the certificate source is unavailable, it falls back to + * creating a {@link SecureConnectProvider}. If the secure connect provider also fails, it throws + * the original {@link com.google.auth.mtls.CertificateSourceUnavailableException}. + * + * @return an instance of {@link MtlsProvider}. + * @throws com.google.auth.mtls.CertificateSourceUnavailableException if neither provider can be + * created. + * @throws IOException if an I/O error occurs during provider creation. + */ + public static com.google.api.gax.rpc.mtls.v2.MtlsProvider create() throws IOException { + com.google.api.gax.rpc.mtls.v2.MtlsProvider mtlsProvider; + try { + mtlsProvider = new X509Provider(); + mtlsProvider.getKeyStore(); + return mtlsProvider; + } catch (CertificateSourceUnavailableException e) { + try { + mtlsProvider = new SecureConnectProvider(); + mtlsProvider.getKeyStore(); + return mtlsProvider; + } catch (CertificateSourceUnavailableException ex) { + throw new CertificateSourceUnavailableException( + "No MtlsSource is available on this device."); + } + } + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/SecureConnectProvider.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/SecureConnectProvider.java new file mode 100644 index 0000000000..1b8771a42c --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/SecureConnectProvider.java @@ -0,0 +1,149 @@ +/* + * Copyright 2025 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.rpc.mtls; + +import com.google.api.client.json.JsonParser; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.client.util.SecurityUtils; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.List; + +/** + * Provider class for mutual TLS. It is used to configure the mutual TLS in the transport with the + * default client certificate on device. + */ +public class SecureConnectProvider implements com.google.api.gax.rpc.mtls.v2.MtlsProvider { + interface ProcessProvider { + public Process createProcess(InputStream metadata) throws IOException; + } + + static class DefaultProcessProvider implements ProcessProvider { + @Override + public Process createProcess(InputStream metadata) throws IOException { + if (metadata == null) { + return null; + } + List command = extractCertificateProviderCommand(metadata); + return new ProcessBuilder(command).start(); + } + } + + private static final String DEFAULT_CONTEXT_AWARE_METADATA_PATH = + System.getProperty("user.home") + "/.secureConnect/context_aware_metadata.json"; + + private String metadataPath; + private ProcessProvider processProvider; + + @VisibleForTesting + SecureConnectProvider(ProcessProvider processProvider, String metadataPath) { + this.processProvider = processProvider; + this.metadataPath = metadataPath; + } + + public SecureConnectProvider() { + this(new DefaultProcessProvider(), DEFAULT_CONTEXT_AWARE_METADATA_PATH); + } + + /** The mutual TLS key store created with the default client certificate on device. */ + @Override + public KeyStore getKeyStore() throws IOException { + try (InputStream stream = new FileInputStream(metadataPath)) { + return getKeyStore(stream, processProvider); + } catch (InterruptedException e) { + throw new IOException("Interrupted executing certificate provider command", e); + } catch (GeneralSecurityException e) { + throw new CertificateSourceUnavailableException( + "SecureConnect encountered GeneralSecurityException:", e); + } catch (FileNotFoundException exception) { + // If the metadata file doesn't exist, then there is no key store, so we will throw sentinel + // error + throw new CertificateSourceUnavailableException("SecureConnect metadata does not exist."); + } + } + + @VisibleForTesting + static KeyStore getKeyStore(InputStream metadata, ProcessProvider processProvider) + throws IOException, InterruptedException, GeneralSecurityException { + Process process = processProvider.createProcess(metadata); + + // Run the command and timeout after 1000 milliseconds. + int exitCode = runCertificateProviderCommand(process, 1000); + if (exitCode != 0) { + throw new IOException("Cert provider command failed with exit code: " + exitCode); + } + + // Create mTLS key store with the input certificates from shell command. + return SecurityUtils.createMtlsKeyStore(process.getInputStream()); + } + + @VisibleForTesting + static ImmutableList extractCertificateProviderCommand(InputStream contextAwareMetadata) + throws IOException { + JsonParser parser = new GsonFactory().createJsonParser(contextAwareMetadata); + ContextAwareMetadataJson json = parser.parse(ContextAwareMetadataJson.class); + return json.getCommands(); + } + + @VisibleForTesting + static int runCertificateProviderCommand(Process commandProcess, long timeoutMilliseconds) + throws IOException, InterruptedException { + long startTime = System.currentTimeMillis(); + long remainTime = timeoutMilliseconds; + + // In the while loop, keep checking if the process is terminated every 100 milliseconds + // until timeout is reached or process is terminated. In getKeyStore we set timeout to + // 1000 milliseconds, so 100 millisecond is a good number for the sleep. + while (remainTime > 0) { + Thread.sleep(Math.min(remainTime + 1, 100)); + remainTime -= System.currentTimeMillis() - startTime; + + try { + return commandProcess.exitValue(); + } catch (IllegalThreadStateException ignored) { + // exitValue throws IllegalThreadStateException if process has not yet terminated. + // Once the process is terminated, exitValue no longer throws exception. Therefore + // in the while loop, we use exitValue to check if process is terminated. See + // https://docs.oracle.com/javase/7/docs/api/java/lang/Process.html#exitValue() + // for more details. + } + } + + commandProcess.destroy(); + throw new IOException("cert provider command timed out"); + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/WorkloadCertificateConfiguration.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/WorkloadCertificateConfiguration.java new file mode 100644 index 0000000000..68c27553c2 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/WorkloadCertificateConfiguration.java @@ -0,0 +1,100 @@ +/* + * Copyright 2025 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.rpc.mtls; + +import com.google.api.client.json.GenericJson; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.gson.GsonFactory; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +class WorkloadCertificateConfiguration { + + private String certPath; + private String privateKeyPath; + + private static JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); + private static JsonObjectParser parser = new JsonObjectParser(jsonFactory); + + WorkloadCertificateConfiguration(String certPath, String privateKeyPath) { + this.certPath = certPath; + this.privateKeyPath = privateKeyPath; + } + + String getCertPath() { + return certPath; + } + + String getPrivateKeyPath() { + return privateKeyPath; + } + + static WorkloadCertificateConfiguration fromCertificateConfigurationStream( + InputStream certConfigStream) throws IOException { + Preconditions.checkNotNull(certConfigStream); + + GenericJson fileContents = + parser.parseAndClose(certConfigStream, StandardCharsets.UTF_8, GenericJson.class); + + Map certConfigs = (Map) fileContents.get("cert_configs"); + if (certConfigs == null) { + throw new IllegalArgumentException( + "The cert_configs object must be provided in the certificate configuration file."); + } + + Map workloadConfig = (Map) certConfigs.get("workload"); + if (workloadConfig == null) { + // Throw a CertificateSourceUnavailableException because there is no workload cert source. + // This tells the transport layer that it should check for another certificate source type. + throw new CertificateSourceUnavailableException( + "A workload certificate configuration must be provided in the cert_configs object."); + } + + String certPath = (String) workloadConfig.get("cert_path"); + if (Strings.isNullOrEmpty(certPath)) { + throw new IllegalArgumentException( + "The cert_path field must be provided in the workload certificate configuration."); + } + + String privateKeyPath = (String) workloadConfig.get("key_path"); + if (Strings.isNullOrEmpty(privateKeyPath)) { + throw new IllegalArgumentException( + "The key_path field must be provided in the workload certificate configuration."); + } + + return new WorkloadCertificateConfiguration(certPath, privateKeyPath); + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/X509Provider.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/X509Provider.java new file mode 100644 index 0000000000..3041c2a2fe --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/X509Provider.java @@ -0,0 +1,199 @@ +/* + * Copyright 2025 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.rpc.mtls; + +import com.google.api.client.util.SecurityUtils; +import com.google.common.base.Strings; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.security.KeyStore; +import java.util.Locale; + +/** + * This class provides certificate key stores to the Google Auth library transport layer via + * certificate configuration files. This is only meant to be used internally to Google Cloud + * libraries, and the public facing methods may be changed without notice, and have no guarantee of + * backwards compatability. + */ +public class X509Provider implements com.google.api.gax.rpc.mtls.v2.MtlsProvider { + static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG"; + static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json"; + static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud"; + + private String certConfigPathOverride; + + /** + * Creates an X509 provider with an override path for the certificate configuration, bypassing the + * normal checks for the well known certificate configuration file path and environment variable. + * This is meant for internal Google Cloud usage and behavior may be changed without warning. + * + * @param certConfigPathOverride the path to read the certificate configuration from. + */ + public X509Provider(String certConfigPathOverride) { + this.certConfigPathOverride = certConfigPathOverride; + } + + /** + * Creates a new X.509 provider that will check the environment variable path and the well known + * Gcloud certificate configuration location. This is meant for internal Google Cloud usage and + * behavior may be changed without warning. + */ + public X509Provider() { + this(null); + } + + /** + * Finds the certificate configuration file, then builds a Keystore using the X.509 certificate + * and private key pointed to by the configuration. This will check the following locations in + * order. + * + *
    + *
  • The certificate config override path, if set. + *
  • The path pointed to by the "GOOGLE_API_CERTIFICATE_CONFIG" environment variable + *
  • The well known gcloud location for the certificate configuration file. + *
+ * + * @return a KeyStore containing the X.509 certificate specified by the certificate configuration. + * @throws IOException if there is an error retrieving the certificate configuration. + */ + @Override + public KeyStore getKeyStore() throws IOException { + + WorkloadCertificateConfiguration workloadCertConfig = getWorkloadCertificateConfiguration(); + + InputStream certStream = null; + InputStream privateKeyStream = null; + SequenceInputStream certAndPrivateKeyStream = null; + try { + // Read the certificate and private key file paths into separate streams. + File certFile = new File(workloadCertConfig.getCertPath()); + File privateKeyFile = new File(workloadCertConfig.getPrivateKeyPath()); + certStream = createInputStream(certFile); + privateKeyStream = createInputStream(privateKeyFile); + + // Merge the two streams into a single stream. + certAndPrivateKeyStream = new SequenceInputStream(certStream, privateKeyStream); + + // Build a key store using the combined stream. + return SecurityUtils.createMtlsKeyStore(certAndPrivateKeyStream); + } catch (CertificateSourceUnavailableException e) { + // Throw the CertificateSourceUnavailableException without wrapping. + throw e; + } catch (Exception e) { + // Wrap all other exception types to an IOException. + throw new IOException(e); + } finally { + if (certStream != null) { + certStream.close(); + } + if (privateKeyStream != null) { + privateKeyStream.close(); + } + if (certAndPrivateKeyStream != null) { + certAndPrivateKeyStream.close(); + } + } + } + + private WorkloadCertificateConfiguration getWorkloadCertificateConfiguration() + throws IOException { + File certConfig; + if (this.certConfigPathOverride != null) { + certConfig = new File(certConfigPathOverride); + } else { + String envCredentialsPath = getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE); + if (!Strings.isNullOrEmpty(envCredentialsPath)) { + certConfig = new File(envCredentialsPath); + } else { + certConfig = getWellKnownCertificateConfigFile(); + } + } + InputStream certConfigStream = null; + try { + if (!isFile(certConfig)) { + // Path will be put in the message from the catch block below + throw new CertificateSourceUnavailableException("File does not exist."); + } + certConfigStream = createInputStream(certConfig); + return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream); + } finally { + if (certConfigStream != null) { + certConfigStream.close(); + } + } + } + + /* + * Start of methods to allow overriding in the test code to isolate from the environment. + */ + boolean isFile(File file) { + return file.isFile(); + } + + InputStream createInputStream(File file) throws FileNotFoundException { + return new FileInputStream(file); + } + + String getEnv(String name) { + return System.getenv(name); + } + + String getOsName() { + return getProperty("os.name", "").toLowerCase(Locale.US); + } + + String getProperty(String property, String def) { + return System.getProperty(property, def); + } + + /* + * End of methods to allow overriding in the test code to isolate from the environment. + */ + + private File getWellKnownCertificateConfigFile() { + File cloudConfigPath; + String envPath = getEnv("CLOUDSDK_CONFIG"); + if (envPath != null) { + cloudConfigPath = new File(envPath); + } else if (getOsName().indexOf("windows") >= 0) { + File appDataPath = new File(getEnv("APPDATA")); + cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY); + } else { + File configPath = new File(getProperty("user.home", ""), ".config"); + cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY); + } + return new File(cloudConfigPath, WELL_KNOWN_CERTIFICATE_CONFIG_FILE); + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/v2/MtlsProvider.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/v2/MtlsProvider.java new file mode 100644 index 0000000000..43d80b82e3 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/v2/MtlsProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.rpc.mtls.v2; + +import java.io.IOException; +import java.security.KeyStore; + +public interface MtlsProvider { + /** Returns the mutual TLS key store. */ + KeyStore getKeyStore() throws IOException; +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index dd1d383801..f9042f6d4f 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -34,7 +34,8 @@ import com.google.api.gax.core.NoCredentialsProvider; import com.google.api.gax.rpc.internal.EnvironmentProvider; -import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.api.gax.rpc.mtls.CertificateBasedAccess; +import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.api.gax.rpc.testing.FakeMtlsProvider; import com.google.auth.Credentials; import com.google.auth.oauth2.ComputeEngineCredentials; @@ -53,12 +54,15 @@ class EndpointContextTest { @BeforeEach void setUp() throws IOException { + MtlsProvider mtlsProvider = + new FakeMtlsProvider(FakeMtlsProvider.createTestMtlsKeyStore(), "", false); defaultEndpointContextBuilder = EndpointContext.newBuilder() .setServiceName("test") .setUniverseDomain(Credentials.GOOGLE_DEFAULT_UNIVERSE) .setClientSettingsEndpoint(DEFAULT_ENDPOINT) - .setMtlsEndpoint(DEFAULT_MTLS_ENDPOINT); + .setMtlsEndpoint(DEFAULT_MTLS_ENDPOINT) + .setMtlsProvider(mtlsProvider); statusCode = Mockito.mock(StatusCode.class); Mockito.when(statusCode.getCode()).thenReturn(StatusCode.Code.UNAUTHENTICATED); Mockito.when(statusCode.getTransportCode()).thenReturn(Status.Code.UNAUTHENTICATED); @@ -66,107 +70,111 @@ void setUp() throws IOException { @Test void mtlsEndpointResolver_switchToMtlsAllowedIsFalse() throws IOException { - boolean useClientCertificate = true; boolean throwExceptionForGetKeyStore = false; MtlsProvider mtlsProvider = new FakeMtlsProvider( - useClientCertificate, - MtlsProvider.MtlsEndpointUsagePolicy.AUTO, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - throwExceptionForGetKeyStore); + FakeMtlsProvider.createTestMtlsKeyStore(), "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = false; + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); Truth.assertThat( defaultEndpointContextBuilder.mtlsEndpointResolver( - DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + DEFAULT_ENDPOINT, + DEFAULT_MTLS_ENDPOINT, + switchToMtlsEndpointAllowed, + mtlsProvider, + cba)) .isEqualTo(DEFAULT_ENDPOINT); } @Test void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAuto() throws IOException { - boolean useClientCertificate = true; boolean throwExceptionForGetKeyStore = false; MtlsProvider mtlsProvider = new FakeMtlsProvider( - useClientCertificate, - MtlsProvider.MtlsEndpointUsagePolicy.AUTO, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - throwExceptionForGetKeyStore); + FakeMtlsProvider.createTestMtlsKeyStore(), "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); Truth.assertThat( defaultEndpointContextBuilder.mtlsEndpointResolver( - DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + DEFAULT_ENDPOINT, + DEFAULT_MTLS_ENDPOINT, + switchToMtlsEndpointAllowed, + mtlsProvider, + cba)) .isEqualTo(DEFAULT_MTLS_ENDPOINT); } @Test void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAlways() throws IOException { - boolean useClientCertificate = true; boolean throwExceptionForGetKeyStore = false; MtlsProvider mtlsProvider = new FakeMtlsProvider( - useClientCertificate, - MtlsProvider.MtlsEndpointUsagePolicy.ALWAYS, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - throwExceptionForGetKeyStore); + FakeMtlsProvider.createTestMtlsKeyStore(), "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "always" : "true"); Truth.assertThat( defaultEndpointContextBuilder.mtlsEndpointResolver( - DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + DEFAULT_ENDPOINT, + DEFAULT_MTLS_ENDPOINT, + switchToMtlsEndpointAllowed, + mtlsProvider, + cba)) .isEqualTo(DEFAULT_MTLS_ENDPOINT); } @Test void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageNever() throws IOException { - boolean useClientCertificate = true; boolean throwExceptionForGetKeyStore = false; MtlsProvider mtlsProvider = new FakeMtlsProvider( - useClientCertificate, - MtlsProvider.MtlsEndpointUsagePolicy.NEVER, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - throwExceptionForGetKeyStore); + FakeMtlsProvider.createTestMtlsKeyStore(), "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "never" : "true"); Truth.assertThat( defaultEndpointContextBuilder.mtlsEndpointResolver( - DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + DEFAULT_ENDPOINT, + DEFAULT_MTLS_ENDPOINT, + switchToMtlsEndpointAllowed, + mtlsProvider, + cba)) .isEqualTo(DEFAULT_ENDPOINT); } @Test void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_useCertificateIsFalse_nullMtlsKeystore() throws IOException { - boolean useClientCertificate = false; boolean throwExceptionForGetKeyStore = false; - MtlsProvider mtlsProvider = - new FakeMtlsProvider( - useClientCertificate, - MtlsProvider.MtlsEndpointUsagePolicy.AUTO, - null, - "", - throwExceptionForGetKeyStore); + MtlsProvider mtlsProvider = new FakeMtlsProvider(null, "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false"); Truth.assertThat( defaultEndpointContextBuilder.mtlsEndpointResolver( - DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + DEFAULT_ENDPOINT, + DEFAULT_MTLS_ENDPOINT, + switchToMtlsEndpointAllowed, + mtlsProvider, + cba)) .isEqualTo(DEFAULT_ENDPOINT); } @Test void mtlsEndpointResolver_getKeyStore_throwsIOException() throws IOException { - boolean useClientCertificate = true; boolean throwExceptionForGetKeyStore = true; - MtlsProvider mtlsProvider = - new FakeMtlsProvider( - useClientCertificate, - MtlsProvider.MtlsEndpointUsagePolicy.AUTO, - null, - "", - throwExceptionForGetKeyStore); + MtlsProvider mtlsProvider = new FakeMtlsProvider(null, "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); assertThrows( IOException.class, () -> @@ -174,7 +182,8 @@ void mtlsEndpointResolver_getKeyStore_throwsIOException() throws IOException { DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, - mtlsProvider)); + mtlsProvider, + cba)); } @Test @@ -260,18 +269,17 @@ void endpointContextBuild_noUniverseDomain_noEndpoints() throws IOException { @Test void endpointContextBuild_mtlsConfigured_GDU() throws IOException { MtlsProvider mtlsProvider = - new FakeMtlsProvider( - true, - MtlsProvider.MtlsEndpointUsagePolicy.ALWAYS, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - false); + new FakeMtlsProvider(FakeMtlsProvider.createTestMtlsKeyStore(), "", false); + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "always" : "true"); EndpointContext endpointContext = defaultEndpointContextBuilder .setClientSettingsEndpoint(null) .setTransportChannelProviderEndpoint(null) .setSwitchToMtlsEndpointAllowed(true) .setMtlsProvider(mtlsProvider) + .setCertificateBasedAccess(cba) .build(); Truth.assertThat(endpointContext.resolvedEndpoint()).isEqualTo(DEFAULT_MTLS_ENDPOINT); Truth.assertThat(endpointContext.resolvedUniverseDomain()) @@ -282,19 +290,18 @@ void endpointContextBuild_mtlsConfigured_GDU() throws IOException { void endpointContextBuild_mtlsConfigured_nonGDU_throwsIllegalArgumentException() throws IOException { MtlsProvider mtlsProvider = - new FakeMtlsProvider( - true, - MtlsProvider.MtlsEndpointUsagePolicy.ALWAYS, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - false); + new FakeMtlsProvider(FakeMtlsProvider.createTestMtlsKeyStore(), "", false); + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "always" : "true"); EndpointContext.Builder endpointContextBuilder = defaultEndpointContextBuilder .setUniverseDomain("random.com") .setClientSettingsEndpoint(null) .setTransportChannelProviderEndpoint(null) .setSwitchToMtlsEndpointAllowed(true) - .setMtlsProvider(mtlsProvider); + .setMtlsProvider(mtlsProvider) + .setCertificateBasedAccess(cba); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, endpointContextBuilder::build); Truth.assertThat(exception.getMessage()) diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java index 1251dc47d1..87a806714c 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java @@ -35,7 +35,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.google.api.gax.rpc.mtls.MtlsProvider.MtlsEndpointUsagePolicy; import com.google.api.gax.rpc.testing.FakeMtlsProvider; import java.io.IOException; import java.security.GeneralSecurityException; @@ -48,42 +47,47 @@ public abstract class AbstractMtlsTransportChannelTest { * GrpcTransportChannel, the mTLS object is the ChannelCredentials. The transport channel is mTLS * if and only if the related mTLS object is not null. */ - protected abstract Object getMtlsObjectFromTransportChannel(MtlsProvider provider) + protected abstract Object getMtlsObjectFromTransportChannel( + com.google.api.gax.rpc.mtls.v2.MtlsProvider provider, CertificateBasedAccess cba) throws IOException, GeneralSecurityException; @Test void testNotUseClientCertificate() throws IOException, GeneralSecurityException { - MtlsProvider provider = - new FakeMtlsProvider(false, MtlsEndpointUsagePolicy.AUTO, null, "", false); - assertNull(getMtlsObjectFromTransportChannel(provider)); + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false"); + com.google.api.gax.rpc.mtls.v2.MtlsProvider provider = new FakeMtlsProvider(null, "", false); + assertNull(getMtlsObjectFromTransportChannel(provider, cba)); } @Test void testUseClientCertificate() throws IOException, GeneralSecurityException { - MtlsProvider provider = - new FakeMtlsProvider( - true, - MtlsEndpointUsagePolicy.AUTO, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - false); - assertNotNull(getMtlsObjectFromTransportChannel(provider)); + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); + com.google.api.gax.rpc.mtls.v2.MtlsProvider provider = + new FakeMtlsProvider(FakeMtlsProvider.createTestMtlsKeyStore(), "", false); + assertNotNull(getMtlsObjectFromTransportChannel(provider, cba)); } @Test void testNoClientCertificate() throws IOException, GeneralSecurityException { - MtlsProvider provider = - new FakeMtlsProvider(true, MtlsEndpointUsagePolicy.AUTO, null, "", false); - assertNull(getMtlsObjectFromTransportChannel(provider)); + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); + com.google.api.gax.rpc.mtls.v2.MtlsProvider provider = new FakeMtlsProvider(null, "", false); + assertNull(getMtlsObjectFromTransportChannel(provider, cba)); } @Test void testGetKeyStoreThrows() throws GeneralSecurityException { // Test the case where provider.getKeyStore() throws. - MtlsProvider provider = - new FakeMtlsProvider(true, MtlsEndpointUsagePolicy.AUTO, null, "", true); + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); + com.google.api.gax.rpc.mtls.v2.MtlsProvider provider = new FakeMtlsProvider(null, "", true); IOException actual = - assertThrows(IOException.class, () -> getMtlsObjectFromTransportChannel(provider)); + assertThrows(IOException.class, () -> getMtlsObjectFromTransportChannel(provider, cba)); assertTrue(actual.getMessage().contains("getKeyStore throws exception")); } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/CertificateBasedAccessTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/CertificateBasedAccessTest.java new file mode 100644 index 0000000000..b02e3282d5 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/CertificateBasedAccessTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.rpc.mtls; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class CertificateBasedAccessTest { + + @Test + void testUseMtlsEndpointAlways() { + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "always" : "false"); + assertEquals( + CertificateBasedAccess.MtlsEndpointUsagePolicy.ALWAYS, cba.getMtlsEndpointUsagePolicy()); + } + + @Test + void testUseMtlsEndpointAuto() { + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false"); + assertEquals( + CertificateBasedAccess.MtlsEndpointUsagePolicy.AUTO, cba.getMtlsEndpointUsagePolicy()); + } + + @Test + void testUseMtlsEndpointNever() { + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "never" : "false"); + assertEquals( + CertificateBasedAccess.MtlsEndpointUsagePolicy.NEVER, cba.getMtlsEndpointUsagePolicy()); + } + + @Test + void testUseMtlsClientCertificateTrue() { + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); + assertTrue(cba.useMtlsClientCertificate()); + } + + @Test + void testUseMtlsClientCertificateFalse() { + CertificateBasedAccess cba = + new CertificateBasedAccess( + name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false"); + assertFalse(cba.useMtlsClientCertificate()); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/MtlsProviderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/MtlsProviderTest.java deleted file mode 100644 index 9835b4c120..0000000000 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/MtlsProviderTest.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.gax.rpc.mtls; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.GeneralSecurityException; -import java.util.List; -import org.junit.jupiter.api.Test; - -class MtlsProviderTest { - - private static class TestCertProviderCommandProcess extends Process { - - private boolean runForever; - private int exitValue; - - public TestCertProviderCommandProcess(int exitValue, boolean runForever) { - this.runForever = runForever; - this.exitValue = exitValue; - } - - @Override - public OutputStream getOutputStream() { - return null; - } - - @Override - public InputStream getInputStream() { - return null; - } - - @Override - public InputStream getErrorStream() { - return null; - } - - @Override - public int waitFor() throws InterruptedException { - return 0; - } - - @Override - public int exitValue() { - if (runForever) { - throw new IllegalThreadStateException(); - } - return exitValue; - } - - @Override - public void destroy() {} - } - - static class TestProcessProvider implements MtlsProvider.ProcessProvider { - - private int exitCode; - - public TestProcessProvider(int exitCode) { - this.exitCode = exitCode; - } - - @Override - public Process createProcess(InputStream metadata) throws IOException { - return new TestCertProviderCommandProcess(exitCode, false); - } - } - - @Test - void testUseMtlsEndpointAlways() { - MtlsProvider mtlsProvider = - new MtlsProvider( - name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "always" : "false", - new TestProcessProvider(0), - "/path/to/missing/file"); - assertEquals( - MtlsProvider.MtlsEndpointUsagePolicy.ALWAYS, mtlsProvider.getMtlsEndpointUsagePolicy()); - } - - @Test - void testUseMtlsEndpointAuto() { - MtlsProvider mtlsProvider = - new MtlsProvider( - name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false", - new TestProcessProvider(0), - "/path/to/missing/file"); - assertEquals( - MtlsProvider.MtlsEndpointUsagePolicy.AUTO, mtlsProvider.getMtlsEndpointUsagePolicy()); - } - - @Test - void testUseMtlsEndpointNever() { - MtlsProvider mtlsProvider = - new MtlsProvider( - name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "never" : "false", - new TestProcessProvider(0), - "/path/to/missing/file"); - assertEquals( - MtlsProvider.MtlsEndpointUsagePolicy.NEVER, mtlsProvider.getMtlsEndpointUsagePolicy()); - } - - @Test - void testUseMtlsClientCertificateTrue() { - MtlsProvider mtlsProvider = - new MtlsProvider( - name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true", - new TestProcessProvider(0), - "/path/to/missing/file"); - assertTrue(mtlsProvider.useMtlsClientCertificate()); - } - - @Test - void testUseMtlsClientCertificateFalse() { - MtlsProvider mtlsProvider = - new MtlsProvider( - name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false", - new TestProcessProvider(0), - "/path/to/missing/file"); - assertFalse(mtlsProvider.useMtlsClientCertificate()); - } - - @Test - void testGetKeyStore() throws IOException { - MtlsProvider mtlsProvider = - new MtlsProvider( - name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "always" : "false", - new TestProcessProvider(0), - "/path/to/missing/file"); - assertNull(mtlsProvider.getKeyStore()); - } - - @Test - void testGetKeyStoreNonZeroExitCode() - throws IOException, InterruptedException, GeneralSecurityException { - InputStream metadata = - this.getClass() - .getClassLoader() - .getResourceAsStream("com/google/api/gax/rpc/mtls/mtlsCertAndKey.pem"); - IOException actual = - assertThrows( - IOException.class, - () -> MtlsProvider.getKeyStore(metadata, new TestProcessProvider(1))); - assertTrue( - actual.getMessage().contains("Cert provider command failed with exit code: 1"), - "expected to fail with nonzero exit code"); - } - - @Test - void testExtractCertificateProviderCommand() throws IOException { - InputStream inputStream = - this.getClass() - .getClassLoader() - .getResourceAsStream("com/google/api/gax/rpc/mtls/mtls_context_aware_metadata.json"); - List command = MtlsProvider.extractCertificateProviderCommand(inputStream); - assertEquals(2, command.size()); - assertEquals("some_binary", command.get(0)); - assertEquals("some_argument", command.get(1)); - } - - @Test - void testRunCertificateProviderCommandSuccess() throws IOException, InterruptedException { - Process certCommandProcess = new TestCertProviderCommandProcess(0, false); - int exitValue = MtlsProvider.runCertificateProviderCommand(certCommandProcess, 100); - assertEquals(0, exitValue); - } - - @Test - void testRunCertificateProviderCommandTimeout() throws InterruptedException { - Process certCommandProcess = new TestCertProviderCommandProcess(0, true); - IOException actual = - assertThrows( - IOException.class, - () -> MtlsProvider.runCertificateProviderCommand(certCommandProcess, 100)); - assertTrue( - actual.getMessage().contains("cert provider command timed out"), - "expected to fail with timeout"); - } -} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeMtlsProvider.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeMtlsProvider.java index e5f5c3a91a..cae336ff88 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeMtlsProvider.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeMtlsProvider.java @@ -32,42 +32,24 @@ import com.google.api.client.util.SecurityUtils; import com.google.api.core.InternalApi; -import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.KeyStore; @InternalApi("for testing") -public class FakeMtlsProvider extends MtlsProvider { - private boolean useClientCertificate; - private MtlsEndpointUsagePolicy mtlsEndpointUsagePolicy; +public class FakeMtlsProvider implements MtlsProvider { private KeyStore keyStore; private boolean throwExceptionForGetKeyStore; public FakeMtlsProvider( - boolean useClientCertificate, - MtlsEndpointUsagePolicy mtlsEndpointUsagePolicy, - KeyStore keystore, - String keyStorePassword, - boolean throwExceptionForGetKeyStore) { + KeyStore keystore, String keyStorePassword, boolean throwExceptionForGetKeyStore) { super(); - this.useClientCertificate = useClientCertificate; - this.mtlsEndpointUsagePolicy = mtlsEndpointUsagePolicy; this.keyStore = keystore; this.throwExceptionForGetKeyStore = throwExceptionForGetKeyStore; } - @Override - public boolean useMtlsClientCertificate() { - return useClientCertificate; - } - - @Override - public MtlsEndpointUsagePolicy getMtlsEndpointUsagePolicy() { - return mtlsEndpointUsagePolicy; - } - @Override public KeyStore getKeyStore() throws IOException { if (throwExceptionForGetKeyStore) { From 79c9ab3e13a28158caf91a8e7f1e5f8aee918eef Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Thu, 12 Jun 2025 11:54:38 -0700 Subject: [PATCH 2/8] feat(mtls): Refactor Mtls dependencies to use Java auth lib. --- .../InstantiatingGrpcChannelProvider.java | 2 +- .../InstantiatingGrpcChannelProviderTest.java | 2 +- .../com/google/api/gax/grpc/SettingsTest.java | 2 +- .../InstantiatingHttpJsonChannelProvider.java | 2 +- ...tantiatingHttpJsonChannelProviderTest.java | 2 +- .../google/api/gax/rpc/EndpointContext.java | 7 +- ...CertificateSourceUnavailableException.java | 73 ------- .../rpc/mtls/ContextAwareMetadataJson.java | 10 +- .../rpc/mtls/DefaultMtlsProviderFactory.java | 65 ------ .../google/api/gax/rpc/mtls/MtlsProvider.java | 6 + .../gax/rpc/mtls/SecureConnectProvider.java | 149 ------------- .../WorkloadCertificateConfiguration.java | 100 --------- .../google/api/gax/rpc/mtls/X509Provider.java | 199 ------------------ .../api/gax/rpc/mtls/v2/MtlsProvider.java | 39 ---- .../api/gax/rpc/EndpointContextTest.java | 2 +- .../AbstractMtlsTransportChannelTest.java | 11 +- .../api/gax/rpc/testing/FakeMtlsProvider.java | 7 +- 17 files changed, 36 insertions(+), 642 deletions(-) delete mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateSourceUnavailableException.java delete mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/DefaultMtlsProviderFactory.java delete mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/SecureConnectProvider.java delete mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/WorkloadCertificateConfiguration.java delete mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/X509Provider.java delete mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/v2/MtlsProvider.java diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java index 8fd31fc370..db411e37ad 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java @@ -43,9 +43,9 @@ import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.internal.EnvironmentProvider; import com.google.api.gax.rpc.mtls.CertificateBasedAccess; -import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.ApiKeyCredentials; import com.google.auth.Credentials; +import com.google.auth.mtls.MtlsProvider; import com.google.auth.oauth2.ComputeEngineCredentials; import com.google.auth.oauth2.SecureSessionAgent; import com.google.auth.oauth2.SecureSessionAgentConfig; diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java index f21a7da3b2..fd8c3dfc8b 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java @@ -47,10 +47,10 @@ import com.google.api.gax.rpc.internal.EnvironmentProvider; import com.google.api.gax.rpc.mtls.AbstractMtlsTransportChannelTest; import com.google.api.gax.rpc.mtls.CertificateBasedAccess; -import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.ApiKeyCredentials; import com.google.auth.Credentials; import com.google.auth.http.AuthHttpConstants; +import com.google.auth.mtls.MtlsProvider; import com.google.auth.oauth2.CloudShellCredentials; import com.google.auth.oauth2.ComputeEngineCredentials; import com.google.auth.oauth2.SecureSessionAgent; diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java index 9b8cc110e9..a1ab8a8e8e 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java @@ -51,8 +51,8 @@ import com.google.api.gax.rpc.StubSettings; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.UnaryCallSettings; -import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.Credentials; +import com.google.auth.mtls.MtlsProvider; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java index 54e3183cdd..15b77e97ac 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java @@ -37,8 +37,8 @@ import com.google.api.gax.rpc.HeaderProvider; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.mtls.CertificateBasedAccess; -import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.Credentials; +import com.google.auth.mtls.MtlsProvider; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.security.GeneralSecurityException; diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java index 7f70a8cedd..a772580169 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java @@ -36,7 +36,7 @@ import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.mtls.AbstractMtlsTransportChannelTest; import com.google.api.gax.rpc.mtls.CertificateBasedAccess; -import com.google.api.gax.rpc.mtls.v2.MtlsProvider; +import com.google.auth.mtls.MtlsProvider; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Collections; diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 362c71922b..9c3dfb6eeb 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -32,9 +32,9 @@ import com.google.api.core.InternalApi; import com.google.api.gax.rpc.internal.EnvironmentProvider; import com.google.api.gax.rpc.mtls.CertificateBasedAccess; -import com.google.api.gax.rpc.mtls.DefaultMtlsProviderFactory; -import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.auth.Credentials; +import com.google.auth.mtls.DefaultMtlsProviderFactory; +import com.google.auth.mtls.MtlsProvider; import com.google.auth.oauth2.ComputeEngineCredentials; import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; @@ -215,8 +215,7 @@ public abstract static class Builder { public abstract Builder setSwitchToMtlsEndpointAllowed(boolean switchToMtlsEndpointAllowed); - public abstract Builder setMtlsProvider( - com.google.api.gax.rpc.mtls.v2.MtlsProvider mtlsProvider); + public abstract Builder setMtlsProvider(MtlsProvider mtlsProvider); public abstract Builder setCertificateBasedAccess( CertificateBasedAccess certificateBasedAccess); diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateSourceUnavailableException.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateSourceUnavailableException.java deleted file mode 100644 index 5657dfb1f3..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateSourceUnavailableException.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.gax.rpc.mtls; - -import java.io.IOException; - -/** - * This exception is thrown by certificate providers in the Google auth library when the certificate - * source is unavailable. This means that the transport layer should move on to the next certificate - * source provider type. - */ -public class CertificateSourceUnavailableException extends IOException { - - /** - * Constructor with a message and throwable cause. - * - * @param message The detail message (which is saved for later retrieval by the {@link - * #getMessage()} method) - * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). - * (A null value is permitted, and indicates that the cause is nonexistent or unknown.) - */ - public CertificateSourceUnavailableException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructor with a throwable cause. - * - * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). - * (A null value is permitted, and indicates that the cause is nonexistent or unknown.) - */ - public CertificateSourceUnavailableException(Throwable cause) { - super(cause); - } - - /** - * Constructor with a message. - * - * @param message The detail message (which is saved for later retrieval by the {@link - * #getMessage()} method) - */ - public CertificateSourceUnavailableException(String message) { - super(message); - } -} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/ContextAwareMetadataJson.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/ContextAwareMetadataJson.java index 145799a3c0..9e92ac5ce0 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/ContextAwareMetadataJson.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/ContextAwareMetadataJson.java @@ -35,7 +35,15 @@ import com.google.common.collect.ImmutableList; import java.util.List; -/** Data class representing context_aware_metadata.json file. */ +/** + * Data class representing context_aware_metadata.json file. + * + *

This class is deprecated. It has been replaced by + * com.google.auth.mtls.ContextAwareMetadataJson from the Java auth library. + * + *

Note: This class is for Google cloud internal use only. + */ +@Deprecated public class ContextAwareMetadataJson extends GenericJson { /** Cert provider command */ @Key("cert_provider_command") diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/DefaultMtlsProviderFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/DefaultMtlsProviderFactory.java deleted file mode 100644 index 99b91ceecd..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/DefaultMtlsProviderFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.gax.rpc.mtls; - -import java.io.IOException; - -public class DefaultMtlsProviderFactory { - - /** - * Creates an instance of {@link MtlsProvider}. It first attempts to create an {@link - * com.google.auth.mtls.X509Provider}. If the certificate source is unavailable, it falls back to - * creating a {@link SecureConnectProvider}. If the secure connect provider also fails, it throws - * the original {@link com.google.auth.mtls.CertificateSourceUnavailableException}. - * - * @return an instance of {@link MtlsProvider}. - * @throws com.google.auth.mtls.CertificateSourceUnavailableException if neither provider can be - * created. - * @throws IOException if an I/O error occurs during provider creation. - */ - public static com.google.api.gax.rpc.mtls.v2.MtlsProvider create() throws IOException { - com.google.api.gax.rpc.mtls.v2.MtlsProvider mtlsProvider; - try { - mtlsProvider = new X509Provider(); - mtlsProvider.getKeyStore(); - return mtlsProvider; - } catch (CertificateSourceUnavailableException e) { - try { - mtlsProvider = new SecureConnectProvider(); - mtlsProvider.getKeyStore(); - return mtlsProvider; - } catch (CertificateSourceUnavailableException ex) { - throw new CertificateSourceUnavailableException( - "No MtlsSource is available on this device."); - } - } - } -} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/MtlsProvider.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/MtlsProvider.java index c24fc80d6f..88e423c47c 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/MtlsProvider.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/MtlsProvider.java @@ -48,8 +48,14 @@ /** * Provider class for mutual TLS. It is used to configure the mutual TLS in the transport with the * default client certificate on device. + * + *

This class is deprecated. It has been replaced by com.google.auth.mtls.SecureConnectProvider + * from the Java auth library. + * + *

Note: This class is for Google cloud internal use only. */ @BetaApi +@Deprecated public class MtlsProvider { interface ProcessProvider { public Process createProcess(InputStream metadata) throws IOException; diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/SecureConnectProvider.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/SecureConnectProvider.java deleted file mode 100644 index 1b8771a42c..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/SecureConnectProvider.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.gax.rpc.mtls; - -import com.google.api.client.json.JsonParser; -import com.google.api.client.json.gson.GsonFactory; -import com.google.api.client.util.SecurityUtils; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.util.List; - -/** - * Provider class for mutual TLS. It is used to configure the mutual TLS in the transport with the - * default client certificate on device. - */ -public class SecureConnectProvider implements com.google.api.gax.rpc.mtls.v2.MtlsProvider { - interface ProcessProvider { - public Process createProcess(InputStream metadata) throws IOException; - } - - static class DefaultProcessProvider implements ProcessProvider { - @Override - public Process createProcess(InputStream metadata) throws IOException { - if (metadata == null) { - return null; - } - List command = extractCertificateProviderCommand(metadata); - return new ProcessBuilder(command).start(); - } - } - - private static final String DEFAULT_CONTEXT_AWARE_METADATA_PATH = - System.getProperty("user.home") + "/.secureConnect/context_aware_metadata.json"; - - private String metadataPath; - private ProcessProvider processProvider; - - @VisibleForTesting - SecureConnectProvider(ProcessProvider processProvider, String metadataPath) { - this.processProvider = processProvider; - this.metadataPath = metadataPath; - } - - public SecureConnectProvider() { - this(new DefaultProcessProvider(), DEFAULT_CONTEXT_AWARE_METADATA_PATH); - } - - /** The mutual TLS key store created with the default client certificate on device. */ - @Override - public KeyStore getKeyStore() throws IOException { - try (InputStream stream = new FileInputStream(metadataPath)) { - return getKeyStore(stream, processProvider); - } catch (InterruptedException e) { - throw new IOException("Interrupted executing certificate provider command", e); - } catch (GeneralSecurityException e) { - throw new CertificateSourceUnavailableException( - "SecureConnect encountered GeneralSecurityException:", e); - } catch (FileNotFoundException exception) { - // If the metadata file doesn't exist, then there is no key store, so we will throw sentinel - // error - throw new CertificateSourceUnavailableException("SecureConnect metadata does not exist."); - } - } - - @VisibleForTesting - static KeyStore getKeyStore(InputStream metadata, ProcessProvider processProvider) - throws IOException, InterruptedException, GeneralSecurityException { - Process process = processProvider.createProcess(metadata); - - // Run the command and timeout after 1000 milliseconds. - int exitCode = runCertificateProviderCommand(process, 1000); - if (exitCode != 0) { - throw new IOException("Cert provider command failed with exit code: " + exitCode); - } - - // Create mTLS key store with the input certificates from shell command. - return SecurityUtils.createMtlsKeyStore(process.getInputStream()); - } - - @VisibleForTesting - static ImmutableList extractCertificateProviderCommand(InputStream contextAwareMetadata) - throws IOException { - JsonParser parser = new GsonFactory().createJsonParser(contextAwareMetadata); - ContextAwareMetadataJson json = parser.parse(ContextAwareMetadataJson.class); - return json.getCommands(); - } - - @VisibleForTesting - static int runCertificateProviderCommand(Process commandProcess, long timeoutMilliseconds) - throws IOException, InterruptedException { - long startTime = System.currentTimeMillis(); - long remainTime = timeoutMilliseconds; - - // In the while loop, keep checking if the process is terminated every 100 milliseconds - // until timeout is reached or process is terminated. In getKeyStore we set timeout to - // 1000 milliseconds, so 100 millisecond is a good number for the sleep. - while (remainTime > 0) { - Thread.sleep(Math.min(remainTime + 1, 100)); - remainTime -= System.currentTimeMillis() - startTime; - - try { - return commandProcess.exitValue(); - } catch (IllegalThreadStateException ignored) { - // exitValue throws IllegalThreadStateException if process has not yet terminated. - // Once the process is terminated, exitValue no longer throws exception. Therefore - // in the while loop, we use exitValue to check if process is terminated. See - // https://docs.oracle.com/javase/7/docs/api/java/lang/Process.html#exitValue() - // for more details. - } - } - - commandProcess.destroy(); - throw new IOException("cert provider command timed out"); - } -} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/WorkloadCertificateConfiguration.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/WorkloadCertificateConfiguration.java deleted file mode 100644 index 68c27553c2..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/WorkloadCertificateConfiguration.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.gax.rpc.mtls; - -import com.google.api.client.json.GenericJson; -import com.google.api.client.json.JsonFactory; -import com.google.api.client.json.JsonObjectParser; -import com.google.api.client.json.gson.GsonFactory; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -class WorkloadCertificateConfiguration { - - private String certPath; - private String privateKeyPath; - - private static JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); - private static JsonObjectParser parser = new JsonObjectParser(jsonFactory); - - WorkloadCertificateConfiguration(String certPath, String privateKeyPath) { - this.certPath = certPath; - this.privateKeyPath = privateKeyPath; - } - - String getCertPath() { - return certPath; - } - - String getPrivateKeyPath() { - return privateKeyPath; - } - - static WorkloadCertificateConfiguration fromCertificateConfigurationStream( - InputStream certConfigStream) throws IOException { - Preconditions.checkNotNull(certConfigStream); - - GenericJson fileContents = - parser.parseAndClose(certConfigStream, StandardCharsets.UTF_8, GenericJson.class); - - Map certConfigs = (Map) fileContents.get("cert_configs"); - if (certConfigs == null) { - throw new IllegalArgumentException( - "The cert_configs object must be provided in the certificate configuration file."); - } - - Map workloadConfig = (Map) certConfigs.get("workload"); - if (workloadConfig == null) { - // Throw a CertificateSourceUnavailableException because there is no workload cert source. - // This tells the transport layer that it should check for another certificate source type. - throw new CertificateSourceUnavailableException( - "A workload certificate configuration must be provided in the cert_configs object."); - } - - String certPath = (String) workloadConfig.get("cert_path"); - if (Strings.isNullOrEmpty(certPath)) { - throw new IllegalArgumentException( - "The cert_path field must be provided in the workload certificate configuration."); - } - - String privateKeyPath = (String) workloadConfig.get("key_path"); - if (Strings.isNullOrEmpty(privateKeyPath)) { - throw new IllegalArgumentException( - "The key_path field must be provided in the workload certificate configuration."); - } - - return new WorkloadCertificateConfiguration(certPath, privateKeyPath); - } -} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/X509Provider.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/X509Provider.java deleted file mode 100644 index 3041c2a2fe..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/X509Provider.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.gax.rpc.mtls; - -import com.google.api.client.util.SecurityUtils; -import com.google.common.base.Strings; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.SequenceInputStream; -import java.security.KeyStore; -import java.util.Locale; - -/** - * This class provides certificate key stores to the Google Auth library transport layer via - * certificate configuration files. This is only meant to be used internally to Google Cloud - * libraries, and the public facing methods may be changed without notice, and have no guarantee of - * backwards compatability. - */ -public class X509Provider implements com.google.api.gax.rpc.mtls.v2.MtlsProvider { - static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG"; - static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json"; - static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud"; - - private String certConfigPathOverride; - - /** - * Creates an X509 provider with an override path for the certificate configuration, bypassing the - * normal checks for the well known certificate configuration file path and environment variable. - * This is meant for internal Google Cloud usage and behavior may be changed without warning. - * - * @param certConfigPathOverride the path to read the certificate configuration from. - */ - public X509Provider(String certConfigPathOverride) { - this.certConfigPathOverride = certConfigPathOverride; - } - - /** - * Creates a new X.509 provider that will check the environment variable path and the well known - * Gcloud certificate configuration location. This is meant for internal Google Cloud usage and - * behavior may be changed without warning. - */ - public X509Provider() { - this(null); - } - - /** - * Finds the certificate configuration file, then builds a Keystore using the X.509 certificate - * and private key pointed to by the configuration. This will check the following locations in - * order. - * - *

    - *
  • The certificate config override path, if set. - *
  • The path pointed to by the "GOOGLE_API_CERTIFICATE_CONFIG" environment variable - *
  • The well known gcloud location for the certificate configuration file. - *
- * - * @return a KeyStore containing the X.509 certificate specified by the certificate configuration. - * @throws IOException if there is an error retrieving the certificate configuration. - */ - @Override - public KeyStore getKeyStore() throws IOException { - - WorkloadCertificateConfiguration workloadCertConfig = getWorkloadCertificateConfiguration(); - - InputStream certStream = null; - InputStream privateKeyStream = null; - SequenceInputStream certAndPrivateKeyStream = null; - try { - // Read the certificate and private key file paths into separate streams. - File certFile = new File(workloadCertConfig.getCertPath()); - File privateKeyFile = new File(workloadCertConfig.getPrivateKeyPath()); - certStream = createInputStream(certFile); - privateKeyStream = createInputStream(privateKeyFile); - - // Merge the two streams into a single stream. - certAndPrivateKeyStream = new SequenceInputStream(certStream, privateKeyStream); - - // Build a key store using the combined stream. - return SecurityUtils.createMtlsKeyStore(certAndPrivateKeyStream); - } catch (CertificateSourceUnavailableException e) { - // Throw the CertificateSourceUnavailableException without wrapping. - throw e; - } catch (Exception e) { - // Wrap all other exception types to an IOException. - throw new IOException(e); - } finally { - if (certStream != null) { - certStream.close(); - } - if (privateKeyStream != null) { - privateKeyStream.close(); - } - if (certAndPrivateKeyStream != null) { - certAndPrivateKeyStream.close(); - } - } - } - - private WorkloadCertificateConfiguration getWorkloadCertificateConfiguration() - throws IOException { - File certConfig; - if (this.certConfigPathOverride != null) { - certConfig = new File(certConfigPathOverride); - } else { - String envCredentialsPath = getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE); - if (!Strings.isNullOrEmpty(envCredentialsPath)) { - certConfig = new File(envCredentialsPath); - } else { - certConfig = getWellKnownCertificateConfigFile(); - } - } - InputStream certConfigStream = null; - try { - if (!isFile(certConfig)) { - // Path will be put in the message from the catch block below - throw new CertificateSourceUnavailableException("File does not exist."); - } - certConfigStream = createInputStream(certConfig); - return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream); - } finally { - if (certConfigStream != null) { - certConfigStream.close(); - } - } - } - - /* - * Start of methods to allow overriding in the test code to isolate from the environment. - */ - boolean isFile(File file) { - return file.isFile(); - } - - InputStream createInputStream(File file) throws FileNotFoundException { - return new FileInputStream(file); - } - - String getEnv(String name) { - return System.getenv(name); - } - - String getOsName() { - return getProperty("os.name", "").toLowerCase(Locale.US); - } - - String getProperty(String property, String def) { - return System.getProperty(property, def); - } - - /* - * End of methods to allow overriding in the test code to isolate from the environment. - */ - - private File getWellKnownCertificateConfigFile() { - File cloudConfigPath; - String envPath = getEnv("CLOUDSDK_CONFIG"); - if (envPath != null) { - cloudConfigPath = new File(envPath); - } else if (getOsName().indexOf("windows") >= 0) { - File appDataPath = new File(getEnv("APPDATA")); - cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY); - } else { - File configPath = new File(getProperty("user.home", ""), ".config"); - cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY); - } - return new File(cloudConfigPath, WELL_KNOWN_CERTIFICATE_CONFIG_FILE); - } -} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/v2/MtlsProvider.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/v2/MtlsProvider.java deleted file mode 100644 index 43d80b82e3..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/v2/MtlsProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.gax.rpc.mtls.v2; - -import java.io.IOException; -import java.security.KeyStore; - -public interface MtlsProvider { - /** Returns the mutual TLS key store. */ - KeyStore getKeyStore() throws IOException; -} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index 01ea34ce99..117bd404f7 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -35,9 +35,9 @@ import com.google.api.gax.core.NoCredentialsProvider; import com.google.api.gax.rpc.internal.EnvironmentProvider; import com.google.api.gax.rpc.mtls.CertificateBasedAccess; -import com.google.api.gax.rpc.mtls.v2.MtlsProvider; import com.google.api.gax.rpc.testing.FakeMtlsProvider; import com.google.auth.Credentials; +import com.google.auth.mtls.MtlsProvider; import com.google.auth.oauth2.ComputeEngineCredentials; import com.google.common.truth.Truth; import io.grpc.Status; diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java index 87a806714c..5fea7fc333 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java @@ -36,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.api.gax.rpc.testing.FakeMtlsProvider; +import com.google.auth.mtls.MtlsProvider; import java.io.IOException; import java.security.GeneralSecurityException; import org.junit.jupiter.api.Test; @@ -48,7 +49,7 @@ public abstract class AbstractMtlsTransportChannelTest { * if and only if the related mTLS object is not null. */ protected abstract Object getMtlsObjectFromTransportChannel( - com.google.api.gax.rpc.mtls.v2.MtlsProvider provider, CertificateBasedAccess cba) + MtlsProvider provider, CertificateBasedAccess cba) throws IOException, GeneralSecurityException; @Test @@ -56,7 +57,7 @@ void testNotUseClientCertificate() throws IOException, GeneralSecurityException CertificateBasedAccess cba = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false"); - com.google.api.gax.rpc.mtls.v2.MtlsProvider provider = new FakeMtlsProvider(null, "", false); + MtlsProvider provider = new FakeMtlsProvider(null, "", false); assertNull(getMtlsObjectFromTransportChannel(provider, cba)); } @@ -65,7 +66,7 @@ void testUseClientCertificate() throws IOException, GeneralSecurityException { CertificateBasedAccess cba = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); - com.google.api.gax.rpc.mtls.v2.MtlsProvider provider = + MtlsProvider provider = new FakeMtlsProvider(FakeMtlsProvider.createTestMtlsKeyStore(), "", false); assertNotNull(getMtlsObjectFromTransportChannel(provider, cba)); } @@ -75,7 +76,7 @@ void testNoClientCertificate() throws IOException, GeneralSecurityException { CertificateBasedAccess cba = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); - com.google.api.gax.rpc.mtls.v2.MtlsProvider provider = new FakeMtlsProvider(null, "", false); + MtlsProvider provider = new FakeMtlsProvider(null, "", false); assertNull(getMtlsObjectFromTransportChannel(provider, cba)); } @@ -85,7 +86,7 @@ void testGetKeyStoreThrows() throws GeneralSecurityException { CertificateBasedAccess cba = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); - com.google.api.gax.rpc.mtls.v2.MtlsProvider provider = new FakeMtlsProvider(null, "", true); + MtlsProvider provider = new FakeMtlsProvider(null, "", true); IOException actual = assertThrows(IOException.class, () -> getMtlsObjectFromTransportChannel(provider, cba)); assertTrue(actual.getMessage().contains("getKeyStore throws exception")); diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeMtlsProvider.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeMtlsProvider.java index cae336ff88..abe374dac6 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeMtlsProvider.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeMtlsProvider.java @@ -32,7 +32,7 @@ import com.google.api.client.util.SecurityUtils; import com.google.api.core.InternalApi; -import com.google.api.gax.rpc.mtls.v2.MtlsProvider; +import com.google.auth.mtls.MtlsProvider; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; @@ -58,6 +58,11 @@ public KeyStore getKeyStore() throws IOException { return keyStore; } + @Override + public boolean isAvailable() throws IOException { + return true; + } + public static KeyStore createTestMtlsKeyStore() throws IOException { try { InputStream certAndKey = From 882ecfcbc99d0280a58d58675fc3e3c40acff981 Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Tue, 24 Jun 2025 14:27:20 -0700 Subject: [PATCH 3/8] Add CLIRR ignore for EndpointContext API changes --- gax-java/gax/clirr-ignored-differences.xml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/gax-java/gax/clirr-ignored-differences.xml b/gax-java/gax/clirr-ignored-differences.xml index 4e0a8816c1..3b0228f94e 100644 --- a/gax-java/gax/clirr-ignored-differences.xml +++ b/gax-java/gax/clirr-ignored-differences.xml @@ -128,4 +128,26 @@ com/google/api/gax/rpc/TransportChannelProvider * needsMtlsEndpoint() + + 7013 + com/google/api/gax/rpc/EndpointContext + com.google.api.gax.rpc.mtls.CertificateBasedAccess certificateBasedAccess() + + + 7006 + com/google/api/gax/rpc/EndpointContext + com.google.api.gax.rpc.mtls.MtlsProvider mtlsProvider()Add commentMore actions + com.google.auth.mtls.MtlsProvider + + + 7013 + com/google/api/gax/rpc/EndpointContext$Builder + com.google.api.gax.rpc.EndpointContext$Builder setCertificateBasedAccess(com.google.api.gax.rpc.mtls.CertificateBasedAccess) + + + 7005 + com/google/api/gax/rpc/EndpointContext$Builder + com.google.api.gax.rpc.EndpointContext$Builder setMtlsProvider(com.google.api.gax.rpc.mtls.MtlsProvider) + com.google.api.gax.rpc.EndpointContext$Builder setMtlsProvider(com.google.auth.mtls.MtlsProvider) + From a557cb6fff0e005157d3ded4cc118a54b249dd71 Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Wed, 25 Jun 2025 10:10:11 -0700 Subject: [PATCH 4/8] feat(mtls): Initialize defaults for CertificateBasedAcess and mtlsProvider in channel providers. --- .../InstantiatingGrpcChannelProvider.java | 36 +++++++++++++++---- .../InstantiatingHttpJsonChannelProvider.java | 36 ++++++++++++++++--- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java index db411e37ad..3a4f2d91c1 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java @@ -45,6 +45,8 @@ import com.google.api.gax.rpc.mtls.CertificateBasedAccess; import com.google.auth.ApiKeyCredentials; import com.google.auth.Credentials; +import com.google.auth.mtls.CertificateSourceUnavailableException; +import com.google.auth.mtls.DefaultMtlsProviderFactory; import com.google.auth.mtls.MtlsProvider; import com.google.auth.oauth2.ComputeEngineCredentials; import com.google.auth.oauth2.SecureSessionAgent; @@ -487,13 +489,15 @@ boolean canUseDirectPathWithUniverseDomain() { @VisibleForTesting ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSecurityException { - if (certificateBasedAccess.useMtlsClientCertificate()) { - KeyStore mtlsKeyStore = mtlsProvider.getKeyStore(); - if (mtlsKeyStore != null) { - KeyManagerFactory factory = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - factory.init(mtlsKeyStore, new char[] {}); - return TlsChannelCredentials.newBuilder().keyManager(factory.getKeyManagers()).build(); + if (certificateBasedAccess != null && certificateBasedAccess.useMtlsClientCertificate()) { + if (mtlsProvider != null) { + KeyStore mtlsKeyStore = mtlsProvider.getKeyStore(); + if (mtlsKeyStore != null) { + KeyManagerFactory factory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + factory.init(mtlsKeyStore, new char[] {}); + return TlsChannelCredentials.newBuilder().keyManager(factory.getKeyManagers()).build(); + } } } return null; @@ -1280,6 +1284,24 @@ CallCredentials createHardBoundTokensCallCredentials( } public InstantiatingGrpcChannelProvider build() { + 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; + } catch (IOException e) { + LOG.log( + Level.WARNING, + "DefaultMtlsProviderFactory encountered unexpected IOException: " + e.getMessage()); + } + } + } + if (isMtlsS2AHardBoundTokensEnabled()) { // Set a {@code ComputeEngineCredentials} instance to be per-RPC call credentials, // which will be used to fetch MTLS_S2A hard bound tokens from the metdata server. diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java index 15b77e97ac..dc5638c11b 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java @@ -38,6 +38,8 @@ import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.mtls.CertificateBasedAccess; import com.google.auth.Credentials; +import com.google.auth.mtls.CertificateSourceUnavailableException; +import com.google.auth.mtls.DefaultMtlsProviderFactory; import com.google.auth.mtls.MtlsProvider; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; @@ -46,6 +48,8 @@ import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Level; +import java.util.logging.Logger; /** * InstantiatingHttpJsonChannelProvider is a TransportChannelProvider which constructs a {@link @@ -62,6 +66,9 @@ @InternalExtensionOnly public final class InstantiatingHttpJsonChannelProvider implements TransportChannelProvider { + @VisibleForTesting + static final Logger LOG = Logger.getLogger(InstantiatingHttpJsonChannelProvider.class.getName()); + private final Executor executor; private final HeaderProvider headerProvider; private final HttpJsonInterceptorProvider interceptorProvider; @@ -177,10 +184,12 @@ public TransportChannelProvider withCredentials(Credentials credentials) { } HttpTransport createHttpTransport() throws IOException, GeneralSecurityException { - if (certificateBasedAccess.useMtlsClientCertificate()) { - KeyStore mtlsKeyStore = mtlsProvider.getKeyStore(); - if (mtlsKeyStore != null) { - return new NetHttpTransport.Builder().trustCertificates(null, mtlsKeyStore, "").build(); + if (certificateBasedAccess != null && certificateBasedAccess.useMtlsClientCertificate()) { + if (mtlsProvider != null) { + KeyStore mtlsKeyStore = mtlsProvider.getKeyStore(); + if (mtlsKeyStore != null) { + return new NetHttpTransport.Builder().trustCertificates(null, mtlsKeyStore, "").build(); + } } } return null; @@ -241,7 +250,7 @@ public static final class Builder { private HttpJsonInterceptorProvider interceptorProvider; private String endpoint; private HttpTransport httpTransport; - private MtlsProvider mtlsProvider = null; + private MtlsProvider mtlsProvider; private CertificateBasedAccess certificateBasedAccess; private Builder() {} @@ -330,6 +339,23 @@ Builder setCertificateBasedAccess(CertificateBasedAccess certificateBasedAccess) } public InstantiatingHttpJsonChannelProvider build() { + 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; + } catch (IOException e) { + LOG.log( + Level.WARNING, + "DefaultMtlsProviderFactory encountered unexpected IOException: " + e.getMessage()); + } + } + } return new InstantiatingHttpJsonChannelProvider( executor, headerProvider, From af66fbd408ce33faa10e4fef9b8f499e110c4014 Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Wed, 25 Jun 2025 10:55:05 -0700 Subject: [PATCH 5/8] feat(mtls): Enhance cba checks for EndpointContext. --- .../google/api/gax/rpc/EndpointContext.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 9c3dfb6eeb..587e48bbfe 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -33,6 +33,7 @@ import com.google.api.gax.rpc.internal.EnvironmentProvider; import com.google.api.gax.rpc.mtls.CertificateBasedAccess; import com.google.auth.Credentials; +import com.google.auth.mtls.CertificateSourceUnavailableException; import com.google.auth.mtls.DefaultMtlsProviderFactory; import com.google.auth.mtls.MtlsProvider; import com.google.auth.oauth2.ComputeEngineCredentials; @@ -40,6 +41,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nullable; /** @@ -52,6 +55,8 @@ @AutoValue public abstract class EndpointContext { + @VisibleForTesting static final Logger LOG = Logger.getLogger(EndpointContext.class.getName()); + private static final EndpointContext INSTANCE; // static block initialization for exception handling @@ -283,18 +288,22 @@ private String determineUniverseDomain() { /** Determines the fully resolved endpoint and universe domain values */ private String determineEndpoint() throws IOException { + CertificateBasedAccess cba = + certificateBasedAccess() == null + ? CertificateBasedAccess.createWithSystemEnv() + : certificateBasedAccess(); MtlsProvider mtlsProvider = mtlsProvider(); if (mtlsProvider == null) { try { mtlsProvider = DefaultMtlsProviderFactory.create(); + } catch (CertificateSourceUnavailableException e) { + // This is okay. Leave mtlsProvider as null; } catch (IOException e) { - // throw e; + LOG.log( + Level.WARNING, + "DefaultMtlsProviderFactory encountered unexpected IOException: " + e.getMessage()); } } - CertificateBasedAccess cba = - certificateBasedAccess() == null - ? CertificateBasedAccess.createWithSystemEnv() - : certificateBasedAccess(); // TransportChannelProvider's endpoint will override the ClientSettings' endpoint String customEndpoint = transportChannelProviderEndpoint() == null @@ -376,7 +385,7 @@ String mtlsEndpointResolver( MtlsProvider mtlsProvider, CertificateBasedAccess cba) throws IOException { - if (switchToMtlsEndpointAllowed && mtlsProvider != null) { + if (switchToMtlsEndpointAllowed && cba != null && mtlsProvider != null) { switch (cba.getMtlsEndpointUsagePolicy()) { case ALWAYS: return mtlsEndpoint; From dede538216bcc9e4c3b23c03e53fe6bef6a58934 Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Wed, 2 Jul 2025 11:17:40 -0700 Subject: [PATCH 6/8] feat(mtls): Address code-review comments. --- .../InstantiatingGrpcChannelProvider.java | 22 +++--- .../InstantiatingGrpcChannelProviderTest.java | 74 ++++++++++--------- .../InstantiatingHttpJsonChannelProvider.java | 16 ++-- ...tantiatingHttpJsonChannelProviderTest.java | 16 ++-- .../google/api/gax/rpc/EndpointContext.java | 17 +++-- .../gax/rpc/mtls/CertificateBasedAccess.java | 8 +- .../api/gax/rpc/EndpointContextTest.java | 32 ++++---- .../AbstractMtlsTransportChannelTest.java | 20 ++--- 8 files changed, 114 insertions(+), 91 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java index 3a4f2d91c1..beda42b280 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java @@ -489,15 +489,16 @@ boolean canUseDirectPathWithUniverseDomain() { @VisibleForTesting ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSecurityException { - if (certificateBasedAccess != null && certificateBasedAccess.useMtlsClientCertificate()) { - if (mtlsProvider != null) { - KeyStore mtlsKeyStore = mtlsProvider.getKeyStore(); - if (mtlsKeyStore != null) { - KeyManagerFactory factory = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - factory.init(mtlsKeyStore, new char[] {}); - return TlsChannelCredentials.newBuilder().keyManager(factory.getKeyManagers()).build(); - } + if (certificateBasedAccess == null || mtlsProvider == null) { + return null; + } + if (certificateBasedAccess.useMtlsClientCertificate()) { + KeyStore mtlsKeyStore = mtlsProvider.getKeyStore(); + if (mtlsKeyStore != null) { + KeyManagerFactory factory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + factory.init(mtlsKeyStore, new char[] {}); + return TlsChannelCredentials.newBuilder().keyManager(factory.getKeyManagers()).build(); } } return null; @@ -1293,7 +1294,8 @@ public InstantiatingGrpcChannelProvider build() { try { mtlsProvider = DefaultMtlsProviderFactory.create(); } catch (CertificateSourceUnavailableException e) { - // This is okay. Leave mtlsProvider as null; + // 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, diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java index fd8c3dfc8b..5c4dfca8c4 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java @@ -93,7 +93,7 @@ class InstantiatingGrpcChannelProviderTest extends AbstractMtlsTransportChannelT private static final String API_KEY_AUTH_HEADER_KEY = "x-goog-api-key"; private static String originalOSName; private ComputeEngineCredentials computeEngineCredentials; - private CertificateBasedAccess cba; + private CertificateBasedAccess certificateBasedAccess; @BeforeAll public static void setupClass() { @@ -103,7 +103,7 @@ public static void setupClass() { @BeforeEach public void setup() throws IOException { computeEngineCredentials = Mockito.mock(ComputeEngineCredentials.class); - cba = + certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "never" : "false"); } @@ -213,7 +213,7 @@ void testWithPoolSize() throws IOException { TransportChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -283,7 +283,7 @@ private void testWithInterceptors(int numChannels) throws Exception { InstantiatingGrpcChannelProvider channelProvider = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setEndpoint("localhost:8080") .setPoolSize(numChannels) .setHeaderProvider(Mockito.mock(HeaderProvider.class)) @@ -317,7 +317,7 @@ void testChannelConfigurator() throws IOException { // Invoke the provider InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setEndpoint("localhost:8080") .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) @@ -339,7 +339,7 @@ void testWithGCECredentials() throws IOException { TransportChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .build() .withExecutor((Executor) executor) @@ -423,7 +423,7 @@ void testWithNonGCECredentials() throws IOException { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPath(true) .setChannelConfigurator(channelConfigurator) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -452,7 +452,7 @@ void testWithDirectPathDisabled() throws IOException { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPath(false) .setChannelConfigurator(channelConfigurator) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -480,7 +480,7 @@ void testWithNoDirectPathFlagSet() throws IOException { TransportChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() .setChannelConfigurator(channelConfigurator) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -500,7 +500,7 @@ void testWithIPv6Address() throws IOException { TransportChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build() .withExecutor((Executor) executor) .withHeaders(Collections.emptyMap()) @@ -526,7 +526,7 @@ void testWithPrimeChannel() throws IOException { .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .setChannelPrimer(mockChannelPrimer) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build(); provider.getTransportChannel().shutdownNow(); @@ -540,7 +540,9 @@ void testWithPrimeChannel() throws IOException { @Test void testWithDefaultDirectPathServiceConfig() { InstantiatingGrpcChannelProvider provider = - InstantiatingGrpcChannelProvider.newBuilder().setCertificateBasedAccess(cba).build(); + InstantiatingGrpcChannelProvider.newBuilder() + .setCertificateBasedAccess(certificateBasedAccess) + .build(); ImmutableMap defaultServiceConfig = provider.directPathServiceConfig; @@ -605,7 +607,7 @@ void testWithCustomDirectPathServiceConfig() { InstantiatingGrpcChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() .setDirectPathServiceConfig(passedServiceConfig) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build(); ImmutableMap defaultServiceConfig = provider.directPathServiceConfig; @@ -614,13 +616,13 @@ void testWithCustomDirectPathServiceConfig() { @Override protected Object getMtlsObjectFromTransportChannel( - MtlsProvider provider, CertificateBasedAccess cba) + MtlsProvider provider, CertificateBasedAccess certificateBasedAccess) throws IOException, GeneralSecurityException { InstantiatingGrpcChannelProvider channelProvider = InstantiatingGrpcChannelProvider.newBuilder() .setEndpoint("localhost:8080") .setMtlsProvider(provider) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .build(); @@ -651,7 +653,7 @@ private void createAndCloseTransportChannel(InstantiatingGrpcChannelProvider pro InstantiatingGrpcChannelProvider provider = createChannelProviderBuilderForDirectPathLogTests() .setAttemptDirectPathXds() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build(); createAndCloseTransportChannel(provider); assertThat(logHandler.getAllMessages()) @@ -667,7 +669,9 @@ void testLogDirectPathMisconfig_AttemptDirectPathNotSetAndAttemptDirectPathXdsSe InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler); InstantiatingGrpcChannelProvider provider = - createChannelProviderBuilderForDirectPathLogTests().setCertificateBasedAccess(cba).build(); + createChannelProviderBuilderForDirectPathLogTests() + .setCertificateBasedAccess(certificateBasedAccess) + .build(); createAndCloseTransportChannel(provider); assertThat(logHandler.getAllMessages()) .contains( @@ -683,7 +687,7 @@ void testLogDirectPathMisconfig_shouldNotLogInTheBuilder() { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPathXds() .setAttemptDirectPath(true) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build(); assertThat(logHandler.getAllMessages()).isEmpty(); @@ -701,7 +705,7 @@ void testLogDirectPathMisconfigWrongCredential() throws Exception { .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .setEndpoint(DEFAULT_ENDPOINT) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build(); TransportChannel transportChannel = provider.getTransportChannel(); @@ -728,7 +732,7 @@ void testLogDirectPathMisconfigNotOnGCE() throws Exception { .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .setEndpoint(DEFAULT_ENDPOINT) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build(); TransportChannel transportChannel = provider.getTransportChannel(); @@ -754,7 +758,7 @@ public void canUseDirectPath_happyPath() throws IOException { .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -782,7 +786,7 @@ public void canUseDirectPath_boundTokenNotEnabledWithNonComputeCredentials() { .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .setAllowHardBoundTokenTypes(Collections.singletonList(HardBoundTokenTypes.ALTS)) .setCredentials(credentials) @@ -807,7 +811,7 @@ public void canUseDirectPath_happyPathWithBoundToken() throws IOException { .thenReturn(ComputeEngineCredentials.newBuilder()); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setAllowHardBoundTokenTypes(Collections.singletonList(HardBoundTokenTypes.ALTS)) @@ -835,7 +839,7 @@ public void canUseDirectPath_directPathEnvVarDisabled() throws IOException { .thenReturn("true"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -856,7 +860,7 @@ public void canUseDirectPath_directPathEnvVarNotSet_attemptDirectPathIsTrue() { System.setProperty("os.name", "Linux"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT); @@ -870,7 +874,7 @@ public void canUseDirectPath_directPathEnvVarNotSet_attemptDirectPathIsFalse() { System.setProperty("os.name", "Linux"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(false) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT); @@ -890,7 +894,7 @@ public void canUseDirectPath_nonComputeCredentials() { .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .setCredentials(credentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -910,7 +914,7 @@ public void canUseDirectPath_isNotOnComputeEngine_invalidOsNameSystemProperty() .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -930,7 +934,7 @@ public void canUseDirectPath_isNotOnComputeEngine_invalidSystemProductName() { .thenReturn("false"); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) @@ -953,7 +957,7 @@ public void canUseDirectPath_isNotOnComputeEngine_unableToGetSystemProductName() .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(DEFAULT_ENDPOINT) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setEnvProvider(envProvider); InstantiatingGrpcChannelProvider provider = new InstantiatingGrpcChannelProvider(builder, ""); Truth.assertThat(provider.canUseDirectPath()).isFalse(); @@ -970,7 +974,7 @@ public void canUseDirectPath_nonGDUUniverseDomain() { String nonGDUEndpoint = "test.random.com:443"; InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setAttemptDirectPath(true) .setCredentials(computeEngineCredentials) .setEndpoint(nonGDUEndpoint) @@ -1000,7 +1004,7 @@ void providersInitializedWithConflictingApiKeyCredentialHeaders_removesDuplicate ApiKeyCredentials apiKeyCredentials = ApiKeyCredentials.create(correctApiKey); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setCredentials(apiKeyCredentials) .setHeaderProvider(getHeaderProviderWithApiKeyHeader()) .setEndpoint("test.random.com:443"); @@ -1018,7 +1022,7 @@ void providersInitializedWithConflictingNonApiKeyCredentialHeaders_doesNotRemove header.put(AuthHttpConstants.AUTHORIZATION, authProvidedHeader); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setCredentials(computeEngineCredentials) .setHeaderProvider(FixedHeaderProvider.create(header)) .setEndpoint("test.random.com:443"); @@ -1047,7 +1051,7 @@ void buildProvider_handlesNullCredentialsMetadataRequest() throws IOException { Mockito.when(credentials.getRequestMetadata()).thenReturn(null); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setHeaderProvider(getHeaderProviderWithApiKeyHeader()) .setEndpoint("test.random.com:443"); @@ -1065,7 +1069,7 @@ void buildProvider_handlesErrorRetrievingCredentialsMetadataRequest() throws IOE .thenThrow(new IOException("Error getting request metadata")); InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder() - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setHeaderProvider(getHeaderProviderWithApiKeyHeader()) .setEndpoint("test.random.com:443"); InstantiatingGrpcChannelProvider provider = builder.build(); diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java index dc5638c11b..fdb535e318 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java @@ -184,12 +184,13 @@ public TransportChannelProvider withCredentials(Credentials credentials) { } HttpTransport createHttpTransport() throws IOException, GeneralSecurityException { - if (certificateBasedAccess != null && certificateBasedAccess.useMtlsClientCertificate()) { - if (mtlsProvider != null) { - KeyStore mtlsKeyStore = mtlsProvider.getKeyStore(); - if (mtlsKeyStore != null) { - return new NetHttpTransport.Builder().trustCertificates(null, mtlsKeyStore, "").build(); - } + 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; @@ -348,7 +349,8 @@ public InstantiatingHttpJsonChannelProvider build() { try { mtlsProvider = DefaultMtlsProviderFactory.create(); } catch (CertificateSourceUnavailableException e) { - // This is okay. Leave mtlsProvider as null; + // 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, diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java index a772580169..17ad9f2cbf 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java @@ -52,11 +52,11 @@ class InstantiatingHttpJsonChannelProviderTest extends AbstractMtlsTransportChan private static final String DEFAULT_ENDPOINT = "localhost:8080"; private static final Map DEFAULT_HEADER_MAP = Collections.emptyMap(); - private CertificateBasedAccess cba; + private CertificateBasedAccess certificateBasedAccess; @BeforeEach public void setup() throws IOException { - cba = + certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "never" : "false"); } @@ -67,7 +67,9 @@ void basicTest() throws IOException { executor.shutdown(); TransportChannelProvider provider = - InstantiatingHttpJsonChannelProvider.newBuilder().setCertificateBasedAccess(cba).build(); + InstantiatingHttpJsonChannelProvider.newBuilder() + .setCertificateBasedAccess(certificateBasedAccess) + .build(); assertThat(provider.needsEndpoint()).isTrue(); provider = provider.withEndpoint(DEFAULT_ENDPOINT); @@ -121,7 +123,7 @@ void managedChannelUsesDefaultChannelExecutor() throws IOException { InstantiatingHttpJsonChannelProvider instantiatingHttpJsonChannelProvider = InstantiatingHttpJsonChannelProvider.newBuilder() .setEndpoint(DEFAULT_ENDPOINT) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build(); instantiatingHttpJsonChannelProvider = (InstantiatingHttpJsonChannelProvider) @@ -154,7 +156,7 @@ void managedChannelUsesCustomExecutor() throws IOException { InstantiatingHttpJsonChannelProvider.newBuilder() .setEndpoint(DEFAULT_ENDPOINT) .setExecutor(executor) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build(); instantiatingHttpJsonChannelProvider = (InstantiatingHttpJsonChannelProvider) @@ -180,13 +182,13 @@ void managedChannelUsesCustomExecutor() throws IOException { @Override protected Object getMtlsObjectFromTransportChannel( - MtlsProvider provider, CertificateBasedAccess cba) + MtlsProvider provider, CertificateBasedAccess certificateBasedAccess) throws IOException, GeneralSecurityException { InstantiatingHttpJsonChannelProvider channelProvider = InstantiatingHttpJsonChannelProvider.newBuilder() .setEndpoint("localhost:8080") .setMtlsProvider(provider) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) .build(); diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 587e48bbfe..896a2ad9f8 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -288,7 +288,7 @@ private String determineUniverseDomain() { /** Determines the fully resolved endpoint and universe domain values */ private String determineEndpoint() throws IOException { - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = certificateBasedAccess() == null ? CertificateBasedAccess.createWithSystemEnv() : certificateBasedAccess(); @@ -325,7 +325,11 @@ private String determineEndpoint() throws IOException { String endpoint = mtlsEndpointResolver( - customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider, cba); + customEndpoint, + mtlsEndpoint(), + switchToMtlsEndpointAllowed(), + mtlsProvider, + certificateBasedAccess); // Check if mTLS is configured with non-GDU if (endpoint.equals(mtlsEndpoint()) @@ -383,16 +387,17 @@ String mtlsEndpointResolver( String mtlsEndpoint, boolean switchToMtlsEndpointAllowed, MtlsProvider mtlsProvider, - CertificateBasedAccess cba) + CertificateBasedAccess certificateBasedAccess) throws IOException { - if (switchToMtlsEndpointAllowed && cba != null && mtlsProvider != null) { - switch (cba.getMtlsEndpointUsagePolicy()) { + if (switchToMtlsEndpointAllowed && certificateBasedAccess != null && mtlsProvider != null) { + switch (certificateBasedAccess.getMtlsEndpointUsagePolicy()) { case ALWAYS: return mtlsEndpoint; case NEVER: return endpoint; default: - if (cba.useMtlsClientCertificate() && mtlsProvider.getKeyStore() != null) { + if (certificateBasedAccess.useMtlsClientCertificate() + && mtlsProvider.getKeyStore() != null) { return mtlsEndpoint; } return endpoint; diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java index 004cf1aa70..e90bdae5a8 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java @@ -32,11 +32,17 @@ import com.google.api.gax.rpc.internal.EnvironmentProvider; -/** Utility class for handling certificate-based access configurations. */ +/** + * Utility class for handling certificate-based access configurations. + * + *

This class handles the processing of GOOGLE_API_USE_CLIENT_CERTIFICATE and + * GOOGLE_API_USE_MTLS_ENDPOINT environment variables according to https://google.aip.dev/auth/4114 + */ public class CertificateBasedAccess { private final EnvironmentProvider envProvider; + /** The EnvironmentProvider mechanism supports env var injection for unit tests. */ public CertificateBasedAccess(EnvironmentProvider envProvider) { this.envProvider = envProvider; } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index 117bd404f7..ef64ccd726 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -75,7 +75,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsFalse() throws IOException { new FakeMtlsProvider( FakeMtlsProvider.createTestMtlsKeyStore(), "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = false; - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); Truth.assertThat( @@ -84,7 +84,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsFalse() throws IOException { DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider, - cba)) + certificateBasedAccess)) .isEqualTo(DEFAULT_ENDPOINT); } @@ -95,7 +95,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAuto() throws IOExc new FakeMtlsProvider( FakeMtlsProvider.createTestMtlsKeyStore(), "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); Truth.assertThat( @@ -104,7 +104,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAuto() throws IOExc DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider, - cba)) + certificateBasedAccess)) .isEqualTo(DEFAULT_MTLS_ENDPOINT); } @@ -115,7 +115,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAlways() throws IOE new FakeMtlsProvider( FakeMtlsProvider.createTestMtlsKeyStore(), "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "always" : "true"); Truth.assertThat( @@ -124,7 +124,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAlways() throws IOE DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider, - cba)) + certificateBasedAccess)) .isEqualTo(DEFAULT_MTLS_ENDPOINT); } @@ -135,7 +135,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageNever() throws IOEx new FakeMtlsProvider( FakeMtlsProvider.createTestMtlsKeyStore(), "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "never" : "true"); Truth.assertThat( @@ -144,7 +144,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageNever() throws IOEx DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider, - cba)) + certificateBasedAccess)) .isEqualTo(DEFAULT_ENDPOINT); } @@ -154,7 +154,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_useCertificateIsFalse_nullMt boolean throwExceptionForGetKeyStore = false; MtlsProvider mtlsProvider = new FakeMtlsProvider(null, "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false"); Truth.assertThat( @@ -163,7 +163,7 @@ void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_useCertificateIsFalse_nullMt DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider, - cba)) + certificateBasedAccess)) .isEqualTo(DEFAULT_ENDPOINT); } @@ -172,7 +172,7 @@ void mtlsEndpointResolver_getKeyStore_throwsIOException() throws IOException { boolean throwExceptionForGetKeyStore = true; MtlsProvider mtlsProvider = new FakeMtlsProvider(null, "", throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); assertThrows( @@ -183,7 +183,7 @@ void mtlsEndpointResolver_getKeyStore_throwsIOException() throws IOException { DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider, - cba)); + certificateBasedAccess)); } @Test @@ -270,7 +270,7 @@ void endpointContextBuild_noUniverseDomain_noEndpoints() throws IOException { void endpointContextBuild_mtlsConfigured_GDU() throws IOException { MtlsProvider mtlsProvider = new FakeMtlsProvider(FakeMtlsProvider.createTestMtlsKeyStore(), "", false); - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "always" : "true"); EndpointContext endpointContext = @@ -279,7 +279,7 @@ void endpointContextBuild_mtlsConfigured_GDU() throws IOException { .setTransportChannelProviderEndpoint(null) .setSwitchToMtlsEndpointAllowed(true) .setMtlsProvider(mtlsProvider) - .setCertificateBasedAccess(cba) + .setCertificateBasedAccess(certificateBasedAccess) .build(); Truth.assertThat(endpointContext.resolvedEndpoint()).isEqualTo(DEFAULT_MTLS_ENDPOINT); Truth.assertThat(endpointContext.resolvedUniverseDomain()) @@ -291,7 +291,7 @@ void endpointContextBuild_mtlsConfigured_nonGDU_throwsIllegalArgumentException() throws IOException { MtlsProvider mtlsProvider = new FakeMtlsProvider(FakeMtlsProvider.createTestMtlsKeyStore(), "", false); - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "always" : "true"); EndpointContext.Builder endpointContextBuilder = @@ -301,7 +301,7 @@ void endpointContextBuild_mtlsConfigured_nonGDU_throwsIllegalArgumentException() .setTransportChannelProviderEndpoint(null) .setSwitchToMtlsEndpointAllowed(true) .setMtlsProvider(mtlsProvider) - .setCertificateBasedAccess(cba); + .setCertificateBasedAccess(certificateBasedAccess); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, endpointContextBuilder::build); Truth.assertThat(exception.getMessage()) diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java index 5fea7fc333..bea4674b76 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/AbstractMtlsTransportChannelTest.java @@ -49,46 +49,48 @@ public abstract class AbstractMtlsTransportChannelTest { * if and only if the related mTLS object is not null. */ protected abstract Object getMtlsObjectFromTransportChannel( - MtlsProvider provider, CertificateBasedAccess cba) + MtlsProvider provider, CertificateBasedAccess certificateBasedAccess) throws IOException, GeneralSecurityException; @Test void testNotUseClientCertificate() throws IOException, GeneralSecurityException { - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false"); MtlsProvider provider = new FakeMtlsProvider(null, "", false); - assertNull(getMtlsObjectFromTransportChannel(provider, cba)); + assertNull(getMtlsObjectFromTransportChannel(provider, certificateBasedAccess)); } @Test void testUseClientCertificate() throws IOException, GeneralSecurityException { - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); MtlsProvider provider = new FakeMtlsProvider(FakeMtlsProvider.createTestMtlsKeyStore(), "", false); - assertNotNull(getMtlsObjectFromTransportChannel(provider, cba)); + assertNotNull(getMtlsObjectFromTransportChannel(provider, certificateBasedAccess)); } @Test void testNoClientCertificate() throws IOException, GeneralSecurityException { - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); MtlsProvider provider = new FakeMtlsProvider(null, "", false); - assertNull(getMtlsObjectFromTransportChannel(provider, cba)); + assertNull(getMtlsObjectFromTransportChannel(provider, certificateBasedAccess)); } @Test void testGetKeyStoreThrows() throws GeneralSecurityException { // Test the case where provider.getKeyStore() throws. - CertificateBasedAccess cba = + CertificateBasedAccess certificateBasedAccess = new CertificateBasedAccess( name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); MtlsProvider provider = new FakeMtlsProvider(null, "", true); IOException actual = - assertThrows(IOException.class, () -> getMtlsObjectFromTransportChannel(provider, cba)); + assertThrows( + IOException.class, + () -> getMtlsObjectFromTransportChannel(provider, certificateBasedAccess)); assertTrue(actual.getMessage().contains("getKeyStore throws exception")); } } From 3509bf08fbf60a241ce643c5f400409c5a3d5768 Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Wed, 16 Jul 2025 10:36:58 -0700 Subject: [PATCH 7/8] feat(mtls): Address code-review comments 2. --- .../main/java/com/google/api/gax/rpc/EndpointContext.java | 5 ++--- .../com/google/api/gax/rpc/mtls/CertificateBasedAccess.java | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 896a2ad9f8..bf2e98f882 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -125,7 +125,7 @@ public static EndpointContext getDefaultInstance() { public abstract MtlsProvider mtlsProvider(); @Nullable - public abstract CertificateBasedAccess certificateBasedAccess(); + abstract CertificateBasedAccess certificateBasedAccess(); public abstract boolean usingGDCH(); @@ -222,8 +222,7 @@ public abstract static class Builder { public abstract Builder setMtlsProvider(MtlsProvider mtlsProvider); - public abstract Builder setCertificateBasedAccess( - CertificateBasedAccess certificateBasedAccess); + abstract Builder setCertificateBasedAccess(CertificateBasedAccess certificateBasedAccess); public abstract Builder setUsingGDCH(boolean usingGDCH); diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java index e90bdae5a8..6f722bff60 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/mtls/CertificateBasedAccess.java @@ -30,6 +30,7 @@ package com.google.api.gax.rpc.mtls; +import com.google.api.core.InternalApi; import com.google.api.gax.rpc.internal.EnvironmentProvider; /** @@ -38,6 +39,7 @@ *

This class handles the processing of GOOGLE_API_USE_CLIENT_CERTIFICATE and * GOOGLE_API_USE_MTLS_ENDPOINT environment variables according to https://google.aip.dev/auth/4114 */ +@InternalApi public class CertificateBasedAccess { private final EnvironmentProvider envProvider; From 58b78a3861850317568466cd89096cef555fb56a Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Wed, 16 Jul 2025 14:24:37 -0700 Subject: [PATCH 8/8] feat(mtls): Address code-review comments 3. --- .../api/gax/grpc/InstantiatingGrpcChannelProvider.java | 6 +++--- .../gax/httpjson/InstantiatingHttpJsonChannelProvider.java | 5 +++-- .../google/api/gax/rpc/mtls/CertificateBasedAccessTest.java | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java index beda42b280..c24c74af04 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java @@ -153,7 +153,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP @Nullable private final Boolean allowNonDefaultServiceAccount; @VisibleForTesting final ImmutableMap directPathServiceConfig; @Nullable private final MtlsProvider mtlsProvider; - @Nullable private final CertificateBasedAccess certificateBasedAccess; + private final CertificateBasedAccess certificateBasedAccess; @Nullable private final SecureSessionAgent s2aConfigProvider; private final List allowedHardBoundTokenTypes; @VisibleForTesting final Map headersWithDuplicatesRemoved = new HashMap<>(); @@ -489,7 +489,7 @@ boolean canUseDirectPathWithUniverseDomain() { @VisibleForTesting ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSecurityException { - if (certificateBasedAccess == null || mtlsProvider == null) { + if (mtlsProvider == null) { return null; } if (certificateBasedAccess.useMtlsClientCertificate()) { @@ -861,7 +861,7 @@ public static final class Builder { private boolean useS2A; private EnvironmentProvider envProvider; private SecureSessionAgent s2aConfigProvider = SecureSessionAgent.create(); - private MtlsProvider mtlsProvider; + @Nullable private MtlsProvider mtlsProvider; private CertificateBasedAccess certificateBasedAccess; @Nullable private GrpcInterceptorProvider interceptorProvider; @Nullable private Integer maxInboundMessageSize; diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java index fdb535e318..528018ec75 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java @@ -50,6 +50,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nullable; /** * InstantiatingHttpJsonChannelProvider is a TransportChannelProvider which constructs a {@link @@ -74,7 +75,7 @@ public final class InstantiatingHttpJsonChannelProvider implements TransportChan private final HttpJsonInterceptorProvider interceptorProvider; private final String endpoint; private final HttpTransport httpTransport; - private final MtlsProvider mtlsProvider; + @Nullable private final MtlsProvider mtlsProvider; private final CertificateBasedAccess certificateBasedAccess; private InstantiatingHttpJsonChannelProvider( @@ -184,7 +185,7 @@ public TransportChannelProvider withCredentials(Credentials credentials) { } HttpTransport createHttpTransport() throws IOException, GeneralSecurityException { - if (certificateBasedAccess == null || mtlsProvider == null) { + if (mtlsProvider == null) { return null; } if (certificateBasedAccess.useMtlsClientCertificate()) { diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/CertificateBasedAccessTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/CertificateBasedAccessTest.java index b02e3282d5..e328e0af47 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/CertificateBasedAccessTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/mtls/CertificateBasedAccessTest.java @@ -69,7 +69,7 @@ void testUseMtlsEndpointNever() { void testUseMtlsClientCertificateTrue() { CertificateBasedAccess cba = new CertificateBasedAccess( - name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "true"); + name -> name.equals("GOOGLE_API_USE_CLIENT_CERTIFICATE") ? "true" : "auto"); assertTrue(cba.useMtlsClientCertificate()); } @@ -77,7 +77,7 @@ void testUseMtlsClientCertificateTrue() { void testUseMtlsClientCertificateFalse() { CertificateBasedAccess cba = new CertificateBasedAccess( - name -> name.equals("GOOGLE_API_USE_MTLS_ENDPOINT") ? "auto" : "false"); + name -> name.equals("GOOGLE_API_USE_CLIENT_CERTIFICATE") ? "false" : "auto"); assertFalse(cba.useMtlsClientCertificate()); } }