Skip to content

Commit a269f15

Browse files
SNOW-2226057: GH Actions moved to key-pair, old driver bump to 3.1.0 (#2432)
1 parent 99bf619 commit a269f15

23 files changed

+462
-699
lines changed

.github/workflows/build_test.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ jobs:
158158
run: |
159159
gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" \
160160
.github/workflows/parameters/public/parameters_${{ matrix.cloud-provider }}.py.gpg > test/parameters.py
161+
- name: Setup private key file
162+
shell: bash
163+
env:
164+
PYTHON_PRIVATE_KEY_SECRET: ${{ secrets.PYTHON_PRIVATE_KEY_SECRET }}
165+
run: |
166+
gpg --quiet --batch --yes --decrypt --passphrase="$PYTHON_PRIVATE_KEY_SECRET" \
167+
.github/workflows/parameters/public/rsa_keys/rsa_key_python_${{ matrix.cloud-provider }}.p8.gpg > test/rsa_key_python_${{ matrix.cloud-provider }}.p8
161168
- name: Download wheel(s)
162169
uses: actions/download-artifact@v4
163170
with:
@@ -229,6 +236,13 @@ jobs:
229236
run: |
230237
gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" \
231238
.github/workflows/parameters/public/parameters_${{ matrix.cloud-provider }}.py.gpg > test/parameters.py
239+
- name: Setup private key file
240+
shell: bash
241+
env:
242+
PYTHON_PRIVATE_KEY_SECRET: ${{ secrets.PYTHON_PRIVATE_KEY_SECRET }}
243+
run: |
244+
gpg --quiet --batch --yes --decrypt --passphrase="$PYTHON_PRIVATE_KEY_SECRET" \
245+
.github/workflows/parameters/public/rsa_keys/rsa_key_python_${{ matrix.cloud-provider }}.p8.gpg > test/rsa_key_python_${{ matrix.cloud-provider }}.p8
232246
- name: Upgrade setuptools, pip and wheel
233247
run: python -m pip install -U setuptools pip wheel
234248
- name: Install tox
@@ -290,6 +304,13 @@ jobs:
290304
run: |
291305
gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" \
292306
.github/workflows/parameters/public/parameters_${{ matrix.cloud-provider }}.py.gpg > test/parameters.py
307+
- name: Setup private key file
308+
shell: bash
309+
env:
310+
PYTHON_PRIVATE_KEY_SECRET: ${{ secrets.PYTHON_PRIVATE_KEY_SECRET }}
311+
run: |
312+
gpg --quiet --batch --yes --decrypt --passphrase="$PYTHON_PRIVATE_KEY_SECRET" \
313+
.github/workflows/parameters/public/rsa_keys/rsa_key_python_${{ matrix.cloud-provider }}.p8.gpg > test/rsa_key_python_${{ matrix.cloud-provider }}.p8
293314
- name: Download wheel(s)
294315
uses: actions/download-artifact@v4
295316
with:
@@ -343,6 +364,13 @@ jobs:
343364
run: |
344365
gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" \
345366
.github/workflows/parameters/public/parameters_${{ matrix.cloud-provider }}.py.gpg > test/parameters.py
367+
- name: Setup private key file
368+
shell: bash
369+
env:
370+
PYTHON_PRIVATE_KEY_SECRET: ${{ secrets.PYTHON_PRIVATE_KEY_SECRET }}
371+
run: |
372+
gpg --quiet --batch --yes --decrypt --passphrase="$PYTHON_PRIVATE_KEY_SECRET" \
373+
.github/workflows/parameters/public/rsa_keys/rsa_key_python_${{ matrix.cloud-provider }}.p8.gpg > test/rsa_key_python_${{ matrix.cloud-provider }}.p8
346374
- name: Download wheel(s)
347375
uses: actions/download-artifact@v4
348376
with:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

ci/test_fips_docker.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ docker run --network=host \
3131
-e cloud_provider \
3232
-e PYTEST_ADDOPTS \
3333
-e GITHUB_ACTIONS \
34+
-e JENKINS_HOME=${JENKINS_HOME:-false} \
3435
--mount type=bind,source="${CONNECTOR_DIR}",target=/home/user/snowflake-connector-python \
3536
${CONTAINER_NAME}:1.0 \
3637
/home/user/snowflake-connector-python/ci/test_fips.sh $1

test/integ/conftest.py

Lines changed: 146 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111

1212
import pytest
1313

14+
# Add cryptography imports for private key handling
15+
from cryptography.hazmat.backends import default_backend
16+
from cryptography.hazmat.primitives import serialization
17+
from cryptography.hazmat.primitives.serialization import (
18+
Encoding,
19+
NoEncryption,
20+
PrivateFormat,
21+
)
22+
1423
import snowflake.connector
1524
from snowflake.connector.compat import IS_WINDOWS
1625
from snowflake.connector.connection import DefaultConverterClass
@@ -28,8 +37,47 @@
2837
from snowflake.connector import SnowflakeConnection
2938

3039
RUNNING_ON_GH = os.getenv("GITHUB_ACTIONS") == "true"
40+
RUNNING_ON_JENKINS = os.getenv("JENKINS_HOME") not in (None, "false")
41+
RUNNING_OLD_DRIVER = os.getenv("TOX_ENV_NAME") == "olddriver"
3142
TEST_USING_VENDORED_ARROW = os.getenv("TEST_USING_VENDORED_ARROW") == "true"
3243

44+
45+
def _get_private_key_bytes_for_olddriver(private_key_file: str) -> bytes:
46+
"""Load private key file and convert to DER format bytes for olddriver compatibility.
47+
48+
The olddriver expects private keys in DER format as bytes.
49+
This function handles both PEM and DER input formats.
50+
"""
51+
with open(private_key_file, "rb") as key_file:
52+
key_data = key_file.read()
53+
54+
# Try to load as PEM first, then DER
55+
try:
56+
# Try PEM format first
57+
private_key = serialization.load_pem_private_key(
58+
key_data,
59+
password=None,
60+
backend=default_backend(),
61+
)
62+
except ValueError:
63+
try:
64+
# Try DER format
65+
private_key = serialization.load_der_private_key(
66+
key_data,
67+
password=None,
68+
backend=default_backend(),
69+
)
70+
except ValueError as e:
71+
raise ValueError(f"Could not load private key from {private_key_file}: {e}")
72+
73+
# Convert to DER format bytes as expected by olddriver
74+
return private_key.private_bytes(
75+
encoding=Encoding.DER,
76+
format=PrivateFormat.PKCS8,
77+
encryption_algorithm=NoEncryption(),
78+
)
79+
80+
3381
if not isinstance(CONNECTION_PARAMETERS["host"], str):
3482
raise Exception("default host is not a string in parameters.py")
3583
RUNNING_AGAINST_LOCAL_SNOWFLAKE = CONNECTION_PARAMETERS["host"].endswith("local")
@@ -72,16 +120,42 @@ def _get_worker_specific_schema():
72120
)
73121

