Skip to content

Commit a2b6437

Browse files
authored
Added support for private key content for set_auth() (#198)
2 parents f0be6a5 + ee903dc commit a2b6437

File tree

5 files changed

+158
-46
lines changed

5 files changed

+158
-46
lines changed

ads/common/auth.py

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import copy
88
import os
99
from dataclasses import dataclass
10-
from typing import Any, Callable, Dict, Optional
10+
from typing import Any, Callable, Dict, Optional, Union
1111

1212
import ads.telemetry
1313
import oci
@@ -45,7 +45,7 @@ class AuthState(metaclass=SingletonMeta):
4545
oci_cli_auth: str = None
4646
oci_config_path: str = None
4747
oci_key_profile: str = None
48-
oci_config: str = None
48+
oci_config: Dict = None
4949
oci_signer: Any = None
5050
oci_signer_callable: Callable = None
5151
oci_signer_kwargs: Dict = None
@@ -65,8 +65,6 @@ def __post_init__(self):
6565
"OCI_CONFIG_PROFILE", DEFAULT_PROFILE
6666
)
6767
self.oci_config = self.oci_config or {}
68-
self.oci_signer = self.oci_signer
69-
self.oci_signer_callable = self.oci_signer_callable
7068
self.oci_signer_kwargs = self.oci_signer_kwargs or {}
7169
self.oci_client_kwargs = self.oci_client_kwargs or {}
7270

