Loading pkgs/by-name/ka/kanidm/package.nix +15 −0 Original line number Diff line number Diff line Loading @@ -13,6 +13,14 @@ , pam , bashInteractive , rust-jemalloc-sys , kanidm # If this is enabled, kanidm will be built with two patches allowing both # oauth2 basic secrets and admin credentials to be provisioned. # This is NOT officially supported (and will likely never be), # see https://github.com/kanidm/kanidm/issues/1747. # Please report any provisioning-related errors to # https://github.com/oddlama/kanidm-provision/issues/ instead. , enableSecretProvisioning ? false }: let Loading @@ -33,6 +41,11 @@ rustPlatform.buildRustPackage rec { KANIDM_BUILD_PROFILE = "release_nixos_${arch}"; patches = lib.optionals enableSecretProvisioning [ ./patches/oauth2-basic-secret-modify.patch ./patches/recover-account.patch ]; postPatch = let format = (formats.toml { }).generate "${KANIDM_BUILD_PROFILE}.toml"; Loading Loading @@ -98,6 +111,8 @@ rustPlatform.buildRustPackage rec { }; updateScript = nix-update-script { }; inherit enableSecretProvisioning; withSecretProvisioning = kanidm.override { enableSecretProvisioning = true; }; }; meta = with lib; { Loading pkgs/by-name/ka/kanidm/patches/oauth2-basic-secret-modify.patch 0 → 100644 +303 −0 Original line number Diff line number Diff line From 44dfbc2b9dccce86c7d7e7b54db4c989344b8c56 Mon Sep 17 00:00:00 2001 From: oddlama <oddlama@oddlama.org> Date: Mon, 12 Aug 2024 23:17:25 +0200 Subject: [PATCH 1/2] oauth2 basic secret modify --- server/core/src/actors/v1_write.rs | 42 ++++++++++++++++++++++++++++++ server/core/src/https/v1.rs | 6 ++++- server/core/src/https/v1_oauth2.rs | 29 +++++++++++++++++++++ server/lib/src/constants/acp.rs | 6 +++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/server/core/src/actors/v1_write.rs b/server/core/src/actors/v1_write.rs index e00a969fb..1cacc67b8 100644 --- a/server/core/src/actors/v1_write.rs +++ b/server/core/src/actors/v1_write.rs @@ -315,20 +315,62 @@ impl QueryServerWriteV1 { }; trace!(?del, "Begin delete event"); idms_prox_write .qs_write .delete(&del) .and_then(|_| idms_prox_write.commit().map(|_| ())) } + #[instrument( + level = "info", + skip_all, + fields(uuid = ?eventid) + )] + pub async fn handle_oauth2_basic_secret_write( + &self, + client_auth_info: ClientAuthInfo, + filter: Filter<FilterInvalid>, + new_secret: String, + eventid: Uuid, + ) -> Result<(), OperationError> { + // Given a protoEntry, turn this into a modification set. + let ct = duration_from_epoch_now(); + let mut idms_prox_write = self.idms.proxy_write(ct).await; + let ident = idms_prox_write + .validate_client_auth_info_to_ident(client_auth_info, ct) + .map_err(|e| { + admin_error!(err = ?e, "Invalid identity"); + e + })?; + + let modlist = ModifyList::new_purge_and_set( + Attribute::OAuth2RsBasicSecret, + Value::SecretValue(new_secret), + ); + + let mdf = + ModifyEvent::from_internal_parts(ident, &modlist, &filter, &idms_prox_write.qs_write) + .map_err(|e| { + admin_error!(err = ?e, "Failed to begin modify during handle_oauth2_basic_secret_write"); + e + })?; + + trace!(?mdf, "Begin modify event"); + + idms_prox_write + .qs_write + .modify(&mdf) + .and_then(|_| idms_prox_write.commit()) + } + #[instrument( level = "info", skip_all, fields(uuid = ?eventid) )] pub async fn handle_reviverecycled( &self, client_auth_info: ClientAuthInfo, filter: Filter<FilterInvalid>, eventid: Uuid, diff --git a/server/core/src/https/v1.rs b/server/core/src/https/v1.rs index 8aba83bb2..f1f815026 100644 --- a/server/core/src/https/v1.rs +++ b/server/core/src/https/v1.rs @@ -1,17 +1,17 @@ //! The V1 API things! use axum::extract::{Path, State}; use axum::http::{HeaderMap, HeaderValue}; use axum::middleware::from_fn; use axum::response::{IntoResponse, Response}; -use axum::routing::{delete, get, post, put}; +use axum::routing::{delete, get, post, put, patch}; use axum::{Extension, Json, Router}; use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite}; use compact_jwt::{Jwk, Jws, JwsSigner}; use kanidm_proto::constants::uri::V1_AUTH_VALID; use std::net::IpAddr; use uuid::Uuid; use kanidm_proto::internal::{ ApiToken, AppLink, CUIntentToken, CURequest, CUSessionToken, CUStatus, CreateRequest, CredentialStatus, DeleteRequest, IdentifyUserRequest, IdentifyUserResponse, ModifyRequest, @@ -3119,20 +3119,24 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> { ) .route( "/v1/oauth2/:rs_name/_image", post(super::v1_oauth2::oauth2_id_image_post) .delete(super::v1_oauth2::oauth2_id_image_delete), ) .route( "/v1/oauth2/:rs_name/_basic_secret", get(super::v1_oauth2::oauth2_id_get_basic_secret), ) + .route( + "/v1/oauth2/:rs_name/_basic_secret", + patch(super::v1_oauth2::oauth2_id_patch_basic_secret), + ) .route( "/v1/oauth2/:rs_name/_scopemap/:group", post(super::v1_oauth2::oauth2_id_scopemap_post) .delete(super::v1_oauth2::oauth2_id_scopemap_delete), ) .route( "/v1/oauth2/:rs_name/_sup_scopemap/:group", post(super::v1_oauth2::oauth2_id_sup_scopemap_post) .delete(super::v1_oauth2::oauth2_id_sup_scopemap_delete), ) diff --git a/server/core/src/https/v1_oauth2.rs b/server/core/src/https/v1_oauth2.rs index 5e481afab..a771aed04 100644 --- a/server/core/src/https/v1_oauth2.rs +++ b/server/core/src/https/v1_oauth2.rs @@ -144,20 +144,49 @@ pub(crate) async fn oauth2_id_get_basic_secret( ) -> Result<Json<Option<String>>, WebError> { let filter = oauth2_id(&rs_name); state .qe_r_ref .handle_oauth2_basic_secret_read(client_auth_info, filter, kopid.eventid) .await .map(Json::from) .map_err(WebError::from) } +#[utoipa::path( + patch, + path = "/v1/oauth2/{rs_name}/_basic_secret", + request_body=ProtoEntry, + responses( + DefaultApiResponse, + ), + security(("token_jwt" = [])), + tag = "v1/oauth2", + operation_id = "oauth2_id_patch_basic_secret" +)] +/// Overwrite the basic secret for a given OAuth2 Resource Server. +#[instrument(level = "info", skip(state, new_secret))] +pub(crate) async fn oauth2_id_patch_basic_secret( + State(state): State<ServerState>, + Extension(kopid): Extension<KOpId>, + VerifiedClientInformation(client_auth_info): VerifiedClientInformation, + Path(rs_name): Path<String>, + Json(new_secret): Json<String>, +) -> Result<Json<()>, WebError> { + let filter = oauth2_id(&rs_name); + state + .qe_w_ref + .handle_oauth2_basic_secret_write(client_auth_info, filter, new_secret, kopid.eventid) + .await + .map(Json::from) + .map_err(WebError::from) +} + #[utoipa::path( patch, path = "/v1/oauth2/{rs_name}", request_body=ProtoEntry, responses( DefaultApiResponse, ), security(("token_jwt" = [])), tag = "v1/oauth2", operation_id = "oauth2_id_patch" diff --git a/server/lib/src/constants/acp.rs b/server/lib/src/constants/acp.rs index f3409649d..42e407b7d 100644 --- a/server/lib/src/constants/acp.rs +++ b/server/lib/src/constants/acp.rs @@ -645,34 +645,36 @@ lazy_static! { Attribute::Image, ], modify_present_attrs: vec![ Attribute::Description, Attribute::DisplayName, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::Image, ], create_attrs: vec![ Attribute::Class, Attribute::Description, Attribute::DisplayName, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::Image, ], create_classes: vec![ EntryClass::Object, EntryClass::OAuth2ResourceServer, EntryClass::OAuth2ResourceServerBasic, EntryClass::OAuth2ResourceServerPublic, @@ -739,36 +741,38 @@ lazy_static! { Attribute::Image, ], modify_present_attrs: vec![ Attribute::Description, Attribute::DisplayName, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::OAuth2AllowLocalhostRedirect, Attribute::OAuth2RsClaimMap, Attribute::Image, ], create_attrs: vec![ Attribute::Class, Attribute::Description, Attribute::DisplayName, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::OAuth2AllowLocalhostRedirect, Attribute::OAuth2RsClaimMap, Attribute::Image, ], create_classes: vec![ EntryClass::Object, EntryClass::OAuth2ResourceServer, @@ -840,36 +844,38 @@ lazy_static! { Attribute::Image, ], modify_present_attrs: vec![ Attribute::Description, Attribute::DisplayName, Attribute::Name, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::OAuth2AllowLocalhostRedirect, Attribute::OAuth2RsClaimMap, Attribute::Image, ], create_attrs: vec![ Attribute::Class, Attribute::Description, Attribute::Name, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::OAuth2AllowLocalhostRedirect, Attribute::OAuth2RsClaimMap, Attribute::Image, ], create_classes: vec![ EntryClass::Object, EntryClass::Account, -- 2.45.2 pkgs/by-name/ka/kanidm/patches/recover-account.patch 0 → 100644 +173 −0 Original line number Diff line number Diff line From cc8269489b56755714f07eee4671f8aa2659c014 Mon Sep 17 00:00:00 2001 From: oddlama <oddlama@oddlama.org> Date: Mon, 12 Aug 2024 23:17:42 +0200 Subject: [PATCH 2/2] recover account --- server/core/src/actors/internal.rs | 3 ++- server/core/src/admin.rs | 6 +++--- server/daemon/src/main.rs | 14 +++++++++++++- server/daemon/src/opt.rs | 4 ++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/server/core/src/actors/internal.rs b/server/core/src/actors/internal.rs index 40c18777f..40d553b40 100644 --- a/server/core/src/actors/internal.rs +++ b/server/core/src/actors/internal.rs @@ -153,25 +153,26 @@ impl QueryServerWriteV1 { } #[instrument( level = "info", skip(self, eventid), fields(uuid = ?eventid) )] pub(crate) async fn handle_admin_recover_account( &self, name: String, + password: Option<String>, eventid: Uuid, ) -> Result<String, OperationError> { let ct = duration_from_epoch_now(); let mut idms_prox_write = self.idms.proxy_write(ct).await; - let pw = idms_prox_write.recover_account(name.as_str(), None)?; + let pw = idms_prox_write.recover_account(name.as_str(), password.as_deref())?; idms_prox_write.commit().map(|()| pw) } #[instrument( level = "info", skip_all, fields(uuid = ?eventid) )] pub(crate) async fn handle_domain_raise(&self, eventid: Uuid) -> Result<u32, OperationError> { diff --git a/server/core/src/admin.rs b/server/core/src/admin.rs index 90ccb1927..85e31ddef 100644 --- a/server/core/src/admin.rs +++ b/server/core/src/admin.rs @@ -17,21 +17,21 @@ use tokio_util::codec::{Decoder, Encoder, Framed}; use tracing::{span, Instrument, Level}; use uuid::Uuid; pub use kanidm_proto::internal::{ DomainInfo as ProtoDomainInfo, DomainUpgradeCheckReport as ProtoDomainUpgradeCheckReport, DomainUpgradeCheckStatus as ProtoDomainUpgradeCheckStatus, }; #[derive(Serialize, Deserialize, Debug)] pub enum AdminTaskRequest { - RecoverAccount { name: String }, + RecoverAccount { name: String, password: Option<String> }, ShowReplicationCertificate, RenewReplicationCertificate, RefreshReplicationConsumer, DomainShow, DomainUpgradeCheck, DomainRaise, DomainRemigrate { level: Option<u32> }, } #[derive(Serialize, Deserialize, Debug)] @@ -302,22 +302,22 @@ async fn handle_client( let mut reqs = Framed::new(sock, ServerCodec); trace!("Waiting for requests ..."); while let Some(Ok(req)) = reqs.next().await { // Setup the logging span let eventid = Uuid::new_v4(); let nspan = span!(Level::INFO, "handle_admin_client_request", uuid = ?eventid); let resp = async { match req { - AdminTaskRequest::RecoverAccount { name } => { - match server_rw.handle_admin_recover_account(name, eventid).await { + AdminTaskRequest::RecoverAccount { name, password } => { + match server_rw.handle_admin_recover_account(name, password, eventid).await { Ok(password) => AdminTaskResponse::RecoverAccount { password }, Err(e) => { error!(err = ?e, "error during recover-account"); AdminTaskResponse::Error } } } AdminTaskRequest::ShowReplicationCertificate => match repl_ctrl_tx.as_mut() { Some(ctrl_tx) => show_replication_certificate(ctrl_tx).await, None => { diff --git a/server/daemon/src/main.rs b/server/daemon/src/main.rs index 577995615..a967928c9 100644 --- a/server/daemon/src/main.rs +++ b/server/daemon/src/main.rs @@ -894,27 +894,39 @@ async fn kanidm_main( } else { let output_mode: ConsoleOutputMode = commonopts.output_mode.to_owned().into(); submit_admin_req( config.adminbindpath.as_str(), AdminTaskRequest::RefreshReplicationConsumer, output_mode, ) .await; } } - KanidmdOpt::RecoverAccount { name, commonopts } => { + KanidmdOpt::RecoverAccount { name, from_environment, commonopts } => { info!("Running account recovery ..."); let output_mode: ConsoleOutputMode = commonopts.output_mode.to_owned().into(); + let password = if *from_environment { + match std::env::var("KANIDM_RECOVER_ACCOUNT_PASSWORD") { + Ok(val) => Some(val), + _ => { + error!("Environment variable KANIDM_RECOVER_ACCOUNT_PASSWORD not set"); + return ExitCode::FAILURE; + } + } + } else { + None + }; submit_admin_req( config.adminbindpath.as_str(), AdminTaskRequest::RecoverAccount { name: name.to_owned(), + password, }, output_mode, ) .await; } KanidmdOpt::Database { commands: DbCommands::Reindex(_copt), } => { info!("Running in reindex mode ..."); reindex_server_core(&config).await; diff --git a/server/daemon/src/opt.rs b/server/daemon/src/opt.rs index f1b45a5b3..9c013e32e 100644 --- a/server/daemon/src/opt.rs +++ b/server/daemon/src/opt.rs @@ -229,20 +229,24 @@ enum KanidmdOpt { /// Create a self-signed ca and tls certificate in the locations listed from the /// configuration. These certificates should *not* be used in production, they /// are for testing and evaluation only! CertGenerate(CommonOpt), #[clap(name = "recover-account")] /// Recover an account's password RecoverAccount { #[clap(value_parser)] /// The account name to recover credentials for. name: String, + /// Use the password given in the environment variable + /// `KANIDM_RECOVER_ACCOUNT_PASSWORD` instead of generating one. + #[clap(long = "from-environment")] + from_environment: bool, #[clap(flatten)] commonopts: CommonOpt, }, /// Display this server's replication certificate ShowReplicationCertificate { #[clap(flatten)] commonopts: CommonOpt, }, /// Renew this server's replication certificate RenewReplicationCertificate { -- 2.45.2 Loading
pkgs/by-name/ka/kanidm/package.nix +15 −0 Original line number Diff line number Diff line Loading @@ -13,6 +13,14 @@ , pam , bashInteractive , rust-jemalloc-sys , kanidm # If this is enabled, kanidm will be built with two patches allowing both # oauth2 basic secrets and admin credentials to be provisioned. # This is NOT officially supported (and will likely never be), # see https://github.com/kanidm/kanidm/issues/1747. # Please report any provisioning-related errors to # https://github.com/oddlama/kanidm-provision/issues/ instead. , enableSecretProvisioning ? false }: let Loading @@ -33,6 +41,11 @@ rustPlatform.buildRustPackage rec { KANIDM_BUILD_PROFILE = "release_nixos_${arch}"; patches = lib.optionals enableSecretProvisioning [ ./patches/oauth2-basic-secret-modify.patch ./patches/recover-account.patch ]; postPatch = let format = (formats.toml { }).generate "${KANIDM_BUILD_PROFILE}.toml"; Loading Loading @@ -98,6 +111,8 @@ rustPlatform.buildRustPackage rec { }; updateScript = nix-update-script { }; inherit enableSecretProvisioning; withSecretProvisioning = kanidm.override { enableSecretProvisioning = true; }; }; meta = with lib; { Loading
pkgs/by-name/ka/kanidm/patches/oauth2-basic-secret-modify.patch 0 → 100644 +303 −0 Original line number Diff line number Diff line From 44dfbc2b9dccce86c7d7e7b54db4c989344b8c56 Mon Sep 17 00:00:00 2001 From: oddlama <oddlama@oddlama.org> Date: Mon, 12 Aug 2024 23:17:25 +0200 Subject: [PATCH 1/2] oauth2 basic secret modify --- server/core/src/actors/v1_write.rs | 42 ++++++++++++++++++++++++++++++ server/core/src/https/v1.rs | 6 ++++- server/core/src/https/v1_oauth2.rs | 29 +++++++++++++++++++++ server/lib/src/constants/acp.rs | 6 +++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/server/core/src/actors/v1_write.rs b/server/core/src/actors/v1_write.rs index e00a969fb..1cacc67b8 100644 --- a/server/core/src/actors/v1_write.rs +++ b/server/core/src/actors/v1_write.rs @@ -315,20 +315,62 @@ impl QueryServerWriteV1 { }; trace!(?del, "Begin delete event"); idms_prox_write .qs_write .delete(&del) .and_then(|_| idms_prox_write.commit().map(|_| ())) } + #[instrument( + level = "info", + skip_all, + fields(uuid = ?eventid) + )] + pub async fn handle_oauth2_basic_secret_write( + &self, + client_auth_info: ClientAuthInfo, + filter: Filter<FilterInvalid>, + new_secret: String, + eventid: Uuid, + ) -> Result<(), OperationError> { + // Given a protoEntry, turn this into a modification set. + let ct = duration_from_epoch_now(); + let mut idms_prox_write = self.idms.proxy_write(ct).await; + let ident = idms_prox_write + .validate_client_auth_info_to_ident(client_auth_info, ct) + .map_err(|e| { + admin_error!(err = ?e, "Invalid identity"); + e + })?; + + let modlist = ModifyList::new_purge_and_set( + Attribute::OAuth2RsBasicSecret, + Value::SecretValue(new_secret), + ); + + let mdf = + ModifyEvent::from_internal_parts(ident, &modlist, &filter, &idms_prox_write.qs_write) + .map_err(|e| { + admin_error!(err = ?e, "Failed to begin modify during handle_oauth2_basic_secret_write"); + e + })?; + + trace!(?mdf, "Begin modify event"); + + idms_prox_write + .qs_write + .modify(&mdf) + .and_then(|_| idms_prox_write.commit()) + } + #[instrument( level = "info", skip_all, fields(uuid = ?eventid) )] pub async fn handle_reviverecycled( &self, client_auth_info: ClientAuthInfo, filter: Filter<FilterInvalid>, eventid: Uuid, diff --git a/server/core/src/https/v1.rs b/server/core/src/https/v1.rs index 8aba83bb2..f1f815026 100644 --- a/server/core/src/https/v1.rs +++ b/server/core/src/https/v1.rs @@ -1,17 +1,17 @@ //! The V1 API things! use axum::extract::{Path, State}; use axum::http::{HeaderMap, HeaderValue}; use axum::middleware::from_fn; use axum::response::{IntoResponse, Response}; -use axum::routing::{delete, get, post, put}; +use axum::routing::{delete, get, post, put, patch}; use axum::{Extension, Json, Router}; use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite}; use compact_jwt::{Jwk, Jws, JwsSigner}; use kanidm_proto::constants::uri::V1_AUTH_VALID; use std::net::IpAddr; use uuid::Uuid; use kanidm_proto::internal::{ ApiToken, AppLink, CUIntentToken, CURequest, CUSessionToken, CUStatus, CreateRequest, CredentialStatus, DeleteRequest, IdentifyUserRequest, IdentifyUserResponse, ModifyRequest, @@ -3119,20 +3119,24 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> { ) .route( "/v1/oauth2/:rs_name/_image", post(super::v1_oauth2::oauth2_id_image_post) .delete(super::v1_oauth2::oauth2_id_image_delete), ) .route( "/v1/oauth2/:rs_name/_basic_secret", get(super::v1_oauth2::oauth2_id_get_basic_secret), ) + .route( + "/v1/oauth2/:rs_name/_basic_secret", + patch(super::v1_oauth2::oauth2_id_patch_basic_secret), + ) .route( "/v1/oauth2/:rs_name/_scopemap/:group", post(super::v1_oauth2::oauth2_id_scopemap_post) .delete(super::v1_oauth2::oauth2_id_scopemap_delete), ) .route( "/v1/oauth2/:rs_name/_sup_scopemap/:group", post(super::v1_oauth2::oauth2_id_sup_scopemap_post) .delete(super::v1_oauth2::oauth2_id_sup_scopemap_delete), ) diff --git a/server/core/src/https/v1_oauth2.rs b/server/core/src/https/v1_oauth2.rs index 5e481afab..a771aed04 100644 --- a/server/core/src/https/v1_oauth2.rs +++ b/server/core/src/https/v1_oauth2.rs @@ -144,20 +144,49 @@ pub(crate) async fn oauth2_id_get_basic_secret( ) -> Result<Json<Option<String>>, WebError> { let filter = oauth2_id(&rs_name); state .qe_r_ref .handle_oauth2_basic_secret_read(client_auth_info, filter, kopid.eventid) .await .map(Json::from) .map_err(WebError::from) } +#[utoipa::path( + patch, + path = "/v1/oauth2/{rs_name}/_basic_secret", + request_body=ProtoEntry, + responses( + DefaultApiResponse, + ), + security(("token_jwt" = [])), + tag = "v1/oauth2", + operation_id = "oauth2_id_patch_basic_secret" +)] +/// Overwrite the basic secret for a given OAuth2 Resource Server. +#[instrument(level = "info", skip(state, new_secret))] +pub(crate) async fn oauth2_id_patch_basic_secret( + State(state): State<ServerState>, + Extension(kopid): Extension<KOpId>, + VerifiedClientInformation(client_auth_info): VerifiedClientInformation, + Path(rs_name): Path<String>, + Json(new_secret): Json<String>, +) -> Result<Json<()>, WebError> { + let filter = oauth2_id(&rs_name); + state + .qe_w_ref + .handle_oauth2_basic_secret_write(client_auth_info, filter, new_secret, kopid.eventid) + .await + .map(Json::from) + .map_err(WebError::from) +} + #[utoipa::path( patch, path = "/v1/oauth2/{rs_name}", request_body=ProtoEntry, responses( DefaultApiResponse, ), security(("token_jwt" = [])), tag = "v1/oauth2", operation_id = "oauth2_id_patch" diff --git a/server/lib/src/constants/acp.rs b/server/lib/src/constants/acp.rs index f3409649d..42e407b7d 100644 --- a/server/lib/src/constants/acp.rs +++ b/server/lib/src/constants/acp.rs @@ -645,34 +645,36 @@ lazy_static! { Attribute::Image, ], modify_present_attrs: vec![ Attribute::Description, Attribute::DisplayName, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::Image, ], create_attrs: vec![ Attribute::Class, Attribute::Description, Attribute::DisplayName, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::Image, ], create_classes: vec![ EntryClass::Object, EntryClass::OAuth2ResourceServer, EntryClass::OAuth2ResourceServerBasic, EntryClass::OAuth2ResourceServerPublic, @@ -739,36 +741,38 @@ lazy_static! { Attribute::Image, ], modify_present_attrs: vec![ Attribute::Description, Attribute::DisplayName, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::OAuth2AllowLocalhostRedirect, Attribute::OAuth2RsClaimMap, Attribute::Image, ], create_attrs: vec![ Attribute::Class, Attribute::Description, Attribute::DisplayName, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::OAuth2AllowLocalhostRedirect, Attribute::OAuth2RsClaimMap, Attribute::Image, ], create_classes: vec![ EntryClass::Object, EntryClass::OAuth2ResourceServer, @@ -840,36 +844,38 @@ lazy_static! { Attribute::Image, ], modify_present_attrs: vec![ Attribute::Description, Attribute::DisplayName, Attribute::Name, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::OAuth2AllowLocalhostRedirect, Attribute::OAuth2RsClaimMap, Attribute::Image, ], create_attrs: vec![ Attribute::Class, Attribute::Description, Attribute::Name, Attribute::OAuth2RsName, Attribute::OAuth2RsOrigin, Attribute::OAuth2RsOriginLanding, Attribute::OAuth2RsSupScopeMap, Attribute::OAuth2RsScopeMap, + Attribute::OAuth2RsBasicSecret, Attribute::OAuth2AllowInsecureClientDisablePkce, Attribute::OAuth2JwtLegacyCryptoEnable, Attribute::OAuth2PreferShortUsername, Attribute::OAuth2AllowLocalhostRedirect, Attribute::OAuth2RsClaimMap, Attribute::Image, ], create_classes: vec![ EntryClass::Object, EntryClass::Account, -- 2.45.2
pkgs/by-name/ka/kanidm/patches/recover-account.patch 0 → 100644 +173 −0 Original line number Diff line number Diff line From cc8269489b56755714f07eee4671f8aa2659c014 Mon Sep 17 00:00:00 2001 From: oddlama <oddlama@oddlama.org> Date: Mon, 12 Aug 2024 23:17:42 +0200 Subject: [PATCH 2/2] recover account --- server/core/src/actors/internal.rs | 3 ++- server/core/src/admin.rs | 6 +++--- server/daemon/src/main.rs | 14 +++++++++++++- server/daemon/src/opt.rs | 4 ++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/server/core/src/actors/internal.rs b/server/core/src/actors/internal.rs index 40c18777f..40d553b40 100644 --- a/server/core/src/actors/internal.rs +++ b/server/core/src/actors/internal.rs @@ -153,25 +153,26 @@ impl QueryServerWriteV1 { } #[instrument( level = "info", skip(self, eventid), fields(uuid = ?eventid) )] pub(crate) async fn handle_admin_recover_account( &self, name: String, + password: Option<String>, eventid: Uuid, ) -> Result<String, OperationError> { let ct = duration_from_epoch_now(); let mut idms_prox_write = self.idms.proxy_write(ct).await; - let pw = idms_prox_write.recover_account(name.as_str(), None)?; + let pw = idms_prox_write.recover_account(name.as_str(), password.as_deref())?; idms_prox_write.commit().map(|()| pw) } #[instrument( level = "info", skip_all, fields(uuid = ?eventid) )] pub(crate) async fn handle_domain_raise(&self, eventid: Uuid) -> Result<u32, OperationError> { diff --git a/server/core/src/admin.rs b/server/core/src/admin.rs index 90ccb1927..85e31ddef 100644 --- a/server/core/src/admin.rs +++ b/server/core/src/admin.rs @@ -17,21 +17,21 @@ use tokio_util::codec::{Decoder, Encoder, Framed}; use tracing::{span, Instrument, Level}; use uuid::Uuid; pub use kanidm_proto::internal::{ DomainInfo as ProtoDomainInfo, DomainUpgradeCheckReport as ProtoDomainUpgradeCheckReport, DomainUpgradeCheckStatus as ProtoDomainUpgradeCheckStatus, }; #[derive(Serialize, Deserialize, Debug)] pub enum AdminTaskRequest { - RecoverAccount { name: String }, + RecoverAccount { name: String, password: Option<String> }, ShowReplicationCertificate, RenewReplicationCertificate, RefreshReplicationConsumer, DomainShow, DomainUpgradeCheck, DomainRaise, DomainRemigrate { level: Option<u32> }, } #[derive(Serialize, Deserialize, Debug)] @@ -302,22 +302,22 @@ async fn handle_client( let mut reqs = Framed::new(sock, ServerCodec); trace!("Waiting for requests ..."); while let Some(Ok(req)) = reqs.next().await { // Setup the logging span let eventid = Uuid::new_v4(); let nspan = span!(Level::INFO, "handle_admin_client_request", uuid = ?eventid); let resp = async { match req { - AdminTaskRequest::RecoverAccount { name } => { - match server_rw.handle_admin_recover_account(name, eventid).await { + AdminTaskRequest::RecoverAccount { name, password } => { + match server_rw.handle_admin_recover_account(name, password, eventid).await { Ok(password) => AdminTaskResponse::RecoverAccount { password }, Err(e) => { error!(err = ?e, "error during recover-account"); AdminTaskResponse::Error } } } AdminTaskRequest::ShowReplicationCertificate => match repl_ctrl_tx.as_mut() { Some(ctrl_tx) => show_replication_certificate(ctrl_tx).await, None => { diff --git a/server/daemon/src/main.rs b/server/daemon/src/main.rs index 577995615..a967928c9 100644 --- a/server/daemon/src/main.rs +++ b/server/daemon/src/main.rs @@ -894,27 +894,39 @@ async fn kanidm_main( } else { let output_mode: ConsoleOutputMode = commonopts.output_mode.to_owned().into(); submit_admin_req( config.adminbindpath.as_str(), AdminTaskRequest::RefreshReplicationConsumer, output_mode, ) .await; } } - KanidmdOpt::RecoverAccount { name, commonopts } => { + KanidmdOpt::RecoverAccount { name, from_environment, commonopts } => { info!("Running account recovery ..."); let output_mode: ConsoleOutputMode = commonopts.output_mode.to_owned().into(); + let password = if *from_environment { + match std::env::var("KANIDM_RECOVER_ACCOUNT_PASSWORD") { + Ok(val) => Some(val), + _ => { + error!("Environment variable KANIDM_RECOVER_ACCOUNT_PASSWORD not set"); + return ExitCode::FAILURE; + } + } + } else { + None + }; submit_admin_req( config.adminbindpath.as_str(), AdminTaskRequest::RecoverAccount { name: name.to_owned(), + password, }, output_mode, ) .await; } KanidmdOpt::Database { commands: DbCommands::Reindex(_copt), } => { info!("Running in reindex mode ..."); reindex_server_core(&config).await; diff --git a/server/daemon/src/opt.rs b/server/daemon/src/opt.rs index f1b45a5b3..9c013e32e 100644 --- a/server/daemon/src/opt.rs +++ b/server/daemon/src/opt.rs @@ -229,20 +229,24 @@ enum KanidmdOpt { /// Create a self-signed ca and tls certificate in the locations listed from the /// configuration. These certificates should *not* be used in production, they /// are for testing and evaluation only! CertGenerate(CommonOpt), #[clap(name = "recover-account")] /// Recover an account's password RecoverAccount { #[clap(value_parser)] /// The account name to recover credentials for. name: String, + /// Use the password given in the environment variable + /// `KANIDM_RECOVER_ACCOUNT_PASSWORD` instead of generating one. + #[clap(long = "from-environment")] + from_environment: bool, #[clap(flatten)] commonopts: CommonOpt, }, /// Display this server's replication certificate ShowReplicationCertificate { #[clap(flatten)] commonopts: CommonOpt, }, /// Renew this server's replication certificate RenewReplicationCertificate { -- 2.45.2