Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
c2c73e0
adding new AuthHttpServer class
sfc-gh-mkeller Jan 14, 2025
a6a4da6
unrelated simplifications
sfc-gh-mkeller Jan 14, 2025
1743562
adding new oauth code flow implementation
sfc-gh-mkeller Jan 14, 2025
4ac2a22
adding new TODO
sfc-gh-mkeller Jan 14, 2025
e4521a0
adding changelog entry
sfc-gh-mkeller Jan 14, 2025
e268a32
making oauth state random
sfc-gh-mkeller Jan 14, 2025
9d3d770
make client_secret optional
sfc-gh-mkeller Jan 15, 2025
c5aaef9
make state check failure fail auth
sfc-gh-mkeller Jan 15, 2025
4c67632
switch to cryptographically-secure source
sfc-gh-mkeller Jan 16, 2025
3849bbd
redo connection parameters based on feedback
sfc-gh-mkeller Jan 24, 2025
c35342e
some string updates
sfc-gh-mkeller Jan 27, 2025
5feca11
add Authorization header to token request
sfc-gh-mkeller Jan 27, 2025
fd839cf
fix logging bug
sfc-gh-mkeller Jan 29, 2025
e8387b9
review feedback
sfc-gh-mkeller Jan 29, 2025
cf5f40c
rewording parameters based on discussion
sfc-gh-mkeller Jan 29, 2025
ae15f69
reworking the defaut oauth scope
sfc-gh-mkeller Jan 29, 2025
88bfe1b
adding port support into oauth code flow auth
sfc-gh-mkeller Jan 29, 2025
1c5ec61
do not log state
sfc-gh-mkeller Jan 31, 2025
4edc204
Update src/snowflake/connector/connection.py
sfc-gh-mkeller Feb 3, 2025
0f92c9b
SNOW-1917265: Add OAUTH_TYPE for authorization flow (#2174)
sfc-gh-pbulawa Feb 25, 2025
092c03a
SNOW-1825621 OAuth code flow PKCE support (#2137)
sfc-gh-mkeller Mar 4, 2025
3488e56
SNOW-1825495 Improve HTTP server targeted at serving OAuth redirects …
sfc-gh-mmishchenko Mar 10, 2025
0db27b4
SNOW-1960985 Wiremock tests for authorization_code (#2189)
sfc-gh-mkubik Mar 17, 2025
bd40157
fix _http_server formatting
sfc-gh-mkubik Mar 17, 2025
3b1ebe4
SNOW-1917302 refresh token and token cache support in OAuth authoriza…
sfc-gh-mmishchenko Mar 18, 2025
0a6c2f8
SNOW-1917232: Client credentials flow (#2218)
sfc-gh-pbulawa Mar 18, 2025
b4dba89
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Mar 18, 2025
ffd6950
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-jszczerbinski Mar 19, 2025
3d7c86e
SNOW-1825621: fix linter errors (#2223)
sfc-gh-jszczerbinski Mar 19, 2025
36f3e98
SNOW-1825624: Secure file token cache (#2214)
sfc-gh-jszczerbinski Mar 19, 2025
17706de
SNOW-1917302 Base Client Credentials on the same refresh token and to…
sfc-gh-mmishchenko Mar 20, 2025
d70c88f
SNOW-1825495 update future release notes
sfc-gh-mmishchenko Mar 20, 2025
2d94568
SNOW-1825495 make redirect url server use common with other drivers -…
sfc-gh-mmishchenko Mar 20, 2025
bbdc036
SNOW-1825495 fix http callback server unit tests
sfc-gh-mmishchenko Mar 20, 2025
42cc527
NO-SNOW make client secret required in oauth code (#2228)
sfc-gh-mkubik Mar 21, 2025
e5652ff
SNOW-1825621 added a retry logic for binding OAuth http server to a p…
sfc-gh-mmishchenko Mar 25, 2025
87aa25f
SNOW-1825621 missed https schema in default authorization/token endpo…
sfc-gh-mmishchenko Mar 25, 2025
ee3b0b3
SNOW-1825621 add external browser timeout as a parameter of OAuth Aut…
sfc-gh-mmishchenko Mar 25, 2025
4eae743
SNOW-1825621 missed authorization url in console prompts when a brows…
sfc-gh-mmishchenko Mar 25, 2025
1e96a1e
SNOW-1825495 reuse existing parameter to enable token cache (#2235)
sfc-gh-mmishchenko Mar 26, 2025
6154a25
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Mar 26, 2025
138297f
SNOW-1825495 use secret detector mask for variable logs (#2237)
sfc-gh-mmishchenko Mar 27, 2025
32ded2d
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Mar 27, 2025
44fba78
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Mar 31, 2025
15a7293
SNOW-2016989: security validation fixes (#2242)
sfc-gh-jszczerbinski Mar 31, 2025
350e9c1
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Mar 31, 2025
13d6c81
SNOW-1825495 address shutdown errors on already closed socket (#2244)
sfc-gh-mmishchenko Mar 31, 2025
601fe9b
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Apr 2, 2025
ab80b74
SNOW-1825495 some usability issues after pentesting (#2249)
sfc-gh-mmishchenko Apr 3, 2025
4659fd8
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Apr 7, 2025
3e79bd3
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Apr 8, 2025
c947e97
SNOW-1927346 Auth Python tests (#2224)
sfc-gh-pcyrek Apr 10, 2025
2500646
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Apr 10, 2025
c3cffb2
require SF_ENABLE_EXPERIMENTAL_AUTHENTICATION to be set to true (#2261)
sfc-gh-mkubik Apr 10, 2025
702607f
SNOW-2026797 Adding tests for PAT (#2265)
sfc-gh-pcyrek Apr 14, 2025
65cffd9
Merge branch 'main' into mkeller/SNOW-1825621/oauth-code-flow-support
sfc-gh-mmishchenko Apr 14, 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.
1 change: 1 addition & 0 deletions DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
- Dropped support for Python 3.8.
- Basic decimal floating-point type support.
- Added handling of PAT provided in `password` field.
- Added experimental support for OAuth authorization code and client credentials flows.
- Improved error message for client-side query cancellations due to timeouts.
- Added support of GCS regional endpoints.
- Added `gcs_use_virtual_endpoints` connection property that forces the usage of the virtual GCS usage. Thanks to this it should be possible to set up private DNS entry for the GCS endpoint. See more: https://cloud.google.com/storage/docs/request-endpoints#xml-api
Expand Down
57 changes: 37 additions & 20 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,46 @@ timestamps {
string(name: 'parent_job', value: env.JOB_NAME),
string(name: 'parent_build_number', value: env.BUILD_NUMBER)
]
stage('Test') {
try {
def commit_hash = "main" // default which we want to override
def bptp_tag = "bptp-stable"
def response = authenticatedGithubCall("https://api.github.com/repos/snowflakedb/snowflake/git/ref/tags/${bptp_tag}")
commit_hash = response.object.sha
// Append the bptp-stable commit sha to params
params += [string(name: 'svn_revision', value: commit_hash)]
} catch(Exception e) {
println("Exception computing commit hash from: ${response}")
parallel(
'Test': {
stage('Test') {
try {
def commit_hash = "main" // default which we want to override
def bptp_tag = "bptp-stable"
def response = authenticatedGithubCall("https://api.github.com/repos/snowflakedb/snowflake/git/ref/tags/${bptp_tag}")
commit_hash = response.object.sha
// Append the bptp-stable commit sha to params
params += [string(name: 'svn_revision', value: commit_hash)]
} catch(Exception e) {
println("Exception computing commit hash from: ${response}")
}
parallel (
'Test Python 39': { build job: 'RT-PyConnector39-PC',parameters: params},
'Test Python 310': { build job: 'RT-PyConnector310-PC',parameters: params},
'Test Python 311': { build job: 'RT-PyConnector311-PC',parameters: params},
'Test Python 312': { build job: 'RT-PyConnector312-PC',parameters: params},
'Test Python 313': { build job: 'RT-PyConnector313-PC',parameters: params},
'Test Python 39 OldDriver': { build job: 'RT-PyConnector39-OldDriver-PC',parameters: params},
'Test Python 39 FIPS': { build job: 'RT-FIPS-PyConnector39',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()
}
parallel (
'Test Python 39': { build job: 'RT-PyConnector39-PC',parameters: params},
'Test Python 310': { build job: 'RT-PyConnector310-PC',parameters: params},
'Test Python 311': { build job: 'RT-PyConnector311-PC',parameters: params},
'Test Python 312': { build job: 'RT-PyConnector312-PC',parameters: params},
'Test Python 313': { build job: 'RT-PyConnector313-PC',parameters: params},
'Test Python 39 OldDriver': { build job: 'RT-PyConnector39-OldDriver-PC',parameters: params},
'Test Python 39 FIPS': { build job: 'RT-FIPS-PyConnector39',parameters: params},
)
}
}
}
)
}
}


pipeline {
Expand Down
24 changes: 24 additions & 0 deletions ci/container/test_authentication.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash -e

set -o pipefail


export WORKSPACE=${WORKSPACE:-/mnt/workspace}
export SOURCE_ROOT=${SOURCE_ROOT:-/mnt/host}

MVNW_EXE=$SOURCE_ROOT/mvnw
AUTH_PARAMETER_FILE=./.github/workflows/parameters/private/parameters_aws_auth_tests.json
eval $(jq -r '.authtestparams | to_entries | map("export \(.key)=\(.value|tostring)")|.[]' $AUTH_PARAMETER_FILE)

export SNOWFLAKE_AUTH_TEST_PRIVATE_KEY_PATH=./.github/workflows/parameters/private/rsa_keys/rsa_key.p8
export SNOWFLAKE_AUTH_TEST_INVALID_PRIVATE_KEY_PATH=./.github/workflows/parameters/private/rsa_keys/rsa_key_invalid.p8

export SF_OCSP_TEST_MODE=true
export SF_ENABLE_EXPERIMENTAL_AUTHENTICATION=true
export RUN_AUTH_TESTS=true
export AUTHENTICATION_TESTS_ENV="docker"
export PYTHONPATH=$SOURCE_ROOT

python3 -m pip install --break-system-packages -e .

python3 -m pytest test/auth/*
27 changes: 27 additions & 0 deletions ci/test_authentication.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash -e

set -o pipefail


export THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export WORKSPACE=${WORKSPACE:-/tmp}

CI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [[ -n "$JENKINS_HOME" ]]; then
ROOT_DIR="$(cd "${CI_DIR}/.." && pwd)"
export WORKSPACE=${WORKSPACE:-/tmp}
echo "Use /sbin/ip"
IP_ADDR=$(/sbin/ip -4 addr show scope global dev eth0 | grep inet | awk '{print $2}' | cut -d / -f 1)

fi

gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" --output $THIS_DIR/../.github/workflows/parameters/private/parameters_aws_auth_tests.json "$THIS_DIR/../.github/workflows/parameters/private/parameters_aws_auth_tests.json.gpg"
gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" --output $THIS_DIR/../.github/workflows/parameters/private/rsa_keys/rsa_key.p8 "$THIS_DIR/../.github/workflows/parameters/private/rsa_keys/rsa_key.p8.gpg"
gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" --output $THIS_DIR/../.github/workflows/parameters/private/rsa_keys/rsa_key_invalid.p8 "$THIS_DIR/../.github/workflows/parameters/private/rsa_keys/rsa_key_invalid.p8.gpg"

docker run \
-v $(cd $THIS_DIR/.. && pwd):/mnt/host \
-v $WORKSPACE:/mnt/workspace \
--rm \
nexus.int.snowflakecomputing.com:8086/docker/snowdrivers-test-external-browser-python:1 \
"/mnt/host/ci/container/test_authentication.sh"
6 changes: 6 additions & 0 deletions src/snowflake/connector/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from .keypair import AuthByKeyPair
from .no_auth import AuthNoAuth
from .oauth import AuthByOAuth
from .oauth_code import AuthByOauthCode
from .oauth_credentials import AuthByOauthCredentials
from .okta import AuthByOkta
from .pat import AuthByPAT
from .usrpwdmfa import AuthByUsrPwdMfa
Expand All @@ -18,6 +20,8 @@
AuthByDefault,
AuthByKeyPair,
AuthByOAuth,
AuthByOauthCode,
AuthByOauthCredentials,
AuthByOkta,
AuthByUsrPwdMfa,
AuthByWebBrowser,
Expand All @@ -34,6 +38,8 @@
"AuthByKeyPair",
"AuthByPAT",
"AuthByOAuth",
"AuthByOauthCode",
"AuthByOauthCredentials",
"AuthByOkta",
"AuthByUsrPwdMfa",
"AuthByWebBrowser",
Expand Down
28 changes: 21 additions & 7 deletions src/snowflake/connector/auth/_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
ACCEPT_TYPE_APPLICATION_SNOWFLAKE,
CONTENT_TYPE_APPLICATION_JSON,
ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE,
OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE,
PYTHON_CONNECTOR_USER_AGENT,
ReauthenticationRequest,
)
Expand Down Expand Up @@ -86,7 +87,7 @@ class Auth:

def __init__(self, rest) -> None:
self._rest = rest
self.token_cache = TokenCache.make()
self._token_cache: TokenCache | None = None

@staticmethod
def base_auth_data(
Expand Down Expand Up @@ -350,7 +351,7 @@ def post_request_wrapper(self, url, headers, body) -> None:
# clear stored id_token if failed to connect because of id_token
# raise an exception for reauth without id_token
self._rest.id_token = None
self.delete_temporary_credential(
self._delete_temporary_credential(
self._rest._host, user, TokenType.ID_TOKEN
)
raise ReauthenticationRequest(
Expand All @@ -360,6 +361,14 @@ def post_request_wrapper(self, url, headers, body) -> None:
sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
)
)
elif errno == OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE:
raise ReauthenticationRequest(
ProgrammingError(
msg=ret["message"],
errno=int(errno),
sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
)
)

from . import AuthByKeyPair

Expand All @@ -374,7 +383,7 @@ def post_request_wrapper(self, url, headers, body) -> None:
from . import AuthByUsrPwdMfa

if isinstance(auth_instance, AuthByUsrPwdMfa):
self.delete_temporary_credential(
self._delete_temporary_credential(
self._rest._host, user, TokenType.MFA_TOKEN
)
Error.errorhandler_wrapper(
Expand Down Expand Up @@ -466,7 +475,7 @@ def _read_temporary_credential(
user: str,
cred_type: TokenType,
) -> str | None:
return self.token_cache.retrieve(TokenKey(host, user, cred_type))
return self.get_token_cache().retrieve(TokenKey(host, user, cred_type))

def read_temporary_credentials(
self,
Expand Down Expand Up @@ -500,7 +509,7 @@ def _write_temporary_credential(
"no credential is given when try to store temporary credential"
)
return
self.token_cache.store(TokenKey(host, user, cred_type), cred)
self.get_token_cache().store(TokenKey(host, user, cred_type), cred)

def write_temporary_credentials(
self,
Expand All @@ -524,10 +533,15 @@ def write_temporary_credentials(
host, user, TokenType.MFA_TOKEN, response["data"].get("mfaToken")
)

def delete_temporary_credential(
def _delete_temporary_credential(
self, host: str, user: str, cred_type: TokenType
) -> None:
self.token_cache.remove(TokenKey(host, user, cred_type))
self.get_token_cache().remove(TokenKey(host, user, cred_type))

def get_token_cache(self) -> TokenCache:
if self._token_cache is None:
self._token_cache = TokenCache.make()
return self._token_cache


def get_token_from_private_key(
Expand Down
Loading
Loading