diff --git a/pica-murmur/.dockerignore b/pica-murmur/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..8d3188d51af5570cae953804a4db830dc67f7799 --- /dev/null +++ b/pica-murmur/.dockerignore @@ -0,0 +1,3 @@ +README.md +docker-compose.yml +secrets/ diff --git a/pica-murmur/Dockerfile b/pica-murmur/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0cfd9bd253034b1d9c1f146e33d336c03f95dc6f --- /dev/null +++ b/pica-murmur/Dockerfile @@ -0,0 +1,28 @@ +FROM debian:buster-slim + +# Install Mumble server +RUN apt-get update -y \ + && apt-get dist-upgrade -y \ + && apt-get install -y \ + libssl-dev \ + libbz2-dev \ + mumble-server=1.3.0~git20190125.440b173+dfsg-2 \ + pcproc \ + python3 \ + python3-pip \ + zeroc-ice-slice \ + && rm -rf /var/lib/apt/lists/* + +# Install prometheus exporter and Python dependencies +COPY requirements.txt / +RUN pip3 install --no-cache-dir -r requirements.txt +COPY exporter.py / + +EXPOSE 64738/tcp 64738/udp 8000/tcp + +# Volume for persistent storage (config file and database) +VOLUME ["/data"] + +# Add entrypoint +COPY entrypoint.sh / +ENTRYPOINT ["/entrypoint.sh"] diff --git a/pica-murmur/README.md b/pica-murmur/README.md new file mode 100644 index 0000000000000000000000000000000000000000..94e274429f16a12b15ccbdbde70fe1b1d100d00e --- /dev/null +++ b/pica-murmur/README.md @@ -0,0 +1,15 @@ +# Murmur + +Docker image for a simple Mumble server (Murmur) + +## Configuration +Some environment variables allow to configure Murmur at startup : +- `MAX_BANDWIDTH` : integer, maximum bandwidth clients are allowed speech at (default is `128000` bps) +- `MAX_USERS` : integer, maximum number of users on the server (default is `100`) +- `METRICS_SERVER_LABEL` : Name of this Murmur server to add as metrics label + +## Mounted volumes +Murmur store its server database and configuration files under `/data/` folder. You should mount this directory on your host. + +## Network +Murmur is listening both TCP and UDP on port 64738. You should bind this container port to your host. diff --git a/pica-murmur/clair-whitelist.yml b/pica-murmur/clair-whitelist.yml new file mode 100644 index 0000000000000000000000000000000000000000..82e705c5a9dda07792d93b2b24af4eb1e201b693 --- /dev/null +++ b/pica-murmur/clair-whitelist.yml @@ -0,0 +1,12 @@ +generalwhitelist: + CVE-2019-19816: On utilise pas btrfs + CVE-2019-19814: On utilise pas f2fs + CVE-2019-19074: ¯\_(ツ)_/¯ + CVE-2020-10543: ¯\_(ツ)_/¯ + CVE-2020-10878: ¯\_(ツ)_/¯ + CVE-2019-19813: On utilise pas btrfs + CVE-2019-19815: On utilise pas f2fs + CVE-2020-8492: ¯\_(ツ)_/¯ + CVE-2013-7445: ¯\_(ツ)_/¯ + CVE-2020-13974: DISPUTED + CVE-2020-14155: ¯\_(ツ)_/¯ diff --git a/pica-murmur/docker-compose.yml b/pica-murmur/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b05c0c11dccb70e3fc805157d8de3cca311dbd5e --- /dev/null +++ b/pica-murmur/docker-compose.yml @@ -0,0 +1,33 @@ +version: "2.4" +networks: + docker_default: + external: true + name: "docker_default" + +volumes: + murmur-data: + name: murmur-data + +services: + murmur: + image: registry.picasoft.net/pica-murmur:1.3.0 + container_name: murmur + environment: + MAX_USERS: 2000 + METRICS_SERVER_LABEL: voice.picasoft.net + ports: + - "64738:64738" + - "64738:64738/udp" + volumes: + - murmur-data:/data + - /DATA/docker/certs/voice.picasoft.net/:/certs + networks: + - docker_default + labels: + - "traefik.enable=true" + - "traefik.port=8000" + - "traefik.frontend.rule=Host:voice.picasoft.net;Path:/metrics" + - "tls-certs-monitor.enable=true" + - "tls-certs-monitor.action=kill:SIGUSR1" + - "tls-certs-monitor.owner=103" + restart: unless-stopped diff --git a/pica-murmur/entrypoint.sh b/pica-murmur/entrypoint.sh new file mode 100755 index 0000000000000000000000000000000000000000..3b28d616911f3a78a3207181db55bcb0034164e1 --- /dev/null +++ b/pica-murmur/entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +CONFIG_FILE="/data/mumble-server.ini" +MAX_BANDWIDTH=${MAX_BANDWIDTH:-128000} +MAX_USERS=${MAX_USERS:-100} + +# Copy configuration file if not exists +if [ ! -f $CONFIG_FILE ] +then + cp /etc/mumble-server.ini $CONFIG_FILE +fi + +# Change configuration +sed -i -E "s/^(\/\/ )?database( )?=.*/database=\/data\/murmur.sqlite/g" $CONFIG_FILE +sed -i -E "s/^(\/\/ )?logfile( )?=.*/logfile=/g" $CONFIG_FILE +sed -i -E "s/^(\/\/ )?bandwidth( )?=.*/bandwidth=$MAX_BANDWIDTH/g" $CONFIG_FILE +sed -i -E "s/^(\/\/ )?users( )?=.*/users=$MAX_USERS/g" $CONFIG_FILE + +# Set correct rights on murmur files +chown -R mumble-server:mumble-server /data + +# Run exporter if variable set +if [ -n "$METRICS_SERVER_LABEL" ] +then + python3 /exporter.py & +fi + +# Trap SIGUSR1 signal to reload certificates without restarting +_reload() { + echo "Caught SIGUSR1 signal!" + /usr/bin/pkill -USR1 murmurd +} +trap _term SIGUSR1 + +# Run murmur +murmurd -fg -v -ini $CONFIG_FILE diff --git a/pica-murmur/exporter.py b/pica-murmur/exporter.py new file mode 100644 index 0000000000000000000000000000000000000000..0c806b73c143f13ba8e0e000b80c65d030e5b569 --- /dev/null +++ b/pica-murmur/exporter.py @@ -0,0 +1,151 @@ +# This script is adapted from another script developped by +# Stefan Hacker <dd0t@users.sourceforge.net> : https://github.com/Natenom/munin-plugins/tree/master/murmur + +# Imports +import tempfile +import os +import sys +import signal +import time +import IcePy +import Ice +from prometheus_client import start_http_server, Counter, Gauge, REGISTRY, PROCESS_COLLECTOR, PLATFORM_COLLECTOR + +# Path to Murmur.ice; the script tries first to retrieve this file dynamically from Murmur itself; if this fails it tries this file. +SLICE_FILE = "/usr/share/slice/Murmur.ice" +# Ice.MessageSizeMax from Murmur server +ICE_MESSAGE_SIZE_MAX = "65535" +# ICE connection variable +ICE_HOST = "127.0.0.1" +ICE_PORT = 6502 +PROXY_STRING = "Meta -e 1.0:tcp -h %s -p %d -t 1000" % (ICE_HOST, ICE_PORT) + +# Number of seconds between 2 metrics collection +COLLECT_INTERVAL = os.getenv('EXPORTER_COLLECT_INTERVAL', 10) + +# Get server name +METRICS_SERVER_LABEL = os.getenv('METRICS_SERVER_LABEL') +if not METRICS_SERVER_LABEL: + print('Must define METRICS_SERVER_LABEL environment variable !') + sys.exit(1) + +# Prepare ICE properties +ice_props = Ice.createProperties() +ice_props.setProperty("Ice.ImplicitContext", "Shared") +ice_props.setProperty("Ice.MessageSizeMax", str(ICE_MESSAGE_SIZE_MAX)) +# Initialize ICE +idata = Ice.InitializationData() +idata.properties = ice_props +ice = Ice.initialize(idata) + +########################################################################################### +###### This part is (almost) an entire copy of the original script to connect to ICE ###### +####################### To be clear : I HAVE NO IDEA WHAT I'M DOING ####################### +########################################################################################### + +connection_done = False +while not connection_done: + try: + ice_proxy = ice.stringToProxy(PROXY_STRING) + + # Get slice directory + slice_dir = Ice.getSliceDir() + slice_dir = ['-I' + slice_dir] + + try: + op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, + True, None, (), (), (), ((), IcePy._t_string, False, 0), ()) + + slice = op.invoke(ice_proxy, ((), None)) + (dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix='.ice') + dynslicefile = os.fdopen(dynslicefiledesc, 'w') + dynslicefile.write(slice) + dynslicefile.flush() + Ice.loadSlice('', slice_dir + [dynslicefilepath]) + dynslicefile.close() + os.remove(dynslicefilepath) + except Exception as e: + try: + Ice.loadSlice('', slice_dir + [SLICE_FILE]) + except: + raise Ice.ConnectionRefusedException + + import Murmur + + # Check connection is working + Murmur.MetaPrx.checkedCast(ice_proxy) + connection_done = True + + except Ice.ConnectionRefusedException: + print('Cannot connect exporter to ICE, retry in 5 seconds') + time.sleep(5) + +########################################################################################### +######################## End of the "NO IDEA WHAT I'M DOING PART" ######################### +########################################################################################### + +# Remove unwanted Prometheus metrics +[REGISTRY.unregister(c) for c in [PROCESS_COLLECTOR, PLATFORM_COLLECTOR, + REGISTRY._names_to_collectors['python_gc_objects_collected_total']]] + +# Start Prometheus exporter server +start_http_server(8000) + +# Register metrics +users_all_gauge = Gauge('murmur_online_users_all', 'Number of online users', ['server']) +users_unregistered_gauge = Gauge('murmur_online_users_unregistered', + 'Number of online unregistered users', ['server']) +users_registered_gauge = Gauge('murmur_online_users_registered', 'Number of online registered users', ['server']) +users_muted_gauge = Gauge('murmur_online_users_muted', 'Number of online muted users', ['server']) +users_banned_gauge = Gauge('murmur_users_banned', 'Number of banned users', ['server']) +chan_count_gauge = Gauge('murmur_channels', 'Number of channels', ['server']) +uptime_gauge = Gauge('murmur_uptime', 'Number of seconds the server is uptime', ['server']) + + +def exit_handler(sig, frame): + # Define handler for stop signals + print('Terminating...') + ice.destroy() + sys.exit(0) + + +# Catch several signals +signal.signal(signal.SIGINT, exit_handler) +signal.signal(signal.SIGTERM, exit_handler) + + +# Loop forever +while True: + # Get data from Murmur server + meta = Murmur.MetaPrx.checkedCast(ice_proxy) + server = meta.getServer(1) + + # Initialize metrics counters + users_muted_count = 0 + users_unregistered_count = 0 + users_registered_count = 0 + + # Collect and count users + onlineusers = server.getUsers() + for key in onlineusers.keys(): + # Count user as registered + if onlineusers[key].userid == -1: + users_unregistered_count += 1 + # Count user as not registered + if onlineusers[key].userid > 0: + users_registered_count += 1 + # Count muted users + if onlineusers[key].mute or onlineusers[key].selfMute or onlineusers[key].suppress: + users_muted_count += 1 + + # Set metrics + users_all_gauge.labels(server=METRICS_SERVER_LABEL).set(len(onlineusers)) + users_muted_gauge.labels(server=METRICS_SERVER_LABEL).set(users_muted_count) + users_unregistered_gauge.labels(server=METRICS_SERVER_LABEL).set(users_unregistered_count) + users_registered_gauge.labels(server=METRICS_SERVER_LABEL).set(users_registered_count) + users_banned_gauge.labels(server=METRICS_SERVER_LABEL).set(len(server.getBans())) + chan_count_gauge.labels(server=METRICS_SERVER_LABEL).set(len(server.getChannels())) + uptime_gauge.labels(server=METRICS_SERVER_LABEL).set(meta.getUptime()) + + # Wait beforce next metrics collection + time.sleep(COLLECT_INTERVAL) diff --git a/pica-murmur/requirements.txt b/pica-murmur/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..5e01e40a21dddfc329f183c6d662849f8004482c --- /dev/null +++ b/pica-murmur/requirements.txt @@ -0,0 +1,3 @@ +zeroc-ice==3.7.3 +prometheus_client==0.7.1 +