diff --git a/docs/docker.md b/docs/docker.md
index 8304b68..fe14cba 100644
--- a/docs/docker.md
+++ b/docs/docker.md
@@ -62,20 +62,23 @@
## Labels
+**Parts surrounded by `[]` are optional**
+
### Syntax
-| Label | Description | Example | Default | Accepted values |
-| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | --------------------------- | ------------------------------------------------------------------------- |
-| `proxy.aliases` | comma separated aliases for subdomain and label matching | `gitlab,gitlab-reg,gitlab-ssh` | `container_name` | any |
-| `proxy.exclude` | to be excluded from `go-proxy` | | false | boolean |
-| `proxy.idle_timeout` | time for idle (no traffic) before put it into sleep **(http/s only)**
_**NOTE: idlewatcher will only be enabled containers that has non-empty `idle_timeout`**_ | `1h` | empty or `0` **(disabled)** | `number[unit]...`, e.g. `1m30s` |
-| `proxy.wake_timeout` | time to wait for target site to be ready | | `30s` | `number[unit]...` |
-| `proxy.stop_method` | method to stop after `idle_timeout` | | `stop` | `stop`, `pause`, `kill` |
-| `proxy.stop_timeout` | time to wait for stop command | | `10s` | `number[unit]...` |
-| `proxy.stop_signal` | signal sent to container for `stop` and `kill` methods | | docker's default | `SIGINT`, `SIGTERM`, `SIGHUP`, `SIGQUIT` and those without **SIG** prefix |
-| `proxy..` | set field for specific alias | `proxy.gitlab-ssh.scheme` | N/A | N/A |
-| `proxy.#.` | set field for specific alias at index (starting from **1**) | `proxy.#3.port` | N/A | N/A |
-| `proxy.*.` | set field for all aliases | `proxy.*.set_headers` | N/A | N/A |
+| Label | Description | Example | Default | Accepted values |
+| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------- |
+| `proxy.aliases` | comma separated aliases for subdomain and label matching | `gitlab,gitlab-reg,gitlab-ssh` | `container_name` | any |
+| `proxy.exclude` | to be excluded from `go-proxy` | | false | boolean |
+| `proxy.idle_timeout` | time for idle (no traffic) before put it into sleep **(http/s only)**
_**NOTE: idlewatcher will only be enabled containers that has non-empty `idle_timeout`**_ | `1h` | empty or `0` **(disabled)** | `number[unit]...`, e.g. `1m30s` |
+| `proxy.wake_timeout` | time to wait for target site to be ready | | `30s` | `number[unit]...` |
+| `proxy.stop_method` | method to stop after `idle_timeout` | | `stop` | `stop`, `pause`, `kill` |
+| `proxy.stop_timeout` | time to wait for stop command | | `10s` | `number[unit]...` |
+| `proxy.stop_signal` | signal sent to container for `stop` and `kill` methods | | docker's default | `SIGINT`, `SIGTERM`, `SIGHUP`, `SIGQUIT` and those without **SIG** prefix |
+| `proxy..` | set field for specific alias | `proxy.gitlab-ssh.scheme` | N/A | N/A |
+| `proxy.#.` | set field for specific alias at index (starting from **1**) | `proxy.#3.port` | N/A | N/A |
+| `proxy.*.` | set field for all aliases | `proxy.*.set_headers` | N/A | N/A |
+| `proxy.?.middlewares.[.]` | enable and set field for specific middleware | **?** here means `` / `$` / `*` - `proxy.#1.middlewares.modify_request.set_headers`
- `proxy.*.middlewares.modify_response.hide_headers`
- `proxy.app1.middlewares.redirect_http`
| N/A | Middleware specific
See [middlewares.md](middlewares.md) for more |
### Fields
@@ -87,8 +90,7 @@
| `port` | proxy port **(tcp/udp)** | `0:first_port` | `x:y`
- **x**: port for `go-proxy` to listen on.
**x** can be 0, which means listen on a random port - **y**: port or [_service name_](../src/common/constants.go#L55) of target container
|
| `no_tls_verify` | whether skip tls verify **(https only)** | `false` | boolean |
| `path_patterns` | proxy path patterns **(http/s only)**
only requests that matched a pattern will be proxied | `/` **(proxy all requests)** | yaml style list[1](#list-example) of ([path patterns](https://pkg.go.dev/net/http#hdr-Patterns-ServeMux)) |
-| `set_headers` | header to set **(http/s only)** | empty | yaml style key-value mapping[2](#key-value-mapping-example) of header-value pairs |
-| `hide_headers` | header to hide **(http/s only)** | empty | yaml style list[1](#list-example) of headers |
+
[🔼Back to top](#table-of-content)
@@ -101,12 +103,9 @@ services:
nginx:
...
labels:
- # values from duplicated header keys will be combined
- proxy.nginx.set_headers: | # remember to add the '|'
+ proxy.nginx.middlewares.modify_request.set_headers: | # remember to add the '|'
X-Custom-Header1: value1, value2
- X-Custom-Header2: value3
- X-Custom-Header2: value4
- # X-Custom-Header2 will be "value3, value4"
+ X-Custom-Header2: value3, value4
```
File Provider
@@ -114,10 +113,11 @@ File Provider
```yaml
service_a:
host: service_a.internal
- set_headers:
- # do not duplicate header keys, as it is not allowed in YAML
- X-Custom-Header1: value1, value2
- X-Custom-Header2: value3
+ middlewares:
+ modify_request:
+ set_headers:
+ X-Custom-Header1: value1, value2
+ X-Custom-Header2: value3
```
[🔼Back to top](#table-of-content)
@@ -134,12 +134,12 @@ services:
proxy.nginx.path_patterns: | # remember to add the '|'
- GET /
- POST /auth
- proxy.nginx.hide_headers: | # remember to add the '|'
+ proxy.nginx.middlewares.modify_request.hide_headers: | # remember to add the '|'
- X-Custom-Header1
- X-Custom-Header2
```
-File Provider
+Include file
```yaml
service_a:
@@ -147,9 +147,11 @@ service_a:
path_patterns:
- GET /
- POST /auth
- hide_headers:
- - X-Custom-Header1
- - X-Custom-Header2
+ middlewares:
+ modify_request:
+ hide_headers:
+ - X-Custom-Header1
+ - X-Custom-Header2
```
[🔼Back to top](#table-of-content)
@@ -209,10 +211,10 @@ services:
restart: unless-stopped
labels:
- proxy.aliases=adg,adg-dns,adg-setup
- - proxy.$1.port=80
- - proxy.$2.scheme=udp
- - proxy.$2.port=20000:dns
- - proxy.$3.port=3000
+ - proxy.#1.port=80
+ - proxy.#2.scheme=udp
+ - proxy.#2.port=20000:dns
+ - proxy.#3.port=3000
volumes:
- adg-work:/opt/adguardhome/work
- adg-conf:/opt/adguardhome/conf
@@ -245,8 +247,8 @@ services:
labels:
- proxy.aliases=pal1,pal2
- proxy.*.scheme=udp
- - proxy.$1.port=20002:8211
- - proxy.$2.port=20003:27015
+ - proxy.#1.port=20002:8211
+ - proxy.#2.port=20003:27015
environment: ...
volumes:
- palworld:/palworld
diff --git a/docs/middlewares.md b/docs/middlewares.md
new file mode 100644
index 0000000..6a1fbb4
--- /dev/null
+++ b/docs/middlewares.md
@@ -0,0 +1,245 @@
+# Middlewares
+
+## Table of content
+
+
+
+- [Middlewares](#middlewares)
+ - [Table of content](#table-of-content)
+ - [Available middlewares](#available-middlewares)
+ - [Redirect http](#redirect-http)
+ - [Modify request or response](#modify-request-or-response)
+ - [Set headers](#set-headers)
+ - [Add headers](#add-headers)
+ - [Hide headers](#hide-headers)
+ - [X-Forwarded-\* Headers](#x-forwarded--headers)
+ - [Add X-Forwarded-\*](#add-x-forwarded-)
+ - [Set X-Forwarded-\*](#set-x-forwarded-)
+ - [Forward Authorization header (experimental)](#forward-authorization-header-experimental)
+ - [Examples](#examples)
+ - [Authentik](#authentik)
+
+
+
+## Available middlewares
+
+### Redirect http
+
+Redirect http requests to https
+
+```yaml
+# docker labels
+proxy.app1.middlewares.redirect_http:
+
+# include file
+app1:
+ middlewares:
+ redirect_http:
+```
+
+nginx equivalent:
+```nginx
+server {
+ listen 80;
+ server_name domain.tld;
+ return 301 https://$host$request_uri;
+}
+```
+
+[🔼Back to top](#table-of-content)
+
+### Modify request or response
+
+```yaml
+# docker labels
+proxy.app1.middlewares.modify_request.field:
+proxy.app1.middlewares.modify_response.field:
+
+# include file
+app1:
+ middlewares:
+ modify_request:
+ field:
+ modify_response:
+ field:
+```
+
+#### Set headers
+
+```yaml
+# docker labels
+proxy.app1.middlewares.modify_request.set_headers: |
+ X-Custom-Header1: value1, value2
+ X-Custom-Header2: value3
+
+# include file
+app1:
+ middlewares:
+ modify_request:
+ set_headers:
+ X-Custom-Header1: value1, value2
+ X-Custom-Header2: value3
+```
+
+nginx equivalent:
+```nginx
+location / {
+ add_header X-Custom-Header1 value1, value2;
+ add_header X-Custom-Header2 value3;
+}
+```
+
+#### Add headers
+
+```yaml
+# docker labels
+proxy.app1.middlewares.modify_request.add_headers: |
+ X-Custom-Header1: value1, value2
+ X-Custom-Header2: value3
+
+# include file
+app1:
+ middlewares:
+ modify_request:
+ add_headers:
+ X-Custom-Header1: value1, value2
+ X-Custom-Header2: value3
+```
+
+nginx equivalent:
+```nginx
+location / {
+ more_set_headers "X-Custom-Header1: value1, value2";
+ more_set_headers "X-Custom-Header2: value3";
+}
+```
+
+#### Hide headers
+
+```yaml
+# docker labels
+proxy.app1.middlewares.modify_request.hide_headers: |
+ - X-Custom-Header1
+ - X-Custom-Header2
+
+# include file
+app1:
+ middlewares:
+ modify_request:
+ hide_headers:
+ - X-Custom-Header1
+ - X-Custom-Header2
+```
+
+nginx equivalent:
+```nginx
+location / {
+ more_clear_headers "X-Custom-Header1";
+ more_clear_headers "X-Custom-Header2";
+}
+```
+
+### X-Forwarded-* Headers
+
+#### Add X-Forwarded-*
+
+Append `X-Forwarded-*` headers to existing headers
+
+```yaml
+# docker labels
+proxy.app1.middlewares.modify_request.add_x_forwarded:
+
+# include file
+app1:
+ middlewares:
+ modify_request:
+ add_x_forwarded:
+```
+
+#### Set X-Forwarded-*
+
+Replace existing `X-Forwarded-*` headers with `go-proxy` provided headers
+
+```yaml
+# docker labels
+proxy.app1.middlewares.modify_request.set_x_forwarded:
+
+# include file
+app1:
+ middlewares:
+ modify_request:
+ set_x_forwarded:
+```
+
+### Forward Authorization header (experimental)
+
+Fields:
+- `address`: authentication provider URL _(required)_
+- `trust_forward_header`: whether to trust `X-Forwarded-*` headers from upstream proxies _(default: `false`)_
+- `auth_response_headers`: list of headers to copy from auth response _(default: empty)_
+- `add_auth_cookies_to_response`: list of cookies to add to response _(default: empty)_
+
+```yaml
+# docker labels
+proxy.app1.middlewares.forward_auth.address: https://auth.example.com
+proxy.app1.middlewares.forward_auth.trust_forward_header: true
+proxy.app1.middlewares.forward_auth.auth_response_headers: |
+ - X-Auth-Token
+ - X-Auth-User
+proxy.app1.middlewares.forward_auth.add_auth_cookies_to_response: |
+ - uid
+ - session_id
+
+# include file
+app1:
+ middlewares:
+ forward_authorization:
+ address: https://auth.example.com
+ trust_forward_header: true
+ auth_response_headers:
+ - X-Auth-Token
+ - X-Auth-User
+ add_auth_cookies_to_response:
+ - uid
+ - session_id
+```
+
+Traefik equivalent:
+```yaml
+# docker labels
+traefik.http.middlewares.authentik.forwardauth.address: https://auth.example.com
+traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true
+traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-Auth-Token, X-Auth-User
+traefik.http.middlewares.authentik.forwardauth.addAuthCookiesToResponse: uid, session_id
+
+# standalone
+http:
+ middlewares:
+ forwardAuth:
+ address: https://auth.example.com
+ trustForwardHeader: true
+ authResponseHeaders:
+ - X-Auth-Token
+ - X-Auth-User
+ addAuthCookiesToResponse:
+ - uid
+ - session_id
+```
+
+## Examples
+
+### Authentik
+
+```yaml
+# docker compose
+services:
+ ...
+ server:
+ ...
+ container_name: authentik
+ labels:
+ proxy.authentik.middlewares.redirect_http:
+ proxy.authentik.middlewares.set_x_forwarded:
+ proxy.authentik.middlewares.modify_request.add_headers: |
+ Strict-Transport-Security: "max-age=63072000" always
+```
\ No newline at end of file
diff --git a/src/common/ports.go b/src/common/ports.go
index a4f3fc1..7816355 100644
--- a/src/common/ports.go
+++ b/src/common/ports.go
@@ -59,8 +59,8 @@ var (
"nginx-proxy-manager": 81,
"open-webui": 8080,
"plex": 32400,
- "portainer": 9000,
- "portainer-ce": 9000,
+ "portainer-be": 9443,
+ "portainer-ce": 9443,
"prometheus": 9090,
"prowlarr": 9696,
"radarr": 7878,
diff --git a/src/docker/label.go b/src/docker/label.go
index edac8b9..39b288f 100644
--- a/src/docker/label.go
+++ b/src/docker/label.go
@@ -108,12 +108,12 @@ func ParseLabel(label string, value string) (*Label, E.NestedError) {
}
// find if namespace has value parser
- pm, ok := valueParserMap.Load(l.Namespace)
+ pm, ok := valueParserMap.Load(U.ToLowerNoSnake(l.Namespace))
if !ok {
return l, nil
}
// find if attribute has value parser
- p, ok := pm[l.Attribute]
+ p, ok := pm[U.ToLowerNoSnake(l.Attribute)]
if !ok {
return l, nil
}
@@ -127,7 +127,11 @@ func ParseLabel(label string, value string) (*Label, E.NestedError) {
}
func RegisterNamespace(namespace string, pm ValueParserMap) {
- valueParserMap.Store(namespace, pm)
+ pmCleaned := make(ValueParserMap, len(pm))
+ for k, v := range pm {
+ pmCleaned[U.ToLowerNoSnake(k)] = v
+ }
+ valueParserMap.Store(U.ToLowerNoSnake(namespace), pmCleaned)
}
func GetRegisteredNamespaces() map[string][]string {
diff --git a/src/docker/label_parser.go b/src/docker/label_parser.go
index def4b07..91b1c34 100644
--- a/src/docker/label_parser.go
+++ b/src/docker/label_parser.go
@@ -49,7 +49,7 @@ func YamlLikeMappingParser(allowDuplicate bool) func(string) (any, E.NestedError
for _, line := range lines {
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
- return nil, E.Invalid("syntax", line)
+ return nil, E.Invalid("syntax", line).With("too many colons")
}
key := strings.TrimSpace(parts[0])
val := strings.TrimSpace(parts[1])
diff --git a/src/route/middleware/forward_auth.go b/src/route/middleware/forward_auth.go
index 0276da0..f7e80e7 100644
--- a/src/route/middleware/forward_auth.go
+++ b/src/route/middleware/forward_auth.go
@@ -74,7 +74,7 @@ func newForwardAuth() (fa *forwardAuth) {
}
faWithOpts.m = &Middleware{
impl: faWithOpts,
- before: fa.forward,
+ before: faWithOpts.forward,
}
err := U.Deserialize(optsRaw, faWithOpts.forwardAuthOpts)
diff --git a/src/utils/serialization.go b/src/utils/serialization.go
index c4cd310..bfff55e 100644
--- a/src/utils/serialization.go
+++ b/src/utils/serialization.go
@@ -108,13 +108,13 @@ func Serialize(data any) (SerializedObject, E.NestedError) {
func Deserialize(src SerializedObject, target any) E.NestedError {
// convert data fields to lower no-snake
- // convert target fields to lower
+ // convert target fields to lower no-snake
// then check if the field of data is in the target
mapping := make(map[string]string)
t := reflect.TypeOf(target).Elem()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
- snakeCaseField := strings.ToLower(field.Name)
+ snakeCaseField := ToLowerNoSnake(field.Name)
mapping[snakeCaseField] = field.Name
}
tValue := reflect.ValueOf(target)
@@ -122,7 +122,7 @@ func Deserialize(src SerializedObject, target any) E.NestedError {
return E.Invalid("value", "nil")
}
for k, v := range src {
- kCleaned := toLowerNoSnake(k)
+ kCleaned := ToLowerNoSnake(k)
if fieldName, ok := mapping[kCleaned]; ok {
prop := reflect.ValueOf(target).Elem().FieldByName(fieldName)
propType := prop.Type()
@@ -175,7 +175,7 @@ func DeserializeJson(j map[string]string, target any) E.NestedError {
return E.From(json.Unmarshal(data, target))
}
-func toLowerNoSnake(s string) string {
+func ToLowerNoSnake(s string) string {
return strings.ToLower(strings.ReplaceAll(s, "_", ""))
}