diff --git a/DEVELOPER_README.md b/DEVELOPER_README.md
index c2caaa053..3fa7258f3 100644
--- a/DEVELOPER_README.md
+++ b/DEVELOPER_README.md
@@ -247,3 +247,25 @@ docker run --rm -it \
```
This assumes you have a `config.ini` file in the current working directory.
+
+## Building the Book
+
+You'll need `mdbook` to build the book:
+
+```shell
+cargo install mdbook
+```
+
+To build it:
+
+```shell
+cd kanidm_book
+mdbook build
+```
+
+Or to run a local webserver:
+
+```shell
+cd kanidm_book
+mdbook serve
+````
diff --git a/kanidm_book/book.toml b/kanidm_book/book.toml
index fff6f68fb..98ab26961 100644
--- a/kanidm_book/book.toml
+++ b/kanidm_book/book.toml
@@ -1,6 +1,13 @@
[book]
-authors = ["William Brown"]
+authors = [
+ "James Hodgkinson",
+ "William Brown",
+]
language = "en"
multilingual = false
src = "src"
title = "Kanidm Administration"
+
+[output.html]
+edit-url-template = "https://github.com/kanidm/kanidm/edit/master/kanidm_book/{path}"
+git-repository-url = "https://github.com/kanidm/kanidm"
diff --git a/kanidm_book/src/accounts_and_groups.md b/kanidm_book/src/accounts_and_groups.md
index f97ff9d64..070d2d1c6 100644
--- a/kanidm_book/src/accounts_and_groups.md
+++ b/kanidm_book/src/accounts_and_groups.md
@@ -27,25 +27,57 @@ and sensitive data), group management, and more.
## Recovering the Initial idm_admin Account
By default the idm_admin user has no password, and can not be accessed. You should recover it with the
-admin (system admin) account. We recommend the use of "reset_credential" as it provides a high
-strength, random, machine only password.
+admin (system admin) account. We recommend the use of the "recover_account" functionality as it provides a high strength, random password.
- kanidm account credential reset_credential --name admin idm_admin
- Generated password for idm_admin: tqoReZfz....
+
+
+
+
+ |
+Warning: The server must not be running at this point, as it requires exclusive access to the database. |
+
+
+
+
+```shell
+kanidmd recover_account -c /etc/kanidm/server.toml -n idm_admin
+Successfully recovered account 'idm_admin' - password reset to -> j9YUv...
+```
+
+To do this in Docker, you'll neeD to stop the existing container and run it with "bash" as the "command" argument, to get a shell, then run the `kanidmd` command above.
+
+For example, if I'm using the developer image in my test environment:
+
+```shell
+docker run --rm -it \
+ -v/tmp/kanidm:/data\
+ --name kanidmd \
+ --hostname kanidmd \
+ ghcr.io/kanidm/kanidmd:devel \
+ bash
+kanidmd:/# kanidmd recover_account -c /data/server.toml -n idm_admin
+Successfully recovered account 'idm_admin' - password reset to -> j9YUv...
+```
+
+Once that's done, exit the shell and start your server container again.
## Creating Accounts
You can now use the idm_admin user to create initial groups and accounts.
- kanidm group create demo_group --name idm_admin
- kanidm account create demo_user "Demonstration User" --name idm_admin
- kanidm group add_members demo_group demo_user --name idm_admin
- kanidm group list_members demo_group --name idm_admin
- kanidm account get demo_user --name idm_admin
+```shell
+kanidm login --name idm_admin
+kanidm group create demo_group --name idm_admin
+kanidm account create demo_user "Demonstration User" --name idm_admin
+kanidm group add_members demo_group demo_user --name idm_admin
+kanidm group list_members demo_group --name idm_admin
+kanidm account get demo_user --name idm_admin
+```
You can also use anonymous to view users and groups - note that you won't see as many fields due
-to the different anonymous access profile limits.
+to the limits of the anonymous access profile.
+ kanidm login --name anonymous
kanidm account get demo_user --name anonymous
## Viewing Default Groups
@@ -62,10 +94,31 @@ Members of the `idm_account_manage_priv` group have the rights to manage other u
accounts security and login aspects. This includes resetting account credentials.
You can perform a password reset on the demo_user, for example as the idm_admin user, who is
-a default member of this group.
+a default member of this group. The lines below prefixed with `#` are the interactive credential
+update interface.
- kanidm account credential set_password demo_user --name idm_admin
- kanidm self whoami --name demo_user
+```shell
+kanidm account credential update demo_user --name idm_admin
+# spn: demo_user@idm.example.com
+# Name: Demonstration User
+# Primary Credential:
+# uuid: 0e19cd08-f943-489e-8ff2-69f9eacb1f31
+# generated password: set
+# Can Commit: true
+#
+# cred update (? for help) # : pass
+# New password:
+# New password: [hidden]
+# Confirm password:
+# Confirm password: [hidden]
+# success
+#
+# cred update (? for help) # : commit
+# Do you want to commit your changes? yes
+# success
+kanidm login --name demo_user
+kanidm self whoami --name demo_user
+```
## Nested Groups
@@ -76,18 +129,20 @@ Kanidm makes all group membership determinations by inspecting an entry's "membe
An example can be easily shown with:
- kanidm group create group_1 --name idm_admin
- kanidm group create group_2 --name idm_admin
- kanidm account create nest_example "Nesting Account Example" --name idm_admin
- kanidm group add_members group_1 group_2 --name idm_admin
- kanidm group add_members group_2 nest_example --name idm_admin
- kanidm account get nest_example --name anonymous
+```shell
+kanidm group create group_1 --name idm_admin
+kanidm group create group_2 --name idm_admin
+kanidm account create nest_example "Nesting Account Example" --name idm_admin
+kanidm group add_members group_1 group_2 --name idm_admin
+kanidm group add_members group_2 nest_example --name idm_admin
+kanidm account get nest_example --name anonymous
+```
## Account Validity
Kanidm supports accounts that are only able to be authenticated between specific date and time
-windows. This takes the form of a "valid from" attribute that defines the earliest start
date where authentication can succeed, and an expiry date where the account will no longer
+windows. This takes the form of a "valid from" attribute that defines the earliest start
allow authentication.
This can be displayed with:
@@ -102,15 +157,24 @@ to aid correct understanding of when the events will occur.
To set the values, an account with account management permission is required (for example, idm_admin).
Again, these values will correctly translated from the entered local timezone to UTC.
- # Set the earliest time the account can start authenticating
- kanidm account validity begin_from demo_user '2020-09-25T11:22:04+00:00' --name idm_admin
- # Set the expiry or end date of the account
- kanidm account validity expire_at demo_user '2020-09-25T11:22:04+00:00' --name idm_admin
+Set the earliest time the account can start authenticating:
-To unset or remove these values the following can be used:
+```shell
+kanidm account validity begin_from demo_user '2020-09-25T11:22:04+00:00' --name idm_admin
+```
+
+Set the expiry or end date of the account:
- kanidm account validity begin_from demo_user any|clear --name idm_admin
- kanidm account validity expire_at demo_user never|clear --name idm_admin
+```shell
+kanidm account validity expire_at demo_user '2020-09-25T11:22:04+00:00' --name idm_admin
+```
+
+To unset or remove these values the following can be used, where `any|clear` means you may use either `any` or `clear`.
+
+```shell
+kanidm account validity begin_from demo_user any|clear --name idm_admin
+kanidm account validity expire_at demo_user never|clear --name idm_admin
+```
To "lock" an account, you can set the expire_at value to the past, or unix epoch. Even in the situation
where the "valid from" is *after* the expire_at, the expire_at will be respected.
diff --git a/kanidm_book/src/images/kani-alert.png b/kanidm_book/src/images/kani-alert.png
new file mode 100644
index 000000000..0882453e0
Binary files /dev/null and b/kanidm_book/src/images/kani-alert.png differ
diff --git a/kanidm_book/src/images/kani-warning.png b/kanidm_book/src/images/kani-warning.png
new file mode 100644
index 000000000..99924c743
Binary files /dev/null and b/kanidm_book/src/images/kani-warning.png differ
diff --git a/kanidm_book/src/intro.md b/kanidm_book/src/intro.md
index 622418cab..e9db8b956 100644
--- a/kanidm_book/src/intro.md
+++ b/kanidm_book/src/intro.md
@@ -9,7 +9,9 @@ The intent of the Kanidm project is to:
* Enable integrations to systems and services so they can authenticate accounts.
* Make system, network, application and web authentication easy and accessible.
-> **NOTICE:**
+>  **NOTICE:**
+>
+>
> This is a pre-release project. While all effort has been made to ensure no data loss
> or security flaws, you should still be careful when using this in your environment.
diff --git a/kanidm_book/theme/favicon.png b/kanidm_book/theme/favicon.png
new file mode 100644
index 000000000..0bcdc0054
Binary files /dev/null and b/kanidm_book/theme/favicon.png differ
diff --git a/kanidm_tools/src/cli/account.rs b/kanidm_tools/src/cli/account.rs
index 5c9c185b2..ca2f35027 100644
--- a/kanidm_tools/src/cli/account.rs
+++ b/kanidm_tools/src/cli/account.rs
@@ -7,7 +7,7 @@ use dialoguer::{theme::ColorfulTheme, Select};
use dialoguer::{Confirm, Input, Password};
use kanidm_client::ClientError::Http as ClientErrorHttp;
use kanidm_client::KanidmClient;
-use kanidm_proto::messages::{AccountChangeMessage, MessageStatus};
+use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus};
use kanidm_proto::v1::OperationError::{InvalidAttribute, PasswordQuality};
use kanidm_proto::v1::{CUIntentToken, CURegState, CUSessionToken, CUStatus};
use qrcode::{render::unicode, QrCode};
@@ -23,7 +23,7 @@ impl AccountOpt {
AccountOpt::Radius { commands } => match commands {
AccountRadius::Show(aro) => aro.copt.debug,
AccountRadius::Generate(aro) => aro.copt.debug,
- AccountRadius::Delete(aro) => aro.copt.debug,
+ AccountRadius::DeleteSecret(aro) => aro.copt.debug,
},
AccountOpt::Posix { commands } => match commands {
AccountPosix::Show(apo) => apo.copt.debug,
@@ -87,14 +87,34 @@ impl AccountOpt {
error!("Error -> {:?}", e);
}
}
- AccountRadius::Delete(aopt) => {
+ AccountRadius::DeleteSecret(aopt) => {
let client = aopt.copt.to_client().await;
- if let Err(e) = client
+ let mut modmessage = AccountChangeMessage {
+ output_mode: ConsoleOutputMode::Text,
+ action: "radius account_delete".to_string(),
+ result: "deleted".to_string(),
+ src_user: aopt
+ .copt
+ .username
+ .to_owned()
+ .unwrap_or(format!("{:?}", client.whoami().await)),
+ dest_user: aopt.aopts.account_id.to_string(),
+ status: MessageStatus::Success,
+ };
+ match client
.idm_account_radius_credential_delete(aopt.aopts.account_id.as_str())
.await
{
- error!("Error -> {:?}", e);
- }
+ Err(e) => {
+ modmessage.status = MessageStatus::Failure;
+ modmessage.result = format!("Error -> {:?}", e);
+ error!("{}", modmessage);
+ }
+ Ok(result) => {
+ debug!("{:?}", result);
+ println!("{}", modmessage);
+ }
+ };
}
}, // end AccountOpt::Radius
AccountOpt::Posix { commands } => match commands {
@@ -149,7 +169,7 @@ impl AccountOpt {
AccountPerson::Extend(aopt) => {
let client = aopt.copt.to_client().await;
let mut result_output = kanidm_proto::messages::AccountChangeMessage {
- output_mode: aopt.copt.output_mode.to_owned().into(),
+ output_mode: ConsoleOutputMode::Text,
action: String::from("account_person_extend"),
result: String::from("This is a filler message"),
src_user: aopt
@@ -221,7 +241,7 @@ impl AccountOpt {
AccountPerson::Set(aopt) => {
let client = aopt.copt.to_client().await;
let mut result_output = AccountChangeMessage {
- output_mode: aopt.copt.output_mode.to_owned().into(),
+ output_mode: ConsoleOutputMode::Text,
action: String::from("account_person set"),
result: String::from(""),
src_user: aopt
@@ -310,12 +330,32 @@ impl AccountOpt {
}
AccountOpt::Delete(aopt) => {
let client = aopt.copt.to_client().await;
- if let Err(e) = client
+ let mut modmessage = AccountChangeMessage {
+ output_mode: ConsoleOutputMode::Text,
+ action: "account delete".to_string(),
+ result: "deleted".to_string(),
+ src_user: aopt
+ .copt
+ .username
+ .to_owned()
+ .unwrap_or(format!("{:?}", client.whoami().await)),
+ dest_user: aopt.aopts.account_id.to_string(),
+ status: MessageStatus::Success,
+ };
+ match client
.idm_account_delete(aopt.aopts.account_id.as_str())
.await
{
- error!("Error -> {:?}", e)
- }
+ Err(e) => {
+ modmessage.result = format!("Error -> {:?}", e);
+ modmessage.status = MessageStatus::Failure;
+ eprintln!("{}", modmessage);
+ }
+ Ok(result) => {
+ debug!("{:?}", result);
+ println!("{}", modmessage);
+ }
+ };
}
AccountOpt::Create(acopt) => {
let client = acopt.copt.to_client().await;
@@ -333,6 +373,7 @@ impl AccountOpt {
AccountValidity::Show(ano) => {
let client = ano.copt.to_client().await;
+ println!("user: {}", ano.aopts.account_id.as_str());
let ex = match client
.idm_account_get_attr(ano.aopts.account_id.as_str(), "account_expire")
.await
@@ -463,9 +504,9 @@ impl AccountOpt {
impl AccountCredential {
pub fn debug(&self) -> bool {
match self {
+ AccountCredential::CreateResetToken(aopt) => aopt.copt.debug,
+ AccountCredential::UseResetToken(aopt) => aopt.copt.debug,
AccountCredential::Update(aopt) => aopt.copt.debug,
- AccountCredential::Reset(aopt) => aopt.copt.debug,
- AccountCredential::CreateResetLink(aopt) => aopt.copt.debug,
}
}
@@ -485,7 +526,8 @@ impl AccountCredential {
}
}
}
- AccountCredential::Reset(aopt) => {
+ // The account credential use_reset_token CLI
+ AccountCredential::UseResetToken(aopt) => {
let client = aopt.copt.to_unauth_client();
let cuintent_token = CUIntentToken {
token: aopt.token.clone(),
@@ -499,11 +541,20 @@ impl AccountCredential {
credential_update_exec(cusession_token, custatus, client).await
}
Err(e) => {
- error!("Error starting credential reset -> {:?}", e);
+ match e {
+ ClientErrorHttp(status_code, error, _kopid) => {
+ eprintln!(
+ "Error completing command: HTTP{} - {:?}",
+ status_code,
+ error.unwrap()
+ );
+ }
+ _ => error!("Error starting use_reset_token -> {:?}", e),
+ };
}
}
}
- AccountCredential::CreateResetLink(aopt) => {
+ AccountCredential::CreateResetToken(aopt) => {
let client = aopt.copt.to_client().await;
// What's the client url?
@@ -517,11 +568,14 @@ impl AccountCredential {
url.query_pairs_mut()
.append_pair("token", cuintent_token.token.as_str());
- println!("success!");
- println!(
- "Send the person one of the following to allow the credential reset"
+ debug!(
+ "Successfully created credential reset token for {}: {}",
+ aopt.aopts.account_id, cuintent_token.token
);
- println!("scan:");
+ println!(
+ "The person can use one of the following to allow the credential reset"
+ );
+ println!("\nScan this QR Code:\n");
let code = match QrCode::new(url.as_str()) {
Ok(c) => c,
Err(e) => {
@@ -537,9 +591,9 @@ impl AccountCredential {
println!("{}", image);
println!();
- println!("link: {}", url.as_str());
+ println!("This link: {}", url.as_str());
println!(
- "command: kanidm account credential reset {}",
+ "Or run this command: kanidm account credential use_reset_token {}",
cuintent_token.token
);
println!();
@@ -844,6 +898,7 @@ fn display_status(status: CUStatus) {
println!("Can Commit: {}", can_commit);
}
+/// This is the REPL for updating a credential for a given account
async fn credential_update_exec(
session_token: CUSessionToken,
status: CUStatus,
diff --git a/kanidm_tools/src/opt/kanidm.rs b/kanidm_tools/src/opt/kanidm.rs
index 47a88efd7..db88c4806 100644
--- a/kanidm_tools/src/opt/kanidm.rs
+++ b/kanidm_tools/src/opt/kanidm.rs
@@ -26,9 +26,6 @@ pub struct CommonOpt {
/// Path to a CA certificate file
#[clap(parse(from_os_str), short = 'C', long = "ca", env = "KANIDM_CA_PATH")]
pub ca_path: Option,
- /// Log format (still in very early development)
- #[clap(short, long = "output", env = "KANIDM_OUTPUT", default_value="text")]
- pub output_mode: String,
}
#[derive(Debug, Args)]
@@ -144,7 +141,8 @@ pub struct AccountNamedTagPkOpt {
}
#[derive(Debug, Args)]
-pub struct AnonTokenOpt {
+/// Command-line options for account credental use_reset_token
+pub struct UseResetTokenOpt {
#[clap(flatten)]
copt: CommonOpt,
#[clap(name = "token")]
@@ -163,26 +161,30 @@ pub struct AccountCreateOpt {
#[derive(Debug, Subcommand)]
pub enum AccountCredential {
- /// Interactively update and change the content of the credentials of an account
+ /// Interactively update/change the credentials for an account
#[clap(name = "update")]
Update(AccountNamedOpt),
- /// Given a reset token, interactively perform a credential reset
- #[clap(name = "reset")]
- Reset(AnonTokenOpt),
- /// Create a reset link (token) that can be given to another person so they can
+ /// Using a reset token, interactively reset credentials for a user
+ #[clap(name = "use_reset_token")]
+ UseResetToken(UseResetTokenOpt),
+ /// Create a reset token that can be given to another person so they can
/// recover or reset their account credentials.
- #[clap(name = "create_reset_link")]
- CreateResetLink(AccountNamedOpt),
+ #[clap(name = "create_reset_token")]
+ CreateResetToken(AccountNamedOpt),
}
+/// RADIUS secret management
#[derive(Debug, Subcommand)]
pub enum AccountRadius {
+ /// Show the RADIUS secret for a user.
#[clap(name = "show_secret")]
Show(AccountNamedOpt),
+ /// Generate a randomized RADIUS secret for a user.
#[clap(name = "generate_secret")]
Generate(AccountNamedOpt),
#[clap(name = "delete_secret")]
- Delete(AccountNamedOpt),
+ /// Remove the configured RADIUS secret for the user.
+ DeleteSecret(AccountNamedOpt),
}
#[derive(Debug, Args)]
diff --git a/kanidmd/idm/src/actors/v1_write.rs b/kanidmd/idm/src/actors/v1_write.rs
index c3dd94366..e009f21fd 100644
--- a/kanidmd/idm/src/actors/v1_write.rs
+++ b/kanidmd/idm/src/actors/v1_write.rs
@@ -778,7 +778,7 @@ impl QueryServerWriteV1 {
let intent_token = CredentialUpdateIntentToken {
intent_id: intent_token.token,
};
-
+ // TODO: this is throwing a 500 error when a session is already in use, that seems bad?
idms_prox_write
.exchange_intent_credential_update(intent_token, ct)
.and_then(|tok| idms_prox_write.commit().map(|_| tok))