Skip to content

Commit

Permalink
SNOW-938188 validation of session properties for protecting unauthori…
Browse files Browse the repository at this point in the history
…zed pool access
  • Loading branch information
sfc-gh-mhofman committed May 9, 2024
1 parent ca5efb5 commit 04633e2
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Snowflake.Data.Client;
using Snowflake.Data.Log;

namespace Snowflake.Data.Core.Authenticator
{
internal static class AuthenticationPropertiesValidator
{
private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<SFSessionProperties>();

public static void Validate(SFSessionProperties properties)
{
if (!properties.TryGetValue(SFSessionProperty.AUTHENTICATOR, out var authenticator))
{
s_logger.Error("Unspecified authenticator");
throw new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR);
}

bool valid;
var error = "";
switch (authenticator.ToLower())
{
case BasicAuthenticator.AUTH_NAME:
valid = !string.IsNullOrEmpty(properties[SFSessionProperty.USER]) &&
!string.IsNullOrEmpty(properties[SFSessionProperty.PASSWORD]);
if (!valid)
error = $"USER and PASSWORD should be provided for Authenticator={authenticator}";
break;
case ExternalBrowserAuthenticator.AUTH_NAME:
valid = true;
break;
case KeyPairAuthenticator.AUTH_NAME:
valid = !string.IsNullOrEmpty(properties[SFSessionProperty.PRIVATE_KEY]) ||
!string.IsNullOrEmpty(properties[SFSessionProperty.PRIVATE_KEY]);
if (!valid)
error = $"PRIVATE_KEY or PRIVATE_KEY should be provided for Authenticator={authenticator}";
break;
case OAuthAuthenticator.AUTH_NAME:
valid = !string.IsNullOrEmpty(properties[SFSessionProperty.TOKEN]);
if (!valid)
error = $"TOKEN should be provided for Authenticator={authenticator}";
break;
default:
if (authenticator.Contains(OktaAuthenticator.AUTH_NAME) &&
authenticator.StartsWith("https://"))
{
valid = !string.IsNullOrEmpty(properties[SFSessionProperty.USER]) &&
!string.IsNullOrEmpty(properties[SFSessionProperty.PASSWORD]);
if (!valid)
error = $"USER and PASSWORD should be provided for Authenticator={authenticator}";
}
else
throw new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, $"Unrecognized Authenticator={authenticator}");
break;
}

if (!valid)
{
s_logger.Error(error);
throw new SnowflakeDbException(SFError.INVALID_CONNECTION_STRING, $"Unrecognized Authenticator={authenticator}");
}
}
}
}
2 changes: 1 addition & 1 deletion Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Snowflake.Data.Core.Authenticator
{
class BasicAuthenticator : BaseAuthenticator, IAuthenticator
{
public static readonly string AUTH_NAME = "snowflake";
public const string AUTH_NAME = "snowflake";
private static readonly SFLogger logger = SFLoggerFactory.GetLogger<BasicAuthenticator>();

internal BasicAuthenticator(SFSession session) : base(session, AUTH_NAME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Snowflake.Data.Core.Authenticator
/// </summary>
class ExternalBrowserAuthenticator : BaseAuthenticator, IAuthenticator
{
public static readonly string AUTH_NAME = "externalbrowser";
public const string AUTH_NAME = "externalbrowser";
private static readonly SFLogger logger = SFLoggerFactory.GetLogger<ExternalBrowserAuthenticator>();
private static readonly string TOKEN_REQUEST_PREFIX = "?token=";
private static readonly byte[] SUCCESS_RESPONSE = System.Text.Encoding.UTF8.GetBytes(
Expand Down Expand Up @@ -87,7 +87,7 @@ await session.restRequester.PostAsync<AuthenticatorResponse>(
logger.Warn("Browser response timeout");
throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec);
}

httpListener.Stop();
}

Expand Down Expand Up @@ -134,7 +134,7 @@ void IAuthenticator.Authenticate()
logger.Warn("Browser response timeout");
throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec);
}

httpListener.Stop();
}

Expand All @@ -150,7 +150,7 @@ private void GetContextCallback(IAsyncResult result)
{
HttpListenerContext context = httpListener.EndGetContext(result);
HttpListenerRequest request = context.Request;

_samlResponseToken = ValidateAndExtractToken(request);
HttpListenerResponse response = context.Response;
try
Expand Down
26 changes: 11 additions & 15 deletions Snowflake.Data/Core/Authenticator/IAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Snowflake.Data.Core.Authenticator
internal interface IAuthenticator
{
/// <summary>
/// Process the authentication asynchronouly
/// Process the authentication asynchronously
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Expand Down Expand Up @@ -49,19 +49,19 @@ internal abstract class BaseAuthenticator
SFLoggerFactory.GetLogger<BaseAuthenticator>();

// The name of the authenticator.
protected string authName;
private string authName;

// The session which created this authenticator.
protected SFSession session;

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

/// <summary>
/// The abstract base for all authenticators.
/// </summary>
/// <param name="session">The session which created the authenticator.</param>
public BaseAuthenticator(SFSession session, string authName)
protected BaseAuthenticator(SFSession session, string authName)
{
this.session = session;
this.authName = authName;
Expand Down Expand Up @@ -104,7 +104,7 @@ protected void Login()
/// <summary>
/// Builds a simple login request. Each authenticator will fill the Data part with their
/// specialized information. The common Data attributes are already filled (clientAppId,
/// ClienAppVersion...).
/// ClientAppVersion...).
/// </summary>
/// <returns>A login request to send to the server.</returns>
private SFRestRequest BuildLoginRequest()
Expand All @@ -129,10 +129,10 @@ private SFRestRequest BuildLoginRequest()
}
}

/// <summary>
/// Authenticator Factory to build authenticators
/// </summary>
internal class AuthenticatorFactory
/// <summary>
/// Authenticator Factory to build authenticators
/// </summary>
internal class AuthenticatorFactory
{
private static readonly SFLogger logger = SFLoggerFactory.GetLogger<AuthenticatorFactory>();
/// <summary>
Expand Down Expand Up @@ -192,12 +192,8 @@ internal static IAuthenticator GetAuthenticator(SFSession session)
{
return new OktaAuthenticator(session, type);
}

var e = new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, type);

logger.Error("Unknown authenticator", e);

throw e;
logger.Error($"Unknown authenticator {type}");
throw new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, type);
}
}
}
26 changes: 13 additions & 13 deletions Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace Snowflake.Data.Core.Authenticator
class KeyPairAuthenticator : BaseAuthenticator, IAuthenticator
{
// The authenticator setting value to use to authenticate using key pair authentication.
public static readonly string AUTH_NAME = "snowflake_jwt";
public const string AUTH_NAME = "snowflake_jwt";

// The logger.
private static readonly SFLogger logger =
Expand Down Expand Up @@ -85,9 +85,9 @@ private string GenerateJwtToken()
{
logger.Info("Key-pair Authentication");

bool hasPkPath =
bool hasPkPath =
session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY_FILE, out var pkPath);
bool hasPkContent =
bool hasPkContent =
session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY, out var pkContent);
session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY_PWD, out var pkPwd);

