diff --git a/README.md b/README.md index 1614b3d..9cee8e1 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,41 @@ # Reverse Proxy -This role sets up a reverse proxy, to route HTTP traffic to the containers. Currently this is facilitated by traefik. + +This role sets up a reverse proxy, facilitated by [Traefik](https://containo.us/traefik/), to route HTTP traffic to the containers. + +> Current `master` targets Traefik 2.x, for Traefik 1.x support use [`traefik-1.x` branch](https://github.com/OneOffTech/ansible-role-reverseproxy/tree/traefik-1.x) ## Requirements -A working Docker installation on the host system is all you need. For automatic certificate generation via LetsEncrypt a domain name has to point toward your host address. + +A working Docker installation with Docker Compose. + +For automatic certificate generation via LetsEncrypt a domain name has to point toward your host address. ## Configuration Variables + ```yaml -path: "/home/user/reverseproxy" # Where the VPN proxy service will reside -data: "/data/reverseproxy/cert" # Where the certificates will be persisted -conf: "/data/reverseproxy/conf" # Where the configuration will be persisted -letsencrypt_email: "root@example.com" # Email of the admin, will be used for notifications +reverseproxy: + # Email used for notify certificate expiration + letsencrypt_email: "root@example.com" + path: "/home/user/reverseproxy" # Where the proxy service will reside + data: "/data/reverseproxy/cert" # Where the certificates will be persisted + conf: "/data/reverseproxy/conf" # Where the configuration will be persisted + log_level: "ERROR" + api: "traefik.domain.com" # domain where the dashboard will be reachable + api_user: "user:passwd" # username and password to protect the access to the dashboard (Use htpasswd to generate the passwords) https://docs.traefik.io/middlewares/basicauth/ ``` ## Upgrading installations + To upgrade the installation it is sufficient to simply run this playbook again after increasing the version number. + +### Upgrade from Traefik 1 to Traefik 2 + +Traefik version 2 is a major release and introduce some breaking changes in the configuration files +and labels. + +- Create a backup of all files and configuration; +- The network name to attach containers has been renamed to `traefik_web`. + The full network name depends on the deployment folder, so if the reverse proxy is deployed under `/home/user/reverseproxy`, + the complete network name will be `reverseproxy_traefik_web`; +- Change `cert` folder as the acme format changed in Traefik version 2 and is not backward compatible, e.g. `data: "/data/reverseproxy/cert-t2"`; diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..5396113 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,3 @@ +--- +default_rule: "{% raw %}Host(`{{ normalize .Name }}.example.domain`){% endraw %}" +log_level: "ERROR" diff --git a/tasks/main.yml b/tasks/main.yml index bd4c151..cd8f308 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -5,8 +5,10 @@ state: directory with_items: - "{{ reverseproxy.path }}" + - "{{ reverseproxy.path }}/rules" - "{{ reverseproxy.data }}" - "{{ reverseproxy.conf }}" + - "{{ reverseproxy.logs }}" - name: ensure docker service file exists template: @@ -22,6 +24,13 @@ notify: - reload reverseproxy + - name: ensure default rules files exists + template: + src: "rules_middlewares.toml.j2" + dest: "{{ reverseproxy.path }}/rules/middlewares.toml" + notify: + - reload reverseproxy + - name: ensure proxy is registered as a system service template: src: "reverseproxy.service.j2" diff --git a/templates/docker-compose.yml.j2 b/templates/docker-compose.yml.j2 index 7b800e8..b601696 100644 --- a/templates/docker-compose.yml.j2 +++ b/templates/docker-compose.yml.j2 @@ -1,23 +1,42 @@ -version: '2' +version: '3.1' networks: - web: + traefik_web: driver: "bridge" services: proxy: - image: "traefik:1.7" - command: "--logLevel=ERROR" + image: "traefik:v2.2" ports: - "80:80" - "443:443" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - - "{{ reverseproxy.data }}/:/cert/" + - "{{ reverseproxy.path }}/rules/:/rules/" + - "{{ reverseproxy.data }}/:/letsencrypt/" +{% if 'logs' in reverseproxy %} + - "{{ reverseproxy.logs }}/:/logs/" +{% endif %} - "{{ reverseproxy.conf }}/:/etc/traefik/:ro" + security_opt: + - no-new-privileges:true # limit containers to gain new privileges https://docs.docker.com/engine/reference/run/#security-configuration labels: - - "traefik.enable=false" # set to true to expose Monitoring & API - - "traefik.backend=proxy" - - "traefik.port=8080" + - "traefik.enable=true" + ## global redirect to https + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" + - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)" + - "traefik.http.routers.http-catchall.entrypoints=web" + - "traefik.http.routers.http-catchall.middlewares=redirect-to-https" +{% if 'api' in reverseproxy %} + - "traefik.http.routers.traefik-rtr.entrypoints=websecure" + - "traefik.http.routers.traefik-rtr.rule=Host(`{{ reverseproxy.api }}`)" + - "traefik.http.routers.traefik-rtr.tls=true" + - "traefik.http.routers.traefik-rtr.tls.certresolver=mytls" + - "traefik.http.routers.traefik-rtr.service=api@internal" +{% if 'api_user' in reverseproxy %} + - "traefik.http.routers.traefik-rtr.middlewares=middlewares-basic-auth@file" +{% endif %} +{% endif %} networks: - - "web" + - "traefik_web" + diff --git a/templates/reverseproxy.service.j2 b/templates/reverseproxy.service.j2 index e68ea1a..cd37dc0 100644 --- a/templates/reverseproxy.service.j2 +++ b/templates/reverseproxy.service.j2 @@ -8,10 +8,10 @@ WorkingDirectory={{ reverseproxy.path }} Restart=on-failure # Compose up -ExecStart=/usr/bin/docker-compose up +ExecStart=/usr/local/bin/docker-compose up # Compose down, remove containers and non-persistent volumes -ExecStop=/usr/bin/docker-compose down -v +ExecStop=/usr/local/bin/docker-compose down -v [Install] WantedBy=multi-user.target diff --git a/templates/rules_middlewares.toml.j2 b/templates/rules_middlewares.toml.j2 new file mode 100644 index 0000000..a22f1dc --- /dev/null +++ b/templates/rules_middlewares.toml.j2 @@ -0,0 +1,14 @@ +[http.middlewares] +{% if 'api_user' in reverseproxy %} + [http.middlewares.middlewares-basic-auth] + [http.middlewares.middlewares-basic-auth.basicAuth] + users = [ + "{{ reverseproxy.api_user }}", + ] + realm = "Traefik2 Basic Auth" +{% endif %} + + [http.middlewares.middlewares-rate-limit] + [http.middlewares.middlewares-rate-limit.rateLimit] + average = 100 + burst = 50 diff --git a/templates/traefik.toml.j2 b/templates/traefik.toml.j2 index 6f5b783..f37732f 100644 --- a/templates/traefik.toml.j2 +++ b/templates/traefik.toml.j2 @@ -1,58 +1,54 @@ -# accept self-signed SSL certs for backends -InsecureSkipVerify = true +[global] + checkNewVersion = false + sendAnonymousUsage = false -defaultEntryPoints = ["http", "https"] - -[acme] -email = "{{ reverseproxy.letsencrypt_email }}" -storage = "cert/acme.json" -entryPoint = "https" -onDemand = false -OnHostRule = true - -[acme.httpChallenge] -entryPoint = "http" +[serversTransport] + insecureSkipVerify = false [entryPoints] - [entryPoints.http] + [entryPoints.web] address = ":80" - [entryPoints.http.redirect] - entryPoint = "https" - [entryPoints.https] + + [entryPoints.websecure] address = ":443" - [entryPoints.https.tls] - -[web] -# own web server address (displays statistics) -address = ":8080" - -[docker] -endpoint = "unix:///var/run/docker.sock" -domain = "docker.local" -watch = true -exposedbydefault = false - -# new domains and subdomains can be configured here. -# note that domains and subdomains not defined in this file will still work, -# when defined in a container Host-Rule. However, they will generate -# their own ACME request, and will count towards LetsEncrypt's rate limit. -# -#[[acme.domains]] -# main = "example.com" -# sans = [ -# # services -# "mumble.example.com", -# # ... -# -# # web vhosts: -# "www.example.com", -# "git.example.com", -# "mail.example.com", -# "chat.example.com", -# ] - -# You can define multiple of these blocks, each of which will result in one -# certificate. -#[[acme.domains]] -# main = "example.org" -# sans = ["www.example.org", "mail.example.org"] + +{% if 'api' in reverseproxy %} +[api] + dashboard = true +{% endif %} + +[log] + level = "{{ reverseproxy.log_level | default(log_level, true) }}" + +[accessLog] + format = "common" + # filePath = "/logs/traefik.log" + # bufferingSize = 100 # Configuring a buffer of 100 lines + + [accessLog.filters] + statusCodes = ["200", "400-500"] + +# [metrics] +# [metrics.prometheus] +# entryPoint = "traefik" +# buckets = [0.1,0.3,1.2,5.0] +# [ping] + +[providers.docker] + network = "reverseproxy_traefik_web" + exposedByDefault = false + defaultRule = "{{ reverseproxy.default_rule | default(default_rule, true) }}" + +# Load dynamic configuration from one or more .toml or .yml files in a directory. +[providers.file] + directory = "/rules" + watch = true + + +[certificatesResolvers.mytls.acme] + email = "{{ reverseproxy.letsencrypt_email }}" + storage = "/letsencrypt/acme.json" + [certificatesResolvers.mytls.acme.httpChallenge] + # used during the challenge + entryPoint = "web" +