diff --git a/src/main/java/net/snowflake/client/core/PrivateLinkDetector.java b/src/main/java/net/snowflake/client/core/PrivateLinkDetector.java new file mode 100644 index 000000000..8d4a01742 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/PrivateLinkDetector.java @@ -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."); + } +} diff --git a/src/main/java/net/snowflake/client/core/SFTrustManager.java b/src/main/java/net/snowflake/client/core/SFTrustManager.java index 171f69e1b..740c70fe3 100644 --- a/src/main/java/net/snowflake/client/core/SFTrustManager.java +++ b/src/main/java/net/snowflake/client/core/SFTrustManager.java @@ -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; @@ -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 */ @@ -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 = @@ -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; @@ -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); } @@ -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) { @@ -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"); diff --git a/src/main/java/net/snowflake/client/core/SessionUtil.java b/src/main/java/net/snowflake/client/core/SessionUtil.java index ad55f79e8..63cdbe14c 100644 --- a/src/main/java/net/snowflake/client/core/SessionUtil.java +++ b/src/main/java/net/snowflake/client/core/SessionUtil.java @@ -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(); diff --git a/src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeS3Client.java b/src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeS3Client.java index 15110bfc8..5b405a15f 100644 --- a/src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeS3Client.java +++ b/src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeS3Client.java @@ -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 diff --git a/src/main/java/net/snowflake/client/jdbc/diagnostic/SnowflakeEndpoint.java b/src/main/java/net/snowflake/client/jdbc/diagnostic/SnowflakeEndpoint.java index 6cecb71d9..2a181c08a 100644 --- a/src/main/java/net/snowflake/client/jdbc/diagnostic/SnowflakeEndpoint.java +++ b/src/main/java/net/snowflake/client/jdbc/diagnostic/SnowflakeEndpoint.java @@ -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: @@ -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; } @@ -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 diff --git a/src/test/java/net/snowflake/client/core/OCSPCacheServerTest.java b/src/test/java/net/snowflake/client/core/OCSPCacheServerTest.java new file mode 100644 index 000000000..9a5af03b2 --- /dev/null +++ b/src/test/java/net/snowflake/client/core/OCSPCacheServerTest.java @@ -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); + } +} diff --git a/src/test/java/net/snowflake/client/core/PrivateLinkDetectorTest.java b/src/test/java/net/snowflake/client/core/PrivateLinkDetectorTest.java new file mode 100644 index 000000000..b3af68011 --- /dev/null +++ b/src/test/java/net/snowflake/client/core/PrivateLinkDetectorTest.java @@ -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)); + } +} diff --git a/src/test/java/net/snowflake/client/core/SFTrustManagerTest.java b/src/test/java/net/snowflake/client/core/SFTrustManagerTest.java index a4326d5bd..6a55b2cd4 100644 --- a/src/test/java/net/snowflake/client/core/SFTrustManagerTest.java +++ b/src/test/java/net/snowflake/client/core/SFTrustManagerTest.java @@ -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 @@ -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( @@ -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, @@ -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"); } diff --git a/src/test/java/net/snowflake/client/core/SessionUtilTest.java b/src/test/java/net/snowflake/client/core/SessionUtilTest.java index 0b5a542c1..cab5fb68f 100644 --- a/src/test/java/net/snowflake/client/core/SessionUtilTest.java +++ b/src/test/java/net/snowflake/client/core/SessionUtilTest.java @@ -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; @@ -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; + } } diff --git a/src/test/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeS3ClientTest.java b/src/test/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeS3ClientTest.java index d9019c8e7..3daddf3df 100644 --- a/src/test/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeS3ClientTest.java +++ b/src/test/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeS3ClientTest.java @@ -14,5 +14,7 @@ public void shouldDetermineDomainForRegion() { assertEquals("amazonaws.com", SnowflakeS3Client.getDomainSuffixForRegionalUrl("us-east-1")); assertEquals( "amazonaws.com.cn", SnowflakeS3Client.getDomainSuffixForRegionalUrl("cn-northwest-1")); + assertEquals( + "amazonaws.com.cn", SnowflakeS3Client.getDomainSuffixForRegionalUrl("CN-NORTHWEST-1")); } } diff --git a/src/test/java/net/snowflake/client/jdbc/diagnostic/SnowflakeEndpointTest.java b/src/test/java/net/snowflake/client/jdbc/diagnostic/SnowflakeEndpointTest.java new file mode 100644 index 000000000..a926a649e --- /dev/null +++ b/src/test/java/net/snowflake/client/jdbc/diagnostic/SnowflakeEndpointTest.java @@ -0,0 +1,28 @@ +package net.snowflake.client.jdbc.diagnostic; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; + +public class SnowflakeEndpointTest { + + @Test + public void shouldDetectPrivateLinkEndpoint() { + Map hostsToPrivateLinks = new HashMap<>(); + hostsToPrivateLinks.put("snowhouse.snowflakecomputing.com", false); + hostsToPrivateLinks.put("snowhouse.privatelink.snowflakecomputing.com", true); + hostsToPrivateLinks.put("snowhouse.snowflakecomputing.cn", false); + hostsToPrivateLinks.put("snowhouse.PRIVATELINK.snowflakecomputing.cn", true); + + hostsToPrivateLinks.forEach( + (host, expectedToBePrivateLink) -> { + SnowflakeEndpoint endpoint = new SnowflakeEndpoint("SNOWFLAKE_DEPLOYMENT", host, 443); + assertEquals( + String.format("Expecting %s to be private link: %s", host, expectedToBePrivateLink), + expectedToBePrivateLink, + endpoint.isPrivateLink()); + }); + } +}