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.
This commit is contained in:
James Hodgkinson 2022-07-06 10:53:43 +10:00 committed by GitHub
parent 4830479bd5
commit fedc21ddca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 259 additions and 47 deletions

View file

@ -28,7 +28,7 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build 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: windows_test_kanidm:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
@ -47,4 +47,4 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: -p kanidm_client -p kanidm_tools -p orca args: -p kanidm_client -p kanidm_tools -p orca -p daemon -p score

13
Cargo.lock generated
View file

@ -1046,6 +1046,7 @@ dependencies = [
"tokio", "tokio",
"toml", "toml",
"users", "users",
"whoami",
] ]
[[package]] [[package]]
@ -2069,6 +2070,7 @@ dependencies = [
"validator", "validator",
"webauthn-authenticator-rs", "webauthn-authenticator-rs",
"webauthn-rs", "webauthn-rs",
"whoami",
"zxcvbn", "zxcvbn",
] ]
@ -2252,6 +2254,7 @@ version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"
dependencies = [ dependencies = [
"cc",
"pkg-config", "pkg-config",
"vcpkg", "vcpkg",
] ]
@ -4471,6 +4474,16 @@ dependencies = [
"cc", "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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View file

@ -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. 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 ### Get Involved
To get started, you'll need to fork or branch, and we'll merge based on pull 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. because without certificates, authentication will fail.
We recommend using [Let's Encrypt](https://letsencrypt.org), but if this is not 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 possible, please use our insecure certificate tool (`insecure_generate_tls.sh`).
insecure certificate tool creates `/tmp/kanidm` and puts some self-signed certificates there.
__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 You can now build and run the server with the commands below. It will use a database
in `/tmp/kanidm.db`. in `/tmp/kanidm.db`.

106
insecure_generate_tls.ps1 Normal file
View file

@ -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}"

View file

@ -11,7 +11,7 @@ fi
echo "Using config file: ${CONFIG_FILE}" echo "Using config file: ${CONFIG_FILE}"
if [ ! -d "/tmp/kanidm/" ]; then 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 fi
echo "Starting the dev container..." echo "Starting the dev container..."

View file

@ -71,8 +71,6 @@ r2d2_sqlite = "^0.20.0"
reqwest = "^0.11.11" reqwest = "^0.11.11"
users = "^0.11.0" users = "^0.11.0"
#async-std = { version = "^1.11.0", features = ["tokio1"] }
lru = "^0.7.7" lru = "^0.7.7"

View file

@ -21,12 +21,15 @@ kanidm = { path = "../idm" }
kanidm_proto = { path = "../../kanidm_proto" } kanidm_proto = { path = "../../kanidm_proto" }
score = { path = "../score" } score = { path = "../score" }
clap = { version = "^3.2", features = ["derive", "env"] } clap = { version = "^3.2", features = ["derive", "env"] }
users = "^0.11.0"
serde = { version = "^1.0.138", features = ["derive"] } serde = { version = "^1.0.138", features = ["derive"] }
tokio = { version = "^1.19.1", features = ["rt-multi-thread", "macros", "signal"] } tokio = { version = "^1.19.1", features = ["rt-multi-thread", "macros", "signal"] }
toml = "0.5.9" toml = "0.5.9"
[target.'cfg(target_family = "windows")'.dependencies]
whoami = "^1.2.1"
[target.'cfg(not(target_family = "windows"))'.dependencies] [target.'cfg(not(target_family = "windows"))'.dependencies]
users = "^0.11.0"
tikv-jemallocator = "0.5" tikv-jemallocator = "0.5"
[build-dependencies] [build-dependencies]

View file

@ -14,7 +14,10 @@
#[global_allocator] #[global_allocator]
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; 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}; 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 serde::Deserialize;
use std::fs::{metadata, File, Metadata}; use std::fs::{metadata, File, Metadata};
@ -30,6 +33,7 @@ use std::str::FromStr;
use kanidm::audit::LogLevel; use kanidm::audit::LogLevel;
use kanidm::config::{Configuration, OnlineBackup, ServerRole}; use kanidm::config::{Configuration, OnlineBackup, ServerRole};
use kanidm::tracing_tree; use kanidm::tracing_tree;
#[cfg(not(target_family = "windows"))]
use kanidm::utils::file_permissions_readonly; use kanidm::utils::file_permissions_readonly;
use score::{ use score::{
backup_server_core, create_server_core, dbscan_get_id2entry_core, dbscan_list_id2entry_core, 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] /// Gets the user details if we're running in unix-land
async fn main() { #[cfg(not(target_family = "windows"))]
tracing_tree::main_init(); fn get_user_details_unix() -> (u32, u32) {
// Get info about who we are.
let cuid = get_current_uid(); let cuid = get_current_uid();
let ceuid = get_effective_uid(); let ceuid = get_effective_uid();
let cgid = get_current_gid(); let cgid = get_current_gid();
@ -141,29 +143,59 @@ async fn main() {
eprintln!("ERROR: Refusing to run - uid and euid OR gid and egid must be consistent."); eprintln!("ERROR: Refusing to run - uid and euid OR gid and egid must be consistent.");
std::process::exit(1); 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 // Read cli args, determine if we should backup/restore
let opt = KanidmdParser::parse(); let opt = KanidmdParser::parse();
let mut config = Configuration::new(); let mut config = Configuration::new();
// Check the permissions are sane. // Check the permissions are OK.
#[cfg(target_family = "unix")]
{
let cfg_meta = read_file_metadata(&(opt.commands.commonopt().config_path)); let cfg_meta = read_file_metadata(&(opt.commands.commonopt().config_path));
#[cfg(target_family = "unix")]
if !file_permissions_readonly(&cfg_meta) { 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 ...", 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")); opt.commands.commonopt().config_path.to_str().unwrap_or("invalid file path"));
} }
#[cfg(target_family = "unix")]
if cfg_meta.mode() & 0o007 != 0 { if cfg_meta.mode() & 0o007 != 0 {
eprintln!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", 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") 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 { 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 ...", 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") opt.commands.commonopt().config_path.to_str().unwrap_or("invalid file path")
); );
} }
}
// Read our config // Read our config
let sconfig = match ServerConfig::new(&(opt.commands.commonopt().config_path)) { let sconfig = match ServerConfig::new(&(opt.commands.commonopt().config_path)) {
@ -205,14 +237,18 @@ async fn main() {
); );
std::process::exit(1); std::process::exit(1);
} }
// TODO: windows support for DB folder permissions checks
#[cfg(target_family = "unix")]
{
if !file_permissions_readonly(&i_meta) { 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")); 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 { 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")); 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"));
} }
} }
}
config.update_log_level(ll); config.update_log_level(ll);
config.update_db_path(&sconfig.db_path.as_str()); config.update_db_path(&sconfig.db_path.as_str());
@ -251,23 +287,37 @@ async fn main() {
if let Some(i_str) = &(sconfig.tls_chain) { if let Some(i_str) = &(sconfig.tls_chain) {
let i_path = PathBuf::from(i_str.as_str()); let i_path = PathBuf::from(i_str.as_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); let i_meta = read_file_metadata(&i_path);
if !file_permissions_readonly(&i_meta) { 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); 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) { if let Some(i_str) = &(sconfig.tls_key) {
let i_path = PathBuf::from(i_str.as_str()); let i_path = PathBuf::from(i_str.as_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);
// TODO: windows support for DB folder permissions checks
#[cfg(target_family = "unix")]
{
let i_meta = read_file_metadata(&i_path); let i_meta = read_file_metadata(&i_path);
if !file_permissions_readonly(&i_meta) { 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); 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 { if i_meta.mode() & 0o007 != 0 {
eprintln!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", i_str); eprintln!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", i_str);
} }
} }
}
let sctx = create_server_core(config, config_test).await; let sctx = create_server_core(config, config_test).await;
if !config_test { if !config_test {

View file

@ -40,7 +40,6 @@ r2d2 = "^0.8.9"
r2d2_sqlite = "^0.20.0" r2d2_sqlite = "^0.20.0"
rand = "^0.8.5" rand = "^0.8.5"
regex = "^1.5.6" regex = "^1.5.6"
rusqlite = "^0.27.0"
saffron = "^0.1.0" saffron = "^0.1.0"
serde = { version = "^1.0.138", features = ["derive"] } serde = { version = "^1.0.138", features = ["derive"] }
serde_cbor = "^0.11.2" serde_cbor = "^0.11.2"
@ -58,12 +57,27 @@ tracing = { version = "^0.1.35", features = ["attributes"] }
tracing-serde = "^0.1.3" tracing-serde = "^0.1.3"
tracing-subscriber = { version = "^0.3.14", features = ["env-filter"] } tracing-subscriber = { version = "^0.3.14", features = ["env-filter"] }
url = { version = "^2.2.2", features = ["serde"] } url = { version = "^2.2.2", features = ["serde"] }
users = "^0.11.0"
uuid = { version = "^1.1.2", features = ["serde", "v4" ] } uuid = { version = "^1.1.2", features = ["serde", "v4" ] }
validator = { version = "^0.15.0", features = ["phone"] } validator = { version = "^0.15.0", features = ["phone"] }
webauthn-rs = "^0.3.2" webauthn-rs = "^0.3.2"
zxcvbn = "^2.2.1" 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] [features]
# default = [ "libsqlite3-sys/bundled", "openssl/vendored" ] # default = [ "libsqlite3-sys/bundled", "openssl/vendored" ]

View file

@ -10,14 +10,16 @@ use uuid::{Builder, Uuid};
use rand::distributions::Distribution; use rand::distributions::Distribution;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
#[cfg(not(target_family = "windows"))]
use std::fs::Metadata; use std::fs::Metadata;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::os::linux::fs::MetadataExt; use std::os::linux::fs::MetadataExt;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use std::os::macos::fs::MetadataExt; use std::os::macos::fs::MetadataExt;
#[cfg(target_os = "windows")] // #[cfg(target_os = "windows")]
use std::os::windows::fs::MetadataExt; // use std::os::windows::fs::MetadataExt;
#[cfg(target_family = "unix")]
use users::{get_current_gid, get_current_uid}; use users::{get_current_gid, get_current_uid};
#[derive(Debug)] #[derive(Debug)]

View file

@ -32,6 +32,7 @@ mod ldaps;
use async_std::task; use async_std::task;
use compact_jwt::JwsSigner; use compact_jwt::JwsSigner;
use kanidm::prelude::*; use kanidm::prelude::*;
#[cfg(not(target_family = "windows"))]
use libc::umask; use libc::umask;
use kanidm::actors::v1_read::QueryServerReadV1; use kanidm::actors::v1_read::QueryServerReadV1;
@ -551,7 +552,10 @@ pub async fn create_server_core(config: Configuration, config_test: bool) -> Res
config config
); );
// Setup umask, so that every we touch or create is secure. // 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 // Similar, create a stats task which aggregates statistics from the
// server as they come in. // server as they come in.