Skip to content

Commit f89e805

Browse files
authored
feat: Use configured DNS name to lookup instance IP address (#2244)
When a custom DNS name is used to connect to a Cloud SQL instance, the dialer should first attempt to resolve the custom DNS name to an IP address and use that for the connection. If the lookup fails, the dialer should fall back to using the IP address from the instance metadata. Fixes: #2243
1 parent 6881969 commit f89e805

File tree

6 files changed

+167
-18
lines changed

6 files changed

+167
-18
lines changed

build.sh

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,12 @@ function test() {
4141
if [[ "$(uname -s)" == "Darwin" ]]; then
4242
echo "macOS detected. Setting up IP aliases for tests."
4343
echo "You may be prompted for your password to run sudo."
44-
sudo ifconfig lo0 alias 127.0.0.2 up
45-
sudo ifconfig lo0 alias 127.0.0.3 up
44+
if ! ifconfig lo0 | grep -q 127.0.0.2 ; then
45+
sudo ifconfig lo0 alias 127.0.0.2 up
46+
fi
47+
if ! ifconfig lo0 | grep -q 127.0.0.3 ; then
48+
sudo ifconfig lo0 alias 127.0.0.3 up
49+
fi
4650
fi
4751
$mvn_cmd -P coverage test
4852
}
@@ -93,7 +97,7 @@ function write_e2e_env(){
9397
secret_vars=(
9498
MYSQL_CONNECTION_NAME=MYSQL_CONNECTION_NAME
9599
MYSQL_USER=MYSQL_USER
96-
MYSQL_USER_IAM=MYSQL_USER_IAM_GO
100+
IMPERSONATED_USER=IMPERSONATED_USER
97101
MYSQL_PASS=MYSQL_PASS
98102
MYSQL_DB=MYSQL_DB
99103
MYSQL_MCP_CONNECTION_NAME=MYSQL_MCP_CONNECTION_NAME
@@ -107,8 +111,8 @@ function write_e2e_env(){
107111
POSTGRES_CAS_PASS=POSTGRES_CAS_PASS
108112
POSTGRES_CUSTOMER_CAS_CONNECTION_NAME=POSTGRES_CUSTOMER_CAS_CONNECTION_NAME
109113
POSTGRES_CUSTOMER_CAS_PASS=POSTGRES_CUSTOMER_CAS_PASS
110-
POSTGRES_CUSTOMER_CAS_DOMAIN_NAME=POSTGRES_CUSTOMER_CAS_DOMAIN_NAME
111-
POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME=POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME
114+
POSTGRES_CUSTOMER_CAS_PASS_VALID_DOMAIN_NAME=POSTGRES_CUSTOMER_CAS_DOMAIN_NAME
115+
POSTGRES_CUSTOMER_CAS_PASS_INVALID_DOMAIN_NAME=POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME
112116
POSTGRES_MCP_CONNECTION_NAME=POSTGRES_MCP_CONNECTION_NAME
113117
POSTGRES_MCP_PASS=POSTGRES_MCP_PASS
114118
SQLSERVER_CONNECTION_NAME=SQLSERVER_CONNECTION_NAME
@@ -133,6 +137,10 @@ function write_e2e_env(){
133137
val=$(gcloud secrets versions access latest --project "$TEST_PROJECT" --secret="$secret_name")
134138
echo "export $env_var_name='$val'"
135139
done
140+
141+
echo "export MYSQL_IAM_USER='$(whoami)'"
142+
echo "export POSTGRES_IAM_USER='$(whoami)@google.com'"
143+
136144
} > "$outfile"
137145

138146
}

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@
2424
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
2525
import java.io.File;
2626
import java.io.IOException;
27+
import java.net.InetAddress;
2728
import java.net.InetSocketAddress;
2829
import java.net.Socket;
30+
import java.net.UnknownHostException;
2931
import java.security.KeyPair;
32+
import java.util.List;
3033
import java.util.Timer;
3134
import java.util.concurrent.ConcurrentHashMap;
3235
import java.util.concurrent.ExecutionException;
@@ -52,6 +55,7 @@ class Connector {
5255
private final ConnectorConfig config;
5356

5457
private final InstanceConnectionNameResolver instanceNameResolver;
58+
private final DnsResolver dnsResolver;
5559
private final Timer instanceNameResolverTimer;
5660
private final ProtocolHandler mdxProtocolHandler;
5761

@@ -65,9 +69,9 @@ class Connector {
6569
long refreshTimeoutMs,
6670
int serverProxyPort,
6771
InstanceConnectionNameResolver instanceNameResolver,
72+
DnsResolver dnsResolver,
6873
ProtocolHandler mdxProtocolHandler) {
6974
this.config = config;
70-
7175
this.adminApi =
7276
connectionInfoRepositoryFactory.create(instanceCredentialFactory.create(), config);
7377
this.instanceCredentialFactory = instanceCredentialFactory;
@@ -76,6 +80,7 @@ class Connector {
7680
this.minRefreshDelayMs = minRefreshDelayMs;
7781
this.serverProxyPort = serverProxyPort;
7882
this.instanceNameResolver = instanceNameResolver;
83+
this.dnsResolver = dnsResolver;
7984
this.instanceNameResolverTimer = new Timer("InstanceNameResolverTimer", true);
8085
this.mdxProtocolHandler = mdxProtocolHandler;
8186
}
@@ -125,6 +130,40 @@ Socket connect(ConnectionConfig config, long timeoutMs) throws IOException {
125130
try {
126131
ConnectionMetadata metadata = instance.getConnectionMetadata(timeoutMs);
127132
String instanceIp = metadata.getPreferredIpAddress();
133+
134+
// If a domain name was used to connect, resolve it to an IP address
135+
if (!Strings.isNullOrEmpty(instance.getConfig().getDomainName())) {
136+
try {
137+
List<InetAddress> addrs = dnsResolver.resolveHost(instance.getConfig().getDomainName());
138+
if (addrs != null && !addrs.isEmpty()) {
139+
logger.debug(
140+
String.format(
141+
"[%s] custom DNS name %s resolved to %s, using it to connect",
142+
instance.getConfig().getCloudSqlInstance(),
143+
instance.getConfig().getDomainName(),
144+
addrs.get(0).getHostAddress()));
145+
instanceIp = addrs.get(0).getHostAddress();
146+
} else {
147+
logger.debug(
148+
String.format(
149+
"[%s] custom DNS name %s resolved but returned no entries, using %s from"
150+
+ " instance metadata",
151+
instance.getConfig().getCloudSqlInstance(),
152+
instance.getConfig().getDomainName(),
153+
instanceIp));
154+
}
155+
} catch (UnknownHostException e) {
156+
logger.debug(
157+
String.format(
158+
"[%s] custom DNS name %s did not resolve to an IP address: %s, using %s from"
159+
+ " instance metadata",
160+
instance.getConfig().getCloudSqlInstance(),
161+
instance.getConfig().getDomainName(),
162+
e.getMessage(),
163+
instanceIp));
164+
}
165+
}
166+
128167
logger.debug(String.format("[%s] Connecting to instance.", instanceIp));
129168

130169
SSLSocket socket = (SSLSocket) metadata.getSslContext().getSocketFactory().createSocket();

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616

1717
package com.google.cloud.sql.core;
1818

19+
import java.net.InetAddress;
1920
import java.net.UnknownHostException;
2021
import java.util.Arrays;
2122
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.List;
2225
import java.util.stream.Collectors;
2326
import javax.naming.NameNotFoundException;
27+
import org.xbill.DNS.ARecord;
2428
import org.xbill.DNS.Lookup;
2529
import org.xbill.DNS.Record;
2630
import org.xbill.DNS.SimpleResolver;
@@ -105,4 +109,44 @@ public Collection<String> resolveTxt(String domainName) throws NameNotFoundExcep
105109
throw new RuntimeException("Invalid domain name format: " + domainName, e);
106110
}
107111
}
112+
113+
/**
114+
* Resolve an A record.
115+
*
116+
* @param hostName the hostname to look up
117+
* @return the resolved IP addresses
118+
* @throws UnknownHostException if no records are found.
119+
*/
120+
@Override
121+
public List<InetAddress> resolveHost(String hostName) throws UnknownHostException {
122+
try {
123+
Lookup lookup = new Lookup(hostName, Type.A);
124+
if (this.resolver != null) {
125+
lookup.setResolver(this.resolver);
126+
}
127+
lookup.run();
128+
129+
int resultCode = lookup.getResult();
130+
if (resultCode == Lookup.HOST_NOT_FOUND) {
131+
throw new UnknownHostException("DNS record not found for " + hostName);
132+
}
133+
if (resultCode != Lookup.SUCCESSFUL) {
134+
throw new UnknownHostException(
135+
"DNS lookup failed for " + hostName + ": " + lookup.getErrorString());
136+
}
137+
138+
Record[] records = lookup.getAnswers();
139+
if (records == null || records.length == 0) {
140+
return Collections.emptyList();
141+
}
142+
143+
return Arrays.stream(records)
144+
.map(r -> (ARecord) r)
145+
.map(ARecord::getAddress)
146+
.collect(Collectors.toList());
147+
148+
} catch (TextParseException e) {
149+
throw new UnknownHostException("Invalid domain name format: " + hostName);
150+
}
151+
}
108152
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616

1717
package com.google.cloud.sql.core;
1818

19+
import java.net.InetAddress;
20+
import java.net.UnknownHostException;
1921
import java.util.Collection;
22+
import java.util.List;
2023
import javax.naming.NameNotFoundException;
2124

2225
/** Wraps the Java DNS API. */
2326
interface DnsResolver {
2427
Collection<String> resolveTxt(String domainName) throws NameNotFoundException;
28+
29+
List<InetAddress> resolveHost(String hostName) throws UnknownHostException;
2530
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ private Connector createConnector(ConnectorConfig config) {
339339
connectTimeoutMs,
340340
serverProxyPort,
341341
new DnsInstanceConnectionNameResolver(new DnsJavaResolver()),
342+
new DnsJavaResolver(),
342343
this.mdxProtocolHandler);
343344
}
344345

0 commit comments

Comments
 (0)