Skip to content

Commit 25ff6cf

Browse files
Reviewed helpers
1 parent 351fdd2 commit 25ff6cf

2 files changed

Lines changed: 109 additions & 94 deletions

File tree

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,59 @@
1-
"""Systemathics APIs Token Helpers
1+
"""Systemathics Ganymede APIs Token Helpers
22
3-
This module helps to create tokens to access Systemathics authenticated APIs
3+
This module helps to create channels to access Systemathics Ganymede authenticated APIs.
44
55
functions:
6-
* get_channel_credentials - get a vanilla ChannelCredentials with default trusted root certificates (from SSL_CERT_FILE environment variable if set, or using auto dection if not)
7-
* autodetect_ca_bundle - automatically detect system wide trusted root certificates to use by probing well known OS paths.
8-
* get_grpc_api_endpoint - get the endpoint to connect to Systemathics APIs (from GRPC_APIS environment variable)
9-
* get_grpc_channel - get a gRPC channel to connect to Systemathics APIs with default credentials.
6+
* get_grpc_channel - Get a channel suitable to call Ganymede gRPC APIs.
7+
* get_aio_grpc_channel - Get an aio channel suitable to call Ganymede gRPC APIs.
108
"""
119

1210
import os
1311
from shutil import ExecError
1412
import grpc
1513

16-
def get_channel_credentials() -> grpc.ChannelCredentials:
14+
DEFAULT_ENDPOINT = "https://grpc.ganymede.cloud"
15+
16+
def get_grpc_channel() -> grpc.Channel:
17+
"""
18+
Get a channel suitable to call Ganymede gRPC APIs.
19+
This uses the GRPC_APIS environment variable in the form http[s]://fdqn[:port] (if no scheme is give, we'll assume https).
20+
If none is detected, use DEFAULT_ENDPOINT.
21+
Note:
22+
For secure channels, we'll try to guess the path of CA certificates chain automatically.
23+
For windows you need to 'pip install wheel python-certifi-win32' for that to work (it exports Windows CA Store to a PEM file).
24+
In the event CA certificates cannot be found, or if you want to use a custom file, set the SSL_CERT_FILE environment variable.
25+
Returns:
26+
An aio channel suitable to call Ganymede gRPC APIs.
27+
"""
28+
endpoint = os.getenv('GRPC_APIS','')
29+
endpoint = endpoint if endpoint else DEFAULT_ENDPOINT # if no endpoint was provided, use the default one
30+
endpoint = endpoint if endpoint.startswith("http") else f"https://{endpoint}" # if no scheme was provided, assume it's https
31+
if (endpoint.startswith("https")):
32+
return grpc.secure_channel(endpoint.replace("https://",""), _get_channel_credentials())
33+
else:
34+
return grpc.insecure_channel(endpoint.replace("http://",""))
35+
36+
def get_aio_grpc_channel() -> grpc.aio.Channel:
37+
"""
38+
Get an aio channel suitable to call Ganymede gRPC APIs.
39+
This uses the GRPC_APIS environment variable in the form http[s]://fdqn[:port].
40+
If none is detected, use DEFAULT_ENDPOINT.
41+
Note:
42+
For secure channels, we'll try to guess the path of CA certificates chain automatically.
43+
For windows you need to 'pip install wheel python-certifi-win32' for that to work (it exports Windows CA Store to a PEM file).
44+
In the event CA certificates cannot be found, or if you want to use a custom file, set the SSL_CERT_FILE environment variable.
45+
Returns:
46+
An aio channel suitable to call Ganymede gRPC APIs.
47+
"""
48+
endpoint = os.getenv('GRPC_APIS','')
49+
endpoint = endpoint if endpoint else DEFAULT_ENDPOINT # if no endpoint was provided, use the default one
50+
endpoint = endpoint if endpoint.startswith("http") else f"https://{endpoint}" # if no scheme was provided, assume it's https
51+
if (endpoint.startswith("https")):
52+
return grpc.aio.secure_channel(endpoint.replace("https://",""), _get_channel_credentials())
53+
else:
54+
return grpc.aio.insecure_channel(endpoint.replace("http://",""))
55+
56+
def _get_channel_credentials() -> grpc.ChannelCredentials:
1757
# If we have a SSL_CERT_FILE env variable, use it
1858
ssl_cert_file = os.getenv('SSL_CERT_FILE','')
1959
if (ssl_cert_file !='' ):
@@ -22,35 +62,27 @@ def get_channel_credentials() -> grpc.ChannelCredentials:
2262
cabundle = ssl_cert_file
2363
# Otherwise try autodetection
2464
else:
25-
cabundle = autodetect_ca_bundle()
65+
cabundle = _autodetect_ca_bundle()
2666

2767
with open(cabundle, 'rb') as f:
2868
credentials = grpc.ssl_channel_credentials(f.read())
2969
return credentials
3070

31-
def autodetect_ca_bundle() -> str:
71+
def _autodetect_ca_bundle() -> str:
3272
cabundles = [
33-
"/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo/etc..
34-
"/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL 6
35-
"/etc/ssl/ca-bundle.pem", # OpenSUSE
36-
"/etc/pki/tls/cacert.pem", # OpenELEC
37-
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", # CentOS/RHEL 7
38-
"/etc/ssl/cert.pem" # Alpine Linux
39-
# Windows ?
73+
"/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo/etc..
74+
"/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL 6
75+
"/etc/ssl/ca-bundle.pem", # OpenSUSE
76+
"/etc/pki/tls/cacert.pem", # OpenELEC
77+
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", # CentOS/RHEL 7
78+
"/etc/ssl/cert.pem", # Alpine Linux
79+
os.path.join(os.getenv('LOCALAPPDATA',''), '.certifi', 'cacert.pem') # Windows (requires: pip install wheel python-certifi-win32)
4080
]
4181

4282
for cabundle in cabundles:
4383
if (os.path.isfile(cabundle)):
4484
return cabundle
4585

46-
raise Exception(f"Could not find any trusted root certificates file, tried {cabundles}")
86+
raise Exception(f"Could not auto detect trusted root certificates file, tried {cabundles}. Please help by setting SSL_CERT_FILE environment variable")
4787

48-
def get_grpc_api_endpoint() -> str:
49-
grpc_apis = os.getenv('GRPC_APIS','')
50-
if (grpc_apis == ''):
51-
raise Exception("Environment variable GRPC_APIS is not set!")
52-
return grpc_apis
53-
54-
def get_grpc_channel() -> grpc.Channel:
55-
credentials = get_channel_credentials()
56-
return grpc.secure_channel(get_grpc_api_endpoint(), credentials)
88+
print(get_grpc_channel())
Lines changed: 51 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,73 @@
1-
"""Systemathics APIs Token Helpers
1+
"""Systemathics Ganymede APIs Token Helpers
22
3-
This module helps to create tokens to access Systemathics authenticated APIs
3+
This module helps to create tokens to access Systemathics Ganymede authenticated APIs.
44
55
functions:
6-
* get_token - get token by autodecting environment variables
7-
* create_bearer_token - create a bearer token (used by get_token when AUTH0_TOKEN env variable is set)
8-
* create_bearer_token_using_rest - create beared token using REST API (used by get_token when AUTH0_TOKEN env variable is not set and CLIENT_ID, CLIENT_SECRET, AUDIENCE and TENANT environment variables are set)
6+
* get_token - Get a JWT Authorization token suitable to call Ganymede gRPC APIs.
97
"""
108

