diff --git a/README.md b/README.md index 0bbc8fca..6d54946b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Notable features: - Add direct push of docker images to GitHub Packages - Consolidated `docker-compose.yml` file - Workaround VirtioFS bug when running Docker Desktop for Mac +- stunnel service for TLS wrapping of plaintext services, such as Redis - ... and many others The underlying spirit of this project is to allow "repeatable deployments", and all pull requests in this direction will be merged post-haste. @@ -374,6 +375,10 @@ The process is *NOT* battle-tested, so it is *NOT* to be followed uncritically. docker compose up ``` +## stunnel service + +See [here](/docs/stunnel-guide.md) + ## Troubleshooting - Make sure you run a fairly recent version of Docker and Docker Compose (if in doubt, update following the steps outlined in ) diff --git a/core/Dockerfile b/core/Dockerfile index e8f2c235..a016051a 100644 --- a/core/Dockerfile +++ b/core/Dockerfile @@ -245,6 +245,7 @@ FROM php-base php8.4-gd \ php8.4-fpm \ php8.4-zip \ + stunnel4 \ unzip \ zip \ curl \ @@ -275,6 +276,7 @@ FROM php-base php8.4-fpm \ php8.4-zip \ php8.4-ldap \ + stunnel4 \ libmagic1 \ libldap-common \ librdkafka1 \ diff --git a/core/files/entrypoint.sh b/core/files/entrypoint.sh index 87079687..b77e4011 100755 --- a/core/files/entrypoint.sh +++ b/core/files/entrypoint.sh @@ -89,6 +89,9 @@ export NGINX_X_FORWARDED_FOR=${NGINX_X_FORWARDED_FOR:-false} export NGINX_SET_REAL_IP_FROM=${NGINX_SET_REAL_IP_FROM} export NGINX_CLIENT_MAX_BODY_SIZE=${NGINX_CLIENT_MAX_BODY_SIZE:-50M} +export STUNNEL=${STUNNEL:-false} +export STUNNEL_CONFIG=${STUNNEL_CONFIG} + export SUPERVISOR_HOST=${SUPERVISOR_HOST:-127.0.0.1} export SUPERVISOR_USERNAME=${SUPERVISOR_USERNAME:-supervisor} export SUPERVISOR_PASSWORD=${SUPERVISOR_PASSWORD:-supervisor} diff --git a/core/files/etc/supervisor/conf.d/40-stunnel.conf b/core/files/etc/supervisor/conf.d/40-stunnel.conf new file mode 100644 index 00000000..bc1c9105 --- /dev/null +++ b/core/files/etc/supervisor/conf.d/40-stunnel.conf @@ -0,0 +1,15 @@ +[program:stunnel] +directory=/tmp +command=/usr/bin/stunnel %(ENV_STUNNEL_CONFIG)s +process_name=%(program_name)s_%(process_num)02d +numprocs=1 +autostart=%(ENV_STUNNEL)s +autorestart=true +redirect_stderr=false +stderr_logfile=/var/www/MISP/app/tmp/logs/stunnel-errors.log +stdout_logfile=/var/www/MISP/app/tmp/logs/stunnel.log +stderr_logfile_backups=2 +stderr_logfile_maxbytes=5MB +stdout_logfile_backups=2 +stdout_logfile_maxbytes=5MB +user=root diff --git a/docker-compose.yml b/docker-compose.yml index 64213a2f..b552e7b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -233,6 +233,9 @@ services: - "S3_ENDPOINT=${S3_ENDPOINT}" - "S3_ACCESS_KEY=${S3_ACCESS_KEY}" - "S3_SECRET_KEY=${S3_SECRET_KEY}" + # stunnel settings + - "STUNNEL=${STUNNEL}" + - "STUNNEL_CONFIG=${STUNNEL_CONFIG}" # supervisor settings - "SUPERVISOR_HOST=${SUPERVISOR_HOST}" - "SUPERVISOR_USERNAME=${SUPERVISOR_USERNAME}" diff --git a/docs/examples/stunnel/redis/docker-compose.override.yml b/docs/examples/stunnel/redis/docker-compose.override.yml new file mode 100644 index 00000000..88dc494f --- /dev/null +++ b/docs/examples/stunnel/redis/docker-compose.override.yml @@ -0,0 +1,50 @@ +services: + misp-core: + volumes: + - ./misp_custom:/custom + redis: + command: | + sh -c ' + if [ "$${ENABLE_REDIS_EMPTY_PASSWORD:-false}" = "true" ]; then + exec valkey-server \ + --port 0 \ + --tls-port 6379 \ + --tls-cert-file /custom/server-cert.pem \ + --tls-key-file /custom/server-key.pem \ + --tls-ca-cert-file /custom/ca.pem \ + --tls-auth-clients yes + else + exec valkey-server \ + --port 0 \ + --tls-port 6379 \ + --tls-cert-file /custom/client-cert.pem \ + --tls-key-file /custom/client-key.pem \ + --tls-ca-cert-file /custom/ca.pem \ + --tls-auth-clients yes \ + --requirepass "$${REDIS_PASSWORD:-redispassword}" + fi + ' + healthcheck: + test: | + sh -c ' + if [ "$${ENABLE_REDIS_EMPTY_PASSWORD:-false}" = "true" ]; then + valkey-cli \ + --tls \ + --cert /custom/client-cert.pem \ + --key /custom/client-key.pem \ + --cacert /custom/ca.pem \ + -p $${REDIS_PORT:-6379} \ + ping | grep -q PONG || exit 1 + else + valkey-cli \ + --tls \ + --cert /custom/client-cert.pem \ + --key /custom/client-key.pem \ + --cacert /custom/ca.pem \ + -a "$${REDIS_PASSWORD:-redispassword}" \ + -p $${REDIS_PORT:-6379} \ + ping | grep -q PONG || exit 1 + fi + ' + volumes: + - ./misp_custom/redis_tls:/custom:ro diff --git a/docs/examples/stunnel/redis/misp_custom/redis_tls/gencerts.sh b/docs/examples/stunnel/redis/misp_custom/redis_tls/gencerts.sh new file mode 100644 index 00000000..77d16462 --- /dev/null +++ b/docs/examples/stunnel/redis/misp_custom/redis_tls/gencerts.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# --- CA --- +openssl genrsa -out ca-key.pem 4096 + +openssl req -x509 -new -nodes \ + -key ca-key.pem \ + -sha256 -days 3650 \ + -subj "/C=AU/ST=VIC/L=Melbourne/O=Example/OU=CA/CN=Example-CA" \ + -out ca.pem + + +# --- SERVER CERT --- +openssl genrsa -out server-key.pem 2048 + +openssl req -new \ + -key server-key.pem \ + -subj "/C=AU/ST=VIC/L=Melbourne/O=Example/OU=Server/CN=redis" \ + -addext "subjectAltName=DNS:redis,DNS:localhost,IP:127.0.0.1" \ + -out server.csr + +openssl x509 -req \ + -in server.csr \ + -CA ca.pem \ + -CAkey ca-key.pem \ + -CAcreateserial \ + -sha256 -days 825 \ + -out server-cert.pem \ + -extfile <(printf "subjectAltName=DNS:redis,DNS:localhost,IP:127.0.0.1") + + +# --- CLIENT CERT --- +openssl genrsa -out client-key.pem 2048 + +openssl req -new \ + -key client-key.pem \ + -subj "/C=AU/ST=VIC/L=Melbourne/O=Example/OU=Client/CN=client" \ + -out client.csr + +openssl x509 -req \ + -in client.csr \ + -CA ca.pem \ + -CAkey ca-key.pem \ + -CAcreateserial \ + -sha256 -days 825 \ + -out client-cert.pem + + +# --- PERMS --- +chmod 600 ca-key.pem server-key.pem client-key.pem +chmod 644 ca.pem server-cert.pem client-cert.pem + diff --git a/docs/examples/stunnel/redis/misp_custom/stunnel/stunnel.conf b/docs/examples/stunnel/redis/misp_custom/stunnel/stunnel.conf new file mode 100644 index 00000000..6b926e67 --- /dev/null +++ b/docs/examples/stunnel/redis/misp_custom/stunnel/stunnel.conf @@ -0,0 +1,13 @@ +foreground = yes +pid = /var/run/stunnel.pid +debug = 3 +[elasticache] +client = yes +accept = localhost:6379 +connect = redis:6379 + +cert = /custom/redis_tls/client-cert.pem +key = /custom/redis_tls/client-key.pem +CAfile = /custom/redis_tls/ca.pem +verify = 1 +OCSPrequire = no diff --git a/docs/stunnel-guide.md b/docs/stunnel-guide.md new file mode 100644 index 00000000..2ee9c906 --- /dev/null +++ b/docs/stunnel-guide.md @@ -0,0 +1,100 @@ +# MISP stunnel Guide # + +This guide provides some basic examples of how to use the [stunnel](https://www.stunnel.org/) functionality provided in the misp-docker image. + +## Enabling ## + +`template.env` contains two stunnel related variables: +| Variable | Values | Default | Purpose | +| :------------- | :--------------- | :------ | :------------------------------------------------------------------------- | +| STUNNEL | `true`/`false` | `false` | If true will start the stunnel service on container start, via supervisord | +| STUNNEL_CONFIG | File path string | | Must contain a file path to a stunnel config file | + +If `STUNNEL` is `true` but `STUNNEL_CONFIG` is unset, empty or otherwise does not point to a config file, supervisord will retry starting the service a few times before failing. + +## Configuration ## + +You can find the stunnel configuration documentation [here](https://www.stunnel.org/static/stunnel.html), and general examples [here](https://www.stunnel.org/examples.html). + +## Example: Redis over TLS ## + +This example demonstrates how misp-docker's Redis (Valkey) container might be configured to use TLS, and how to leverage the stunnel functionality to communicate with it over TLS. + +The general idea here is that stunnel will expose a plaintext port for the MISP codebase to talk Redis over, which will then be proxied to the `redis` container's TLS speaking port. Traffic across the containers will be via TLS as a result. You could just as easily point to an AWS Elasticache or other external Redis instance with the benefit of TLS encryption. + +This example largely builds on [this](https://redis.io/blog/stunnel-secure-redis-ssl/) redis.io blog post. + +### Steps: ### + +#### Copy the example files into the root of your misp-docker project #### + +If you have an existing directory named `misp_custom` or a `docker-compose.override.yml` file already, the below will mess with them. You may wish to manually add these things in that case. + +Change into your misp-docker project dir first. + +``` +cp -r docs/examples/stunnel/redis/misp_custom . +cp docs/examples/stunnel/redis/docker-compose.override.yml . +``` + +The `docker-compose.override.yml` file will reference the files within the `misp_custom` directory, tell Redis to use TLS key files for TLS communications, and tell the health check to use it as well. + +#### Roll some certificates #### + +We will do this in the `misp_custom/redis_tls` directory using `gencerts.sh` which were copied from the last step: + +``` +cd misp_custom/redis_tls +./gencerts.sh +cd ../.. +``` + +You should be left with a directory structure that looks like this: + +``` +tree misp_custom +misp_custom/ +├── redis_tls +│   ├── ca-key.pem +│   ├── ca.pem +│   ├── ca.srl +│   ├── client-cert.pem +│   ├── client.csr +│   ├── client-key.pem +│   ├── gencerts.sh +│   ├── server-cert.pem +│   ├── server.csr +│   └── server-key.pem +└── stunnel + └── stunnel.conf + +3 directories, 11 files +``` + +#### Update .env file with necessary values #### + +Edit your .env file so that the following envars are like this: + +``` +REDIS_HOST=localhost +STUNNEL=true +STUNNEL_CONFIG=/custom/stunnel/stunnel.conf +``` + +#### Bring up the compose project #### + +``` +docker compose up -d +``` + +You should now have TLS wrapped Redis, where the client and server are authenticating each other. + +Check the Administration -> Server Settings & Maintenance -> Diagnostics tool for system status. + +### Troubleshooting ### + +Places to look for clues if you run into trouble: + +* stunnel log files will appear in in the `logs` dir as `stunnel.log` and `stunnel-errors.log` +* Redis/valkey log output can be found in the `redis` container +* Supervisord log output can be found in the `misp-core` container diff --git a/template.env b/template.env index 91d45d5c..bbab0e22 100644 --- a/template.env +++ b/template.env @@ -123,9 +123,9 @@ SYNCSERVERS_1_PULL_RULES= # optional and used to set mysql db TLS configuration # MYSQL_TLS=true -# MYSQL_TLS_CA=/custom/files/tls/misp_ca.pem -# MYSQL_TLS_CERT=/custom/files/tls/misp_cert.cert -# MYSQL_TLS_KEY=/custom/files/tls/misp_key.key +# MYSQL_TLS_CA=/custom/files/tls/mysql_ca.pem +# MYSQL_TLS_CERT=/custom/files/tls/mysql_pubcert.cert +# MYSQL_TLS_KEY=/custom/files/tls/mysql_privkey.key # optional and used to set redis # REDIS_HOST= @@ -252,6 +252,10 @@ SYNCSERVERS_1_PULL_RULES= # S3_ACCESS_KEY= # S3_SECRET_KEY= +# stunnel configuration for arbitrary proxying of local plaintext connections to tls endpoints +# STUNNEL=false +# STUNNEL_CONFIG=/custom/files/stunnel/stunnel.conf + ## MISP-Guard # Configure rules in ./guard/config.json. # Requires restart of misp-guard container after changes.