Skip to content

Commit d99fdfa

Browse files
Snow 2324796 secrets api migration 1 (#3781)
1 parent ba2d66f commit d99fdfa

8 files changed

Lines changed: 465 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66

77
#### New Features
88

9+
- Added a new module `snowflake.snowpark.secrets` that provides Python wrappers for accessing Snowflake Secrets within Python UDFs and stored procedures that execute inside Snowflake.
10+
- `get_generic_secret_string`
11+
- `get_oauth_access_token`
12+
- `get_secret_type`
13+
- `get_username_password`
14+
- `get_cloud_provider_token`
15+
916
### Snowpark pandas API Updates
1017

1118
#### New Features

docs/source/snowpark/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Snowpark APIs
2020
udf
2121
udaf
2222
udtf
23+
secrets
2324
observability
2425
files
2526
catalog

docs/source/snowpark/secrets.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=============================
2+
Snowpark Secrets
3+
=============================
4+
5+
.. currentmodule:: snowflake.snowpark.secrets
6+
7+
.. rubric:: Classes
8+
9+
.. autosummary::
10+
:toctree: api/
11+
12+
UsernamePassword
13+
CloudProviderToken
14+
15+
.. rubric:: Functions
16+
17+
.. autosummary::
18+
:toctree: api/
19+
20+
get_generic_secret_string
21+
get_oauth_access_token
22+
get_secret_type
23+
get_username_password
24+
get_cloud_provider_token

src/snowflake/snowpark/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Contains core classes of Snowpark.
88
"""
99

10-
# types, udf, functions, exceptions still use its own modules
10+
# types, udf, functions, exceptions, secrets still use its own modules
1111

1212
__all__ = [
1313
"Column",

src/snowflake/snowpark/secrets.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#
2+
# Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved.
3+
#
4+
from snowflake.snowpark._internal.utils import publicapi
5+
6+
# Reference for Python API for Secret Access:
7+
# https://docs.snowflake.com/en/developer-guide/external-network-access/secret-api-reference#python-api-for-secret-access
8+
9+
10+
class UsernamePassword:
11+
def __init__(self, username, password) -> None:
12+
self.username = username
13+
self.password = password
14+
15+
16+
class CloudProviderToken:
17+
def __init__(self, id, key, token) -> None:
18+
self.access_key_id = id
19+
self.secret_access_key = key
20+
self.token = token
21+
22+
23+
@publicapi
24+
def get_generic_secret_string(secret_name: str) -> str:
25+
"""Get a generic token string from Snowflake.
26+
Note:
27+
Require a Snowflake environment with generic secret strings configured
28+
Returns:
29+
The secret value as a string.
30+
Raises:
31+
NotImplementedError: If the _snowflake module cannot be imported.
32+
"""
33+
try:
34+
import _snowflake
35+
36+
return _snowflake.get_generic_secret_string(secret_name)
37+
except ImportError:
38+
raise NotImplementedError(
39+
"Cannot import _snowflake module. Secret API is only supported on Snowflake server environment."
40+
)
41+
42+
43+
@publicapi
44+
def get_oauth_access_token(secret_name: str) -> str:
45+
"""Get an OAuth2 access token from Snowflake.
46+
Note:
47+
Require a Snowflake environment with OAuth secrets configured
48+
Returns:
49+
The OAuth2 access token as a string.
50+
Raises:
51+
NotImplementedError: If the _snowflake module cannot be imported.
52+
"""
53+
try:
54+
import _snowflake
55+
56+
return _snowflake.get_oauth_access_token(secret_name)
57+
except ImportError:
58+
raise NotImplementedError(
59+
"Cannot import _snowflake module. Secret API is only supported on Snowflake server environment."
60+
)
61+
62+
63+
@publicapi
64+
def get_secret_type(secret_name: str) -> str:
65+
"""Get the type of a secret from Snowflake.
66+
Note:
67+
Require a Snowflake environment with secrets configured
68+
Returns:
69+
The type of the secret as a string.
70+
Raises:
71+
NotImplementedError: If the _snowflake module cannot be imported.
72+
"""
73+
try:
74+
import _snowflake
75+
76+
return str(_snowflake.get_secret_type(secret_name))
77+
except ImportError:
78+
raise NotImplementedError(
79+
"Cannot import _snowflake module. Secret API is only supported on Snowflake server environment."
80+
)
81+
82+
83+
@publicapi
84+
def get_username_password(secret_name: str) -> UsernamePassword:
85+
"""Get a username and password secret from Snowflake.
86+
Note:
87+
Require a Snowflake environment with username/password secrets configured
88+
Returns:
89+
UsernamePassword: An object with attributes ``username`` and ``password``.
90+
Raises:
91+
NotImplementedError: If the _snowflake module cannot be imported.
92+
"""
93+
try:
94+
import _snowflake
95+
96+
secret_object = _snowflake.get_username_password(secret_name)
97+
return UsernamePassword(secret_object.username, secret_object.password)
98+
except ImportError:
99+
raise NotImplementedError(
100+
"Cannot import _snowflake module. Secret API is only supported on Snowflake server environment."
101+
)
102+
103+
104+
@publicapi
105+
def get_cloud_provider_token(secret_name: str) -> CloudProviderToken:
106+
"""Get a cloud provider token secret from Snowflake.
107+
Note:
108+
Require a Snowflake environment with cloud provider secrets configured
109+
Returns:
110+
CloudProviderToken: An object with attributes ``access_key_id``,
111+
``secret_access_key``, and ``token``.
112+
Raises:
113+
NotImplementedError: If the _snowflake module cannot be imported.
114+
"""
115+
try:
116+
import _snowflake
117+
118+
secret_object = _snowflake.get_cloud_provider_token(secret_name)
119+
return CloudProviderToken(
120+
secret_object.access_key_id,
121+
secret_object.secret_access_key,
122+
secret_object.token,
123+
)
124+
except ImportError:
125+
raise NotImplementedError(
126+
"Cannot import _snowflake module. Secret API is only supported on Snowflake server environment."
127+
)

tests/integ/conftest.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,16 @@ def print_help() -> None:
6262

6363

6464
def set_up_external_access_integration_resources(
65-
session, rule1, rule2, key1, key2, integration1, integration2
65+
session,
66+
rule1,
67+
rule2,
68+
rule3,
69+
key1,
70+
key2,
71+
key3,
72+
integration1,
73+
integration2,
74+
integration3,
6675
):
6776
try:
6877
# IMPORTANT SETUP NOTES: the test role needs to be granted the creation privilege
@@ -87,6 +96,14 @@ def set_up_external_access_integration_resources(
8796
).collect()
8897
session.sql(
8998
f"""
99+
CREATE IF NOT EXISTS NETWORK RULE {rule3}
100+
MODE = EGRESS
101+
TYPE = HOST_PORT
102+
VALUE_LIST = ('www.amazon.com');
103+
"""
104+
).collect()
105+
session.sql(
106+
f"""
90107
CREATE IF NOT EXISTS SECRET {key1}
91108
TYPE = GENERIC_STRING
92109
SECRET_STRING = 'replace-with-your-api-key';
@@ -101,6 +118,14 @@ def set_up_external_access_integration_resources(
101118
).collect()
102119
session.sql(
103120
f"""
121+
CREATE IF NOT EXISTS SECRET {key3}
122+
TYPE = PASSWORD
123+
USERNAME = 'replace-with-your-username';
124+
PASSWORD = 'replace-with-your-password';
125+
"""
126+
).collect()
127+
session.sql(
128+
f"""
104129
CREATE IF NOT EXISTS EXTERNAL ACCESS INTEGRATION {integration1}
105130
ALLOWED_NETWORK_RULES = ({rule1})
106131
ALLOWED_AUTHENTICATION_SECRETS = ({key1})
@@ -113,14 +138,25 @@ def set_up_external_access_integration_resources(
113138
ALLOWED_NETWORK_RULES = ({rule2})
114139
ALLOWED_AUTHENTICATION_SECRETS = ({key2})
115140
ENABLED = true;
141+
"""
142+
).collect()
143+
session.sql(
144+
f"""
145+
CREATE IF NOT EXISTS EXTERNAL ACCESS INTEGRATION {integration3}
146+
ALLOWED_NETWORK_RULES = ({rule3})
147+
ALLOWED_AUTHENTICATION_SECRETS = ({key3})
148+
ENABLED = true;
116149
"""
117150
).collect()
118151
CONNECTION_PARAMETERS["external_access_rule1"] = rule1
119152
CONNECTION_PARAMETERS["external_access_rule2"] = rule2
153+
CONNECTION_PARAMETERS["external_access_rule3"] = rule3
120154
CONNECTION_PARAMETERS["external_access_key1"] = key1
121155
CONNECTION_PARAMETERS["external_access_key2"] = key2
156+
CONNECTION_PARAMETERS["external_access_key3"] = key3
122157
CONNECTION_PARAMETERS["external_access_integration1"] = integration1
123158
CONNECTION_PARAMETERS["external_access_integration2"] = integration2
159+
CONNECTION_PARAMETERS["external_access_integration3"] = integration3
124160
except SnowparkSQLException:
125161
# GCP currently does not support external access integration
126162
# we can remove the exception once the integration is available on GCP
@@ -142,10 +178,13 @@ def set_up_external_access_integration_resources(
142178
def clean_up_external_access_integration_resources():
143179
CONNECTION_PARAMETERS.pop("external_access_rule1", None)
144180
CONNECTION_PARAMETERS.pop("external_access_rule2", None)
181+
CONNECTION_PARAMETERS.pop("external_access_rule3", None)
145182
CONNECTION_PARAMETERS.pop("external_access_key1", None)
146183
CONNECTION_PARAMETERS.pop("external_access_key2", None)
184+
CONNECTION_PARAMETERS.pop("external_access_key3", None)
147185
CONNECTION_PARAMETERS.pop("external_access_integration1", None)
148186
CONNECTION_PARAMETERS.pop("external_access_integration2", None)
187+
CONNECTION_PARAMETERS.pop("external_access_integration3", None)
149188

150189

151190
def set_up_dataframe_processor_parameters(
@@ -264,10 +303,13 @@ def session(
264303
set_ast_state(AstFlagSource.TEST, ast_enabled)
265304
rule1 = "snowpark_python_test_rule1"
266305
rule2 = "snowpark_python_test_rule2"
306+
rule3 = "snowpark_python_test_rule3"
267307
key1 = "snowpark_python_test_key1"
268308
key2 = "snowpark_python_test_key2"
309+
key3 = "snowpark_python_test_key3"
269310
integration1 = "snowpark_python_test_integration1"
270311
integration2 = "snowpark_python_test_integration2"
312+
integration3 = "snowpark_python_test_integration3"
271313

272314
session = (
273315
Session.builder.configs(db_parameters)
@@ -291,7 +333,16 @@ def session(
291333

292334
if (RUNNING_ON_GH or RUNNING_ON_JENKINS) and not local_testing_mode:
293335
set_up_external_access_integration_resources(
294-
session, rule1, rule2, key1, key2, integration1, integration2
336+
session,
337+
rule1,
338+
rule2,
339+
rule3,
340+
key1,
341+
key2,
342+
key3,
343+
integration1,
344+
integration2,
345+
integration3,
295346
)
296347

297348
if validate_ast:
@@ -321,10 +372,13 @@ def profiler_session(
321372
):
322373
rule1 = "snowpark_python_profiler_test_rule1"
323374
rule2 = "snowpark_python_profiler_test_rule2"
375+
rule3 = "snowpark_python_profiler_test_rule3"
324376
key1 = "snowpark_python_profiler_test_key1"
325377
key2 = "snowpark_python_profiler_test_key2"
378+
key3 = "snowpark_python_profiler_test_key3"
326379
integration1 = "snowpark_python_profiler_test_integration1"
327380
integration2 = "snowpark_python_profiler_test_integration2"
381+
integration3 = "snowpark_python_profiler_test_integration3"
328382
session = (
329383
Session.builder.configs(db_parameters)
330384
.config("local_testing", local_testing_mode)
@@ -334,7 +388,16 @@ def profiler_session(
334388
session._cte_optimization_enabled = cte_optimization_enabled
335389
if RUNNING_ON_GH and not local_testing_mode:
336390
set_up_external_access_integration_resources(
337-
session, rule1, rule2, key1, key2, integration1, integration2
391+
session,
392+
rule1,
393+
rule2,
394+
rule3,
395+
key1,
396+
key2,
397+
key3,
398+
integration1,
399+
integration2,
400+
integration3,
338401
)
339402
try:
340403
yield session

0 commit comments

Comments
 (0)