docs updates and UI cleanup (#874)

* showing the queried user when running account validity show
* updating account delete
* tweaking account and radius delete to show new message formats
* renaming credential reset token ui
* updating documentation for functionality
* added notes to dev readme on how to install/build mdbook and updated docs
This commit is contained in:
James Hodgkinson 2022-07-05 11:38:25 +10:00 committed by GitHub
parent 33caec05d2
commit 61e32bce4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 216 additions and 64 deletions

View file

@ -247,3 +247,25 @@ docker run --rm -it \
``` ```
This assumes you have a `config.ini` file in the current working directory. 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
````

View file

@ -1,6 +1,13 @@
[book] [book]
authors = ["William Brown"] authors = [
"James Hodgkinson",
"William Brown",
]
language = "en" language = "en"
multilingual = false multilingual = false
src = "src" src = "src"
title = "Kanidm Administration" 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"

View file

@ -27,25 +27,57 @@ and sensitive data), group management, and more.
## Recovering the Initial idm_admin Account ## 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 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 admin (system admin) account. We recommend the use of the "recover_account" functionality as it provides a high strength, random password.
strength, random, machine only password.
kanidm account credential reset_credential --name admin idm_admin <table>
Generated password for idm_admin: tqoReZfz.... <tr>
<td>
<img src="/images/kani-warning.png" style="float:left">
</td>
<td>Warning: The server must not be running at this point, as it requires exclusive access to the database.</td>
</tr>
</table>
```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 ## Creating Accounts
You can now use the idm_admin user to create initial groups and accounts. You can now use the idm_admin user to create initial groups and accounts.
kanidm group create demo_group --name idm_admin ```shell
kanidm account create demo_user "Demonstration User" --name idm_admin kanidm login --name idm_admin
kanidm group add_members demo_group demo_user --name idm_admin kanidm group create demo_group --name idm_admin
kanidm group list_members demo_group --name idm_admin kanidm account create demo_user "Demonstration User" --name idm_admin
kanidm account get demo_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 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 kanidm account get demo_user --name anonymous
## Viewing Default Groups ## 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. 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 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 ```shell
kanidm self whoami --name demo_user 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 ## 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: An example can be easily shown with:
kanidm group create group_1 --name idm_admin ```shell
kanidm group create group_2 --name idm_admin kanidm group create group_1 --name idm_admin
kanidm account create nest_example "Nesting Account Example" --name idm_admin kanidm group create group_2 --name idm_admin
kanidm group add_members group_1 group_2 --name idm_admin kanidm account create nest_example "Nesting Account Example" --name idm_admin
kanidm group add_members group_2 nest_example --name idm_admin kanidm group add_members group_1 group_2 --name idm_admin
kanidm account get nest_example --name anonymous kanidm group add_members group_2 nest_example --name idm_admin
kanidm account get nest_example --name anonymous
```
## Account Validity ## Account Validity
Kanidm supports accounts that are only able to be authenticated between specific date and time 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 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. allow authentication.
This can be displayed with: 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). 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. Again, these values will correctly translated from the entered local timezone to UTC.
# Set the earliest time the account can start authenticating 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
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 ```shell
kanidm account validity expire_at demo_user never|clear --name idm_admin 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 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. where the "valid from" is *after* the expire_at, the expire_at will be respected.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -9,7 +9,9 @@ The intent of the Kanidm project is to:
* Enable integrations to systems and services so they can authenticate accounts. * Enable integrations to systems and services so they can authenticate accounts.
* Make system, network, application and web authentication easy and accessible. * Make system, network, application and web authentication easy and accessible.
> **NOTICE:** > ![Kanidm Alert](/images/kani-alert.png) **NOTICE:**
>
>
> This is a pre-release project. While all effort has been made to ensure no data loss > 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. > or security flaws, you should still be careful when using this in your environment.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -7,7 +7,7 @@ use dialoguer::{theme::ColorfulTheme, Select};
use dialoguer::{Confirm, Input, Password}; use dialoguer::{Confirm, Input, Password};
use kanidm_client::ClientError::Http as ClientErrorHttp; use kanidm_client::ClientError::Http as ClientErrorHttp;
use kanidm_client::KanidmClient; 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::OperationError::{InvalidAttribute, PasswordQuality};
use kanidm_proto::v1::{CUIntentToken, CURegState, CUSessionToken, CUStatus}; use kanidm_proto::v1::{CUIntentToken, CURegState, CUSessionToken, CUStatus};
use qrcode::{render::unicode, QrCode}; use qrcode::{render::unicode, QrCode};
@ -23,7 +23,7 @@ impl AccountOpt {
AccountOpt::Radius { commands } => match commands { AccountOpt::Radius { commands } => match commands {
AccountRadius::Show(aro) => aro.copt.debug, AccountRadius::Show(aro) => aro.copt.debug,
AccountRadius::Generate(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 { AccountOpt::Posix { commands } => match commands {
AccountPosix::Show(apo) => apo.copt.debug, AccountPosix::Show(apo) => apo.copt.debug,
@ -87,14 +87,34 @@ impl AccountOpt {
error!("Error -> {:?}", e); error!("Error -> {:?}", e);
} }
} }
AccountRadius::Delete(aopt) => { AccountRadius::DeleteSecret(aopt) => {
let client = aopt.copt.to_client().await; 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()) .idm_account_radius_credential_delete(aopt.aopts.account_id.as_str())
.await .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 }, // end AccountOpt::Radius
AccountOpt::Posix { commands } => match commands { AccountOpt::Posix { commands } => match commands {
@ -149,7 +169,7 @@ impl AccountOpt {
AccountPerson::Extend(aopt) => { AccountPerson::Extend(aopt) => {
let client = aopt.copt.to_client().await; let client = aopt.copt.to_client().await;
let mut result_output = kanidm_proto::messages::AccountChangeMessage { 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"), action: String::from("account_person_extend"),
result: String::from("This is a filler message"), result: String::from("This is a filler message"),
src_user: aopt src_user: aopt
@ -221,7 +241,7 @@ impl AccountOpt {
AccountPerson::Set(aopt) => { AccountPerson::Set(aopt) => {
let client = aopt.copt.to_client().await; let client = aopt.copt.to_client().await;
let mut result_output = AccountChangeMessage { let mut result_output = AccountChangeMessage {
output_mode: aopt.copt.output_mode.to_owned().into(), output_mode: ConsoleOutputMode::Text,
action: String::from("account_person set"), action: String::from("account_person set"),
result: String::from(""), result: String::from(""),
src_user: aopt src_user: aopt
@ -310,12 +330,32 @@ impl AccountOpt {
} }
AccountOpt::Delete(aopt) => { AccountOpt::Delete(aopt) => {
let client = aopt.copt.to_client().await; 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()) .idm_account_delete(aopt.aopts.account_id.as_str())
.await .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) => { AccountOpt::Create(acopt) => {
let client = acopt.copt.to_client().await; let client = acopt.copt.to_client().await;
@ -333,6 +373,7 @@ impl AccountOpt {
AccountValidity::Show(ano) => { AccountValidity::Show(ano) => {
let client = ano.copt.to_client().await; let client = ano.copt.to_client().await;
println!("user: {}", ano.aopts.account_id.as_str());
let ex = match client let ex = match client
.idm_account_get_attr(ano.aopts.account_id.as_str(), "account_expire") .idm_account_get_attr(ano.aopts.account_id.as_str(), "account_expire")
.await .await
@ -463,9 +504,9 @@ impl AccountOpt {
impl AccountCredential { impl AccountCredential {
pub fn debug(&self) -> bool { pub fn debug(&self) -> bool {
match self { match self {
AccountCredential::CreateResetToken(aopt) => aopt.copt.debug,
AccountCredential::UseResetToken(aopt) => aopt.copt.debug,
AccountCredential::Update(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 client = aopt.copt.to_unauth_client();
let cuintent_token = CUIntentToken { let cuintent_token = CUIntentToken {
token: aopt.token.clone(), token: aopt.token.clone(),
@ -499,11 +541,20 @@ impl AccountCredential {
credential_update_exec(cusession_token, custatus, client).await credential_update_exec(cusession_token, custatus, client).await
} }
Err(e) => { 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; let client = aopt.copt.to_client().await;
// What's the client url? // What's the client url?
@ -517,11 +568,14 @@ impl AccountCredential {
url.query_pairs_mut() url.query_pairs_mut()
.append_pair("token", cuintent_token.token.as_str()); .append_pair("token", cuintent_token.token.as_str());
println!("success!"); debug!(
println!( "Successfully created credential reset token for {}: {}",
"Send the person one of the following to allow the credential reset" 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()) { let code = match QrCode::new(url.as_str()) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
@ -537,9 +591,9 @@ impl AccountCredential {
println!("{}", image); println!("{}", image);
println!(); println!();
println!("link: {}", url.as_str()); println!("This link: {}", url.as_str());
println!( println!(
"command: kanidm account credential reset {}", "Or run this command: kanidm account credential use_reset_token {}",
cuintent_token.token cuintent_token.token
); );
println!(); println!();
@ -844,6 +898,7 @@ fn display_status(status: CUStatus) {
println!("Can Commit: {}", can_commit); println!("Can Commit: {}", can_commit);
} }
/// This is the REPL for updating a credential for a given account
async fn credential_update_exec( async fn credential_update_exec(
session_token: CUSessionToken, session_token: CUSessionToken,
status: CUStatus, status: CUStatus,

View file

@ -26,9 +26,6 @@ pub struct CommonOpt {
/// Path to a CA certificate file /// Path to a CA certificate file
#[clap(parse(from_os_str), short = 'C', long = "ca", env = "KANIDM_CA_PATH")] #[clap(parse(from_os_str), short = 'C', long = "ca", env = "KANIDM_CA_PATH")]
pub ca_path: Option<PathBuf>, pub ca_path: Option<PathBuf>,
/// Log format (still in very early development)
#[clap(short, long = "output", env = "KANIDM_OUTPUT", default_value="text")]
pub output_mode: String,
} }
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -144,7 +141,8 @@ pub struct AccountNamedTagPkOpt {
} }
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct AnonTokenOpt { /// Command-line options for account credental use_reset_token
pub struct UseResetTokenOpt {
#[clap(flatten)] #[clap(flatten)]
copt: CommonOpt, copt: CommonOpt,
#[clap(name = "token")] #[clap(name = "token")]
@ -163,26 +161,30 @@ pub struct AccountCreateOpt {
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum AccountCredential { 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")] #[clap(name = "update")]
Update(AccountNamedOpt), Update(AccountNamedOpt),
/// Given a reset token, interactively perform a credential reset /// Using a reset token, interactively reset credentials for a user
#[clap(name = "reset")] #[clap(name = "use_reset_token")]
Reset(AnonTokenOpt), UseResetToken(UseResetTokenOpt),
/// Create a reset link (token) that can be given to another person so they can /// Create a reset token that can be given to another person so they can
/// recover or reset their account credentials. /// recover or reset their account credentials.
#[clap(name = "create_reset_link")] #[clap(name = "create_reset_token")]
CreateResetLink(AccountNamedOpt), CreateResetToken(AccountNamedOpt),
} }
/// RADIUS secret management
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum AccountRadius { pub enum AccountRadius {
/// Show the RADIUS secret for a user.
#[clap(name = "show_secret")] #[clap(name = "show_secret")]
Show(AccountNamedOpt), Show(AccountNamedOpt),
/// Generate a randomized RADIUS secret for a user.
#[clap(name = "generate_secret")] #[clap(name = "generate_secret")]
Generate(AccountNamedOpt), Generate(AccountNamedOpt),
#[clap(name = "delete_secret")] #[clap(name = "delete_secret")]
Delete(AccountNamedOpt), /// Remove the configured RADIUS secret for the user.
DeleteSecret(AccountNamedOpt),
} }
#[derive(Debug, Args)] #[derive(Debug, Args)]

View file

@ -778,7 +778,7 @@ impl QueryServerWriteV1 {
let intent_token = CredentialUpdateIntentToken { let intent_token = CredentialUpdateIntentToken {
intent_id: intent_token.token, 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 idms_prox_write
.exchange_intent_credential_update(intent_token, ct) .exchange_intent_credential_update(intent_token, ct)
.and_then(|tok| idms_prox_write.commit().map(|_| tok)) .and_then(|tok| idms_prox_write.commit().map(|_| tok))