push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
import ./generic.nix {
version = "1.5.0";
hash = "sha256-swrqyjA7Wgq17vd+753LDFcXrSFixVNLhTvj1bhG3DU=";
cargoHash = "sha256-72IwS8Nk1y6xDH9y8JW2LpbhFWaq0tpORx7JQSCF5/M=";
unsupported = true;
}

View File

@@ -0,0 +1,6 @@
import ./generic.nix {
version = "1.6.4";
hash = "sha256-ui3w1HDHXHARsjQ3WtJfZbM7Xgg3ODnUneXJMQwaOMw=";
cargoHash = "sha256-KJGELBzScwsLd6g3GR9Vk0nfDU2EjZBfXwlXJ+bZb1k=";
unsupported = true;
}

View File

@@ -0,0 +1,9 @@
import ./generic.nix {
version = "1.7.3";
hash = "sha256-eptbxhbd3pUvYCncgKprh0qes9CjdvGUl3CsG/sHX7M=";
cargoHash = "sha256-M0TXGvpMkV/4U0MRYVqiWQsA+9AHdeS89noLxE2Llt0=";
patches = [
# remove 1.7.4 - https://github.com/kanidm/kanidm/issues/3813
./a3bc718a8a0325a53e0857668b8a0134d371794d.patch
];
}

View File

@@ -0,0 +1,45 @@
# Kanidm release guide
Kanidm supports one release at any given time, with a 30-day overlap to allow for upgrades from old to new version.
Version upgrades are only supported on adjacent releases, with no support for jumping versions.
To ensure we provide sufficient coverage for upgrading, we will aim to have two or three releases in tree at any given time.
Unsupported versions will be marked as vulnerable (lacking an "unsupported" mechanism), but built by hydra to avoid pushing the very large rebuild on users.
The default version will be updated with each new version, but the default will not be backported.
It is expected that stable users will have to manually specify the version, and update that version, throughout the lifecycle of a NixOS release.
## New release
For example, when upgrading from 1.4 -> 1.5
### Init new version
1. `cp pkgs/by-name/ka/kanidm/1_4.nix pkgs/by-name/ka/kanidm/1_5.nix`
1. `cp -r pkgs/by-name/ka/kanidm/patches/1_4 pkgs/by-name/ka/kanidm/patches/1_5`
1. Update `1_5.nix` hashes/paths, and as needed for upstream changes, `generic.nix`
1. Update `all-packages.nix` to add `kanidm_1_5` and `kanidmWithSecretProvisioning_1_5`, leave default
1. Update the previous release, e.g. `1_4.nix` and set `eolDate = "YYYY-MM-DD"` where the date is 30 days from release of 1.5.
1. Create commit, `kanidm_1_5: init at 1.5.0` - this is the only commit that will be backported
### Update default
1. Update kanidm aliases in `aliases.nix`. Should remove completely after 25.11 branch off.
1. Create commit `kanidm: update default to 1.5.0`
### Backport to stable
1. Manually create a backport using _only_ the init commit
## Remove release
Kanidm versions are supported for 30 days after the release of new versions. Following the example above, 1.5.x superseding 1.4.x in 30 days, do the following near the end of the 30-day window
1. Update `pkgs/by-name/ka/kanidm/1_4.nix` by adding `unsupported = true;`
1. Update `pkgs/top-level/release.nix` and add `kanidm_1_4-1.4.6` and `kanidmWithSecretProvisioning_1_4-1.4.6` to `permittedInsecurePackages`
1. Create commit `kanidm_1_4: mark EOL`, this commit alone should be backported
1. Remove the third oldest release from `all-packages.nix`, e.g. 1.3.x continuing the example. Remove `kanidm_1_3` and `kanidmWithSecretProvisioning_1_3`
1. Update `pkgs/top-level/release.nix` and remove `kanidm_1_3*` from `permittedInsecurePackages`
1. Update `pkgs/top-level/aliases.nix` and add `kanidm_1_4` and `kanidmWithSecretProvisioning_1_4-1.4.6`
1. Remove `pkgs/by-name/ka/kanidm/1_3.nix`

View File

