Skip to content

Commit 05117dd

Browse files
committed
refactor: Use new ConnectSettings.DnsNames field to validate the server certificate's server name.
1 parent 31917a4 commit 05117dd

5 files changed

Lines changed: 108 additions & 18 deletions

File tree

core/src/main/java/com/google/cloud/sql/core/DefaultConnectionInfoRepository.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
2020
import com.google.api.services.sqladmin.SQLAdmin;
2121
import com.google.api.services.sqladmin.model.ConnectSettings;
22+
import com.google.api.services.sqladmin.model.DnsNameMapping;
2223
import com.google.api.services.sqladmin.model.GenerateEphemeralCertRequest;
2324
import com.google.api.services.sqladmin.model.GenerateEphemeralCertResponse;
2425
import com.google.api.services.sqladmin.model.IpMapping;
@@ -287,10 +288,31 @@ private InstanceMetadata fetchMetadata(CloudSqlInstanceName instanceName, AuthTy
287288
boolean pscEnabled =
288289
instanceMetadata.getPscEnabled() != null
289290
&& instanceMetadata.getPscEnabled().booleanValue();
290-
if (pscEnabled
291-
&& instanceMetadata.getDnsName() != null
292-
&& !instanceMetadata.getDnsName().isEmpty()) {
293-
ipAddrs.put(IpType.PSC, instanceMetadata.getDnsName());
291+
292+
if (pscEnabled) {
293+
// Search the dns_names field for the PSC DNS Name.
294+
String pscDnsName = null;
295+
if (instanceMetadata.getDnsNames() != null) {
296+
for (DnsNameMapping dnm : instanceMetadata.getDnsNames()) {
297+
if ("PRIVATE_SERVICE_CONNECT".equals(dnm.getConnectionType())
298+
&& "INSTANCE".equals(dnm.getDnsScope())) {
299+
pscDnsName = dnm.getName();
300+
break;
301+
}
302+
}
303+
}
304+
305+
// If the psc dns name was not found, use the legacy dns_name field
306+
if (pscDnsName == null
307+
&& instanceMetadata.getDnsName() != null
308+
&& !instanceMetadata.getDnsName().isEmpty()) {
309+
pscDnsName = instanceMetadata.getDnsName();
310+
}
311+
312+
// If the psc dns name was found, add it to the ipaddrs map.
313+
if (pscDnsName != null) {
314+
ipAddrs.put(IpType.PSC, pscDnsName);
315+
}
294316
}
295317

296318
// Verify the instance has at least one IP type assigned that can be used to connect.
@@ -301,6 +323,18 @@ private InstanceMetadata fetchMetadata(CloudSqlInstanceName instanceName, AuthTy
301323
+ "IP address.",
302324
instanceName.getConnectionName()));
303325
}
326+
327+
// Find a DNS name to use to validate the certificate from the dns_names field. Any
328+
// name in the list may be used to validate the server TLS certificate.
329+
// Fall back to legacy dns_name field if necessary.
330+
String serverName = null;
331+
if (instanceMetadata.getDnsNames() != null && !instanceMetadata.getDnsNames().isEmpty()) {
332+
serverName = instanceMetadata.getDnsNames().get(0).getName();
333+
}
334+
if (serverName == null) {
335+
serverName = instanceMetadata.getDnsName();
336+
}
337+
304338
// Update the Server CA certificate used to create the SSL connection with the instance.
305339
try {
306340
List<Certificate> instanceCaCertificates =
@@ -313,7 +347,7 @@ private InstanceMetadata fetchMetadata(CloudSqlInstanceName instanceName, AuthTy
313347
ipAddrs,
314348
instanceCaCertificates,
315349
isCasManagedCertificate(instanceMetadata.getServerCaMode()),
316-
instanceMetadata.getDnsName(),
350+
serverName,
317351
pscEnabled);
318352
} catch (CertificateException ex) {
319353
throw new RuntimeException(

core/src/main/java/com/google/cloud/sql/core/InstanceCheckingTrustManger.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,14 @@ private void checkCertificateChain(X509Certificate[] chain) throws CertificateEx
9696
throw new CertificateException("Subject is missing");
9797
}
9898

99-
if (instanceMetadata.isCasManagedCertificate() || instanceMetadata.isPscEnabled()) {
100-
checkSan(chain);
101-
} else {
99+
// If the connector was configured without a DNS name and the
100+
// instance metadata does not contain a domain name, use legacy CN validation
101+
if (Strings.isNullOrEmpty(instanceMetadata.getInstanceName().getDomainName())
102+
&& Strings.isNullOrEmpty(instanceMetadata.getDnsName())) {
102103
checkCn(chain);
104+
} else {
105+
// If there is a DNS name, check the Subject Alternative Names.
106+
checkSan(chain);
103107
}
104108
}
105109

core/src/test/java/com/google/cloud/sql/core/DefaultConnectionInfoRepositoryTest.java

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void testFetchInstanceData_returnsIpAddresses()
5151
throws ExecutionException, InterruptedException, GeneralSecurityException,
5252
OperatorCreationException {
5353
MockAdminApi mockAdminApi =
54-
buildMockAdminApi(INSTANCE_CONNECTION_NAME, DATABASE_VERSION, DEFAULT_BASE_URL);
54+
buildMockAdminApi(INSTANCE_CONNECTION_NAME, DATABASE_VERSION, DEFAULT_BASE_URL, false);
5555
ConnectorConfig config = new ConnectorConfig.Builder().build();
5656
ConnectionInfoRepository repo =
5757
new StubConnectionInfoRepositoryFactory(mockAdminApi.getHttpTransport())
@@ -85,7 +85,45 @@ public void testFetchInstanceData_returnsPscForNonIpDatabase()
8585
null,
8686
DATABASE_VERSION,
8787
SAMPLE_PCS_DNS_NAME,
88-
DEFAULT_BASE_URL);
88+
DEFAULT_BASE_URL,
89+
false);
90+
mockAdminApi.addGenerateEphemeralCertResponse(
91+
INSTANCE_CONNECTION_NAME, Duration.ofHours(1), DEFAULT_BASE_URL);
92+
ConnectorConfig config = new ConnectorConfig.Builder().build();
93+
94+
ConnectionInfoRepository repo =
95+
new StubConnectionInfoRepositoryFactory(mockAdminApi.getHttpTransport())
96+
.create(new StubCredentialFactory().create(), config);
97+
98+
ConnectionInfo connectionInfo =
99+
repo.getConnectionInfo(
100+
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
101+
() -> Optional.empty(),
102+
AuthType.PASSWORD,
103+
newTestExecutor(),
104+
Futures.immediateFuture(mockAdminApi.getClientKeyPair()))
105+
.get();
106+
assertThat(connectionInfo.getSslContext()).isInstanceOf(SSLContext.class);
107+
108+
Map<IpType, String> ipAddrs = connectionInfo.getIpAddrs();
109+
assertThat(ipAddrs.get(IpType.PSC)).isEqualTo(SAMPLE_PCS_DNS_NAME);
110+
assertThat(ipAddrs.size()).isEqualTo(1);
111+
}
112+
113+
@Test
114+
public void testFetchInstanceData_legacyPscDns_returnsPscForNonIpDatabase()
115+
throws ExecutionException, InterruptedException, GeneralSecurityException,
116+
OperatorCreationException {
117+
118+
MockAdminApi mockAdminApi = new MockAdminApi();
119+
mockAdminApi.addConnectSettingsResponse(
120+
INSTANCE_CONNECTION_NAME,
121+
null,
122+
null,
123+
DATABASE_VERSION,
124+
SAMPLE_PCS_DNS_NAME,
125+
DEFAULT_BASE_URL,
126+
true);
89127
mockAdminApi.addGenerateEphemeralCertResponse(
90128
INSTANCE_CONNECTION_NAME, Duration.ofHours(1), DEFAULT_BASE_URL);
91129
ConnectorConfig config = new ConnectorConfig.Builder().build();
@@ -122,7 +160,8 @@ private ListeningScheduledExecutorService newTestExecutor() {
122160
public void testFetchInstanceData_throwsException_whenIamAuthnIsNotSupported()
123161
throws GeneralSecurityException, OperatorCreationException {
124162
MockAdminApi mockAdminApi =
125-
buildMockAdminApi(INSTANCE_CONNECTION_NAME, "SQLSERVER_2019_STANDARD", DEFAULT_BASE_URL);
163+
buildMockAdminApi(
164+
INSTANCE_CONNECTION_NAME, "SQLSERVER_2019_STANDARD", DEFAULT_BASE_URL, false);
126165
ConnectorConfig config = new ConnectorConfig.Builder().build();
127166
ConnectionInfoRepository repo =
128167
new StubConnectionInfoRepositoryFactory(mockAdminApi.getHttpTransport())
@@ -149,7 +188,7 @@ public void testFetchInstanceData_throwsException_whenIamAuthnIsNotSupported()
149188
public void testFetchInstanceData_throwsException_whenRequestsTimeout()
150189
throws GeneralSecurityException, OperatorCreationException {
151190
MockAdminApi mockAdminApi =
152-
buildMockAdminApi(INSTANCE_CONNECTION_NAME, DATABASE_VERSION, DEFAULT_BASE_URL);
191+
buildMockAdminApi(INSTANCE_CONNECTION_NAME, DATABASE_VERSION, DEFAULT_BASE_URL, false);
153192
ConnectorConfig config = new ConnectorConfig.Builder().build();
154193
ConnectionInfoRepository repo =
155194
new StubConnectionInfoRepositoryFactory(new BadConnectionFactory())
@@ -182,7 +221,7 @@ public void testSetAdminUrl_FetchInstanceData_returnsIpAddresses()
182221
String adminServicePath = "sqladmin/";
183222
String baseUrl = adminRootUrl + adminServicePath;
184223
MockAdminApi mockAdminApi =
185-
buildMockAdminApi(INSTANCE_CONNECTION_NAME, DATABASE_VERSION, baseUrl);
224+
buildMockAdminApi(INSTANCE_CONNECTION_NAME, DATABASE_VERSION, baseUrl, false);
186225
ConnectorConfig config =
187226
new ConnectorConfig.Builder()
188227
.withAdminRootUrl(adminRootUrl)
@@ -210,7 +249,7 @@ public void testSetAdminUrl_FetchInstanceData_returnsIpAddresses()
210249

211250
@SuppressWarnings("SameParameterValue")
212251
private MockAdminApi buildMockAdminApi(
213-
String instanceConnectionName, String databaseVersion, String baseUrl)
252+
String instanceConnectionName, String databaseVersion, String baseUrl, boolean legacyDnsName)
214253
throws GeneralSecurityException, OperatorCreationException {
215254
MockAdminApi mockAdminApi = new MockAdminApi();
216255
mockAdminApi.addConnectSettingsResponse(
@@ -219,7 +258,8 @@ private MockAdminApi buildMockAdminApi(
219258
SAMPLE_PRIVATE_IP,
220259
databaseVersion,
221260
SAMPLE_PCS_DNS_NAME,
222-
baseUrl);
261+
baseUrl,
262+
legacyDnsName);
223263
mockAdminApi.addGenerateEphemeralCertResponse(
224264
instanceConnectionName, Duration.ofHours(1), baseUrl);
225265
return mockAdminApi;

core/src/test/java/com/google/cloud/sql/core/MockAdminApi.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
2727
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
2828
import com.google.api.services.sqladmin.model.ConnectSettings;
29+
import com.google.api.services.sqladmin.model.DnsNameMapping;
2930
import com.google.api.services.sqladmin.model.GenerateEphemeralCertResponse;
3031
import com.google.api.services.sqladmin.model.IpMapping;
3132
import com.google.api.services.sqladmin.model.SslCert;
@@ -38,6 +39,7 @@
3839
import java.security.spec.InvalidKeySpecException;
3940
import java.time.Duration;
4041
import java.util.ArrayList;
42+
import java.util.Collections;
4143
import java.util.Date;
4244
import java.util.List;
4345
import java.util.concurrent.atomic.AtomicInteger;
@@ -83,7 +85,8 @@ public void addConnectSettingsResponse(
8385
String privateIp,
8486
String databaseVersion,
8587
String pscHostname,
86-
String baseUrl) {
88+
String baseUrl,
89+
boolean legacyPscDnsName) {
8790
CloudSqlInstanceName cloudSqlInstanceName = new CloudSqlInstanceName(instanceConnectionName);
8891

8992
ArrayList<IpMapping> ipMappings = new ArrayList<>();
@@ -103,10 +106,19 @@ public void addConnectSettingsResponse(
103106
.setServerCaCert(new SslCert().setCert(TestKeys.getServerCertPem()))
104107
.setDatabaseVersion(databaseVersion)
105108
.setPscEnabled(pscHostname != null)
106-
.setDnsName(pscHostname)
107109
.setPscEnabled(pscHostname != null)
108110
.setRegion(cloudSqlInstanceName.getRegionId());
109111
settings.setFactory(GsonFactory.getDefaultInstance());
112+
if (legacyPscDnsName) {
113+
settings.setDnsName(pscHostname);
114+
} else {
115+
settings.setDnsNames(
116+
Collections.singletonList(
117+
new DnsNameMapping()
118+
.setDnsScope("INSTANCE")
119+
.setConnectionType("PRIVATE_SERVICE_CONNECT")
120+
.setName(pscHostname)));
121+
}
110122

111123
connectSettingsRequests.add(
112124
new ConnectSettingsRequest(cloudSqlInstanceName, settings, baseUrl));

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@
148148
<dependency>
149149
<groupId>com.google.apis</groupId>
150150
<artifactId>google-api-services-sqladmin</artifactId>
151-
<version>v1beta4-rev20250205-2.0.0</version>
151+
<version>v1beta4-rev20250226-2.0.0</version>
152152
</dependency>
153153
<dependency>
154154
<groupId>com.google.http-client</groupId>

0 commit comments

Comments
 (0)