diff options
author | Adam C. Stephens | 2024-08-16 10:15:22 -0400 |
---|---|---|
committer | GitHub | 2024-08-16 10:15:22 -0400 |
commit | c49d0387e0b2ee9a53f5298eaaa6b2d37809962f (patch) | |
tree | e6088ac8e71d4377102de4c85060a15b1ec9bde3 /pkgs/by-name/ka | |
parent | 5c30512e09c15ff9d5a214d75093eb368e83829c (diff) | |
parent | 558fa6abc6d7ea107003d3be64c7861ec7aa2a1f (diff) |
Merge pull request #251598 from oddlama/feat-kanidm-provision
nixos/kanidm: add basic provisioning
Diffstat (limited to 'pkgs/by-name/ka')
-rw-r--r-- | pkgs/by-name/ka/kanidm-provision/package.nix | 29 | ||||
-rw-r--r-- | pkgs/by-name/ka/kanidm/package.nix | 17 | ||||
-rw-r--r-- | pkgs/by-name/ka/kanidm/patches/oauth2-basic-secret-modify.patch | 303 | ||||
-rw-r--r-- | pkgs/by-name/ka/kanidm/patches/recover-account.patch | 173 |
4 files changed, 521 insertions, 1 deletions
diff --git a/pkgs/by-name/ka/kanidm-provision/package.nix b/pkgs/by-name/ka/kanidm-provision/package.nix new file mode 100644 index 000000000000..47ccb70f86f6 --- /dev/null +++ b/pkgs/by-name/ka/kanidm-provision/package.nix @@ -0,0 +1,29 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, +}: +rustPlatform.buildRustPackage rec { + pname = "kanidm-provision"; + version = "1.1.1"; + + src = fetchFromGitHub { + owner = "oddlama"; + repo = "kanidm-provision"; + rev = "v${version}"; + hash = "sha256-tX24cszmWu7kB5Eoa3OrPqU1bayD62OpAV12U0ayoEo="; + }; + + cargoHash = "sha256-Ok8A47z5Z3QW4teql/4RyDlox/nrhkdA6IN/qJm13bM="; + + meta = with lib; { + description = "A small utility to help with kanidm provisioning"; + homepage = "https://github.com/oddlama/kanidm-provision"; + license = with licenses; [ + asl20 + mit + ]; + maintainers = with maintainers; [ oddlama ]; + mainProgram = "kanidm-provision"; + }; +} diff --git a/pkgs/by-name/ka/kanidm/package.nix b/pkgs/by-name/ka/kanidm/package.nix index febfec0c16f9..dc032583ae07 100644 --- a/pkgs/by-name/ka/kanidm/package.nix +++ b/pkgs/by-name/ka/kanidm/package.nix @@ -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 @@ -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"; @@ -94,10 +107,12 @@ rustPlatform.buildRustPackage rec { passthru = { tests = { - inherit (nixosTests) kanidm; + inherit (nixosTests) kanidm kanidm-provisioning; }; updateScript = nix-update-script { }; + inherit enableSecretProvisioning; + withSecretProvisioning = kanidm.override { enableSecretProvisioning = true; }; }; meta = with lib; { diff --git a/pkgs/by-name/ka/kanidm/patches/oauth2-basic-secret-modify.patch b/pkgs/by-name/ka/kanidm/patches/oauth2-basic-secret-modify.patch new file mode 100644 index 000000000000..afff94ca51e9 --- /dev/null +++ b/pkgs/by-name/ka/kanidm/patches/oauth2-basic-secret-modify.patch @@ -0,0 +1,303 @@ +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 + diff --git a/pkgs/by-name/ka/kanidm/patches/recover-account.patch b/pkgs/by-name/ka/kanidm/patches/recover-account.patch new file mode 100644 index 000000000000..a344f5a2086f --- /dev/null +++ b/pkgs/by-name/ka/kanidm/patches/recover-account.patch @@ -0,0 +1,173 @@ +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 + |