74122

75-
DEFAULT_PARAMETERS: dict[str, Any] = {
76-
"account": "<account_name>",
77-
"user": "<user_name>",
78-
"password": "<password>",
79-
"database": "<database_name>",
80-
"schema": "<schema_name>",
81-
"protocol": "https",
82-
"host": "<host>",
83-
"port": "443",
84-
}
123+
if RUNNING_ON_JENKINS:
124+
DEFAULT_PARAMETERS: dict[str, Any] = {
125+
"account": "<account_name>",
126+
"user": "<user_name>",
127+
"password": "<password>",
128+
"database": "<database_name>",
129+
"schema": "<schema_name>",
130+
"protocol": "https",
131+
"host": "<host>",
132+
"port": "443",
133+
}
134+
else:
135+
if RUNNING_OLD_DRIVER:
136+
DEFAULT_PARAMETERS: dict[str, Any] = {
137+
"account": "<account_name>",
138+
"user": "<user_name>",
139+
"database": "<database_name>",
140+
"schema": "<schema_name>",
141+
"protocol": "https",
142+
"host": "<host>",
143+
"port": "443",
144+
"authenticator": "SNOWFLAKE_JWT",
145+
"private_key_file": "<private_key_file>",
146+
}
147+
else:
148+
DEFAULT_PARAMETERS: dict[str, Any] = {
149+
"account": "<account_name>",
150+
"user": "<user_name>",
151+
"database": "<database_name>",
152+
"schema": "<schema_name>",
153+
"protocol": "https",
154+
"host": "<host>",
155+
"port": "443",
156+
"authenticator": "<authenticator>",
157+
"private_key_file": "<private_key_file>",
158+
}
85159

