Skip to content

Commit 80c18ea

Browse files
[SNOW-1346233] Tests for authentication methods (external browser, oauth, okta, keypair) (#1264)
1 parent 220e36e commit 80c18ea

14 files changed

+498
-6
lines changed
Binary file not shown.
1.37 KB
Binary file not shown.
Binary file not shown.

Jenkinsfile

+20-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,26 @@ timestamps {
1818
string(name: 'parent_job', value: env.JOB_NAME),
1919
string(name: 'parent_build_number', value: env.BUILD_NUMBER)
2020
]
21-
stage('Test') {
22-
build job: 'RT-LanguageGo-PC',parameters: params
23-
}
21+
parallel(
22+
'Test': {
23+
stage('Test') {
24+
build job: 'RT-LanguageGo-PC', parameters: params
25+
}
26+
},
27+
'Test Authentication': {
28+
stage('Test Authentication') {
29+
withCredentials([
30+
string(credentialsId: 'a791118f-a1ea-46cd-b876-56da1b9bc71c', variable: 'NEXUS_PASSWORD'),
31+
string(credentialsId: 'sfctest0-parameters-secret', variable: 'PARAMETERS_SECRET')
32+
]) {
33+
sh '''\
34+
|#!/bin/bash -e
35+
|$WORKSPACE/ci/test_authentication.sh
36+
'''.stripMargin()
37+
}
38+
}
39+
}
40+
)
2441
}
2542
}
2643

auth_generic_test_methods_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package gosnowflake
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func getAuthTestConfigFromEnv() (*Config, error) {
9+
return GetConfigFromEnv([]*ConfigParam{
10+
{Name: "Account", EnvName: "SNOWFLAKE_TEST_ACCOUNT", FailOnMissing: true},
11+
{Name: "User", EnvName: "SNOWFLAKE_AUTH_TEST_OKTA_USER", FailOnMissing: true},
12+
{Name: "Password", EnvName: "SNOWFLAKE_AUTH_TEST_OKTA_PASS", FailOnMissing: true},
13+
{Name: "Host", EnvName: "SNOWFLAKE_TEST_HOST", FailOnMissing: false},
14+
{Name: "Port", EnvName: "SNOWFLAKE_TEST_PORT", FailOnMissing: false},
15+
{Name: "Protocol", EnvName: "SNOWFLAKE_AUTH_TEST_PROTOCOL", FailOnMissing: false},
16+
{Name: "Role", EnvName: "SNOWFLAKE_TEST_ROLE", FailOnMissing: false},
17+
})
18+
}
19+
20+
func getAuthTestsConfig(t *testing.T, authMethod AuthType) (*Config, error) {
21+
cfg, err := getAuthTestConfigFromEnv()
22+
assertNilF(t, err, fmt.Sprintf("failed to get config: %v", err))
23+
24+
cfg.Authenticator = authMethod
25+
26+
return cfg, nil
27+
}

