Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNOW-363754 - Ability to turn off Certificate Revocation List validation #328

Merged
merged 2 commits into from
Jun 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,25 @@ Note: If the keyword or value contains an equal sign (=), you must precede the e
The following table lists all valid connection properties:
<br />

| Connection Property | Required | Comment |
|---------------------------|----------|-------------------------------------------------------------------------------|
| ACCOUNT | Yes | Account should not include region or cloud provider information. e.g. account should be XXX instead of XXX.us-east-1.|
| DB | No | |
| HOST | No | If no value is specified, the driver uses \<ACCOUNT\>.snowflakecomputing.com. However, if you are not in us-west deployment, or you want to use global url, HOST is required, e.g. XXX.us-east-1.snowflakecomputing.com, or XXX-jkabfvdjisoa778wqfgeruishafeuw89q.global.snowflakecomputing.com|
| PASSWORD | Depends | Required if AUTHENTICATOR is set to `snowflake` (the default value) or the URL for native SSO through Okta. Ignored for all the other authentication types.|
| ROLE | No | |
| SCHEMA | No | |
| USER | Yes | If AUTHENTICATOR is set to `externalbrowser` or the URL for native SSO through Okta, set this to the login name for your identity provider (IdP). |
| WAREHOUSE | No | |
| CONNECTION_TIMEOUT | No | Total timeout in seconds when connecting to Snowflake. Default to 900 seconds |
| AUTHENTICATOR | No | The method of authentication. Currently supports the following values: <br /> - snowflake (default): You must also set USER and PASSWORD. <br /> - [the URL for native SSO through Okta](https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-use.html#native-sso-okta-only): You must also set USER and PASSWORD. <br /> - [externalbrowser](https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-use.html#browser-based-sso): You must also set USER. <br /> - [snowflake_jwt](https://docs.snowflake.com/en/user-guide/key-pair-auth.html): You must also set PRIVATE_KEY_FILE or PRIVATE_KEY. <br /> - [oauth](https://docs.snowflake.com/en/user-guide/oauth.html): You must also set TOKEN.
|VALIDATE_DEFAULT_PARAMETERS| No | Whether DB, SCHEMA and WAREHOUSE should be verified when making connection. Default to be true. |
|PRIVATE_KEY_FILE |Depends |The path to the private key file to use for key-pair authentication. Must be used in combination with AUTHENTICATOR=snowflake_jwt|
|PRIVATE_KEY_PWD |No |The passphrase to use for decrypting the private key, if the key is encrypted.|
|PRIVATE_KEY |Depends |The private key to use for key-pair authentication. Must be used in combination with AUTHENTICATOR=snowflake_jwt. <br /> If the private key value includes any equal signs (=), make sure to replace each equal sign with two signs (==) to ensure that the connection string is parsed correctly.|
|TOKEN |Depends |The OAuth token to use for OAuth authentication. Must be used in combination with AUTHENTICATOR=oauth.|
| Connection Property | Required | Comment |
|----------------------------|----------|-------------------------------------------------------------------------------|
| ACCOUNT | Yes | Account should not include region or cloud provider information. e.g. account should be XXX instead of XXX.us-east-1.|
| DB | No | |
| HOST | No | If no value is specified, the driver uses \<ACCOUNT\>.snowflakecomputing.com. However, if you are not in us-west deployment, or you want to use global url, HOST is required, e.g. XXX.us-east-1.snowflakecomputing.com, or XXX-jkabfvdjisoa778wqfgeruishafeuw89q.global.snowflakecomputing.com|
| PASSWORD | Depends | Required if AUTHENTICATOR is set to `snowflake` (the default value) or the URL for native SSO through Okta. Ignored for all the other authentication types.|
| ROLE | No | |
| SCHEMA | No | |
| USER | Yes | If AUTHENTICATOR is set to `externalbrowser` or the URL for native SSO through Okta, set this to the login name for your identity provider (IdP). |
| WAREHOUSE | No | |
| CONNECTION_TIMEOUT | No | Total timeout in seconds when connecting to Snowflake. Default to 900 seconds |
| AUTHENTICATOR | No | The method of authentication. Currently supports the following values: <br /> - snowflake (default): You must also set USER and PASSWORD. <br /> - [the URL for native SSO through Okta](https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-use.html#native-sso-okta-only): You must also set USER and PASSWORD. <br /> - [externalbrowser](https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-use.html#browser-based-sso): You must also set USER. <br /> - [snowflake_jwt](https://docs.snowflake.com/en/user-guide/key-pair-auth.html): You must also set PRIVATE_KEY_FILE or PRIVATE_KEY. <br /> - [oauth](https://docs.snowflake.com/en/user-guide/oauth.html): You must also set TOKEN.
| VALIDATE_DEFAULT_PARAMETERS| No | Whether DB, SCHEMA and WAREHOUSE should be verified when making connection. Default to be true. |
| PRIVATE_KEY_FILE |Depends | The path to the private key file to use for key-pair authentication. Must be used in combination with AUTHENTICATOR=snowflake_jwt|
| PRIVATE_KEY_PWD |No | The passphrase to use for decrypting the private key, if the key is encrypted.|
| PRIVATE_KEY |Depends | The private key to use for key-pair authentication. Must be used in combination with AUTHENTICATOR=snowflake_jwt. <br /> If the private key value includes any equal signs (=), make sure to replace each equal sign with two signs (==) to ensure that the connection string is parsed correctly.|
| TOKEN |Depends | The OAuth token to use for OAuth authentication. Must be used in combination with AUTHENTICATOR=oauth.|
| INSECUREMODE |No | Set to true to disable the certificate revocation list check. Default is false.|


<br />

Expand Down
2 changes: 1 addition & 1 deletion Snowflake.Data.Tests/Mock/MockRetryUntilRestTimeout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
{
// Http timeout of 1ms to force retries
request.Properties[BaseRestRequest.HTTP_REQUEST_TIMEOUT_KEY] = TimeSpan.FromMilliseconds(1);
var response = await HttpUtil.getHttpClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead, linkedCts.Token).ConfigureAwait(false);
var response = await HttpUtil.getHttpClient(false).SendAsync(request, HttpCompletionOption.ResponseHeadersRead, linkedCts.Token).ConfigureAwait(false);
response.EnsureSuccessStatusCode();

return response;
Expand Down
47 changes: 47 additions & 0 deletions Snowflake.Data.Tests/SFConnectionIT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,53 @@ public void TestBasicConnection()
}
}

