From 932f24c966b3308ceae2df0797868b68d96d44a7 Mon Sep 17 00:00:00 2001 From: Noxcis Date: Tue, 5 Dec 2023 04:44:01 -0800 Subject: [PATCH 01/40] Update dashboard.py --- src/dashboard.py | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/dashboard.py b/src/dashboard.py index 072410a..87b6ec9 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -5,7 +5,7 @@ Under Apache-2.0 License import sqlite3 import configparser -import hashlib +import bcrypt import ipaddress import json # Python Built-in Library @@ -705,16 +705,20 @@ def auth(): """ data = request.get_json() config = get_dashboard_conf() - password = hashlib.sha256(data['password'].encode()) - if password.hexdigest() == config["Account"]["password"] \ - and data['username'] == config["Account"]["username"]: + saved_password_hash = config["Account"]["password"] + + # Verify the password using bcrypt + if bcrypt.checkpw(data['password'].encode(), saved_password_hash.encode()): session['username'] = data['username'] config.clear() return jsonify({"status": True, "msg": ""}) + config.clear() return jsonify({"status": False, "msg": "Username or Password is incorrect."}) + + """ Index Page """ @@ -857,6 +861,7 @@ def update_peer_default_config(): return redirect(url_for("settings")) + # Update dashboard password @app.route('/update_pwd', methods=['POST']) def update_pwd(): @@ -866,10 +871,19 @@ def update_pwd(): """ config = get_dashboard_conf() - if hashlib.sha256(request.form['currentpass'].encode()).hexdigest() == config.get("Account", "password"): - if hashlib.sha256(request.form['newpass'].encode()).hexdigest() == hashlib.sha256( - request.form['repnewpass'].encode()).hexdigest(): - config.set("Account", "password", hashlib.sha256(request.form['repnewpass'].encode()).hexdigest()) + saved_password_hash = config.get("Account", "password") + current_password = request.form['currentpass'] + new_password = request.form['newpass'] + rep_new_password = request.form['repnewpass'] + + # Verify the current password using bcrypt + if bcrypt.checkpw(current_password.encode(), saved_password_hash.encode()): + # Check if the new passwords match + if new_password == rep_new_password: + # Hash the new password and update the config + new_password_hash = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()) + config.set("Account", "password", new_password_hash.decode()) + try: set_dashboard_conf(config) session['message'] = "Password update successfully!" @@ -893,6 +907,7 @@ def update_pwd(): return redirect(url_for("settings")) + @app.route('/update_app_ip_port', methods=['POST']) def update_app_ip_port(): """ @@ -1610,7 +1625,15 @@ def init_dashboard(): if "username" not in config['Account']: config['Account']['username'] = 'admin' if "password" not in config['Account']: - config['Account']['password'] = '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918' + wg_dash_pass = "admin" + #wg_dash_pass = os.environ.get('WG_DASH_PASS') + # Hash the password using bcrypt + salt = bcrypt.gensalt(rounds=12) + hashed_password_bytes = bcrypt.hashpw(wg_dash_pass.encode('utf-8'), salt) + # Convert the hashed password bytes to a string and remove the leading 'b' + hashed_password_str = hashed_password_bytes.decode('utf-8').lstrip('b') + hashpassword_output = f"{hashed_password_str}" + config['Account']['password'] = hashpassword_output # Default dashboard server setting if "Server" not in config: config['Server'] = {} From a4151800f183ef2483e7113035e158a3fa50b5c0 Mon Sep 17 00:00:00 2001 From: Noxcis Date: Tue, 5 Dec 2023 04:47:40 -0800 Subject: [PATCH 02/40] Update requirements.txt --- src/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/requirements.txt b/src/requirements.txt index 5d3b347..19422f9 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,6 +1,7 @@ Flask ifcfg icmplib +bcrypt flask-qrcode gunicorn -certbot \ No newline at end of file +certbot From 8378030c7033f0eee6c62665e42e00266f77c9d5 Mon Sep 17 00:00:00 2001 From: Noxcis Date: Thu, 22 Aug 2024 23:15:39 -0500 Subject: [PATCH 03/40] Fixed Docker Vulnerability + Switched Base Image to Alpine + Simplified Docker Build + Added Alpine support to wgd.sh script. + Maintained Project Layout. --- Dockerfile | 24 +++ compose.yaml | 23 +++ docker/Dockerfile | 77 -------- docker/compose.yaml | 23 --- docker/entrypoint.sh | 109 ----------- docker/requirements.txt | 8 + docker/wgd.sh | 397 ++++++++++++++++++++++++++++++++++++++++ src/dashboard.py | 14 +- src/entrypoint.sh | 41 +++++ 9 files changed, 504 insertions(+), 212 deletions(-) create mode 100644 Dockerfile create mode 100644 compose.yaml delete mode 100644 docker/Dockerfile delete mode 100644 docker/compose.yaml delete mode 100644 docker/entrypoint.sh create mode 100644 docker/requirements.txt create mode 100644 docker/wgd.sh create mode 100644 src/entrypoint.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..73da4cc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# Pull from small Debian stable image. +FROM alpine:latest +LABEL maintainer="dselen@nerthus.nl" +ENV PYTHONPATH="/usr/bin/python" + +WORKDIR /home/app +RUN apk update && \ + apk add --no-cache py3-bcrypt py3-psutil && \ + apk add --no-cache wireguard-tools && \ + apk add --no-cache net-tools iproute2 iptables ip6tables && \ + apk add --no-cache inotify-tools procps openresolv && \ + mkdir /home/app/master-key + +COPY ./src /home/app +COPY ./docker/wgd.sh /home/app/ +COPY ./docker/requirements.txt /home/app/ + +RUN chmod u+x /home/app/entrypoint.sh + + +# Defining a way for Docker to check the health of the container. In this case: checking the login URL. +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD curl -f http://localhost:10086/signin || exit 1 + +ENTRYPOINT ["/home/app/entrypoint.sh"] \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..7003626 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,23 @@ +services: + + wireguard-dashboard: + build: ./ + container_name: wiregate + cap_add: + - NET_ADMIN + - SYS_MODULE + restart: unless-stopped + volumes: + - wgd_configs:/etc/wireguard + - wgd_app:/home/app + ports: + - 10086:10086/tcp + - 51820:51820/udp + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 + + +volumes: + wgd_configs: + wgd_app: \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index fb373d2..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,77 +0,0 @@ -# Pull from small Debian stable image. -FROM debian:stable-slim -LABEL maintainer="dselen@nerthus.nl" - -# Declaring environment variables, change Peernet to an address you like, standard is a 24 bit subnet. -ENV wg_net="10.0.0.1" -# wg_net is used functionally as an ARG for its environment variable nature, do not change unless you know what you are doing. - -# Following ENV variables are changable on container runtime because /entrypoint.sh handles that. See compose.yaml for more info. -ENV tz="Europe/Amsterdam" -ENV global_dns="1.1.1.1" -ENV enable_wg0="false" -ENV isolated_peers="true" -ENV public_ip="0.0.0.0" - -# Doing basic system maintenance. Change the timezone to the desired timezone. -RUN ln -sf /usr/share/zoneinfo/${tz} /etc/localtime - -# Doing package management operations, such as upgrading -RUN apt-get update && apt-get upgrade -y \ - && apt-get install -y --no-install-recommends curl \ - git \ - iproute2 \ - iptables \ - iputils-ping \ - openresolv \ - procps \ - python3 \ - python3-pip \ - python3-venv \ - traceroute \ - wireguard \ - wireguard-tools \ - && apt-get remove linux-image-* --autoremove -y \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* -# Removing the Linux Image package to preserve space on the image, for this reason also deleting apt lists, to be able to install packages: run apt update. - -# Using WGDASH -- like wg_net functionally as a ARG command. But it is needed in entrypoint.sh so it needs to be exported as environment variable. -ENV WGDASH=/opt/wireguarddashboard -RUN python3 -m venv ${WGDASH}/venv - -# Doing WireGuard Dashboard installation measures. Modify the git clone command to get the preferred version, with a specific branch for example. -RUN . ${WGDASH}/venv/bin/activate \ - && git clone https://github.com/donaldzou/WGDashboard.git ${WGDASH}/app \ - && pip3 install -r ${WGDASH}/app/src/requirements.txt \ - && chmod +x ${WGDASH}/app/src/wgd.sh \ - && .${WGDASH}/app/src/wgd.sh install - -# Set the volume to be used for persistency. -VOLUME /etc/wireguard - -# Generate basic WireGuard interface. Echoing the WireGuard interface config for readability, adjust if you want it for efficiency. -# Also setting the pipefail option, verbose: https://github.com/hadolint/hadolint/wiki/DL4006. -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN wg genkey | tee /etc/wireguard/wg0_privatekey \ - && echo "[Interface]" > /wg0.conf \ - && echo "SaveConfig = true" >> /wg0.conf \ - && echo "Address = ${wg_net}/24" >> /wg0.conf \ - && echo "PrivateKey = $(cat /etc/wireguard/wg0_privatekey)" >> /wg0.conf \ - && echo "PostUp = iptables -t nat -I POSTROUTING 1 -s ${wg_net}/24 -o $(ip -o -4 route show to default | awk '{print $NF}') -j MASQUERADE" >> /wg0.conf \ - && echo "PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP" >> /wg0.conf \ - && echo "PreDown = iptables -t nat -D POSTROUTING 1" >> /wg0.conf \ - && echo "PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP" >> /wg0.conf \ - && echo "ListenPort = 51820" >> /wg0.conf \ - #&& echo "DNS = ${global_dns}" >> /wg0.conf \ - && rm /etc/wireguard/wg0_privatekey - -# Defining a way for Docker to check the health of the container. In this case: checking the login URL. -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD curl -f http://localhost:10086/signin || exit 1 - -# Copy the basic entrypoint.sh script. -COPY entrypoint.sh /entrypoint.sh - -# Exposing the default WireGuard Dashboard port for web access. -EXPOSE 10086 -ENTRYPOINT ["/bin/bash", "/entrypoint.sh"] \ No newline at end of file diff --git a/docker/compose.yaml b/docker/compose.yaml deleted file mode 100644 index 9d7509f..0000000 --- a/docker/compose.yaml +++ /dev/null @@ -1,23 +0,0 @@ -services: - wireguard-dashboard: - image: repo.nerthus.nl/app/wireguard-dashboard:latest - restart: unless-stopped - container_name: wire-dash - environment: - #- tz= # <--- Set container timezone, default: Europe/Amsterdam. - #- global_dns= # <--- Set global DNS address, default: 1.1.1.1. - - enable_wg0=true # <--- If true, wg0 will be started on container startup. default: false. - - isolated_peers=false # <--- When set to true, it disallows peers to talk to eachother, setting to false, allows it, default: true. - #- public_ip= # <--- Set public IP to ensure the correct one is chosen, defaulting to the IP give by ifconfig.me. - ports: - - 10086:10086/tcp - - 51820:51820/udp - volumes: - - conf:/etc/wireguard - - app:/opt/wireguarddashboard/app - cap_add: - - NET_ADMIN - -volumes: - conf: - app: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100644 index 118e9ef..0000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/bash -echo "Starting the WireGuard Dashboard Docker container." - -clean_up() { - # Cleaning out previous data such as the .pid file and starting the WireGuard Dashboard. Making sure to use the python venv. - echo "Looking for remains of previous instances..." - if [ -f "/opt/wireguarddashboard/app/src/gunicorn.pid" ]; then - echo "Found old .pid file, removing." - rm /opt/wireguarddashboard/app/src/gunicorn.pid - else - echo "No remains found, continuing." - fi -} - -start_core() { - # This first step is to ensure the wg0.conf file exists, and if not, then its copied over from the ephemeral container storage. - if [ ! -f "/etc/wireguard/wg0.conf" ]; then - cp "/wg0.conf" "/etc/wireguard/wg0.conf" - echo "WireGuard interface file copied over." - else - echo "WireGuard interface file looks to already be existing." - fi - - echo "Activating Python venv and executing the WireGuard Dashboard service." - - . "${WGDASH}"/venv/bin/activate - cd "${WGDASH}"/app/src || return # If changing the directory fails (permission or presence error), then bash will exist this function, causing the WireGuard Dashboard to not be succesfully launched. - bash wgd.sh start - - # The following section takes care of the firewall rules regarding the 'isolated_peers' feature, which allows or drops packets destined from the wg0 to the wg0 interface. - if [ "${isolated_peers,,}" = "false" ]; then - echo "Isolated peers disabled, adjusting." - - sed -i '/PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP/d' /etc/wireguard/wg0.conf - sed -i '/PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP/d' /etc/wireguard/wg0.conf - elif [ "${isolated_peers,,}" = "true" ]; then - upblocking=$(grep -c "PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP" /etc/wireguard/wg0.conf) - downblocking=$(grep -c "PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP" /etc/wireguard/wg0.conf) - if [ "$upblocking" -lt 1 ] && [ "$downblocking" -lt 1 ]; then - echo "Isolated peers enabled, adjusting." - - sed -i '/PostUp = iptables -t nat -I POSTROUTING 1 -s/a PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP' /etc/wireguard/wg0.conf - sed -i '/PreDown = iptables -t nat -D POSTROUTING 1 -s/a PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP' /etc/wireguard/wg0.conf - fi - - fi - - # The following section takes care of - if [ "${enable_wg0,,}" = "true" ]; then - echo "Preference for wg0 to be turned on found." - - wg-quick up wg0 - else - echo "Preference for wg0 to be turned off found." - fi -} - -set_envvars() { - echo "Setting relevant variables for operation." - - # If the timezone is different, for example in North-America or Asia. - if [ "${tz}" != "$(cat /etc/timezone)" ]; then - echo "Changing timezone." - - ln -sf /usr/share/zoneinfo/"${tz}" /etc/localtime - echo "${tz}" > /etc/timezone - fi - - # Changing the DNS used for clients and the dashboard itself. - if [ "${global_dns}" != "$(grep "peer_global_dns = " /opt/wireguarddashboard/app/src/wg-dashboard.ini | awk '{print $NF}')" ]; then - echo "Changing default dns." - - #sed -i "s/^DNS = .*/DNS = ${global_dns}/" /etc/wireguard/wg0.conf # Uncomment if you want to have DNS on server-level. - sed -i "s/^peer_global_dns = .*/peer_global_dns = ${global_dns}/" /opt/wireguarddashboard/app/src/wg-dashboard.ini - fi - - # Setting the public IP of the WireGuard Dashboard container host. If not defined, it will trying fetching it using a curl to ifconfig.me. - if [ "${public_ip}" = "0.0.0.0" ]; then - default_ip=$(curl -s ifconfig.me) - echo "Trying to fetch the Public-IP using ifconfig.me: ${default_ip}" - - sed -i "s/^remote_endpoint = .*/remote_endpoint = ${default_ip}/" /opt/wireguarddashboard/app/src/wg-dashboard.ini - elif [ "${public_ip}" != "$(grep "remote_endpoint = " /opt/wireguarddashboard/app/src/wg-dashboard.ini | awk '{print $NF}')" ]; then - echo "Setting the Public-IP using given variable: ${public_ip}" - - sed -i "s/^remote_endpoint = .*/remote_endpoint = ${public_ip}/" /opt/wireguarddashboard/app/src/wg-dashboard.ini - fi -} - -ensure_blocking() { - sleep 1s - echo "Ensuring container continuation." - - # This function checks if the latest error log is created and tails it for docker logs uses. - if find "/opt/wireguarddashboard/app/src/log" -mindepth 1 -maxdepth 1 -type f | read -r; then - latestErrLog=$(find /opt/wireguarddashboard/app/src/log -name "error_*.log" | head -n 1) - latestAccLog=$(find /opt/wireguarddashboard/app/src/log -name "access_*.log" | head -n 1) - tail -f "${latestErrLog}" "${latestAccLog}" - fi - - # Blocking command in case of erroring. So the container does not quit. - sleep infinity -} - -# Execute functions for the WireGuard Dashboard services, then set the environment variables -clean_up -start_core -set_envvars -ensure_blocking \ No newline at end of file diff --git a/docker/requirements.txt b/docker/requirements.txt new file mode 100644 index 0000000..5671fc8 --- /dev/null +++ b/docker/requirements.txt @@ -0,0 +1,8 @@ +#bcrypt +ifcfg +#psutil +pyotp +Flask +flask-cors +icmplib +gunicorn \ No newline at end of file diff --git a/docker/wgd.sh b/docker/wgd.sh new file mode 100644 index 0000000..624e9de --- /dev/null +++ b/docker/wgd.sh @@ -0,0 +1,397 @@ +#!/bin/bash + +# wgd.sh - Copyright(C) 2024 Donald Zou [https://github.com/donaldzou] +# Under Apache-2.0 License +app_name="dashboard.py" +app_official_name="WGDashboard" +venv_python="./venv/bin/python3" +venv_gunicorn="./venv/bin/gunicorn" + +PID_FILE=./gunicorn.pid +environment=$(if [[ $ENVIRONMENT ]]; then echo $ENVIRONMENT; else echo 'develop'; fi) +if [[ $CONFIGURATION_PATH ]]; then + cb_work_dir=$CONFIGURATION_PATH/letsencrypt/work-dir + cb_config_dir=$CONFIGURATION_PATH/letsencrypt/config-dir +else + cb_work_dir=/etc/letsencrypt + cb_config_dir=/var/lib/letsencrypt +fi + +dashes='------------------------------------------------------------' +equals='============================================================' +helpMsg="[WGDashboard] Please check ./log/install.txt for more details. For further assistance, please open a ticket on https://github.com/donaldzou/WGDashboard/issues/new/choose, I'm more than happy to help :)" +help () { + printf "=================================================================================\n" + printf "+ by Donald Zou - https://github.com/donaldzou +\n" + printf "=================================================================================\n" + printf "| Usage: ./wgd.sh