auth_with_external_browser_test.go

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package gosnowflake
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"fmt"
8+
"log"
9+
"os/exec"
10+
"sync"
11+
"testing"
12+
"time"
13+
)
14+
15+
func TestExternalBrowserSuccessful(t *testing.T) {
16+
cfg := setupExternalBrowserTest(t)
17+
var wg sync.WaitGroup
18+
wg.Add(2)
19+
go func() {
20+
defer wg.Done()
21+
provideExternalBrowserCredentials(t, externalBrowserType.Success, cfg.User, cfg.Password)
22+
}()
23+
go func() {
24+
defer wg.Done()
25+
err := verifyConnectionToSnowflakeAuthTests(t, cfg)
26+
assertNilE(t, err, fmt.Sprintf("Connection failed due to %v", err))
27+
}()
28+
wg.Wait()
29+
}
30+
31+
func TestExternalBrowserFailed(t *testing.T) {
32+
cfg := setupExternalBrowserTest(t)
33+
cfg.ExternalBrowserTimeout = time.Duration(10) * time.Second
34+
var wg sync.WaitGroup
35+
wg.Add(2)
36+
go func() {
37+
defer wg.Done()
38+
provideExternalBrowserCredentials(t, externalBrowserType.Fail, "FakeAccount", "NotARealPassword")
39+
}()
40+
go func() {
41+
defer wg.Done()
42+
err := verifyConnectionToSnowflakeAuthTests(t, cfg)
43+
assertEqualE(t, err.Error(), "authentication timed out")
44+
}()
45+
wg.Wait()
46+
}
47+
48+
func TestExternalBrowserTimeout(t *testing.T) {
49+
cfg := setupExternalBrowserTest(t)
50+
cfg.ExternalBrowserTimeout = time.Duration(1) * time.Second
51+
var wg sync.WaitGroup
52+
wg.Add(2)
53+
go func() {
54+
defer wg.Done()
55+
provideExternalBrowserCredentials(t, externalBrowserType.Timeout, cfg.User, cfg.Password)
56+
}()
57+
go func() {
58+
defer wg.Done()
59+
err := verifyConnectionToSnowflakeAuthTests(t, cfg)
60+
assertEqualE(t, err.Error(), "authentication timed out")
61+
}()
62+
wg.Wait()
63+
}
64+
65+
func TestExternalBrowserMismatchUser(t *testing.T) {
66+
cfg := setupExternalBrowserTest(t)
67+
correctUsername := cfg.User
68+
cfg.User = "fakeAccount"
69+
var wg sync.WaitGroup
70+
71+
wg.Add(2)
72+
go func() {
73+
defer wg.Done()
74+
provideExternalBrowserCredentials(t, externalBrowserType.Success, correctUsername, cfg.Password)
75+
}()
76+
go func() {
77+
defer wg.Done()
78+
err := verifyConnectionToSnowflakeAuthTests(t, cfg)
79+
var snowflakeErr *SnowflakeError
80+
assertTrueF(t, errors.As(err, &snowflakeErr))
81+
assertEqualE(t, snowflakeErr.Number, 390191, fmt.Sprintf("Expected 390191, but got %v", snowflakeErr.Number))
82+
}()
83+
wg.Wait()
84+
}
85+
86+
func TestClientStoreCredentials(t *testing.T) {
87+
cfg := setupExternalBrowserTest(t)
88+
cfg.ClientStoreTemporaryCredential = 1
89+
cfg.ExternalBrowserTimeout = time.Duration(10) * time.Second
90+
91+
t.Run("Obtains the ID token from the server and saves it on the local storage", func(t *testing.T) {
92+
cleanupBrowserProcesses(t)
93+
var wg sync.WaitGroup
94+
wg.Add(2)
95+
go func() {
96+
defer wg.Done()
97+
provideExternalBrowserCredentials(t, externalBrowserType.Success, cfg.User, cfg.Password)
98+
}()
99+
go func() {
100+
defer wg.Done()
101+
err := verifyConnectionToSnowflakeAuthTests(t, cfg)
102+
assertNilE(t, err, fmt.Sprintf("Connection failed: err %v", err))
103+
}()
104+
wg.Wait()
105+
})
106+
107+
t.Run("Verify validation of ID token if option enabled", func(t *testing.T) {
108+
cleanupBrowserProcesses(t)
109+
cfg.ClientStoreTemporaryCredential = 1
110+
db := getDbHandlerFromConfig(t, cfg)
111+
conn, err := db.Conn(context.Background())
112+
assertNilE(t, err, fmt.Sprintf("Failed to connect to Snowflake. err: %v", err))
113+
defer conn.Close()
114+
rows, err := conn.QueryContext(context.Background(), "SELECT 1")
115+
assertNilE(t, err, fmt.Sprintf("Failed to run a query. err: %v", err))
116+
rows.Close()
117+
})
118+
119+
t.Run("Verify validation of IDToken if option disabled", func(t *testing.T) {
120+
cleanupBrowserProcesses(t)
121+
cfg.ClientStoreTemporaryCredential = 0
122+
db := getDbHandlerFromConfig(t, cfg)
123+
_, err := db.Conn(context.Background())
124+
assertEqualE(t, err.Error(), "authentication timed out", fmt.Sprintf("Expected timeout, but got %v", err))
125+
})
126+
}
127+
128+
type ExternalBrowserProcessResult struct {
129+
Success string
130+
Fail string
131+
Timeout string
132+
}
133+
134+
var externalBrowserType = ExternalBrowserProcessResult{
135+
Success: "success",
136+
Fail: "fail",
137+
Timeout: "timeout",
138+
}
139+
140+
func cleanupBrowserProcesses(t *testing.T) {
141+
const cleanBrowserProcessesPath = "/externalbrowser/cleanBrowserProcesses.js"
142+
_, err := exec.Command("node", cleanBrowserProcessesPath).Output()
143+
assertNilE(t, err, fmt.Sprintf("failed to execute command: %v", err))
144+
}
145+
146+
func provideExternalBrowserCredentials(t *testing.T, ExternalBrowserProcess string, user string, password string) {
147+
const provideBrowserCredentialsPath = "/externalbrowser/provideBrowserCredentials.js"
148+
_, err := exec.Command("node", provideBrowserCredentialsPath, ExternalBrowserProcess, user, password).Output()
149+
assertNilE(t, err, fmt.Sprintf("failed to execute command: %v", err))
150+
}
151+
152+
func verifyConnectionToSnowflakeAuthTests(t *testing.T, cfg *Config) (err error) {
153+
dsn, err := DSN(cfg)
154+
assertNilE(t, err, "failed to create DSN from Config")
155+
156+
db, err := sql.Open("snowflake", dsn)
157+
assertNilE(t, err, "failed to open Snowflake DB connection")
158+
defer db.Close()
159+
160+
rows, err := db.Query("SELECT 1")
161+
if err != nil {
162+
log.Printf("failed to run a query. 'SELECT 1', err: %v", err)
163+
return err
164+
}
165+
166+
defer rows.Close()
167+
assertTrueE(t, rows.Next(), "failed to get result", "There were no results for query: ")
168+
169+
return err
170+
}
171+
172+
func setupExternalBrowserTest(t *testing.T) *Config {
173+
runOnlyOnDockerContainer(t, "Running only on Docker container")
174+
cleanupBrowserProcesses(t)
175+
cfg, err := getAuthTestsConfig(t, AuthTypeExternalBrowser)
176+
assertNilF(t, err, fmt.Sprintf("failed to get config: %v", err))
177+
return cfg
178+
}

