Skip to content

Commit

Permalink
SNOW-1526507: Pick top level domain for Snowflake hosts (#1820)
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-dprzybysz authored Jul 16, 2024
1 parent 579cb8f commit b9dcc47
Show file tree
Hide file tree
Showing 11 changed files with 278 additions and 19 deletions.
13 changes: 13 additions & 0 deletions src/main/java/net/snowflake/client/core/PrivateLinkDetector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.snowflake.client.core;

@SnowflakeJdbcInternalApi
public class PrivateLinkDetector {
/**
* We can only tell if private link is enabled for certain hosts when the hostname contains the
* word 'privatelink' but we don't have a good way of telling if a private link connection is
* expected for internal stages for example.
*/
public static boolean isPrivateLink(String host) {
return host.toLowerCase().contains(".privatelink.snowflakecomputing.");
}
}
23 changes: 14 additions & 9 deletions src/main/java/net/snowflake/client/core/SFTrustManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,10 @@ public class SFTrustManager extends X509ExtendedTrustManager {
private static final int DEFAULT_OCSP_CACHE_SERVER_CONNECTION_TIMEOUT = 5000;
/** Default OCSP responder connection timeout */
private static final int DEFAULT_OCSP_RESPONDER_CONNECTION_TIMEOUT = 10000;
/** Default OCSP Cache server host name prefix */
private static final String DEFAULT_OCSP_CACHE_HOST_PREFIX = "http://ocsp.snowflakecomputing.";
/** Default OCSP Cache server host name */
private static final String DEFAULT_OCSP_CACHE_HOST = "http://ocsp.snowflakecomputing.com";
private static final String DEFAULT_OCSP_CACHE_HOST = DEFAULT_OCSP_CACHE_HOST_PREFIX + "com";

/** OCSP response file cache directory */
private static final FileCacheManager fileCacheManager;
Expand Down Expand Up @@ -200,7 +202,7 @@ public class SFTrustManager extends X509ExtendedTrustManager {
/** OCSP Response Cache server Retry URL pattern */
static String SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN;
/** OCSP response cache server URL. */
private static String SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE;
static String SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE;

private static JcaX509CertificateConverter CONVERTER_X509 = new JcaX509CertificateConverter();
/** RootCA cache */
Expand Down Expand Up @@ -315,7 +317,7 @@ static void resetOCSPResponseCacherServerURL(String ocspCacheServerUrl) throws I
return;
}
SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE = ocspCacheServerUrl;
if (!SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE.startsWith(DEFAULT_OCSP_CACHE_HOST)) {
if (!SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE.startsWith(DEFAULT_OCSP_CACHE_HOST_PREFIX)) {
URL url = new URL(SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);
if (url.getPort() > 0) {
SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN =
Expand All @@ -331,7 +333,7 @@ static void resetOCSPResponseCacherServerURL(String ocspCacheServerUrl) throws I
}
}

private static void setOCSPResponseCacheServerURL() {
private static void setOCSPResponseCacheServerURL(String topLevelDomain) {
String ocspCacheUrl = systemGetProperty(SF_OCSP_RESPONSE_CACHE_SERVER_URL);
if (ocspCacheUrl != null) {
SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE = ocspCacheUrl;
Expand All @@ -348,7 +350,7 @@ private static void setOCSPResponseCacheServerURL() {
}
if (SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE == null) {
SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE =
String.format("%s/%s", DEFAULT_OCSP_CACHE_HOST, CACHE_FILE_NAME);
String.format("%s%s/%s", DEFAULT_OCSP_CACHE_HOST_PREFIX, topLevelDomain, CACHE_FILE_NAME);
}
logger.debug("Set OCSP response cache server to: {}", SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);
}
Expand Down Expand Up @@ -791,7 +793,8 @@ void validateRevocationStatus(X509Certificate[] chain, String peerHost)
ocspCacheServer.resetOCSPResponseCacheServer(peerHost);
}

setOCSPResponseCacheServerURL();
String topLevelDomain = peerHost.substring(peerHost.lastIndexOf(".") + 1);
setOCSPResponseCacheServerURL(topLevelDomain);
boolean isCached = isCached(pairIssuerSubjectList);
if (useOCSPResponseCacheServer() && !isCached) {
if (!ocspCacheServer.new_endpoint_enabled) {
Expand Down Expand Up @@ -1546,14 +1549,16 @@ static class OCSPCacheServer {

void resetOCSPResponseCacheServer(String host) {
String ocspCacheServerUrl;
if (host.indexOf(".global.snowflakecomputing.com") > 0) {
if (host.toLowerCase().contains(".global.snowflakecomputing.")) {
ocspCacheServerUrl =
String.format("https://ocspssd%s/%s", host.substring(host.indexOf('-')), "ocsp");
} else if (host.indexOf(".snowflakecomputing.com") > 0) {
} else if (host.toLowerCase().contains(".snowflakecomputing.")) {
ocspCacheServerUrl =
String.format("https://ocspssd%s/%s", host.substring(host.indexOf('.')), "ocsp");
} else {
ocspCacheServerUrl = "https://ocspssd.snowflakecomputing.com/ocsp";
String topLevelDomain = host.substring(host.lastIndexOf(".") + 1);
ocspCacheServerUrl =
String.format("https://ocspssd.snowflakecomputing.%s/ocsp", topLevelDomain);
}
SF_OCSP_RESPONSE_CACHE_SERVER = String.format("%s/%s", ocspCacheServerUrl, "fetch");
SF_OCSP_RESPONSE_RETRY_URL = String.format("%s/%s", ocspCacheServerUrl, "retry");
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/net/snowflake/client/core/SessionUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -1702,7 +1702,7 @@ enum TokenRequestType {
* @param serverUrl The Snowflake URL includes protocol such as "https://"
*/
public static void resetOCSPUrlIfNecessary(String serverUrl) throws IOException {
if (serverUrl.indexOf(".privatelink.snowflakecomputing.com") > 0) {
if (PrivateLinkDetector.isPrivateLink(serverUrl)) {
// Privatelink uses special OCSP Cache server
URL url = new URL(serverUrl);
String host = url.getHost();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ private void setupSnowflakeS3Client(
}

static String getDomainSuffixForRegionalUrl(String regionName) {
return regionName.startsWith("cn-") ? "amazonaws.com.cn" : "amazonaws.com";
return regionName.toLowerCase().startsWith("cn-") ? "amazonaws.com.cn" : "amazonaws.com";
}

// Returns the Max number of retry attempts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.snowflake.client.jdbc.diagnostic;

import net.snowflake.client.core.PrivateLinkDetector;

/*
The SnowflakeEndpoint class represents an endpoint as returned by the System$allowlist() SQL
function. Example:
Expand All @@ -20,10 +22,6 @@ public SnowflakeEndpoint(String type, String host, int port) {
this.isSecure = (this.port == 443);
}

public SnowflakeEndpoint() {
this(null, null, -1);
}

public String getType() {
return this.type;
}
Expand All @@ -40,11 +38,8 @@ public int getPort() {
return this.port;
}

// We can only tell if private link is enabled for certain hosts when the hostname contains
// the word 'privatelink' but we don't have a good way of telling if a private link connection
// is expected for internal stages for example.
public boolean isPrivateLink() {
return (host.contains("privatelink.snowflakecomputing.com"));
return PrivateLinkDetector.isPrivateLink(host);
}

@Override
Expand Down
97 changes: 97 additions & 0 deletions src/test/java/net/snowflake/client/core/OCSPCacheServerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package net.snowflake.client.core;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class OCSPCacheServerTest {

@Parameterized.Parameters(
name = "For host {0} cache server fetch url should be {1} and retry url {2}")
public static Object[][] data() {
return new Object[][] {
{
"bla-12345.global.snowflakecomputing.com",
"https://ocspssd-12345.global.snowflakecomputing.com/ocsp/fetch",
"https://ocspssd-12345.global.snowflakecomputing.com/ocsp/retry"
},
{
"bla-12345.global.snowflakecomputing.cn",
"https://ocspssd-12345.global.snowflakecomputing.cn/ocsp/fetch",
"https://ocspssd-12345.global.snowflakecomputing.cn/ocsp/retry"
},
{
"bla-12345.global.snowflakecomputing.xyz",
"https://ocspssd-12345.global.snowflakecomputing.xyz/ocsp/fetch",
"https://ocspssd-12345.global.snowflakecomputing.xyz/ocsp/retry"
},
{
"bla-12345.GLOBAL.snowflakecomputing.xyz",
"https://ocspssd-12345.GLOBAL.snowflakecomputing.xyz/ocsp/fetch",
"https://ocspssd-12345.GLOBAL.snowflakecomputing.xyz/ocsp/retry"
},
{
"bla-12345.snowflakecomputing.com",
"https://ocspssd.snowflakecomputing.com/ocsp/fetch",
"https://ocspssd.snowflakecomputing.com/ocsp/retry"
},
{
"bla-12345.snowflakecomputing.cn",
"https://ocspssd.snowflakecomputing.cn/ocsp/fetch",
"https://ocspssd.snowflakecomputing.cn/ocsp/retry"
},
{
"bla-12345.snowflakecomputing.xyz",
"https://ocspssd.snowflakecomputing.xyz/ocsp/fetch",
"https://ocspssd.snowflakecomputing.xyz/ocsp/retry"
},
{
"bla-12345.SNOWFLAKEcomputing.xyz",
"https://ocspssd.SNOWFLAKEcomputing.xyz/ocsp/fetch",
"https://ocspssd.SNOWFLAKEcomputing.xyz/ocsp/retry"
},
{
"s3.amazoncomaws.com",
"https://ocspssd.snowflakecomputing.com/ocsp/fetch",
"https://ocspssd.snowflakecomputing.com/ocsp/retry"
},
{
"s3.amazoncomaws.COM",
"https://ocspssd.snowflakecomputing.COM/ocsp/fetch",
"https://ocspssd.snowflakecomputing.COM/ocsp/retry"
},
{
"s3.amazoncomaws.com.cn",
"https://ocspssd.snowflakecomputing.cn/ocsp/fetch",
"https://ocspssd.snowflakecomputing.cn/ocsp/retry"
},
{
"S3.AMAZONCOMAWS.COM.CN",
"https://ocspssd.snowflakecomputing.CN/ocsp/fetch",
"https://ocspssd.snowflakecomputing.CN/ocsp/retry"
},
};
}

private final String host;
private final String expectedFetchUrl;
private final String expectedRetryUrl;

public OCSPCacheServerTest(String host, String expectedFetchUrl, String expectedRetryUrl) {
this.host = host;
this.expectedFetchUrl = expectedFetchUrl;
this.expectedRetryUrl = expectedRetryUrl;
}

@Test
public void shouldChooseOcspCacheServerUrls() {
SFTrustManager.OCSPCacheServer ocspCacheServer = new SFTrustManager.OCSPCacheServer();
ocspCacheServer.resetOCSPResponseCacheServer(host);

assertEquals(expectedFetchUrl, ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER);
assertEquals(expectedRetryUrl, ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package net.snowflake.client.core;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class PrivateLinkDetectorTest {

@Parameterized.Parameters(name = "Host {0} is private link: {1}")
public static Object[][] data() {
return new Object[][] {
{"snowhouse.snowflakecomputing.com", false},
{"snowhouse.privatelink.snowflakecomputing.com", true},
{"snowhouse.PRIVATELINK.snowflakecomputing.com", true},
{"snowhouse.snowflakecomputing.cn", false},
{"snowhouse.privatelink.snowflakecomputing.cn", true},
{"snowhouse.PRIVATELINK.snowflakecomputing.cn", true},
{"snowhouse.snowflakecomputing.xyz", false},
{"snowhouse.privatelink.snowflakecomputing.xyz", true},
{"snowhouse.PRIVATELINK.snowflakecomputing.xyz", true},
};
}

private final String host;
private final boolean expectedToBePrivateLink;

public PrivateLinkDetectorTest(String host, boolean expectedToBePrivateLink) {
this.host = host;
this.expectedToBePrivateLink = expectedToBePrivateLink;
}

@Test
public void shouldDetectPrivateLinkHost() {
assertEquals(
String.format("Expecting %s to be private link: %s", host, expectedToBePrivateLink),
expectedToBePrivateLink,
PrivateLinkDetector.isPrivateLink(host));
}
}
38 changes: 38 additions & 0 deletions src/test/java/net/snowflake/client/core/SFTrustManagerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ public void testBuildRetryURL() throws Exception {
SFTrustManager.resetOCSPResponseCacherServerURL(
"http://ocsp.snowflakecomputing.com:80/" + SFTrustManager.CACHE_FILE_NAME);
assertThat(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN, nullValue());

// default OCSP Cache server URL in specific domain without port
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;
SFTrustManager.resetOCSPResponseCacherServerURL(
"http://ocsp.snowflakecomputing.cn/" + SFTrustManager.CACHE_FILE_NAME);
assertThat(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN, nullValue());

// default OCSP Cache server URL in specific domain with port
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;
SFTrustManager.resetOCSPResponseCacherServerURL(
"http://ocsp.snowflakecomputing.cn:80/" + SFTrustManager.CACHE_FILE_NAME);
assertThat(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN, nullValue());
}

@Test
Expand All @@ -65,6 +77,14 @@ public void testBuildNewRetryURL() {
tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,
equalTo("https://ocspssd.snowflakecomputing.com/ocsp/retry"));

tManager.ocspCacheServer.resetOCSPResponseCacheServer("a1.snowflakecomputing.cn");
assertThat(
tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,
equalTo("https://ocspssd.snowflakecomputing.cn/ocsp/fetch"));
assertThat(
tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,
equalTo("https://ocspssd.snowflakecomputing.cn/ocsp/retry"));

tManager.ocspCacheServer.resetOCSPResponseCacheServer(
"a1-12345.global.snowflakecomputing.com");
assertThat(
Expand All @@ -74,6 +94,15 @@ public void testBuildNewRetryURL() {
tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,
equalTo("https://ocspssd-12345.global.snowflakecomputing.com/ocsp/retry"));

tManager.ocspCacheServer.resetOCSPResponseCacheServer(
"a1-12345.global.snowflakecomputing.cn");
assertThat(
tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,
equalTo("https://ocspssd-12345.global.snowflakecomputing.cn/ocsp/fetch"));
assertThat(
tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,
equalTo("https://ocspssd-12345.global.snowflakecomputing.cn/ocsp/retry"));

tManager.ocspCacheServer.resetOCSPResponseCacheServer("okta.snowflake.com");
assertThat(
tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,
Expand All @@ -90,6 +119,15 @@ public void testBuildNewRetryURL() {
assertThat(
tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,
equalTo("https://ocspssd.us-east-1.privatelink.snowflakecomputing.com/ocsp/retry"));

tManager.ocspCacheServer.resetOCSPResponseCacheServer(
"a1.us-east-1.privatelink.snowflakecomputing.cn");
assertThat(
tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,
equalTo("https://ocspssd.us-east-1.privatelink.snowflakecomputing.cn/ocsp/fetch"));
assertThat(
tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,
equalTo("https://ocspssd.us-east-1.privatelink.snowflakecomputing.cn/ocsp/retry"));
} finally {
System.clearProperty("net.snowflake.jdbc.ocsp_activate_new_endpoint");
}
Expand Down
39 changes: 39 additions & 0 deletions src/test/java/net/snowflake/client/core/SessionUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static org.junit.Assert.assertTrue;

import com.fasterxml.jackson.databind.node.BooleanNode;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
Expand Down Expand Up @@ -129,4 +130,42 @@ public void testIsLoginRequestInvalidURIPath() {
}
}
}

@Test
public void shouldDerivePrivateLinkOcspCacheServerUrlBasedOnHost() throws IOException {
resetOcspConfiguration();

SessionUtil.resetOCSPUrlIfNecessary("https://test.privatelink.snowflakecomputing.com");
assertEquals(
"http://ocsp.test.privatelink.snowflakecomputing.com/ocsp_response_cache.json",
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);
assertEquals(
"http://ocsp.test.privatelink.snowflakecomputing.com/retry/%s/%s",
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN);

resetOcspConfiguration();

SessionUtil.resetOCSPUrlIfNecessary("https://test.privatelink.snowflakecomputing.cn");
assertEquals(
"http://ocsp.test.privatelink.snowflakecomputing.cn/ocsp_response_cache.json",
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);
assertEquals(
"http://ocsp.test.privatelink.snowflakecomputing.cn/retry/%s/%s",
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN);

resetOcspConfiguration();

SessionUtil.resetOCSPUrlIfNecessary("https://test.privatelink.snowflakecomputing.xyz");
assertEquals(
"http://ocsp.test.privatelink.snowflakecomputing.xyz/ocsp_response_cache.json",
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);
assertEquals(
"http://ocsp.test.privatelink.snowflakecomputing.xyz/retry/%s/%s",
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN);
}

private void resetOcspConfiguration() {
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE = null;
SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;
}
}
Loading

0 comments on commit b9dcc47

Please sign in to comment.