Add rendered example

This commit is contained in:
Daniel Berteaud 2024-01-05 12:11:30 +01:00
parent 371efd0edf
commit b55a2f232d
6 changed files with 530 additions and 0 deletions

55
example/README.md Normal file
View File

@ -0,0 +1,55 @@
# Initial configuration
Before running Traefik, you have to create a role on vault, to get a Consul token for traefik. This token will be used by Traefik to query consul service catalog and adjust routes dynamicaly
```
vault write consul/roles/traefik consul_policies=traefik ttl=72h max_ttl=168h
```
# IP whitelist / blacklist
The job support getting lists of IP addresses/CIDR dynamycally from Consul K/V store. To create a new list, go on Consul Key/Value from the web interface, in common/ip (create the tree if needed), then create an entry. For example, an entry named trusted, with the following content:
```
- 10.99.9.1 # fw-dc for healthcheck
- 192.168.7.0/24 # Private LAN
- 10.99.20.0/24 # VPN roadwarriors
- 10.99.23.0/24 # VPN Wireguard
```
Format it as a YAML document. Now, as soon as you save this, a new middleware named ip-trusted@file will be available on Traefik, and can be used by routers to protect sensitive applications. You can also include an IP list in another one. For example, another IP list named admin could contain
```
- include:trusted
- 10.29.0.0/16
```
Now the ip-admin@file middleware contains all the IP of the trusted list, plus 10.29.0.0/16
# Basic authentication middlewares
You can configure basic auth middlewares by putting user/password in vault. For example
```
vault kv put kv/service/traefik/basicauth/monitoring john='S3cr3t.' marie='p@ssw0rd'
```
Then, a basicauth-monitoring@file middleware will be automaticaly created and available for routers to protect the app you want. The password can be either plain text, or an already bcrypt encrypted password (starting with ```$2y$```).
Note that while creating new or updating existing basicauth entry will be reloaded without a need to restart Traefik, it can take up to 5 min for the change to propagate.
# Lemonldap::NG Handler
The job support running a Lemonldap::NG handler, using the REST API to reach config and session databases
```
lemonldap:
enabled: True
config:
url: https://auth.example.org/index.psgi/config
user: lemonldap
password: '{{ with secret "kv/service/traefik" }}{{ .Data.data.llng_api_pwd }}{{ end }}'
realm: Lemonldap::NG API
sessions:
url: https://auth.example.org/index.psgi/sessions/global
user: lemonldap
password: '{{ with secret "kv/service/traefik" }}{{ .Data.data.llng_api_pwd }}{{ end }}'
realm: Lemonldap::NG API
```
And the password for the API is stored in vault
```
vault kv put kv/service/traefik llng_api_pwd='ThisIsNotAVeryStrongPassword'
```

View File

@ -0,0 +1,19 @@
key_prefix "service/traefik" {
policy = "read"
}
key_prefix "common/ip" {
policy = "read"
}
service "traefik" {
policy = "write"
}
node_prefix "" {
policy = "read"
}
service_prefix "" {
policy = "read"
}

View File

@ -0,0 +1,19 @@
FROM danielberteaud/alpine:24.1-1
MAINTAINER Daniel Berteaud <dbd@ehtrace.com>
ARG VERSION=3.0.0-beta5
RUN set -eux &&\
apk --no-cache upgrade &&\
apk --no-cache add ca-certificates tzdata curl &&\
cd /tmp &&\
curl -sSLO "https://github.com/traefik/traefik/releases/download/v${VERSION}/traefik_v${VERSION}_linux_amd64.tar.gz" &&\
curl -sSLO "https://github.com/traefik/traefik/releases/download/v${VERSION}/traefik_v${VERSION}_checksums.txt" &&\
grep traefik_v${VERSION}_linux_amd64.tar.gz traefik_v${VERSION}_checksums.txt | sha256sum -c &&\
tar xzvf traefik_v${VERSION}_linux_amd64.tar.gz -C /usr/local/bin traefik &&\
rm -f traefik_v${VERSION}_linux_amd64.tar.gz &&\
chmod +x /usr/local/bin/traefik
EXPOSE 80
CMD ["traefik"]

View File

@ -0,0 +1,19 @@
#!/bin/sh
set -eu
if [ "traefik" != "traefik" ]; then
for DIR in vault consul nomad; do
if [ -d output/${DIR} ]; then
for FILE in $(find output/${DIR} -name "*traefik*.hcl" -type f); do
NEW_FILE=$(echo "${FILE}" | sed -E "s/traefik/traefik/g")
mv "${FILE}" "${NEW_FILE}"
done
fi
done
fi