auth_with_keypair_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package gosnowflake
2+
3+
import (
4+
"crypto/rsa"
5+
"errors"
6+
"fmt"
7+
"golang.org/x/crypto/ssh"
8+
"os"
9+
"testing"
10+
)
11+
12+
func TestKeypairSuccessful(t *testing.T) {
13+
cfg := setupKeyPairTest(t)
14+
cfg.PrivateKey = loadRsaPrivateKeyForKeyPair(t, "SNOWFLAKE_AUTH_TEST_PRIVATE_KEY_PATH")
15+
16+
err := verifyConnectionToSnowflakeAuthTests(t, cfg)
17+
assertNilE(t, err, fmt.Sprintf("failed to connect. err: %v", err))
18+
}
19+
20+
func TestKeypairInvalidKey(t *testing.T) {
21+
cfg := setupKeyPairTest(t)
22+
cfg.PrivateKey = loadRsaPrivateKeyForKeyPair(t, "SNOWFLAKE_AUTH_TEST_INVALID_PRIVATE_KEY_PATH")
23+
err := verifyConnectionToSnowflakeAuthTests(t, cfg)
24+
var snowflakeErr *SnowflakeError
25+
assertTrueF(t, errors.As(err, &snowflakeErr))
26+
assertEqualE(t, snowflakeErr.Number, 390144, fmt.Sprintf("Expected 390144, but got %v", snowflakeErr.Number))
27+
}
28+
29+
func setupKeyPairTest(t *testing.T) *Config {
30+
runOnlyOnDockerContainer(t, "Running only on Docker container")
31+
32+
cfg, err := getAuthTestsConfig(t, AuthTypeJwt)
33+
assertEqualE(t, err, nil, fmt.Sprintf("failed to get config: %v", err))
34+
35+
return cfg
36+
}
37+
38+
func loadRsaPrivateKeyForKeyPair(t *testing.T, envName string) *rsa.PrivateKey {
39+
filePath, err := GetFromEnv(envName, true)
40+
assertNilF(t, err, fmt.Sprintf("failed to get env: %v", err))
41+
42+
bytes, err := os.ReadFile(filePath)
43+
assertNilF(t, err, fmt.Sprintf("failed to read file: %v", err))
44+
45+
key, err := ssh.ParseRawPrivateKey(bytes)
46+
assertNilF(t, err, fmt.Sprintf("failed to parse private key: %v", err))
47+
48+
return key.(*rsa.PrivateKey)
49+
}

