Maximilian Weiler 2024-01-23 20:30:27 +00:00 zatwierdzone przez GitHub
commit 5a791ec7d6
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
6 zmienionych plików z 366 dodań i 6 usunięć

1
.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1 @@
/target

61
Cargo.lock wygenerowano 100644
Wyświetl plik

@ -0,0 +1,61 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "libc"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "mumble-docker"
version = "0.1.0"
dependencies = [
"libc",
"regex",
]
[[package]]
name = "regex"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"

17
Cargo.toml 100644
Wyświetl plik

@ -0,0 +1,17 @@
[package]
name = "mumble-docker"
version = "0.1.0"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
regex = "1.10.3"
libc = "0.2.152"
[profile.release]
lto = true
codegen-units = 1
opt-level = "s"
strip = true

Wyświetl plik

@ -1,4 +1,24 @@
FROM ubuntu:20.04 as base
ARG DEBIAN_RELEASE_NAME=bullseye
ARG DEBIAN_RELEASE_NUM=11
# ------------------------------------------------------#
# --- Build stage for the entrypoint.sh replacement ----#
# ------------------------------------------------------#
FROM rust:slim-${DEBIAN_RELEASE_NAME} as builder
WORKDIR /data
COPY Cargo.toml /data
COPY Cargo.lock /data
RUN mkdir -p /data/src
RUN echo 'fn main() {}' > /data/src/main.rs
RUN cargo build
COPY ./src/main.rs /data/src/main.rs
RUN cargo build --release
# ------------------------------------------------------#
# --- Runtime image with mumble-server's dependencies --#
# --- (Used to extract the shared libs from later) -----#
# ------------------------------------------------------#
FROM debian:${DEBIAN_RELEASE_NAME}-slim as base
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --no-install-recommends -y \
@ -26,6 +46,9 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
# ------------------------------------------------------#
# --- Build stage for mumble-server itself -------------#
# ------------------------------------------------------#
FROM base as build
ARG DEBIAN_FRONTEND=noninteractive
@ -62,8 +85,11 @@ RUN /mumble/scripts/clone.sh && /mumble/scripts/build.sh \
&& /mumble/scripts/copy_one_of.sh ./scripts/murmur.ini ./auxiliary_files/mumble-server.ini default_config.ini
FROM base
# ------------------------------------------------------#
# --- Preparation stage that could run mumble ----------#
# --- Determines config files and libs for final stage -#
# ------------------------------------------------------#
FROM base as runner
ARG MUMBLE_UID=1000
ARG MUMBLE_GID=1000
@ -73,11 +99,35 @@ COPY --from=build /mumble/repo/build/mumble-server /usr/bin/mumble-server
COPY --from=build /mumble/repo/default_config.ini /etc/mumble/bare_config.ini
RUN mkdir -p /data && chown -R mumble:mumble /data && chown -R mumble:mumble /etc/mumble
COPY copy-libs.sh /copy-libs.sh
COPY entrypoint.sh /entrypoint.sh
COPY --from=builder /data/target/release/mumble-docker/ /mumble-docker-entrypoint
# Copy over all required shared libraries to /lib/copy so we can
# reuse them in the distroless container in the final stage
RUN /copy-libs.sh /mumble-docker-entrypoint /lib/copy
RUN /copy-libs.sh /usr/bin/mumble-server /lib/copy
RUN /copy-libs.sh /usr/lib/x86_64-linux-gnu/qt5/plugins/sqldrivers/libqsqlite.so /lib/copy
RUN cp -r /usr/lib/x86_64-linux-gnu/qt-default /lib/copy
RUN cp -r /usr/lib/x86_64-linux-gnu/qt5/ /lib/copy
# ------------------------------------------------------#
# --- Distroless base image for the final container --- #
# --- Copying over needed libs from previous stage ---- #
# ------------------------------------------------------#
FROM gcr.io/distroless/cc-debian${DEBIAN_RELEASE_NUM}:latest
COPY --from=runner /etc/passwd /etc/passwd
COPY --from=runner /etc/shadow /etc/shadow
COPY --from=runner /usr/bin/mumble-server /usr/bin/mumble-server
COPY --from=runner /lib/copy /usr/lib/x86_64-linux-gnu
COPY --from=runner /etc/mumble /etc/mumble
COPY --chown=1000:1000 --from=runner /data /data
COPY --from=runner /mumble-docker-entrypoint /mumble-docker-entrypoint
USER mumble
EXPOSE 64738/tcp 64738/udp
COPY entrypoint.sh /entrypoint.sh
VOLUME ["/data"]
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["/mumble-docker-entrypoint"]
CMD ["/usr/bin/mumble-server", "-fg"]