[Test]
public void TestCrlCheckSwitchConnection()
{
using (IDbConnection conn = new SnowflakeDbConnection())
{
conn.ConnectionString = ConnectionString + ";INSECUREMODE=true";
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);

}

using (IDbConnection conn = new SnowflakeDbConnection())
{
conn.ConnectionString = ConnectionString;
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);
}

using (IDbConnection conn = new SnowflakeDbConnection())
{
conn.ConnectionString = ConnectionString + ";INSECUREMODE=false";
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);
}

using (IDbConnection conn = new SnowflakeDbConnection())
{
conn.ConnectionString = ConnectionString;
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);
}

using (IDbConnection conn = new SnowflakeDbConnection())
{
conn.ConnectionString = ConnectionString + ";INSECUREMODE=false";
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);
}

using (IDbConnection conn = new SnowflakeDbConnection())
{
conn.ConnectionString = ConnectionString + ";INSECUREMODE=true";
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);
}
}

[Test]
public void TestConnectViaSecureString()
{
Expand Down
1 change: 1 addition & 0 deletions Snowflake.Data.Tests/SFSessionPropertyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public void TestValidConnectionString()
{ SFSessionProperty.PASSWORD, "123" },
{ SFSessionProperty.PORT, "443" },
{ SFSessionProperty.VALIDATE_DEFAULT_PARAMETERS, "true" },
{ SFSessionProperty.INSECUREMODE, "false" },
},
},
};
Expand Down
9 changes: 6 additions & 3 deletions Snowflake.Data/Core/Authenticator/IAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ internal abstract class BaseAuthenticator
// The session which created this authenticator.
protected SFSession session;

// The client environment properties
protected LoginRequestClientEnv ClientEnv = SFEnvironment.ClientEnv;

/// <summary>
/// The abstract base for all authenticators.
/// </summary>
Expand All @@ -62,6 +65,8 @@ public BaseAuthenticator(SFSession session, string authName)
{
this.session = session;
this.authName = authName;
// Update the value for insecureMode because it can be different for each session
ClientEnv.insecureMode = session.properties[SFSessionProperty.INSECUREMODE];
}

