Skip to content

Commit

Permalink
feat: refactoring (#54)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: OIDC configuration changed. Only discovery URL supported now
  • Loading branch information
kharkevich authored Dec 7, 2024
1 parent bedb8dc commit 777de16
Show file tree
Hide file tree
Showing 54 changed files with 1,608 additions and 1,152 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/documentation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Deploy documentation

on:
push:
branches: ["main"]

workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './docs'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Empty file added docs/.nojekyll
Empty file.
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## mlflow-oidc-auth
Mlflow auth plugin to use OpenID Connect (OIDC) as authentication and authorization provider

## Inspired by MLFlow basic-auth plugin
https://github.com/mlflow/mlflow/tree/master/mlflow/server/auth
19 changes: 19 additions & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
- [Home](/)
- [Permission management](permission-management/index.md)
- [User permission](./permission-management/user-permissions.md)
- [Experiment permissions](./permission-management/experiment-permissions.md)
- [Registered model permissions](./permission-management/registered-model-permissions.md)
- Group permissions
- [Experiment permissions](./permission-management/oidc-group-experiment-permissions.md)
- [Registered model permissions](./permission-management/oidc-group-registered-model-permissions.md)
- [Installation](installation)
- Authentication
- [Session](auth/session.md)
- [Basic Auth](auth/basic-auth.md)
- [Bearer Token](auth/auth-token-bearer.md)
- Configuration
- [Caching](configuration/cashing)
- OIDC configuration examples
- [Okta](configuration/examples/okta)
- [Microsoft Entra ID](configuration/examples/microsoft)
- [Development and Contribution](development.md)
14 changes: 14 additions & 0 deletions docs/auth/auth-token-bearer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Bearer Token
You can use bearer token authentication for the auth plugin.
It can be useful in case when your app is located behind some oauth proxy and/or you want to do auth outside of the application.

## Configuration

```
OIDC_DISCOVERY_URL=https://path.to.issuer.url/
```

No more configuration needed. If your request to the MLFlow instance with oidc

## Recommendation
You need to enable caching to improve application performance and avoid cases when the application pull JWKS endpoint every time with incoming request.
34 changes: 34 additions & 0 deletions docs/auth/basic-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Basic Auth

To use Basic authentication with your notebooks you need to generate access token

![create-pat](./images/create-pat.png)

Do not forget to copy personal access token to safe place.

![pat](./images/pat.png)

## PAT usage example

```python
import mlflow
from mlflow import log_metric, log_param

tracking_server_url = "https://mlflow.technicaldomain.xyz"
username = "<email>"
password = "<token>"

mlflow.set_tracking_uri(tracking_server_url)
mlflow.set_experiment("demo_experiment")

import os
os.environ["MLFLOW_TRACKING_USERNAME"] = username
os.environ["MLFLOW_TRACKING_PASSWORD"] = password

with mlflow.start_run(run_name="demo_run"):
log_param("param1", 5)
log_param("param2", "test_param")
log_metric("metric1", 0.1)
log_metric("metric2", 0.2)
print("Experiment data logged successfully!")
```
Binary file added docs/auth/images/create-pat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/auth/images/pat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/auth/session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Session
3 changes: 3 additions & 0 deletions docs/configuration/cashing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Session storage

# Caching
22 changes: 22 additions & 0 deletions docs/configuration/examples/microsoft.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Microsoft Entra ID

## Get started
Register to have [Microsoft Entra ID](https://www.microsoft.com/security/business/identity-access/microsoft-entra-id)

Register an [application](https://learn.microsoft.com/entra/identity-platform/quickstart-register-app) with the Microsoft identity platform

Please note, that for getting group membership information, the application should have ["GroupMember.Read.All"](https://learn.microsoft.com/graph/api/group-list-members) permission

## Configuration example

```bash
OIDC_DISCOVERY_URL = 'https://login.microsoftonline.com/<tenant_id>/v2.0/.well-known/openid-configuration'
OIDC_CLIENT_SECRET = '<super_secret>'
OIDC_CLIENT_ID = '<client_id>'
OIDC_PROVIDER_DISPLAY_NAME = "Login with Microsoft"
OIDC_GROUP_DETECTION_PLUGIN = 'mlflow_oidc_auth.plugins.group_detection_microsoft_entra_id'
OIDC_SCOPE = "openid,profile,email"
OIDC_GROUP_NAME = "mlflow_users_group_name"
OIDC_ADMIN_GROUP_NAME = "mlflow_admins_group_name"
```

15 changes: 15 additions & 0 deletions docs/configuration/examples/okta.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Okta



## Configuration example

```bash
OIDC_DISCOVERY_URL = 'https://<your_domain>.okta.com/.well-known/openid-configuration'
OIDC_CLIENT_SECRET ='<super_secret>'
OIDC_CLIENT_ID ='<client_id>'
OIDC_PROVIDER_DISPLAY_NAME = "Login with Okta"
OIDC_SCOPE = "openid,profile,email,groups"
OIDC_GROUP_NAME = "mlflow-users-group-name"
OIDC_ADMIN_GROUP_NAME = "mlflow-admin-group-name"
```
39 changes: 39 additions & 0 deletions docs/configuration/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Configuration
The plugin required the following environment variables but also supported `.env` file

## Application configuration
| Parameter | Description|
|---|---|
| OIDC_REDIRECT_URI | Application redirect/callback url (https://example.com/callback) |
| OIDC_DISCOVERY_URL | OIDC Discovery URL |
| OIDC_CLIENT_SECRET | OIDC Client Secret |
| OIDC_CLIENT_ID | OIDC Client ID |
| OIDC_GROUP_DETECTION_PLUGIN | OIDC plugin to detect groups |
| OIDC_PROVIDER_DISPLAY_NAME | any text to display |
| OIDC_SCOPE | OIDC scope |
| OIDC_GROUP_NAME | User group name to be allowed login to MLFlow, currently supported groups in OIDC claims and Microsoft Entra ID groups |
| OIDC_ADMIN_GROUP_NAME | User group name to be allowed login to MLFlow manage and define permissions, currently supported groups in OIDC claims and Microsoft Entra ID groups |
| OIDC_AUTHORIZATION_URL | OIDC Auth URL (if discovery URL is not defined) |
| OIDC_TOKEN_URL | OIDC Token URL (if discovery URL is not defined) |
| OIDC_USER_URL | OIDC User info URL (if discovery URL is not defined) |
| SECRET_KEY | Key to perform cookie encryption |
| OAUTHLIB_INSECURE_TRANSPORT | Development only. Allow to use insecure endpoints for OIDC |
| LOG_LEVEL | Application log level |
| OIDC_USERS_DB_URI | Database connection string |

## Application session storage configuration
| Parameter | Description | Default |
|---|---|---|
| SESSION_TYPE | Flask session type (filesystem or redis supported) | filesystem |
| SESSION_FILE_DIR | The directory where session files are stored | flask_session |
| SESSION_PERMANENT | Whether use permanent session or not | False |
| PERMANENT_SESSION_LIFETIME | Server-side session expiration time (in seconds) | 86400 |
| SESSION_KEY_PREFIX | A prefix that is added before all session keys | mlflow_oidc: |
| REDIS_HOST | Redis hostname | localhost |
| REDIS_PORT | Redis port | 6379 |
| REDIS_DB | Redis DB number | 0 |
| REDIS_USERNAME | Redis username | None |
| REDIS_PASSWORD | Redis password | None |
| REDIS_SSL | Use SSL | false |


21 changes: 21 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### Development

Preconditions:

The following tools should be installed for local development:

* git
* nodejs
* Python

```shell
git clone https://github.com/data-platform-hq/mlflow-oidc-auth
cd mlflow-oidc-auth
./scripts/run-dev-server.sh
```

### Contribution

Any contribution is always welcomed. We seek help with testing (including unit test development), showcases and success stories (if you can share them), documentation improvement, and examples.

Fork, hack, and make a PR!
24 changes: 24 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OpenID Connect Plugin for MLFlow</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: 'mlflow-oidc-auth',
repo: 'https://github.com/data-platform-hq/mlflow-oidc-auth',
loadSidebar: true,
loadNavbar: true,
};
</script>
<!-- Docsify v4 -->
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# PyPI

## Recommended option
To get full version (with entire MLFlow, MLFlow UI and all dependencies) run:

```bash
python3 -m pip install mlflow-oidc-auth[full]
```

## Distributed deployment (autoscaling/k8s)

To achieve best experience and allow application scaling you need to install package with caching support

```bash
python3 -m pip install mlflow-oidc-auth[full,caching-redis]
```

## Lightweight installation

To get skinny version run:

```bash
python3 -m pip install mlflow-oidc-auth
```

> Please note: the lightweight installation has no MLFlow UI package because it is not included in the mlflow-skinny version. Please install the full version, or install mlflow w/o dependencies at your own risk.
# Anaconda

Not available yet
1 change: 1 addition & 0 deletions docs/permission-management/experiment-permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Experiment permissions
7 changes: 7 additions & 0 deletions docs/permission-management/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Permissions management

## Overview


## Permissions hierarchy

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Group permissions: Experiment permissions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Group permissions: Registered model permissions
1 change: 1 addition & 0 deletions docs/permission-management/registered-model-permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Registered model permissions
1 change: 1 addition & 0 deletions docs/permission-management/user-permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# User permission
2 changes: 1 addition & 1 deletion mlflow_oidc_auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os

version = os.environ.get("MLFLOW_OIDC_AUTH_VERSION", "2.0.0.dev0")
version = os.environ.get("MLFLOW_OIDC_AUTH_VERSION", "3.0.0.dev0")

__version__ = version
15 changes: 9 additions & 6 deletions mlflow_oidc_auth/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
from flask_session import Session

from mlflow_oidc_auth import routes, views
from mlflow_oidc_auth.config import AppConfig
from mlflow_oidc_auth.config import config
from mlflow_oidc_auth.hooks import before_request_hook, after_request_hook
from flask_caching import Cache

# Configure custom Flask app
template_dir = os.path.dirname(__file__)
template_dir = os.path.join(template_dir, "templates")

app.config.from_object(AppConfig)
app.config.from_object(config)
app.secret_key = app.config["SECRET_KEY"].encode("utf8")
app.template_folder = template_dir
static_folder = app.static_folder
Expand All @@ -34,12 +36,12 @@

# UI routes support
app.add_url_rule(rule=routes.GET_EXPERIMENTS, methods=["GET"], view_func=views.get_experiments)
app.add_url_rule(rule=routes.GET_MODELS, methods=["GET"], view_func=views.get_models)
app.add_url_rule(rule=routes.GET_MODELS, methods=["GET"], view_func=views.get_registered_models)
app.add_url_rule(rule=routes.GET_USERS, methods=["GET"], view_func=views.get_users)
app.add_url_rule(rule=routes.GET_USER_EXPERIMENTS, methods=["GET"], view_func=views.get_user_experiments)
app.add_url_rule(rule=routes.GET_USER_MODELS, methods=["GET"], view_func=views.get_user_models)
app.add_url_rule(rule=routes.GET_EXPERIMENT_USERS, methods=["GET"], view_func=views.get_experiment_users)
app.add_url_rule(rule=routes.GET_MODEL_USERS, methods=["GET"], view_func=views.get_model_users)
app.add_url_rule(rule=routes.GET_MODEL_USERS, methods=["GET"], view_func=views.get_registered_model_users)

# User management
app.add_url_rule(rule=routes.CREATE_USER, methods=["POST"], view_func=views.create_user)
Expand Down Expand Up @@ -82,8 +84,9 @@
app.add_url_rule(rule=routes.UPDATE_GROUP_MODEL_PERMISSION, methods=["PATCH"], view_func=views.update_group_model_permission)

# Add new hooks
app.before_request(views.before_request_hook)
app.after_request(views.after_request_hook)
app.before_request(before_request_hook)
app.after_request(after_request_hook)

# Set up session
Session(app)
cache = Cache(app)
Loading

0 comments on commit 777de16

Please sign in to comment.