mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-24 09:53:54 +02:00
Improve server hardening
This adds a number of warnings to the server to help administrators make better informed decisions about the security of their environment.
This commit is contained in:
parent
cdd7e0e49a
commit
c4805d2915
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -1566,6 +1566,7 @@ dependencies = [
|
|||
"kanidm_proto",
|
||||
"lazy_static",
|
||||
"ldap3_server",
|
||||
"libc",
|
||||
"libsqlite3-sys",
|
||||
"log",
|
||||
"num_cpus",
|
||||
|
@ -1587,6 +1588,7 @@ dependencies = [
|
|||
"tokio-openssl",
|
||||
"tokio-util 0.2.0",
|
||||
"toml",
|
||||
"users",
|
||||
"uuid",
|
||||
"zxcvbn",
|
||||
]
|
||||
|
@ -1666,6 +1668,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tokio-util 0.3.1",
|
||||
"toml",
|
||||
"users",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3301,6 +3304,16 @@ dependencies = [
|
|||
"percent-encoding 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "users"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.1"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
[profile.release]
|
||||
debug = true
|
||||
lto = true
|
||||
# Have to disable to allow aarch64 to build
|
||||
# lto = true
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
|
|
3
Makefile
3
Makefile
|
@ -9,7 +9,8 @@ help:
|
|||
|
||||
buildx/kanidmd: ## build multiarch images
|
||||
buildx/kanidmd:
|
||||
@docker buildx build --progress plain --platform linux/arm64 -f kanidmd/Dockerfile -t $(IMAGE_BASE)/server:$(IMAGE_VERSION) .
|
||||
@docker buildx build --push --platform linux/amd64,linux/arm64 -f kanidmd/Dockerfile -t $(IMAGE_BASE)/server:$(IMAGE_VERSION) .
|
||||
@docker buildx imagetools inspect $(IMAGE_BASE)/server:$(IMAGE_VERSION)
|
||||
|
||||
build/kanidmd: ## build kanidmd images
|
||||
build/kanidmd:
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
- [Password Quality and Badlisting](./password_quality.md)
|
||||
- [Recycle Bin](./recycle_bin.md)
|
||||
- [Legacy Applications -- LDAP](./ldap.md)
|
||||
- [Security Hardening](./security_hardening.md)
|
||||
-----------
|
||||
[Why TLS?](./why_tls.md)
|
||||
|
||||
|
|
117
kanidm_book/src/security_hardening.md
Normal file
117
kanidm_book/src/security_hardening.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
# Security Hardening
|
||||
|
||||
Kanidm ships with a secure-by-default configuration, however that is only as strong
|
||||
as the platform that Kanidm operates in. This could be your container environment
|
||||
or your Unix-like system.
|
||||
|
||||
This chapter will detail a number of warnings and security practices you should
|
||||
follow to ensure that Kanidm operates in a secure environment.
|
||||
|
||||
The main server is a high-value target for a potential attack, as Kanidm serves as
|
||||
the authority on identity and authorisation in a network. Compromise of the Kanidm
|
||||
server is equivalent to a full-network take over, AKA "game over".
|
||||
|
||||
The unixd resolver is also a high value target as it can be accessed to allow unauthorised
|
||||
access to a server, to intercept communications to the server, or more. This also must be protected
|
||||
carfully.
|
||||
|
||||
For this reason the kanidm's components must be protected carefully. Kanidm avoids many classic
|
||||
attacks by being developed in a memory safe language, but risks still exist.
|
||||
|
||||
At startup Kanidm will warn you if the environment it is running in is suspicious or
|
||||
has risks. For example:
|
||||
|
||||
kanidmd server -c /tmp/server.toml
|
||||
WARNING: permissions on /tmp/server.toml may not be secure. Should be readonly to running uid. This could be a security risk ...
|
||||
WARNING: /tmp/server.toml has 'everyone' permission bits in the mode. This could be a security risk ...
|
||||
WARNING: /tmp/server.toml owned by the current uid, which may allow file permission changes. This could be a security risk ...
|
||||
WARNING: permissions on ../insecure/ca.pem may not be secure. Should be readonly to running uid. This could be a security risk ...
|
||||
WARNING: permissions on ../insecure/cert.pem may not be secure. Should be readonly to running uid. This could be a security risk ...
|
||||
WARNING: permissions on ../insecure/key.pem may not be secure. Should be readonly to running uid. This could be a security risk ...
|
||||
WARNING: ../insecure/key.pem has 'everyone' permission bits in the mode. This could be a security risk ...
|
||||
WARNING: DB folder /tmp has 'everyone' permission bits in the mode. This could be a security risk ...
|
||||
|
||||
Each warning highlights an issue that may exist in your environment. It is not possible for us to
|
||||
prescribe an exact configuration that may secure your system. This is why we only present
|
||||
possible risks.
|
||||
|
||||
### Should be readonly to running uid
|
||||
|
||||
Files such as configurations should be readonly to this UID/GID. This is so that if an attacker is
|
||||
able to gain code execution, they are unable to modify the configuration to write or over-write
|
||||
files in other locations, or to tamper with the systems configuration.
|
||||
|
||||
This can be prevented by changing the files ownership to another user, or removing "write" bits
|
||||
from the group.
|
||||
|
||||
### 'everyone' permission bits in the mode
|
||||
|
||||
This means that given a permission mask, "everyone" or all users of the system can read, write or
|
||||
execute the content of this file. This may mean that if an account on the system is compromised the
|
||||
attacker can read Kanidm content and may be able to further attack the system as a result.
|
||||
|
||||
This can be prevented by removed everyone execute bits from parent directories containing the
|
||||
configuration, and removing everyone bits from the files in question.
|
||||
|
||||
### owned by the current uid, which may allow file permission changes
|
||||
|
||||
File permissions in unix systems are a discrestionary access control system, which means the
|
||||
named uid owner is able to further modify the access of a file regardless of the current
|
||||
settings. For example:
|
||||
|
||||
[william@amethyst 12:25] /tmp > touch test
|
||||
[william@amethyst 12:25] /tmp > ls -al test
|
||||
-rw-r--r-- 1 william wheel 0 29 Jul 12:25 test
|
||||
[william@amethyst 12:25] /tmp > chmod 400 test
|
||||
[william@amethyst 12:25] /tmp > ls -al test
|
||||
-r-------- 1 william wheel 0 29 Jul 12:25 test
|
||||
[william@amethyst 12:25] /tmp > chmod 644 test
|
||||
[william@amethyst 12:26] /tmp > ls -al test
|
||||
-rw-r--r-- 1 william wheel 0 29 Jul 12:25 test
|
||||
|
||||
Notice that even though the file was set to "read only" to william, and no permission to any
|
||||
other users, that as "william" I can change the bits to add write permissions back or permissions
|
||||
for other users.
|
||||
|
||||
This can be prevent by making the file owner a different UID to the running process for kanidm.
|
||||
|
||||
### A secure example
|
||||
|
||||
Between these three issues it can be hard to see a possible strategy to secure files, however
|
||||
one way exists - group read permissions. The most effective method to secure resources for kanidm
|
||||
is to set configurations to:
|
||||
|
||||
[william@amethyst 12:26] /etc/kanidm > ls -al server.toml
|
||||
-r--r----- 1 root kanidm 212 28 Jul 16:53 server.toml
|
||||
|
||||
The kanidm server should be run as "kanidm:kanidm" with the appropriate user and user private
|
||||
group created on your system. This applies to unixd configuration as well.
|
||||
|
||||
For the database your data folder should be:
|
||||
|
||||
[root@amethyst 12:38] /data/kanidm > ls -al .
|
||||
total 1064
|
||||
drwxrwx--- 3 root kanidm 96 29 Jul 12:38 .
|
||||
-rw-r----- 1 kanidm kanidm 544768 29 Jul 12:38 kanidm.db
|
||||
|
||||
IE this means 770 root:kanidm. This allows kanidm to create new files in the folder, but prevents
|
||||
kanidm being able to change the permissions of the folder. Because the folder does not have
|
||||
everyone mode bits, the content of the database is secure because users can now cd/read
|
||||
from the directory.
|
||||
|
||||
Configurations for clients such as /etc/kanidm/config should be secured with the permissions
|
||||
as:
|
||||
|
||||
[william@amethyst 12:26] /etc/kanidm > ls -al config
|
||||
-r--r--r-- 1 root wheel 38 10 Jul 10:10 config
|
||||
|
||||
This file should be everyone-readable which is why the bits are defined as such.
|
||||
|
||||
> NOTE: Why do you use 440 or 444 modes?
|
||||
>
|
||||
> A bug exists in the implementation of readonly() in rust that checks this as "does a write
|
||||
> bit exist for any user" vs "can the current uid write the file?". This distinction is subtle
|
||||
> but it affects the check. We don't believe this is a significant issue though because
|
||||
> setting these to 440 and 444 helps to prevent accidental changes by an administrator anyway
|
||||
|
||||
|
|
@ -19,6 +19,7 @@ serde_json = "1.0"
|
|||
serde_derive = "1.0"
|
||||
toml = "0.5"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
# users = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = "0.2"
|
||||
|
|
|
@ -9,11 +9,13 @@ use serde::de::DeserializeOwned;
|
|||
use serde::Serialize;
|
||||
use serde_derive::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::fs::{metadata, File, Metadata};
|
||||
use std::io::Read;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
// use users::{get_current_uid, get_effective_uid};
|
||||
|
||||
use kanidm_proto::v1::{
|
||||
AccountUnixExtend, AuthCredential, AuthRequest, AuthResponse, AuthState, AuthStep,
|
||||
|
@ -60,6 +62,16 @@ pub struct KanidmClientBuilder {
|
|||
connect_timeout: Option<u64>,
|
||||
}
|
||||
|
||||
fn read_file_metadata<P: AsRef<Path>>(path: &P) -> Result<Metadata, ()> {
|
||||
metadata(path).map_err(|e| {
|
||||
error!(
|
||||
"Unable to read metadata for {} - {:?}",
|
||||
path.as_ref().to_str().unwrap(),
|
||||
e
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
impl KanidmClientBuilder {
|
||||
pub fn new() -> Self {
|
||||
KanidmClientBuilder {
|
||||
|
@ -73,6 +85,21 @@ impl KanidmClientBuilder {
|
|||
|
||||
fn parse_certificate(ca_path: &str) -> Result<reqwest::Certificate, ()> {
|
||||
let mut buf = Vec::new();
|
||||
// Is the CA secure?
|
||||
let path = Path::new(ca_path);
|
||||
let ca_meta = read_file_metadata(&path)?;
|
||||
|
||||
if !ca_meta.permissions().readonly() {
|
||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", ca_path);
|
||||
}
|
||||
|
||||
if ca_meta.uid() != 0 || ca_meta.gid() != 0 {
|
||||
warn!(
|
||||
"{} should be owned be root:root to prevent tampering",
|
||||
ca_path
|
||||
);
|
||||
}
|
||||
|
||||
// TODO #253: Handle these errors better, or at least provide diagnostics?
|
||||
let mut f = File::open(ca_path).map_err(|_| ())?;
|
||||
f.read_to_end(&mut buf).map_err(|_| ())?;
|
||||
|
@ -186,6 +213,23 @@ impl KanidmClientBuilder {
|
|||
})
|
||||
}
|
||||
|
||||
fn display_warnings(&self, address: &str) {
|
||||
// Check for problems now
|
||||
if !self.verify_ca {
|
||||
warn!("verify_ca set to false - this may allow network interception of passwords!");
|
||||
}
|
||||
|
||||
if !self.verify_hostnames {
|
||||
warn!(
|
||||
"verify_hostnames set to false - this may allow network interception of passwords!"
|
||||
);
|
||||
}
|
||||
|
||||
if !address.starts_with("https://") {
|
||||
warn!("address does not start with 'https://' - this may allow network interception of passwords!");
|
||||
}
|
||||
}
|
||||
|
||||
// Consume self and return a client.
|
||||
pub fn build(self) -> Result<KanidmClient, reqwest::Error> {
|
||||
// Errghh, how to handle this cleaner.
|
||||
|
@ -197,6 +241,8 @@ impl KanidmClientBuilder {
|
|||
}
|
||||
};
|
||||
|
||||
self.display_warnings(address.as_str());
|
||||
|
||||
let client_builder = reqwest::blocking::Client::builder()
|
||||
.cookie_store(true)
|
||||
.danger_accept_invalid_hostnames(!self.verify_hostnames)
|
||||
|
@ -233,6 +279,8 @@ impl KanidmClientBuilder {
|
|||
}
|
||||
};
|
||||
|
||||
self.display_warnings(address.as_str());
|
||||
|
||||
let client_builder = reqwest::Client::builder()
|
||||
.cookie_store(true)
|
||||
.danger_accept_invalid_hostnames(!self.verify_hostnames)
|
||||
|
|
|
@ -65,6 +65,8 @@ r2d2_sqlite = "0.16"
|
|||
|
||||
reqwest = { version = "0.10" }
|
||||
|
||||
users = "0.10"
|
||||
|
||||
[features]
|
||||
default = [ "libsqlite3-sys/bundled" ]
|
||||
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
|
||||
|
||||
use std::fs::metadata;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
|
@ -61,7 +67,7 @@ impl ClientCodec {
|
|||
|
||||
fn rm_if_exist(p: &str) {
|
||||
let _ = std::fs::remove_file(p).map_err(|e| {
|
||||
error!("attempting to remove {:?} -> {:?}", p, e);
|
||||
warn!("attempting to remove {:?} -> {:?}", p, e);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -201,17 +207,85 @@ async fn handle_client(
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let cuid = get_current_uid();
|
||||
let ceuid = get_effective_uid();
|
||||
let cgid = get_current_gid();
|
||||
let cegid = get_effective_gid();
|
||||
|
||||
if cuid == 0 || ceuid == 0 || cgid == 0 || cegid == 0 {
|
||||
eprintln!("Refusing to run - this process must not operate as root.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// ::std::env::set_var("RUST_LOG", "kanidm=debug,kanidm_client=debug");
|
||||
env_logger::init();
|
||||
|
||||
// setup
|
||||
let cb = KanidmClientBuilder::new()
|
||||
.read_options_from_optional_config("/etc/kanidm/config")
|
||||
.expect("Failed to parse /etc/kanidm/config");
|
||||
let cfg_path = Path::new("/etc/kanidm/config");
|
||||
if cfg_path.exists() {
|
||||
let cfg_meta = match metadata(&cfg_path) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Unable to read metadata for {} - {:?}",
|
||||
cfg_path.to_str().unwrap(),
|
||||
e
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
if !cfg_meta.permissions().readonly() {
|
||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
|
||||
cfg_path.to_str().unwrap());
|
||||
}
|
||||
|
||||
let cfg = KanidmUnixdConfig::new()
|
||||
.read_options_from_optional_config("/etc/kanidm/unixd")
|
||||
.expect("Failed to parse /etc/kanidm/unixd");
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let unixd_path = Path::new("/etc/kanidm/config");
|
||||
if unixd_path.exists() {
|
||||
let unixd_meta = match metadata(&unixd_path) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Unable to read metadata for {} - {:?}",
|
||||
unixd_path.to_str().unwrap(),
|
||||
e
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
if !unixd_meta.permissions().readonly() {
|
||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
|
||||
unixd_path.to_str().unwrap());
|
||||
}
|
||||
|
||||
if unixd_meta.uid() == cuid || unixd_meta.uid() == ceuid {
|
||||
warn!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...",
|
||||
unixd_path.to_str().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// setup
|
||||
let cb = match KanidmClientBuilder::new().read_options_from_optional_config(cfg_path) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
error!("Failed to parse {}", cfg_path.to_str().unwrap());
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let cfg = match KanidmUnixdConfig::new().read_options_from_optional_config(unixd_path) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
error!("Failed to parse {}", unixd_path.to_str().unwrap());
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
rm_if_exist(cfg.sock_path.as_str());
|
||||
|
||||
|
@ -219,6 +293,51 @@ async fn main() {
|
|||
|
||||
let rsclient = cb.build_async().expect("Failed to build async client");
|
||||
|
||||
// Check the pb path will be okay.
|
||||
if cfg.db_path != "" {
|
||||
let db_path = PathBuf::from(cfg.db_path.as_str());
|
||||
// We only need to check the parent folder path permissions as the db itself may not
|
||||
// exist yet.
|
||||
if let Some(db_parent_path) = db_path.parent() {
|
||||
if !db_parent_path.exists() {
|
||||
error!(
|
||||
"Refusing to run, DB folder {} does not exist",
|
||||
db_parent_path.to_str().unwrap()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let db_par_path_buf = db_parent_path.to_path_buf();
|
||||
|
||||
let i_meta = match metadata(&db_par_path_buf) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Unable to read metadata for {} - {:?}",
|
||||
db_par_path_buf.to_str().unwrap(),
|
||||
e
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if !i_meta.is_dir() {
|
||||
error!(
|
||||
"Refusing to run - DB folder {} may not be a directory",
|
||||
db_par_path_buf.to_str().unwrap()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
if i_meta.permissions().readonly() {
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let cachelayer = Arc::new(
|
||||
CacheLayer::new(
|
||||
cfg.db_path.as_str(), // The sqlite db path
|
||||
|
|
|
@ -83,6 +83,9 @@ futures-util = "0.3"
|
|||
tokio-util = { version = "0.2", features = ["codec"] }
|
||||
tokio-openssl = "0.4"
|
||||
|
||||
libc = "0.2"
|
||||
users = "0.10"
|
||||
|
||||
[features]
|
||||
default = [ "libsqlite3-sys/bundled", "openssl/vendored" ]
|
||||
|
||||
|
|
|
@ -2,39 +2,29 @@ ARG BASE_IMAGE=opensuse/tumbleweed:latest
|
|||
FROM ${BASE_IMAGE} AS builder
|
||||
LABEL mantainer william@blackhats.net.au
|
||||
|
||||
RUN zypper mr -d repo-non-oss && \
|
||||
zypper mr -d repo-oss && \
|
||||
zypper mr -d repo-update && \
|
||||
zypper ar https://download.opensuse.org/update/tumbleweed/ repo-update-https && \
|
||||
zypper ar https://download.opensuse.org/tumbleweed/repo/oss/ repo-oss-https && \
|
||||
zypper ar https://download.opensuse.org/tumbleweed/repo/non-oss/ repo-non-oss-https && \
|
||||
zypper ref && \
|
||||
RUN zypper ref && \
|
||||
zypper install -y \
|
||||
cargo \
|
||||
rust \
|
||||
gcc \
|
||||
automake \
|
||||
autoconf \
|
||||
make \
|
||||
libopenssl-devel \
|
||||
pam-devel && \
|
||||
clang lld \
|
||||
make automake autoconf \
|
||||
libopenssl-devel pam-devel && \
|
||||
zypper clean -a
|
||||
|
||||
|
||||
COPY . /usr/src/kanidm
|
||||
WORKDIR /usr/src/kanidm/kanidmd
|
||||
|
||||
RUN RUSTC_BOOTSTRAP=1 cargo build --features=concread/simd_support --release
|
||||
RUN ln -s -f /usr/bin/clang /usr/bin/cc && \
|
||||
ln -s -f /usr/bin/ld.lld /usr/bin/ld
|
||||
|
||||
RUN CC=/usr/bin/clang RUSTC_BOOTSTRAP=1 cargo build --features=concread/simd_support --release
|
||||
|
||||
FROM ${BASE_IMAGE}
|
||||
LABEL mantainer william@blackhats.net.au
|
||||
|
||||
RUN zypper mr -d repo-non-oss && \
|
||||
zypper mr -d repo-oss && \
|
||||
zypper mr -d repo-update && \
|
||||
zypper ar https://download.opensuse.org/update/tumbleweed/ repo-update-https && \
|
||||
zypper ar https://download.opensuse.org/tumbleweed/repo/oss/ repo-oss-https && \
|
||||
zypper ar https://download.opensuse.org/tumbleweed/repo/non-oss/ repo-non-oss-https && \
|
||||
zypper ref && \
|
||||
RUN zypper ref && \
|
||||
zypper install -y \
|
||||
timezone \
|
||||
pam && \
|
||||
|
|
|
@ -715,6 +715,7 @@ pub trait AccessControlsTransaction {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn modify_allow_operation(
|
||||
&self,
|
||||
audit: &mut AuditScope,
|
||||
|
@ -913,6 +914,7 @@ pub trait AccessControlsTransaction {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn create_allow_operation(
|
||||
&self,
|
||||
audit: &mut AuditScope,
|
||||
|
|
|
@ -11,6 +11,8 @@ use uuid::Uuid;
|
|||
|
||||
use std::str::FromStr;
|
||||
|
||||
pub const AUDIT_LINE_SIZE: usize = 512;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[repr(u32)]
|
||||
pub enum LogTag {
|
||||
|
@ -97,18 +99,23 @@ impl fmt::Display for LogTag {
|
|||
|
||||
macro_rules! lqueue {
|
||||
($au:expr, $tag:expr, $($arg:tt)*) => ({
|
||||
use crate::audit::LogTag;
|
||||
use crate::audit::{LogTag, AUDIT_LINE_SIZE};
|
||||
if cfg!(test) {
|
||||
println!($($arg)*)
|
||||
}
|
||||
if ($au.level & $tag as u32) == $tag as u32 {
|
||||
use std::fmt;
|
||||
$au.log_event(
|
||||
$tag,
|
||||
fmt::format(
|
||||
format_args!($($arg)*)
|
||||
)
|
||||
)
|
||||
// We have to buffer the string to over-alloc it.
|
||||
let mut output = String::with_capacity(AUDIT_LINE_SIZE);
|
||||
match fmt::write(&mut output, format_args!($($arg)*)) {
|
||||
Ok(_) => $au.log_event($tag, output),
|
||||
Err(e) => {
|
||||
$au.log_event(
|
||||
LogTag::AdminError,
|
||||
format!("CRITICAL UNABLE TO WRITE LOG EVENT - {:?}", e)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mod ctx;
|
||||
mod ldaps;
|
||||
// use actix_files as fs;
|
||||
use libc::umask;
|
||||
use actix::prelude::*;
|
||||
use actix_session::{CookieSession, Session};
|
||||
use actix_web::web::{self, Data, HttpResponse, Json, Path};
|
||||
|
@ -1626,9 +1627,11 @@ pub async fn create_server_core(config: Configuration) -> Result<ServerCtx, ()>
|
|||
}
|
||||
|
||||
info!("Starting kanidm with configuration: {}", config);
|
||||
// Setup umask, so that every we touch or create is secure.
|
||||
let _ = unsafe { umask(0o0027) };
|
||||
|
||||
// The log server is started on it's own thread, and is contacted
|
||||
// asynchronously.
|
||||
|
||||
let (log_tx, log_rx) = unbounded();
|
||||
let log_thread = thread::spawn(move || async_log::run(log_rx));
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ use uuid::Uuid;
|
|||
|
||||
use kanidm_proto::v1::{OperationError, UserAuthToken};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PasswordChangeEvent {
|
||||
pub event: Event,
|
||||
pub target: Uuid,
|
||||
|
@ -60,7 +59,6 @@ impl PasswordChangeEvent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnixPasswordChangeEvent {
|
||||
pub event: Event,
|
||||
pub target: Uuid,
|
||||
|
@ -223,13 +221,21 @@ impl UnixGroupTokenEvent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnixUserAuthEvent {
|
||||
pub event: Event,
|
||||
pub target: Uuid,
|
||||
pub cleartext: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for UnixUserAuthEvent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("UnixUserAuthEvent")
|
||||
.field("event", &self.event)
|
||||
.field("target", &self.target)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl UnixUserAuthEvent {
|
||||
#[cfg(test)]
|
||||
pub fn new_internal(target: &Uuid, cleartext: &str) -> Self {
|
||||
|
@ -333,7 +339,6 @@ impl VerifyTOTPEvent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LdapAuthEvent {
|
||||
// pub event: Event,
|
||||
pub target: Uuid,
|
||||
|
|
|
@ -977,6 +977,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn delete(&self, au: &mut AuditScope, de: &DeleteEvent) -> Result<(), OperationError> {
|
||||
lperf_segment!(au, "server::delete", || {
|
||||
// Do you have access to view all the set members? Reduce based on your
|
||||
|
@ -1279,6 +1280,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn modify(&self, au: &mut AuditScope, me: &ModifyEvent) -> Result<(), OperationError> {
|
||||
lperf_segment!(au, "server::modify", || {
|
||||
// Get the candidates.
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
#![deny(warnings)]
|
||||
|
||||
use users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
|
||||
|
||||
use serde_derive::Deserialize;
|
||||
use std::fs::File;
|
||||
use std::fs::{metadata, File, Metadata};
|
||||
use std::io::Read;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
@ -127,13 +130,63 @@ impl Opt {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_file_metadata(path: &PathBuf) -> Metadata {
|
||||
match metadata(path) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Unable to read metadata for {} - {:?}",
|
||||
path.to_str().unwrap(),
|
||||
e
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() {
|
||||
// Get info about who we are.
|
||||
let cuid = get_current_uid();
|
||||
let ceuid = get_effective_uid();
|
||||
let cgid = get_current_gid();
|
||||
let cegid = get_effective_gid();
|
||||
|
||||
if cuid == 0 || ceuid == 0 || cgid == 0 || cegid == 0 {
|
||||
eprintln!("ERROR: Refusing to run - this process must not operate as root.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if cuid != ceuid || cgid != cegid {
|
||||
eprintln!("{} != {} || {} != {}", cuid, ceuid, cgid, cegid);
|
||||
eprintln!("ERROR: Refusing to run - uid and euid OR gid and egid must be consistent.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Read cli args, determine if we should backup/restore
|
||||
let opt = Opt::from_args();
|
||||
|
||||
// Read our config
|
||||
let mut config = Configuration::new();
|
||||
// Check the permissions are sane.
|
||||
let cfg_meta = read_file_metadata(&(opt.commonopt().config_path));
|
||||
if !cfg_meta.permissions().readonly() {
|
||||
eprintln!("WARNING: permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
|
||||
opt.commonopt().config_path.to_str().unwrap());
|
||||
}
|
||||
|
||||
if cfg_meta.mode() & 0o007 != 0 {
|
||||
eprintln!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...",
|
||||
opt.commonopt().config_path.to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
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.commonopt().config_path.to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
// Read our config
|
||||
let sconfig = match ServerConfig::new(&(opt.commonopt().config_path)) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
|
@ -152,6 +205,64 @@ async fn main() {
|
|||
}
|
||||
});
|
||||
|
||||
// Check the permissions of the files from the configuration.
|
||||
|
||||
if let Some(i_str) = &(sconfig.tls_ca) {
|
||||
let i_path = PathBuf::from(i_str.as_str());
|
||||
let i_meta = read_file_metadata(&i_path);
|
||||
if !i_meta.permissions().readonly() {
|
||||
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_cert) {
|
||||
let i_path = PathBuf::from(i_str.as_str());
|
||||
let i_meta = read_file_metadata(&i_path);
|
||||
if !i_meta.permissions().readonly() {
|
||||
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 !i_meta.permissions().readonly() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
let db_path = PathBuf::from(sconfig.db_path.as_str());
|
||||
// We can't check the db_path permissions because it may note exist yet!
|
||||
if let Some(db_parent_path) = db_path.parent() {
|
||||
if !db_parent_path.exists() {
|
||||
eprintln!(
|
||||
"DB folder {} may not exist, server startup may FAIL!",
|
||||
db_parent_path.to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
let db_par_path_buf = db_parent_path.to_path_buf();
|
||||
let i_meta = read_file_metadata(&db_par_path_buf);
|
||||
if !i_meta.is_dir() {
|
||||
eprintln!(
|
||||
"ERROR: Refusing to run - DB folder {} may not be a directory",
|
||||
db_par_path_buf.to_str().unwrap()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
if i_meta.permissions().readonly() {
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
config.update_log_level(ll);
|
||||
config.update_db_path(&sconfig.db_path.as_str());
|
||||
config.update_tls(&sconfig.tls_ca, &sconfig.tls_cert, &sconfig.tls_key);
|
||||
|
|
Loading…
Reference in a new issue