Skip to content

Commit 665a090

Browse files
Added Session Token-based authentication support when using
OCI Cloud Native Authentication (#527).
1 parent 108d2b2 commit 665a090

File tree

3 files changed

+112
-48
lines changed

3 files changed

+112
-48
lines changed

doc/src/release_notes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ Thick Mode Changes
3131
Common Changes
3232
++++++++++++++
3333

34+
#) Added Session Token-based authentication support when using
35+
:ref:`OCI Cloud Native Authentication <cloudnativeauthoci>`
36+
(`issue 527 <https://github.com/oracle/python-oracledb/issues/527>`__).
3437
#) Fixed bug that caused ``ORA-03137: malformed TTC packet from client
3538
rejected`` exception to be raised when attempting to call
3639
:meth:`Cursor.parse()` on a scrollable cursor.

doc/src/user_guide/authentication_methods.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -690,20 +690,26 @@ the following table.
690690
- Description
691691
- Required or Optional
692692
* - ``auth_type``
693-
- The authentication type. The value should be the string "ConfigFileAuthentication", "SimpleAuthentication", or "InstancePrincipal".
693+
- The authentication type. The value should be the string "ConfigFileAuthentication",
694+
"InstancePrincipal", "SecurityToken", "SecurityTokenSimple" or "SimpleAuthentication".
694695

695696
With Configuration File Authentication, the location of a configuration file containing the necessary information must be provided. By default, this file is located at */home/username/.oci/config*, unless a custom location is specified during OCI IAM setup.
696697

697-
With Simple Authentication, the individual configuration parameters can be provided at runtime.
698-
699698
With Instance Principal Authentication, OCI compute instances can be authorized to access services on Oracle Cloud such as Oracle Autonomous Database. Python-oracledb applications running on such a compute instance are automatically authenticated, eliminating the need to provide database user credentials. This authentication method will only work on compute instances where internal network endpoints are reachable. See :ref:`instanceprincipalauth`.
700699

700+
With Security Token authentication or Session Token-based authentication, the authentication happens using *security_token_file* parameter present in the configuration file. By default, this file is located at */home/username/.oci/config*, unless a custom location is specified during OCI IAM setup. You also need to specify the *profile* which contains the *security_token_file* parameter.
701+
702+
With Security Token Simple authentication or Session Token-based Simple authentication, the authentication happens using *security_token_file* parameter. The individual configuration parameters can be provided at runtime.
703+
704+
With Simple Authentication, the individual configuration parameters can be provided at runtime.
705+
701706
See `OCI SDK Authentication Methods <https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdk_authentication_methods.htm>`__ for more information.
702707
- Required
703708
* - ``user``
704709
- The Oracle Cloud Identifier (OCID) of the user invoking the API. For example, *ocid1.user.oc1..<unique_ID>*.
705710

706-
This parameter can be specified when the value of the ``auth_type`` key is "SimpleAuthentication".
711+
This parameter can be specified when the value of the ``auth_type`` key is "SimpleAuthentication". This is not required when ``auth_type`` is
712+
"SecurityToken" or "SecurityTokenSimple"
707713
- Required
708714
* - ``key_file``
709715
- The full path and filename of the private key.

src/oracledb/plugins/oci_tokens.py

Lines changed: 99 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,48 @@
2828
# Methods that generates an OCI access token using the OCI SDK
2929
# -----------------------------------------------------------------------------
3030

31+
import enum
32+
import pathlib
33+
34+
from cryptography.hazmat.primitives import serialization
35+
from cryptography.hazmat.primitives.asymmetric import rsa
3136
import oci
3237
import oracledb
33-
from cryptography.hazmat.primitives.asymmetric import rsa
34-
from cryptography.hazmat.primitives import serialization
3538

3639

37-
def generate_token(token_auth_config, refresh=False):
40+
class AuthType(str, enum.Enum):
41+
ConfigFileAuthentication = "ConfigFileAuthentication".lower()
42+
InstancePrincipal = "InstancePrincipal".lower()
43+
SecurityToken = "SecurityToken".lower()
44+
SecurityTokenSimple = "SecurityTokenSimple".lower()
45+
SimpleAuthentication = "SimpleAuthentication".lower()
46+
47+
48+
def _config_file_based_authentication(token_auth_config):
3849
"""
39-
Generates an OCI access token based on provided credentials.
50+
Config file base authentication implementation: config parameters
51+
are provided in a file.
4052
"""
41-
user_auth_type = token_auth_config.get("auth_type") or ""
42-
auth_type = user_auth_type.lower()
43-
if auth_type == "configfileauthentication":
44-
return _config_file_based_authentication(token_auth_config)
45-
elif auth_type == "simpleauthentication":
46-
return _simple_authentication(token_auth_config)
47-
elif auth_type == "instanceprincipal":
48-
return _instance_principal_authentication(token_auth_config)
49-
else:
50-
raise ValueError(
51-
f"Unrecognized auth_type authentication method {user_auth_type}"
52-
)
53+
config = _load_oci_config(token_auth_config)
54+
client = oci.identity_data_plane.DataplaneClient(config)
55+
return _generate_access_token(client, token_auth_config)
56+
57+
58+
def _generate_access_token(client, token_auth_config):
59+
"""
60+
Token generation logic used by authentication methods.
61+
"""
62+
key_pair = _get_key_pair()
63+
scope = token_auth_config.get("scope", "urn:oracle:db::id::*")
64+
65+
details = oci.identity_data_plane.models.GenerateScopedAccessTokenDetails(
66+
scope=scope, public_key=key_pair["public_key"]
67+
)
68+
response = client.generate_scoped_access_token(
69+
generate_scoped_access_token_details=details
70+
)
71+
72+
return (response.data.token, key_pair["private_key"])
5373

5474

5575
def _get_key_pair():
@@ -76,52 +96,75 @@ def _get_key_pair():
7696
)
7797

