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-1346233] Tests for authentication methods (external browser, oauth, okta, keypair) #1264

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ba5a0c3
Additional external browser tests
sfc-gh-pcyrek Dec 4, 2024
efc3ecc
add err handling, docker container change
sfc-gh-pcyrek Dec 4, 2024
7f31099
skip github actions check
sfc-gh-pcyrek Dec 5, 2024
65e7cdd
docker repo setup2
sfc-gh-pcyrek Dec 5, 2024
719f724
test default configuration
sfc-gh-pcyrek Dec 5, 2024
298a08a
test default configuration, only IPADDR
sfc-gh-pcyrek Dec 5, 2024
9d63c1d
test default configuration, only IPADDR2
sfc-gh-pcyrek Dec 5, 2024
29b8f2b
test default configuration3
sfc-gh-pcyrek Dec 5, 2024
5540296
adding okta tests, solution refactor
sfc-gh-pcyrek Dec 5, 2024
707c64b
adding oauth tests, small refactor
sfc-gh-pcyrek Dec 6, 2024
c77b081
linter fix
sfc-gh-pcyrek Dec 6, 2024
3053d2d
linter fix2
sfc-gh-pcyrek Dec 10, 2024
0c6d4b9
review 1
sfc-gh-pcyrek Dec 13, 2024
26d3ff2
fix usernames
sfc-gh-pcyrek Dec 13, 2024
ff49f15
logging for flaky tc
sfc-gh-pcyrek Dec 16, 2024
245ade2
after review session
sfc-gh-pcyrek Dec 17, 2024
bf866bf
after review session2
sfc-gh-pcyrek Dec 17, 2024
ca5c1d7
linters fix
sfc-gh-pcyrek Dec 17, 2024
833a75f
Merge branch 'master' into pcyrek-golang-external-browser-tests
sfc-gh-pcyrek Dec 17, 2024
e78d18f
review - round 2
sfc-gh-pcyrek Dec 18, 2024
361984d
Merge remote-tracking branch 'origin/pcyrek-golang-external-browser-t…
sfc-gh-pcyrek Dec 18, 2024
fd3dc3c
Merge branch 'master' into pcyrek-golang-external-browser-tests
sfc-gh-pcyrek Dec 18, 2024
a26313a
linters fix
sfc-gh-pcyrek Dec 18, 2024
03adfa1
linters fix2
sfc-gh-pcyrek Dec 18, 2024
b6ac5ba
Merge remote-tracking branch 'origin/pcyrek-golang-external-browser-t…
sfc-gh-pcyrek Dec 18, 2024
4959876
linters fix3
sfc-gh-pcyrek Dec 18, 2024
b1073b1
linterfix4
sfc-gh-pcyrek Dec 18, 2024
d2f480b
linterfix5
sfc-gh-pcyrek Dec 18, 2024
a52cb60
errorhandling
sfc-gh-pcyrek Dec 18, 2024
c3ef9e5
review - round 3
sfc-gh-pcyrek Dec 19, 2024
cf0a112
lintersfix1
sfc-gh-pcyrek Dec 19, 2024
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 added .github/workflows/rsa_keys/rsa_key.p8.gpg
Binary file not shown.
Binary file not shown.
23 changes: 20 additions & 3 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,26 @@ timestamps {
string(name: 'parent_job', value: env.JOB_NAME),
string(name: 'parent_build_number', value: env.BUILD_NUMBER)
]
stage('Test') {
build job: 'RT-LanguageGo-PC',parameters: params
}
parallel(
'Test': {
stage('Test') {
build job: 'RT-LanguageGo-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()
}
}
}
)
}
}

Expand Down
60 changes: 60 additions & 0 deletions auth_generic_test_methods_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package gosnowflake

import (
"context"
"database/sql"
"log"
)

func getAuthTestConfigFromEnv() (*Config, error) {
return GetConfigFromEnv([]*ConfigParam{
{Name: "Account", EnvName: "SNOWFLAKE_TEST_ACCOUNT", FailOnMissing: true},
{Name: "User", EnvName: "SNOWFLAKE_AUTH_TEST_OKTA_USER", FailOnMissing: true},
{Name: "Password", EnvName: "SNOWFLAKE_AUTH_TEST_OKTA_PASS", FailOnMissing: true},
{Name: "Host", EnvName: "SNOWFLAKE_TEST_HOST", FailOnMissing: false},
{Name: "Port", EnvName: "SNOWFLAKE_TEST_PORT", FailOnMissing: false},
{Name: "Protocol", EnvName: "SNOWFLAKE_AUTH_TEST_PROTOCOL", FailOnMissing: false},
{Name: "Role", EnvName: "SNOWFLAKE_TEST_ROLE", FailOnMissing: false},
})
}