auth_with_oauth_test.go

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package gosnowflake
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
"net/url"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func TestOauthSuccessful(t *testing.T) {
14+
cfg := setupOauthTest(t)
15+
token, err := getOauthTestToken(t, cfg)
16+
assertNilE(t, err, fmt.Sprintf("failed to get token. err: %v", err))
17+
cfg.Token = token
18+
err = verifyConnectionToSnowflakeAuthTests(t, cfg)
19+
assertNilE(t, err, fmt.Sprintf("failed to connect. err: %v", err))
20+
}
21+
22+
func TestOauthInvalidToken(t *testing.T) {
23+
cfg := setupOauthTest(t)
24+
cfg.Token = "invalid_token"
25+
26+
err := verifyConnectionToSnowflakeAuthTests(t, cfg)
27+
28+
var snowflakeErr *SnowflakeError
29+
assertTrueF(t, errors.As(err, &snowflakeErr))
30+
assertEqualE(t, snowflakeErr.Number, 390303, fmt.Sprintf("Expected 390303, but got %v", snowflakeErr.Number))
31+
}
32+
33+
func TestOauthMismatchedUser(t *testing.T) {
34+
cfg := setupOauthTest(t)
35+
token, err := getOauthTestToken(t, cfg)
36+
assertNilE(t, err, fmt.Sprintf("failed to get token. err: %v", err))
37+
cfg.Token = token
38+
cfg.User = "fakeaccount"
39+
40+
err = verifyConnectionToSnowflakeAuthTests(t, cfg)
41+
42+
var snowflakeErr *SnowflakeError
43+
assertTrueF(t, errors.As(err, &snowflakeErr))
44+
assertEqualE(t, snowflakeErr.Number, 390309, fmt.Sprintf("Expected 390309, but got %v", snowflakeErr.Number))
45+
}
46+
47+
func setupOauthTest(t *testing.T) *Config {
48+
runOnlyOnDockerContainer(t, "Running only on Docker container")
49+
50+
cfg, err := getAuthTestsConfig(t, AuthTypeOAuth)
51+
assertNilF(t, err, fmt.Sprintf("failed to connect. err: %v", err))
52+
53+
return cfg
54+
}
55+
56+
func getOauthTestToken(t *testing.T, cfg *Config) (string, error) {
57+
58+
client := &http.Client{}
59+
60+
authURL, err := GetFromEnv("SNOWFLAKE_AUTH_TEST_OAUTH_URL", true)
61+
assertNilF(t, err, "SNOWFLAKE_AUTH_TEST_OAUTH_URL is not set")
62+
63+
oauthClientID, err := GetFromEnv("SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_ID", true)
64+
assertNilF(t, err, "SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_ID is not set")
65+
66+
oauthClientSecret, err := GetFromEnv("SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_SECRET", true)
67+
assertNilF(t, err, "SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_SECRET is not set")
68+
69+
inputData := formData(cfg)
70+
71+
req, err := http.NewRequest("POST", authURL, strings.NewReader(inputData.Encode()))
72+
assertNilF(t, err, fmt.Sprintf("Request failed %v", err))
73+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
74+
req.SetBasicAuth(oauthClientID, oauthClientSecret)
75+
resp, err := client.Do(req)
76+
77+
assertNilF(t, err, fmt.Sprintf("Response failed %v", err))
78+
79+
if resp.StatusCode != http.StatusOK {
80+
return "", fmt.Errorf("failed to get access token, status code: %d", resp.StatusCode)
81+
}
82+
83+
defer resp.Body.Close()
84+
85+
var response OAuthTokenResponse
86+
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
87+
return "", fmt.Errorf("failed to decode response: %v", err)
88+
}
89+
90+
return response.Token, err
91+
}
92+
93+
func formData(cfg *Config) url.Values {
94+
data := url.Values{}
95+
data.Set("username", cfg.User)
96+
data.Set("password", cfg.Password)
97+
data.Set("grant_type", "password")
98+
data.Set("scope", fmt.Sprintf("session:role:%s", strings.ToLower(cfg.Role)))
99+
100+
return data
101+
102+
}
103+
104+
type OAuthTokenResponse struct {
105+
Type string `json:"token_type"`
106+
Expiration int `json:"expires_in"`
107+
Token string `json:"access_token"`
108+
Scope string `json:"scope"`
109+
}

0 commit comments

Comments
 (0)