86160

87161
def print_help() -> None:
@@ -91,9 +165,10 @@ def print_help() -> None:
91165
CONNECTION_PARAMETERS = {
92166
'account': 'testaccount',
93167
'user': 'user1',
94-
'password': 'test',
95168
'database': 'testdb',
96169
'schema': 'public',
170+
'authenticator': 'KEY_PAIR_AUTHENTICATOR',
171+
'private_key_file': '/path/to/private_key.p8',
97172
}
98173
"""
99174
)
@@ -196,15 +271,48 @@ def init_test_schema(db_parameters) -> Generator[None]:
196271
197272
This is automatically called per test session.
198273
"""
199-
connection_params = {
200-
"user": db_parameters["user"],
201-
"password": db_parameters["password"],
202-
"host": db_parameters["host"],
203-
"port": db_parameters["port"],
204-
"database": db_parameters["database"],
205-
"account": db_parameters["account"],
206-
"protocol": db_parameters["protocol"],
207-
}
274+
if RUNNING_ON_JENKINS:
275+
connection_params = {
276+
"user": db_parameters["user"],
277+
"password": db_parameters["password"],
278+
"host": db_parameters["host"],
279+
"port": db_parameters["port"],
280+
"database": db_parameters["database"],
281+
"account": db_parameters["account"],
282+
"protocol": db_parameters["protocol"],
283+
}
284+
else:
285+
connection_params = {
286+
"user": db_parameters["user"],
287+
"host": db_parameters["host"],
288+
"port": db_parameters["port"],
289+
"database": db_parameters["database"],
290+
"account": db_parameters["account"],
291+
"protocol": db_parameters["protocol"],
292+
}
293+
294+
# Handle private key authentication differently for old vs new driver
295+
if RUNNING_OLD_DRIVER:
296+
# Old driver expects private_key as bytes and SNOWFLAKE_JWT authenticator
297+
private_key_file = db_parameters.get("private_key_file")
298+
if private_key_file:
299+
private_key_bytes = _get_private_key_bytes_for_olddriver(
300+
private_key_file
301+
)
302+
connection_params.update(
303+
{
304+
"authenticator": "SNOWFLAKE_JWT",
305+
"private_key": private_key_bytes,
306+
}
307+
)
308+
else:
309+
# New driver expects private_key_file and KEY_PAIR_AUTHENTICATOR
310+
connection_params.update(
311+
{
312+
"authenticator": db_parameters["authenticator"],
313+
"private_key_file": db_parameters["private_key_file"],
314+
}
315+
)
208316

209317
# Role may be needed when running on preprod, but is not present on Jenkins jobs
210318
optional_role = db_parameters.get("role")
@@ -226,6 +334,24 @@ def create_connection(connection_name: str, **kwargs) -> SnowflakeConnection:
226334
"""
227335
ret = get_db_parameters(connection_name)
228336
ret.update(kwargs)
337+
338+
# Handle private key authentication differently for old vs new driver (only if not on Jenkins)
339+
if not RUNNING_ON_JENKINS and "private_key_file" in ret:
340+
if RUNNING_OLD_DRIVER:
341+
# Old driver (3.1.0) expects private_key as bytes and SNOWFLAKE_JWT authenticator
342+
private_key_file = ret.get("private_key_file")
343+
if (
344+
private_key_file and "private_key" not in ret
345+
): # Don't override if private_key already set
346+
private_key_bytes = _get_private_key_bytes_for_olddriver(
347+
private_key_file
348+
)
349+
ret["authenticator"] = "SNOWFLAKE_JWT"
350+
ret["private_key"] = private_key_bytes
351+
ret.pop(
352+
"private_key_file", None
353+
) # Remove private_key_file for old driver
354+
229355
connection = snowflake.connector.connect(**ret)
230356
return connection
231357

test/integ/pandas_it/test_pandas_tools.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ def test_write_pandas(
241241
with conn_cnx(
242242
user=db_parameters["user"],
243243
account=db_parameters["account"],
244-
password=db_parameters["password"],
245244
) as cnx:
246245
table_name = "driver_versions"
247246

0 commit comments

Comments
 (0)