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-1881731] Add externalBrowser, oauth, okta, keypair automated authentication tests #1082

Merged
merged 44 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
688a1a7
init work, before review, without cicd
sfc-gh-pcyrek Jan 23, 2025
029a16d
after initial review
sfc-gh-pcyrek Jan 27, 2025
63c289f
jenkinsfile edit
sfc-gh-pcyrek Jan 27, 2025
98f1238
move params into jenkins
sfc-gh-pcyrek Jan 27, 2025
7c3a967
changes in jenkinsfile
sfc-gh-pcyrek Jan 27, 2025
2886a8b
jenkinsfile with waiting
sfc-gh-pcyrek Jan 27, 2025
fb75015
image name change jenkins
sfc-gh-pcyrek Jan 27, 2025
d1a1cdf
diagnostic jenkins
sfc-gh-pcyrek Jan 27, 2025
0e403f6
with agent
sfc-gh-pcyrek Jan 27, 2025
d3b9dab
without browser
sfc-gh-pcyrek Jan 27, 2025
d512986
no mismatched user
sfc-gh-pcyrek Jan 27, 2025
a90deca
only one test
sfc-gh-pcyrek Jan 28, 2025
698a726
adding stamps
sfc-gh-pcyrek Jan 28, 2025
37dfd04
s_logger
sfc-gh-pcyrek Jan 28, 2025
120d396
more params, edited jenkinsfile
sfc-gh-pcyrek Jan 28, 2025
97d9336
fixjenkins
sfc-gh-pcyrek Jan 28, 2025
e2d194b
cleanup jenkins
sfc-gh-pcyrek Jan 28, 2025
3e3bc21
without basetest
sfc-gh-pcyrek Jan 28, 2025
e95071f
without fixtures
sfc-gh-pcyrek Jan 28, 2025
4eece68
creating parameters.json
sfc-gh-pcyrek Jan 28, 2025
913e2ec
different test set
sfc-gh-pcyrek Jan 28, 2025
f924d25
silent sfbasetest
sfc-gh-pcyrek Jan 28, 2025
554477f
changing name space
sfc-gh-pcyrek Jan 28, 2025
e1f0794
oauth test
sfc-gh-pcyrek Jan 28, 2025
1ba357d
add externalbrowser
sfc-gh-pcyrek Jan 28, 2025
5489a9b
externalbrowser on
sfc-gh-pcyrek Jan 29, 2025
6c2b04f
more tests added
sfc-gh-pcyrek Jan 29, 2025
772c236
Merge branch 'master' into pcyrek/authentication-tests
sfc-gh-pcyrek Jan 29, 2025
45367d1
specify only .net 9
sfc-gh-pcyrek Jan 29, 2025
69dee96
ignore on ci import
sfc-gh-pcyrek Jan 29, 2025
bee0b3f
without parameters.json in docker
sfc-gh-pcyrek Jan 29, 2025
343dc82
test with different namespace
sfc-gh-pcyrek Jan 29, 2025
21acb6e
cleanup
sfc-gh-pcyrek Jan 29, 2025
6eb6251
comment oauth test
sfc-gh-pcyrek Jan 29, 2025
715d134
ignoreOnCI
sfc-gh-pcyrek Jan 29, 2025
3b54397
Merge branch 'master' into pcyrek/authentication-tests
sfc-gh-pcyrek Jan 29, 2025
ad5dcac
convert to connection string
sfc-gh-pcyrek Jan 29, 2025
ccb5b97
after review
sfc-gh-pcyrek Jan 30, 2025
092a07c
Merge branch 'master' into pcyrek/authentication-tests
sfc-gh-pcyrek Jan 30, 2025
5d8b334
review2
sfc-gh-pcyrek Jan 30, 2025
1d01960
review3
sfc-gh-pcyrek Jan 30, 2025
1aacad4
review4
sfc-gh-pcyrek Jan 30, 2025
793cd94
review6
sfc-gh-pcyrek Jan 30, 2025
b75825e
review7
sfc-gh-pcyrek Jan 30, 2025
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
46 changes: 30 additions & 16 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import groovy.json.JsonOutput


