IDP with rauthy
This commit is contained in:
parent
a6cb828aee
commit
bcb14b3c48
74
Cargo.lock
generated
74
Cargo.lock
generated
@ -296,12 +296,6 @@ version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "anymap"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.7.1"
|
||||
@ -1232,6 +1226,7 @@ dependencies = [
|
||||
"cookie 0.18.1",
|
||||
"parking_lot 0.12.3",
|
||||
"password-hash",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
@ -1430,16 +1425,6 @@ version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.9"
|
||||
@ -2155,17 +2140,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghost"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0e085ded9f1267c32176b40921b9754c474f7dd96f7e808d4a982e48aa1e854"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.29.0"
|
||||
@ -2718,28 +2692,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0eb5160c60ba1e809707918ee329adb99d222888155835c6feedba19f6c3fd4"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"ghost",
|
||||
"inventory-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory-impl"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e41b53715c6f0c4be49510bb82dee2c1e51c8586d885abe65396e82ed518548"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
@ -3618,7 +3570,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
"toml 0.7.8",
|
||||
"tracing",
|
||||
"traitcast",
|
||||
"uuid 1.9.0",
|
||||
]
|
||||
|
||||
@ -4081,13 +4032,11 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff5addf3e73bea9ef39c44c077335f1be5e9bfa368aaa89368879fda3e931f"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"base64 0.22.1",
|
||||
"bincode",
|
||||
"cached",
|
||||
"chacha20poly1305",
|
||||
"chrono",
|
||||
"http 1.1.0",
|
||||
"jwt-simple",
|
||||
"qrcode",
|
||||
"rand 0.8.5",
|
||||
@ -6038,27 +5987,6 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "traitcast"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f80b1cde694e5ff2dcb33875530f2f031a9a34dec8ba2744cacaf80a88658740"
|
||||
dependencies = [
|
||||
"inventory",
|
||||
"lazy_static",
|
||||
"traitcast_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "traitcast_core"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aabba8f4a83963f61a84d8cfc5829b4fad692aa6c6ad5d7b08b9549777e3cc4a"
|
||||
dependencies = [
|
||||
"anymap",
|
||||
"inventory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
|
@ -35,9 +35,7 @@ impl CacheObject {
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let Some(data) = self.0 else {
|
||||
return Ok(None)
|
||||
};
|
||||
let Some(data) = self.0 else { return Ok(None) };
|
||||
bincode::deserialize(&data)
|
||||
.map_err(|e| {
|
||||
warn!("Malformed cache entry: {e}");
|
||||
|
@ -20,6 +20,8 @@ pub enum Error {
|
||||
Addresses,
|
||||
#[error("No account for identity found")]
|
||||
InvalidIdentity,
|
||||
#[error("OIDC Provider is not set up")]
|
||||
OidcNotProvided,
|
||||
}
|
||||
|
||||
pub static CLIENT_NAME: &str = "account-manager";
|
||||
@ -150,6 +152,28 @@ pub mod find_by_identity {
|
||||
pub type Output = Result<Details, Error>;
|
||||
}
|
||||
|
||||
pub mod auth_check {
|
||||
use model::*;
|
||||
|
||||
pub use crate::accounts::Error;
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Input {
|
||||
pub authorization: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Details {
|
||||
Redirect {
|
||||
location: String,
|
||||
set_cookie: String,
|
||||
},
|
||||
Accepted,
|
||||
}
|
||||
|
||||
pub type Output = Result<Details, Error>;
|
||||
}
|
||||
|
||||
impl AsyncClient {
|
||||
pub async fn emit_account_created(&self, account: &model::FullAccount) {
|
||||
self.publish_or_log(Topic::AccountCreated, QoS::AtLeastOnce, true, account)
|
||||
|
@ -12,6 +12,7 @@ actix-web = { workspace = true, features = [], optional = true }
|
||||
cookie = { workspace = true, features = ["signed"], optional = true }
|
||||
parking_lot = { workspace = true, features = [] }
|
||||
password-hash = { workspace = true, features = ["alloc"] }
|
||||
rand.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, features = [] }
|
||||
thiserror = { workspace = true }
|
||||
|
@ -357,10 +357,12 @@ pub struct IdpConfig {
|
||||
pub idm_url: String,
|
||||
#[serde(default)]
|
||||
pub secret: Option<String>,
|
||||
pub enc_key: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for IdpConfig {
|
||||
fn default() -> Self {
|
||||
use rand::{distributions, Rng};
|
||||
Self {
|
||||
rpc_port: 19329,
|
||||
rpc_bind: "0.0.0.0".into(),
|
||||
@ -369,6 +371,12 @@ impl Default for IdpConfig {
|
||||
database_url: "postgres://postgres@localhost/myco_accounts".into(),
|
||||
idm_url: "https://localhost:8443".into(),
|
||||
secret: Some("CHANGE ME".into()),
|
||||
enc_key: rand::thread_rng()
|
||||
.sample_iter(&distributions::Alphanumeric)
|
||||
.take(32)
|
||||
.map(char::from)
|
||||
.collect::<String>()
|
||||
.into_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ impl plugin_api::Plugin for RedisEventBusPlugin {
|
||||
|
||||
async fn register_event_bus(&mut self, register: &'static mut EventBusRegister) {
|
||||
let Ok((stream, sender)) = RedisEventBus::connect(self.plugin_config.clone()).await else {
|
||||
return
|
||||
return;
|
||||
};
|
||||
register.register(PLUGIN_NAME, EventBus::new(stream, sender));
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use file_storage_adapter::{Error, PluginConfig, SResult, Url};
|
||||
use futures::{AsyncRead, AsyncReadExt, TryFutureExt};
|
||||
use futures::{AsyncRead, AsyncReadExt};
|
||||
use s3::creds::Credentials;
|
||||
use s3::{Bucket, Region};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -17,7 +17,7 @@ futures = { version = "0" }
|
||||
gumdrop = { version = "0" }
|
||||
json = { version = "0" }
|
||||
model = { path = "../model", features = ['db'] }
|
||||
rauthy-client = { version = "0.4.0", features = ["actix-web", "qrcode"] }
|
||||
rauthy-client = { version = "0.4.0", features = ["qrcode", "userinfo"] }
|
||||
rumqttc = { version = "*" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
sqlx = { version = "0", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] }
|
||||
|
@ -1,6 +1,9 @@
|
||||
use channels::accounts::{all, find_by_identity, me, register};
|
||||
use channels::accounts::{all, auth_check, find_by_identity, me, register};
|
||||
use config::SharedAppConfig;
|
||||
use model::{Encrypt, FullAccount, Ranged};
|
||||
use rauthy_client::cookie_state::OidcCookieState;
|
||||
use rauthy_client::handler::{OidcCallbackParams, OidcCookieInsecure};
|
||||
use rauthy_client::principal::PrincipalOidc;
|
||||
|
||||
use crate::db::{AccountAddresses, Database, FindAccount};
|
||||
use crate::{Error, Result};
|
||||
@ -122,3 +125,142 @@ pub async fn find_by_identity(
|
||||
|
||||
Ok(find_by_identity::Details { account })
|
||||
}
|
||||
|
||||
pub async fn principal_opt_from_req(value: &str) -> Option<PrincipalOidc> {
|
||||
// split off the `Bearer ` part from the value
|
||||
let (_bearer, access_token) = value.split_once("Bearer ").unwrap_or_default();
|
||||
|
||||
if access_token.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// PrincipalOidc::from_token_validated() is the important part.
|
||||
// This will build and validate the principal from a given raw JWT access token.
|
||||
match PrincipalOidc::from_token_validated(access_token).await {
|
||||
Ok(p) => {
|
||||
// This just makes sure, that at least the user_claim is satisfied.
|
||||
// You might skip this, if just any user from Rauthy should have access
|
||||
// to this app. no matter what groups / roles he is assigned to.
|
||||
if p.is_user {
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Err(_err) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// OIDC Auth check and login
|
||||
///
|
||||
/// Endpoint with no redirect on purpose to use the result inside Javascript
|
||||
/// from the frontend. HTTP 202 means logged in Principal
|
||||
/// HTTP 406 will have a location header and a manual redirect must be done
|
||||
pub async fn get_auth_check(
|
||||
input: auth_check::Input,
|
||||
config: SharedAppConfig,
|
||||
) -> auth_check::Output {
|
||||
let enc_key = config.lock().idp().enc_key.clone();
|
||||
|
||||
// We need to get the principal manually.
|
||||
// We use our own custom helper function to extract the principal.
|
||||
let principal = principal_opt_from_req(&input.authorization).await;
|
||||
|
||||
// if we are in dev mode, we allow insecure cookies
|
||||
let insecure = if cfg!(debug_assertions) {
|
||||
OidcCookieInsecure::Yes
|
||||
} else {
|
||||
OidcCookieInsecure::No
|
||||
};
|
||||
|
||||
match rauthy_client::handler::validate_principal_generic(principal, &enc_key, insecure).await {
|
||||
Ok(()) => Ok(auth_check::Details::Accepted),
|
||||
Err(header_values) => match header_values {
|
||||
// we return HTTP 406 to not trigger default browser behavior from 401
|
||||
Some((loc, state)) => Ok(auth_check::Details::Redirect {
|
||||
location: loc,
|
||||
set_cookie: state,
|
||||
}),
|
||||
None => Err(auth_check::Error::OidcNotProvided),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// OIDC Callback endpoint - must match the `redirect_uri` for the login flow
|
||||
pub async fn get_callback(
|
||||
req: HttpRequest,
|
||||
config: ConfigExt,
|
||||
// How you extract these OidcCallbackParams depends on your setup.
|
||||
// These are query parameters that are being appended to the callback URL.
|
||||
// You just need to extract these in whatever way makes sense to you.
|
||||
// We need `code` and `state` params here to proceed.
|
||||
params: Query<OidcCallbackParams>,
|
||||
) -> HttpResponse {
|
||||
let enc_key = config.enc_key.as_slice();
|
||||
|
||||
// We need to manually extract the state cookie
|
||||
let cookie_state = match req.cookie(rauthy_client::cookie_state::OIDC_STATE_COOKIE) {
|
||||
None => {
|
||||
tracing::warn!("STATE_COOKIE is missing - Request may have expired");
|
||||
return HttpResponse::BadRequest()
|
||||
.body("STATE_COOKIE is missing - Request may have expired");
|
||||
}
|
||||
Some(cookie) => match OidcCookieState::from_cookie_value(cookie.value(), enc_key) {
|
||||
Ok(cookie_state) => cookie_state,
|
||||
Err(err) => {
|
||||
return HttpResponse::BadRequest().body(err.to_string());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// The `DEV_MODE` again here to just have a nicer DX when developing -> we allow
|
||||
// insecure cookies
|
||||
let insecure = if DEV_MODE {
|
||||
OidcCookieInsecure::Yes
|
||||
} else {
|
||||
OidcCookieInsecure::No
|
||||
};
|
||||
|
||||
let callback_res =
|
||||
rauthy_client::handler::oidc_callback(cookie_state, params.into_inner(), insecure).await;
|
||||
let (cookie_str, token_set, _id_claims) = match callback_res {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
return HttpResponse::BadRequest().body(format!("Invalid OIDC Callback: {}", err))
|
||||
}
|
||||
};
|
||||
|
||||
// At this point, the redirect was valid and everything was fine.
|
||||
// Depending on how you like to proceed, you could create an independant session
|
||||
// for the user, or maybe create just another factor of authentication like
|
||||
// a CSRF token. Otherwise, you could just go on and using the existing
|
||||
// access token for further authentication.
|
||||
//
|
||||
// For the sake of this example, we will return the raw access token to the user
|
||||
// via the HTML so we can use it for future authentication from the
|
||||
// frontend, but this is really up to you and the security needs of your
|
||||
// application.
|
||||
|
||||
// This is a very naive approach to HTML templating and only for simplicity in
|
||||
// this example. Please don't do this in production and use a proper
|
||||
// templating engine.
|
||||
let body = templates::HTML_CALLBACK
|
||||
.replace("{{ TOKEN }}", &token_set.access_token)
|
||||
.replace("{{ URI }}", "/");
|
||||
|
||||
HttpResponse::Ok()
|
||||
.append_header((SET_COOKIE, cookie_str))
|
||||
.append_header((CONTENT_TYPE, "text/html"))
|
||||
.body(body)
|
||||
}
|
||||
|
||||
pub async fn get_protected(req: HttpRequest) -> Result<HttpResponse, actix_web::Error> {
|
||||
let principal = extractors::principal_from_req(&req).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().body(format!(
|
||||
"Hello from Protected Resource:<br/>{:?}",
|
||||
principal
|
||||
)))
|
||||
}
|
||||
*/
|
||||
|
@ -4,7 +4,7 @@ use config::UpdateConfig;
|
||||
|
||||
pub mod actions;
|
||||
pub mod db;
|
||||
pub mod idp;
|
||||
// pub mod idp;
|
||||
pub mod mqtt;
|
||||
pub mod rpc;
|
||||
|
||||
@ -39,23 +39,6 @@ async fn main() {
|
||||
|
||||
let db = db::Database::build(config.clone()).await;
|
||||
|
||||
let kanidm = kanidm_client::KanidmClientBuilder::new()
|
||||
.address(config.lock().idp().idm_url().to_owned())
|
||||
.danger_accept_invalid_certs(cfg!(debug_assertions))
|
||||
.connect_timeout(2)
|
||||
.build()
|
||||
.unwrap();
|
||||
idp::accounts(&kanidm).await.unwrap();
|
||||
idp::create_account_with_password(
|
||||
&kanidm,
|
||||
"eraden",
|
||||
"Adrian Woźniak",
|
||||
"adrian.wozniak@ita-prog.pl",
|
||||
"n59GmOOdcpVUJqJ1",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mqtt_client = mqtt::start(config.clone(), db.clone()).await;
|
||||
rpc::start(config.clone(), db.clone(), mqtt_client.clone()).await;
|
||||
}
|
||||
|
@ -14,5 +14,5 @@ serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
toml = { version = "0.7.3" }
|
||||
tracing = { version = "0" }
|
||||
traitcast = { workspace = true }
|
||||
# traitcast = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
|
@ -1,28 +1,44 @@
|
||||
version: '3'
|
||||
services:
|
||||
kanidm-server:
|
||||
image: kanidm/server:latest
|
||||
volumes:
|
||||
- kanidmd:/data
|
||||
- ./config/kanidm.toml:/data/server.toml
|
||||
- ./config/ca.pem:/data/ca.pem
|
||||
- ./config/ca.key:/data/ca.key
|
||||
ports:
|
||||
- 636:3636
|
||||
- 443:8443
|
||||
- 8443:8443
|
||||
- 8400:80
|
||||
rumqqtd:
|
||||
image: bytebeamio/rumqttd
|
||||
ports:
|
||||
- 1883:1883
|
||||
- 1884:1884
|
||||
rauthy:
|
||||
image: ghcr.io/sebadob/rauthy:0.23.5-lite
|
||||
rauthy-nginx:
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 80:80
|
||||
volumes:
|
||||
# /etc/nginx/conf.d/*.conf;
|
||||
- ./config/rauth.nginx:/etc/nginx/conf.d/rauth.conf:ro
|
||||
depends_on:
|
||||
- rauthy
|
||||
|
||||
rauthy-psql:
|
||||
image: postgres:latest
|
||||
environment:
|
||||
POSTGRES_PASSWORD: 123SuperSafe
|
||||
POSTGRES_USER: rauthy
|
||||
POSTGRES_DB: rauthy
|
||||
DATABASE_URL: postgresql://rauthy:123SuperSafe@localhost:5432/rauthy
|
||||
volumes:
|
||||
- rauthy-psql:/var/lib/postgresql/data
|
||||
|
||||
rauthy:
|
||||
image: ghcr.io/sebadob/rauthy:0.23.5
|
||||
depends_on:
|
||||
- rauthy-psql
|
||||
ports:
|
||||
- 8302:8302
|
||||
- 8301:9090
|
||||
environment:
|
||||
COOKIE_MODE: danger-insecure
|
||||
SWAGGER_UI_EXTERNAL: true
|
||||
volumes:
|
||||
- rauthy:/app/data
|
||||
- ./config/rauthy.cfg:/app/rauthy.cfg
|
||||
|
||||
quickwit:
|
||||
image: quickwit/quickwit:v0.5.2
|
||||
command: run
|
||||
@ -30,9 +46,9 @@ services:
|
||||
environment:
|
||||
QW_ENABLE_OTLP_ENDPOINT: true
|
||||
QW_ENABLE_JAEGER_ENDPOINT: true
|
||||
ports:
|
||||
- '7280:7280'
|
||||
- '7281:7281'
|
||||
# ports:
|
||||
# - '7280:7280'
|
||||
# - '7281:7281'
|
||||
# volumes:
|
||||
# - ./qwdata:/quickwit/qwdata
|
||||
|
||||
@ -44,8 +60,8 @@ services:
|
||||
environment:
|
||||
SPAN_STORAGE_TYPE: 'grpc-plugin'
|
||||
GRPC_STORAGE_SERVER: 'quickwit:7281'
|
||||
ports:
|
||||
- '16686:16686'
|
||||
# ports:
|
||||
# - '16686:16686'
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana-enterprise:10.0.0
|
||||
@ -59,5 +75,14 @@ services:
|
||||
volumes:
|
||||
- ./grafana/plugins:/var/lib/grafana/plugins
|
||||
|
||||
mailcrab:
|
||||
image: 'marlonb/mailcrab:latest'
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 1125:1025
|
||||
- 1180:1080
|
||||
|
||||
volumes:
|
||||
kanidmd:
|
||||
rauthy:
|
||||
rauthy-psql:
|
||||
|
Loading…
Reference in New Issue
Block a user