Expand Down Expand Up @@ -152,31 +152,31 @@ private string GenerateJwtToken()
byte[] sha256Hash = SHA256Encoder.ComputeHash(publicKeyEncoded);
publicKeyFingerPrint = "SHA256:" + Convert.ToBase64String(sha256Hash);
}
// Generating the token

// Generating the token
var now = DateTime.UtcNow;
System.DateTime dtDateTime =
new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
long secondsSinceEpoch = (long)((now - dtDateTime).TotalSeconds);

/*
/*
* Payload content
* iss : $accountName.$userName.$pulicKeyFingerprint
* iss : $accountName.$userName.$publicKeyFingerprint
* sub : $accountName.$userName
* iat : $now
* exp : $now + LIFETIME
*
*
* Note : Lifetime = 120sec for Python impl, 60sec for Jdbc and Odbc
*/
String accountUser =
session.properties[SFSessionProperty.ACCOUNT].ToUpper() +
"." +
String accountUser =
session.properties[SFSessionProperty.ACCOUNT].ToUpper() +
"." +
session.properties[SFSessionProperty.USER].ToUpper();
String issuer = accountUser + "." + publicKeyFingerPrint;
var claims = new[] {
new Claim(
JwtRegisteredClaimNames.Iat,
secondsSinceEpoch.ToString(),
JwtRegisteredClaimNames.Iat,
secondsSinceEpoch.ToString(),
System.Security.Claims.ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Sub, accountUser),
};
Expand Down
2 changes: 1 addition & 1 deletion Snowflake.Data/Core/Authenticator/OAuthAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Snowflake.Data.Core.Authenticator
class OAuthAuthenticator : BaseAuthenticator, IAuthenticator
{
// The authenticator setting value to use to authenticate using key pair authentication.
public static readonly string AUTH_NAME = "oauth";
public const string AUTH_NAME = "oauth";

// The logger.
private static readonly SFLogger logger =
Expand Down
1 change: 1 addition & 0 deletions Snowflake.Data/Core/Authenticator/OktaAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace Snowflake.Data.Core.Authenticator
/// </summary>
class OktaAuthenticator : BaseAuthenticator, IAuthenticator
{
public const string AUTH_NAME = "okta";
private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<OktaAuthenticator>();

internal const string RetryCountHeader = "RetryCount";
Expand Down
2 changes: 2 additions & 0 deletions Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Snowflake.Data.Client;
using Snowflake.Data.Core.Authenticator;
using Snowflake.Data.Core.Session;
using Snowflake.Data.Core.Tools;
using Snowflake.Data.Log;
Expand Down Expand Up @@ -42,6 +43,7 @@ internal class SFSessionHttpClientProperties
public static SFSessionHttpClientProperties ExtractAndValidate(SFSessionProperties properties)
{
var extractedProperties = s_propertiesExtractor.ExtractProperties(properties);
AuthenticationPropertiesValidator.Validate(properties);
extractedProperties.CheckPropertiesAreValid();
return extractedProperties;
}
Expand Down
11 changes: 0 additions & 11 deletions Snowflake.Data/Core/Session/SessionPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ private static Tuple<ConnectionPoolConfig, string> ExtractConfig(string connecti
{
var properties = SFSessionProperties.ParseConnectionString(connectionString, password);
var extractedProperties = SFSessionHttpClientProperties.ExtractAndValidate(properties);
ValidatePasswordProvided(properties);
return Tuple.Create(extractedProperties.BuildConnectionPoolConfig(), properties.ConnectionStringWithoutSecrets);
}
catch (Exception exception)
Expand All @@ -119,16 +118,6 @@ private static Tuple<ConnectionPoolConfig, string> ExtractConfig(string connecti
}
}

private static void ValidatePasswordProvided(SFSessionProperties properties)
{
var isPasswordGiven = !string.IsNullOrEmpty(properties[SFSessionProperty.PASSWORD]);
if (!isPasswordGiven)
{
s_logger.Error($"To obtain a pool a password must to be given with a connection string or SecureString parameter");
throw new SnowflakeDbException(SFError.MISSING_CONNECTION_PROPERTY, "Could not provide the pool without the password");
}
}

internal SFSession GetSession(string connStr, SecureString password)
{
s_logger.Debug("SessionPool::GetSession" + PoolIdentification());
Expand Down

0 comments on commit 04633e2

Please sign in to comment.