402
example/traefik.nomad.hcl Normal file
View File

@ -0,0 +1,402 @@
job "traefik" {
datacenters = ["dc1"]
priority = 100
group "traefik" {
count = 2
shutdown_delay = "6s"
# Force different instances to run on distinct nodes
constraint {
operator = "distinct_hosts"
value = "true"
}
network {
mode = "bridge"
port "http" {
static = 80
to = 5080
}
port "https" {
static = 443
to = 5443
}
port "metrics" {}
}
service {
name = "traefik-sidecar"
port = "https"
connect {
sidecar_service {
}
sidecar_task {
resources {
cpu = 50
memory = 64
}
}
}
}
service {
name = "traefik"
port = "https"
task = "traefik"
# Traefik supports native Consul service mesh
connect {
native = true
}
tags = [
"traefik.enable=true",
"traefik.http.middlewares.traefik-path.replacepathregex.regex=^/dashboard/(.*)",
"traefik.http.middlewares.traefik-path.replacepathregex.replacement=/dashboard/$${1}",
"traefik.http.routers.traefik-api.rule=(Host(`traefik.example.org`) || HostRegexp(`(.+\\.)?traefik.service.consul`)) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))",
"traefik.http.routers.traefik-api.entrypoints=https",
"traefik.http.routers.traefik-api.service=api@internal",
"traefik.http.routers.traefik-api.middlewares=rate-limit-std@file,inflight-std@file,security-headers@file,hsts@file,compression@file,csp-relaxed@file,ip-trusted@file,csp-strict@file,traefik-path",
"traefik.http.routers.traefik-ping.rule=(Host(`traefik.example.org`) || HostRegexp(`(.+\\.)?traefik.service.consul`)) && Path(`/ping`) && Method(`GET`)",
"traefik.http.routers.traefik-ping.entrypoints=http,https",
"traefik.http.routers.traefik-ping.service=ping@internal",
"traefik.http.routers.traefik-ping.priority=2000",
"traefik.http.routers.traefik-ping.middlewares=rate-limit-std@file,inflight-std@file,security-headers@file,hsts@file,compression@file,csp-relaxed@file",
"traefik-${NOMAD_ALLOC_INDEX}"
]
}
task "traefik" {
driver = "docker"
user = 5443
vault {
policies = ["traefik"]
}
config {
image = "danielberteaud/traefik:3.0.0-beta5-1"
command = "traefik"
args = [
"--configfile=/secrets/traefik.yml"
]
}
# Main traefik configuration
template {
data = <<_EOF
log:
level: INFO
accessLog:
bufferingSize: 100
entryPoints:
http:
address: ":{{ env "NOMAD_PORT_http" }}"
http:
redirections:
entryPoint:
priority: 1000
to: :{{ env "NOMAD_HOST_PORT_https" }}
transport:
lifeCycle:
requestAcceptGraceTimeout: 4
https:
address: ":{{ env "NOMAD_PORT_https" }}"
http:
tls: {}
transport:
lifeCycle:
requestAcceptGraceTimeout: 4
api:
dashboard: True
providers:
consulCatalog:
prefix: traefik
endpoint:
address: {{ sockaddr "GetInterfaceIP \"nomad\"" }}:8500
scheme: http
token: {{ with secret "consul/creds/traefik" }}{{ .Data.token }}{{ end }}
exposedByDefault: False
connectAware: True
connectByDefault: True
serviceName: traefik
refreshInterval: 5s
watch: True
file:
directory: /secrets/config
watch: True
ping:
manualRouting: True
_EOF
destination = "secrets/traefik.yml"
perms = "0400"
uid = 105443
gid = 100000
}
# Dynamic file configuration
template {
data = <<_EOF
---
{{ if gt (len (secrets "kv/service/traefik/basicauth/")) 0 }}
http:
middlewares:
{{- range secrets "kv/service/traefik/basicauth/" }}
basicauth-{{ . }}:
basicAuth:
realm: {{ . }}
removeheader: true
users:
{{- with secret (printf "kv/data/service/traefik/basicauth/%s" .) }}
{{- range $k, $v := .Data.data }}
- {{ $k }}:{{ if $v | regexMatch "^\\$2y\\$" }}{{ $v }}{{ else }}{{ sprig_bcrypt $v }}{{ end }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
_EOF
destination = "secrets/config/basicauth.yml"
change_mode = "noop"
perms = "0400"
uid = 105443
gid = 100000
}
template {
data = <<_EOF
---
_EOF
destination = "secrets/config/lemonldap.yml"
change_mode = "noop"
perms = "0400"
uid = 105443
gid = 100000
}
template {
data = <<_EOF
---
{{- if ne 0 (len (secrets "kv/service/traefik/certs/")) }}
tls:
certificates:
{{- range secrets "kv/service/traefik/certs/" }}
{{- $cn := . }}
{{- with secret (printf "kv/service/traefik/certs/%s" $cn) }}
# {{ $cn }}
- certFile: |-
{{ .Data.data.cert | replaceAll "\n\n" "\n" | indent 8 }}
keyFile: |-
{{ .Data.data.key | indent 8 }}
{{- end }}
{{- end }}
{{- end }}
_EOF
destination = "secrets/config/certificates.yml"
change_mode = "noop"
perms = "0400"
uid = 105443
gid = 100000
}
template {
data = <<_EOF
---
{{ if ne 0 (len (ls "common/ip")) }}
http:
middlewares:
{{ range ls "common/ip" }}
ip-{{ .Key }}:
ipAllowList:
sourceRange:{{ range .Value | parseYAML }}
{{- if . | regexMatch "^include:.*" }}
# Include IP from {{ . | replaceAll "include:" "" }}
{{- range key (printf "common/ip/%s" . | replaceAll "include:" "") | parseYAML }}
- {{ . }}{{ end }}
{{- else }}
- {{ . }}{{ end }}{{ end }}
{{ end }}
tcp:
middlewares:
{{ range ls "common/ip" }}
ip-{{ .Key }}:
ipAllowList:
sourceRange:{{ range .Value | parseYAML }}
{{- if . | regexMatch "^include:.*" }}
# Include IP from {{ . | replaceAll "include:" "" }}
{{- range key (printf "common/ip/%s" . | replaceAll "include:" "") | parseYAML }}
- {{ . }}{{ end }}
{{- else }}
- {{ . }}{{ end }}{{ end }}
{{ end }}
{{ end }}
_EOF
destination = "secrets/config/ip.yml"
change_mode = "noop"
perms = "0400"
uid = 105443
gid = 100000
}
template {
data = <<_EOF
---
http:
middlewares:
autodetect:
contentType: {}
compression:
compress:
excludedContentTypes:
- image/png
- image/jpeg
- image/jpg
- image/pjpeg
- image/avif
- image/webp
- image/x-icon
- font/woff2
- font/woff
- video/webm
- video/ogg
- video/mpeg
- video/mp4
- video/3gpp
- video/3gpp2
- video/ogg
- video/x-flv
- video/h261
- video/h263
- video/h264
- video/jpm
- video/jpeg
- video/quicktime
- audio/x-aac
- audio/x-aiff
- audio/mpeg
- audio/mp4
- audio/ogg
- audio/webm
- audio/opus
- application/zip
- application/gzip
- application/x-7z-compressed
- application/x-ace-compressed
- application/x-debian-package
- application/vnd.android.package-archive
_EOF
destination = "secrets/config/performance.yml"
change_mode = "noop"
perms = "0400"
uid = 105443
gid = 100000
}
template {
data = <<_EOF
---
http:
middlewares:
rate-limit-std:
rateLimit:
average: 30
burst: 50
rate-limit-high:
rateLimit:
average: 100
burst: 200
inflight-std:
inFlightReq:
amount: 100
inflight-high:
inFlightReq:
amount: 300
security-headers:
headers:
contentTypeNosniff: True
browserXssFilter: True
# customFrameOptionsValue: sameorigin
customResponseHeaders:
Server: ""
X-Powered-By: ""
X-Envoy-Upstream-Service-Time: ""
hsts:
headers:
forceSTSHeader: True
stsIncludeSubdomains: True
stsSeconds: 63072000
stsPreload: True
csp-strict:
headers:
contentSecurityPolicy: "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'"
csp-relaxed:
headers:
contentSecurityPolicy: "default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self' data:"
_EOF
destination = "secrets/config/security.yml"
change_mode = "noop"
perms = "0400"
uid = 105443
gid = 100000
}
resources {
cpu = 500
memory = 256
}
}
}
}
# vim: syntax=hcl

View File

@ -0,0 +1,16 @@
# Get a consul token
path "consul/creds/traefik" {
capabilities = ["read"]
}
# Read traefik specific settings
path "kv/data/service/traefik" {
capabilities = ["read", "list"]
}
# LIst and read traefik basic auth &cie
path "kv/metadata/service/traefik/*" {
capabilities = ["list","read"]
}
path "kv/data/service/traefik/*" {
capabilities = ["read"]
}