@@ -0,0 +1,29 @@
From a3bc718a8a0325a53e0857668b8a0134d371794d Mon Sep 17 00:00:00 2001
From: Firstyear <william@blackhats.net.au>
Date: Sat, 16 Aug 2025 13:46:23 +1000
Subject: [PATCH] Fix account recover-disable edge case (#3796)
---
server/lib/src/idm/server.rs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/server/lib/src/idm/server.rs b/server/lib/src/idm/server.rs
index 0fc6d78787..51bfbf6705 100644
--- a/server/lib/src/idm/server.rs
+++ b/server/lib/src/idm/server.rs
@@ -1900,6 +1900,7 @@ impl IdmServerProxyWriteTransaction<'_> {
let modlist = ModifyList::new_list(vec![
// Ensure the account is valid from *now*, and that the expiry is unset.
m_purge(Attribute::AccountExpire),
+ m_purge(Attribute::AccountValidFrom),
Modify::Present(Attribute::AccountValidFrom, v_valid_from),
// We need to remove other credentials too.
m_purge(Attribute::PassKeys),
@@ -1934,6 +1935,7 @@ impl IdmServerProxyWriteTransaction<'_> {
let modlist = ModifyList::new_list(vec![
// Ensure that the account has no validity, and the expiry is now.
m_purge(Attribute::AccountValidFrom),
+ m_purge(Attribute::AccountExpire),
Modify::Present(Attribute::AccountExpire, v_expire),
]);

View File

@@ -0,0 +1,189 @@
{
version,
hash,
cargoHash,
unsupported ? false,
eolDate ? null,
patches ? [ ],
}:
{
stdenv,
lib,
formats,
nixosTests,
rustPlatform,
fetchFromGitHub,
installShellFiles,
nix-update-script,
pkg-config,
udev,
openssl,
sqlite,
pam,
bashInteractive,
rust-jemalloc-sys,
kanidmWithSecretProvisioning,
# 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
arch = if stdenv.hostPlatform.isx86_64 then "x86_64" else "generic";
versionUnderscored =
finalAttrs: lib.replaceStrings [ "." ] [ "_" ] (lib.versions.majorMinor finalAttrs.version);
upgradeNote = ''
Please upgrade by verifying `kanidmd domain upgrade-check` and choosing the
next version with `services.kanidm.package = pkgs.kanidm_1_x;`
See upgrade guide at https://kanidm.github.io/kanidm/master/server_updates.html
'';
in
rustPlatform.buildRustPackage (finalAttrs: {
pname = "kanidm" + (lib.optionalString enableSecretProvisioning "-with-secret-provisioning");
inherit version cargoHash;
cargoDepsName = "kanidm";
src = fetchFromGitHub {
owner = "kanidm";
repo = "kanidm";
tag = "v${finalAttrs.version}";
inherit hash;
};
env.KANIDM_BUILD_PROFILE = "release_nixpkgs_${arch}";
patches =
patches
++ lib.optionals enableSecretProvisioning [
(./. + "/provision-patches/${versionUnderscored finalAttrs}/oauth2-basic-secret-modify.patch")
(./. + "/provision-patches/${versionUnderscored finalAttrs}/recover-account.patch")
];
postPatch =
let
format = (formats.toml { }).generate "${finalAttrs.env.KANIDM_BUILD_PROFILE}.toml";
socket_path = if stdenv.hostPlatform.isLinux then "/run/kanidmd/sock" else "/var/run/kanidm.socket";
profile = {
cpu_flags = if stdenv.hostPlatform.isx86_64 then "x86_64_legacy" else "none";
}
// lib.optionalAttrs (lib.versionAtLeast finalAttrs.version "1.5") {
client_config_path = "/etc/kanidm/config";
resolver_config_path = "/etc/kanidm/unixd";
resolver_unix_shell_path = "${lib.getBin bashInteractive}/bin/bash";
server_admin_bind_path = socket_path;
server_config_path = "/etc/kanidm/server.toml";
server_ui_pkg_path = "@htmx_ui_pkg_path@";
};
in
''
cp ${format profile} libs/profiles/${finalAttrs.env.KANIDM_BUILD_PROFILE}.toml
substituteInPlace libs/profiles/${finalAttrs.env.KANIDM_BUILD_PROFILE}.toml --replace-fail '@htmx_ui_pkg_path@' "$out/ui/hpkg"
'';
nativeBuildInputs = [
pkg-config
installShellFiles
];
buildInputs = [
openssl
sqlite
pam
rust-jemalloc-sys
]
++ lib.optionals stdenv.hostPlatform.isLinux [
udev
];
# The UI needs to be in place before the tests are run.
postBuild = ''
mkdir -p $out/ui
cp -r server/core/static $out/ui/hpkg
'';
# Upstream runs with the Rust equivalent of -Werror,
# which breaks when we upgrade to new Rust before them.
# Just allow warnings. It's fine, really.
env.RUSTFLAGS = "--cap-lints warn";
# Not sure what pathological case it hits when compiling tests with LTO,
# but disabling it takes the total `cargo check` time from 40 minutes to
# around 5 on a 16-core machine.
cargoTestFlags = [
"--config"
''profile.release.lto="off"''
];
preFixup = ''
installShellCompletion \
--bash $releaseDir/build/completions/*.bash \
--zsh $releaseDir/build/completions/_*
''
+ lib.optionalString (!stdenv.hostPlatform.isDarwin) ''
# PAM and NSS need fix library names
mv $out/lib/libnss_kanidm.so $out/lib/libnss_kanidm.so.2
mv $out/lib/libpam_kanidm.so $out/lib/pam_kanidm.so
'';
passthru = {
tests = {
kanidm = nixosTests.kanidm.extend {
modules = [ { _module.args.kanidmPackage = finalAttrs.finalPackage; } ];
};
kanidm-provisioning = nixosTests.kanidm-provisioning.extend {
modules = [ { _module.args.kanidmPackage = finalAttrs.finalPackage.withSecretProvisioning; } ];
};
};
updateScript = lib.optionals (!enableSecretProvisioning) (nix-update-script {
extraArgs = [
"-vr"
"v(${lib.versions.major finalAttrs.version}\\.${lib.versions.minor finalAttrs.version}\\.[0-9]*)"
"--override-filename"
"pkgs/by-name/ka/kanidm/${versionUnderscored finalAttrs}.nix"
];
});
inherit enableSecretProvisioning;
# Unfortunately there is no such thing as finalAttrs.finalPackage.override,
# so we have to resort to this.
withSecretProvisioning = kanidmWithSecretProvisioning;
eolMessage = lib.optionalString (eolDate != null) ''
kanidm ${lib.versions.majorMinor finalAttrs.version} is deprecated and will reach end-of-life on ${eolDate}
${upgradeNote}
'';
};
# can take over 4 hours on 2 cores and needs 16GB+ RAM
requiredSystemFeatures = [ "big-parallel" ];
meta = {
changelog = "https://github.com/kanidm/kanidm/releases/tag/v${finalAttrs.version}";
description = "Simple, secure and fast identity management platform";
homepage = "https://github.com/kanidm/kanidm";
license = lib.licenses.mpl20;
platforms = lib.platforms.linux ++ lib.platforms.darwin;
maintainers = with lib.maintainers; [
adamcstephens
Flakebi
];
knownVulnerabilities = lib.optionals unsupported [
''
kanidm ${lib.versions.majorMinor finalAttrs.version} has reached end-of-life.
${upgradeNote}
''
];
};
})

View File

@@ -0,0 +1,199 @@
From cf50a972b446b0ae051cfa4b01d82a4f8077386e Mon Sep 17 00:00:00 2001
From: Benjamin Bädorf <hello@benjaminbaedorf.eu>
Date: Fri, 28 Mar 2025 19:27:42 +0100
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 | 8 ++++++
4 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/server/core/src/actors/v1_write.rs b/server/core/src/actors/v1_write.rs
index 732e826c8..a2b8e503f 100644
--- a/server/core/src/actors/v1_write.rs
+++ b/server/core/src/actors/v1_write.rs
@@ -324,6 +324,48 @@ impl QueryServerWriteV1 {
.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,
diff --git a/server/core/src/https/v1.rs b/server/core/src/https/v1.rs
index c410a4b5d..cc67cac6c 100644
--- a/server/core/src/https/v1.rs
+++ b/server/core/src/https/v1.rs
@@ -4,7 +4,7 @@ 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};
@@ -3127,6 +3127,10 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
"/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)
diff --git a/server/core/src/https/v1_oauth2.rs b/server/core/src/https/v1_oauth2.rs
index f399539bc..ffad9921e 100644
--- a/server/core/src/https/v1_oauth2.rs
+++ b/server/core/src/https/v1_oauth2.rs
@@ -151,6 +151,35 @@ pub(crate) async fn oauth2_id_get_basic_secret(
.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}",
diff --git a/server/lib/src/constants/acp.rs b/server/lib/src/constants/acp.rs
index 7c0487745..3cd83ad52 100644
--- a/server/lib/src/constants/acp.rs
+++ b/server/lib/src/constants/acp.rs
@@ -665,6 +665,7 @@ lazy_static! {
Attribute::OAuth2RsOriginLanding,
Attribute::OAuth2RsSupScopeMap,
Attribute::OAuth2RsScopeMap,
+ Attribute::OAuth2RsBasicSecret,
Attribute::OAuth2AllowInsecureClientDisablePkce,
Attribute::OAuth2JwtLegacyCryptoEnable,
Attribute::OAuth2PreferShortUsername,
@@ -681,6 +682,7 @@ lazy_static! {
Attribute::OAuth2RsOriginLanding,
Attribute::OAuth2RsSupScopeMap,
Attribute::OAuth2RsScopeMap,
+ Attribute::OAuth2RsBasicSecret,
Attribute::OAuth2AllowInsecureClientDisablePkce,
Attribute::OAuth2JwtLegacyCryptoEnable,
Attribute::OAuth2PreferShortUsername,
@@ -766,6 +768,7 @@ lazy_static! {
Attribute::OAuth2RsOriginLanding,
Attribute::OAuth2RsSupScopeMap,
Attribute::OAuth2RsScopeMap,
+ Attribute::OAuth2RsBasicSecret,
Attribute::OAuth2AllowInsecureClientDisablePkce,
Attribute::OAuth2JwtLegacyCryptoEnable,
Attribute::OAuth2PreferShortUsername,
@@ -783,6 +786,7 @@ lazy_static! {
Attribute::OAuth2RsOriginLanding,
Attribute::OAuth2RsSupScopeMap,
Attribute::OAuth2RsScopeMap,
+ Attribute::OAuth2RsBasicSecret,
Attribute::OAuth2AllowInsecureClientDisablePkce,
Attribute::OAuth2JwtLegacyCryptoEnable,
Attribute::OAuth2PreferShortUsername,
@@ -871,6 +875,7 @@ lazy_static! {
Attribute::OAuth2RsOriginLanding,
Attribute::OAuth2RsSupScopeMap,
Attribute::OAuth2RsScopeMap,
+ Attribute::OAuth2RsBasicSecret,
Attribute::OAuth2AllowInsecureClientDisablePkce,
Attribute::OAuth2JwtLegacyCryptoEnable,
Attribute::OAuth2PreferShortUsername,
@@ -889,6 +894,7 @@ lazy_static! {
Attribute::OAuth2RsOriginLanding,
Attribute::OAuth2RsSupScopeMap,
Attribute::OAuth2RsScopeMap,
+ Attribute::OAuth2RsBasicSecret,
Attribute::OAuth2AllowInsecureClientDisablePkce,
Attribute::OAuth2JwtLegacyCryptoEnable,
Attribute::OAuth2PreferShortUsername,
@@ -980,6 +986,7 @@ lazy_static! {
Attribute::OAuth2RsOriginLanding,
Attribute::OAuth2RsSupScopeMap,
Attribute::OAuth2RsScopeMap,
+ Attribute::OAuth2RsBasicSecret,
Attribute::OAuth2AllowInsecureClientDisablePkce,
Attribute::OAuth2JwtLegacyCryptoEnable,
Attribute::OAuth2PreferShortUsername,
@@ -999,6 +1006,7 @@ lazy_static! {
Attribute::OAuth2RsOriginLanding,
Attribute::OAuth2RsSupScopeMap,
Attribute::OAuth2RsScopeMap,
+ Attribute::OAuth2RsBasicSecret,
Attribute::OAuth2AllowInsecureClientDisablePkce,
Attribute::OAuth2JwtLegacyCryptoEnable,
Attribute::OAuth2PreferShortUsername,
--
2.47.2

View File

@@ -0,0 +1,174 @@
From c8ed69efe3f702b19834c2659be1dd3ec2d41c17 Mon Sep 17 00:00:00 2001
From: oddlama <oddlama@oddlama.org>
Date: Fri, 1 Nov 2024 12:27:43 +0100
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 420e72c6c..5c4353116 100644
--- a/server/core/src/actors/internal.rs
+++ b/server/core/src/actors/internal.rs
@@ -171,25 +171,26 @@ impl QueryServerWriteV1 {
}
#[instrument(
level = "info",
- skip(self, eventid),
+ skip(self, password, 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 7486d34a8..784106352 100644
--- a/server/daemon/src/main.rs
+++ b/server/daemon/src/main.rs
@@ -903,27 +903,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.46.1

View File

@@ -0,0 +1,159 @@
From fc26fe5ac9e9cd65af82609c5a4966c8f756ea0f Mon Sep 17 00:00:00 2001
From: oddlama <oddlama@oddlama.org>
Date: Fri, 21 Mar 2025 16:07:54 +0100
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/server/migrations.rs | 16 +++++++++++
4 files changed, 92 insertions(+), 1 deletion(-)
diff --git a/server/core/src/actors/v1_write.rs b/server/core/src/actors/v1_write.rs
index 732e826c8..a2b8e503f 100644
--- a/server/core/src/actors/v1_write.rs
+++ b/server/core/src/actors/v1_write.rs
@@ -324,6 +324,48 @@ impl QueryServerWriteV1 {
.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,
diff --git a/server/core/src/https/v1.rs b/server/core/src/https/v1.rs
index 30de387b8..a11aa8ecd 100644
--- a/server/core/src/https/v1.rs
+++ b/server/core/src/https/v1.rs
@@ -4,7 +4,7 @@ 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};
@@ -3129,6 +3129,10 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
"/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)
diff --git a/server/core/src/https/v1_oauth2.rs b/server/core/src/https/v1_oauth2.rs
index f399539bc..ffad9921e 100644
--- a/server/core/src/https/v1_oauth2.rs
+++ b/server/core/src/https/v1_oauth2.rs
@@ -151,6 +151,35 @@ pub(crate) async fn oauth2_id_get_basic_secret(
.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}",
diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs
index fd0bca8db..8621714f2 100644
--- a/server/lib/src/server/migrations.rs
+++ b/server/lib/src/server/migrations.rs
@@ -171,6 +171,22 @@ impl QueryServer {
reload_required = true;
};
+ // secret provisioning: allow idm_admin to modify OAuth2RsBasicSecret.
+ write_txn.internal_modify_uuid(
+ UUID_IDM_ACP_OAUTH2_MANAGE_V1,
+ &ModifyList::new_append(
+ Attribute::AcpCreateAttr,
+ Attribute::OAuth2RsBasicSecret.into(),
+ ),
+ )?;
+ write_txn.internal_modify_uuid(
+ UUID_IDM_ACP_OAUTH2_MANAGE_V1,
+ &ModifyList::new_append(
+ Attribute::AcpModifyPresentAttr,
+ Attribute::OAuth2RsBasicSecret.into(),
+ ),
+ )?;
+
// Execute whatever operations we have batched up and ready to go. This is needed
// to preserve ordering of the operations - if we reloaded after a remigrate then
// we would have skipped the patch level fix which needs to have occurred *first*.
--
2.49.0

View File

@@ -0,0 +1,122 @@
From 229165abe5be596fc2be8e285884813a1b5a38c8 Mon Sep 17 00:00:00 2001
From: oddlama <oddlama@oddlama.org>
Date: Fri, 21 Mar 2025 16:08:15 +0100
Subject: [PATCH 2/2] recover account
---
server/core/src/actors/internal.rs | 5 +++--
server/core/src/admin.rs | 6 +++---
server/daemon/src/main.rs | 23 ++++++++++++++++++++++-
server/daemon/src/opt.rs | 7 +++++++
4 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/server/core/src/actors/internal.rs b/server/core/src/actors/internal.rs
index 420e72c6c..e252bca51 100644
--- a/server/core/src/actors/internal.rs
+++ b/server/core/src/actors/internal.rs
@@ -172,17 +172,18 @@ impl QueryServerWriteV1 {
#[instrument(
level = "info",
- skip(self, eventid),
+ skip(self, password, 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)
}
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
@@ -24,7 +24,7 @@ pub use kanidm_proto::internal::{
#[derive(Serialize, Deserialize, Debug)]
pub enum AdminTaskRequest {
- RecoverAccount { name: String },
+ RecoverAccount { name: String, password: Option<String> },
ShowReplicationCertificate,
RenewReplicationCertificate,
RefreshReplicationConsumer,
@@ -309,8 +309,8 @@ async fn handle_client(
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");
diff --git a/server/daemon/src/main.rs b/server/daemon/src/main.rs
index c3b40faa0..2a57a307c 100644
--- a/server/daemon/src/main.rs
+++ b/server/daemon/src/main.rs
@@ -923,13 +923,34 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
.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_FILE") {
+ Ok(path) => match tokio::fs::read_to_string(&path).await {
+ Ok(contents) => Some(contents),
+ Err(e) => {
+ error!("Failed to read password file '{}': {}", path, e);
+ return ExitCode::FAILURE;
+ }
+ },
+ Err(_) => match std::env::var("KANIDM_RECOVER_ACCOUNT_PASSWORD") {
+ Ok(val) => Some(val),
+ Err(_) => {
+ error!("Neither KANIDM_RECOVER_ACCOUNT_PASSWORD_FILE nor KANIDM_RECOVER_ACCOUNT_PASSWORD was set");
+ return ExitCode::FAILURE;
+ }
+ }
+ }
+ } else {
+ None
+ };
submit_admin_req(
config.adminbindpath.as_str(),
AdminTaskRequest::RecoverAccount {
name: name.to_owned(),
+ password,
},
output_mode,
)
diff --git a/server/daemon/src/opt.rs b/server/daemon/src/opt.rs
index f1b45a5b3..ca19fb6a5 100644
--- a/server/daemon/src/opt.rs
+++ b/server/daemon/src/opt.rs
@@ -236,6 +236,13 @@ enum KanidmdOpt {
#[clap(value_parser)]
/// The account name to recover credentials for.
name: String,
+ /// Use a password given via an environment variable.
+ /// - `KANIDM_RECOVER_ACCOUNT_PASSWORD_FILE` takes precedence and reads the desired
+ /// password from the given file
+ /// - `KANIDM_RECOVER_ACCOUNT_PASSWORD` directly takes a
+ /// password - beware that this will leave the password in the environment
+ #[clap(long = "from-environment")]
+ from_environment: bool,
#[clap(flatten)]
commonopts: CommonOpt,
},
--
2.49.0

View File

@@ -0,0 +1,159 @@
From fc26fe5ac9e9cd65af82609c5a4966c8f756ea0f Mon Sep 17 00:00:00 2001
From: oddlama <oddlama@oddlama.org>
Date: Fri, 21 Mar 2025 16:07:54 +0100
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/server/migrations.rs | 16 +++++++++++
4 files changed, 92 insertions(+), 1 deletion(-)
diff --git a/server/core/src/actors/v1_write.rs b/server/core/src/actors/v1_write.rs
index 732e826c8..a2b8e503f 100644
--- a/server/core/src/actors/v1_write.rs
+++ b/server/core/src/actors/v1_write.rs
@@ -324,6 +324,48 @@ impl QueryServerWriteV1 {
.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,
diff --git a/server/core/src/https/v1.rs b/server/core/src/https/v1.rs
index 30de387b8..a11aa8ecd 100644
--- a/server/core/src/https/v1.rs
+++ b/server/core/src/https/v1.rs
@@ -4,7 +4,7 @@ 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};
@@ -3129,6 +3129,10 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
"/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)
diff --git a/server/core/src/https/v1_oauth2.rs b/server/core/src/https/v1_oauth2.rs
index f399539bc..ffad9921e 100644
--- a/server/core/src/https/v1_oauth2.rs
+++ b/server/core/src/https/v1_oauth2.rs
@@ -151,6 +151,35 @@ pub(crate) async fn oauth2_id_get_basic_secret(
.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}",
diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs
index fd0bca8db..8621714f2 100644
--- a/server/lib/src/server/migrations.rs
+++ b/server/lib/src/server/migrations.rs
@@ -171,6 +171,22 @@ impl QueryServer {
reload_required = true;
};
+ // secret provisioning: allow idm_admin to modify OAuth2RsBasicSecret.
+ write_txn.internal_modify_uuid(
+ UUID_IDM_ACP_OAUTH2_MANAGE_V1,
+ &ModifyList::new_append(
+ Attribute::AcpCreateAttr,
+ Attribute::OAuth2RsBasicSecret.into(),
+ ),
+ )?;
+ write_txn.internal_modify_uuid(
+ UUID_IDM_ACP_OAUTH2_MANAGE_V1,
+ &ModifyList::new_append(
+ Attribute::AcpModifyPresentAttr,
+ Attribute::OAuth2RsBasicSecret.into(),
+ ),
+ )?;
+
// Execute whatever operations we have batched up and ready to go. This is needed
// to preserve ordering of the operations - if we reloaded after a remigrate then
// we would have skipped the patch level fix which needs to have occurred *first*.
--
2.49.0

View File

@@ -0,0 +1,122 @@
From 229165abe5be596fc2be8e285884813a1b5a38c8 Mon Sep 17 00:00:00 2001
From: oddlama <oddlama@oddlama.org>
Date: Fri, 21 Mar 2025 16:08:15 +0100
Subject: [PATCH 2/2] recover account
---
server/core/src/actors/internal.rs | 5 +++--
server/core/src/admin.rs | 6 +++---
server/daemon/src/main.rs | 23 ++++++++++++++++++++++-
server/daemon/src/opt.rs | 7 +++++++
4 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/server/core/src/actors/internal.rs b/server/core/src/actors/internal.rs
index 420e72c6c..e252bca51 100644
--- a/server/core/src/actors/internal.rs
+++ b/server/core/src/actors/internal.rs
@@ -172,17 +172,18 @@ impl QueryServerWriteV1 {
#[instrument(
level = "info",
- skip(self, eventid),
+ skip(self, password, 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)
}
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
@@ -24,7 +24,7 @@ pub use kanidm_proto::internal::{
#[derive(Serialize, Deserialize, Debug)]
pub enum AdminTaskRequest {
- RecoverAccount { name: String },
+ RecoverAccount { name: String, password: Option<String> },
DisableAccount { name: String },
ShowReplicationCertificate,
RenewReplicationCertificate,
@@ -309,8 +309,8 @@ async fn handle_client(
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");
diff --git a/server/daemon/src/main.rs b/server/daemon/src/main.rs
index c3b40faa0..2a57a307c 100644
--- a/server/daemon/src/main.rs
+++ b/server/daemon/src/main.rs
@@ -923,13 +923,34 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
.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_FILE") {
+ Ok(path) => match tokio::fs::read_to_string(&path).await {
+ Ok(contents) => Some(contents),
+ Err(e) => {
+ error!("Failed to read password file '{}': {}", path, e);
+ return ExitCode::FAILURE;
+ }
+ },
+ Err(_) => match std::env::var("KANIDM_RECOVER_ACCOUNT_PASSWORD") {
+ Ok(val) => Some(val),
+ Err(_) => {
+ error!("Neither KANIDM_RECOVER_ACCOUNT_PASSWORD_FILE nor KANIDM_RECOVER_ACCOUNT_PASSWORD was set");
+ return ExitCode::FAILURE;
+ }
+ }
+ }
+ } else {
+ None
+ };
submit_admin_req(
config.adminbindpath.as_str(),
AdminTaskRequest::RecoverAccount {
name: name.to_owned(),
+ password,
},
output_mode,
)
diff --git a/server/daemon/src/opt.rs b/server/daemon/src/opt.rs
index f1b45a5b3..ca19fb6a5 100644
--- a/server/daemon/src/opt.rs
+++ b/server/daemon/src/opt.rs
@@ -236,6 +236,13 @@ enum KanidmdOpt {
#[clap(value_parser)]
/// The account name to recover credentials for.
name: String,
+ /// Use a password given via an environment variable.
+ /// - `KANIDM_RECOVER_ACCOUNT_PASSWORD_FILE` takes precedence and reads the desired
+ /// password from the given file
+ /// - `KANIDM_RECOVER_ACCOUNT_PASSWORD` directly takes a
+ /// password - beware that this will leave the password in the environment
+ #[clap(long = "from-environment")]
+ from_environment: bool,
#[clap(flatten)]
commonopts: CommonOpt,
},
--
2.49.0