traefik/example/traefik.nomad.hcl

462 lines
10 KiB
HCL

job "traefik" {
datacenters = ["dc1"]
region = "global"
group "traefik" {
count = 2
shutdown_delay = "6s"
constraint {
operator = "distinct_hosts"
value = "true"
}
ephemeral_disk {
# Use minimal ephemeral disk
size = 101
}
network {
mode = "bridge"
port "http" {
static = 80
to = 5080
}
port "https" {
static = 443
to = 5443
}
}
service {
name = "traefik-sidecar"
port = "https"
connect {
sidecar_service {
}
sidecar_task {
config {
args = [
"-c",
"${NOMAD_SECRETS_DIR}/envoy_bootstrap.json",
"-l",
"${meta.connect.log_level}",
"--concurrency",
"${meta.connect.proxy_concurrency}",
"--disable-hot-restart"
]
}
resources {
cpu = 50
memory = 64
}
}
}
}
service {
name = "traefik"
port = "https"
task = "traefik"
meta {
alloc = "${NOMAD_ALLOC_INDEX}"
datacenter = "${NOMAD_DC}"
group = "${NOMAD_GROUP_NAME}"
job = "${NOMAD_JOB_NAME}"
namespace = "${NOMAD_NAMESPACE}"
node = "${node.unique.name}"
region = "${NOMAD_REGION}"
}
# Traefik supports native Consul service mesh
connect {
native = true
}
tags = [
"traefik.http.routers.traefik-api.rule=(Host(`traefik.example.org`) || HostRegexp(`(.+\\.)?traefik.service.consul`)) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))",
"traefik.http.routers.traefik-api.service=api@internal",
"traefik.enable=true",
"traefik.http.routers.traefik-api.entrypoints=https",
"traefik.http.middlewares.csp-traefik-api.headers.contentsecuritypolicy=default-src 'self';font-src 'self' data:;img-src 'self' data:;script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';",
"traefik.http.middlewares.traefik-api-99-path.replacepathregex.regex=^/dashboard/(.*)",
"traefik.http.middlewares.traefik-api-99-path.replacepathregex.replacement=/dashboard/$${1}",
"traefik.http.routers.traefik-api.middlewares=security-headers@file,rate-limit-std@file,forward-proto@file,inflight-std@file,hsts@file,compression@file,traefik-api-99-path,csp-traefik-api",
"traefik.http.routers.traefik-ping.rule=(Host(`traefik.example.org`) || HostRegexp(`(.+\\.)?traefik.service.consul`)) && Path(`/ping`) && Method(`GET`)",
"traefik.http.routers.traefik-ping.service=ping@internal",
"traefik.enable=true",
"traefik.http.routers.traefik-ping.entrypoints=http,https",
"traefik.http.routers.traefik-ping.priority=2000",
"traefik.http.middlewares.csp-traefik-ping.headers.contentsecuritypolicy=default-src 'self';font-src 'self' data:;img-src 'self' data:;script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';",
"traefik.http.routers.traefik-ping.middlewares=security-headers@file,rate-limit-std@file,forward-proto@file,inflight-std@file,hsts@file,compression@file,csp-traefik-ping",
"traefik-${NOMAD_ALLOC_INDEX}"
]
}
task "traefik" {
driver = "docker"
user = 5443
vault {
policies = ["traefik"]
}
config {
image = "danielberteaud/traefik:3.0.0-rc4-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
}
template {
data = <<_EOF
---
http:
middlewares:
forward-proto:
headers:
customRequestHeaders:
X-Forwarded-Proto: https
_EOF
destination = "secrets/config/proxy.yml"
change_mode = "noop"
perms = "0400"
uid = 105443
gid = 100000
}
resources {
cpu = 500
memory = 256
memory_max = 300
}
}
}
}
# vim: syntax=hcl