Amundsen — Integrate with Okta SingleSignOn
Okta is a cloud-based identity and access management service that provides secure and reliable user authentication for web and mobile applications. It helps organizations manage and control access to their applications, as well as providing single sign-on capabilities for their users. With Okta, organizations can easily and securely manage user access to their applications, enabling their employees to work from anywhere and on any device.
So how do we integrate Amundsen (or some other applications) into Okta ? Let’s find out.
(Notes: I couldn’t get all these stuff working without referencing this amundsen article. Huge credit goes to Verdan Mahmood !)
Step 1 — Create your own Okta Account
First step for us is to set up our own okta account. For this you’d need to visit https://www.okta.com/ and follow their GetStarted instructions. I won’t go into the details there as I think it’s pretty intuitive. At the end you should be able to login to the user console, click the admin button, & arrive at the admin console like below :
Step 2 — Create your okta OIDC-enabled application
Second step is to create our okta OIDC-enabled application. We choose OIDC here since this is the authentication method that Amundsen supports. So to do this — Go to the application
tab on your left, click application
, and click create app integration
, then choose the OIDC option like below :
Now, here comes the fun part. You’d need to check Authorization_code
and the implicate(hybrid)
box, and also provide a correct sign-in redirect uri
and sign-out redirect uri
like below :
The most important thing here is to add the /auth
suffix for the sign-in redirect uri
. Without providing this, authentication wouldn’t take place and you won’t be able to visit amundsen frontend.
In the screenshot above, I’m showing http://localhost:8080
which is where I host my amundsen frontend page. But at a production environment where you’d host Amundsen in a Kubernetes cluster, you should be posting your load balancer address that exposes your k8s cluster. For example, in an AWS environment it might look something like xxxxx.elb.[region-name].amazonaws.com
.
Afterwards, you can just click save. And then you should be seeing your client_id
and client_secret
like below. Pencil down these 2 info as you would need them very soon when deploying Amundsen
Step 3 — Configure the API-Scope
After creating the OIDC-enabled application, you’d need to configure its API scope also. Specifically, you’d need to grant 2 API scopes, which are okta.users.read
, and okta.users.read.self
. To do this, go to the Okta API Scopes
tab, search for okta.users.read
and okta.users.read.self
, and click Grant
Step 4 — Revise the metadata service to programmatically get user token from okta
This, in my opinion, is the hardest part. The metadata service from Amundsen, by default, doesn’t have the python code to connect to the okta endpoint and fetch user’s token from there. So you’d need to supplement your own code to do this. I can’t guarantee that my code piece below works in all scanerios but I believe it will provide a good starting point for you to work on.
So in the /metadata/metadata_service
folder, create a python file get_okta_user.py
and post the following code :
from flask import current_app as app
from amundsen_common.models.user import UserSchema
from metadata_service.exception import NotFoundException
from typing import Dict
from metadata_service.proxy import get_proxy_client
import logging
def get_user_from_oidc(user_id: str) -> Dict:
metadata = app.auth_client.load_server_metadata()
search_endpoint = f'{metadata["issuer"]}/api/v1/users?q={user_id}&limit=1'
_not_found_error = f"User Not Found in the OIDC Provider. User ID: {user_id}"
logging.info("Calling app.auth_client.get() to fetch user information from okta")
response = app.auth_client.get(search_endpoint)
response.raise_for_status()
user_info = response.json()
if not user_info:
raise NotFoundException(_not_found_error)
user_data = dict()
_user = user_info[0]
user_data.update(_user["profile"])
user_data.update({
"name": f'{_user["profile"]["firstName"]} {_user["profile"]["lastName"]}'
})
profile_url = _user.get("_links", {}).get("self", {}).get("href")
user_data.update({"profile_url": profile_url})
return {
"user_id": user_id,
"email": user_data["email"],
"first_name": user_data["firstName"],
"last_name": user_data["lastName"],
"full_name": user_data["name"],
"display_name": user_data["name"],
"profile_url": user_data["profile_url"],
}
def get_user_details(user_id: str) -> Dict:
client = get_proxy_client()
schema = UserSchema()
if not hasattr(app, 'auth_client'):
logging.exception("auth_client not available in the current app")
try:
user = client.get_user(id=user_id)
except NotFoundException as e:
logging.info("Original Error : {e}\n\nUser not found in the database. Trying to create one using oidc.get_user_detail")
if user is not None:
return schema.dump(user)
else:
try:
logging.info(f"{user_id} ==> Calling get_user_from_oidc() at get_okta_user.py...")
user_info = get_user_from_oidc(user_id=user_id)
user = schema.load(user_info)
new_user, is_created = client.create_update_user(user=user)
return schema.dump(new_user)
except Exception as ex:
logging.exception(str(ex), exc_info=True)
# Return the required information only
return {
"email": user_id,
"user_id": user_id,
}
Then, in the /metadata/metadata_service/__init__.py
file, add the following line inside the create_app()
function.
app.config["USER_DETAIL_METHOD"] = get_user_details
Step 5 — Put the Client_ID & Client_Secret into the helm value file
Finally, we need to configure our helm value file and put our client_id
and client_secret
there. A couple of configurations are also necessary in order for the Amundsen application to use the proper class. Below code snippet specifies the necessary configs that you need to include for integrating with Okta :
flaskApp:
module: "flaskoidc"
class: "FlaskOIDC"
oidc:
enabled: true
frontend:
client_id: []
client_secret: ""
metadata:
client_id: [CLIENT_ID]
client_secret: [CLIENT_SECRET]
search:
client_id: [CLIENT_ID]
client_secret: [CLIENT_SECRET]
configs:
FLASK_OIDC_PROVIDER_NAME: okta
FLASK_OIDC_SCOPES: "openid email profile okta.users.read okta.users.read.self offline_access"
FLASK_OIDC_USER_ID_FIELD: email
FLASK_OIDC_REDIRECT_URI: "/auth"
FLASK_OIDC_CONFIG_URL: "[Your Okta Domain]/.well-known/openid-configuration"
FLASK_OIDC_WHITELISTED_ENDPOINTS: "status,healthcheck,health,api.healthcheck"
frontEnd:
config:
class: amundsen_application.oidc_config.OidcConfig
That’s it ! After doing all these you should be able to view the amundsen frontend page like below !
Thanks for reading. Stay tuned for more contents =)
Owen