119
import os
1210
import http.client
1311
import json
1412

13+
DEFAULT_AUDIENCE = "https://prod.ganymede-prod"
14+
15+
DEFAULT_TENANT = "ganymede-prod.eu.auth0.com"
16+
1517
def get_token() -> str:
16-
auth0_token = os.getenv('AUTH0_TOKEN','')
17-
client_id = os.getenv('CLIENT_ID','')
18-
client_secret = os.getenv('CLIENT_SECRET','')
19-
audience = os.getenv('AUDIENCE','')
20-
tenant = os.getenv('TENANT','')
18+
"""
19+
Get a JWT Authorization token suitable to call Ganymede gRPC APIs.
20+
We either use 'AUTH0_TOKEN' environment variable (if present) to create a bearer token from it.
21+
Or 'CLIENT_ID' and 'CLIENT_SECRET' environment variables (optionally 'AUDIENCE' can override DEFAULT_AUDIENCE, and 'TENANT' can override DEFAULT_TENANT).
22+
Returns:
23+
A JWT Authorization token suitable to call Ganymede gRPC APIs.
24+
"""
25+
auth0_token = os.getenv("AUTH0_TOKEN","")
26+
client_id = os.getenv("CLIENT_ID","")
27+
client_secret = os.getenv("CLIENT_SECRET","")
28+
audience = os.getenv("AUDIENCE","")
29+
tenant = os.getenv("TENANT","")
2130

2231
# If we have AUTH0_TOKEN, generate a bearer token
23-
if(auth0_token != ''):
24-
if (client_id != ''):
25-
print(f"print: AUTH0_TOKEN environment variable is set, CLIENT_ID environment variable will be ignored")
26-
if (client_secret != ''):
27-
print(f"print: AUTH0_TOKEN environment variable is set, CLIENT_SECRET environment variable will be ignored")
28-
if (audience != ''):
29-
print(f"print: AUTH0_TOKEN environment variable is set, AUDIENCE environment variable will be ignored")
30-
if (tenant != ''):
31-
print(f"print: AUTH0_TOKEN environment variable is set, TENANT environment variable will be ignored")
32-
return create_bearer_token(auth0_token)
32+
if(auth0_token != ""):
33+
return f"Bearer {auth0_token}"
3334