@@ -82,15 +80,14 @@ def set_auth(
8280
client_kwargs: Optional[Dict] = {},
8381
) -> None:
8482
"""
85-
Save type of authentication, profile, config location, config (keypair identity) or signer, which will be used
86-
when actual creation of config or signer happens.
83+
Sets the default authentication type.
8784
8885
Parameters
8986
----------
9087
auth: Optional[str], default 'api_key'
9188
'api_key', 'resource_principal' or 'instance_principal'. Enable/disable resource principal identity,
9289
instance principal or keypair identity in a notebook session
93-
oci_config_location: Optional[str], default oci.config.DEFAULT_LOCATION, which is '~/.oci/config'
90+
oci_config_location: Optional[str], default '~/.oci/config'
9491
config file location
9592
profile: Optional[str], default is DEFAULT_PROFILE, which is 'DEFAULT'
9693
profile name for api keys config file
@@ -121,10 +118,46 @@ def set_auth(
121118
>>> other_config = oci.config.from_file("other_config_location", "OTHER_PROFILE") # Create non-default config
122119
>>> ads.set_auth(config=other_config) # Set api keys type of authentication based on provided config
123120
121+
>>> config={
122+
... user=ocid1.user.oc1..<unique_ID>,
123+
... fingerprint=<fingerprint>,
124+
... tenancy=ocid1.tenancy.oc1..<unique_ID>,
125+
... region=us-ashburn-1,
126+
... key_content=<private key content>,
127+
... }
128+
>>> ads.set_auth(config=config) # Set api key authentication using private key content based on provided config
129+
130+
>>> config={
131+
... user=ocid1.user.oc1..<unique_ID>,
132+
... fingerprint=<fingerprint>,
133+
... tenancy=ocid1.tenancy.oc1..<unique_ID>,
134+
... region=us-ashburn-1,
135+
... key_file=~/.oci/oci_api_key.pem,
136+
... }
137+
>>> ads.set_auth(config=config) # Set api key authentication using private key file location based on provided config
138+
124139
>>> ads.set_auth("resource_principal") # Set resource principal authentication
125140
126141
>>> ads.set_auth("instance_principal") # Set instance principal authentication
127142
143+
>>> singer = oci.signer.Signer(
144+
... user=ocid1.user.oc1..<unique_ID>,
145+
... fingerprint=<fingerprint>,
146+
... tenancy=ocid1.tenancy.oc1..<unique_ID>,
147+
... region=us-ashburn-1,
148+
... private_key_content=<private key content>,
149+
... )
150+
>>> ads.set_auth(singer=singer) # Set api keys authentication with private key content based on provided signer
151+
152+
>>> singer = oci.signer.Signer(
153+
... user=ocid1.user.oc1..<unique_ID>,
154+
... fingerprint=<fingerprint>,
155+
... tenancy=ocid1.tenancy.oc1..<unique_ID>,
156+
... region=us-ashburn-1,
157+
... private_key_file_location=<private key content>,
158+
... )
159+
>>> ads.set_auth(singer=singer) # Set api keys authentication with private key file location based on provided signer
160+
128161
>>> singer = oci.auth.signers.get_resource_principals_signer()
129162
>>> ads.auth.create_signer(config={}, singer=signer) # resource principals authentication dictionary created
130163
@@ -157,22 +190,15 @@ def set_auth(
157190

158191
auth_state.oci_config = config
159192
auth_state.oci_key_profile = profile
160-
if auth == AuthType.API_KEY and not signer and not signer_callable:
161-
if os.path.exists(os.path.expanduser(oci_config_location)):
162-
auth_state.oci_config_path = oci_config_location
163-
else:
164-
raise ValueError(
165-
f"{oci_config_location} path does not exist, please provide existing path to config file."
166-
)
167-
193+
auth_state.oci_config_path = oci_config_location
168194
auth_state.oci_signer = signer
169195
auth_state.oci_signer_callable = signer_callable
170196
auth_state.oci_signer_kwargs = signer_kwargs
171197
auth_state.oci_client_kwargs = client_kwargs
172198

173199

174200
def api_keys(
175-
oci_config: str = os.path.join(os.path.expanduser("~"), ".oci", "config"),
201+
oci_config: Union[str, Dict] = os.path.expanduser(DEFAULT_LOCATION),
176202
profile: str = DEFAULT_PROFILE,
177203
client_kwargs: Dict = None,
178204
) -> Dict:
@@ -182,8 +208,8 @@ def api_keys(
182208
183209
Parameters
184210
----------
185-
oci_config: Optional[str], default is $HOME/.oci/config
186-
OCI authentication config file location.
211+
oci_config: Optional[Union[str, Dict]], default is ~/.oci/config
212+
OCI authentication config file location or a dictionary with config attributes.
187213
profile: Optional[str], is DEFAULT_PROFILE, which is 'DEFAULT'
188214
Profile name to select from the config file.
189215
client_kwargs: Optional[Dict], default None
@@ -205,7 +231,10 @@ def api_keys(
205231
>>> oc.OCIClientFactory(**auth).object_storage # Creates Object storage client with timeout set to 6000 using API Key authentication
206232
"""
207233
signer_args = dict(
208-
oci_config_location=oci_config,
234+
oci_config=oci_config if isinstance(oci_config, Dict) else {},
235+
oci_config_location=oci_config
236+
if isinstance(oci_config, str)
237+
else os.path.expanduser(DEFAULT_LOCATION),
209238
oci_key_profile=profile,
210239
client_kwargs=client_kwargs,
211240
)
@@ -291,6 +320,24 @@ def create_signer(
291320
>>> config = oci.config.from_file("other_config_location", "OTHER_PROFILE")
292321
>>> auth = ads.auth.create_signer(config=config) # api_key type of authentication dictionary created based on provided config
293322
323+
>>> config={
324+
... user=ocid1.user.oc1..<unique_ID>,
325+
... fingerprint=<fingerprint>,
326+
... tenancy=ocid1.tenancy.oc1..<unique_ID>,
327+
... region=us-ashburn-1,
328+
... key_content=<private key content>,
329+
... }
330+
>>> auth = ads.auth.create_signer(config=config) # api_key type of authentication dictionary with private key content created based on provided config
331+
332+
>>> config={
333+
... user=ocid1.user.oc1..<unique_ID>,
334+
... fingerprint=<fingerprint>,
335+
... tenancy=ocid1.tenancy.oc1..<unique_ID>,
336+
... region=us-ashburn-1,
337+
... key_file=~/.oci/oci_api_key.pem,
338+
... }
339+
>>> auth = ads.auth.create_signer(config=config) # api_key type of authentication dictionary with private key file location created based on provided config
340+
294341
>>> singer = oci.auth.signers.get_resource_principals_signer()
295342
>>> auth = ads.auth.create_signer(config={}, signer=signer) # resource principals authentication dictionary created
296343
@@ -482,8 +529,8 @@ def create_signer(self) -> Dict:
482529
Signer constructed from the `oci_config` provided. If not 'oci_config', configuration will be
483530
constructed from 'oci_config_location' and 'oci_key_profile' in place.
484531
485-
Resturns
486-
--------
532+
Returns
533+
-------
487534
dict
488535
Contains keys - config, signer and client_kwargs.
489536
@@ -506,15 +553,18 @@ def create_signer(self) -> Dict:
506553
configuration = ads.telemetry.update_oci_client_config(
507554
oci.config.from_file(self.oci_config_location, self.oci_key_profile)
508555
)
556+
557+
oci.config.validate_config(configuration)
509558
logger.info(f"Using 'api_key' authentication.")
510559
return {
511560
"config": configuration,
512561
"signer": oci.signer.Signer(
513-
configuration["tenancy"],
514-
configuration["user"],
515-
configuration["fingerprint"],
516-
configuration["key_file"],
517-
configuration.get("pass_phrase"),
562+
tenancy=configuration["tenancy"],
563+
user=configuration["user"],
564+
fingerprint=configuration["fingerprint"],
565+
private_key_file_location=configuration.get("key_file"),
566+
pass_phrase= configuration.get("pass_phrase"),
567+
private_key_content=configuration.get("key_content")
518568
),
519569
"client_kwargs": self.client_kwargs,
520570
}
@@ -544,8 +594,8 @@ def create_signer(self) -> Dict:
544594
"""
545595
Creates Resource Principal signer with extra arguments necessary for creating clients.
546596
547-
Resturns
548-
--------
597+
Returns
598+
-------
549599
dict
550600
Contains keys - config, signer and client_kwargs.
551601
@@ -600,8 +650,8 @@ def create_signer(self) -> Dict:
600650
Signer instantiated from the `signer_callable` or if the `signer` provided is will be return by this method.
601651
If `signer_callable` or `signer` not provided new signer will be created in place.
602652
603-
Resturns
604-
--------
653+
Returns
654+
-------
605655
dict
606656
Contains keys - config, signer and client_kwargs.
607657

docs/source/user_guide/cli/authentication.rst

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ The ``~/.oci/config`` configuration allow for multiple configurations to be stor
8282
ads.set_auth("api_key") # default signer is set to API Keys
8383
ads.set_auth("api_key", profile = "TEST") # default signer is set to API Keys and to use TEST profile
8484
ads.set_auth("api_key", oci_config_location = "~/.test_oci/config") # default signer is set to API Keys and to use non-default oci_config_location
85+
private_key_content = """
86+
-----BEGIN RSA PRIVATE KEY-----
87+
MIIBIjANBgkqhkiG9w0BAQE...
88+
...
89+
-----END RSA PRIVATE KEY-----
90+
"""
91+
config = dict(
92+
user="ocid1.user.oc1..xxx",
93+
fingerprint="35:67:25:90:89:87:45:78:bf:4h:g5:13:16:32:4d:f4",
94+
tenancy="ocid1.tenancy.oc1..xxx",
95+
region="us-ashburn-1",
96+
key_content=private_key_content,
97+
)
98+
ads.set_auth(config = config) # default signer is set to API Keys with private key content
99+
85100
ads.set_auth("resource_principal") # default signer is set to resource principal authentication
86101
ads.set_auth("instance_principal") # default signer is set to instance principal authentication
87102
@@ -121,7 +136,7 @@ In the this example, the default authentication uses API keys specified with the
121136
os_auth = authutil.resource_principal() # use resource principal to as the preferred way to access object store
122137
123138
124-
More signers can be created using the ``create_signer()`` method. With the ``auth_type`` parameter set to ``instance_principal``, the method will return a signer that uses instance principals. For other signers there are ``signer`` or ``signer_callable`` parameters. Here are examples:
139+
More signers can be created using the ``create_signer()`` method. With the ``auth_type`` parameter set to ``instance_principal``, the method will return a signer that uses instance principals. For other signers there are ``signer``, ``signer_callable`` or ``signer_kwargs`` parameters. Here are examples:
125140

126141
.. code-block:: python
127142

0 commit comments

Comments
 (0)