diff --git a/README.md b/README.md index 7ca1b26..1ad2195 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ These tools are not vital to Mistborn itself but are integrated to enhance secur - [Pi-hole](https://pi-hole.net): A DNS server for network-wide ad blocking, etc - [DNScrypt](https://www.dnscrypt.org): prevents DNS spoofing via cryptographic signatures to verify that responses originate from the chosen DNS resolver and haven't been tampered - [Traefik](https://docs.traefik.io): A modern, efficient reverse-proxy +- [Wazuh](https://wazuh.com/): Wazuh is a free, open source and enterprise-ready security monitoring solution for threat detection, integrity monitoring, incident response and compliance. Within Mistborn is a panel to enable and manage these free extra services (off by default), locally hosted in Docker containers: - [Home Assistant](https://www.home-assistant.io): Open source home automation that puts local control and privacy first @@ -82,6 +83,7 @@ Recommended System Specifications: | Default | Bare bones + Cockpit | 2 GB+ | 15 GB | | Low-resource services | Default + Bitwarden, Tor, Syncthing | 4 GB | 20 GB | | High-resource services | Default + Jitsi, Nextcloud, Jellyfin, Rocket.Chat, Home Assistant, OnlyOffice | 6 GB+ | 25 GB+ | +| SIEM | Default + Wazuh + Extras | 16 GB+ | 100 GB+ | Starting from base installation ``` @@ -109,6 +111,18 @@ Mistborn protects your data in a variety of ways: See the [Mistborn Network Security](https://gitlab.com/cyber5k/mistborn/-/wikis/Mistborn-Network-Security) wiki page to see more network diagrams and the network scan results for Mistborn. +# Security Information & Event Management (SIEM) + +![Mistborn Security Center](https://gitlab.com/cyber5k/public/-/raw/master/graphics/home.mistborn_soc.png) + +The Mistborn Security Operations Center provides SIEM services with Wazuh. The Wazuh Manager requires an Open Distro for Elasticsearch backend. When the Mistborn host has >8 GB RAM the provided Elasticsearch backend can be used. Just click "Start Wazuh" on the `Security Center` page and enjoy your Enterprise-grade SIEM. Wazuh agents can be installed on just about any OS and all Wazuh agent traffic is communicated over the Wireguard connections. Instructions for adding endpoint agents can be found within Wazuh itself. + +![Mistborn Security Center: Wazuh Modules](https://gitlab.com/cyber5k/public/-/raw/master/graphics/wazuh_modules.png) + +The Wazuh Kibana plugin leverages the power of Elasticsearch: + +![Mistborn Security Center: Wazuh Dashboard](https://gitlab.com/cyber5k/public/-/raw/master/graphics/wazuh_se_dashboard.png) + # Coppercloud Pihole provides a way to block outgoing DNS requests for given lists of blocked domains. Coppercloud provides a way to block outgoing network calls of all types to given lists of IP addresses (IPv4 only for now). This is especially useful for blocking outgoing telemetry (data and state sharing) to owners of software running on all of your devices. @@ -290,6 +304,7 @@ Mistborn uses the following domains (that can be reached by all Wireguard client | Jitsi | jitsi.mistborn | Off | | Guacamole | guac.mistborn | Off | | RaspAP | raspap.mistborn | Off | +| Wazuh | wazuh.mistborn | Off | # Default Credentials These are the default credentials to use in the services you choose to use: @@ -298,6 +313,7 @@ These are the default credentials to use in the services you choose to use: | ------- | -------- | -------- | | Pihole | | {{default mistborn password}} | | Cockpit | cockpit | {{default mistborn password}} | +| Wazuh | mistborn | {{default mistborn password}} | | Nextcloud | mistborn | {{default mistborn password}} | | Guacamole | mistborn | {{default mistborn password }} | | RaspAP | mistborn | {{default mistborn password}} | @@ -464,6 +480,8 @@ sudo journalctl -xfu Mistborn-guacamole sudo journalctl -xfu Mistborn-rocketchat sudo journalctl -xfu Mistborn-onlyoffice sudo journalctl -xfu Mistborn-tor +sudo journalctl -xfu Mistborn-raspap +sudo journalctl -xfu Mistborn-wazuh ``` ## Troubleshooting Docker @@ -529,8 +547,6 @@ Many features and refinements are in the works at various stages including: - Plugins for Extra Services (enabling third-party development) - Plugin repository - IPv6 support -- Integration with RaspAP to enable managing an Access Point for local network connections -- Internal network scan tool and feedback - Anomaly detection in network traffic # Featured In diff --git a/base.yml b/base.yml index 2b19d53..035a6e8 100644 --- a/base.yml +++ b/base.yml @@ -158,7 +158,7 @@ services: pihole: container_name: mistborn_production_pihole - image: pihole/pihole:latest + image: pihole/pihole:v5.7 env_file: - ./.envs/.production/.pihole ports: diff --git a/extra/elasticsearch.yml b/extra/elasticsearch.yml new file mode 100644 index 0000000..c1a3a70 --- /dev/null +++ b/extra/elasticsearch.yml @@ -0,0 +1,30 @@ +version: '3.7' + +services: + + elasticsearch: + image: amazon/opendistro-for-elasticsearch:1.12.0 + hostname: elasticsearch + restart: unless-stopped + ports: + - "${MISTBORN_BIND_IP}:9200:9200" + environment: + - discovery.type=single-node + - cluster.name=mistborn-cluster + - network.host=0.0.0.0 + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - bootstrap.memory_lock=true + volumes: + - ../../mistborn_volumes/extra/elasticsearch/init/internal_users.yml:/usr/share/elasticsearch/plugins/opendistro_security/securityconfig/internal_users.yml + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + +networks: + default: + external: + name: mistborn_default diff --git a/extra/wazuh.yml b/extra/wazuh.yml new file mode 100644 index 0000000..f7df12a --- /dev/null +++ b/extra/wazuh.yml @@ -0,0 +1,70 @@ +# Wazuh App Copyright (C) 2021 Wazuh Inc. (License GPLv2) +version: '3.7' + +services: + wazuh: + image: wazuh/wazuh-odfe:4.1.2 + hostname: wazuh-manager + restart: unless-stopped + ports: + - "${MISTBORN_BIND_IP}:1514:1514" + - "${MISTBORN_BIND_IP}:1515:1515" + - "${MISTBORN_BIND_IP}:514:514/udp" + - "${MISTBORN_BIND_IP}:55000:55000" + environment: + - FILEBEAT_SSL_VERIFICATION_MODE=none + env_file: + - ../.envs/.production/.wazuh + volumes: + - ossec_api_configuration:/var/ossec/api/configuration + - ossec_etc:/var/ossec/etc + - ossec_logs:/var/ossec/logs + - ossec_queue:/var/ossec/queue + - ossec_var_multigroups:/var/ossec/var/multigroups + - ossec_integrations:/var/ossec/integrations + - ossec_active_response:/var/ossec/active-response/bin + - ossec_agentless:/var/ossec/agentless + - ossec_wodles:/var/ossec/wodles + - filebeat_etc:/etc/filebeat + - filebeat_var:/var/lib/filebeat + + wazuh-kibana: + image: wazuh/wazuh-kibana-odfe:4.1.2 + hostname: wazuh-kibana + restart: unless-stopped + labels: + - "traefik.enable=true" + - "traefik.http.routers.wazuhk-http.rule=Host(`wazuh.mistborn`)" + - "traefik.http.routers.wazuhk-http.entrypoints=web" + - "traefik.http.routers.wazuhk-http.middlewares=mistborn_auth@file" + - "traefik.http.routers.wazuhk-https.rule=Host(`wazuh.mistborn`)" + - "traefik.http.routers.wazuhk-https.entrypoints=websecure" + - "traefik.http.routers.wazuhk-https.middlewares=mistborn_auth@file" + - "traefik.http.routers.wazuhk-https.tls.certresolver=basic" + - "traefik.http.services.wazuhk-service.loadbalancer.server.port=5601" + #ports: + # - "${MISTBORN_BIND_IP}:5601:5601" + environment: + - SERVER_SSL_ENABLED=false + - SERVER_SSL_CERTIFICATE=/usr/share/kibana/config/opendistroforelasticsearch.example.org.cert + - SERVER_SSL_KEY=/usr/share/kibana/config/opendistroforelasticsearch.example.org.key + env_file: + - ../.envs/.production/.wazuh + +volumes: + ossec_api_configuration: + ossec_etc: + ossec_logs: + ossec_queue: + ossec_var_multigroups: + ossec_integrations: + ossec_active_response: + ossec_agentless: + ossec_wodles: + filebeat_etc: + filebeat_var: + +networks: + default: + external: + name: mistborn_default diff --git a/scripts/services/Mistborn-elasticsearch.service b/scripts/services/Mistborn-elasticsearch.service new file mode 100644 index 0000000..63933c7 --- /dev/null +++ b/scripts/services/Mistborn-elasticsearch.service @@ -0,0 +1,22 @@ +[Unit] +Description=Mistborn Elasticsearch Service +Requires=Mistborn-base.service +After=Mistborn-base.service +PartOf=Mistborn-base.service + +[Service] +Restart=always +RestartSec=15 +User=root +Group=docker +PermissionsStartOnly=true +# Shutdown container (if running) when unit is stopped +ExecStartPre=/usr/sbin/sysctl -w vm.max_map_count=262144 +ExecStartPre=/opt/mistborn/scripts/wrappers/mistborn_docker.sh elasticsearch docker-compose -f /opt/mistborn/extra/elasticsearch.yml down +# Start container when unit is started +ExecStart=/opt/mistborn/scripts/wrappers/mistborn_docker.sh elasticsearch docker-compose -f /opt/mistborn/extra/elasticsearch.yml up --build +# Stop container when unit is stopped +ExecStop=/opt/mistborn/scripts/wrappers/mistborn_docker.sh elasticsearch docker-compose -f /opt/mistborn/extra/elasticsearch.yml down + +[Install] +WantedBy=multi-user.target diff --git a/scripts/services/Mistborn-wazuh.service b/scripts/services/Mistborn-wazuh.service new file mode 100644 index 0000000..b4cac82 --- /dev/null +++ b/scripts/services/Mistborn-wazuh.service @@ -0,0 +1,26 @@ +[Unit] +Description=Mistborn Wazuh Service +Requires=Mistborn-elasticsearch.service +After=Mistborn-elasticsearch.service +PartOf=Mistborn-base.service + +[Service] +Restart=always +RestartSec=15 +User=root +Group=docker +PermissionsStartOnly=true +# Shutdown container (if running) when unit is stopped +ExecStartPre=/opt/mistborn/scripts/wrappers/mistborn_docker.sh wazuh docker-compose -f /opt/mistborn/extra/wazuh.yml down +# Start container when unit is started +ExecStart=/opt/mistborn/scripts/wrappers/mistborn_docker.sh wazuh docker-compose -f /opt/mistborn/extra/wazuh.yml up --build +# Agent install +ExecStartPost=/opt/mistborn/scripts/wrappers/mistborn_docker.sh wazuh /opt/mistborn/scripts/services/wazuh/agent.sh +ExecStartPost=-/opt/mistborn/scripts/wrappers/mistborn_docker.sh wazuh /opt/mistborn/scripts/services/wazuh/agent_start.sh +# Stop container when unit is stopped +ExecStop=-/opt/mistborn/scripts/wrappers/mistborn_docker.sh wazuh /opt/mistborn/scripts/services/wazuh/agent_stop.sh +ExecStop=/opt/mistborn/scripts/wrappers/mistborn_docker.sh wazuh docker-compose -f /opt/mistborn/extra/wazuh.yml down + + +[Install] +WantedBy=multi-user.target diff --git a/scripts/services/elasticsearch/files/internal_users.yml b/scripts/services/elasticsearch/files/internal_users.yml new file mode 100644 index 0000000..540eede --- /dev/null +++ b/scripts/services/elasticsearch/files/internal_users.yml @@ -0,0 +1,17 @@ +--- +# This is the internal user database +# The hash value is a bcrypt hash and can be generated with plugin/tools/hash.sh + +_meta: + type: "internalusers" + config_version: 2 + +# Define your internal users here + +mistborn: + hash: "__MISTBORN_HASH__" + reserved: true + backend_roles: + - "admin" + description: "Mistborn user" + diff --git a/scripts/services/elasticsearch/init.sh b/scripts/services/elasticsearch/init.sh new file mode 100755 index 0000000..1b3d21b --- /dev/null +++ b/scripts/services/elasticsearch/init.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +if [[ -f "/opt/mistborn_volumes/extra/elasticsearch/init/internal_users.yml" ]]; then + echo "internal_users.yml exists. Proceeding." + exit 0 +fi + +mkdir -p /opt/mistborn_volumes/extra/elasticsearch/init/ >/dev/null 2>&1 +chmod -R +x /opt/mistborn_volumes/extra/elasticsearch/init/ +cp /opt/mistborn/scripts/services/elasticsearch/files/internal_users.yml /opt/mistborn_volumes/extra/elasticsearch/init/ + +ELASTICSEARCH_MISTBORN_HASHED=$(docker run --rm amazon/opendistro-for-elasticsearch:1.12.0 bash /usr/share/elasticsearch/plugins/opendistro_security/tools/hash.sh -p ${MISTBORN_DEFAULT_PASSWORD} | tr -d '\n') + +sed -i "s|__MISTBORN_HASH__|${ELASTICSEARCH_MISTBORN_HASHED}|" /opt/mistborn_volumes/extra/elasticsearch/init/internal_users.yml diff --git a/scripts/services/wazuh/agent.sh b/scripts/services/wazuh/agent.sh new file mode 100755 index 0000000..222d664 --- /dev/null +++ b/scripts/services/wazuh/agent.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# detect if already installed +if dpkg -s wazuh-agent &> /dev/null; then + echo "Wazuh agent already installed" + exit 0 +fi + +# prepare repo +echo "Adding Wazuh Repository" +curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | apt-key add - +echo "deb https://packages.wazuh.com/4.x/apt/ stable main" | tee -a /etc/apt/sources.list.d/wazuh.list + +apt-get update + +# wait for service to be listening +while ! nc -z 10.2.3.1 55000; do + WAIT_TIME=10 + echo "Waiting ${WAIT_TIME} seconds for Wazuh API..." + sleep ${WAIT_TIME} +done + +# install +echo "Installing Wazuh agent" +WAZUH_MANAGER="10.2.3.1" apt-get install wazuh-agent + diff --git a/scripts/services/wazuh/agent_start.sh b/scripts/services/wazuh/agent_start.sh new file mode 100755 index 0000000..5996970 --- /dev/null +++ b/scripts/services/wazuh/agent_start.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +systemctl start wazuh-agent +systemctl enable wazuh-agent diff --git a/scripts/services/wazuh/agent_stop.sh b/scripts/services/wazuh/agent_stop.sh new file mode 100755 index 0000000..19cf173 --- /dev/null +++ b/scripts/services/wazuh/agent_stop.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +systemctl stop wazuh-agent +systemctl disable wazuh-agent diff --git a/scripts/subinstallers/extra/elasticsearch.sh b/scripts/subinstallers/extra/elasticsearch.sh new file mode 100755 index 0000000..e7547ae --- /dev/null +++ b/scripts/subinstallers/extra/elasticsearch.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Elasticsearch +ELASTICSEARCH_PROD_FILE="$1" +echo "MISTBORN_DEFAULT_PASSWORD=$MISTBORN_DEFAULT_PASSWORD" >> $ELASTICSEARCH_PROD_FILE +chmod 600 $ELASTICSEARCH_PROD_FILE diff --git a/scripts/subinstallers/extra/wazuh.sh b/scripts/subinstallers/extra/wazuh.sh new file mode 100755 index 0000000..1a7b6f7 --- /dev/null +++ b/scripts/subinstallers/extra/wazuh.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Wazuh +WAZUH_PROD_FILE="$1" +echo "ELASTIC_USERNAME=mistborn" > $WAZUH_PROD_FILE +echo "ELASTIC_PASSWORD=$MISTBORN_DEFAULT_PASSWORD" >> $WAZUH_PROD_FILE + +echo "ELASTICSEARCH_USERNAME=mistborn" >> $WAZUH_PROD_FILE +echo "ELASTICSEARCH_PASSWORD=$MISTBORN_DEFAULT_PASSWORD" >> $WAZUH_PROD_FILE + +# kibana odfe +# kibana-odfe/config/wazuh_app_config.sh +# https://wazuh +echo "WAZUH_API_URL=https://10.2.3.1" >> $WAZUH_PROD_FILE +echo "API_PORT=55000" >> $WAZUH_PROD_FILE +echo "API_USERNAME=wazuh-wui" >> $WAZUH_PROD_FILE + +#API_PASSWORD=$(python3 -c "import secrets; import string; print(f''.join([secrets.choice(string.ascii_letters+string.digits) for x in range(32)]))") + +API_PASSWORD_PYTHON=$(cat << EOF + +import secrets +import random +import string + +random_pass = ([secrets.choice("@$!%*?-_"), + secrets.choice(string.digits), + secrets.choice(string.ascii_lowercase), + secrets.choice(string.ascii_uppercase), + ] + + [secrets.choice(string.ascii_lowercase + + string.ascii_uppercase + + "@$!%*?-_" + + string.digits) for i in range(12)]) + +random.shuffle(random_pass) +random_pass = ''.join(random_pass) +print(random_pass) + +EOF +) + +API_PASSWORD=$(python3 -c "${API_PASSWORD_PYTHON}") + +echo "API_PASSWORD=${API_PASSWORD}" >> $WAZUH_PROD_FILE + +# kibana-odfe/config/entrypoint.sh: +# https://elasticsearch:9200 +echo "ELASTICSEARCH_URL=https://10.2.3.1:9200" >> $WAZUH_PROD_FILE + + +cat >> ${WAZUH_PROD_FILE}<< EOF + +PATTERN="wazuh-alerts-*" + +CHECKS_PATTERN=true +CHECKS_TEMPLATE=true +CHECKS_API=true +CHECKS_SETUP=true + +EXTENSIONS_PCI=true +EXTENSIONS_GDPR=true +EXTENSIONS_HIPAA=true +EXTENSIONS_NIST=true +EXTENSIONS_TSC=true +EXTENSIONS_AUDIT=true +EXTENSIONS_OSCAP=false +EXTENSIONS_CISCAT=false +EXTENSIONS_AWS=false +EXTENSIONS_GCP=false +EXTENSIONS_VIRUSTOTAL=true +EXTENSIONS_OSQUERY=true +EXTENSIONS_DOCKER=true + +APP_TIMEOUT=20000 + +API_SELECTOR=true +IP_SELECTOR=true +IP_IGNORE="[]" + +WAZUH_MONITORING_ENABLED=true +WAZUH_MONITORING_FREQUENCY=900 +WAZUH_MONITORING_SHARDS=2 +WAZUH_MONITORING_REPLICAS=0 + +ADMIN_PRIVILEGES=true + +EOF + +echo "MISTBORN_DEFAULT_PASSWORD=$MISTBORN_DEFAULT_PASSWORD" >> $WAZUH_PROD_FILE + +chmod 600 $WAZUH_PROD_FILE diff --git a/scripts/wrappers/mistborn_docker.sh b/scripts/wrappers/mistborn_docker.sh index bb33894..a5fa403 100755 --- a/scripts/wrappers/mistborn_docker.sh +++ b/scripts/wrappers/mistborn_docker.sh @@ -2,38 +2,43 @@ set -e -SERVICE="$1" +MISTBORN_HOME="/opt/mistborn" + +SERVICES="$1" shift -export MISTBORN_HOME="/opt/mistborn" -export MISTBORN_SERVICE_FILE=${MISTBORN_HOME}/.envs/.production/.${SERVICE} -export MISTBORN_SERVICE_INIT=${MISTBORN_HOME}/scripts/services/${SERVICE}/init.sh - -# check and create file if needed -${MISTBORN_HOME}/scripts/env/check_env_file.sh ${SERVICE} - -# read in variables -set -a -source ${MISTBORN_HOME}/.env - -if [[ -f "${MISTBORN_SERVICE_FILE}" ]]; then - echo "Loading service variables" - source ${MISTBORN_SERVICE_FILE} -else - echo "No service variables to load. Proceeding." -fi -set +a - -# init script -if [[ -f "${MISTBORN_SERVICE_INIT}" ]]; then - echo "Running init script" - ${MISTBORN_SERVICE_INIT} -else - echo "No init script. Proceeding." -fi +IFS=',' +read -ra SERVICES_ARRAY <<< "${SERVICES}" +for SERVICE in "${SERVICES_ARRAY[@]}"; do + MISTBORN_SERVICE_FILE=${MISTBORN_HOME}/.envs/.production/.${SERVICE} + MISTBORN_SERVICE_INIT=${MISTBORN_HOME}/scripts/services/${SERVICE}/init.sh + + # check and create file if needed + ${MISTBORN_HOME}/scripts/env/check_env_file.sh ${SERVICE} + + # read in variables + set -a + source ${MISTBORN_HOME}/.env + + if [[ -f "${MISTBORN_SERVICE_FILE}" ]]; then + echo "Loading service variables" + source ${MISTBORN_SERVICE_FILE} + else + echo "No service variables to load. Proceeding." + fi + set +a + + # init script + if [[ -f "${MISTBORN_SERVICE_INIT}" ]]; then + echo "Running init script" + ${MISTBORN_SERVICE_INIT} + else + echo "No init script. Proceeding." + fi +done # ensure base is up and listening -echo "Waiting for Mistborn-base to finish starting up..." +echo "Checking that Mistborn-base has finished starting up..." while ! nc -z 10.2.3.1 5000; do WAIT_TIME=$((5 + $RANDOM % 15))