func getAuthTestsConfig(authMethod AuthType) (*Config, error) {
cfg, err := getAuthTestConfigFromEnv()
if err != nil {
return nil, err
}

cfg.Authenticator = authMethod
cfg.DisableQueryContextCache = true
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved

return cfg, nil
}

func executeQuery(query string, dsn string) (rows *sql.Rows, err error) {
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
db, err := sql.Open("snowflake", dsn)
if err != nil {
log.Fatalf("failed to connect. %v, err: %v", dsn, err)
}
defer db.Close()

rows, err = db.Query(query)
return rows, err
}

func getDbHandler(cfg *Config) *sql.DB {
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
dsn, err := DSN(cfg)
if err != nil {
log.Fatalf("failed to create DSN from Config: %v, err: %v", cfg, err)
}

db, err := sql.Open("snowflake", dsn)
if err != nil {
log.Fatalf("failed to open database. %v, err: %v", dsn, err)
}
return db
}

func createConnection(db *sql.DB) (*sql.Conn, error) {
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
conn, err := db.Conn(context.Background())
return conn, err
}
189 changes: 189 additions & 0 deletions auth_with_external_browser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package gosnowflake

import (
"context"
"errors"
"fmt"
"log"
"os/exec"
"sync"
"testing"
"time"
)

func TestExternalBrowserSuccessful(t *testing.T) {
cfg := setupExternalBrowserTest(t)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
provideCredentials(externalBrowserType.Success, cfg.User, cfg.Password)
}()
go func() {
defer wg.Done()
err := connectToSnowflake(cfg, "SELECT 1", true)
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
assertNilF(t, err, fmt.Sprintf("Connection failed due to %v", err))
}()
wg.Wait()
}

func TestExternalBrowserFailed(t *testing.T) {
cfg := setupExternalBrowserTest(t)
cfg.ExternalBrowserTimeout = time.Duration(10) * time.Second
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
provideCredentials(externalBrowserType.Fail, "FakeAccount", "NotARealPassword")
}()
go func() {
defer wg.Done()
tOut := "authentication timed out"
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
err := connectToSnowflake(cfg, "SELECT 1", false)
assertTrueF(t, err.Error() == tOut, fmt.Sprintf("Expected %v, but got %v", tOut, err))
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
}()
wg.Wait()
}

func TestExternalBrowserTimeout(t *testing.T) {
cfg := setupExternalBrowserTest(t)
cfg.ExternalBrowserTimeout = time.Duration(1) * time.Second
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
provideCredentials(externalBrowserType.Timeout, cfg.User, cfg.Password)
}()
go func() {
defer wg.Done()
tOut := "authentication timed out"
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
err := connectToSnowflake(cfg, "SELECT 1", false)
assertTrueF(t, err.Error() == tOut, fmt.Sprintf("Expected %v, but got %v", tOut, err))
}()
wg.Wait()
}

func TestExternalBrowserMismatchUser(t *testing.T) {
cfg := setupExternalBrowserTest(t)
correctUsername := cfg.User
cfg.User = "fakeAccount"
var wg sync.WaitGroup

wg.Add(2)
go func() {
defer wg.Done()
provideCredentials(externalBrowserType.Success, correctUsername, cfg.Password)
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
}()
go func() {
defer wg.Done()
expectedErrorMsg := "390191 (08004): The user you were trying to authenticate " +
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
"as differs from the user currently logged in at the IDP."
err := connectToSnowflake(cfg, "SELECT 1", false)
assertTrueF(t, err.Error() == expectedErrorMsg, fmt.Sprintf("Expected %v, but got %v", expectedErrorMsg, err))
}()
wg.Wait()
}

func TestClientStoreCredentials(t *testing.T) {
cfg := setupExternalBrowserTest(t)
cfg.ClientStoreTemporaryCredential = 1
cfg.ExternalBrowserTimeout = time.Duration(10) * time.Second
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved

t.Run("Obtains the ID token from the server and saves it on the local storage", func(t *testing.T) {
cleanupBrowserProcesses()
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
provideCredentials(externalBrowserType.Success, cfg.User, cfg.Password)
}()
go func() {
defer wg.Done()
err := connectToSnowflake(cfg, "SELECT 1", true)
assertNilF(t, err, fmt.Sprintf("Connection failed: err %v", err))
}()
wg.Wait()
})

t.Run("Verify validation of ID token if option enabled", func(t *testing.T) {
cleanupBrowserProcesses()
cfg.ClientStoreTemporaryCredential = 1
conn, _ := createConnection(getDbHandler(cfg))
_, err := conn.QueryContext(context.Background(), "SELECT 1")
assertNilF(t, err, fmt.Sprintf("Failed to run a query. err: %v", err))
})

