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:** +> ![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. 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))