Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/mdbook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Sample workflow for building and deploying a mdBook site to GitHub Pages
#
# To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html
#
name: Deploy mdBook site to Pages

on:
pull_request:
branches: ["main" ]
push:
branches: ["main"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
# Build job
build:
runs-on: ubuntu-latest
env:
MDBOOK_VERSION: 0.4.51
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Install mdBook
run: |
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh
rustup update
cargo install --version ${MDBOOK_VERSION} mdbook
cargo install mdbook-mermaid

- name: Setup Pages
id: pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0

- name: Build with mdBook
run: mdbook build doc

- name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: ./doc/html

# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
if: ${{ github.event_name == 'push' }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
**/target
# Ignore rust files in the root folder
*.rs

doc/html
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ repos:
exclude: '^zuul.d/.*$'
- id: check-json
- repo: https://github.com/crate-ci/typos
rev: v1.29.3
rev: v1.33.1
hooks:
- id: typos
- repo: https://github.com/crate-ci/committed
rev: v1.0.20
rev: v1.1.7
hooks:
- id: committed
- repo: local
Expand All @@ -36,6 +36,6 @@ repos:
minimum_pre_commit_version: 2.21.0
require_serial: true
- repo: https://github.com/gitleaks/gitleaks
rev: v8.16.3
rev: v8.27.2
hooks:
- id: gitleaks
24 changes: 24 additions & 0 deletions doc/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[book]
authors = ["Artem Goncharov"]
language = "en"
src = "src"
title = "OpenStack Keystone"

[rust]
edition = "2024"

[build]
build-dir = "html"
create-missing = false

[output.html.fold]
enable = true
level = 2

[preprocessor]

[preprocessor.mermaid]
command = "mdbook-mermaid"

[output.html]
additional-js = ["mermaid.min.js", "mermaid-init.js"]
35 changes: 35 additions & 0 deletions doc/mermaid-init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
(() => {
const darkThemes = ['ayu', 'navy', 'coal'];
const lightThemes = ['light', 'rust'];

const classList = document.getElementsByTagName('html')[0].classList;

let lastThemeWasLight = true;
for (const cssClass of classList) {
if (darkThemes.includes(cssClass)) {
lastThemeWasLight = false;
break;
}
}

const theme = lastThemeWasLight ? 'default' : 'dark';
mermaid.initialize({ startOnLoad: true, theme });

// Simplest way to make mermaid re-render the diagrams in the new theme is via refreshing the page

for (const darkTheme of darkThemes) {
document.getElementById(darkTheme).addEventListener('click', () => {
if (lastThemeWasLight) {
window.location.reload();
}
});
}

for (const lightTheme of lightThemes) {
document.getElementById(lightTheme).addEventListener('click', () => {
if (!lastThemeWasLight) {
window.location.reload();
}
});
}
})();
2,609 changes: 2,609 additions & 0 deletions doc/mermaid.min.js

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions doc/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# OpenStack Keystone

[Introduction](./intro.md)
[Installation](./install.md)

---

- [Federation](./federation.md)
- [Passkeys](./passkey.md)


---

[Performance comparison](./performance.md)
120 changes: 120 additions & 0 deletions doc/src/federation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Federation support

Python Keystone is not implementing the Federation natively (neither SAML2, nor
OIDC). It relies on the proxy server for the authentication protocol specifics
and tries to map resulting users into the local database. This leads to a
pretty big number of limitations (not limited to):

- Identity Provider can be only configured by cloud administrators only

- Pretty much any change on the IdP configuration require restart of the service

- Certain protocol specifics can not be implemented at all (i.e. backend
initiated logout)

- Forces deployment of the proxy service in front of Keystone relying on the
modules for SAML2 and/or OIDC implementation (such modules may be abandoned
or removed).

- Client authentication right now is complex and error prone (every public
provider has implementation specifics that are often even not cross-compatible)


In order to address those challenges and complete reimplementation is being
done here. This leads to a completely different design opening doors for new
features.

- Federation is controlled on the domain level by the domain managers. This
means that the domain manager is responsible for the configuration of how users
should be federated from external IdPs.

- Keystone serves as a relying party in the OIDC authentication flow. This
moves the complex logic from client to the the Keystone side. This allows
making client applications much simpler and more reliable.

## Authentication using the Authorization Code flow and Keystone serving as RP

```mermaid
sequenceDiagram

Actor Human
Human ->> Cli: Initiate auth
Cli ->> Keystone: Fetch the OP auth url
Keystone --> Keystone: Initialize authorization request
Keystone ->> Cli: Returns authURL of the IdP with cli as redirect_uri
Cli ->> User-Agent: Go to authURL
User-Agent -->> IdP: opens authURL
IdP -->> User-Agent: Ask for consent
Human -->> User-Agent: give consent
User-Agent -->> IdP: Proceed
IdP ->> Cli: callback with Authorization code
Cli ->> Keystone: Exchange Authorization code for Keystone token
Keystone ->> IdP: Exchange Authorization code for Access token
IdP ->> Keystone: Return Access token
Keystone ->> Cli: return Keystone token
Cli ->> Human: Authorized

```

## Authenticating with the JWT

This is a work in progress and is not implemented yet

## API changes

A series of brand new API endpoints have been added to the Keystone API.

- /v3/federation/identity_providers (manage the identity providers)

- /v3/federation/mappings (manage the mappings tied to the identity provider)

- /v3/federation/auth (initiate the authentication and get the IdP url)

- /v3/federation/oidc/callback (exchange the authorization code for the Keystone token)

## DB changes

Following tables are added:

- federated_identity_provider

```rust
{{#rustdoc_include ../../src/db/entity/federated_identity_provider.rs:9:21}}
```

- federated_mapping

```rust
{{#include ../../src/db/entity/federated_mapping.rs:9:24}}
```

- federated_auth_state

```rust
{{#include ../../src/db/entity/federated_auth_state.rs:8:16}}
```


## Compatibility notes

Since the federation is implemented very differently to how it was done before
it certain compatibility steps are implemented:

- Identity provider is "mirrored" into the existing identity_provider with the
subset of attributes

- For every identity provider "oidc" protocol entry in the federation_protocol
table is created pointing to the "<<null>>" mapping.

## Testing

Federation is very complex and need to be tested with every supported public
provider. Only this can guarantee that issues with not fully compliant OIDC
implementations can be identified early enough.

Authorization code flow requires presence of the browser. Due to that the tests
need to rely on Selenium.

At the moment following integrations are tested automatically:

- Keycloak (login using browser)
18 changes: 18 additions & 0 deletions doc/src/install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Installation

TODO:

- Prepare the binary (download from GH releases, build yourself, use the
container image, ...)

- Perform the DB migration `keystone-db up`

- Start the binary as `keystone -c <PATH_TO_THE_KEYSTONE_CONFIG>`


## Database migrations

Rust Keystone is using different ORM and implements migration that co-exist
together with alembic migrations of the python Keystone. It also ONLY manages
the database schema additions and does NOT include the original database
schema. Therefore it is necessary to apply both migrations.
43 changes: 43 additions & 0 deletions doc/src/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# OpenStack Keystone in Rust

What happens if OpenStack Keystone would be rewritten in Rust? Is it possible?
How complex is it? Which improvements are possible?

This project exists to answer this questions.

Primary target of the project is to implement a Rust library implementing
Keystone functionality to be able to split a Keystone monolith into smaller
pieces (similar to the microservices). Once this is done, adding an API is
becoming also pretty simple.

It targets deploying Python and Rust implementation in parallel and do request
routing on the web server level to get the speed and security of Rust
implementation while keeping functions not implemented (yet) being served by the
original Keystone. This approach also makes it possible to deploy Rust
implementation in parallel to a much older version of Keystone giving
possibility for the operators to enable new features while still using older
version of Keystone (whatever the reason for that is).


## Compatibility

Highest priority is to ensure that this implementation is compatible with the
original python Keystone: authentication issued by Rust implementation is
accepted by the Python Keystone and vice versa. At the same time it is
expected, that the new implementation may implement new features not supported
by the Python implementation. In this case, it is still expected that such
features do not break authentication flows. It must be possible to deploy
Python and Rust implementation in parallel and do request routing on the web
server level.

## Database

Adding new features most certanly require having database changes. It is not
expected that such changes interfere with the Python implementation to ensure
it is working correctly.

## API

Also here it is expected that new API resources are going to be added. As above
it is not expected that such changes interfere with the Python implementation
to ensure it is still working correctly and existing clients will not break.
Loading