mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Changing to allow startup without a config file (#2582)
* Changing to allow startup without a config file, using environment variables
This commit is contained in:
parent
7b490d73dc
commit
4096b8f02d
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1121,6 +1121,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sketching",
|
"sketching",
|
||||||
|
"tempfile",
|
||||||
"tikv-jemallocator",
|
"tikv-jemallocator",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
|
1
Makefile
1
Makefile
|
@ -186,6 +186,7 @@ doc/format: ## Format docs and the Kanidm book
|
||||||
-not -path './target/*' \
|
-not -path './target/*' \
|
||||||
-not -path './docs/*' \
|
-not -path './docs/*' \
|
||||||
-not -path '*/.venv/*' -not -path './vendor/*'\
|
-not -path '*/.venv/*' -not -path './vendor/*'\
|
||||||
|
-not -path '*/.*/*' \
|
||||||
-name \*.md \
|
-name \*.md \
|
||||||
-exec deno fmt --check $(MARKDOWN_FORMAT_ARGS) "{}" +
|
-exec deno fmt --check $(MARKDOWN_FORMAT_ARGS) "{}" +
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,10 @@ The maximum length in seconds that an authentication session may exist for.
|
||||||
The minimum security strength of credentials that may be assigned to this account. In order from
|
The minimum security strength of credentials that may be assigned to this account. In order from
|
||||||
weakest to strongest:
|
weakest to strongest:
|
||||||
|
|
||||||
* `any`
|
- `any`
|
||||||
* `mfa`
|
- `mfa`
|
||||||
* `passkey`
|
- `passkey`
|
||||||
* `attested_passkey`
|
- `attested_passkey`
|
||||||
|
|
||||||
### Password Minimum Length
|
### Password Minimum Length
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,29 @@ In this section we will configure your server and create its container instance.
|
||||||
|
|
||||||
## Configuring server.toml
|
## Configuring server.toml
|
||||||
|
|
||||||
You need a configuration file in the volume named `server.toml`. (Within the container it should be
|
There are two methods for configuration:
|
||||||
`/data/server.toml`) The following is a commented example configuration.
|
|
||||||
|
|
||||||
The full options and explanations are in the
|
1. Providing a configuration file in the volume named `server.toml`. (Within the container it should
|
||||||
|
be `/data/server.toml`)
|
||||||
|
2. Using environment variables to specify configuration options (uppercased, prefixed with
|
||||||
|
`KANIDM_`).
|
||||||
|
|
||||||
|
You can use one or both methods, but environment variables take precedence over options specified in
|
||||||
|
files.The full options and explanations are in the
|
||||||
[kanidmd_core::config::ServerConfig](https://kanidm.github.io/kanidm/master/rustdoc/kanidmd_core/config/struct.ServerConfig.html)
|
[kanidmd_core::config::ServerConfig](https://kanidm.github.io/kanidm/master/rustdoc/kanidmd_core/config/struct.ServerConfig.html)
|
||||||
for your particular build.
|
docs page for your particular build.
|
||||||
|
|
||||||
|
<!-- deno-fmt-ignore-start -->
|
||||||
|
|
||||||
|
{{#template templates/kani-warning.md
|
||||||
|
imagepath=images
|
||||||
|
title=Warning!
|
||||||
|
text=You MUST set the "domain", "origin", "tls_chain" and "tls_path" options via one method or the other, or the server cannot start!
|
||||||
|
}}
|
||||||
|
|
||||||
|
<!-- deno-fmt-ignore-end -->
|
||||||
|
|
||||||
|
The following is a commented example configuration.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
{{#rustdoc_include ../../examples/server_container.toml}}
|
{{#rustdoc_include ../../examples/server_container.toml}}
|
||||||
|
@ -23,7 +40,7 @@ This example is located in
|
||||||
{{#template templates/kani-warning.md
|
{{#template templates/kani-warning.md
|
||||||
imagepath=images
|
imagepath=images
|
||||||
title=Warning!
|
title=Warning!
|
||||||
text=You MUST set the `domain` name correctly, aligned with your `origin`, else the server may refuse to start or some features (e.g. webauthn, oauth) may not work correctly!
|
text=You MUST set the "domain" name correctly, aligned with your "origin", else the server may refuse to start or some features (e.g. WebAuthn, OAuth2) may not work correctly!
|
||||||
}}
|
}}
|
||||||
|
|
||||||
<!-- deno-fmt-ignore-end -->
|
<!-- deno-fmt-ignore-end -->
|
||||||
|
@ -40,8 +57,8 @@ docker run --rm -i -t -v kanidmd:/data \
|
||||||
|
|
||||||
## Run the Server
|
## Run the Server
|
||||||
|
|
||||||
Now we can run the server so that it can accept connections. This defaults to using
|
Now we can run the server so that it can accept connections. The container defaults to using a
|
||||||
`-c /data/server.toml`.
|
configuration file in `/data/server.toml`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -p 443:8443 -v kanidmd:/data kanidm/server:latest
|
docker run -p 443:8443 -v kanidmd:/data kanidm/server:latest
|
||||||
|
@ -50,12 +67,14 @@ docker run -p 443:8443 -v kanidmd:/data kanidm/server:latest
|
||||||
### Using the NET\_BIND\_SERVICE capability
|
### Using the NET\_BIND\_SERVICE capability
|
||||||
|
|
||||||
If you plan to run without using docker port mapping or some other reverse proxy, and your
|
If you plan to run without using docker port mapping or some other reverse proxy, and your
|
||||||
bindaddress or ldapbindaddress port is less than `1024` you will need the `NET_BIND_SERVICE` in
|
`bindaddress` or `ldapbindaddress` port is less than `1024` you will need the `NET_BIND_SERVICE` in
|
||||||
docker to allow these port binds. You can add this with `--cap-add` in your docker run command.
|
docker to allow these port binds. You can add this with `--cap-add` in your docker run command.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --cap-add NET_BIND_SERVICE --network [host OR macvlan OR ipvlan] \
|
docker run --cap-add NET_BIND_SERVICE \
|
||||||
-v kanidmd:/data kanidm/server:latest
|
--network [host OR macvlan OR ipvlan] \
|
||||||
|
-v kanidmd:/data \
|
||||||
|
kanidm/server:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- deno-fmt-ignore-start -->
|
<!-- deno-fmt-ignore-start -->
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
# - set up a test oauth2 rp (https://kanidm.com)
|
# - set up a test oauth2 rp (https://kanidm.com)
|
||||||
# - prompt to reset testuser's creds online
|
# - prompt to reset testuser's creds online
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [ -n "${BUILD_MODE}" ]; then
|
if [ -n "${BUILD_MODE}" ]; then
|
||||||
BUILD_MODE="--${BUILD_MODE}"
|
BUILD_MODE="--${BUILD_MODE}"
|
||||||
else
|
else
|
||||||
|
@ -83,13 +81,16 @@ if [ "${REMOVE_TEST_DB}" -eq 1 ]; then
|
||||||
rm /tmp/kanidm/kanidm.db || true
|
rm /tmp/kanidm/kanidm.db || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export KANIDM_CONFIG="../../examples/insecure_server.toml"
|
||||||
IDM_ADMIN_USER="idm_admin@localhost"
|
IDM_ADMIN_USER="idm_admin@localhost"
|
||||||
|
|
||||||
|
|
||||||
echo "Resetting the idm_admin user..."
|
echo "Resetting the idm_admin user..."
|
||||||
IDM_ADMIN_PASS=$(${KANIDMD} recover-account idm_admin -o json 2>&1 | grep password | jq -r .password)
|
IDM_ADMIN_PASS_RAW="$(${KANIDMD} recover-account idm_admin -o json 2>&1)"
|
||||||
|
IDM_ADMIN_PASS="$(echo "${IDM_ADMIN_PASS_RAW}" | grep password | jq -r .password)"
|
||||||
if [ -z "${IDM_ADMIN_PASS}" ] || [ "${IDM_ADMIN_PASS}" == "null " ]; then
|
if [ -z "${IDM_ADMIN_PASS}" ] || [ "${IDM_ADMIN_PASS}" == "null " ]; then
|
||||||
echo "Failed to reset idm_admin password!"
|
echo "Failed to reset idm_admin password!"
|
||||||
|
echo "Raw output:"
|
||||||
|
echo "${IDM_ADMIN_PASS_RAW}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "idm_admin pass: '${IDM_ADMIN_PASS}'"
|
echo "idm_admin pass: '${IDM_ADMIN_PASS}'"
|
||||||
|
|
|
@ -25,16 +25,18 @@ if [ ! -f "run_insecure_dev_server.sh" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export KANIDM_CONFIG="../../examples/insecure_server.toml"
|
||||||
|
|
||||||
mkdir -p /tmp/kanidm/client_ca
|
mkdir -p /tmp/kanidm/client_ca
|
||||||
|
|
||||||
echo "Generating certificates..."
|
echo "Generating certificates..."
|
||||||
cargo run --bin kanidmd --release cert-generate --config ../../examples/insecure_server.toml
|
cargo run --bin kanidmd --release cert-generate
|
||||||
|
|
||||||
echo "Making sure it runs with the DB..."
|
echo "Making sure it runs with the DB..."
|
||||||
cargo run --bin kanidmd --release recover-account idm_admin -o json --config ../../examples/insecure_server.toml
|
cargo run --bin kanidmd --release recover-account idm_admin -o json
|
||||||
|
|
||||||
echo "Running the server..."
|
echo "Running the server..."
|
||||||
cargo run --bin kanidmd --release server --config ../../examples/insecure_server.toml &
|
cargo run --bin kanidmd --release server &
|
||||||
KANIDMD_PID=$!
|
KANIDMD_PID=$!
|
||||||
echo "Kanidm PID: ${KANIDMD_PID}"
|
echo "Kanidm PID: ${KANIDMD_PID}"
|
||||||
|
|
||||||
|
|
|
@ -80,25 +80,27 @@ pub struct TlsConfiguration {
|
||||||
pub client_ca: Option<PathBuf>,
|
pub client_ca: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is the Server Configuration as read from `server.toml`.
|
/// This is the Server Configuration as read from `server.toml` or environment variables.
|
||||||
|
///
|
||||||
|
/// Fields noted as "REQUIRED" are required for the server to start, even if they show as optional due to how file parsing works.
|
||||||
|
///
|
||||||
|
/// If you want to set these as environment variables, prefix them with `KANIDM_` and they will be picked up. This does not include replication peer config.
|
||||||
///
|
///
|
||||||
/// NOTE: not all flags or values from the internal [Configuration] object are exposed via this structure
|
/// NOTE: not all flags or values from the internal [Configuration] object are exposed via this structure
|
||||||
/// to prevent certain settings being set (e.g. integration test modes)
|
/// to prevent certain settings being set (e.g. integration test modes)
|
||||||
///
|
#[derive(Debug, Deserialize, Default)]
|
||||||
/// If you want to set these as environment variables, prefix them with `KANIDM_` and they will be picked up. This doesn't include replication peer config.
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
/// Kanidm Domain, eg `kanidm.example.com`.
|
/// *REQUIRED* - Kanidm Domain, eg `kanidm.example.com`.
|
||||||
pub domain: String,
|
pub domain: Option<String>,
|
||||||
/// The user-facing HTTPS URL for this server, eg <https://idm.example.com>
|
/// *REQUIRED* - The user-facing HTTPS URL for this server, eg <https://idm.example.com>
|
||||||
// TODO -this should be URL
|
// TODO -this should be URL
|
||||||
pub origin: String,
|
pub origin: Option<String>,
|
||||||
/// File path of the database file
|
/// File path of the database file
|
||||||
pub db_path: String,
|
pub db_path: Option<String>,
|
||||||
/// The file path to the TLS Certificate Chain
|
/// *REQUIRED* - The file path to the TLS Certificate Chain
|
||||||
pub tls_chain: Option<String>,
|
pub tls_chain: Option<String>,
|
||||||
/// The file path to the TLS Private Key
|
/// *REQUIRED* - The file path to the TLS Private Key
|
||||||
pub tls_key: Option<String>,
|
pub tls_key: Option<String>,
|
||||||
|
|
||||||
/// The directory path of the client ca and crl dir.
|
/// The directory path of the client ca and crl dir.
|
||||||
|
@ -115,7 +117,7 @@ pub struct ServerConfig {
|
||||||
/// If unset, the LDAP server will be disabled.
|
/// If unset, the LDAP server will be disabled.
|
||||||
pub ldapbindaddress: Option<String>,
|
pub ldapbindaddress: Option<String>,
|
||||||
|
|
||||||
/// The role of this server, one of write_replica, write_replica_no_ui, read_only_replica
|
/// The role of this server, one of write_replica, write_replica_no_ui, read_only_replica, defaults to [ServerRole::WriteReplica]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub role: ServerRole,
|
pub role: ServerRole,
|
||||||
/// The log level, one of info, debug, trace. Defaults to "info" if not set.
|
/// The log level, one of info, debug, trace. Defaults to "info" if not set.
|
||||||
|
@ -129,7 +131,8 @@ pub struct ServerConfig {
|
||||||
|
|
||||||
/// The filesystem type, either "zfs" or "generic". Defaults to "generic" if unset. I you change this, run a database vacuum.
|
/// The filesystem type, either "zfs" or "generic". Defaults to "generic" if unset. I you change this, run a database vacuum.
|
||||||
pub db_fs_type: Option<kanidm_proto::internal::FsType>,
|
pub db_fs_type: Option<kanidm_proto::internal::FsType>,
|
||||||
/// The path to the "admin" socket, used for local communication when performing cer ain server control tasks.
|
|
||||||
|
/// The path to the "admin" socket, used for local communication when performing certain server control tasks. Default is set on build, based on the system target.
|
||||||
pub adminbindpath: Option<String>,
|
pub adminbindpath: Option<String>,
|
||||||
|
|
||||||
/// Don't touch this unless you know what you're doing!
|
/// Don't touch this unless you know what you're doing!
|
||||||
|
@ -139,43 +142,101 @@ pub struct ServerConfig {
|
||||||
#[serde(rename = "replication")]
|
#[serde(rename = "replication")]
|
||||||
/// Replication configuration, this is a development feature and not yet ready for production use.
|
/// Replication configuration, this is a development feature and not yet ready for production use.
|
||||||
pub repl_config: Option<ReplicationConfiguration>,
|
pub repl_config: Option<ReplicationConfiguration>,
|
||||||
/// An optional OpenTelemetry collector (GRPC) url to send trace and log data to, eg http://localhost:4317
|
/// An optional OpenTelemetry collector (GRPC) url to send trace and log data to, eg `http://localhost:4317`. If not set, disables the feature.
|
||||||
pub otel_grpc_url: Option<String>,
|
pub otel_grpc_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerConfig {
|
impl ServerConfig {
|
||||||
/// loads the configuration file from the path specified, then overlays fields from environment variables starting with `KANIDM_``
|
/// loads the configuration file from the path specified, then overlays fields from environment variables starting with `KANIDM_``
|
||||||
pub fn new<P: AsRef<Path>>(config_path: P) -> Result<Self, std::io::Error> {
|
pub fn new<P: AsRef<Path>>(config_path: Option<P>) -> Result<Self, std::io::Error> {
|
||||||
let mut f = File::open(config_path.as_ref()).map_err(|e| {
|
// start with a base config
|
||||||
eprintln!("Unable to open config file [{:?}] 🥺", e);
|
let mut config = ServerConfig::default();
|
||||||
let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
|
|
||||||
eprintln!("{}", diag);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut contents = String::new();
|
if let Some(config_path) = config_path {
|
||||||
f.read_to_string(&mut contents).map_err(|e| {
|
// see if we can load it from the config file you asked for
|
||||||
eprintln!("unable to read contents {:?}", e);
|
if config_path.as_ref().exists() {
|
||||||
let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
|
eprintln!("📜 Using config file: {:?}", config_path.as_ref());
|
||||||
eprintln!("{}", diag);
|
let mut f: File = File::open(config_path.as_ref()).map_err(|e| {
|
||||||
e
|
eprintln!("Unable to open config file [{:?}] 🥺", e);
|
||||||
})?;
|
let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
|
||||||
|
eprintln!("{}", diag);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
let res: ServerConfig = toml::from_str(contents.as_str()).map_err(|e| {
|
let mut contents = String::new();
|
||||||
eprintln!(
|
|
||||||
"Unable to parse config from '{:?}': {:?}",
|
|
||||||
config_path.as_ref(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
std::io::Error::new(std::io::ErrorKind::Other, e)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let res = res.try_from_env().map_err(|e| {
|
f.read_to_string(&mut contents).map_err(|e| {
|
||||||
|
eprintln!("unable to read contents {:?}", e);
|
||||||
|
let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
|
||||||
|
eprintln!("{}", diag);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// if we *can* load the config we'll set config to that.
|
||||||
|
match toml::from_str::<ServerConfig>(contents.as_str()) {
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Unable to parse config from '{:?}': {:?}",
|
||||||
|
config_path.as_ref(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(val) => config = val,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
eprintln!("📜 No config file found at {:?}", config_path.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build from the environment variables
|
||||||
|
let res = config.try_from_env().map_err(|e| {
|
||||||
println!("Failed to use environment variable config: {e}");
|
println!("Failed to use environment variable config: {e}");
|
||||||
std::io::Error::new(std::io::ErrorKind::Other, e)
|
std::io::Error::new(std::io::ErrorKind::Other, e)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(res)
|
// check if the required fields are there
|
||||||
|
let mut config_failed = false;
|
||||||
|
if res.domain.is_none() {
|
||||||
|
eprintln!("❌ 'domain' field in server configuration is not set, server cannot start!");
|
||||||
|
config_failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.origin.is_none() {
|
||||||
|
eprintln!("❌ 'origin' field in server configuration is not set, server cannot start!");
|
||||||
|
config_failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.db_path.is_none() {
|
||||||
|
eprintln!(
|
||||||
|
"❌ 'db_path' field in server configuration is not set, server cannot start!"
|
||||||
|
);
|
||||||
|
config_failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
if res.tls_chain.is_none() {
|
||||||
|
eprintln!(
|
||||||
|
"❌ 'tls_chain' field in server configuration is not set, server cannot start!"
|
||||||
|
);
|
||||||
|
config_failed = true;
|
||||||
|
}
|
||||||
|
#[cfg(not(test))]
|
||||||
|
if res.tls_key.is_none() {
|
||||||
|
eprintln!(
|
||||||
|
"❌ 'tls_key' field in server configuration is not set, server cannot start!"
|
||||||
|
);
|
||||||
|
config_failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if config_failed {
|
||||||
|
eprintln!("Failed to parse configuration, server cannot start!");
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"Failed to parse configuration, server cannot start!",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the ServerConfig from environment variables starting with `KANIDM_`
|
/// Updates the ServerConfig from environment variables starting with `KANIDM_`
|
||||||
|
@ -202,13 +263,13 @@ impl ServerConfig {
|
||||||
|
|
||||||
match key.replace("KANIDM_", "").as_str() {
|
match key.replace("KANIDM_", "").as_str() {
|
||||||
"DOMAIN" => {
|
"DOMAIN" => {
|
||||||
self.domain = value.to_string();
|
self.domain = Some(value.to_string());
|
||||||
}
|
}
|
||||||
"ORIGIN" => {
|
"ORIGIN" => {
|
||||||
self.origin = value.to_string();
|
self.origin = Some(value.to_string());
|
||||||
}
|
}
|
||||||
"DB_PATH" => {
|
"DB_PATH" => {
|
||||||
self.origin = value.to_string();
|
self.origin = Some(value.to_string());
|
||||||
}
|
}
|
||||||
"TLS_CHAIN" => {
|
"TLS_CHAIN" => {
|
||||||
self.tls_chain = Some(value.to_string());
|
self.tls_chain = Some(value.to_string());
|
||||||
|
|
|
@ -35,6 +35,7 @@ tokio-util = { workspace = true, features = ["codec"] }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
opentelemetry = { workspace = true, features = ["logs"] }
|
opentelemetry = { workspace = true, features = ["logs"] }
|
||||||
opentelemetry_api = { workspace = true, features = ["logs"] }
|
opentelemetry_api = { workspace = true, features = ["logs"] }
|
||||||
|
tempfile = { workspace = true }
|
||||||
tracing = { workspace = true, features = [
|
tracing = { workspace = true, features = [
|
||||||
"max_level_trace",
|
"max_level_trace",
|
||||||
"release_max_level_debug",
|
"release_max_level_debug",
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||||
|
|
||||||
use std::fs::{metadata, File};
|
use std::fs::{metadata, File};
|
||||||
use std::str::FromStr;
|
|
||||||
// This works on both unix and windows.
|
// This works on both unix and windows.
|
||||||
use fs2::FileExt;
|
use fs2::FileExt;
|
||||||
use kanidm_proto::messages::ConsoleOutputMode;
|
use kanidm_proto::messages::ConsoleOutputMode;
|
||||||
|
@ -268,19 +267,8 @@ async fn kanidm_main() -> ExitCode {
|
||||||
//we set up a list of these so we can set the log config THEN log out the errors.
|
//we set up a list of these so we can set the log config THEN log out the errors.
|
||||||
let mut config_error: Vec<String> = Vec::new();
|
let mut config_error: Vec<String> = Vec::new();
|
||||||
let mut config = Configuration::new();
|
let mut config = Configuration::new();
|
||||||
let cfg_path = opt
|
|
||||||
.commands
|
|
||||||
.commonopt()
|
|
||||||
.config_path
|
|
||||||
.clone()
|
|
||||||
.or_else(|| PathBuf::from_str(env!("KANIDM_DEFAULT_CONFIG_PATH")).ok());
|
|
||||||
|
|
||||||
let Some(cfg_path) = cfg_path else {
|
let sconfig = match ServerConfig::new(opt.config_path()) {
|
||||||
eprintln!("Unable to start - can not locate any configuration file");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
};
|
|
||||||
|
|
||||||
let sconfig = match ServerConfig::new(&cfg_path) {
|
|
||||||
Ok(c) => Some(c),
|
Ok(c) => Some(c),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
config_error.push(format!("Config Parse failure {:?}", e));
|
config_error.push(format!("Config Parse failure {:?}", e));
|
||||||
|
@ -365,85 +353,104 @@ async fn kanidm_main() -> ExitCode {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
if let Some(cfg_path) = opt.config_path() {
|
||||||
{
|
#[cfg(target_family = "unix")]
|
||||||
let cfg_meta = match metadata(&cfg_path) {
|
{
|
||||||
Ok(m) => m,
|
if let Some(cfg_meta) = match metadata(&cfg_path) {
|
||||||
Err(e) => {
|
Ok(m) => Some(m),
|
||||||
error!(
|
Err(e) => {
|
||||||
"Unable to read metadata for '{}' - {:?}",
|
error!(
|
||||||
cfg_path.display(),
|
"Unable to read metadata for configuration file '{}' - {:?}",
|
||||||
e
|
cfg_path.display(),
|
||||||
);
|
e
|
||||||
return ExitCode::FAILURE;
|
);
|
||||||
|
// return ExitCxode::FAILURE;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
if !kanidm_lib_file_permissions::readonly(&cfg_meta) {
|
||||||
|
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
|
||||||
|
cfg_path.to_str().unwrap_or("invalid file path"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg_meta.mode() & 0o007 != 0 {
|
||||||
|
warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...",
|
||||||
|
cfg_path.to_str().unwrap_or("invalid file path")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg_meta.uid() == cuid || cfg_meta.uid() == ceuid {
|
||||||
|
warn!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...",
|
||||||
|
cfg_path.to_str().unwrap_or("invalid file path")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if !kanidm_lib_file_permissions::readonly(&cfg_meta) {
|
|
||||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
|
|
||||||
cfg_path.to_str().unwrap_or("invalid file path"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg_meta.mode() & 0o007 != 0 {
|
|
||||||
warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...",
|
|
||||||
cfg_path.to_str().unwrap_or("invalid file path")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg_meta.uid() == cuid || cfg_meta.uid() == ceuid {
|
|
||||||
warn!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...",
|
|
||||||
cfg_path.to_str().unwrap_or("invalid file path")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the permissions of the files from the configuration.
|
// Check the permissions of the files from the configuration.
|
||||||
|
if let Some(db_path) = sconfig.db_path.clone() {
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
let db_pathbuf = PathBuf::from(db_path.as_str());
|
||||||
|
// We can't check the db_path permissions because it may not exist yet!
|
||||||
|
if let Some(db_parent_path) = db_pathbuf.parent() {
|
||||||
|
if !db_parent_path.exists() {
|
||||||
|
warn!(
|
||||||
|
"DB folder {} may not exist, server startup may FAIL!",
|
||||||
|
db_parent_path.to_str().unwrap_or("invalid file path")
|
||||||
|
);
|
||||||
|
let diag = kanidm_lib_file_permissions::diagnose_path(&db_pathbuf);
|
||||||
|
info!(%diag);
|
||||||
|
}
|
||||||
|
|
||||||
let db_path = PathBuf::from(sconfig.db_path.as_str());
|
let db_par_path_buf = db_parent_path.to_path_buf();
|
||||||
// We can't check the db_path permissions because it may not exist yet!
|
let i_meta = match metadata(&db_par_path_buf) {
|
||||||
if let Some(db_parent_path) = db_path.parent() {
|
Ok(m) => m,
|
||||||
if !db_parent_path.exists() {
|
Err(e) => {
|
||||||
warn!(
|
error!(
|
||||||
"DB folder {} may not exist, server startup may FAIL!",
|
"Unable to read metadata for database folder '{}' - {:?}",
|
||||||
db_parent_path.to_str().unwrap_or("invalid file path")
|
&db_par_path_buf.to_str().unwrap_or("invalid file path"),
|
||||||
);
|
e
|
||||||
let diag = kanidm_lib_file_permissions::diagnose_path(&db_path);
|
);
|
||||||
info!(%diag);
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
let db_par_path_buf = db_parent_path.to_path_buf();
|
if !i_meta.is_dir() {
|
||||||
let i_meta = match metadata(&db_par_path_buf) {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
error!(
|
||||||
"Unable to read metadata for '{}' - {:?}",
|
"ERROR: Refusing to run - DB folder {} may not be a directory",
|
||||||
&db_par_path_buf.to_str().unwrap_or("invalid file path"),
|
db_par_path_buf.to_str().unwrap_or("invalid file path")
|
||||||
e
|
|
||||||
);
|
);
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if !i_meta.is_dir() {
|
|
||||||
error!(
|
|
||||||
"ERROR: Refusing to run - DB folder {} may not be a directory",
|
|
||||||
db_par_path_buf.to_str().unwrap_or("invalid file path")
|
|
||||||
);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if kanidm_lib_file_permissions::readonly(&i_meta) {
|
if kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||||
warn!("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"));
|
warn!("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"));
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
if i_meta.mode() & 0o007 != 0 {
|
if i_meta.mode() & 0o007 != 0 {
|
||||||
warn!("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"));
|
warn!("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_db_path(&db_path);
|
||||||
|
} else {
|
||||||
|
error!("No db_path set in configuration, server startup will FAIL!");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(origin) = sconfig.origin.clone() {
|
||||||
|
config.update_origin(&origin);
|
||||||
|
} else {
|
||||||
|
error!("No origin set in configuration, server startup will FAIL!");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(domain) = sconfig.domain.clone() {
|
||||||
|
config.update_domain(&domain);
|
||||||
|
} else {
|
||||||
|
error!("No domain set in configuration, server startup will FAIL!");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
config.update_db_path(sconfig.db_path.as_str());
|
|
||||||
config.update_db_fs_type(&sconfig.db_fs_type);
|
|
||||||
config.update_origin(sconfig.origin.as_str());
|
|
||||||
config.update_domain(sconfig.domain.as_str());
|
|
||||||
config.update_db_arc_size(sconfig.get_db_arc_size());
|
config.update_db_arc_size(sconfig.get_db_arc_size());
|
||||||
config.update_role(sconfig.role);
|
config.update_role(sconfig.role);
|
||||||
config.update_output_mode(opt.commands.commonopt().output_mode.to_owned().into());
|
config.update_output_mode(opt.commands.commonopt().output_mode.to_owned().into());
|
||||||
|
@ -461,7 +468,16 @@ async fn kanidm_main() -> ExitCode {
|
||||||
| KanidmdOpt::HealthCheck(_) => (),
|
| KanidmdOpt::HealthCheck(_) => (),
|
||||||
_ => {
|
_ => {
|
||||||
// Okay - Lets now create our lock and go.
|
// Okay - Lets now create our lock and go.
|
||||||
let klock_path = format!("{}.klock", sconfig.db_path.as_str());
|
#[allow(clippy::expect_used)]
|
||||||
|
let klock_path = match sconfig.db_path.clone() {
|
||||||
|
Some(val) => format!("{}.klock", val),
|
||||||
|
None => std::env::temp_dir()
|
||||||
|
.join("kanidmd.klock")
|
||||||
|
.to_str()
|
||||||
|
.expect("Unable to create klock path")
|
||||||
|
.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
let flock = match File::create(&klock_path) {
|
let flock = match File::create(&klock_path) {
|
||||||
Ok(flock) => flock,
|
Ok(flock) => flock,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -499,7 +515,7 @@ async fn kanidm_main() -> ExitCode {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
"Unable to read metadata for '{}' - {:?}",
|
"Unable to read metadata for TLS chain file '{}' - {:?}",
|
||||||
&i_path.to_str().unwrap_or("invalid file path"),
|
&i_path.to_str().unwrap_or("invalid file path"),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
|
@ -520,7 +536,7 @@ async fn kanidm_main() -> ExitCode {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
"Unable to read metadata for '{}' - {:?}",
|
"Unable to read metadata for TLS key file '{}' - {:?}",
|
||||||
&i_path.to_str().unwrap_or("invalid file path"),
|
&i_path.to_str().unwrap_or("invalid file path"),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
struct CommonOpt {
|
struct CommonOpt {
|
||||||
/// Path to the server's configuration file. If it does not exist, it will be created.
|
/// Path to the server's configuration file.
|
||||||
#[clap(short, long = "config", env = "KANIDM_CONFIG")]
|
#[clap(short, long = "config", env = "KANIDM_CONFIG")]
|
||||||
config_path: Option<PathBuf>,
|
config_path: Option<PathBuf>,
|
||||||
/// Log format (still in very early development)
|
/// Log format (still in very early development)
|
||||||
|
@ -154,6 +154,58 @@ struct KanidmdParser {
|
||||||
commands: KanidmdOpt,
|
commands: KanidmdOpt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl KanidmdParser {
|
||||||
|
/// Returns the configuration path that was specified on the command line, if any.
|
||||||
|
fn config_path(&self) -> Option<PathBuf> {
|
||||||
|
match self.commands {
|
||||||
|
KanidmdOpt::Server(ref c) => c.config_path.clone(),
|
||||||
|
KanidmdOpt::ConfigTest(ref c) => c.config_path.clone(),
|
||||||
|
KanidmdOpt::CertGenerate(ref c) => c.config_path.clone(),
|
||||||
|
KanidmdOpt::RecoverAccount { ref commonopts, .. } => commonopts.config_path.clone(),
|
||||||
|
KanidmdOpt::ShowReplicationCertificate { ref commonopts, .. } => {
|
||||||
|
commonopts.config_path.clone()
|
||||||
|
}
|
||||||
|
KanidmdOpt::RenewReplicationCertificate { ref commonopts, .. } => {
|
||||||
|
commonopts.config_path.clone()
|
||||||
|
}
|
||||||
|
KanidmdOpt::RefreshReplicationConsumer { ref commonopts, .. } => {
|
||||||
|
commonopts.config_path.clone()
|
||||||
|
}
|
||||||
|
KanidmdOpt::DbScan { ref commands } => match commands {
|
||||||
|
DbScanOpt::ListIndexes(ref c) => c.config_path.clone(),
|
||||||
|
DbScanOpt::ListIndex(ref c) => c.commonopts.config_path.clone(),
|
||||||
|
DbScanOpt::ListId2Entry(ref c) => c.config_path.clone(),
|
||||||
|
DbScanOpt::GetId2Entry(ref c) => c.commonopts.config_path.clone(),
|
||||||
|
DbScanOpt::ListIndexAnalysis(ref c) => c.config_path.clone(),
|
||||||
|
DbScanOpt::QuarantineId2Entry { ref commonopts, .. } => {
|
||||||
|
commonopts.config_path.clone()
|
||||||
|
}
|
||||||
|
DbScanOpt::ListQuarantined { ref commonopts } => commonopts.config_path.clone(),
|
||||||
|
DbScanOpt::RestoreQuarantined { ref commonopts, .. } => {
|
||||||
|
commonopts.config_path.clone()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
KanidmdOpt::Database { ref commands } => match commands {
|
||||||
|
DbCommands::Vacuum(ref c) => c.config_path.clone(),
|
||||||
|
DbCommands::Backup(ref c) => c.commonopts.config_path.clone(),
|
||||||
|
DbCommands::Restore(ref c) => c.commonopts.config_path.clone(),
|
||||||
|
DbCommands::Verify(ref c) => c.config_path.clone(),
|
||||||
|
DbCommands::Reindex(ref c) => c.config_path.clone(),
|
||||||
|
},
|
||||||
|
KanidmdOpt::DomainSettings { ref commands } => match commands {
|
||||||
|
DomainSettingsCmds::Show { ref commonopts } => commonopts.config_path.clone(),
|
||||||
|
DomainSettingsCmds::Change { ref commonopts } => commonopts.config_path.clone(),
|
||||||
|
DomainSettingsCmds::Raise { ref commonopts } => commonopts.config_path.clone(),
|
||||||
|
DomainSettingsCmds::Remigrate { ref commonopts, .. } => {
|
||||||
|
commonopts.config_path.clone()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
KanidmdOpt::HealthCheck(ref c) => c.commonopts.config_path.clone(),
|
||||||
|
KanidmdOpt::Version(ref c) => c.config_path.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
enum KanidmdOpt {
|
enum KanidmdOpt {
|
||||||
#[clap(name = "server")]
|
#[clap(name = "server")]
|
||||||
|
|
|
@ -432,7 +432,7 @@ pub trait IdmServerTransaction<'a> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
warn!("No client certificate or bearer tokens were supplied");
|
debug!("No client certificate or bearer tokens were supplied");
|
||||||
Err(OperationError::NotAuthenticated)
|
Err(OperationError::NotAuthenticated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,7 +464,7 @@ pub trait IdmServerTransaction<'a> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
warn!("No client certificate or bearer tokens were supplied");
|
debug!("No client certificate or bearer tokens were supplied");
|
||||||
Err(OperationError::NotAuthenticated)
|
Err(OperationError::NotAuthenticated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3810,7 +3810,7 @@ mod tests {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("A critical error has occurred! {:?}", e);
|
error!("A critical error has occurred! {:?}", e);
|
||||||
// Should not occur!
|
// Should not occur!
|
||||||
panic!();
|
panic!("A critical error has occurred! {:?}", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1093,7 +1093,7 @@ fn config_security_checks(cfg_path: &Path) -> bool {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
"Unable to read metadata for '{}' during security checks - {:?}",
|
"Unable to read metadata for config file '{}' during security checks - {:?}",
|
||||||
cfg_path_str, e
|
cfg_path_str, e
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
|
|
Loading…
Reference in a new issue