From fedc21ddcaee4bef374002fae68031751830be67 Mon Sep 17 00:00:00 2001 From: James Hodgkinson Date: Wed, 6 Jul 2022 10:53:43 +1000 Subject: [PATCH] Windows build support (#903) `kanidmd` builds and runs in Windows now. Currently skipping file permissions checks on startup, but it's tested OK on a Windows 10 box. --- .github/workflows/windows_build.yml | 4 +- Cargo.lock | 13 +++ DEVELOPER_README.md | 26 ++++- insecure_generate_tls.ps1 | 106 +++++++++++++++++++ kanidm_rlm_python/run_radius_container.sh | 2 +- kanidm_unix_int/Cargo.toml | 2 - kanidmd/daemon/Cargo.toml | 5 +- kanidmd/daemon/src/main.rs | 118 +++++++++++++++------- kanidmd/idm/Cargo.toml | 18 +++- kanidmd/idm/src/utils.rs | 6 +- kanidmd/score/src/lib.rs | 6 +- 11 files changed, 259 insertions(+), 47 deletions(-) create mode 100644 insecure_generate_tls.ps1 diff --git a/.github/workflows/windows_build.yml b/.github/workflows/windows_build.yml index ae2597cce..989e97e0f 100644 --- a/.github/workflows/windows_build.yml +++ b/.github/workflows/windows_build.yml @@ -28,7 +28,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --release -p kanidm_client -p kanidm_tools -p orca + args: --release -p kanidm_client -p kanidm_tools -p orca -p daemon windows_test_kanidm: runs-on: windows-latest steps: @@ -47,4 +47,4 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: -p kanidm_client -p kanidm_tools -p orca + args: -p kanidm_client -p kanidm_tools -p orca -p daemon -p score diff --git a/Cargo.lock b/Cargo.lock index 5ddad0623..628782864 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1046,6 +1046,7 @@ dependencies = [ "tokio", "toml", "users", + "whoami", ] [[package]] @@ -2069,6 +2070,7 @@ dependencies = [ "validator", "webauthn-authenticator-rs", "webauthn-rs", + "whoami", "zxcvbn", ] @@ -2252,6 +2254,7 @@ version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" dependencies = [ + "cc", "pkg-config", "vcpkg", ] @@ -4471,6 +4474,16 @@ dependencies = [ "cc", ] +[[package]] +name = "whoami" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/DEVELOPER_README.md b/DEVELOPER_README.md index 18064f1be..f9b0f2ff3 100644 --- a/DEVELOPER_README.md +++ b/DEVELOPER_README.md @@ -61,6 +61,25 @@ sudo apt-get install libsqlite3-dev libudev-dev libssl-dev pkg-config libpam0g-d Tested with Ubuntu 20.04 and 22.04. +#### Windows + +You need [rustup](https://rustup.rs/) to install a Rust toolchain. + +An easy way to grab the dependencies is to install [vcpkg](https://vcpkg.io/en/getting-started.html). + +This is how it works in the automated build: + +1. Enable use of installed packages for the user system-wide: +```shell +vcpkg integrate install +``` +2. Install the openssl dependency, which compiles it from source. This downloads all sorts of dependencies, including perl for the build. +```shell +vcpkg install openssl:x64-windows-static-md +``` + +There's a powershell script in the root directory of the repository which, in concert with `openssl` will generate a config file and certs for testing. + ### Get Involved To get started, you'll need to fork or branch, and we'll merge based on pull @@ -140,8 +159,11 @@ Once you have the source code, you need encryption certificates to use with the because without certificates, authentication will fail. We recommend using [Let's Encrypt](https://letsencrypt.org), but if this is not -possible, please use our insecure certificate tool (`insecure_generate_tls.sh`). The -insecure certificate tool creates `/tmp/kanidm` and puts some self-signed certificates there. +possible, please use our insecure certificate tool (`insecure_generate_tls.sh`). + +__NOTE:__ Windows developers can use `insecure_generate_tls.ps1`, which puts everything (including a templated confi gfile) in `$TEMP\kanidm`. Please adjust paths below to suit. + +The insecure certificate tool creates `/tmp/kanidm` and puts some self-signed certificates there. You can now build and run the server with the commands below. It will use a database in `/tmp/kanidm.db`. diff --git a/insecure_generate_tls.ps1 b/insecure_generate_tls.ps1 new file mode 100644 index 000000000..2cc4ef0a7 --- /dev/null +++ b/insecure_generate_tls.ps1 @@ -0,0 +1,106 @@ + +$ErrorActionPreference = "Stop" + +$KANI_TMP="$Env:TEMP\kanidm\" + +$ALTNAME_FILE="${KANI_TMP}altnames.cnf" +$CACERT="${KANI_TMP}ca.pem" +$CAKEY="${KANI_TMP}cakey.pem" + +$KEYFILE="${KANI_TMP}key.pem" +$CERTFILE="${KANI_TMP}cert.pem" +$CSRFILE="${KANI_TMP}cert.csr" +$CHAINFILE="${KANI_TMP}chain.pem" +# $DHFILE="${KANI_TMP}dh.pem" +$CONFIG_FILE="${KANI_TMP}server.toml" + + +if (Test-Path -Path "$KANI_TMP" ) { + Write-Output "Output dir exists at $KANI_TMP" +} else { + Write-Warning "Output dir missing at $KANI_TMP" + $result = New-Item -Path "$KANI_TMP" -ItemType Directory +} + + +if ( $(Test-Path -Path "examples\insecure_server.toml") -eq $false ) { + Write-Error "You need to run this from the base dir of the repo!" + exit 1 +} +# Building the config file +$CONFIG = Get-Content "examples\insecure_server.toml" +$CONFIG = $CONFIG -replace "/tmp/kanidm/", "$KANI_TMP" +$CONFIG = $CONFIG -replace "\\", "/" + +$CONFIG | Set-Content "${CONFIG_FILE}" -Force + +$ALTNAME_FILE_CONTENTS = @' +[req] +nsComment = "Certificate" +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[ req_distinguished_name ] + +countryName = Country Name (2 letter code) +countryName_default = AU +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Queensland + +localityName = Locality Name (eg, city) +localityName_default = Brisbane + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = INSECURE EXAMPLE + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = kanidm + +commonName = Common Name (eg, your name or your servers hostname) +commonName_max = 64 +commonName_default = localhost + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = localhost +IP.1 = 127.0.0.1 +'@ + +Write-Output "Creating cert template" +$result = New-Item -Path "$ALTNAME_FILE" -ItemType File -Value "$ALTNAME_FILE_CONTENTS" -Force + +write-debug $result + +Write-Output "Generate the CA" +openssl req -x509 -new -newkey rsa:4096 -sha256 -keyout "${CAKEY}" -out "${CACERT}" -days 31 -subj "/C=AU/ST=Queensland/L=Brisbane/O=INSECURE/CN=insecure.ca.localhost" -nodes +if ( $LastExitCode -ne 0 ){ + exit 1 +} + +Write-Output "Generating the private key" +openssl genrsa -out "${KEYFILE}" 4096 +if ( $LastExitCode -ne 0 ){ + exit 1 +} + +Write-Output "Generating the certficate signing request" +openssl req -sha256 -config "${ALTNAME_FILE}" -days 31 -new -extensions v3_req -key "${KEYFILE}" -out "${CSRFILE}" +if ( $LastExitCode -ne 0 ){ + exit 1 +} +Write-Output "Signing the certificate" +openssl x509 -req -days 31 -extfile "${ALTNAME_FILE}" -CA "${CACERT}" -CAkey "${CAKEY}" -CAcreateserial -in "${CSRFILE}" -out "${CERTFILE}" -extensions v3_req -sha256 + +Write-Output "Creating the certificate chain" +Get-Content "${CERTFILE}" ,"${CACERT}" | Set-Content "${CHAINFILE}" -Force + +Write-Output "Certificate chain is at: ${CHAINFILE}" +Write-Output "Private key is at: ${KEYFILE}" +Write-Output "The configuration file is at: ${CONFIG_FILE}" diff --git a/kanidm_rlm_python/run_radius_container.sh b/kanidm_rlm_python/run_radius_container.sh index 7aed81605..b24f50035 100755 --- a/kanidm_rlm_python/run_radius_container.sh +++ b/kanidm_rlm_python/run_radius_container.sh @@ -11,7 +11,7 @@ fi echo "Using config file: ${CONFIG_FILE}" if [ ! -d "/tmp/kanidm/" ]; then - echo "Can't find /tmp/kanidm - you might need to run insecure_generate_certs.sh" + echo "Can't find /tmp/kanidm - you might need to run insecure_generate_tls.sh" fi echo "Starting the dev container..." diff --git a/kanidm_unix_int/Cargo.toml b/kanidm_unix_int/Cargo.toml index 2d268e768..cfc164472 100644 --- a/kanidm_unix_int/Cargo.toml +++ b/kanidm_unix_int/Cargo.toml @@ -71,8 +71,6 @@ r2d2_sqlite = "^0.20.0" reqwest = "^0.11.11" users = "^0.11.0" -#async-std = { version = "^1.11.0", features = ["tokio1"] } - lru = "^0.7.7" diff --git a/kanidmd/daemon/Cargo.toml b/kanidmd/daemon/Cargo.toml index 42c3b661f..e7cfad6b6 100644 --- a/kanidmd/daemon/Cargo.toml +++ b/kanidmd/daemon/Cargo.toml @@ -21,12 +21,15 @@ kanidm = { path = "../idm" } kanidm_proto = { path = "../../kanidm_proto" } score = { path = "../score" } clap = { version = "^3.2", features = ["derive", "env"] } -users = "^0.11.0" serde = { version = "^1.0.138", features = ["derive"] } tokio = { version = "^1.19.1", features = ["rt-multi-thread", "macros", "signal"] } toml = "0.5.9" +[target.'cfg(target_family = "windows")'.dependencies] +whoami = "^1.2.1" + [target.'cfg(not(target_family = "windows"))'.dependencies] +users = "^0.11.0" tikv-jemallocator = "0.5" [build-dependencies] diff --git a/kanidmd/daemon/src/main.rs b/kanidmd/daemon/src/main.rs index 167c1d42c..192e71c0a 100644 --- a/kanidmd/daemon/src/main.rs +++ b/kanidmd/daemon/src/main.rs @@ -14,7 +14,10 @@ #[global_allocator] static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +#[cfg(not(target_family = "windows"))] // not needed for windows builds use users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid}; +#[cfg(target_family = "windows")] // for windows builds +use whoami; use serde::Deserialize; use std::fs::{metadata, File, Metadata}; @@ -30,6 +33,7 @@ use std::str::FromStr; use kanidm::audit::LogLevel; use kanidm::config::{Configuration, OnlineBackup, ServerRole}; use kanidm::tracing_tree; +#[cfg(not(target_family = "windows"))] use kanidm::utils::file_permissions_readonly; use score::{ backup_server_core, create_server_core, dbscan_get_id2entry_core, dbscan_list_id2entry_core, @@ -120,11 +124,9 @@ fn read_file_metadata(path: &PathBuf) -> Metadata { } } -#[tokio::main] -async fn main() { - tracing_tree::main_init(); - - // Get info about who we are. +/// Gets the user details if we're running in unix-land +#[cfg(not(target_family = "windows"))] +fn get_user_details_unix() -> (u32, u32) { let cuid = get_current_uid(); let ceuid = get_effective_uid(); let cgid = get_current_gid(); @@ -141,28 +143,58 @@ async fn main() { eprintln!("ERROR: Refusing to run - uid and euid OR gid and egid must be consistent."); std::process::exit(1); } + (cuid, ceuid) +} + +/// Get information on the windows username +#[cfg(target_family = "windows")] +fn get_user_details_windows() { + eprintln!( + "Running on windows, current username is: {:?}", + whoami::username() + ); +} + +#[tokio::main] +async fn main() { + tracing_tree::main_init(); + + // Get information on the windows username + #[cfg(target_family = "windows")] + get_user_details_windows(); + + // Get info about who we are. + #[cfg(target_family = "unix")] + let (cuid, ceuid) = get_user_details_unix(); // Read cli args, determine if we should backup/restore let opt = KanidmdParser::parse(); let mut config = Configuration::new(); - // Check the permissions are sane. - let cfg_meta = read_file_metadata(&(opt.commands.commonopt().config_path)); - if !file_permissions_readonly(&cfg_meta) { - eprintln!("WARNING: permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", - opt.commands.commonopt().config_path.to_str().unwrap_or("invalid file path")); - } + // Check the permissions are OK. + #[cfg(target_family = "unix")] + { + let cfg_meta = read_file_metadata(&(opt.commands.commonopt().config_path)); - if cfg_meta.mode() & 0o007 != 0 { - eprintln!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", - opt.commands.commonopt().config_path.to_str().unwrap_or("invalid file path") - ); - } + #[cfg(target_family = "unix")] + if !file_permissions_readonly(&cfg_meta) { + eprintln!("WARNING: permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", + opt.commands.commonopt().config_path.to_str().unwrap_or("invalid file path")); + } - if cfg_meta.uid() == cuid || cfg_meta.uid() == ceuid { - eprintln!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...", - opt.commands.commonopt().config_path.to_str().unwrap_or("invalid file path") - ); + #[cfg(target_family = "unix")] + if cfg_meta.mode() & 0o007 != 0 { + eprintln!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", + opt.commands.commonopt().config_path.to_str().unwrap_or("invalid file path") + ); + } + + #[cfg(target_family = "unix")] + if cfg_meta.uid() == cuid || cfg_meta.uid() == ceuid { + eprintln!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...", + opt.commands.commonopt().config_path.to_str().unwrap_or("invalid file path") + ); + } } // Read our config @@ -205,12 +237,16 @@ async fn main() { ); std::process::exit(1); } - if !file_permissions_readonly(&i_meta) { - eprintln!("WARNING: DB folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", db_par_path_buf.to_str().unwrap_or("invalid file path")); - } - if i_meta.mode() & 0o007 != 0 { - eprintln!("WARNING: DB folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", db_par_path_buf.to_str().unwrap_or("invalid file path")); + // TODO: windows support for DB folder permissions checks + #[cfg(target_family = "unix")] + { + if !file_permissions_readonly(&i_meta) { + eprintln!("WARNING: DB folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", db_par_path_buf.to_str().unwrap_or("invalid file path")); + } + if i_meta.mode() & 0o007 != 0 { + eprintln!("WARNING: DB folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", db_par_path_buf.to_str().unwrap_or("invalid file path")); + } } } @@ -251,21 +287,35 @@ async fn main() { if let Some(i_str) = &(sconfig.tls_chain) { let i_path = PathBuf::from(i_str.as_str()); - let i_meta = read_file_metadata(&i_path); - if !file_permissions_readonly(&i_meta) { - eprintln!("WARNING: permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", i_str); + // TODO: windows support for DB folder permissions checks + #[cfg(not(target_family = "unix"))] + eprintln!("WARNING: permissions checks on windows aren't implemented, cannot check TLS Key at {:?}", i_path); + + #[cfg(target_family = "unix")] + { + let i_meta = read_file_metadata(&i_path); + if !file_permissions_readonly(&i_meta) { + eprintln!("WARNING: permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", i_str); + } } } if let Some(i_str) = &(sconfig.tls_key) { let i_path = PathBuf::from(i_str.as_str()); - let i_meta = read_file_metadata(&i_path); - if !file_permissions_readonly(&i_meta) { - eprintln!("WARNING: permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", i_str); - } + // TODO: windows support for DB folder permissions checks + #[cfg(not(target_family = "unix"))] + eprintln!("WARNING: permissions checks on windows aren't implemented, cannot check TLS Key at {:?}", i_path); - if i_meta.mode() & 0o007 != 0 { - eprintln!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", i_str); + // TODO: windows support for DB folder permissions checks + #[cfg(target_family = "unix")] + { + let i_meta = read_file_metadata(&i_path); + if !file_permissions_readonly(&i_meta) { + eprintln!("WARNING: permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", i_str); + } + if i_meta.mode() & 0o007 != 0 { + eprintln!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", i_str); + } } } diff --git a/kanidmd/idm/Cargo.toml b/kanidmd/idm/Cargo.toml index e7ff927ab..0c8f90198 100644 --- a/kanidmd/idm/Cargo.toml +++ b/kanidmd/idm/Cargo.toml @@ -40,7 +40,6 @@ r2d2 = "^0.8.9" r2d2_sqlite = "^0.20.0" rand = "^0.8.5" regex = "^1.5.6" -rusqlite = "^0.27.0" saffron = "^0.1.0" serde = { version = "^1.0.138", features = ["derive"] } serde_cbor = "^0.11.2" @@ -58,12 +57,27 @@ tracing = { version = "^0.1.35", features = ["attributes"] } tracing-serde = "^0.1.3" tracing-subscriber = { version = "^0.3.14", features = ["env-filter"] } url = { version = "^2.2.2", features = ["serde"] } -users = "^0.11.0" uuid = { version = "^1.1.2", features = ["serde", "v4" ] } validator = { version = "^0.15.0", features = ["phone"] } webauthn-rs = "^0.3.2" zxcvbn = "^2.2.1" +# because windows really can't build without the bundled one +[target.'cfg(target_family = "windows")'.dependencies.rusqlite] +version = "^0.27.0" +features = ["bundled"] + +[target.'cfg(not(target_family = "windows"))'.dependencies.rusqlite] +version = "^0.27.0" + +[target.'cfg(target_family = "windows")'.dependencies] +whoami = "^1.2.1" + + +[target.'cfg(not(target_family = "windows"))'.dependencies] +users = "^0.11.0" + + [features] # default = [ "libsqlite3-sys/bundled", "openssl/vendored" ] diff --git a/kanidmd/idm/src/utils.rs b/kanidmd/idm/src/utils.rs index 3e1935a1c..70922a4ff 100644 --- a/kanidmd/idm/src/utils.rs +++ b/kanidmd/idm/src/utils.rs @@ -10,14 +10,16 @@ use uuid::{Builder, Uuid}; use rand::distributions::Distribution; use rand::{thread_rng, Rng}; +#[cfg(not(target_family = "windows"))] use std::fs::Metadata; #[cfg(target_os = "linux")] use std::os::linux::fs::MetadataExt; #[cfg(target_os = "macos")] use std::os::macos::fs::MetadataExt; -#[cfg(target_os = "windows")] -use std::os::windows::fs::MetadataExt; +// #[cfg(target_os = "windows")] +// use std::os::windows::fs::MetadataExt; +#[cfg(target_family = "unix")] use users::{get_current_gid, get_current_uid}; #[derive(Debug)] diff --git a/kanidmd/score/src/lib.rs b/kanidmd/score/src/lib.rs index d05bde315..9ae9d049e 100644 --- a/kanidmd/score/src/lib.rs +++ b/kanidmd/score/src/lib.rs @@ -32,6 +32,7 @@ mod ldaps; use async_std::task; use compact_jwt::JwsSigner; use kanidm::prelude::*; +#[cfg(not(target_family = "windows"))] use libc::umask; use kanidm::actors::v1_read::QueryServerReadV1; @@ -551,7 +552,10 @@ pub async fn create_server_core(config: Configuration, config_test: bool) -> Res config ); // Setup umask, so that every we touch or create is secure. - let _ = unsafe { umask(0o0027) }; + #[cfg(not(target_family = "windows"))] + unsafe { + umask(0o0027) + }; // Similar, create a stats task which aggregates statistics from the // server as they come in.