34-
# If we don't, look for CLIENT_ID, CLIENT_SECRET, AUDIENCE and TENANT to create a token using Auth0 API
35-
missing=[]
36-
if(client_id == ''):
37-
missing.append("CLIENT_ID")
38-
if(client_secret == ''):
39-
missing.append("CLIENT_SECRET")
40-
if(audience == ''):
41-
missing.append("AUDIENCE")
42-
if(tenant == ''):
43-
missing.append("TENANT")
44-
45-
if (len(missing) == 0):
46-
return create_bearer_token_using_rest(client_id, client_secret, audience, tenant)
35+
# If we don't, use Auth0 REST API to request one (we need CLIENT_ID and CLIENT_SECRET; Optionally AUDIENCE and TENANT)
36+
if (client_id and client_secret):
37+
return _create_bearer_token_using_rest(
38+
client_id,
39+
client_secret,
40+
audience if audience else DEFAULT_AUDIENCE,
41+
tenant if tenant else DEFAULT_TENANT)
4742
else:
48-
raise Exception(f"AUTH0_TOKEN environment variable is not set, therefore CLIENT_ID, CLIENT_SECRET, AUDIENCE and TENANT environment variables must be set. Missing env variables {missing}")
49-
50-
def create_bearer_token(auth0_token) -> str:
51-
if (auth0_token == ''):
52-
raise Exception(f"auth0_token cannot be null")
43+
raise Exception(f"AUTH0_TOKEN environment variable is not set, therefore CLIENT_ID and CLIENT_SECRET (and optionally AUDIENCE and TENANT) environment variables must be set")
5344

54-
return f"Bearer {auth0_token}"
55-
56-
def create_bearer_token_using_rest(client_id, client_secret, audience, tenant) -> str:
57-
if (client_id == ''):
45+
def _create_bearer_token_using_rest(client_id, client_secret, audience, tenant) -> str:
46+
if (client_id == ""):
5847
raise Exception(f"client_id cannot be null")
59-
if (client_secret == ''):
48+
if (client_secret == ""):
6049
raise Exception(f"client_secret cannot be null")
61-
if (audience == ''):
50+
if (audience == ""):
6251
raise Exception(f"audience cannot be null")
63-
if (tenant == ''):
52+
if (tenant == ""):
6453
raise Exception(f"tenant cannot be null")
6554

66-
try:
67-
# Setup connection and payload
68-
conn = http.client.HTTPSConnection(tenant)
69-
headers = { 'content-type': "application/json" }
70-
params = {"client_id": client_id, "client_secret": client_secret, "grant_type" : "client_credentials", "audience": audience }
71-
payload = json.dumps(params)
55+
# Setup connection and payload
56+
conn = http.client.HTTPSConnection(tenant)
57+
headers = { "content-type": "application/json" }
58+
params = {"client_id": client_id, "client_secret": client_secret, "grant_type" : "client_credentials", "audience": audience }
59+
payload = json.dumps(params)
7260

73-
# Send Request
74-
conn.request("POST", "/oauth/token", payload, headers)
75-
res = conn.getresponse()
76-
data = res.read()
77-
78-
json_data = json.loads(data.decode("utf-8"))
61+
# Send Request
62+
conn.request("POST", "/oauth/token", payload, headers)
63+
res = conn.getresponse()
64+
data = res.read()
65+
66+
json_data = json.loads(data.decode("utf-8"))
7967

80-
# Get access token to be used to authenticate against API
81-
try:
82-
token = f"{json_data['token_type']} {json_data['access_token']}"
83-
return token
84-
except Exception as ee:
85-
print(f"create_bearer_token_using_rest: Returned JSON doesn't contain 'token_type' and/or 'access_token'. Check your client_id, client_secret, audience and tenant: {json_data}")
86-
return ""
87-
88-
except Exception as e:
89-
print(f"create_bearer_token_using_rest: Got exception {e}")
90-
return ""
68+
# Get access token to be used to authenticate against API
69+
try:
70+
token = f"{json_data['token_type']} {json_data['access_token']}"
71+
return token
72+
except Exception as ee:
73+
raise Exception(f"Returned JSON doesn't contain 'token_type' and/or 'access_token'. Check your client ID, client secret, audience and tenant: {json_data}")

0 commit comments

Comments
 (0)