Expose TPM in more interface places (#2334)

This commit is contained in:
Firstyear 2023-11-27 14:35:59 +10:00 committed by GitHub
parent e9ae132631
commit 060cb729a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 274 additions and 102 deletions

18
Cargo.lock generated
View file

@ -1433,18 +1433,18 @@ dependencies = [
[[package]] [[package]]
name = "enum-map" name = "enum-map"
version = "2.7.2" version = "2.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09e6b4f374c071b18172e23134e01026653dc980636ee139e0dfe59c538c61e5" checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9"
dependencies = [ dependencies = [
"enum-map-derive", "enum-map-derive",
] ]
[[package]] [[package]]
name = "enum-map-derive" name = "enum-map-derive"
version = "0.16.0" version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfdb3d73d1beaf47c8593a1364e577fde072677cbfd103600345c0f547408cc0" checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1857,9 +1857,9 @@ dependencies = [
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.28.0" version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]] [[package]]
name = "gix" name = "gix"
@ -2994,11 +2994,12 @@ dependencies = [
[[package]] [[package]]
name = "kanidm-hsm-crypto" name = "kanidm-hsm-crypto"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc2a5dbe42b03b3e5e3f99eab1c5773032c8e508896691b3f4562c7de264d67" checksum = "d325d5f7a3978ad1451f8bad2fdea1cc70a7b33dcaa8bbff7617a80d4c36c449"
dependencies = [ dependencies = [
"argon2", "argon2",
"hex",
"openssl", "openssl",
"serde", "serde",
"tracing", "tracing",
@ -3175,6 +3176,7 @@ dependencies = [
"bytes", "bytes",
"clap", "clap",
"clap_complete", "clap_complete",
"compact_jwt 0.3.2",
"csv", "csv",
"futures", "futures",
"hashbrown 0.14.2", "hashbrown 0.14.2",

View file

@ -84,7 +84,7 @@ kanidmd_lib_macros = { path = "./server/lib-macros", version = "1.1.0-rc.15-dev"
kanidmd_testkit = { path = "./server/testkit", version = "1.1.0-rc.15-dev" } kanidmd_testkit = { path = "./server/testkit", version = "1.1.0-rc.15-dev" }
kanidm_build_profiles = { path = "./libs/profiles", version = "1.1.0-rc.15-dev" } kanidm_build_profiles = { path = "./libs/profiles", version = "1.1.0-rc.15-dev" }
kanidm_client = { path = "./libs/client", version = "1.1.0-rc.15-dev" } kanidm_client = { path = "./libs/client", version = "1.1.0-rc.15-dev" }
kanidm-hsm-crypto = "^0.1.1" kanidm-hsm-crypto = "^0.1.3"
kanidm_lib_crypto = { path = "./libs/crypto", version = "1.1.0-rc.15-dev" } kanidm_lib_crypto = { path = "./libs/crypto", version = "1.1.0-rc.15-dev" }
kanidm_lib_file_permissions = { path = "./libs/file_permissions", version = "1.1.0-rc.15-dev" } kanidm_lib_file_permissions = { path = "./libs/file_permissions", version = "1.1.0-rc.15-dev" }
kanidm_proto = { path = "./proto", version = "1.1.0-rc.15-dev" } kanidm_proto = { path = "./proto", version = "1.1.0-rc.15-dev" }
@ -231,8 +231,6 @@ tracing = { version = "^0.1.40", features = [
tracing-subscriber = { version = "^0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "^0.3.18", features = ["env-filter"] }
tracing-forest = "^0.1.6" tracing-forest = "^0.1.6"
tss-esapi = "^7.4.0"
url = "^2.4.1" url = "^2.4.1"
urlencoding = "2.1.3" urlencoding = "2.1.3"
utoipa = "4.1.0" utoipa = "4.1.0"

View file

@ -38,7 +38,7 @@ systemctl status kanidm-unixd-tasks
``` ```
> **NOTE** The `kanidm_unixd_tasks` daemon is not required for PAM and nsswitch functionality. If > **NOTE** The `kanidm_unixd_tasks` daemon is not required for PAM and nsswitch functionality. If
> disabled, your system will function as usual. It is, however, recommended due to the features it > disabled, your system will function as usual. It is however strongly recommended due to the features it
> provides supporting Kanidm's capabilities. > provides supporting Kanidm's capabilities.
Both unixd daemons use the connection configuration from /etc/kanidm/config. This is the covered in Both unixd daemons use the connection configuration from /etc/kanidm/config. This is the covered in
@ -47,35 +47,9 @@ Both unixd daemons use the connection configuration from /etc/kanidm/config. Thi
You can also configure some unixd-specific options with the file /etc/kanidm/unixd: You can also configure some unixd-specific options with the file /etc/kanidm/unixd:
```toml ```toml
pam_allowed_login_groups = ["posix_group"] {{#rustdoc_include ../../examples/unixd}}
default_shell = "/bin/sh"
home_prefix = "/home/"
home_attr = "uuid"
home_alias = "spn"
use_etc_skel = false
uid_attr_map = "spn"
gid_attr_map = "spn"
selinux = true
allow_local_account_override = ["account_name"]
``` ```
`pam_allowed_login_groups` defines a set of POSIX groups where membership of any of these groups
will be allowed to login via PAM. All POSIX users and groups can be resolved by nss regardless of
PAM login status. This may be a group name, spn, or uuid.
`default_shell` is the default shell for users. Defaults to `/bin/sh`.
`home_prefix` is the prepended path to where home directories are stored. Must end with a trailing
`/`. Defaults to `/home/`.
`home_attr` is the default token attribute used for the home directory path. Valid choices are
`uuid`, `name`, `spn`. Defaults to `uuid`.
`home_alias` is the default token attribute used for generating symlinks pointing to the user's home
directory. If set, this will become the value of the home path to nss calls. It is recommended you
choose a "human friendly" attribute here. Valid choices are `none`, `uuid`, `name`, `spn`. Defaults
to `spn`.
> **NOTICE:** All users in Kanidm can change their name (and their spn) at any time. If you change > **NOTICE:** All users in Kanidm can change their name (and their spn) at any time. If you change
> `home_attr` from `uuid` you _must_ have a plan on how to manage these directory renames in your > `home_attr` from `uuid` you _must_ have a plan on how to manage these directory renames in your
> system. We recommend that you have a stable ID (like the UUID), and symlinks from the name to the > system. We recommend that you have a stable ID (like the UUID), and symlinks from the name to the
@ -85,27 +59,6 @@ to `spn`.
> **NOTE:** Ubuntu users please see: > **NOTE:** Ubuntu users please see:
> [Why aren't snaps launching with home_alias set?](../frequently_asked_questions.md#why-arent-snaps-launching-with-home_alias-set) > [Why aren't snaps launching with home_alias set?](../frequently_asked_questions.md#why-arent-snaps-launching-with-home_alias-set)
`use_etc_skel` controls if home directories should be prepopulated with the contents of `/etc/skel`
when first created. Defaults to false.
`uid_attr_map` chooses which attribute is used for domain local users in presentation. Defaults to
`spn`. Users from a trust will always use spn.
`gid_attr_map` chooses which attribute is used for domain local groups in presentation. Defaults to
`spn`. Groups from a trust will always use spn.
`selinux` controls whether the `kanidm_unixd_tasks` daemon should detect and enable SELinux runtime
compatibility features to ensure that newly created home directories are labeled correctly. This
setting as no bearing on systems without SELinux, as these features will automatically be disabled
if SELinux is not detected when the daemon starts. Note that `kanidm_unixd_tasks` must also be built
with the SELinux feature flag for this functionality. Defaults to true.
`allow_local_account_override` allows kanidm to "override" the content of a user or group that is
defined locally. By default kanidm will detect when a user/group conflict with their entries from
`/etc/passwd` or `/etc/group` and will ignore the kanidm entry. However if you want kanidm to
override users or groups from the local system, you must list them in this field. Note that this can
have many unexpected consequences, so it is not recommended to enable this.
You can then check the communication status of the daemon: You can then check the communication status of the daemon:
```bash ```bash

View file

@ -1,12 +1,117 @@
# this should be at /etc/kanidm/unixd, and configures kanidm-unixd ## Kanidm Unixd Service Configuration - /etc/kanidm/unixd
# some documentation is here: https://github.com/kanidm/kanidm/blob/master/book/src/pam_and_nsswitch.md
# pam_allowed_login_groups = ["posix_group"] # Defines a set of POSIX groups where membership of any of these groups
# will be allowed to login via PAM. All POSIX users and groups can be
# resolved by nss regardless of PAM login status. This may be a group
# name, spn, or uuid.
#
# Default: empty set (no access allowed)
pam_allowed_login_groups = ["posix_group"]
# Kanidm unix will bind all cached credentials to a local Hardware Security
# Module (HSM) to prevent exfiltration and attacks against these. In addition,
# any internal private keys will also be stored in this HSM.
#
# * soft: A software hsm that encrypts all local key material
# * tpm: Use a tpm for all key storage and binding
#
# Default: soft
# hsm_type = "tpm"
# If using `hsm_type = "tpm"`, this allows configuration of the TCTI name of
# the tpm to use. For more, see: https://tpm2-tools.readthedocs.io/en/latest/man/common/tcti/
#
# You should leave this value as the default kernel resource manager.
#
# Default: device:/dev/tpmrm0
# tpm_tcti_name = "device:/dev/tpmrm0"
# Default shell for users if no value is set.
#
# Default: /bin/sh
# default_shell = "/bin/sh" # default_shell = "/bin/sh"
# The prefix prepended to where home directories are stored. Must end with a trailing `/`.
#
# Default: /home/
# home_prefix = "/home/" # home_prefix = "/home/"
# The attribute to use for the stable home directory path. Valid choices are
# `uuid`, `name`, `spn`.
# > **NOTICE:** All users in Kanidm can change their name (and their spn) at any time. If you change
# > `home_attr` from `uuid` you _must_ have a plan on how to manage these directory renames in your
# > system. We recommend that you have a stable ID (like the UUID), and symlinks from the name to the
# > UUID folder. Automatic support is provided for this via the unixd tasks daemon, as documented
# > here.
# Default: uuid
# home_attr = "uuid" # home_attr = "uuid"
# The default token attribute used for generating symlinks pointing to the user's home
# directory. If set, this will become the value of the home path to nss calls. It is recommended you
# choose a "human friendly" attribute here. Valid choices are `none`, `uuid`, `name`, `spn`. Defaults
# to `spn`.
#
# Default: spn
# home_alias = "spn" # home_alias = "spn"
# Controls if home directories should be prepopulated with the contents of `/etc/skel`
# when first created.
#
# Default: false
# use_etc_skel = false # use_etc_skel = false
# Chooses which attribute is used for domain local users in presentation of the uid value.
#
# Default: spn
# NOTE: Users from a trust will always use spn.
# uid_attr_map = "spn" # uid_attr_map = "spn"
# Chooses which attribute is used for domain local groups in presentation of the gid value.
# Default: spn
# NOTE: Groups from a trust will always use spn.
# gid_attr_map = "spn" # gid_attr_map = "spn"
# `selinux` controls whether the `kanidm_unixd_tasks` daemon should detect and enable SELinux runtime
# compatibility features to ensure that newly created home directories are labeled correctly. This
# setting as no bearing on systems without SELinux, as these features will automatically be disabled
# if SELinux is not detected when the daemon starts. Note that `kanidm_unixd_tasks` must also be built
# with the SELinux feature flag for this functionality.
#
# Default: true
# selinux = true
# allows kanidm to "override" the content of a user or group that is defined locally when a name
# collision occurs. By default kanidm will detect when a user/group conflict with their entries from
# `/etc/passwd` or `/etc/group` and will ignore the kanidm entry. However if you want kanidm to
# override users or groups from the local system, you must list them in this field. Note that this can
# have many unexpected consequences, so it is not recommended to enable this.
#
# Default: Empty set (no overrides)
# allow_local_account_override = ["admin"] # allow_local_account_override = ["admin"]

View file

@ -55,10 +55,16 @@ const DS_SSHA512_HASH_LEN: usize = 64;
const ARGON2_VERSION: u32 = 19; const ARGON2_VERSION: u32 = 19;
const ARGON2_SALT_LEN: usize = 16; const ARGON2_SALT_LEN: usize = 16;
const ARGON2_KEY_LEN: usize = 32; const ARGON2_KEY_LEN: usize = 32;
// Default amount of ram we sacrifice per thread is 8MB
const ARGON2_MIN_RAM_KIB: u32 = 8 * 1024; const ARGON2_MIN_RAM_KIB: u32 = 8 * 1024;
const ARGON2_MAX_RAM_KIB: u32 = 32 * 1024; // Max is 64MB. This may change in time.
const ARGON2_MAX_RAM_KIB: u32 = 64 * 1024;
// Amount of ram to subtract when we do a T cost iter. This
// is because t=2 m=32 == t=3 m=20. So we just step down a little
// to keep the value about the same.
const ARGON2_TCOST_RAM_ITER_KIB: u32 = 12 * 1024;
const ARGON2_MIN_T_COST: u32 = 2; const ARGON2_MIN_T_COST: u32 = 2;
const ARGON2_MAX_T_COST: u32 = 4; const ARGON2_MAX_T_COST: u32 = 16;
const ARGON2_MAX_P_COST: u32 = 1; const ARGON2_MAX_P_COST: u32 = 1;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -254,10 +260,7 @@ impl CryptoPolicy {
// thread x ram will be used. If we had 8 threads at 64mb of ram, that would require // thread x ram will be used. If we had 8 threads at 64mb of ram, that would require
// 512mb of ram alone just for hashing. This becomes worse as core counts scale, with // 512mb of ram alone just for hashing. This becomes worse as core counts scale, with
// 24 core xeons easily reaching 1.5GB in these cases. // 24 core xeons easily reaching 1.5GB in these cases.
//
// To try to balance this we cap max ram at 32MB for now.
// Default amount of ram we sacrifice per thread is 8MB
let mut m_cost = ARGON2_MIN_RAM_KIB; let mut m_cost = ARGON2_MIN_RAM_KIB;
let mut t_cost = ARGON2_MIN_T_COST; let mut t_cost = ARGON2_MIN_T_COST;
let p_cost = ARGON2_MAX_P_COST; let p_cost = ARGON2_MAX_P_COST;
@ -278,7 +281,7 @@ impl CryptoPolicy {
}; };
if let Some(ubt) = Password::bench_argon2id(params) { if let Some(ubt) = Password::bench_argon2id(params) {
trace!("{}µs - t_cost {} m_cost {}", ubt.as_nanos(), t_cost, m_cost); debug!("{}µs - t_cost {} m_cost {}", ubt.as_nanos(), t_cost, m_cost);
// Parameter adjustment // Parameter adjustment
if ubt < target_time { if ubt < target_time {
if m_cost < ARGON2_MAX_RAM_KIB { if m_cost < ARGON2_MAX_RAM_KIB {
@ -293,7 +296,7 @@ impl CryptoPolicy {
m_cost * 2 m_cost * 2
} else { } else {
// Close! Increase in a small step // Close! Increase in a small step
m_cost + (2 * 1024) m_cost + 1024
}; };
m_cost = if m_adjust > ARGON2_MAX_RAM_KIB { m_cost = if m_adjust > ARGON2_MAX_RAM_KIB {
@ -308,15 +311,20 @@ impl CryptoPolicy {
// here though, just to give a little window under that for adjustment. // here though, just to give a little window under that for adjustment.
// //
// Similar, once we hit t=4 we just need to have max ram. // Similar, once we hit t=4 we just need to have max ram.
let m_adjust = (t_cost.saturating_sub(ARGON2_MIN_T_COST) t_cost += 1;
* ARGON2_MIN_RAM_KIB) // Halve the ram cost.
+ ARGON2_MAX_RAM_KIB; let m_adjust = m_cost
.checked_sub(ARGON2_TCOST_RAM_ITER_KIB)
.unwrap_or(ARGON2_MIN_RAM_KIB);
// Floor and Ceil
m_cost = if m_adjust > ARGON2_MAX_RAM_KIB { m_cost = if m_adjust > ARGON2_MAX_RAM_KIB {
ARGON2_MAX_RAM_KIB ARGON2_MAX_RAM_KIB
} else if m_adjust < ARGON2_MIN_RAM_KIB {
ARGON2_MIN_RAM_KIB
} else { } else {
m_adjust m_adjust
}; };
t_cost += 1;
continue; continue;
} else { } else {
// Unable to proceed, parameters are maxed out. // Unable to proceed, parameters are maxed out.
@ -1291,7 +1299,7 @@ mod tests {
let im_pw = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ"; let im_pw = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ";
let password = "password"; let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse"); let r = Password::try_from(im_pw).expect("Failed to parse");
assert!(r.requires_upgrade()); assert!(!r.requires_upgrade());
assert!(r.verify(password).unwrap_or(false)); assert!(r.verify(password).unwrap_or(false));
} }
@ -1363,7 +1371,7 @@ mod tests {
let mut hsm: Box<dyn Tpm> = Box::new(SoftTpm::new()); let mut hsm: Box<dyn Tpm> = Box::new(SoftTpm::new());
let auth_value = AuthValue::new_random().unwrap(); let auth_value = AuthValue::ephemeral().unwrap();
let loadable_machine_key = hsm.machine_key_create(&auth_value).unwrap(); let loadable_machine_key = hsm.machine_key_create(&auth_value).unwrap();
let machine_key = hsm let machine_key = hsm

View file

@ -13,10 +13,15 @@ SupplementaryGroups=tss
UMask=0027 UMask=0027
CacheDirectory=kanidm-unixd CacheDirectory=kanidm-unixd
RuntimeDirectory=kanidm-unixd RuntimeDirectory=kanidm-unixd
StateDirectory=kanidm-unixd
Type=simple Type=simple
ExecStart=/usr/sbin/kanidm_unixd ExecStart=/usr/sbin/kanidm_unixd
## If you wish to setup an external HSM pin you should set:
# LoadCredential=hsmpin:/etc/kanidm/kanidm-unixd-hsm-pin
# Environment=KANIDM_HSM_PIN_PATH=%d/hsmpin
# Implied by dynamic user. # Implied by dynamic user.
# ProtectHome= # ProtectHome=
# ProtectSystem=strict # ProtectSystem=strict

View file

@ -57,6 +57,7 @@ base64urlsafedata = { workspace = true }
bytes = { workspace = true } bytes = { workspace = true }
clap = { workspace = true, features = ["derive", "env"] } clap = { workspace = true, features = ["derive", "env"] }
csv = { workspace = true } csv = { workspace = true }
compact_jwt = { workspace = true, features = ["hsm-crypto"] }
futures = { workspace = true } futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
libc = { workspace = true } libc = { workspace = true }

View file

@ -15,4 +15,4 @@ pub const DEFAULT_UID_ATTR_MAP: UidAttr = UidAttr::Spn;
pub const DEFAULT_GID_ATTR_MAP: UidAttr = UidAttr::Spn; pub const DEFAULT_GID_ATTR_MAP: UidAttr = UidAttr::Spn;
pub const DEFAULT_SELINUX: bool = true; pub const DEFAULT_SELINUX: bool = true;
pub const DEFAULT_TPM_TCTI_NAME: &str = "device:/dev/tpmrm0"; pub const DEFAULT_TPM_TCTI_NAME: &str = "device:/dev/tpmrm0";
pub const DEFAULT_HSM_PIN_PATH: &str = "/etc/kanidm/unixd-hsm-pin"; pub const DEFAULT_HSM_PIN_PATH: &str = "/var/lib/kanidm-unixd/hsm-pin";

View file

@ -439,7 +439,6 @@ async fn process_etc_passwd_group(
async fn read_hsm_pin(hsm_pin_path: &str) -> Result<Vec<u8>, Box<dyn Error>> { async fn read_hsm_pin(hsm_pin_path: &str) -> Result<Vec<u8>, Box<dyn Error>> {
if !PathBuf::from_str(hsm_pin_path)?.exists() { if !PathBuf::from_str(hsm_pin_path)?.exists() {
// TODO generate the file by default
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::NotFound, std::io::ErrorKind::NotFound,
format!("HSM PIN file '{}' not found", hsm_pin_path), format!("HSM PIN file '{}' not found", hsm_pin_path),
@ -453,6 +452,21 @@ async fn read_hsm_pin(hsm_pin_path: &str) -> Result<Vec<u8>, Box<dyn Error>> {
Ok(contents) Ok(contents)
} }
async fn write_hsm_pin(hsm_pin_path: &str) -> Result<(), Box<dyn Error>> {
if !PathBuf::from_str(hsm_pin_path)?.exists() {
let new_pin = AuthValue::generate().map_err(|hsm_err| {
error!(?hsm_err, "Unable to generate new pin");
std::io::Error::new(std::io::ErrorKind::Other, "Unable to generate new pin")
})?;
std::fs::write(hsm_pin_path, new_pin)?;
info!("Generated new HSM pin");
}
Ok(())
}
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> ExitCode { async fn main() -> ExitCode {
let cuid = get_current_uid(); let cuid = get_current_uid();
@ -652,8 +666,8 @@ async fn main() -> ExitCode {
.to_str() .to_str()
.unwrap_or("<db_parent_path invalid>") .unwrap_or("<db_parent_path invalid>")
); );
let diag = kanidm_lib_file_permissions::diagnose_path(db_path.as_ref()); let diag = kanidm_lib_file_permissions::diagnose_path(db_path.as_ref());
info!(%diag); info!(%diag);
return ExitCode::FAILURE return ExitCode::FAILURE
} }
@ -702,8 +716,8 @@ async fn main() -> ExitCode {
"Refusing to run - DB path {} already exists and is not a file.", "Refusing to run - DB path {} already exists and is not a file.",
db_path.to_str().unwrap_or("<db_path invalid>") db_path.to_str().unwrap_or("<db_path invalid>")
); );
let diag = kanidm_lib_file_permissions::diagnose_path(db_path.as_ref()); let diag = kanidm_lib_file_permissions::diagnose_path(db_path.as_ref());
info!(%diag); info!(%diag);
return ExitCode::FAILURE return ExitCode::FAILURE
}; };
@ -715,8 +729,8 @@ async fn main() -> ExitCode {
db_path.to_str().unwrap_or("<db_path invalid>"), db_path.to_str().unwrap_or("<db_path invalid>"),
e e
); );
let diag = kanidm_lib_file_permissions::diagnose_path(db_path.as_ref()); let diag = kanidm_lib_file_permissions::diagnose_path(db_path.as_ref());
info!(%diag); info!(%diag);
return ExitCode::FAILURE return ExitCode::FAILURE
} }
}; };
@ -744,6 +758,22 @@ async fn main() -> ExitCode {
} }
}; };
// perform any db migrations.
let mut dbtxn = db.write().await;
if dbtxn.migrate()
.and_then(|_| {
dbtxn.commit()
}).is_err() {
error!("Failed to migrate database");
return ExitCode::FAILURE
}
// Check for and create the hsm pin if required.
if let Err(err) = write_hsm_pin(cfg.hsm_pin_path.as_str()).await {
error!(?err, "Failed to create HSM PIN into {}", cfg.hsm_pin_path.as_str());
return ExitCode::FAILURE
};
// read the hsm pin // read the hsm pin
let hsm_pin = match read_hsm_pin(cfg.hsm_pin_path.as_str()).await { let hsm_pin = match read_hsm_pin(cfg.hsm_pin_path.as_str()).await {
Ok(hp) => hp, Ok(hp) => hp,
@ -802,7 +832,8 @@ async fn main() -> ExitCode {
let machine_key = match hsm.machine_key_load(&auth_value, &loadable_machine_key) { let machine_key = match hsm.machine_key_load(&auth_value, &loadable_machine_key) {
Ok(mk) => mk, Ok(mk) => mk,
Err(err) => { Err(err) => {
error!(?err, "Unable to load machine key"); error!(?err, "Unable to load machine root key - This can occur if you have changed your HSM pin");
error!("To proceed you must remove the content of the cache db ({}) to reset all keys", cfg.db_path.as_str());
return ExitCode::FAILURE return ExitCode::FAILURE
} }
}; };

View file

@ -1238,7 +1238,7 @@ mod tests {
#[cfg(not(feature = "tpm"))] #[cfg(not(feature = "tpm"))]
let mut hsm: Box<dyn Tpm> = Box::new(SoftTpm::new()); let mut hsm: Box<dyn Tpm> = Box::new(SoftTpm::new());
let auth_value = AuthValue::new_random().unwrap(); let auth_value = AuthValue::ephemeral().unwrap();
let loadable_machine_key = hsm.machine_key_create(&auth_value).unwrap(); let loadable_machine_key = hsm.machine_key_create(&auth_value).unwrap();
let machine_key = hsm let machine_key = hsm

View file

@ -104,18 +104,34 @@ pub trait IdProvider {
Ok(()) Ok(())
} }
async fn provider_authenticate(&self) -> Result<(), IdpError>; /// This is similar to a "domain join" process. What do we actually need to pass here
/// for this to work for kanidm or himmelblau? Should we make it take a generic?
/*
async fn configure_machine_identity<D: KeyStoreTxn + Send>(
&self,
_keystore: &mut D,
_tpm: &mut (dyn tpm::Tpm + Send),
_machine_key: &tpm::MachineKey,
) -> Result<(), IdpError> {
Ok(())
}
*/
async fn provider_authenticate(&self, _tpm: &mut (dyn tpm::Tpm + Send))
-> Result<(), IdpError>;
async fn unix_user_get( async fn unix_user_get(
&self, &self,
_id: &Id, _id: &Id,
_token: Option<&UserToken>, _token: Option<&UserToken>,
_tpm: &mut (dyn tpm::Tpm + Send),
) -> Result<UserToken, IdpError>; ) -> Result<UserToken, IdpError>;
async fn unix_user_online_auth_init( async fn unix_user_online_auth_init(
&self, &self,
_account_id: &str, _account_id: &str,
_token: Option<&UserToken>, _token: Option<&UserToken>,
_tpm: &mut (dyn tpm::Tpm + Send),
) -> Result<(AuthRequest, AuthCredHandler), IdpError>; ) -> Result<(AuthRequest, AuthCredHandler), IdpError>;
async fn unix_user_online_auth_step( async fn unix_user_online_auth_step(
@ -123,6 +139,7 @@ pub trait IdProvider {
_account_id: &str, _account_id: &str,
_cred_handler: &mut AuthCredHandler, _cred_handler: &mut AuthCredHandler,
_pam_next_req: PamAuthRequest, _pam_next_req: PamAuthRequest,
_tpm: &mut (dyn tpm::Tpm + Send),
) -> Result<(AuthResult, AuthCacheAction), IdpError>; ) -> Result<(AuthResult, AuthCacheAction), IdpError>;
async fn unix_user_offline_auth_init( async fn unix_user_offline_auth_init(
@ -155,5 +172,9 @@ pub trait IdProvider {
) -> Result<AuthResult, IdpError>; ) -> Result<AuthResult, IdpError>;
*/ */
async fn unix_group_get(&self, id: &Id) -> Result<GroupToken, IdpError>; async fn unix_group_get(
&self,
id: &Id,
_tpm: &mut (dyn tpm::Tpm + Send),
) -> Result<GroupToken, IdpError>;
} }

View file

@ -115,7 +115,10 @@ impl IdProvider for KanidmProvider {
} }
// Needs .read on all types except re-auth. // Needs .read on all types except re-auth.
async fn provider_authenticate(&self) -> Result<(), IdpError> { async fn provider_authenticate(
&self,
_tpm: &mut (dyn tpm::Tpm + Send),
) -> Result<(), IdpError> {
match self.client.write().await.auth_anonymous().await { match self.client.write().await.auth_anonymous().await {
Ok(_uat) => Ok(()), Ok(_uat) => Ok(()),
Err(err) => { Err(err) => {
@ -129,6 +132,7 @@ impl IdProvider for KanidmProvider {
&self, &self,
id: &Id, id: &Id,
_token: Option<&UserToken>, _token: Option<&UserToken>,
_tpm: &mut (dyn tpm::Tpm + Send),
) -> Result<UserToken, IdpError> { ) -> Result<UserToken, IdpError> {
match self match self
.client .client
@ -191,6 +195,7 @@ impl IdProvider for KanidmProvider {
&self, &self,
_account_id: &str, _account_id: &str,
_token: Option<&UserToken>, _token: Option<&UserToken>,
_tpm: &mut (dyn tpm::Tpm + Send),
) -> Result<(AuthRequest, AuthCredHandler), IdpError> { ) -> Result<(AuthRequest, AuthCredHandler), IdpError> {
// Not sure that I need to do much here? // Not sure that I need to do much here?
Ok((AuthRequest::Password, AuthCredHandler::Password)) Ok((AuthRequest::Password, AuthCredHandler::Password))
@ -201,6 +206,7 @@ impl IdProvider for KanidmProvider {
account_id: &str, account_id: &str,
cred_handler: &mut AuthCredHandler, cred_handler: &mut AuthCredHandler,
pam_next_req: PamAuthRequest, pam_next_req: PamAuthRequest,
_tpm: &mut (dyn tpm::Tpm + Send),
) -> Result<(AuthResult, AuthCacheAction), IdpError> { ) -> Result<(AuthResult, AuthCacheAction), IdpError> {
match (cred_handler, pam_next_req) { match (cred_handler, pam_next_req) {
(AuthCredHandler::Password, PamAuthRequest::Password { cred }) => { (AuthCredHandler::Password, PamAuthRequest::Password { cred }) => {
@ -303,7 +309,11 @@ impl IdProvider for KanidmProvider {
} }
*/ */
async fn unix_group_get(&self, id: &Id) -> Result<GroupToken, IdpError> { async fn unix_group_get(
&self,
id: &Id,
_tpm: &mut (dyn tpm::Tpm + Send),
) -> Result<GroupToken, IdpError> {
match self match self
.client .client
.read() .read()

View file

@ -110,11 +110,6 @@ where
let hsm = Mutex::new(hsm); let hsm = Mutex::new(hsm);
let mut hsm_lock = hsm.lock().await; let mut hsm_lock = hsm.lock().await;
// setup and do a migrate.
let mut dbtxn = db.write().await;
dbtxn.migrate().map_err(|_| ())?;
dbtxn.commit().map_err(|_| ())?;
// Setup our internal keys // Setup our internal keys
let mut dbtxn = db.write().await; let mut dbtxn = db.write().await;
@ -477,7 +472,16 @@ where
account_id: &Id, account_id: &Id,
token: Option<UserToken>, token: Option<UserToken>,
) -> Result<Option<UserToken>, ()> { ) -> Result<Option<UserToken>, ()> {
match self.client.unix_user_get(account_id, token.as_ref()).await { let mut hsm_lock = self.hsm.lock().await;
let user_get_result = self
.client
.unix_user_get(account_id, token.as_ref(), &mut **hsm_lock.deref_mut())
.await;
drop(hsm_lock);
match user_get_result {
Ok(mut n_tok) => { Ok(mut n_tok) => {
if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { if self.check_nxset(&n_tok.name, n_tok.gidnumber).await {
// Refuse to release the token, it's in the denied set. // Refuse to release the token, it's in the denied set.
@ -527,7 +531,16 @@ where
grp_id: &Id, grp_id: &Id,
token: Option<GroupToken>, token: Option<GroupToken>,
) -> Result<Option<GroupToken>, ()> { ) -> Result<Option<GroupToken>, ()> {
match self.client.unix_group_get(grp_id).await { let mut hsm_lock = self.hsm.lock().await;
let group_get_result = self
.client
.unix_group_get(grp_id, &mut **hsm_lock.deref_mut())
.await;
drop(hsm_lock);
match group_get_result {
Ok(n_tok) => { Ok(n_tok) => {
if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { if self.check_nxset(&n_tok.name, n_tok.gidnumber).await {
// Refuse to release the token, it's in the denied set. // Refuse to release the token, it's in the denied set.
@ -863,8 +876,9 @@ where
}; };
let maybe_err = if online_at_init { let maybe_err = if online_at_init {
let mut hsm_lock = self.hsm.lock().await;
self.client self.client
.unix_user_online_auth_init(account_id, token.as_ref()) .unix_user_online_auth_init(account_id, token.as_ref(), &mut **hsm_lock.deref_mut())
.await .await
} else { } else {
// Can the auth proceed offline? // Can the auth proceed offline?
@ -919,11 +933,20 @@ where
}, },
CacheState::Online, CacheState::Online,
) => { ) => {
let mut hsm_lock = self.hsm.lock().await;
let maybe_cache_action = self let maybe_cache_action = self
.client .client
.unix_user_online_auth_step(account_id, cred_handler, pam_next_req) .unix_user_online_auth_step(
account_id,
cred_handler,
pam_next_req,
&mut **hsm_lock.deref_mut(),
)
.await; .await;
drop(hsm_lock);
match maybe_cache_action { match maybe_cache_action {
Ok((res, AuthCacheAction::None)) => Ok(res), Ok((res, AuthCacheAction::None)) => Ok(res),
Ok(( Ok((
@ -1120,7 +1143,16 @@ where
false false
} }
CacheState::OfflineNextCheck(_time) => { CacheState::OfflineNextCheck(_time) => {
match self.client.provider_authenticate().await { let mut hsm_lock = self.hsm.lock().await;
let prov_auth_result = self
.client
.provider_authenticate(&mut **hsm_lock.deref_mut())
.await;
drop(hsm_lock);
match prov_auth_result {
Ok(()) => { Ok(()) => {
debug!("OfflineNextCheck -> authenticated"); debug!("OfflineNextCheck -> authenticated");
self.set_cachestate(CacheState::Online).await; self.set_cachestate(CacheState::Online).await;

View file

@ -10,7 +10,7 @@ use kanidm_unix_common::constants::{
DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX, DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX,
DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP, DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP,
}; };
use kanidm_unix_common::db::Db; use kanidm_unix_common::db::{Cache, CacheTxn, Db};
use kanidm_unix_common::idprovider::interface::Id; use kanidm_unix_common::idprovider::interface::Id;
use kanidm_unix_common::idprovider::kanidm::KanidmProvider; use kanidm_unix_common::idprovider::kanidm::KanidmProvider;
use kanidm_unix_common::resolver::Resolver; use kanidm_unix_common::resolver::Resolver;
@ -103,9 +103,15 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver<KanidmProvider>, KanidmClient)
) )
.expect("Failed to setup DB"); .expect("Failed to setup DB");
let mut dbtxn = db.write().await;
dbtxn
.migrate()
.and_then(|_| dbtxn.commit())
.expect("Unable to migrate cache db");
let mut hsm: Box<dyn Tpm + Send> = Box::new(SoftTpm::new()); let mut hsm: Box<dyn Tpm + Send> = Box::new(SoftTpm::new());
let auth_value = AuthValue::new_random().unwrap(); let auth_value = AuthValue::ephemeral().unwrap();
let loadable_machine_key = hsm.machine_key_create(&auth_value).unwrap(); let loadable_machine_key = hsm.machine_key_create(&auth_value).unwrap();
let machine_key = hsm let machine_key = hsm