1812 1813 post axum cleanup (#1817)

This commit is contained in:
Firstyear 2023-07-06 19:34:53 +10:00 committed by GitHub
parent 9f886b85dd
commit d1f51f0a84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 280 additions and 213 deletions

View file

@ -0,0 +1,78 @@
use axum::{
async_trait,
extract::{ConnectInfo, FromRequestParts},
http::{header::HeaderName, request::Parts, StatusCode},
RequestPartsExt,
};
use std::net::{IpAddr, SocketAddr};
use crate::https::ServerState;
#[allow(clippy::declare_interior_mutable_const)]
const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
pub struct TrustedClientIp(pub IpAddr);
#[async_trait]
impl FromRequestParts<ServerState> for TrustedClientIp {
type Rejection = (StatusCode, &'static str);
#[instrument(level = "debug", skip(state))]
async fn from_request_parts(
parts: &mut Parts,
state: &ServerState,
) -> Result<Self, Self::Rejection> {
if state.trust_x_forward_for {
if let Some(x_forward_for) = parts.headers.get(X_FORWARDED_FOR) {
// X forward for may be comma separate.
let first = x_forward_for
.to_str()
.map(|s|
// Split on an optional comma, return the first result.
s.split(',').next().unwrap_or(s))
.map_err(|_| {
(
StatusCode::BAD_REQUEST,
"X-Forwarded-For contains invalid data",
)
})?;
first.parse::<IpAddr>().map(TrustedClientIp).map_err(|_| {
(
StatusCode::BAD_REQUEST,
"X-Forwarded-For contains invalid ip addr",
)
})
} else {
let ConnectInfo(addr) =
parts
.extract::<ConnectInfo<SocketAddr>>()
.await
.map_err(|_| {
error!("Connect info contains invalid IP address");
(
StatusCode::BAD_REQUEST,
"connect info contains invalid IP address",
)
})?;
Ok(TrustedClientIp(addr.ip()))
}
} else {
let ConnectInfo(addr) =
parts
.extract::<ConnectInfo<SocketAddr>>()
.await
.map_err(|_| {
error!("Connect info contains invalid IP address");
(
StatusCode::BAD_REQUEST,
"connect info contains invalid IP address",
)
})?;
Ok(TrustedClientIp(addr.ip()))
}
}
}

View file

@ -3,7 +3,7 @@ use axum::{
http::{self, Request},
middleware::Next,
response::Response,
Extension, TypedHeader,
TypedHeader,
};
use axum_sessions::SessionHandle;
use http::HeaderValue;
@ -21,7 +21,6 @@ pub async fn version_middleware<B>(request: Request<B>, next: Next<B>) -> Respon
let mut response = next.run(request).await;
let headers = response.headers_mut();
headers.insert("X-KANIDM-VERSION", HeaderValue::from_static(KANIDM_VERSION));
response
}
@ -32,16 +31,9 @@ pub struct KOpId {
pub uat: Option<String>,
}
impl KOpId {
/// Return the event ID as a string
pub fn eventid_value(&self) -> String {
let res = self.eventid;
res.as_hyphenated().to_string()
}
}
/// This runs at the start of the request, adding an extension with `KOpId` which has useful things inside it.
pub async fn kopid_start<B>(
#[instrument(name = "request", skip_all)]
pub async fn kopid_middleware<B>(
auth: Option<TypedHeader<Authorization<Bearer>>>,
mut request: Request<B>,
next: Next<B>,
@ -66,23 +58,12 @@ pub async fn kopid_start<B>(
// insert the extension so we can pull it out later
request.extensions_mut().insert(KOpId { eventid, uat });
next.run(request).await
}
/// This runs at the start of the request, adding an extension with the OperationID
pub async fn kopid_end<B>(
Extension(kopid): Extension<KOpId>,
request: Request<B>,
next: Next<B>,
) -> Response {
// generate the event ID
// insert the extension so we can pull it out later
let mut response = next.run(request).await;
#[allow(clippy::unwrap_used)]
response.headers_mut().insert(
"X-KANIDM-OPID",
HeaderValue::from_str(&kopid.eventid_value()).unwrap(),
HeaderValue::from_str(&eventid.as_hyphenated().to_string()).unwrap(),
);
response

View file

@ -1,3 +1,4 @@
mod extractors;
mod generic;
mod javascript;
mod manifest;
@ -60,7 +61,7 @@ pub struct ServerState {
pub jws_validator: compact_jwt::JwsValidator,
// The SHA384 hashes of javascript files we're going to serve to users
pub js_files: Vec<JavaScriptFile>,
// pub(crate) trust_x_forward_for: bool,
pub(crate) trust_x_forward_for: bool,
pub csp_header: HeaderValue,
}
@ -133,8 +134,7 @@ pub fn get_js_files(role: ServerRole) -> Vec<JavaScriptFile> {
pub async fn create_https_server(
config: Configuration,
// trust_x_forward_for: bool, // TODO: #1787 make XFF headers work
cookie_key: [u8; 32],
cookie_key: [u8; 64],
jws_signer: JwsSigner,
status_ref: &'static StatusActor,
qe_w_ref: &'static QueryServerWriteV1,
@ -186,15 +186,16 @@ pub async fn create_https_server(
);
let store = async_session::CookieStore::new();
let secret = format!("{:?}", cookie_key);
let secret = secret.as_bytes(); // TODO the cookie/session secret needs to be longer?
let session_layer = SessionLayer::new(store, secret)
let session_layer = SessionLayer::new(store, &cookie_key)
.with_cookie_name("kanidm-session")
.with_session_ttl(None)
.with_cookie_domain(config.domain)
.with_same_site_policy(SameSite::Strict)
.with_secure(true);
let trust_x_forward_for = config.trust_x_forward_for;
let state = ServerState {
status_ref,
qe_w_ref,
@ -202,6 +203,7 @@ pub async fn create_https_server(
jws_signer,
jws_validator,
js_files,
trust_x_forward_for,
csp_header: csp_header.finish(),
};
@ -219,15 +221,13 @@ pub async fn create_https_server(
ServerRole::WriteReplicaNoUI => Router::new(),
};
// // == oauth endpoints.
// TODO: turn this from a nest into a merge because state things are bad in nested routes
let app = Router::new()
.nest("/oauth2", oauth2::oauth2_route_setup(state.clone()))
.nest("/scim", v1_scim::scim_route_setup())
.route("/robots.txt", get(robots_txt))
.nest("/v1", v1::router(state.clone()))
// Shared account features only - mainly this is for unix-like features.
.route("/status", get(status));
.route("/status", get(status))
.merge(oauth2::oauth2_route_setup(state.clone()))
.merge(v1_scim::scim_route_setup())
.merge(v1::router(state.clone()));
let app = match config.role {
ServerRole::WriteReplicaNoUI => app,
ServerRole::WriteReplica | ServerRole::ReadOnlyReplica => {
@ -250,11 +250,14 @@ pub async fn create_https_server(
middleware::csp_headers::cspheaders_layer,
))
.layer(from_fn(middleware::version_middleware))
.layer(from_fn(middleware::kopid_end))
.layer(from_fn(middleware::kopid_start))
.layer(session_layer)
.with_state(state)
.layer(TraceLayer::new_for_http())
// This must be the LAST middleware.
// This is because the last middleware here is the first to be entered and the last
// to be exited, and this middleware sets up ids' and other bits for for logging
// coherence to be maintained.
.layer(from_fn(middleware::kopid_middleware))
.with_state(state)
// the connect_info bit here lets us pick up the remote address of the client
.into_make_service_with_connect_info::<SocketAddr>();
@ -301,7 +304,7 @@ async fn server_loop(
let mut tls_builder = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls())?;
let mut app = app;
tls_builder
.set_certificate_file(tls_param.chain.clone(), SslFiletype::PEM)
.set_certificate_chain_file(tls_param.chain.clone())
.map_err(|err| {
std::io::Error::new(
ErrorKind::Other,

View file

@ -59,7 +59,6 @@ pub async fn oauth2_id_get(
Path(rs_name): Path<String>,
Extension(kopid): Extension<KOpId>,
) -> impl IntoResponse {
let filter = oauth2_id(&rs_name);
let res = state
@ -227,8 +226,6 @@ pub async fn oauth2_id_delete(
// valid Kanidm instance in the topology can handle these request.
//
pub async fn oauth2_authorise_post(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
@ -349,8 +346,8 @@ async fn oauth2_authorise(
// we do NOT redirect in an error condition, and just render the error ourselves.
Err(e) => {
admin_error!(
"Unable to authorise - Error ID: {} error: {}",
&kopid.eventid_value(),
"Unable to authorise - Error ID: {:?} error: {}",
kopid.eventid,
&e.to_string()
);
#[allow(clippy::unwrap_used)]
@ -514,7 +511,7 @@ async fn oauth2_authorise_reject(
pub async fn oauth2_token_post(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
headers: HeaderMap, // TOOD: make this a typed basic auth header
headers: HeaderMap, // TODO: make this a typed basic auth header
Form(tok_req): Form<AccessTokenRequest>,
) -> impl IntoResponse {
// This is called directly by the resource server, where we then issue
@ -834,52 +831,58 @@ pub async fn oauth2_token_revoke_post(
pub fn oauth2_route_setup(state: ServerState) -> Router<ServerState> {
// this has all the openid-related routes
let openid_router = Router::new() // appserver.at("/oauth2/openid");
let openid_router = Router::new()
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route(
"/:client_id/.well-known/openid-configuration",
"/oauth2/openid/:client_id/.well-known/openid-configuration",
get(oauth2_openid_discovery_get),
)
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route("/:client_id/userinfo", get(oauth2_openid_userinfo_get))
.route(
"/oauth2/openid/:client_id/userinfo",
get(oauth2_openid_userinfo_get),
)
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route(
"/:client_id/public_key.jwk",
"/oauth2/openid/:client_id/public_key.jwk",
get(oauth2_openid_publickey_get),
)
.with_state(state.clone());
Router::new() //= appserver.at("/oauth2");
.route("/", get(oauth2_get))
Router::new()
.route("/oauth2", get(oauth2_get))
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route(
"/authorise",
"/oauth2/authorise",
post(oauth2_authorise_post).get(oauth2_authorise_get),
)
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route(
"/authorise/permit",
"/oauth2/authorise/permit",
post(oauth2_authorise_permit_post).get(oauth2_authorise_permit_get),
)
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route(
"/authorise/reject",
"/oauth2/authorise/reject",
post(oauth2_authorise_reject_post).get(oauth2_authorise_reject_get),
)
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route("/token", post(oauth2_token_post))
.route("/oauth2/token", post(oauth2_token_post))
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route("/token/introspect", post(oauth2_token_introspect_post))
.route("/token/revoke", post(oauth2_token_revoke_post))
.nest("/openid", openid_router)
.route(
"/oauth2/token/introspect",
post(oauth2_token_introspect_post),
)
.route("/oauth2/token/revoke", post(oauth2_token_revoke_post))
.merge(openid_router)
.with_state(state)
.layer(from_fn(super::middleware::caching::dont_cache_me))
}

View file

@ -1,9 +1,6 @@
use std::net::SocketAddr;
#[allow(unused_imports)]
// //! The V1 API things!
use std::str::FromStr;
//! The V1 API things!
use axum::extract::{ConnectInfo, Path, Query, State};
use axum::extract::{Path, Query, State};
use axum::headers::{CacheControl, HeaderMapExt};
use axum::middleware::from_fn;
use axum::response::{IntoResponse, Response};
@ -29,6 +26,7 @@ use kanidmd_lib::value::PartialValue;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::https::extractors::TrustedClientIp;
use crate::https::to_axum_response;
use super::middleware::caching::dont_cache_me;
@ -1244,24 +1242,15 @@ pub async fn applinks_get(
pub async fn reauth(
State(state): State<ServerState>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, // TODO: test x-ff-headers
TrustedClientIp(ip_addr): TrustedClientIp,
Extension(kopid): Extension<KOpId>,
session: WritableSession,
Json(obj): Json<AuthIssueSession>,
) -> impl IntoResponse {
// TODO: xff things check that we can get the remote IP address first, since this doesn't touch the backend at all
// let ip_addr = req.get_remote_addr().ok_or_else(|| {
// error!("Unable to get remote addr for auth event, refusing to proceed");
// tide::Error::from_str(
// tide::StatusCode::InternalServerError,
// "unable to validate peer address",
// )
// })?;
// This may change in the future ...
let inter = state
.qe_r_ref
.handle_reauth(kopid.uat, obj, kopid.eventid, addr.ip())
.handle_reauth(kopid.uat, obj, kopid.eventid, ip_addr)
.await;
debug!("REAuth result: {:?}", inter);
auth_session_state_management(state, inter, session)
@ -1269,28 +1258,16 @@ pub async fn reauth(
pub async fn auth(
State(state): State<ServerState>,
TrustedClientIp(ip_addr): TrustedClientIp,
session: WritableSession,
headers: HeaderMap,
Extension(kopid): Extension<KOpId>,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
Json(obj): Json<AuthRequest>,
) -> impl IntoResponse {
// TODO: check this trusts the x-ff-header
let ip_addr = addr.ip();
// check that we can get the remote IP address first, since this doesn't touch the backend at all
// let ip_addr = req.get_remote_addr().ok_or_else(|| {
// error!("Unable to get remote addr for auth event, refusing to proceed");
// tide::Error::from_str(
// tide::StatusCode::InternalServerError,
// "unable to validate peer address",
// )
// })?;
// First, deal with some state management.
// Do anything here first that's needed like getting the session details
// out of the req cookie.
// TODO
let maybe_sessionid = state.get_current_auth_session_id(&headers, &session);
debug!("Session ID: {:?}", maybe_sessionid);
// We probably need to know if we allocate the cookie, that this is a
@ -1434,281 +1411,295 @@ pub async fn auth_valid(
#[instrument(skip(state))]
pub fn router(state: ServerState) -> Router<ServerState> {
Router::new()
.route("/oauth2", get(super::oauth2::oauth2_get))
.route("/v1/oauth2", get(super::oauth2::oauth2_get))
.route(
"/oauth2/:rs_name",
"/v1/oauth2/:rs_name",
get(super::oauth2::oauth2_id_get)
.patch(super::oauth2::oauth2_id_patch)
.delete(super::oauth2::oauth2_id_delete),
)
.route(
"/oauth2/:rs_name/_basic_secret",
"/v1/oauth2/:rs_name/_basic_secret",
get(super::oauth2::oauth2_id_get_basic_secret),
)
.route("/oauth2/_basic", post(super::oauth2::oauth2_basic_post))
.route("/v1/oauth2/_basic", post(super::oauth2::oauth2_basic_post))
.route(
"/oauth2/:rs_name/_scopemap/:group",
"/v1/oauth2/:rs_name/_scopemap/:group",
post(super::oauth2::oauth2_id_scopemap_post)
.delete(super::oauth2::oauth2_id_scopemap_delete),
)
.route(
"/oauth2/:rs_name/_sup_scopemap/:group",
"/v1/oauth2/:rs_name/_sup_scopemap/:group",
post(super::oauth2::oauth2_id_sup_scopemap_post)
.delete(super::oauth2::oauth2_id_sup_scopemap_delete),
)
.route("/raw/create", post(create))
.route("/raw/modify", post(v1_modify))
.route("/raw/delete", post(v1_delete))
.route("/raw/search", post(search))
.route("/schema", get(schema_get))
.route("/v1/raw/create", post(create))
.route("/v1/raw/modify", post(v1_modify))
.route("/v1/raw/delete", post(v1_delete))
.route("/v1/raw/search", post(search))
.route("/v1/schema", get(schema_get))
.route(
"/schema/attributetype",
"/v1/schema/attributetype",
get(schema_attributetype_get), // post(|| async { "TODO" })
)
.route(
"/schema/attributetype/:id",
"/v1/schema/attributetype/:id",
get(schema_attributetype_get_id),
)
// .route("/schema/attributetype/:id", put(|| async { "TODO" }).patch(|| async { "TODO" }))
.route(
"/schema/classtype",
"/v1/schema/classtype",
get(schema_classtype_get), // .post(|| async { "TODO" })
)
.route(
"/schema/classtype/:id",
"/v1/schema/classtype/:id",
get(schema_classtype_get_id)
.put(|| async { "TODO" })
.patch(|| async { "TODO" }),
)
.route("/self", get(whoami))
.route("/self/_uat", get(whoami_uat))
.route("/self/_attr/:attr", get(|| async { "TODO" }))
.route("/self/_credential", get(|| async { "TODO" }))
.route("/self/_credential/:cid/_lock", get(|| async { "TODO" }))
.route("/v1/self", get(whoami))
.route("/v1/self/_uat", get(whoami_uat))
.route("/v1/self/_attr/:attr", get(|| async { "TODO" }))
.route("/v1/self/_credential", get(|| async { "TODO" }))
.route("/v1/self/_credential/:cid/_lock", get(|| async { "TODO" }))
.route(
"/self/_radius",
"/v1/self/_radius",
get(|| async { "TODO" })
.delete(|| async { "TODO" })
.post(|| async { "TODO" }),
)
.route("/self/_radius/_config", post(|| async { "TODO" }))
.route("/self/_radius/_config/:token", get(|| async { "TODO" }))
.route("/v1/self/_radius/_config", post(|| async { "TODO" }))
.route("/v1/self/_radius/_config/:token", get(|| async { "TODO" }))
.route(
"/self/_radius/_config/:token/apple",
"/v1/self/_radius/_config/:token/apple",
get(|| async { "TODO" }),
)
// Applinks are the list of apps this account can access.
.route("/self/_applinks", get(applinks_get))
.route("/v1/self/_applinks", get(applinks_get))
// Person routes
.route("/person", get(person_get))
.route("/person", post(person_post))
.route("/v1/person", get(person_get))
.route("/v1/person", post(person_post))
.route(
"/person/:id",
"/v1/person/:id",
get(person_id_get)
.patch(account_id_patch)
.delete(person_account_id_delete),
)
.route(
"/person/:id/_attr/:attr",
"/v1/person/:id/_attr/:attr",
get(account_id_get_attr)
.put(account_id_put_attr)
.post(account_id_post_attr)
.delete(account_id_delete_attr),
)
.route("/person/:id/_lock", get(|| async { "TODO" }))
.route("/person/:id/_credential", get(|| async { "TODO" }))
.route("/v1/person/:id/_lock", get(|| async { "TODO" }))
.route("/v1/person/:id/_credential", get(|| async { "TODO" }))
.route(
"/person/:id/_credential/_status",
"/v1/person/:id/_credential/_status",
get(account_get_id_credential_status),
)
.route(
"/person/:id/_credential/:cid/_lock",
"/v1/person/:id/_credential/:cid/_lock",
get(|| async { "TODO" }),
)
.route(
"/person/:id/_credential/_update",
"/v1/person/:id/_credential/_update",
get(account_get_id_credential_update),
)
.route(
"/person/:id/_credential/_update_intent/:ttl",
"/v1/person/:id/_credential/_update_intent/:ttl",
get(account_get_id_credential_update_intent_ttl),
)
.route(
"/person/:id/_credential/_update_intent",
"/v1/person/:id/_credential/_update_intent",
get(account_get_id_credential_update_intent),
)
.route(
"/person/:id/_ssh_pubkeys",
"/v1/person/:id/_ssh_pubkeys",
get(account_get_id_ssh_pubkeys).post(account_post_id_ssh_pubkey),
)
.route(
"/person/:id/_ssh_pubkeys/:tag",
"/v1/person/:id/_ssh_pubkeys/:tag",
get(account_get_id_ssh_pubkey_tag).delete(account_delete_id_ssh_pubkey_tag),
)
.route(
"/person/:id/_radius",
"/v1/person/:id/_radius",
get(account_get_id_radius)
.post(account_post_id_radius_regenerate)
.delete(account_delete_id_radius),
)
.route(
"/person/:id/_radius/_token",
"/v1/person/:id/_radius/_token",
get(account_get_id_radius_token),
) // TODO: make this cacheable
.route("/person/:id/_unix", post(account_post_id_unix))
.route("/v1/person/:id/_unix", post(account_post_id_unix))
.route(
"/person/:id/_unix/_credential",
"/v1/person/:id/_unix/_credential",
put(account_put_id_unix_credential).delete(account_delete_id_unix_credential),
)
// Service accounts
.route(
"/service_account",
"/v1/service_account",
get(service_account_get).post(service_account_post),
)
.route(
"/service_account/",
"/v1/service_account/",
get(service_account_get).post(service_account_post),
)
.route(
"/service_account/:id",
"/v1/service_account/:id",
get(service_account_id_get).delete(service_account_id_delete),
)
.route(
"/service_account/:id/_attr/:attr",
"/v1/service_account/:id/_attr/:attr",
get(account_id_get_attr)
.put(account_id_put_attr)
.post(account_id_post_attr)
.delete(account_id_delete_attr),
)
.route("/service_account/:id/_lock", get(|| async { "TODO" }))
.route("/v1/service_account/:id/_lock", get(|| async { "TODO" }))
.route(
"/service_account/:id/_into_person",
"/v1/service_account/:id/_into_person",
post(service_account_into_person),
)
.route(
"/service_account/:id/_api_token",
"/v1/service_account/:id/_api_token",
post(service_account_api_token_post).get(service_account_api_token_get),
)
.route(
"/service_account/:id/_api_token/:token_id",
"/v1/service_account/:id/_api_token/:token_id",
delete(service_account_api_token_delete),
)
.route("/service_account/:id/_credential", get(|| async { "TODO" }))
.route(
"/service_account/:id/_credential/_generate",
get(service_account_credential_generate),
)
.route(
"/service_account/:id/_credential/_status",
get(account_get_id_credential_status),
)
.route(
"/service_account/:id/_credential/:cid/_lock",
"/v1/service_account/:id/_credential",
get(|| async { "TODO" }),
)
.route(
"/service_account/:id/_ssh_pubkeys",
"/v1/service_account/:id/_credential/_generate",
get(service_account_credential_generate),
)
.route(
"/v1/service_account/:id/_credential/_status",
get(account_get_id_credential_status),
)
.route(
"/v1/service_account/:id/_credential/:cid/_lock",
get(|| async { "TODO" }),
)
.route(
"/v1/service_account/:id/_ssh_pubkeys",
get(account_get_id_ssh_pubkeys).post(account_post_id_ssh_pubkey),
)
.route(
"/service_account/:id/_ssh_pubkeys/:tag",
"/v1/service_account/:id/_ssh_pubkeys/:tag",
get(account_get_id_ssh_pubkey_tag).delete(account_delete_id_ssh_pubkey_tag),
)
.route("/service_account/:id/_unix", post(account_post_id_unix))
.route("/account/:id/_unix/_auth", post(account_post_id_unix_auth))
.route("/v1/service_account/:id/_unix", post(account_post_id_unix))
.route(
"/account/:id/_unix/_token",
"/v1/account/:id/_unix/_auth",
post(account_post_id_unix_auth),
)
.route(
"/v1/account/:id/_unix/_token",
post(account_get_id_unix_token).get(account_get_id_unix_token), // TODO: make this cacheable
)
.route(
"/account/:id/_radius/_token",
"/v1/account/:id/_radius/_token",
post(account_get_id_radius_token).get(account_get_id_radius_token), // TODO: make this cacheable
)
.route("/account/:id/_ssh_pubkeys", get(account_get_id_ssh_pubkeys))
.route(
"/account/:id/_ssh_pubkeys/:tag",
"/v1/account/:id/_ssh_pubkeys",
get(account_get_id_ssh_pubkeys),
)
.route(
"/v1/account/:id/_ssh_pubkeys/:tag",
get(account_get_id_ssh_pubkey_tag),
)
.route(
"/account/:id/_user_auth_token",
"/v1/account/:id/_user_auth_token",
get(account_get_id_user_auth_token),
)
.route(
"/account/:id/_user_auth_token/:token_id",
"/v1/account/:id/_user_auth_token/:token_id",
delete(account_user_auth_token_delete),
)
.route(
"/credential/_exchange_intent",
"/v1/credential/_exchange_intent",
post(credential_update_exchange_intent),
)
.route("/credential/_status", post(credential_update_status))
.route("/credential/_update", post(credential_update_update))
.route("/credential/_commit", post(credential_update_commit))
.route("/credential/_cancel", post(credential_update_cancel))
.route("/v1/credential/_status", post(credential_update_status))
.route("/v1/credential/_update", post(credential_update_update))
.route("/v1/credential/_commit", post(credential_update_commit))
.route("/v1/credential/_cancel", post(credential_update_cancel))
// domain-things
.route("/domain", get(domain_get))
.route("/v1/domain", get(domain_get))
.route(
"/domain/_attr/:attr",
"/v1/domain/_attr/:attr",
get(domain_get_attr)
.put(domain_put_attr)
.delete(domain_delete_attr),
)
.route("/group/:id/_unix/_token", get(group_get_id_unix_token))
.route("/group/:id/_unix", post(group_post_id_unix))
.route("/group", get(group_get).post(group_post))
.route("/group/:id", get(group_id_get).delete(group_id_delete))
.route("/v1/group/:id/_unix/_token", get(group_get_id_unix_token))
.route("/v1/group/:id/_unix", post(group_post_id_unix))
.route("/v1/group", get(group_get).post(group_post))
.route("/v1/group/:id", get(group_id_get).delete(group_id_delete))
.route(
"/group/:id/_attr/:attr",
"/v1/group/:id/_attr/:attr",
delete(group_id_delete_attr)
.get(group_id_get_attr)
.put(group_id_put_attr)
.post(group_id_post_attr),
)
.with_state(state.clone())
.route("/system", get(system_get))
.route("/v1/system", get(system_get))
.route(
"/system/_attr/:attr",
"/v1/system/_attr/:attr",
get(system_get_attr)
.post(system_post_attr)
.delete(system_delete_attr),
)
.route("/recycle_bin", get(recycle_bin_get))
.route("/recycle_bin/:id", get(recycle_bin_id_get))
.route("/recycle_bin/:id/_revive", post(recycle_bin_revive_id_post))
.route("/access_profile", get(|| async { "TODO" }))
.route("/access_profile/:id", get(|| async { "TODO" }))
.route("/access_profile/:id/_attr/:attr", get(|| async { "TODO" }))
.route("/auth", post(auth))
.route("/auth/valid", get(auth_valid))
.route("/logout", get(logout))
.route("/reauth", post(reauth))
.route("/v1/recycle_bin", get(recycle_bin_get))
.route("/v1/recycle_bin/:id", get(recycle_bin_id_get))
.route(
"/sync_account",
"/v1/recycle_bin/:id/_revive",
post(recycle_bin_revive_id_post),
)
.route("/v1/access_profile", get(|| async { "TODO" }))
.route("/v1/access_profile/:id", get(|| async { "TODO" }))
.route(
"/v1/access_profile/:id/_attr/:attr",
get(|| async { "TODO" }),
)
.route("/v1/auth", post(auth))
.route("/v1/auth/valid", get(auth_valid))
.route("/v1/logout", get(logout))
.route("/v1/reauth", post(reauth))
.route(
"/v1/sync_account",
get(sync_account_get).post(sync_account_post),
)
.route(
"/sync_account/",
"/v1/sync_account/",
get(sync_account_get).post(sync_account_post),
)
.route(
"/sync_account/:id",
"/v1/sync_account/:id",
get(sync_account_id_get).patch(sync_account_id_patch),
)
.route(
"/sync_account/:id/_attr/:attr",
"/v1/sync_account/:id/_attr/:attr",
get(sync_account_id_get_attr).put(sync_account_id_put_attr),
)
.route(
"/sync_account/:id/_finalise",
"/v1/sync_account/:id/_finalise",
get(sync_account_id_get_finalise),
)
.route(
"/sync_account/:id/_terminate",
"/v1/sync_account/:id/_terminate",
get(sync_account_id_get_terminate),
)
.route(
"/sync_account/:id/_sync_token",
// .get(&mut sync_account_token_get)
"/v1/sync_account/:id/_sync_token",
post(sync_account_token_post).delete(sync_account_token_delete),
)
.with_state(state)

View file

@ -254,6 +254,6 @@ pub fn scim_route_setup() -> Router<ServerState> {
//
// POST Send a sync update
//
.route("/v1/Sync", post(scim_sync_post).get(scim_sync_get))
.route("/v1/Sink", get(scim_sink_get))
.route("/scim/v1/Sync", post(scim_sync_post).get(scim_sync_get))
.route("/scim/v1/Sink", get(scim_sink_get))
}

View file

@ -762,7 +762,7 @@ pub async fn create_server_core(
}
};
let cookie_key: [u8; 32] = idms.get_cookie_key();
let cookie_key: [u8; 64] = idms.get_cookie_key();
// Any pre-start tasks here.
match &config.integration_test_config {

View file

@ -126,21 +126,15 @@ async fn main() -> ExitCode {
}
};
// if they specified it in the environment then that overrides everything
let log_filter = match EnvFilter::try_from_default_env() {
Ok(val) => val,
Err(_e) => {
// we couldn't get it from the env, so we'll try the config file!
match sconfig.as_ref() {
Some(val) => {
let tmp = val.log_level.clone();
tmp.unwrap_or_default()
}
None => LogLevel::Info,
}
.into()
// We only allow config file for log level now.
let log_filter: EnvFilter = match sconfig.as_ref() {
Some(val) => {
let tmp = val.log_level.clone();
tmp.unwrap_or_default()
}
};
None => LogLevel::Info,
}
.into();
// TODO: only send to stderr when we're not in a TTY
tracing_forest::worker_task()

View file

@ -585,7 +585,7 @@ lazy_static! {
"description",
Value::new_utf8s("System (local) info and metadata object.")
),
("version", Value::Uint32(12))
("version", Value::Uint32(13))
);
}

View file

@ -66,7 +66,7 @@ pub struct DomainKeys {
pub(crate) uat_jwt_signer: JwsSigner,
pub(crate) uat_jwt_validator: JwsValidator,
pub(crate) token_enc_key: Fernet,
pub(crate) cookie_key: [u8; 32],
pub(crate) cookie_key: [u8; 64],
}
pub struct IdmServer {
@ -262,7 +262,7 @@ impl IdmServer {
))
}
pub fn get_cookie_key(&self) -> [u8; 32] {
pub fn get_cookie_key(&self) -> [u8; 64] {
self.domain_keys.read().cookie_key
}

View file

@ -129,7 +129,7 @@ impl Domain {
if !e.attribute_pres("private_cookie_key") {
security_info!("regenerating domain cookie key");
let mut key = [0; 32];
let mut key = [0; 64];
let mut rng = StdRng::from_entropy();
rng.fill(&mut key);
let v = Value::new_privatebinary(&key);

View file

@ -99,6 +99,10 @@ impl QueryServer {
if system_info_version < 12 {
write_txn.migrate_11_to_12()?;
}
if system_info_version < 13 {
write_txn.migrate_12_to_13()?;
}
}
write_txn.reload()?;
@ -391,6 +395,19 @@ impl<'a> QueryServerWriteTransaction<'a> {
self.internal_apply_writable(mod_candidates)
}
#[instrument(level = "debug", skip_all)]
pub fn migrate_12_to_13(&mut self) -> Result<(), OperationError> {
admin_warn!("starting 12 to 13 migration.");
let filter = filter!(f_and!([
f_eq("class", PVCLASS_DOMAIN_INFO.clone()),
f_eq("uuid", PVUUID_DOMAIN_INFO.clone()),
]));
// Delete the existing cookie key to trigger a regeneration.
let modlist = ModifyList::new_purge("private_cookie_key");
self.internal_modify(&filter, &modlist)
// Complete
}
#[instrument(level = "debug", skip_all)]
pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
admin_debug!("initialise_schema_core -> start ...");

View file

@ -763,12 +763,12 @@ pub trait QueryServerTransaction<'a> {
})
}
fn get_domain_cookie_key(&mut self) -> Result<[u8; 32], OperationError> {
fn get_domain_cookie_key(&mut self) -> Result<[u8; 64], OperationError> {
self.internal_search_uuid(UUID_DOMAIN_INFO)
.and_then(|e| {
e.get_ava_single_private_binary("private_cookie_key")
.and_then(|s| {
let mut x = [0; 32];
let mut x = [0; 64];
if s.len() == x.len() {
x.copy_from_slice(s);
Some(x)