diff --git a/pkgs/by-name/ka/kanidm/1_3.nix b/pkgs/by-name/ka/kanidm/1_3.nix index e9d7b1cd8211b..8d4d310ac7138 100644 --- a/pkgs/by-name/ka/kanidm/1_3.nix +++ b/pkgs/by-name/ka/kanidm/1_3.nix @@ -1,7 +1,8 @@ import ./generic.nix { version = "1.3.3"; hash = "sha256-W5G7osV4du6w/BfyY9YrDzorcLNizRsoz70RMfO2AbY="; - cargoHash = "sha256-gJrzOK6vPPBgsQFkKrbMql00XSfKGjgpZhYJLTURxoI="; + cargoHash = "sha256-iziTHr0gvv319Rzgkze9J1H4UzPR7WxMmCkiGVsb33k="; + patchDir = ./patches/1_3; extraMeta = { knownVulnerabilities = [ '' diff --git a/pkgs/by-name/ka/kanidm/1_4.nix b/pkgs/by-name/ka/kanidm/1_4.nix index 73d7a7a2f7f9d..7c4ada64db240 100644 --- a/pkgs/by-name/ka/kanidm/1_4.nix +++ b/pkgs/by-name/ka/kanidm/1_4.nix @@ -1,5 +1,6 @@ import ./generic.nix { version = "1.4.5"; hash = "sha256-0nn/ZyjkLXWXBZasNhbeEynEN52cmZQAcgg3hLmRpdo="; - cargoHash = "sha256-sLz1EdczSj0/ACLUpWex3i8ZUhNeyU/RVwuAqccLIz8="; + cargoHash = "sha256-9ZB9PwVnqoCRMFXOY7ejh76hmOg7cjVpnjgJfh8aXGI="; + patchDir = ./patches/1_4; } diff --git a/pkgs/by-name/ka/kanidm/generic.nix b/pkgs/by-name/ka/kanidm/generic.nix index 607062bafdda7..5c0a04bcbc2bf 100644 --- a/pkgs/by-name/ka/kanidm/generic.nix +++ b/pkgs/by-name/ka/kanidm/generic.nix @@ -2,6 +2,7 @@ version, hash, cargoHash, + patchDir, extraMeta ? { }, }: @@ -35,8 +36,9 @@ let arch = if stdenv.hostPlatform.isx86_64 then "x86_64" else "generic"; in rustPlatform.buildRustPackage rec { - pname = "kanidm"; + pname = "kanidm" + (lib.optionalString enableSecretProvisioning "-with-secret-provisioning"); inherit version cargoHash; + cargoDepsName = "kanidm"; src = fetchFromGitHub { owner = pname; @@ -48,8 +50,8 @@ rustPlatform.buildRustPackage rec { KANIDM_BUILD_PROFILE = "release_nixos_${arch}"; patches = lib.optionals enableSecretProvisioning [ - ./patches/oauth2-basic-secret-modify.patch - ./patches/recover-account.patch + "${patchDir}/oauth2-basic-secret-modify.patch" + "${patchDir}/recover-account.patch" ]; postPatch = diff --git a/pkgs/by-name/ka/kanidm/patches/1_3/oauth2-basic-secret-modify.patch b/pkgs/by-name/ka/kanidm/patches/1_3/oauth2-basic-secret-modify.patch new file mode 100644 index 0000000000000..afff94ca51e94 --- /dev/null +++ b/pkgs/by-name/ka/kanidm/patches/1_3/oauth2-basic-secret-modify.patch @@ -0,0 +1,303 @@ +From 44dfbc2b9dccce86c7d7e7b54db4c989344b8c56 Mon Sep 17 00:00:00 2001 +From: oddlama +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, ++ 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, + 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 { + ) + .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>, 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, ++ Extension(kopid): Extension, ++ VerifiedClientInformation(client_auth_info): VerifiedClientInformation, ++ Path(rs_name): Path, ++ Json(new_secret): Json, ++) -> Result, 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 + diff --git a/pkgs/by-name/ka/kanidm/patches/1_3/recover-account.patch b/pkgs/by-name/ka/kanidm/patches/1_3/recover-account.patch new file mode 100644 index 0000000000000..a344f5a2086ff --- /dev/null +++ b/pkgs/by-name/ka/kanidm/patches/1_3/recover-account.patch @@ -0,0 +1,173 @@ +From cc8269489b56755714f07eee4671f8aa2659c014 Mon Sep 17 00:00:00 2001 +From: oddlama +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, + eventid: Uuid, + ) -> Result { + 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 { +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 }, + ShowReplicationCertificate, + RenewReplicationCertificate, + RefreshReplicationConsumer, + DomainShow, + DomainUpgradeCheck, + DomainRaise, + DomainRemigrate { level: Option }, + } + + #[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 + diff --git a/pkgs/by-name/ka/kanidm/patches/oauth2-basic-secret-modify.patch b/pkgs/by-name/ka/kanidm/patches/1_4/oauth2-basic-secret-modify.patch similarity index 100% rename from pkgs/by-name/ka/kanidm/patches/oauth2-basic-secret-modify.patch rename to pkgs/by-name/ka/kanidm/patches/1_4/oauth2-basic-secret-modify.patch diff --git a/pkgs/by-name/ka/kanidm/patches/recover-account.patch b/pkgs/by-name/ka/kanidm/patches/1_4/recover-account.patch similarity index 100% rename from pkgs/by-name/ka/kanidm/patches/recover-account.patch rename to pkgs/by-name/ka/kanidm/patches/1_4/recover-account.patch