7898
if not oracledb.is_thin_mode():
79-
p_key = "".join(
99+
private_key_pem = "".join(
80100
line.strip()
81101
for line in private_key_pem.splitlines()
82102
if not (
83103
line.startswith("-----BEGIN") or line.startswith("-----END")
84104
)
85105
)
86-
private_key_pem = p_key
87106

88107
return {"private_key": private_key_pem, "public_key": public_key_pem}
89108

90109

91-
def _generate_access_token(client, token_auth_config):
110+
def _instance_principal_authentication(token_auth_config):
92111
"""
93-
Token generation logic used by authentication methods.
112+
Instance principal authentication: for compute instances
113+
with dynamic group access.
94114
"""
95-
key_pair = _get_key_pair()
96-
scope = token_auth_config.get("scope", "urn:oracle:db::id::*")
97-
98-
details = oci.identity_data_plane.models.GenerateScopedAccessTokenDetails(
99-
scope=scope, public_key=key_pair["public_key"]
100-
)
101-
response = client.generate_scoped_access_token(
102-
generate_scoped_access_token_details=details
103-
)
104-
105-
return (response.data.token, key_pair["private_key"])
115+
signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
116+
client = oci.identity_data_plane.DataplaneClient(config={}, signer=signer)
117+
return _generate_access_token(client, token_auth_config)
106118

107119

108-
def _config_file_based_authentication(token_auth_config):
120+
def _load_oci_config(token_auth_config):
109121
"""
110-
Config file base authentication implementation: config parameters
111-
are provided in a file.
122+
Load OCI configuration using the file location and profile specified
123+
in token_auth_config, or fall back to OCI defaults.
112124
"""
113125
file_location = token_auth_config.get(
114126
"file_location", oci.config.DEFAULT_LOCATION
115127
)
116128
profile = token_auth_config.get("profile", oci.config.DEFAULT_PROFILE)
117-
118-
# Load OCI config
119129
config = oci.config.from_file(file_location, profile)
120130
oci.config.validate_config(config)
131+
return config
121132

122-
# Initialize service client with default config file
123-
client = oci.identity_data_plane.DataplaneClient(config)
124133

134+
def _security_token_authentication(token_auth_config):
135+
"""
136+
Session token-based authentication: uses the security token specified by
137+
the security_token_file parameter based on the OCI config file.
138+
"""
139+
config = _load_oci_config(token_auth_config)
140+
client = _security_token_create_dataplane_client(config)
141+
return _generate_access_token(client, token_auth_config)
142+
143+
144+
def _security_token_create_dataplane_client(config):
145+
"""
146+
Create and return an OCI Identity Data Plane client using
147+
the security token and private key specified in the given OCI config.
148+
"""
149+
token = pathlib.Path(config["security_token_file"]).read_text()
150+
private_key = oci.signer.load_private_key_from_file(config["key_file"])
151+
signer = oci.auth.signers.SecurityTokenSigner(token, private_key)
152+
return oci.identity_data_plane.DataplaneClient(config, signer=signer)
153+
154+
155+
def _security_token_simple_authentication(token_auth_config):
156+
"""
157+
Session token-based authentication: uses security token authentication.
158+
Config parameters are passed as parameters.
159+
"""
160+
config = {
161+
"key_file": token_auth_config["key_file"],
162+
"fingerprint": token_auth_config["fingerprint"],
163+
"tenancy": token_auth_config["tenancy"],
164+
"region": token_auth_config["region"],
165+
"security_token_file": token_auth_config["security_token_file"],
166+
}
167+
client = _security_token_create_dataplane_client(config)
125168
return _generate_access_token(client, token_auth_config)
126169

127170

@@ -143,14 +186,26 @@ def _simple_authentication(token_auth_config):
143186
return _generate_access_token(client, token_auth_config)
144187

145188

146-
def _instance_principal_authentication(token_auth_config):
189+
def generate_token(token_auth_config, refresh=False):
147190
"""
148-
Instance principal authentication: for compute instances
149-
with dynamic group access.
191+
Generates an OCI access token based on provided credentials.
150192
"""
151-
signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
152-
client = oci.identity_data_plane.DataplaneClient(config={}, signer=signer)
153-
return _generate_access_token(client, token_auth_config)
193+
user_auth_type = token_auth_config.get("auth_type") or ""
194+
auth_type = user_auth_type.lower()
195+
if auth_type == AuthType.ConfigFileAuthentication:
196+
return _config_file_based_authentication(token_auth_config)
197+
elif auth_type == AuthType.InstancePrincipal:
198+
return _instance_principal_authentication(token_auth_config)
199+
elif auth_type == AuthType.SecurityToken:
200+
return _security_token_authentication(token_auth_config)
201+
elif auth_type == AuthType.SecurityTokenSimple:
202+
return _security_token_simple_authentication(token_auth_config)
203+
elif auth_type == AuthType.SimpleAuthentication:
204+
return _simple_authentication(token_auth_config)
205+
else:
206+
raise ValueError(
207+
f"Unrecognized auth_type authentication method {user_auth_type}"
208+
)
154209

155210

156211
def oci_token_hook(params: oracledb.ConnectParams):

0 commit comments

Comments
 (0)