4444 AuthByPlugin ,
4545 AuthByUsrPwdMfa ,
4646 AuthByWebBrowser ,
47+ AuthByWorkloadIdentity ,
4748 AuthNoAuth ,
4849)
4950from .auth .idtoken import AuthByIdToken
5556from .constants import (
5657 _CONNECTIVITY_ERR_MSG ,
5758 _DOMAIN_NAME_MAP ,
59+ ENV_VAR_EXPERIMENTAL_AUTHENTICATION ,
5860 ENV_VAR_PARTNER ,
5961 PARAMETER_AUTOCOMMIT ,
6062 PARAMETER_CLIENT_PREFETCH_THREADS ,
8789 ER_FAILED_TO_CONNECT_TO_DB ,
8890 ER_INVALID_BACKOFF_POLICY ,
8991 ER_INVALID_VALUE ,
92+ ER_INVALID_WIF_SETTINGS ,
9093 ER_NO_ACCOUNT_NAME ,
9194 ER_NO_NUMPY ,
9295 ER_NO_PASSWORD ,
104107 PROGRAMMATIC_ACCESS_TOKEN ,
105108 REQUEST_ID ,
106109 USR_PWD_MFA_AUTHENTICATOR ,
110+ WORKLOAD_IDENTITY_AUTHENTICATOR ,
107111 ReauthenticationRequest ,
108112 SnowflakeRestful ,
109113)
112116from .time_util import HeartBeatTimer , get_time_millis
113117from .url_util import extract_top_level_domain_from_hostname
114118from .util_text import construct_hostname , parse_account , split_statements
119+ from .wif_util import AttestationProvider
115120
116121DEFAULT_CLIENT_PREFETCH_THREADS = 4
117122MAX_CLIENT_PREFETCH_THREADS = 10
@@ -188,12 +193,14 @@ def _get_private_bytes_from_file(
188193 "private_key" : (None , (type (None ), bytes , str , RSAPrivateKey )),
189194 "private_key_file" : (None , (type (None ), str )),
190195 "private_key_file_pwd" : (None , (type (None ), str , bytes )),
191- "token" : (None , (type (None ), str )), # OAuth/JWT/PAT Token
196+ "token" : (None , (type (None ), str )), # OAuth/JWT/PAT/OIDC Token
192197 "token_file_path" : (
193198 None ,
194199 (type (None ), str , bytes ),
195- ), # OAuth/JWT/PAT Token file path
200+ ), # OAuth/JWT/PAT/OIDC Token file path
196201 "authenticator" : (DEFAULT_AUTHENTICATOR , (type (None ), str )),
202+ "workload_identity_provider" : (None , (type (None ), AttestationProvider )),
203+ "workload_identity_entra_resource" : (None , (type (None ), str )),
197204 "mfa_callback" : (None , (type (None ), Callable )),
198205 "password_callback" : (None , (type (None ), Callable )),
199206 "auth_class" : (None , (type (None ), AuthByPlugin )),
@@ -1140,6 +1147,29 @@ def __open_connection(self):
11401147 if not self ._token and self ._password :
11411148 self ._token = self ._password
11421149 self .auth_class = AuthByPAT (self ._token )
1150+ elif self ._authenticator == WORKLOAD_IDENTITY_AUTHENTICATOR :
1151+ if ENV_VAR_EXPERIMENTAL_AUTHENTICATION not in os .environ :
1152+ Error .errorhandler_wrapper (
1153+ self ,
1154+ None ,
1155+ ProgrammingError ,
1156+ {
1157+ "msg" : f"Please set the '{ ENV_VAR_EXPERIMENTAL_AUTHENTICATION } ' environment variable to use the '{ WORKLOAD_IDENTITY_AUTHENTICATOR } ' authenticator." ,
1158+ "errno" : ER_INVALID_WIF_SETTINGS ,
1159+ },
1160+ )
1161+ # Standardize the provider enum.
1162+ if self ._workload_identity_provider and isinstance (
1163+ self ._workload_identity_provider , str
1164+ ):
1165+ self ._workload_identity_provider = AttestationProvider .from_string (
1166+ self ._workload_identity_provider
1167+ )
1168+ self .auth_class = AuthByWorkloadIdentity (
1169+ provider = self ._workload_identity_provider ,
1170+ token = self ._token ,
1171+ entra_resource = self ._workload_identity_entra_resource ,
1172+ )
11431173 else :
11441174 # okta URL, e.g., https://<account>.okta.com/
11451175 self .auth_class = AuthByOkta (
@@ -1268,6 +1298,7 @@ def __config(self, **kwargs):
12681298 KEY_PAIR_AUTHENTICATOR ,
12691299 OAUTH_AUTHENTICATOR ,
12701300 USR_PWD_MFA_AUTHENTICATOR ,
1301+ WORKLOAD_IDENTITY_AUTHENTICATOR ,
12711302 ]:
12721303 self ._authenticator = auth_tmp
12731304
@@ -1278,14 +1309,18 @@ def __config(self, **kwargs):
12781309 self ._token = f .read ()
12791310
12801311 # Set of authenticators allowing empty user.
1281- empty_user_allowed_authenticators = {OAUTH_AUTHENTICATOR , NO_AUTH_AUTHENTICATOR }
1312+ empty_user_allowed_authenticators = {
1313+ OAUTH_AUTHENTICATOR ,
1314+ NO_AUTH_AUTHENTICATOR ,
1315+ WORKLOAD_IDENTITY_AUTHENTICATOR ,
1316+ }
12821317
12831318 if not (self ._master_token and self ._session_token ):
12841319 if (
12851320 not self .user
12861321 and self ._authenticator not in empty_user_allowed_authenticators
12871322 ):
1288- # OAuth and NoAuth Authentications does not require a username
1323+ # Some authenticators do not require a username
12891324 Error .errorhandler_wrapper (
12901325 self ,
12911326 None ,
@@ -1296,6 +1331,25 @@ def __config(self, **kwargs):
12961331 if self ._private_key or self ._private_key_file :
12971332 self ._authenticator = KEY_PAIR_AUTHENTICATOR
12981333
1334+ workload_identity_dependent_options = [
1335+ "workload_identity_provider" ,
1336+ "workload_identity_entra_resource" ,
1337+ ]
1338+ for dependent_option in workload_identity_dependent_options :
1339+ if (
1340+ self .__getattribute__ (f"_{ dependent_option } " ) is not None
1341+ and self ._authenticator != WORKLOAD_IDENTITY_AUTHENTICATOR
1342+ ):
1343+ Error .errorhandler_wrapper (
1344+ self ,
1345+ None ,
1346+ ProgrammingError ,
1347+ {
1348+ "msg" : f"{ dependent_option } was set but authenticator was not set to { WORKLOAD_IDENTITY_AUTHENTICATOR } " ,
1349+ "errno" : ER_INVALID_WIF_SETTINGS ,
1350+ },
1351+ )
1352+
12991353 if (
13001354 self .auth_class is None
13011355 and self ._authenticator
@@ -1304,6 +1358,7 @@ def __config(self, **kwargs):
13041358 OAUTH_AUTHENTICATOR ,
13051359 KEY_PAIR_AUTHENTICATOR ,
13061360 PROGRAMMATIC_ACCESS_TOKEN ,
1361+ WORKLOAD_IDENTITY_AUTHENTICATOR ,
13071362 )
13081363 and not self ._password
13091364 ):
0 commit comments