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

13
Cargo.lock generated
View file

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

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.
#### 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`.

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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