//// <see cref="IAuthenticator.AuthenticateAsync"/>
Expand Down Expand Up @@ -107,15 +112,13 @@ private SFRestRequest BuildLoginRequest()
accountName = session.properties[SFSessionProperty.ACCOUNT],
clientAppId = SFEnvironment.DriverName,
clientAppVersion = SFEnvironment.DriverVersion,
clientEnv = SFEnvironment.ClientEnv,
clientEnv = ClientEnv,
SessionParameters = session.ParameterMap,
Authenticator = authName,
};

SetSpecializedAuthenticatorData(ref data);

int connectionTimeoutSec = int.Parse(session.properties[SFSessionProperty.CONNECTION_TIMEOUT]);

return session.BuildTimeoutRestRequest(loginUrl, new LoginRequest() { data = data });
}
}
Expand Down
13 changes: 11 additions & 2 deletions Snowflake.Data/Core/Authenticator/OktaAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private SFRestRequest BuildAuthenticatorRestRequest()

private IdpTokenRestRequest BuildIdpTokenRestRequest(Uri tokenUrl)
{
return new IdpTokenRestRequest()
return new IdpTokenRestRequest(base.session.InsecureMode)
{
Url = tokenUrl,
RestTimeout = session.connectionTimeout,
Expand All @@ -151,7 +151,7 @@ private IdpTokenRestRequest BuildIdpTokenRestRequest(Uri tokenUrl)

private SAMLRestRequest BuildSAMLRestRequest(Uri ssoUrl, string onetimeToken)
{
return new SAMLRestRequest()
return new SAMLRestRequest(base.session.InsecureMode)
{
Url = ssoUrl,
RestTimeout = session.connectionTimeout,
Expand Down Expand Up @@ -227,6 +227,10 @@ internal class IdpTokenRestRequest : BaseRestRequest, IRestRequest

internal IdpTokenRequest JsonBody { get; set; }

internal IdpTokenRestRequest(bool insecureMode) : base(insecureMode)
{
}

HttpRequestMessage IRestRequest.ToRequestMessage(HttpMethod method)
{
HttpRequestMessage message = newMessage(method, Url);
Expand Down Expand Up @@ -257,6 +261,11 @@ class IdpTokenResponse
class SAMLRestRequest : BaseRestRequest, IRestRequest
{
internal string OnetimeToken { set; get; }

internal SAMLRestRequest(bool insecure) : base(insecure)
{
}

HttpRequestMessage IRestRequest.ToRequestMessage(HttpMethod method)
{
UriBuilder builder = new UriBuilder(Url);
Expand Down
3 changes: 2 additions & 1 deletion Snowflake.Data/Core/ChunkDownloaderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public static IChunkDownloader GetDownloader(QueryExecResponseData responseData,
responseData.chunks,
responseData.qrmk,
responseData.chunkHeaders,
cancellationToken);
cancellationToken,
resultSet.sfStatement.SfSession.InsecureMode);
default:
return new SFBlockingChunkDownloaderV3(responseData.rowType.Count,
responseData.chunks,
Expand Down
38 changes: 27 additions & 11 deletions Snowflake.Data/Core/HttpUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ namespace Snowflake.Data.Core
{
class HttpUtil
{
static private HttpClient httpClient = null;

private static readonly SFLogger logger = SFLoggerFactory.GetLogger<HttpUtil>();

static private HttpClient HttpClient = null;

static private HttpClient HttpClientNoCrlCheck = null;

static private CookieContainer cookieContainer = null;

Expand All @@ -41,35 +46,46 @@ static public void ClearCookies(Uri uri)
}
}


static public HttpClient getHttpClient()
static public HttpClient getHttpClient(bool insecureMode)
{
lock (httpClientInitLock)
{
HttpClient httpClient = insecureMode ? HttpClientNoCrlCheck : HttpClient;
if (httpClient == null)
{
initHttpClient();
httpClient = initHttpClient(!insecureMode);

if (insecureMode)
{
HttpClientNoCrlCheck = httpClient;
}
else
{
HttpClient = httpClient;
}
}
return httpClient;
}
}

static private void initHttpClient()
static private HttpClient initHttpClient(bool crlCheckEnabled)
{

HttpClientHandler httpHandler = new HttpClientHandler()
logger.Debug("Creating new http client handler with CheckCertificateRevocationList : " + crlCheckEnabled);
HttpClientHandler httpHandler = new HttpClientHandler()
{
// Verify no certificates have been revoked
CheckCertificateRevocationList = true,
CheckCertificateRevocationList = crlCheckEnabled,
// Enforce tls v1.2
SslProtocols = SslProtocols.Tls12,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
};
CookieContainer = cookieContainer = new CookieContainer()
};

HttpUtil.httpClient = new HttpClient(new RetryHandler(httpHandler));
var httpClient = new HttpClient(new RetryHandler(httpHandler));
// HttpClient has a default timeout of 100 000 ms, we don't want to interfere with our
// own connection and command timeout
HttpUtil.httpClient.Timeout = Timeout.InfiniteTimeSpan;
httpClient.Timeout = Timeout.InfiniteTimeSpan;
return httpClient;
}

/// <summary>
Expand Down
35 changes: 30 additions & 5 deletions Snowflake.Data/Core/RestRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ internal interface IRestRequest
{
HttpRequestMessage ToRequestMessage(HttpMethod method);
TimeSpan GetRestTimeout();

bool GetInsecureMode();
}

/// <summary>
Expand All @@ -30,15 +32,27 @@ internal abstract class BaseRestRequest : IRestRequest
public static int DEFAULT_REST_RETRY_MINUTE_TIMEOUT = 15;

internal Uri Url { get; set; }

/// <summary>
/// Timeout of the overall rest request
/// </summary>
internal TimeSpan RestTimeout { get; set; }

/// <summary>
/// Timeout for every single HTTP request
/// </summary>
internal TimeSpan HttpTimeout { get; set; }

/// <summary>
/// Timeout for every single HTTP request
/// </summary>
internal bool InsecureMode { get; set; }

public BaseRestRequest(bool insecureMode)
{
InsecureMode = insecureMode;
}

HttpRequestMessage IRestRequest.ToRequestMessage(HttpMethod method)
{
throw new NotImplementedException();
Expand All @@ -56,6 +70,11 @@ TimeSpan IRestRequest.GetRestTimeout()
{
return RestTimeout;
}

bool IRestRequest.GetInsecureMode()
{
return InsecureMode;
}
}

internal class S3DownloadRequest : BaseRestRequest, IRestRequest
Expand All @@ -66,11 +85,14 @@ internal class S3DownloadRequest : BaseRestRequest, IRestRequest

private const string SSE_C_AES = "AES256";


internal string qrmk { get; set; }

internal Dictionary<string, string> chunkHeaders { get; set; }

internal S3DownloadRequest(bool insecure) : base(insecure)
{
}

HttpRequestMessage IRestRequest.ToRequestMessage(HttpMethod method)
{
HttpRequestMessage message = newMessage(method, Url);
Expand Down Expand Up @@ -98,12 +120,12 @@ internal class SFRestRequest : BaseRestRequest, IRestRequest
private const string SF_AUTHORIZATION_HEADER = "Authorization";
private const string SF_SERVICE_NAME_HEADER = "X-Snowflake-Service";

internal SFRestRequest()
internal SFRestRequest(bool insecureMode) : base(insecureMode)
{
RestTimeout = TimeSpan.FromMinutes(DEFAULT_REST_RETRY_MINUTE_TIMEOUT);

// default each http request timeout to 16 seconds
HttpTimeout = TimeSpan.FromSeconds(16);
HttpTimeout = TimeSpan.FromSeconds(16);
}

internal Object jsonBody { get; set; }
Expand Down Expand Up @@ -244,10 +266,13 @@ class LoginRequestClientEnv
[JsonProperty(PropertyName = "NET_VERSION")]
internal string netVersion { get; set; }

[JsonProperty(PropertyName = "INSECURE_MODE")]
internal string insecureMode { get; set; }

public override string ToString()
{
return String.Format("{{ APPLICATION: {0}, OS_VERSION: {1}, NET_RUNTIME: {2}, NET_VERSION: {3} }}",
application, osVersion, netRuntime, netVersion);
return String.Format("{{ APPLICATION: {0}, OS_VERSION: {1}, NET_RUNTIME: {2}, NET_VERSION: {3}, INSECURE_MODE: {4} }}",
application, osVersion, netRuntime, netVersion, insecureMode);
}
}

Expand Down
Loading