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.
## 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]
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"

View file

@ -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....
<table>
<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
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
```
kanidm account validity begin_from demo_user any|clear --name idm_admin
kanidm account validity expire_at demo_user never|clear --name idm_admin
Set the expiry or end date of the account:
```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.

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.
* 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
> 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 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,

View file

@ -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<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)]
@ -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)]

View file

@ -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))