diff --git a/.github/workflows/pick-to-staging.yml b/.github/workflows/pick-to-staging.yml index 7191ed4..5cdab2b 100644 --- a/.github/workflows/pick-to-staging.yml +++ b/.github/workflows/pick-to-staging.yml @@ -63,4 +63,3 @@ jobs: reviewers: ${{ github.event.pull_request.user.login }} assignees: ${{ github.event.pull_request.user.login }} labels: picked-to-staging - milestone: 4 diff --git a/Dockerfile b/Dockerfile index 461a141..126a006 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,61 @@ -FROM caddy:2.3.0-alpine +ARG CONTEXT=prod -LABEL org.opencontainers.image.title="OpenSlides Proxy" -LABEL org.opencontainers.image.description="The proxy is the entrypoint for traffic going into an OpenSlides instance." -LABEL org.opencontainers.image.licenses="MIT" -LABEL org.opencontainers.image.source="https://github.com/OpenSlides/OpenSlides/tree/main/proxy" +FROM traefik:3.6.0 AS base + +## Setup +ARG CONTEXT +WORKDIR /app +ENV APP_CONTEXT=${CONTEXT} -RUN apk update && apk add --no-cache jq gettext +# curl for healthcheck, gettext for templating (envsubst) +RUN apk add --no-cache curl gettext -COPY caddy_base.json /caddy_base.json -COPY entrypoint /entrypoint +# Copy configuration files +COPY entrypoint.sh /entrypoint.sh COPY certs /certs +COPY services /services +COPY templates /templates + +# Create dynamic config directory and make entrypoint executable +RUN mkdir -p /etc/traefik/dynamic +RUN chmod +x /entrypoint.sh + +# External Information +LABEL org.opencontainers.image.title="OpenSlides Traefik Proxy" +LABEL org.opencontainers.image.description="The Traefik proxy is the entrypoint for traffic going into an OpenSlides instance." +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.source="https://github.com/OpenSlides/OpenSlides/tree/main/openslides-proxy" + +# Health check +HEALTHCHECK --interval=30s --timeout=3s \ + CMD curl -f http://localhost:8080/ping + +# Command +ENTRYPOINT ["/entrypoint.sh"] +COPY ./dev/command.sh ./ +RUN chmod +x command.sh +CMD ["./command.sh"] + + +# Development Image +FROM base AS dev + +ENV ENABLE_LOCAL_HTTPS=1 +ENV ENABLE_DASHBOARD=1 +ENV TRAEFIK_LOG_LEVEL=DEBUG + + +# Testing Image +FROM base AS tests + + +# Production Image +FROM base AS prod + +# Add appuser for security +RUN adduser -S -D -H appuser +RUN chown -R appuser /app/ && \ + chown -R appuser /etc/traefik/ && \ + chown appuser /entrypoint.sh -ENTRYPOINT ["/entrypoint"] -CMD ["caddy", "run", "--config", "/etc/caddy/config.json"] +USER appuser diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index c9ec9d7..0000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,12 +0,0 @@ -FROM caddy:2.3.0-alpine - -RUN apk update && apk add --no-cache jq gettext - -COPY caddy_base.json /caddy_base.json -COPY entrypoint /entrypoint -COPY certs /certs - -ENV ENABLE_LOCAL_HTTPS=1 - -ENTRYPOINT ["/entrypoint"] -CMD ["caddy", "run", "--config", "/etc/caddy/config.json"] diff --git a/Makefile b/Makefile index 519433b..0b7910e 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,21 @@ +override SERVICE=proxy + +# Build images for different contexts + +build-prod: + docker build ./ $(ARGS) --tag "openslides-$(SERVICE)" --build-arg CONTEXT="prod" --target "prod" + build-dev: ./make-localhost-cert.sh - docker build -t openslides-proxy-dev -f Dockerfile.dev . + docker build ./ $(ARGS) --tag "openslides-$(SERVICE)-dev" --build-arg CONTEXT="dev" --target "dev" + +build-tests: + docker build ./ $(ARGS) --tag "openslides-$(SERVICE)-tests" --build-arg CONTEXT="tests" --target "tests" + +# Tests + +run-tests: + bash dev/run-tests.sh + +lint: + bash dev/run-lint.sh -l diff --git a/README.md b/README.md index 6ffb57d..ca454c4 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,39 @@ -# OpenSlides Proxy - -The proxy - based on [caddy](https://hub.docker.com/_/caddy) - is the entrypoint -for traffic going into an OpenSlides instance and hides all the services needed -for production behind a single port. On the docker container this will be port -8000 . An arbitrary port from the host can then be forwarded to that (e.g. -443->8000). - -## HTTPS - -It is possible to make use of caddy's automatic https feature in order to not -having to manually generate TLS certificates. -Set `ENABLE_AUTO_HTTPS=1` and `EXTERNAL_ADDRESS=openslides.example.com` to -activate it. Caddy will then retrieve a letsencrypt certificate for that -domain. -For testing a setup e.g. -`ACME_ENDPOINT=https://acme-staging-v02.api.letsencrypt.org/directory` can also -be set to avoid hitting rate limits. -Importantly, port 80 on the host must be forwarded to port 8001 on which caddy -will answer the ACME-challenge during certificate retrieval. - -Alternatively a locally generated certificate can be used by setting -`ENABLE_LOCAL_HTTPS=1 HTTPS_CERT_FILE=path/to/crt HTTPS_CERT_FILE=path/to/key` -and providing cert and key files at the specified location. This is mostly for -dev and testing setups and is not useful for a public domain as the cert is not -issued by a trusted CA and therefore not trusted by browsers. If set, this -overrules `ENABLE_AUTO_HTTPS`. +# OpenSlides Traefik Proxy Service + +The OpenSlides Traefik proxy service is a reverse proxy based on [Traefik](https://traefik.io/) that +routes all external traffic to the appropriate OpenSlides services. + +## Overview + +This service: + +- Can provide HTTPS termination with self-signed certificates for development + or with certs retrieved via ACME protocol (e.g. lets encrypt) for production +- Routes requests to appropriate microservices based on URL paths +- Handles WebSocket connections for real-time features +- Supports gRPC communication for the manage service + +## Configuration + +The proxy service is configured through: + +- `traefik.yml` - Static/install configuration +- `dynamic.yml` - Dynamic/routing configuration +- `entrypoint.sh` - Both yml config files are generated here during container startup. + - -> Environment variables are taken into account affecting the final `.yml` configuration, see below. + +### Environment Variables + +- `ENABLE_LOCAL_HTTPS` - Enable HTTPS with local certificates (default: 1 in dev image) +- `TRAEFIK_LOG_LEVEL` - Log level (default: INFO, DEBUG in dev image) +- `ENABLE_DASHBOARD` - Enable traefik web-based dashboard, also sets `debug: true` for now +- `ENABLE_LOCAL_HTTPS` - Enable TLS using certs provided through `HTTPS_*_FILE`. Can be self-signed (used in dev by default) or manually generated/trusted. +- `ENABLE_AUTO_HTTPS` - Enable cert retrieval through ACME. Depends on further variables. (Overruled by `ENABLE_LOCAL_HTTPS` if both are set) + - `EXTERNAL_ADDRESS` - domain for which to retrieve cert + - `ACME_ENDPOINT` - when unset will fallback to traefiks default value for `acme.caServer: https://acme-v02.api.letsencrypt.org/directory` + - `ACME_EMAIL` - Email Address sent to acme endpoint during cert retrieval +- `*_HOST` and `*_PORT` - endpoints (container (host-)names) of OpenSlides microservices. Defaults should be fine in most cases. + +## License + +This service is part of OpenSlides and licensed under the MIT license. diff --git a/caddy_base.json b/caddy_base.json deleted file mode 100644 index 9c2c18c..0000000 --- a/caddy_base.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "apps": { - "tls": { - "automation": { - "policies": [ - { - "issuers": [ - { - "module": "acme", - "challenges": { - "tls-alpn": { - "disabled": true - } - } - } - ] - } - ] - } - }, - "http": { - "servers": { - "srv0": { - "listen": [":8000"], - "allow_h2c": true, - "routes": [ - { - "handle": [ - { - "flush_interval": -1, - "handler": "reverse_proxy", - "upstreams": [ - { - "dial": "$AUTOUPDATE_HOST:$AUTOUPDATE_PORT" - } - ] - } - ], - "match": [ - { - "path": ["/system/autoupdate*"] - } - ] - }, - { - "handle": [ - { - "handler": "reverse_proxy", - "upstreams": [ - { - "dial": "$PRESENTER_HOST:$PRESENTER_PORT" - } - ] - } - ], - "match": [ - { - "path": ["/system/presenter*"] - } - ] - }, - { - "handle": [ - { - "handler": "reverse_proxy", - "upstreams": [ - { - "dial": "$SEARCH_HOST:$SEARCH_PORT" - } - ] - } - ], - "match": [ - { - "path": ["/system/search*"] - } - ] - }, - { - "handle": [ - { - "handler": "reverse_proxy", - "upstreams": [ - { - "dial": "$ACTION_HOST:$ACTION_PORT" - } - ] - } - ], - "match": [ - { - "path": ["/system/action*"] - } - ] - }, - { - "handle": [ - { - "handler": "reverse_proxy", - "upstreams": [ - { - "dial": "$MEDIA_HOST:$MEDIA_PORT" - } - ] - } - ], - "match": [ - { - "path": ["/system/media*"] - } - ] - }, - { - "handle": [ - { - "handler": "reverse_proxy", - "upstreams": [ - { - "dial": "$AUTH_HOST:$AUTH_PORT" - } - ] - } - ], - "match": [ - { - "path": ["/system/auth*", "/system/saml*"] - } - ] - }, - { - "handle": [ - { - "flush_interval": -1, - "handler": "reverse_proxy", - "upstreams": [ - { - "dial": "$ICC_HOST:$ICC_PORT" - } - ] - } - ], - "match": [ - { - "path": ["/system/icc*"] - } - ] - }, - { - "handle": [ - { - "handler": "reverse_proxy", - "upstreams": [ - { - "dial": "$VOTE_HOST:$VOTE_PORT" - } - ] - } - ], - "match": [ - { - "path": ["/system/vote*"] - } - ] - }, - { - "handle": [ - { - "handler": "reverse_proxy", - "flush_interval": -1, - "transport": { - "protocol": "http", - "versions": ["2", "h2c"] - }, - "upstreams": [ - { - "dial": "$MANAGE_HOST:$MANAGE_PORT" - } - ] - } - ], - "match": [ - { - "header": { - "Content-Type": ["application/grpc"] - } - } - ] - }, - { - "handle": [ - { - "body": "Misdirected Request", - "close": true, - "handler": "static_response", - "status_code": 421 - } - ], - "match": [ - { - "not": [ - { - "header": { - "Host": [] - } - } - ] - } - ] - }, - { - "handle": [ - { - "handler": "reverse_proxy", - "upstreams": [ - { - "dial": "$CLIENT_HOST:$CLIENT_PORT" - } - ] - } - ] - } - ], - "automatic_https": { - "disable": true - } - } - } - } - } -} diff --git a/dev/command.sh b/dev/command.sh new file mode 100644 index 0000000..33d0162 --- /dev/null +++ b/dev/command.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +if [ "$APP_CONTEXT" = "dev" ]; then + echo "Starting Traefik in development mode..." + exec traefik +fi + +if [ "$APP_CONTEXT" = "prod" ]; then + echo "Starting Traefik in production mode..." + exec traefik +fi + +if [ "$APP_CONTEXT" = "tests" ]; then + echo "Starting Traefik in test mode..." + exec traefik +fi diff --git a/dev/run-lint.sh b/dev/run-lint.sh new file mode 100644 index 0000000..99dcf00 --- /dev/null +++ b/dev/run-lint.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "########################################################################" +echo "####################### Proxy has no linters ###########################" +echo "########################################################################" diff --git a/dev/run-tests.sh b/dev/run-tests.sh new file mode 100644 index 0000000..43060af --- /dev/null +++ b/dev/run-tests.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +echo "########################################################################" +echo "###################### Proxy has no tests ##############################" +echo "########################################################################" + +# Setup +LOCAL_PWD=$(dirname "$0") + +# Linters +bash "$LOCAL_PWD"/run-lint.sh diff --git a/entrypoint b/entrypoint deleted file mode 100755 index 32d9b25..0000000 --- a/entrypoint +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh - -set -e - -config=/etc/caddy/config.json -base=/caddy_base.json -HTTPS_CERT_FILE="${HTTPS_CERT_FILE:-/certs/cert.pem}" -HTTPS_KEY_FILE="${HTTPS_KEY_FILE:-/certs/key.pem}" - -cp $base $config - -# set defaults in base -ACTION_HOST="${ACTION_HOST:-backend}" ACTION_PORT="${ACTION_PORT:-9002}" \ -PRESENTER_HOST="${PRESENTER_HOST:-backend}" PRESENTER_PORT="${PRESENTER_PORT:-9003}" \ -AUTOUPDATE_HOST="${AUTOUPDATE_HOST:-autoupdate}" AUTOUPDATE_PORT="${AUTOUPDATE_PORT:-9012}" \ -ICC_HOST="${ICC_HOST:-icc}" ICC_PORT="${ICC_PORT:-9007}" \ -AUTH_HOST="${AUTH_HOST:-auth}" AUTH_PORT="${AUTH_PORT:-9004}" \ -SEARCH_HOST="${SEARCH_HOST:-search}" SEARCH_PORT="${SEARCH_PORT:-9050}" \ -MEDIA_HOST="${MEDIA_HOST:-media}" MEDIA_PORT="${MEDIA_PORT:-9006}" \ -MANAGE_HOST="${MANAGE_HOST:-manage}" MANAGE_PORT="${MANAGE_PORT:-9008}" \ -CLIENT_HOST="${CLIENT_HOST:-client}" CLIENT_PORT="${CLIENT_PORT:-9001}" \ -VOTE_HOST="${VOTE_HOST:-vote}" VOTE_PORT="${VOTE_PORT:-9013}" \ -envsubst < "$config" > "$config.out" && mv -f "$config.out" "$config" - -jq_write() { - filter=$1 - jq "$filter" "$config" > "$config.out" && mv -f "$config.out" "$config" -} - -### HTTPS ### -if [ -n "$ENABLE_LOCAL_HTTPS" ]; then - [ -f "$HTTPS_CERT_FILE" ] && [ -f "$HTTPS_KEY_FILE" ] || { - echo "ERROR: no local cert-files provided. Did you run make-localhost-cert.sh?" - exit 1 - } - jq_write ".apps.http.servers.srv0.tls_connection_policies = [{ certificate_selection: { any_tag: [ \"cert0\" ] }}]" - jq_write ".apps.tls = { certificates: { load_files: [{ certificate: \"$HTTPS_CERT_FILE\", key: \"$HTTPS_KEY_FILE\", tags: [ \"cert0\" ] }] }}" -else - if [ -n "$ENABLE_AUTO_HTTPS" ]; then - if [ -n "$EXTERNAL_ADDRESS" ]; then - echo "INFO: For the automatic https to work ports 443 and 80 of the host must be - directed to 8000 and 8001 of this container" - jq_write "del(.apps.http.servers.srv0.automatic_https)" # disabled in base - jq_write ".apps.http.https_port = 8000" - jq_write ".apps.http.http_port = 8001" - jq_write ".apps.http.servers.srv0.routes[-1].match = [{ host: [\"$EXTERNAL_ADDRESS\"]}]" - - if [ -n "$ACME_ENDPOINT" ]; then - jq_write ".apps.tls.automation.policies[0].issuers[0].ca = \"${ACME_ENDPOINT}\"" - fi - else - echo "ERROR: EXTERNAL_ADDRESS needed for automatic HTTPS / cert generation" - exit 1 - fi - fi -fi - -### ALLOWED HOSTS ### -if [ -n "$ALLOWED_HOSTS" ]; then - for host in $ALLOWED_HOSTS; do - jq_write ".apps.http.servers.srv0.routes[-2].match[0].not[0].header.Host += [\"$host\"]" - done -else - jq_write "del(.apps.http.servers.srv0.routes[-2])" -fi - -exec "$@" diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..4e20aa7 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,177 @@ +#!/bin/sh +# Co-authored-by: Claude + +set -ae + +# Define variables and set defaults where applicable +TRAEFIK_CONFIG="${TRAEFIK_CONFIG:-/etc/traefik/traefik.yml}" +TRAEFIK_LOG_LEVEL="${TRAEFIK_LOG_LEVEL:-INFO}" +ENABLE_DASHBOARD="${ENABLE_DASHBOARD:-}" +DYNAMIC_DIR="${DYNAMIC_DIR:-/etc/traefik/dynamic}" +DYNAMIC_CONFIG="${DYNAMIC_DIR}/dynamic.yml" +SERVICES_DIR="${SERVICES_DIR:-/services}" +ENABLE_LOCAL_HTTPS="${ENABLE_LOCAL_HTTPS:-}" +HTTPS_CERT_FILE="${HTTPS_CERT_FILE:-/certs/cert.pem}" +HTTPS_KEY_FILE="${HTTPS_KEY_FILE:-/certs/key.pem}" +ENABLE_AUTO_HTTPS="${ENABLE_AUTO_HTTPS:-}" +EXTERNAL_ADDRESS="${EXTERNAL_ADDRESS:-openslides.example.com}" +ACME_ENDPOINT="${ACME_ENDPOINT:-}" +ACME_EMAIL="${ACME_EMAIL:-}" + +# Set default values for service endpoints +ACTION_HOST="${ACTION_HOST:-backend}" +ACTION_PORT="${ACTION_PORT:-9002}" +PRESENTER_HOST="${PRESENTER_HOST:-backend}" +PRESENTER_PORT="${PRESENTER_PORT:-9003}" +AUTOUPDATE_HOST="${AUTOUPDATE_HOST:-autoupdate}" +AUTOUPDATE_PORT="${AUTOUPDATE_PORT:-9012}" +ICC_HOST="${ICC_HOST:-icc}" +ICC_PORT="${ICC_PORT:-9007}" +AUTH_HOST="${AUTH_HOST:-auth}" +AUTH_PORT="${AUTH_PORT:-9004}" +SEARCH_HOST="${SEARCH_HOST:-search}" +SEARCH_PORT="${SEARCH_PORT:-9050}" +MEDIA_HOST="${MEDIA_HOST:-media}" +MEDIA_PORT="${MEDIA_PORT:-9006}" +MANAGE_HOST="${MANAGE_HOST:-manage}" +MANAGE_PORT="${MANAGE_PORT:-9008}" +VOTE_HOST="${VOTE_HOST:-vote}" +VOTE_PORT="${VOTE_PORT:-9013}" +CLIENT_HOST="${CLIENT_HOST:-client}" +CLIENT_PORT="${CLIENT_PORT:-9001}" + + +# ================================= +# = Build static / install config = +# ================================= + +# Generate base config from template +envsubst < /templates/traefik.yml > "$TRAEFIK_CONFIG" + +# Add dashboard if enabled +if [ -n "$ENABLE_DASHBOARD" ]; then + echo "Enabling dashboard. 'debug: true' for now. NOT FOR PRODUCTION" + cat >> "$TRAEFIK_CONFIG" << 'EOF' + +api: + dashboard: true + debug: true +EOF +fi + +# Add entryPoints in accordance to HTTPS related variables +cat >> "$TRAEFIK_CONFIG" << 'EOF' +entryPoints: + main: + address: ":8000" + http: +EOF + +if [ -n "$ENABLE_LOCAL_HTTPS" ]; then + # Define tls property, which will cause all routers to terminate TLS and + # foward decrypted traffic. + cat >> "$TRAEFIK_CONFIG" << 'EOF' + tls: {} +EOF +elif [ -n "$ENABLE_AUTO_HTTPS" ]; then + # Also needs tls property, but with additional information for cert retrieval + cat >> "$TRAEFIK_CONFIG" << EOF + tls: + domains: + - main: ${EXTERNAL_ADDRESS} + certResolver: acmeResolver +EOF + # Additionally a plain HTTP endpoint to answer ACME challenges on must be + # configured + cat >> "$TRAEFIK_CONFIG" << 'EOF' + + acme: + address: ":8001" +EOF + # Add the certificates resolver providing information for automatic ACME + # based cert retrieval. + cat >> "$TRAEFIK_CONFIG" << EOF + +certificatesResolvers: + acmeResolver: + acme: + email: ${ACME_EMAIL} + storage: acme.json + httpChallenge: + entryPoint: acme +EOF + if [ -n "$ACME_ENDPOINT" ]; then + cat >> "$TRAEFIK_CONFIG" << EOF + caServer: ${ACME_ENDPOINT} +EOF + fi + + echo "traefik was configured to automatically retrieve a TLS certificates via acme." + echo "Make sure incoming challange requests (to HOST:80/.well-known/acme-challenge/) reach this container on port 8001" + echo "In most cases forwarding the hosts port 80 to containers port 8001 is enough." +fi + + +# ================================== +# = Build dynamic / routing config = +# ================================== + +# Start with empty file +echo "" > "$DYNAMIC_CONFIG" + +if [ -n "$ENABLE_LOCAL_HTTPS" ]; then + if [ -f "$HTTPS_CERT_FILE" ] && [ -f "$HTTPS_KEY_FILE" ]; then + envsubst < /templates/tls.yml >> "$DYNAMIC_CONFIG" + else + echo "ERROR: no local cert-files provided. Did you run make-localhost-cert.sh?" + exit 1 + fi +fi + +# First build SERVICES list (space separated) based on files present in +# services directory +SERVICES= +for service_file in $SERVICES_DIR/*.service; do + service=$(basename $service_file .service) + service_upper=$(echo "$service" | tr '[:lower:]' '[:upper:]') + host_var="${service_upper}_HOST" + + if [[ ! -f "$SERVICES_DIR/$service.service" ]] || [[ ! -f "$SERVICES_DIR/$service.router" ]]; then + echo "Skipping, config incomplete: $service" + continue + fi + + if eval [[ -n "\$${host_var}" ]]; then + eval "echo \"Adding config: $service (host: \$${host_var})\"" >&2 + SERVICES="$SERVICES $service" + else + echo "Skipping, disabled in environment: $service" + fi +done + +# Write to config file +cat >> "$DYNAMIC_CONFIG" << 'EOF' + +http: + routers: +EOF + +# Concatenate all enabled .router files +for service in $SERVICES; do + envsubst < "$SERVICES_DIR/${service}.router" >> "$DYNAMIC_CONFIG" +done + +# Add services section +cat >> "$DYNAMIC_CONFIG" << 'EOF' + + services: +EOF + +# Concatenate all enabled .service files +for service in $SERVICES; do + envsubst < "$SERVICES_DIR/${service}.service" >> "$DYNAMIC_CONFIG" +done + + +# Finally start CMD +exec "$@" diff --git a/make-localhost-cert.sh b/make-localhost-cert.sh index a49735f..54a74e3 100755 --- a/make-localhost-cert.sh +++ b/make-localhost-cert.sh @@ -3,20 +3,55 @@ set -e cd "$(dirname "$0")" +# Ensure certs directory exists +mkdir -p certs + if [[ -f "certs/key.pem" ]] || [[ -f "certs/cert.pem" ]]; then echo "Certificate already exists." exit 0 fi -if ! type 2>&1 >/dev/null openssl ; then - echo >&2 "Error: openssl not found!" +echo "Creating certificates..." + +# Check if mkcert is available +if type mkcert >/dev/null 2>&1; then + echo "Using mkcert to generate trusted certificates..." + + # Install local CA if not already installed + mkcert -install >/dev/null 2>&1 || true + + # Generate certificate for localhost and common local addresses + mkcert -cert-file certs/cert.pem -key-file certs/key.pem \ + localhost 127.0.0.1 ::1 \ + "*.localhost" \ + "localhost.localdomain" \ + "*.localhost.localdomain" + + echo "Certificates created with mkcert." + echo "If the certs were generated on a different machine than the one running the" + echo "browser to access the endpoint, you will first have to copy mkcerts local" + echo "rootCA there and add it to the trust store." + +elif type openssl >/dev/null 2>&1; then + echo "mkcert not found, falling back to openssl..." + echo "You will need to accept a security exception for the" + echo "generated certificate in your browser manually." + + openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \ + -subj "/C=DE/O=Selfsigned Test/CN=localhost" \ + -keyout certs/key.pem -out certs/cert.pem + + echo "Self-signed certificate created with openssl" + +else + echo >&2 "Error: Neither mkcert nor openssl found!" + echo >&2 "Please install either mkcert (recommended) or openssl." + echo >&2 "" + echo >&2 "To install mkcert:" + echo >&2 " - Linux: Check https://github.com/FiloSottile/mkcert#installation" + echo >&2 " - macOS: brew install mkcert" + echo >&2 " - Windows: choco install mkcert or scoop install mkcert" exit 1 fi -echo "Creating certificates..." -echo "You will need to accept an security exception for the" -echo "generated certificate in your browser manually." -openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \ - -subj "/C=DE/O=Selfsigned Test/CN=localhost" \ - -keyout certs/key.pem -out certs/cert.pem echo "done" diff --git a/services/action.router b/services/action.router new file mode 100644 index 0000000..8cdc6b6 --- /dev/null +++ b/services/action.router @@ -0,0 +1,5 @@ + action: + rule: "PathPrefix(`/system/action`)" + service: action + entryPoints: + - main diff --git a/services/action.service b/services/action.service new file mode 100644 index 0000000..fc9bbeb --- /dev/null +++ b/services/action.service @@ -0,0 +1,6 @@ + action: + loadBalancer: + servers: + - url: "http://${ACTION_HOST}:${ACTION_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/services/auth.router b/services/auth.router new file mode 100644 index 0000000..0282964 --- /dev/null +++ b/services/auth.router @@ -0,0 +1,5 @@ + auth: + rule: "PathPrefix(`/system/auth`) || PathPrefix(`/system/saml`)" + service: auth + entryPoints: + - main diff --git a/services/auth.service b/services/auth.service new file mode 100644 index 0000000..6f9cda6 --- /dev/null +++ b/services/auth.service @@ -0,0 +1,6 @@ + auth: + loadBalancer: + servers: + - url: "http://${AUTH_HOST}:${AUTH_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/services/autoupdate.router b/services/autoupdate.router new file mode 100644 index 0000000..19a3806 --- /dev/null +++ b/services/autoupdate.router @@ -0,0 +1,5 @@ + autoupdate: + rule: "PathPrefix(`/system/autoupdate`)" + service: autoupdate + entryPoints: + - main diff --git a/services/autoupdate.service b/services/autoupdate.service new file mode 100644 index 0000000..f96f5cb --- /dev/null +++ b/services/autoupdate.service @@ -0,0 +1,6 @@ + autoupdate: + loadBalancer: + servers: + - url: "http://${AUTOUPDATE_HOST}:${AUTOUPDATE_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/services/client.router b/services/client.router new file mode 100644 index 0000000..5064e01 --- /dev/null +++ b/services/client.router @@ -0,0 +1,7 @@ + client: + rule: "PathPrefix(`/`)" + service: client + entryPoints: + - main + # Priority ensures this catch-all route is evaluated last + priority: 1 diff --git a/services/client.service b/services/client.service new file mode 100644 index 0000000..aa523a9 --- /dev/null +++ b/services/client.service @@ -0,0 +1,6 @@ + client: + loadBalancer: + servers: + - url: "http://${CLIENT_HOST}:${CLIENT_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/services/icc.router b/services/icc.router new file mode 100644 index 0000000..eb7ac0f --- /dev/null +++ b/services/icc.router @@ -0,0 +1,5 @@ + icc: + rule: "PathPrefix(`/system/icc`)" + service: icc + entryPoints: + - main diff --git a/services/icc.service b/services/icc.service new file mode 100644 index 0000000..b6db89e --- /dev/null +++ b/services/icc.service @@ -0,0 +1,6 @@ + icc: + loadBalancer: + servers: + - url: "http://${ICC_HOST}:${ICC_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/services/manage.router b/services/manage.router new file mode 100644 index 0000000..415208b --- /dev/null +++ b/services/manage.router @@ -0,0 +1,5 @@ + manage: + rule: "Header(`Content-Type`, `application/grpc`)" + service: manage + entryPoints: + - main diff --git a/services/manage.service b/services/manage.service new file mode 100644 index 0000000..6d6104c --- /dev/null +++ b/services/manage.service @@ -0,0 +1,7 @@ + manage: + loadBalancer: + servers: + # h2c = HTTP/2 Cleartext (unencrypted) for gRPC + - url: "h2c://${MANAGE_HOST}:${MANAGE_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/services/media.router b/services/media.router new file mode 100644 index 0000000..c428375 --- /dev/null +++ b/services/media.router @@ -0,0 +1,5 @@ + media: + rule: "PathPrefix(`/system/media`)" + service: media + entryPoints: + - main diff --git a/services/media.service b/services/media.service new file mode 100644 index 0000000..378b83a --- /dev/null +++ b/services/media.service @@ -0,0 +1,6 @@ + media: + loadBalancer: + servers: + - url: "http://${MEDIA_HOST}:${MEDIA_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/services/presenter.router b/services/presenter.router new file mode 100644 index 0000000..62fd449 --- /dev/null +++ b/services/presenter.router @@ -0,0 +1,5 @@ + presenter: + rule: "PathPrefix(`/system/presenter`)" + service: presenter + entryPoints: + - main diff --git a/services/presenter.service b/services/presenter.service new file mode 100644 index 0000000..e7bc05d --- /dev/null +++ b/services/presenter.service @@ -0,0 +1,6 @@ + presenter: + loadBalancer: + servers: + - url: "http://${PRESENTER_HOST}:${PRESENTER_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/services/search.router b/services/search.router new file mode 100644 index 0000000..d6af74c --- /dev/null +++ b/services/search.router @@ -0,0 +1,5 @@ + search: + rule: "PathPrefix(`/system/search`)" + service: search + entryPoints: + - main diff --git a/services/search.service b/services/search.service new file mode 100644 index 0000000..bd935e0 --- /dev/null +++ b/services/search.service @@ -0,0 +1,6 @@ + search: + loadBalancer: + servers: + - url: "http://${SEARCH_HOST}:${SEARCH_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/services/vote.router b/services/vote.router new file mode 100644 index 0000000..a357117 --- /dev/null +++ b/services/vote.router @@ -0,0 +1,5 @@ + vote: + rule: "PathPrefix(`/system/vote`)" + service: vote + entryPoints: + - main diff --git a/services/vote.service b/services/vote.service new file mode 100644 index 0000000..96e4cbd --- /dev/null +++ b/services/vote.service @@ -0,0 +1,6 @@ + vote: + loadBalancer: + servers: + - url: "http://${VOTE_HOST}:${VOTE_PORT}" + # Forward the original Host header to the backend service + passHostHeader: true diff --git a/templates/tls.yml b/templates/tls.yml new file mode 100644 index 0000000..024deba --- /dev/null +++ b/templates/tls.yml @@ -0,0 +1,17 @@ + +# TLS configuration for using locally provided certs. +# Defining the cert files also as defaultCertificate in order for requests +# without an SNI to also be handled by it. Otherwise a self-signed cert +# generated by traefik would be used. +# See: https://doc.traefik.io/traefik/reference/routing-configuration/http/tls/tls-certificates/ +tls: + certificates: + - certFile: ${HTTPS_CERT_FILE} + keyFile: ${HTTPS_KEY_FILE} + stores: + - default + stores: + default: + defaultCertificate: + certFile: ${HTTPS_CERT_FILE} + keyFile: ${HTTPS_KEY_FILE} diff --git a/templates/traefik.yml b/templates/traefik.yml new file mode 100644 index 0000000..c5cb72f --- /dev/null +++ b/templates/traefik.yml @@ -0,0 +1,20 @@ +# Traefik configuration for OpenSlides + +# Add provider to read routing config from file +providers: + file: + directory: ${DYNAMIC_DIR} + watch: true + +# Enable /ping for HEALTHCHECK on default traefik endpoint (:8080) +ping: {} + +# Add logging config +log: + level: ${TRAEFIK_LOG_LEVEL} + +accessLog: {} + +# entryPoints are generated dynamically in entrypoint.sh script as their +# definitions depend on TLS configuration +