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}, http::{self, Request},
middleware::Next, middleware::Next,
response::Response, response::Response,
Extension, TypedHeader, TypedHeader,
}; };
use axum_sessions::SessionHandle; use axum_sessions::SessionHandle;
use http::HeaderValue; 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 mut response = next.run(request).await;
let headers = response.headers_mut(); let headers = response.headers_mut();
headers.insert("X-KANIDM-VERSION", HeaderValue::from_static(KANIDM_VERSION)); headers.insert("X-KANIDM-VERSION", HeaderValue::from_static(KANIDM_VERSION));
response response
} }
@ -32,16 +31,9 @@ pub struct KOpId {
pub uat: Option<String>, 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. /// 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>>>, auth: Option<TypedHeader<Authorization<Bearer>>>,
mut request: Request<B>, mut request: Request<B>,
next: Next<B>, next: Next<B>,
@ -66,23 +58,12 @@ pub async fn kopid_start<B>(
// insert the extension so we can pull it out later // insert the extension so we can pull it out later
request.extensions_mut().insert(KOpId { eventid, uat }); 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; let mut response = next.run(request).await;
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
response.headers_mut().insert( response.headers_mut().insert(
"X-KANIDM-OPID", "X-KANIDM-OPID",
HeaderValue::from_str(&kopid.eventid_value()).unwrap(), HeaderValue::from_str(&eventid.as_hyphenated().to_string()).unwrap(),
); );
response response

View file

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

View file

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

View file

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

View file

@ -254,6 +254,6 @@ pub fn scim_route_setup() -> Router<ServerState> {
// //
// POST Send a sync update // POST Send a sync update
// //
.route("/v1/Sync", post(scim_sync_post).get(scim_sync_get)) .route("/scim/v1/Sync", post(scim_sync_post).get(scim_sync_get))
.route("/v1/Sink", get(scim_sink_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. // Any pre-start tasks here.
match &config.integration_test_config { 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 // We only allow config file for log level now.
let log_filter = match EnvFilter::try_from_default_env() { let log_filter: EnvFilter = match sconfig.as_ref() {
Ok(val) => val, Some(val) => {
Err(_e) => { let tmp = val.log_level.clone();
// we couldn't get it from the env, so we'll try the config file! tmp.unwrap_or_default()
match sconfig.as_ref() {
Some(val) => {
let tmp = val.log_level.clone();
tmp.unwrap_or_default()
}
None => LogLevel::Info,
}
.into()
} }
}; None => LogLevel::Info,
}
.into();
// TODO: only send to stderr when we're not in a TTY // TODO: only send to stderr when we're not in a TTY
tracing_forest::worker_task() tracing_forest::worker_task()

View file

@ -585,7 +585,7 @@ lazy_static! {
"description", "description",
Value::new_utf8s("System (local) info and metadata object.") 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_signer: JwsSigner,
pub(crate) uat_jwt_validator: JwsValidator, pub(crate) uat_jwt_validator: JwsValidator,
pub(crate) token_enc_key: Fernet, pub(crate) token_enc_key: Fernet,
pub(crate) cookie_key: [u8; 32], pub(crate) cookie_key: [u8; 64],
} }
pub struct IdmServer { 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 self.domain_keys.read().cookie_key
} }

View file

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

View file

@ -99,6 +99,10 @@ impl QueryServer {
if system_info_version < 12 { if system_info_version < 12 {
write_txn.migrate_11_to_12()?; write_txn.migrate_11_to_12()?;
} }
if system_info_version < 13 {
write_txn.migrate_12_to_13()?;
}
} }
write_txn.reload()?; write_txn.reload()?;
@ -391,6 +395,19 @@ impl<'a> QueryServerWriteTransaction<'a> {
self.internal_apply_writable(mod_candidates) 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)] #[instrument(level = "debug", skip_all)]
pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> { pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
admin_debug!("initialise_schema_core -> start ..."); 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) self.internal_search_uuid(UUID_DOMAIN_INFO)
.and_then(|e| { .and_then(|e| {
e.get_ava_single_private_binary("private_cookie_key") e.get_ava_single_private_binary("private_cookie_key")
.and_then(|s| { .and_then(|s| {
let mut x = [0; 32]; let mut x = [0; 64];
if s.len() == x.len() { if s.len() == x.len() {
x.copy_from_slice(s); x.copy_from_slice(s);
Some(x) Some(x)