timestamps {
node('regular-memory-node') {
stage('checkout') {
Expand All @@ -13,8 +12,8 @@ timestamps {
stage('Build') {
withCredentials([
usernamePassword(credentialsId: '063fc85b-62a6-4181-9d72-873b43488411', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
string(credentialsId: 'a791118f-a1ea-46cd-b876-56da1b9bc71c',variable: 'NEXUS_PASSWORD')
]) {
string(credentialsId: 'a791118f-a1ea-46cd-b876-56da1b9bc71c', variable: 'NEXUS_PASSWORD')
]) {
sh '''\
|#!/bin/bash -e
|export GIT_BRANCH=${GIT_BRANCH}
Expand All @@ -23,7 +22,8 @@ timestamps {
'''.stripMargin()
}
}
params = [

def params = [
string(name: 'svn_revision', value: 'bptp-built'),
string(name: 'branch', value: 'main'),
string(name: 'client_git_commit', value: scmInfo.GIT_COMMIT),
Expand All @@ -32,23 +32,37 @@ timestamps {
string(name: 'parent_job', value: env.JOB_NAME),
string(name: 'parent_build_number', value: env.BUILD_NUMBER)
]

stage('Test') {
build job: 'RT-LanguageDotnet-PC',parameters: params
parallel(
'Test': {
stage('Test') {
build job: 'RT-LanguageDotnet-PC', parameters: params
}
},
'Test Authentication': {
stage('Test Authentication') {
withCredentials([
string(credentialsId: 'a791118f-a1ea-46cd-b876-56da1b9bc71c', variable: 'NEXUS_PASSWORD'),
string(credentialsId: 'sfctest0-parameters-secret', variable: 'PARAMETERS_SECRET')
]) {
sh '''\
|#!/bin/bash -e
|$WORKSPACE/ci/test_authentication.sh
'''.stripMargin()
}
}
}
)
}
}
}


pipeline {
agent { label 'regular-memory-node' }
options { timestamps() }
environment {
COMMIT_SHA_LONG = sh(returnStdout: true, script: "echo \$(git rev-parse " + "HEAD)").trim()

// environment variables for semgrep_agent (for findings / analytics page)
// remove .git at the end
// remove SCM URL + .git at the end

COMMIT_SHA_LONG = sh(returnStdout: true, script: "echo \$(git rev-parse HEAD)").trim()
BASELINE_BRANCH = "${env.CHANGE_TARGET}"
}
stages {
Expand All @@ -61,7 +75,7 @@ pipeline {
}

def wgetUpdateGithub(String state, String folder, String targetUrl, String seconds) {
def ghURL = "https://api.github.com/repos/snowflakedb/snowflake-connector-net/statuses/$COMMIT_SHA_LONG"
def data = JsonOutput.toJson([state: "${state}", context: "jenkins/${folder}",target_url: "${targetUrl}"])
sh "wget ${ghURL} --spider -q --header='Authorization: token $GIT_PASSWORD' --post-data='${data}'"
}
def ghURL = "https://api.github.com/repos/snowflakedb/snowflake-connector-net/statuses/$COMMIT_SHA_LONG"
def data = JsonOutput.toJson([state: "${state}", context: "jenkins/${folder}", target_url: "${targetUrl}"])
sh "wget ${ghURL} --spider -q --header='Authorization: token $GIT_PASSWORD' --post-data='${data}'"
}
157 changes: 157 additions & 0 deletions Snowflake.Data.Tests/AuthenticationTests/AuthConnectionString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using System.IO;
using System.Net;
using Snowflake.Data.Core;
using System.Net.Http;
using System.Security.Authentication;

namespace Snowflake.Data.AuthenticationTests

{
static class AuthConnectionString
{
public static readonly string SsoUser = Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_BROWSER_USER");
public static readonly string Host = Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_HOST");
public static readonly string SsoPassword = Environment.GetEnvironmentVariable("SNOWFLAKE_TEST_OKTA_PASS");

private static SFSessionProperties GetBaseConnectionParameters()
{
var properties = new SFSessionProperties()
{
{SFSessionProperty.HOST, Host },
{SFSessionProperty.PORT, Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_PORT") },
{SFSessionProperty.ROLE, Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_ROLE") },
{SFSessionProperty.ACCOUNT, Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_ACCOUNT") },
{SFSessionProperty.DB, Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_DATABASE") },
{SFSessionProperty.SCHEMA, Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_SCHEMA") },
{SFSessionProperty.WAREHOUSE, Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_WAREHOUSE") },
};
return properties;
}

public static SFSessionProperties GetExternalBrowserConnectionString()
{
var properties = GetBaseConnectionParameters();
properties.Add(SFSessionProperty.AUTHENTICATOR, "externalbrowser");
properties.Add(SFSessionProperty.USER, Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_BROWSER_USER"));
return properties;
}

public static SFSessionProperties GetOauthConnectionString(string token)
{
var properties = GetBaseConnectionParameters();
properties.Add(SFSessionProperty.AUTHENTICATOR, "OAUTH");
properties.Add(SFSessionProperty.USER, SsoUser);
properties.Add(SFSessionProperty.TOKEN, token);
return properties;
}

public static SFSessionProperties GetOktaConnectionString()
{
var properties = GetBaseConnectionParameters();
properties.Add(SFSessionProperty.AUTHENTICATOR, Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_OAUTH_URL"));
properties.Add(SFSessionProperty.USER, SsoUser);
properties.Add(SFSessionProperty.PASSWORD, SsoPassword);

return properties;
}

public static SFSessionProperties GetKeyPairFromFileContentParameters(string privateKey)
{

var properties = GetBaseConnectionParameters();
properties.Add(SFSessionProperty.AUTHENTICATOR, "snowflake_jwt");
properties.Add(SFSessionProperty.USER, SsoUser);
properties.Add(SFSessionProperty.PRIVATE_KEY, privateKey);

return properties;
}


public static SFSessionProperties GetKeyPairFromFilePathConnectionString(string privateKeyPath)
{

var properties = GetBaseConnectionParameters();
properties.Add(SFSessionProperty.AUTHENTICATOR, "snowflake_jwt");
properties.Add(SFSessionProperty.USER, AuthConnectionString.SsoUser);
properties.Add(SFSessionProperty.PRIVATE_KEY_FILE, privateKeyPath);
return properties;
}

public static string ConvertToConnectionString(SFSessionProperties properties)
{
StringBuilder connectionStringBuilder = new StringBuilder();

foreach (var property in properties)
{
connectionStringBuilder.Append($"{property.Key.ToString().ToLower()}={property.Value};");
}
return connectionStringBuilder.ToString();
}

public static string GetPrivateKeyContentForKeypairAuth(string fileLocation)
{
string filePath = Environment.GetEnvironmentVariable(fileLocation);
Assert.IsNotNull(filePath);
string pemKey = File.ReadAllText(Path.Combine("..", "..", "..", "..", filePath));
Assert.IsNotNull(pemKey, $"Failed to read file: {filePath}");
return pemKey;

}

public static string GetPrivateKeyPathForKeypairAuth(string relativeFileLocationEnvVariable)
{
string filePath = Environment.GetEnvironmentVariable(relativeFileLocationEnvVariable);
Assert.IsNotNull(filePath);
return Path.Combine("..", "..", "..", "..", filePath);
}

public static string GetOauthToken()
{
try
{
using (var client = new HttpClient(new HttpClientHandler
{
CheckCertificateRevocationList = true,
SslProtocols = SslProtocols.Tls12,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
UseProxy = false,
UseCookies = false
}))
{
var authUrl = Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_OAUTH_URL");
var clientId = Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_SECRET");
var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"));

client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials);

var values = new Dictionary<string, string>
{
{ "username", Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_OKTA_USER") },
{ "password", Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_OKTA_PASS") },
{ "grant_type", "password" },
{ "scope", "session:role:" + Environment.GetEnvironmentVariable("SNOWFLAKE_AUTH_TEST_ROLE") }
};

var content = new FormUrlEncodedContent(values);
var response = client.PostAsync(authUrl, content).Result;
response.EnsureSuccessStatusCode();

var fullResponse = response.Content.ReadAsStringAsync().Result;
var responseObject = JObject.Parse(fullResponse);
Assert.IsNotNull(responseObject["access_token"]);
return responseObject["access_token"].ToString();
}
}
catch (Exception e)
{
throw new Exception($"Failed to get OAuth token: {e.Message}");
}
}
}
}
140 changes: 140 additions & 0 deletions Snowflake.Data.Tests/AuthenticationTests/AuthTestHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System;
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
using System.Threading;
using System.Diagnostics;
using System.Data;
using NUnit.Framework;
using Snowflake.Data.Client;
using Snowflake.Data.Log;

namespace Snowflake.Data.AuthenticationTests
{

public class AuthTestHelper
{
private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<AuthTestHelper>();
private Exception _exception;
private readonly bool _runAuthTestsManually;

public AuthTestHelper()
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
{
string envVar = Environment.GetEnvironmentVariable("RUN_AUTH_TESTS_MANUALLY");
_runAuthTestsManually = bool.Parse(envVar ?? "true");
}

public void CleanBrowserProcess()
{
if (_runAuthTestsManually)
return;
try
{
StartNodeProcess("/externalbrowser/cleanBrowserProcesses.js", TimeSpan.FromSeconds(20));
}
catch (Exception e)
{
throw new Exception(e.ToString());
}
}

public void ConnectAndExecuteSimpleQuery(string connectionString)
{
try
{
using (IDbConnection conn = new SnowflakeDbConnection())
{
conn.ConnectionString = connectionString;

conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);

using (IDbCommand command = conn.CreateCommand())
{
command.CommandText = "SELECT 1";
var result = command.ExecuteScalar();
Assert.AreEqual("1", result.ToString());
s_logger.Info(result.ToString());
}
}
}
catch (SnowflakeDbException e)
{
_exception = e;
}
}

public Thread GetConnectAndExecuteSimpleQueryThread(string parameters)
{
return new Thread(() => ConnectAndExecuteSimpleQuery(parameters));
}

public Thread GetProvideCredentialsThread(string scenario, string login, string password)
{
return new Thread(() => ProvideCredentials(scenario, login, password));
}

public void VerifyExceptionIsNotThrown() {
Assert.That(_exception, Is.Null, "Unexpected exception thrown");
}

public void VerifyExceptionIsThrown(string error) {
Assert.That(_exception, Is.Not.Null, "Expected exception was not thrown");
Assert.That(_exception.Message, Does.Contain(error), "Unexpected exception message.");

}

public void ConnectAndProvideCredentials(Thread provideCredentialsThread, Thread connectThread)
{
if (_runAuthTestsManually)
{
connectThread.Start();
connectThread.Join();
}
else
{
provideCredentialsThread.Start();
connectThread.Start();
provideCredentialsThread.Join();
connectThread.Join();
}
}

private void StartNodeProcess(string path, TimeSpan timeout)
{
var startInfo = new ProcessStartInfo
{
FileName = "node",
Arguments = path,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (var process = new Process { StartInfo = startInfo })
{
process.Start();
if (!process.WaitForExit((int) timeout.TotalMilliseconds))
{
process.Kill();
throw new TimeoutException("The process did not complete in the allotted time.");
}
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

s_logger.Info("Output: " + output);
s_logger.Info("Error: " + error);
}
}

private void ProvideCredentials(string scenario, string login, string password)
{
try
{
StartNodeProcess($"/externalbrowser/provideBrowserCredentials.js {scenario} {login} {password}", TimeSpan.FromSeconds(15));
}
catch (Exception e)
{
_exception = e;
}
}
}
}

Loading
Loading