t.Run("Verify validation of IDToken if option disabled", func(t *testing.T) {
cleanupBrowserProcesses()
cfg.ClientStoreTemporaryCredential = 0
tOut := "authentication timed out"
_, err := createConnection(getDbHandler(cfg))
assertTrueF(t, err.Error() == tOut, fmt.Sprintf("Expected %v, but got %v", tOut, err))
})
}

type ExternalBrowserProcess struct {
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
Success string
Fail string
Timeout string
}

var externalBrowserType = ExternalBrowserProcess{
Success: "success",
Fail: "fail",
Timeout: "timeout",
}

func cleanupBrowserProcesses() {
const cleanBrowserProcessesPath = "/externalbrowser/cleanBrowserProcesses.js"
_, err := exec.Command("node", cleanBrowserProcessesPath).Output()
if err != nil {
log.Fatalf("failed to execute command: %v", err)
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
}
}

func provideCredentials(ExternalBrowserProcess string, user string, password string) {
const provideBrowserCredentialsPath = "/externalbrowser/provideBrowserCredentials.js"
_, err := exec.Command("node", provideBrowserCredentialsPath, ExternalBrowserProcess, user, password).Output()
if err != nil {
log.Fatalf("failed to execute command: %v", err)
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
}
}

func connectToSnowflake(cfg *Config, query string, isCatchException bool) (err error) {
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
dsn, err := DSN(cfg)
if err != nil {
log.Fatalf("failed to create DSN from Config: %v, err: %v", cfg, err)
}
rows, err := executeQuery(query, dsn)
if isCatchException && err != nil {
log.Fatalf("failed to run a query. %v, err: %v", query, err)
} else if err != nil {
return err
}
defer rows.Close()
var v int
var hasAnyRows bool
for rows.Next() {
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
hasAnyRows = true
err := rows.Scan(&v)
if isCatchException && err != nil {
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
log.Fatalf("failed to get result. err: %v", err)
}
}
if !hasAnyRows {
return errors.New("There were no results for query: ")
}
fmt.Printf("Congrats! You have successfully run '%v' with Snowflake DB! \n", query)
return err
}

func setupExternalBrowserTest(t *testing.T) *Config {
runOnlyOnDockerContainer(t, "Running only on Docker container")
cleanupBrowserProcesses()
cfg, err := getAuthTestsConfig(AuthTypeExternalBrowser)
if err != nil {
t.Fatalf("failed to get config: %v", err)
}
return cfg
}
53 changes: 53 additions & 0 deletions auth_with_keypair_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package gosnowflake

import (
"crypto/rsa"
"fmt"
"golang.org/x/crypto/ssh"
"os"
"strings"
"testing"
)

func TestKeypairSuccessful(t *testing.T) {
cfg := setupKeyPairTest(t)
cfg.PrivateKey = loadRsaPrivateKeyForKeyPair(t, "SNOWFLAKE_AUTH_TEST_PRIVATE_KEY_PATH")

err := connectToSnowflake(cfg, "SELECT 1", true)
assertNilF(t, err, fmt.Sprintf("failed to connect. err: %v", err))
}

func TestKeypairInvalidKey(t *testing.T) {
cfg := setupKeyPairTest(t)
cfg.PrivateKey = loadRsaPrivateKeyForKeyPair(t, "SNOWFLAKE_AUTH_TEST_INVALID_PRIVATE_KEY_PATH")
var errParts string
errMsg := "390144 (08004): JWT token is invalid."
err := connectToSnowflake(cfg, "SELECT 1", false)
if err != nil {
errParts = strings.Split(err.Error(), " [")[0]
}
assertTrueF(t, err != nil, "Expected error, but got nil")
sfc-gh-pcyrek marked this conversation as resolved.
Show resolved Hide resolved
assertTrueF(t, errParts == errMsg, fmt.Sprintf("Expected %v, but got %v", errMsg, errParts))
}

func setupKeyPairTest(t *testing.T) *Config {
runOnlyOnDockerContainer(t, "Running only on Docker container")

cfg, err := getAuthTestsConfig(AuthTypeJwt)
assertTrueF(t, err == nil, fmt.Sprintf("failed to get config: %v", err))

return cfg
}

func loadRsaPrivateKeyForKeyPair(t *testing.T, envName string) *rsa.PrivateKey {
filePath, err := GetFromEnv(envName, true)
assertNilF(t, err, fmt.Sprintf("failed to get env: %v", err))

bytes, err := os.ReadFile(filePath)
assertNilF(t, err, fmt.Sprintf("failed to read file: %v", err))

key, err := ssh.ParseRawPrivateKey(bytes)
assertNilF(t, err, fmt.Sprintf("failed to parse private key: %v", err))

return key.(*rsa.PrivateKey)
}
Loading
Loading