16
copy-libs.sh 100755
Wyświetl plik

@ -0,0 +1,16 @@
#!/bin/sh
if [ $# -lt 2 ]
then
echo "Too few arguments. Run copy-libs.sh <binary or shared lib to analyze> <target directory>"
exit 1
fi
binary_path=$1
target_path=$2
mkdir -p "$2"
for i in $(ldd "$binary_path" | grep "=>" | awk -F ' => ' '{print $2}' | cut -d ' ' -f1)
do
echo "Copying $i"
cp -f "$i" "$2"
done

215
src/main.rs 100644
Wyświetl plik

@ -0,0 +1,215 @@
const DATA_DIR: &str = "/data";
const BARE_BONES_CONFIG_FILE: &str = "/etc/mumble/bare_config.ini";
const CONFIG_REGEX: &str = r"^(;|#)? *([a-zA-Z_0-9]+)=.*";
use regex::Regex;
use std::os::unix::process::CommandExt;
use std::path::Path;
use std::process::Command;
use std::{collections::HashMap, env, fs, os::unix::prelude::MetadataExt};
const SENSITIVE_CONFIGS: [&str; 6] = [
"dbPassword",
"icesecretread",
"icesecretwrite",
"serverpassword",
"registerpassword",
"sslPassPhrase",
];
fn slice_contains(slice: &[&'static str; 6], search: &str) -> bool {
for entry in slice.iter() {
if *entry == search {
return true;
}
}
false
}
fn normalize_name(name: &str) -> String {
name.to_uppercase().replace('_', "")
}
fn set_config_internal(
config_file_content: &mut String,
used_configs: &mut Vec<String>,
config_name: &str,
config_value: &str,
is_default: bool,
) {
if is_default && used_configs.contains(&config_name.to_string()) {
//Do not overwrite user specified options with defaults
return;
}
if slice_contains(&SENSITIVE_CONFIGS, config_name) {
println!("Setting config \"{}\" to: *********", config_name);
} else {
println!("Setting config \"{}\" to: {}", config_name, config_value);
}
used_configs.push(String::from(config_name));
config_file_content.push_str(&format!("{}={}\n", config_name, config_value));
}
fn list_mumble_config_secrets() -> Vec<(String, String)> {
let mut result: Vec<(String, String)> = Vec::new();
if let Ok(read_dir) = fs::read_dir("/run/secrets/") {
for entry in read_dir {
let file_name_os = entry.unwrap().file_name();
let file_name_str = file_name_os.to_str().unwrap().to_string();
if file_name_str.starts_with("MUMBLE_CONFIG_") {
let file_content = fs::read_to_string(&file_name_str).unwrap();
result.push((file_name_str, file_content));
}
}
}
result
}
fn list_mumble_config_env_vars() -> Vec<(String, String)> {
let mut result: Vec<(String, String)> = Vec::new();
for env_var in env::vars() {
if env_var.0.starts_with("MUMBLE_CONFIG_") {
result.push(env_var);
}
}
result
}
fn get_existing_config_options(
bare_bones_config: &str,
option_for: &mut HashMap<String, String>,
) -> Vec<String> {
let config_line_regex = Regex::new(CONFIG_REGEX).unwrap();
let mut existing_config_options: Vec<String> = Vec::new();
for line in bare_bones_config.lines() {
let captures = config_line_regex.captures(line);
if let Some(matches) = captures {
let option = matches.get(2).unwrap();
let option_string = option.as_str().to_string();
option_for.insert(
format!("MUMBLE_CONFIG_{}", normalize_name(&option_string)),
option_string.clone(),
);
existing_config_options.push(option_string.clone());
}
}
existing_config_options
}
fn set_superuser_password(server_invocation: &[String], mumble_supw_password_secret: String) {
let mut set_secret_server_invocation = server_invocation.to_owned();
set_secret_server_invocation.push(String::from("-supw"));
set_secret_server_invocation.push(mumble_supw_password_secret);
let status = Command::new(&set_secret_server_invocation[0])
.args(&set_secret_server_invocation[1..])
.status()
.expect("Could not set superuse password");
println!(
"Successfully configured superuser password with exit status {}",
status
);
}
fn main() {
let mut used_config_options: Vec<String> = Vec::new();
let mut option_for: HashMap<String, String> = HashMap::new();
let mut config_file = format!("{DATA_DIR}/mumble_server_config.ini");
let mut config_file_content: String = String::from("# Config file automatically generated from the MUMBLE_CONFIG_* environment variables or secrets in /run/secrets/MUMBLE_CONFIG_* files\n");
let bare_bones_config =
fs::read_to_string(BARE_BONES_CONFIG_FILE).expect("Could not read barebones config file");
get_existing_config_options(&bare_bones_config, &mut option_for);
let mut set_config = |config_name: &str, config_value: &str, is_default: bool| {
set_config_internal(
&mut config_file_content,
&mut used_config_options,
config_name,
config_value,
is_default,
);
};
match env::var("MUMBLE_CUSTOM_CONFIG_FILE") {
Ok(custom_config_path) => {
println!("Using manually specified config file at $MUMBLE_CUSTOM_CONFIG_FILE\nAll MUMBLE_CONFIG variables will be ignored");
config_file = custom_config_path;
}
Err(_e) => {
for mumble_env in list_mumble_config_env_vars() {
let config_option = option_for.get(mumble_env.0.as_str());
match config_option {
Some(config_name) => {
set_config(config_name, &mumble_env.1, false);
}
None => {
println!("Could not find config option for variable {}", mumble_env.0);
}
}
}
for mumble_secret in list_mumble_config_secrets() {
let config_option = option_for.get(mumble_secret.0.as_str());
match config_option {
Some(config_name) => set_config(config_name, &mumble_secret.1, false),
None => {
println!(
"Could not find config option for secret {}",
mumble_secret.0
);
}
}
}
//Apply default settings if they're missing
let old_db_file = format!("{DATA_DIR}/murmur.sqlite");
if Path::new(&old_db_file).is_file() {
set_config("database", &old_db_file, true);
} else {
set_config(
"database",
&format!("{DATA_DIR}/mumble-server.sqlite"),
true,
);
}
set_config("ice", "\"tcp -h 127.0.0.1 -p 6502\"", true);
set_config(
"welcometext",
"\"<br />Welcome to this server, running the official Mumble Docker image.<br />Enjoy your stay!<br />\"",
true,
);
set_config("port", "64738", true);
set_config("users", "100", true);
config_file_content
.push_str("\n[Ice]\nIce.Warn.UnknownProperties=1\nIce.MessageSizeMax=65536");
fs::write(&config_file, &config_file_content)
.expect("Could not write generated config file");
}
}
let mut server_invocation: Vec<String> = env::args().skip(1).collect();
server_invocation.push(String::from("-ini"));
server_invocation.push(config_file);
let variable = env::var("MUMBLE_SUPERUSER_PASSWORD").ok();
let mumble_supw_secret_path = Path::new("/run/secrets/MUMBLE_SUPERUSER_PASSWORD");
if mumble_supw_secret_path.is_file() {
let mumble_supw_password_secret = fs::read_to_string(mumble_supw_secret_path).unwrap();
set_superuser_password(&server_invocation, mumble_supw_password_secret);
} else if variable.is_some() {
set_superuser_password(&server_invocation, variable.unwrap());
}
let user_uid = unsafe { libc::getuid() };
let user_gid = unsafe { libc::getgid() };
println!("Running Mumble server as UID={user_uid} GID={user_gid}");
let metadata = Path::new(DATA_DIR)
.metadata()
.expect("Could not query permissions for data directory");
println!(
"{DATA_DIR} has the following permissions set: {:o} with UID={} and GID={}",
metadata.mode(),
metadata.uid(),
metadata.gid()
);
println!("Command run to start the service: {:?}", server_invocation);
Command::new(&server_invocation[0])
.args(&server_invocation[1..])
.exec();
}