diff --git a/README.md b/README.md
index d1a1d79..b47c001 100644
--- a/README.md
+++ b/README.md
@@ -19,14 +19,14 @@ With it, you can listen on sockets/ports and express logic such as:
Because this is a caddy app, it can be used alongside other Caddy apps such as the [HTTP server](https://caddyserver.com/docs/modules/http) or [TLS certificate manager](https://caddyserver.com/docs/modules/tls).
-Note that only JSON config is available at this time. More documentation will come soon. For now, please read the code, especially type definitions and their comments. It's actually a pretty simple code base, and the JSON config isn't that bad once you get used to it! See below for tips and examples writing config.
+Note that both Caddyfile and JSON configs are available at this time. More documentation will come soon. For now, please read the code, especially type definitions and their comments. It's actually a pretty simple code base. See below for tips and examples writing config.
> [!NOTE]
> This is not an official repository of the [Caddy Web Server](https://github.com/caddyserver) organization.
## Introduction
-This app works similarly to the `http` app. You define servers, and each server consists of routes. A route has a set of matchers and handlers; if a connection matches, the assoicated handlers are invoked.
+This app works similarly to the `http` app. You define servers, and each server consists of routes. A route has a set of matchers and handlers; if a connection matches, the associated handlers are invoked.
Current matchers:
@@ -36,15 +36,18 @@ Current matchers:
- **layer4.matchers.postgres** - matches connections that look like Postgres connections.
- **layer4.matchers.remote_ip** - matches connections based on remote IP (or CIDR range).
- **layer4.matchers.local_ip** - matches connections based on local IP (or CIDR range).
+- **layer4.matchers.not** - matches connections that aren't matched by inner matcher sets.
- **layer4.matchers.proxy_protocol** - matches connections that start with [HAPROXY proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt).
- **layer4.matchers.rdp** - matches connections that look like [RDP](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-RDPBCGR/%5BMS-RDPBCGR%5D.pdf).
- **layer4.matchers.socks4** - matches connections that look like [SOCKSv4](https://www.openssh.com/txt/socks4.protocol).
- **layer4.matchers.socks5** - matches connections that look like [SOCKSv5](https://www.rfc-editor.org/rfc/rfc1928.html).
+- **layer4.matchers.xmpp** - matches connections that look like [XMPP](https://xmpp.org/about/technology-overview/).
Current handlers:
- **layer4.handlers.echo** - An echo server.
- **layer4.handlers.proxy** - Powerful layer 4 proxy, capable of multiple upstreams (with load balancing and health checks) and establishing new TLS connections to backends. Optionally supports sending the [HAProxy proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt).
+- **layer4.handlers.subroute** - Implements recursion logic, i.e. allows to match and handle already matched connections.
- **layer4.handlers.tee** - Branches the handling of a connection into a concurrent handler chain.
- **layer4.handlers.throttle** - Throttle connections to simulate slowness and latency.
- **layer4.handlers.tls** - TLS termination.
@@ -79,6 +82,24 @@ See below for some examples to help you get started.
A simple echo server:
+
+ Caddyfile
+
+```
+{
+ layer4 {
+ 127.0.0.1:5000 {
+ route {
+ echo
+ }
+ }
+ }
+}
+```
+
+
+ JSON
+
```json
{
"apps": {
@@ -99,10 +120,30 @@ A simple echo server:
}
}
```
+
A simple echo server with TLS termination that uses a self-signed cert for `localhost`:
+
+ Caddyfile
+
+```
+{
+ layer4 {
+ 127.0.0.1:5000 {
+ route {
+ tls
+ echo
+ }
+ }
+ }
+}
+```
+
+
+ JSON
+
```json
{
"apps": {
@@ -136,9 +177,41 @@ A simple echo server with TLS termination that uses a self-signed cert for `loca
}
}
```
+
A simple TCP reverse proxy that terminates TLS on 993, and sends the PROXY protocol header to 1143 through 143:
+
+ Caddyfile
+
+```
+{
+ layer4 {
+ 0.0.0.0:993 {
+ route {
+ tls
+ proxy {
+ proxy_protocol v1
+ upstream localhost:143
+ }
+ }
+ }
+ 0.0.0.0:143 {
+ route {
+ proxy_protocol
+ proxy {
+ proxy_protocol v2
+ upstream localhost:1143
+ }
+ }
+ }
+ }
+}
+```
+
+
+ JSON
+
```json
{
"apps": {
@@ -187,9 +260,33 @@ A simple TCP reverse proxy that terminates TLS on 993, and sends the PROXY proto
}
}
```
+
A multiplexer that proxies HTTP to one backend, and TLS to another (without terminating TLS):
+
+ Caddyfile
+
+```
+{
+ layer4 {
+ 127.0.0.1:5000 {
+ @insecure http
+ route @insecure {
+ proxy localhost:80
+ }
+ @secure tls
+ route @secure {
+ proxy localhost:443
+ }
+ }
+ }
+}
+```
+
+
+ JSON
+
```json
{
"apps": {
@@ -235,9 +332,38 @@ A multiplexer that proxies HTTP to one backend, and TLS to another (without term
}
}
```
+
Same as previous, but only applies to HTTP requests with specific hosts:
+
+ Caddyfile
+
+```
+{
+ layer4 {
+ 127.0.0.1:5000 {
+ @example http host example.com
+ route @example {
+ subroute {
+ @insecure http
+ route @insecure {
+ proxy localhost:80
+ }
+ @secure tls
+ route @secure {
+ proxy localhost:443
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+
+ JSON
+
```json
{
"apps": {
@@ -299,9 +425,33 @@ Same as previous, but only applies to HTTP requests with specific hosts:
}
}
```
+
Same as previous, but filter by HTTP Host header and/or TLS ClientHello ServerName:
+
+ Caddyfile
+
+```
+{
+ layer4 {
+ 127.0.0.1:5000 {
+ @insecure http host example.com
+ route @insecure {
+ proxy localhost:80
+ }
+ @secure tls sni example.net
+ route @secure {
+ proxy localhost:443
+ }
+ }
+ }
+}
+```
+
+
+ JSON
+
```json
{
"apps": {
@@ -351,11 +501,39 @@ Same as previous, but filter by HTTP Host header and/or TLS ClientHello ServerNa
}
}
```
-
+
Forwarding SOCKSv4 to a remote server and handling SOCKSv5 directly in caddy.
While only allowing connections from a specific network and requiring a username and password for SOCKSv5.
+
+ Caddyfile
+
+```
+{
+ layer4 {
+ 0.0.0.0:1080 {
+ @s5 {
+ socks5
+ ip 10.0.0.0/24
+ }
+ route @s5 {
+ socks5 {
+ credentials bob qHoEtVpGRM
+ }
+ }
+ @s4 socks4
+ route @s4 {
+ proxy 10.64.0.1:1080
+ }
+ }
+ }
+}
+```
+
+
+ JSON
+
```json
{
"apps": {
@@ -402,3 +580,4 @@ While only allowing connections from a specific network and requiring a username
}
}
```
+
diff --git a/go.mod b/go.mod
index 1ca618a..ba88241 100644
--- a/go.mod
+++ b/go.mod
@@ -1,28 +1,32 @@
module github.com/mholt/caddy-l4
-go 1.21
+go 1.21.0
require (
- github.com/caddyserver/caddy/v2 v2.7.6
+ github.com/caddyserver/caddy/v2 v2.8.4
github.com/mastercactapus/proxyprotocol v0.0.4
github.com/things-go/go-socks5 v0.0.5
go.uber.org/zap v1.27.0
- golang.org/x/crypto v0.21.0
- golang.org/x/net v0.22.0
+ golang.org/x/crypto v0.23.0
+ golang.org/x/net v0.25.0
golang.org/x/time v0.5.0
)
require (
- filippo.io/edwards25519 v1.0.0 // indirect
+ filippo.io/edwards25519 v1.1.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
+ github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
- github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
+ github.com/alecthomas/chroma/v2 v2.13.0 // indirect
+ github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/caddyserver/certmagic v0.20.0 // indirect
+ github.com/caddyserver/certmagic v0.21.3 // indirect
+ github.com/caddyserver/zerossl v0.1.3 // indirect
+ github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
@@ -31,83 +35,117 @@ require (
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
+ github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/fxamacker/cbor/v2 v2.6.0 // indirect
+ github.com/go-chi/chi/v5 v5.0.12 // indirect
+ github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-kit/kit v0.13.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
+ github.com/go-logr/logr v1.4.1 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
- github.com/golang/glog v1.1.2 // indirect
- github.com/golang/protobuf v1.5.3 // indirect
+ github.com/golang/glog v1.2.0 // indirect
+ github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
- github.com/google/cel-go v0.17.1 // indirect
+ github.com/google/cel-go v0.20.1 // indirect
+ github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect
+ github.com/google/go-tpm v0.9.0 // indirect
+ github.com/google/go-tspi v0.3.0 // indirect
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
- github.com/google/uuid v1.3.1 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
- github.com/jackc/pgconn v1.14.1 // indirect
+ github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
- github.com/jackc/pgproto3/v2 v2.3.2 // indirect
+ github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
- github.com/jackc/pgx/v4 v4.18.1 // indirect
- github.com/klauspost/compress v1.17.1 // indirect
- github.com/klauspost/cpuid/v2 v2.2.5 // indirect
- github.com/libdns/libdns v0.2.1 // indirect
+ github.com/jackc/pgx/v4 v4.18.3 // indirect
+ github.com/klauspost/compress v1.17.8 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+ github.com/libdns/libdns v0.2.2 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
- github.com/mattn/go-isatty v0.0.19 // indirect
- github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
- github.com/mholt/acmez v1.2.0 // indirect
- github.com/micromdm/scep/v2 v2.1.0 // indirect
- github.com/miekg/dns v1.1.56 // indirect
+ github.com/mholt/acmez/v2 v2.0.1 // indirect
+ github.com/miekg/dns v1.1.59 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
+ github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
- github.com/prometheus/client_golang v1.17.0 // indirect
+ github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
- github.com/prometheus/common v0.44.0 // indirect
+ github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
- github.com/quic-go/quic-go v0.41.0 // indirect
+ github.com/quic-go/quic-go v0.44.0 // indirect
+ github.com/rs/xid v1.5.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
+ github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slackhq/nebula v1.7.2 // indirect
- github.com/smallstep/certificates v0.25.0 // indirect
- github.com/smallstep/nosql v0.6.0 // indirect
- github.com/smallstep/truststore v0.12.1 // indirect
+ github.com/smallstep/certificates v0.26.1 // indirect
+ github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect
+ github.com/smallstep/nosql v0.6.1 // indirect
+ github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
+ github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
+ github.com/smallstep/truststore v0.13.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
- github.com/spf13/cobra v1.7.0 // indirect
+ github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
- github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 // indirect
+ github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect
github.com/urfave/cli v1.22.14 // indirect
+ github.com/x448/float16 v0.8.4 // indirect
+ github.com/yuin/goldmark v1.7.1 // indirect
+ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
- go.etcd.io/bbolt v1.3.7 // indirect
- go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
- go.step.sm/cli-utils v0.8.0 // indirect
- go.step.sm/crypto v0.36.0 // indirect
+ go.etcd.io/bbolt v1.3.9 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
+ go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 // indirect
+ go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect
+ go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect
+ go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
+ go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
+ go.opentelemetry.io/otel v1.24.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect
+ go.opentelemetry.io/otel/metric v1.24.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.21.0 // indirect
+ go.opentelemetry.io/otel/trace v1.24.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.0.0 // indirect
+ go.step.sm/cli-utils v0.9.0 // indirect
+ go.step.sm/crypto v0.45.0 // indirect
go.step.sm/linkedca v0.20.1 // indirect
+ go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
- golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect
- golang.org/x/mod v0.15.0 // indirect
- golang.org/x/sys v0.18.0 // indirect
- golang.org/x/term v0.18.0 // indirect
- golang.org/x/text v0.14.0 // indirect
- golang.org/x/tools v0.17.0 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
- google.golang.org/grpc v1.59.0 // indirect
- google.golang.org/protobuf v1.31.0 // indirect
- gopkg.in/square/go-jose.v2 v2.6.0 // indirect
+ go.uber.org/zap/exp v0.2.0 // indirect
+ golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect
+ golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
+ golang.org/x/mod v0.17.0 // indirect
+ golang.org/x/sync v0.7.0 // indirect
+ golang.org/x/sys v0.20.0 // indirect
+ golang.org/x/term v0.20.0 // indirect
+ golang.org/x/text v0.15.0 // indirect
+ golang.org/x/tools v0.21.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect
+ google.golang.org/grpc v1.63.2 // indirect
+ google.golang.org/protobuf v1.34.1 // indirect
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
)
diff --git a/go.sum b/go.sum
index 50dc6b0..aa1a104 100644
--- a/go.sum
+++ b/go.sum
@@ -1,17 +1,23 @@
-cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o=
-cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
-cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
-cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
-cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
-cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4=
-cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
-cloud.google.com/go/kms v1.15.2 h1:lh6qra6oC4AyWe5fUUUBe/S27k12OHAleOOOw6KakdE=
-cloud.google.com/go/kms v1.15.2/go.mod h1:3hopT4+7ooWRCjc2DxgnpESFxhIraaI2IpAVUEhbT/w=
-filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
-filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
+cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
+cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
+cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
+cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
+cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
+cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
+cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
+cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
+cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
+cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
+cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
+cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
+cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
@@ -26,20 +32,57 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18=
-github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
+github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
+github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
+github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
+github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
+github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
+github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
+github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
+github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
+github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
+github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
-github.com/aws/aws-sdk-go v1.45.20 h1:U/wLZEwqVB6o2XlcJ7um8kczx+A1X2MgO2y4wdKDQTs=
-github.com/aws/aws-sdk-go v1.45.20/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
+github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
+github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A=
+github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
+github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0=
+github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ=
+github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs=
+github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
+github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns=
+github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
+github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
+github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
-github.com/caddyserver/caddy/v2 v2.7.6 h1:w0NymbG2m9PcvKWsrXO6EEkY9Ru4FJK8uQbYcev1p3A=
-github.com/caddyserver/caddy/v2 v2.7.6/go.mod h1:JCiwFMnRWjk8lOa7po0wM/75kwd38ccJPMSrXvQCMQ0=
-github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
-github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
+github.com/caddyserver/caddy/v2 v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=
+github.com/caddyserver/caddy/v2 v2.8.4/go.mod h1:vmDAHp3d05JIvuhc24LmnxVlsZmWnUwbP5WMjzcMPWw=
+github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
+github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
+github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
+github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
+github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
+github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -80,24 +123,39 @@ github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkz
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
+github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
+github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
+github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
+github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
+github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
-github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
-github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -107,31 +165,30 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
-github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
+github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
+github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
-github.com/google/cel-go v0.17.1 h1:s2151PDGy/eqpCI80/8dl4VL3xTkqI/YubXLXCFw0mw=
-github.com/google/cel-go v0.17.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
-github.com/google/certificate-transparency-go v1.1.6 h1:SW5K3sr7ptST/pIvNkSVWMiJqemRmkjJPPT0jzXdOOY=
-github.com/google/certificate-transparency-go v1.1.6/go.mod h1:0OJjOsOk+wj6aYQgP7FU0ioQ0AJUmnWPFMqTjQeazPQ=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
+github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
+github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
+github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
+github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
-github.com/google/go-tpm-tools v0.4.1 h1:gYU6iwRo0tY3V6NDnS6m+XYog+b3g6YFhHQl3sYaUL4=
-github.com/google/go-tpm-tools v0.4.1/go.mod h1:w03m0jynhTo7puXTYoyfpNOMqyQ9SB7sixnKWsS/1L0=
+github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
+github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
@@ -140,16 +197,17 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
-github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ=
-github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
-github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
-github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
-github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
+github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
+github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
@@ -169,9 +227,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
-github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4=
-github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
+github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
+github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
@@ -187,8 +244,8 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0=
-github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
+github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
@@ -202,25 +259,21 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
-github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
+github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
+github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
-github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
-github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g=
-github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
+github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
-github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
-github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -233,10 +286,11 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
-github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
+github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
@@ -250,18 +304,14 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
-github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
-github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
-github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
-github.com/micromdm/scep/v2 v2.1.0 h1:2fS9Rla7qRR266hvUoEauBJ7J6FhgssEiq2OkSKXmaU=
-github.com/micromdm/scep/v2 v2.1.0/go.mod h1:BkF7TkPPhmgJAMtHfP+sFTKXmgzNJgLQlvvGoOExBcc=
-github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
-github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
+github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
+github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
+github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
+github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@@ -279,24 +329,27 @@ github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8P
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
+github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
-github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
+github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
+github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
+github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
+github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
-github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
+github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
+github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
-github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
-github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
+github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
+github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
@@ -326,14 +379,18 @@ github.com/slackhq/nebula v1.7.2 h1:Rko1Mlksz/nC0c919xjGpB8uOSrTJ5e6KPgZx+lVfYw=
github.com/slackhq/nebula v1.7.2/go.mod h1:cnaoahkUipDs1vrNoIszyp0QPRIQN9Pm68ppQEW1Fhg=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
-github.com/smallstep/certificates v0.25.0 h1:WWihtjQ7SprnRxDV44mBp8t5SMsNO5EWsQaEwy1rgFg=
-github.com/smallstep/certificates v0.25.0/go.mod h1:thJmekMKUplKYip+la99Lk4IwQej/oVH/zS9PVMagEE=
-github.com/smallstep/go-attestation v0.4.4-0.20230627102604-cf579e53cbd2 h1:UIAS8DTWkeclraEGH2aiJPyNPu16VbT41w4JoBlyFfU=
-github.com/smallstep/go-attestation v0.4.4-0.20230627102604-cf579e53cbd2/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
-github.com/smallstep/nosql v0.6.0 h1:ur7ysI8s9st0cMXnTvB8tA3+x5Eifmkb6hl4uqNV5jc=
-github.com/smallstep/nosql v0.6.0/go.mod h1:jOXwLtockXORUPPZ2MCUcIkGR6w0cN1QGZniY9DITQA=
-github.com/smallstep/truststore v0.12.1 h1:guLUKkc1UlsXeS3t6BuVMa4leOOpdiv02PCRTiy1WdY=
-github.com/smallstep/truststore v0.12.1/go.mod h1:M4mebeNy28KusGX3lJxpLARIktLcyqBOrj3ZiZ46pqw=
+github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o=
+github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis=
+github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA=
+github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
+github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y=
+github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y=
+github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg=
+github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y=
+github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw=
+github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU=
+github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
+github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -343,8 +400,8 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
-github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
-github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -366,17 +423,25 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 h1:8rUlviSVOEe7TMk7W0gIPrW8MqEzYfZHpsNWSf8s2vg=
-github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 h1:pV0H+XIvFoP7pl1MRtyPXh5hqoxB5I7snOtTHgrn6HU=
+github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
+github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
@@ -384,23 +449,50 @@ github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvv
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
-go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
-go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
-go.mozilla.org/pkcs7 v0.0.0-20210730143726-725912489c62/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
-go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak=
-go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
+go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
+go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
-go.step.sm/cli-utils v0.8.0 h1:b/Tc1/m3YuQq+u3ghTFP7Dz5zUekZj6GUmd5pCvkEXQ=
-go.step.sm/cli-utils v0.8.0/go.mod h1:S77aISrC0pKuflqiDfxxJlUbiXcAanyJ4POOnzFSxD4=
-go.step.sm/crypto v0.36.0 h1:njrVQqjWVyCzHxqQUDprR7HXypZX8IxtOqe4IIHSjgU=
-go.step.sm/crypto v0.36.0/go.mod h1:XMRbgkhoigPLaPMjZeY1VFIYXVw03Wul0+5lENF6l7c=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
+go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w=
+go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y=
+go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c=
+go.opentelemetry.io/contrib/propagators/aws v1.17.0/go.mod h1:pAlCYRWff4uGqRXOVn3WP8pDZ5E0K56bEoG7a1VSL4k=
+go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo=
+go.opentelemetry.io/contrib/propagators/b3 v1.17.0/go.mod h1:IkfUfMpKWmynvvE0264trz0sf32NRTZL4nuAN9AbWRc=
+go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWEl3a6Xvd4tw/3xxGO1i05Y=
+go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA=
+go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0=
+go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk=
+go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
+go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
+go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
+go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
+go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
+go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
+go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
+go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
+go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
+go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
+go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
+go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
+go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc=
+go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY=
go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU=
go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
+go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
@@ -416,6 +508,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
+go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
@@ -428,18 +522,20 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
-golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
-golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
+golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
+golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
+golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
-golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -448,16 +544,16 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
-golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
-golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
-golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
+golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
-golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -475,21 +571,26 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -498,8 +599,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -512,30 +615,26 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
-golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
+golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.143.0 h1:o8cekTkqhywkbZT6p1UHJPZ9+9uuCAJs/KYomxZB8fA=
-google.golang.org/api v0.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8=
-google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE=
-google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k=
-google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
-google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
-google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
+google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
+google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
+google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
+google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk=
+google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
+google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
+google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -545,8 +644,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
-gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
-gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/integration/caddyfile_adapt/gd_empty.caddytest b/integration/caddyfile_adapt/gd_empty.caddytest
new file mode 100644
index 0000000..d445eaa
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_empty.caddytest
@@ -0,0 +1,11 @@
+{
+ layer4 {
+ # empty
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {}
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_handler_echo.caddytest b/integration/caddyfile_adapt/gd_handler_echo.caddytest
new file mode 100644
index 0000000..1565f39
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_handler_echo.caddytest
@@ -0,0 +1,32 @@
+{
+ layer4 {
+ 0.0.0.0:8888 {
+ route {
+ echo
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ "0.0.0.0:8888"
+ ],
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "echo"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_handler_proxy.caddytest b/integration/caddyfile_adapt/gd_handler_proxy.caddytest
new file mode 100644
index 0000000..2b5782d
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_handler_proxy.caddytest
@@ -0,0 +1,137 @@
+{
+ layer4 {
+ 0.0.0.0:8765 {
+ route {
+ proxy {
+ health_interval 1s
+ health_port 8080
+ health_timeout 2s
+ fail_duration 5s
+ max_fails 10
+ unhealthy_connection_count 5
+ lb_policy round_robin
+ lb_try_duration 5s
+ lb_try_interval 15s
+ proxy_protocol v2
+ upstream 10.0.0.1:8080
+ upstream 10.0.0.2:8080 10.0.0.2:8888
+ }
+ }
+ }
+ 0.0.0.0:9876 {
+ route {
+ proxy {
+ lb_policy random_choose 2
+ upstream {
+ dial 10.0.0.3:443 10.0.0.33:443
+ max_connections 2
+ tls
+ }
+ upstream {
+ dial 10.0.0.4:443 10.0.0.44:443
+ max_connections 4
+ tls_insecure_skip_verify
+ tls_renegotiation once
+ }
+ }
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ "0.0.0.0:8765"
+ ],
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "health_checks": {
+ "active": {
+ "interval": 1000000000,
+ "port": 8080,
+ "timeout": 2000000000
+ },
+ "passive": {
+ "fail_duration": 5000000000,
+ "max_fails": 10,
+ "unhealthy_connection_count": 5
+ }
+ },
+ "load_balancing": {
+ "selection": {
+ "policy": "round_robin"
+ },
+ "try_duration": 5000000000,
+ "try_interval": 15000000000
+ },
+ "proxy_protocol": "v2",
+ "upstreams": [
+ {
+ "dial": [
+ "10.0.0.1:8080"
+ ]
+ },
+ {
+ "dial": [
+ "10.0.0.2:8080",
+ "10.0.0.2:8888"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "srv1": {
+ "listen": [
+ "0.0.0.0:9876"
+ ],
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "load_balancing": {
+ "selection": {
+ "choose": 2,
+ "policy": "random_choose"
+ }
+ },
+ "upstreams": [
+ {
+ "dial": [
+ "10.0.0.3:443",
+ "10.0.0.33:443"
+ ],
+ "max_connections": 2,
+ "tls": {}
+ },
+ {
+ "dial": [
+ "10.0.0.4:443",
+ "10.0.0.44:443"
+ ],
+ "max_connections": 4,
+ "tls": {
+ "insecure_skip_verify": true,
+ "renegotiation": "once"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_handler_proxyprotocol.caddytest b/integration/caddyfile_adapt/gd_handler_proxyprotocol.caddytest
new file mode 100644
index 0000000..3f5cfc0
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_handler_proxyprotocol.caddytest
@@ -0,0 +1,88 @@
+{
+ layer4 {
+ 0.0.0.0:8080 {
+ route {
+ proxy_protocol {
+ allow 10.0.0.0/8
+ timeout 5s
+ }
+ proxy 10.0.0.1:8080
+ }
+ }
+ 0.0.0.0:8081 {
+ route {
+ proxy_protocol {
+ allow 10.0.0.0/8 192.168.0.0/16
+ timeout 3s
+ }
+ proxy 192.168.0.1:8080
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ "0.0.0.0:8080"
+ ],
+ "routes": [
+ {
+ "handle": [
+ {
+ "allow": [
+ "10.0.0.0/8"
+ ],
+ "handler": "proxy_protocol",
+ "timeout": 5000000000
+ },
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "10.0.0.1:8080"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "srv1": {
+ "listen": [
+ "0.0.0.0:8081"
+ ],
+ "routes": [
+ {
+ "handle": [
+ {
+ "allow": [
+ "10.0.0.0/8",
+ "192.168.0.0/16"
+ ],
+ "handler": "proxy_protocol",
+ "timeout": 3000000000
+ },
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "192.168.0.1:8080"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_handler_socks.caddytest b/integration/caddyfile_adapt/gd_handler_socks.caddytest
new file mode 100644
index 0000000..a9cfd96
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_handler_socks.caddytest
@@ -0,0 +1,62 @@
+{
+ layer4 {
+ :1080 {
+ @s5 socks5
+ route @s5 {
+ socks5 {
+ commands CONNECT
+ commands ASSOCIATE
+ credentials account1 password1 account2 password2
+ credentials account3 password3
+ }
+ }
+ route {
+ echo
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":1080"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "socks5": {}
+ }
+ ],
+ "handle": [
+ {
+ "commands": [
+ "CONNECT",
+ "ASSOCIATE"
+ ],
+ "credentials": {
+ "account1": "password1",
+ "account2": "password2",
+ "account3": "password3"
+ },
+ "handler": "socks5"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "echo"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_handler_subroute.caddytest b/integration/caddyfile_adapt/gd_handler_subroute.caddytest
new file mode 100644
index 0000000..7a6e2a7
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_handler_subroute.caddytest
@@ -0,0 +1,113 @@
+{
+ layer4 {
+ :443 {
+ @tls tls
+ route @tls {
+ subroute {
+ @abc tls sni abc.example.com
+ route @abc {
+ proxy abc.machine.local:443
+ }
+ @def tls sni def.example.com
+ @ghi tls sni ghi.example.com
+ route @def @ghi {
+ proxy defghi.machine.local:443
+ }
+ }
+ }
+ route {
+ echo
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "tls": {}
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "abc.machine.local:443"
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "tls": {
+ "sni": [
+ "abc.example.com"
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "defghi.machine.local:443"
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "tls": {
+ "sni": [
+ "def.example.com"
+ ]
+ }
+ },
+ {
+ "tls": {
+ "sni": [
+ "ghi.example.com"
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "echo"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_handler_tee.caddytest b/integration/caddyfile_adapt/gd_handler_tee.caddytest
new file mode 100644
index 0000000..6a3aa7d
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_handler_tee.caddytest
@@ -0,0 +1,81 @@
+{
+ layer4 {
+ 0.0.0.0:443 {
+ @external not remote_ip 192.168.0.0/16
+ route @external {
+ tee {
+ proxy primary.machine.local:443
+ proxy secondary.machine.local:443
+ }
+ }
+ route {
+ echo
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ "0.0.0.0:443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "not": [
+ {
+ "remote_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "branch": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "primary.machine.local:443"
+ ]
+ }
+ ]
+ },
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "secondary.machine.local:443"
+ ]
+ }
+ ]
+ }
+ ],
+ "handler": "tee"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "echo"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_handler_throttle.caddytest b/integration/caddyfile_adapt/gd_handler_throttle.caddytest
new file mode 100644
index 0000000..ad31b89
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_handler_throttle.caddytest
@@ -0,0 +1,54 @@
+{
+ layer4 {
+ :80 {
+ route {
+ throttle {
+ read_bytes_per_second 100000
+ total_read_bytes_per_second 500000
+ read_burst_size 20000
+ total_read_burst_size 100000
+ latency 2s
+ }
+ proxy localhost:8080
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":80"
+ ],
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "throttle",
+ "latency": 2000000000,
+ "read_burst_size": 20000,
+ "read_bytes_per_second": 100000,
+ "total_read_burst_size": 100000,
+ "total_read_bytes_per_second": 500000
+ },
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:8080"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_handler_tls.caddytest b/integration/caddyfile_adapt/gd_handler_tls.caddytest
new file mode 100644
index 0000000..665ad0e
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_handler_tls.caddytest
@@ -0,0 +1,247 @@
+{
+ layer4 {
+ :443 {
+ @h1 tls alpn http/1 http/1.1
+ route @h1 {
+ tls
+ subroute {
+ @alpha http host alpha.example.com
+ route @alpha {
+ proxy alpha.machine.local:80
+ }
+ @beta http host beta.example.com
+ route @beta {
+ proxy beta.machine.local:80
+ }
+ route {
+ proxy gamma.machine.local:80
+ }
+ }
+ }
+ @h2a tls {
+ alpn http/2
+ sni alpha.example.com
+ }
+ route @h2a {
+ tls {
+ connection_policy {
+ curves x25519
+ cert_selection {
+ serial_number 123456789012
+ }
+ }
+ }
+ proxy alpha.machine.local:80
+ }
+ @h2b tls {
+ alpn http/2
+ sni beta.example.com
+ }
+ route @h2b {
+ tls {
+ connection_policy {
+ curves secp256r1
+ cert_selection {
+ serial_number 123456789012
+ }
+ }
+ }
+ proxy beta.machine.local:80
+ }
+ route {
+ echo
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "tls": {
+ "alpn": [
+ "http/1",
+ "http/1.1"
+ ]
+ }
+ }
+ ],
+ "handle": [
+ {
+ "handler": "tls"
+ },
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "alpha.machine.local:80"
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "http": [
+ {
+ "host": [
+ "alpha.example.com"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "beta.machine.local:80"
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "http": [
+ {
+ "host": [
+ "beta.example.com"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "gamma.machine.local:80"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "match": [
+ {
+ "tls": {
+ "alpn": [
+ "http/2"
+ ],
+ "sni": [
+ "alpha.example.com"
+ ]
+ }
+ }
+ ],
+ "handle": [
+ {
+ "connection_policies": [
+ {
+ "certificate_selection": {
+ "serial_number": [
+ "123456789012"
+ ]
+ },
+ "curves": [
+ "x25519"
+ ]
+ }
+ ],
+ "handler": "tls"
+ },
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "alpha.machine.local:80"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "match": [
+ {
+ "tls": {
+ "alpn": [
+ "http/2"
+ ],
+ "sni": [
+ "beta.example.com"
+ ]
+ }
+ }
+ ],
+ "handle": [
+ {
+ "connection_policies": [
+ {
+ "certificate_selection": {
+ "serial_number": [
+ "123456789012"
+ ]
+ },
+ "curves": [
+ "secp256r1"
+ ]
+ }
+ ],
+ "handler": "tls"
+ },
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "beta.machine.local:80"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "echo"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_matcher_http.caddytest b/integration/caddyfile_adapt/gd_matcher_http.caddytest
new file mode 100644
index 0000000..c7f81de
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_matcher_http.caddytest
@@ -0,0 +1,121 @@
+{
+ layer4 {
+ :80 {
+ @a http host localhost
+ @b http {
+ host example.com
+ remote_ip 192.168.0.0/16
+ }
+ @c http not path /index.html
+ route @c {
+ proxy localhost:8083
+ }
+ route @a {
+ proxy localhost:8081
+ }
+ route @b {
+ proxy localhost:8082
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":80"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "http": [
+ {
+ "not": [
+ {
+ "path": [
+ "/index.html"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:8083"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "match": [
+ {
+ "http": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:8081"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "match": [
+ {
+ "http": [
+ {
+ "host": [
+ "example.com"
+ ],
+ "remote_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:8082"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_matcher_postgres.caddytest b/integration/caddyfile_adapt/gd_matcher_postgres.caddytest
new file mode 100644
index 0000000..2d2f3a3
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_matcher_postgres.caddytest
@@ -0,0 +1,62 @@
+{
+ layer4 {
+ :443 {
+ @a postgres
+ route @a {
+ proxy postgres.machine.local:443
+ }
+ route {
+ proxy fallback.machine.local:443
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "postgres": {}
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "postgres.machine.local:443"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "fallback.machine.local:443"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_matcher_proxyprotocol.caddytest b/integration/caddyfile_adapt/gd_matcher_proxyprotocol.caddytest
new file mode 100644
index 0000000..5edec79
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_matcher_proxyprotocol.caddytest
@@ -0,0 +1,62 @@
+{
+ layer4 {
+ :443 {
+ @a proxy_protocol
+ route @a {
+ proxy haproxy.machine.local:443
+ }
+ route {
+ proxy fallback.machine.local:443
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "proxy_protocol": {}
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "haproxy.machine.local:443"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "fallback.machine.local:443"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_matcher_rdp.caddytest b/integration/caddyfile_adapt/gd_matcher_rdp.caddytest
new file mode 100644
index 0000000..1b25a07
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_matcher_rdp.caddytest
@@ -0,0 +1,109 @@
+{
+ layer4 {
+ :443 {
+ @ch_jacob rdp {
+ cookie_hash jacob
+ }
+ @ci_jacob rdp {
+ custom_info_regexp ^(.*)jacob$
+ }
+ route @ch_jacob @ci_jacob {
+ proxy jacob.machine.local:3389
+ }
+ @ch_sarah rdp {
+ cookie_hash sarah
+ }
+ @ci_sarah rdp {
+ custom_info_regexp ^(.*)sarah$
+ }
+ route @ch_sarah @ci_sarah {
+ proxy sarah.machine.local:3389
+ }
+ route {
+ proxy fallback.machine.local:443
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "rdp": {
+ "cookie_hash": "jacob"
+ }
+ },
+ {
+ "rdp": {
+ "custom_info_regexp": "^(.*)jacob$"
+ }
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "jacob.machine.local:3389"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "match": [
+ {
+ "rdp": {
+ "cookie_hash": "sarah"
+ }
+ },
+ {
+ "rdp": {
+ "custom_info_regexp": "^(.*)sarah$"
+ }
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "sarah.machine.local:3389"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "fallback.machine.local:443"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_matcher_sets.caddytest b/integration/caddyfile_adapt/gd_matcher_sets.caddytest
new file mode 100644
index 0000000..069be3a
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_matcher_sets.caddytest
@@ -0,0 +1,100 @@
+{
+ layer4 {
+ 0.0.0.0:443 {
+ @a not remote_ip 192.168.0.0/16
+ @b {
+ local_ip 192.168.0.0/16
+ remote_ip 192.168.0.0/16
+ }
+ @c not local_ip 192.168.0.0/16
+ route @a {
+ proxy localhost:4431
+ }
+ route @b @c {
+ proxy localhost:4432
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ "0.0.0.0:443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "not": [
+ {
+ "remote_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:4431"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "match": [
+ {
+ "local_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ },
+ "remote_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ }
+ },
+ {
+ "not": [
+ {
+ "local_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:4432"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_matcher_socks.caddytest b/integration/caddyfile_adapt/gd_matcher_socks.caddytest
new file mode 100644
index 0000000..ad581d7
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_matcher_socks.caddytest
@@ -0,0 +1,109 @@
+{
+ layer4 {
+ :443 {
+ @s4 socks4 {
+ commands BIND GET
+ networks 10.0.0.0/8 fc::/7
+ ports 443 1080
+ }
+ route @s4 {
+ proxy socks4.machine.local:1080
+ }
+ @s5 socks5 {
+ auth_methods 1 2
+ }
+ route @s5 {
+ proxy socks5.machine.local:1080
+ }
+ route {
+ proxy fallback.machine.local:443
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "socks4": {
+ "commands": [
+ "BIND",
+ "GET"
+ ],
+ "networks": [
+ "10.0.0.0/8",
+ "fc::/7"
+ ],
+ "ports": [
+ 443,
+ 1080
+ ]
+ }
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "socks4.machine.local:1080"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "match": [
+ {
+ "socks5": {
+ "auth_methods": [
+ 1,
+ 2
+ ]
+ }
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "socks5.machine.local:1080"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "fallback.machine.local:443"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_matcher_ssh.caddytest b/integration/caddyfile_adapt/gd_matcher_ssh.caddytest
new file mode 100644
index 0000000..bf46400
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_matcher_ssh.caddytest
@@ -0,0 +1,66 @@
+{
+ layer4 {
+ :443 {
+ @a ssh
+ route @a {
+ proxy localhost:22
+ }
+ route {
+ tls
+ proxy localhost:8080
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "ssh": {}
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:22"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "tls"
+ },
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:8080"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_matcher_xmpp.caddytest b/integration/caddyfile_adapt/gd_matcher_xmpp.caddytest
new file mode 100644
index 0000000..c06e96f
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_matcher_xmpp.caddytest
@@ -0,0 +1,62 @@
+{
+ layer4 {
+ :80 {
+ @a xmpp
+ route @a {
+ proxy localhost:5222
+ }
+ route {
+ proxy localhost:8080
+ }
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":80"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "xmpp": {}
+ }
+ ],
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:5222"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:8080"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/gd_servers.caddytest b/integration/caddyfile_adapt/gd_servers.caddytest
new file mode 100644
index 0000000..68d614b
--- /dev/null
+++ b/integration/caddyfile_adapt/gd_servers.caddytest
@@ -0,0 +1,29 @@
+{
+ layer4 {
+ 0.0.0.0:443 {
+ # empty
+ }
+ [::]:8080 {
+ # empty
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "layer4": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ "0.0.0.0:443"
+ ]
+ },
+ "srv1": {
+ "listen": [
+ "[::]:8080"
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/lw_empty.caddytest b/integration/caddyfile_adapt/lw_empty.caddytest
new file mode 100644
index 0000000..ce4f3e8
--- /dev/null
+++ b/integration/caddyfile_adapt/lw_empty.caddytest
@@ -0,0 +1,44 @@
+{
+ servers {
+ listener_wrappers {
+ layer4
+ tls
+ }
+ }
+}
+:80 {
+ respond 200 "OK"
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":80"
+ ],
+ "listener_wrappers": [
+ {
+ "wrapper": "layer4"
+ },
+ {
+ "wrapper": "tls"
+ }
+ ],
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "200",
+ "handler": "static_response",
+ "status_code": "OK"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt/lw_matcher_sets.caddytest b/integration/caddyfile_adapt/lw_matcher_sets.caddytest
new file mode 100644
index 0000000..91a60a3
--- /dev/null
+++ b/integration/caddyfile_adapt/lw_matcher_sets.caddytest
@@ -0,0 +1,125 @@
+{
+ servers {
+ listener_wrappers {
+ layer4 {
+ @a not remote_ip 192.168.0.0/16
+ @b {
+ local_ip 192.168.0.0/16
+ remote_ip 192.168.0.0/16
+ }
+ @c not local_ip 192.168.0.0/16
+ route @a {
+ proxy localhost:4431
+ }
+ route @b @c {
+ proxy localhost:4432
+ }
+ }
+ tls
+ }
+ }
+}
+:443 {
+ respond 200 "OK"
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "listener_wrappers": [
+ {
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:4431"
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "not": [
+ {
+ "remote_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "proxy",
+ "upstreams": [
+ {
+ "dial": [
+ "localhost:4432"
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "local_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ },
+ "remote_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ }
+ },
+ {
+ "not": [
+ {
+ "local_ip": {
+ "ranges": [
+ "192.168.0.0/16"
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "wrapper": "layer4"
+ },
+ {
+ "wrapper": "tls"
+ }
+ ],
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "200",
+ "handler": "static_response",
+ "status_code": "OK"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration/caddyfile_adapt_test.go b/integration/caddyfile_adapt_test.go
new file mode 100644
index 0000000..b138b0c
--- /dev/null
+++ b/integration/caddyfile_adapt_test.go
@@ -0,0 +1,56 @@
+package integration
+
+import (
+ jsonMod "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "testing"
+
+ "github.com/caddyserver/caddy/v2/caddytest"
+ _ "github.com/mholt/caddy-l4"
+)
+
+func TestCaddyfileAdaptToJSON(t *testing.T) {
+ // load the list of test files from the dir
+ files, err := os.ReadDir("./caddyfile_adapt")
+ if err != nil {
+ t.Errorf("failed to read caddyfile_adapt dir: %s", err)
+ }
+
+ // prep a regexp to fix strings on windows
+ winNewlines := regexp.MustCompile(`\r?\n`)
+
+ for _, f := range files {
+ if f.IsDir() {
+ continue
+ }
+
+ // read the test file
+ filename := f.Name()
+ data, err := os.ReadFile("./caddyfile_adapt/" + filename)
+ if err != nil {
+ t.Errorf("failed to read %s dir: %s", filename, err)
+ }
+
+ // split the Caddyfile (first) and JSON (second) parts
+ // (append newline to Caddyfile to match formatter expectations)
+ parts := strings.Split(string(data), "----------")
+ caddyfile, json := strings.TrimSpace(parts[0])+"\n", strings.TrimSpace(parts[1])
+
+ // replace windows newlines in the json with unix newlines
+ json = winNewlines.ReplaceAllString(json, "\n")
+
+ // replace os-specific default path for file_server's hide field
+ replacePath, _ := jsonMod.Marshal(fmt.Sprint(".", string(filepath.Separator), "Caddyfile"))
+ json = strings.ReplaceAll(json, `"./Caddyfile"`, string(replacePath))
+
+ // run the test
+ ok := caddytest.CompareAdapt(t, filename, caddyfile, "caddyfile", json)
+ if !ok {
+ t.Errorf("failed to adapt %s", filename)
+ }
+ }
+}
diff --git a/layer4/caddyfile.go b/layer4/caddyfile.go
new file mode 100644
index 0000000..685f109
--- /dev/null
+++ b/layer4/caddyfile.go
@@ -0,0 +1,235 @@
+package layer4
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
+)
+
+func init() {
+ httpcaddyfile.RegisterGlobalOption("layer4", parseLayer4)
+}
+
+// parseLayer4 sets up the App from Caddyfile tokens. Syntax:
+//
+// {
+// layer4 {
+// # srv0
+// {
+// ...
+// }
+// # srv1
+// {
+// ...
+// }
+// }
+// }
+func parseLayer4(d *caddyfile.Dispenser, existingVal any) (any, error) {
+ app := &App{Servers: make(map[string]*Server)}
+
+ // Multiple global layer4 blocks are combined
+ if existingVal != nil {
+ appConfig, ok := existingVal.(httpcaddyfile.App)
+ if !ok {
+ return nil, d.Errf("existing %T config of unexpected type: %T", *app, existingVal)
+ }
+ err := json.Unmarshal(appConfig.Value, app)
+ if err != nil {
+ return nil, d.Errf("parsing existing %T config: %v", *app, err)
+ }
+ }
+
+ d.Next() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return nil, d.ArgErr()
+ }
+
+ i := len(app.Servers)
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ server := &Server{}
+ var inst interface{} = server
+ unm, ok := inst.(caddyfile.Unmarshaler)
+ if !ok {
+ return nil, d.Errf("%T is not a Caddyfile unmarshaler", inst)
+ }
+ if err := unm.UnmarshalCaddyfile(d); err != nil {
+ return nil, err
+ }
+ app.Servers["srv"+strconv.Itoa(i)] = server
+ i++
+ }
+
+ return httpcaddyfile.App{
+ Name: "layer4",
+ Value: caddyconfig.JSON(app, nil),
+ }, nil
+}
+
+// ParseCaddyfileNestedRoutes parses the Caddyfile tokens for nested named matcher sets, handlers and matching timeout,
+// composes a list of route configurations, and adjusts the matching timeout.
+func ParseCaddyfileNestedRoutes(d *caddyfile.Dispenser, routes *RouteList, matchingTimeout *caddy.Duration) error {
+ var hasMatchingTimeout bool
+ matcherSetTokensByName, routeTokens := make(map[string][]caddyfile.Token), make([]caddyfile.Token, 0)
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ if len(optionName) > 1 && optionName[0] == '@' {
+ if _, exists := matcherSetTokensByName[optionName]; exists {
+ return d.Errf("duplicate matcher set '%s'", d.Val())
+ }
+ matcherSetTokensByName[optionName] = append(matcherSetTokensByName[optionName], d.NextSegment()...)
+ } else if optionName == "matching_timeout" {
+ if hasMatchingTimeout {
+ return d.Errf("duplicate option '%s'", optionName)
+ }
+ if d.CountRemainingArgs() > 1 || !d.NextArg() {
+ return d.ArgErr()
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing option '%s' duration: %v", optionName, err)
+ }
+ *matchingTimeout, hasMatchingTimeout = caddy.Duration(dur), true
+ } else if optionName == "route" {
+ routeTokens = append(routeTokens, d.NextSegment()...)
+ } else {
+ return d.ArgErr()
+ }
+ }
+
+ matcherSetsByName := make(map[string]caddy.ModuleMap)
+ for matcherSetName, tokens := range matcherSetTokensByName {
+ dd := caddyfile.NewDispenser(tokens)
+ dd.Next() // consume wrapper name
+ if !dd.NextArg() && !dd.NextBlock(dd.Nesting()) {
+ return dd.ArgErr()
+ }
+
+ dd.Reset() // reset dispenser after argument/block checks above
+ dd.Next() // consume wrapper name again
+ matcherSet, err := ParseCaddyfileNestedMatcherSet(dd)
+ if err != nil {
+ return err
+ }
+ matcherSetsByName[matcherSetName] = matcherSet
+ }
+
+ dd := caddyfile.NewDispenser(routeTokens)
+ for dd.Next() { // consume route wrapper name
+ route := Route{}
+
+ if dd.CountRemainingArgs() > 0 {
+ for dd.NextArg() {
+ matcherSetName := dd.Val()
+ matcherSet, exists := matcherSetsByName[matcherSetName]
+ if !exists {
+ return dd.Errf("undefined matcher set '%s'", matcherSetName)
+ }
+ route.MatcherSetsRaw = append(route.MatcherSetsRaw, matcherSet)
+ }
+ }
+
+ if err := ParseCaddyfileNestedHandlers(dd, &route.HandlersRaw); err != nil {
+ return err
+ }
+ *routes = append(*routes, &route)
+ }
+
+ return nil
+}
+
+// ParseCaddyfileNestedHandlers parses the Caddyfile tokens for nested handlers,
+// and composes a list of their raw json configurations.
+func ParseCaddyfileNestedHandlers(d *caddyfile.Dispenser, handlersRaw *[]json.RawMessage) error {
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ handlerName := d.Val()
+
+ unm, err := caddyfile.UnmarshalModule(d, "layer4.handlers."+handlerName)
+ if err != nil {
+ return err
+ }
+ nh, ok := unm.(NextHandler)
+ if !ok {
+ return d.Errf("handler module '%s' is not a layer4 connection handler", handlerName)
+ }
+ handlerConfig := caddyconfig.JSON(nh, nil)
+
+ handlerConfig, err = SetModuleNameInline("handler", handlerName, handlerConfig)
+ if err != nil {
+ return err
+ }
+ *handlersRaw = append(*handlersRaw, handlerConfig)
+ }
+
+ return nil
+}
+
+// ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested matcher set,
+// and returns its raw module map value.
+func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
+ matcherMap := make(map[string]ConnMatcher)
+
+ tokensByMatcherName := make(map[string][]caddyfile.Token)
+ for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
+ matcherName := d.Val()
+ if _, exists := tokensByMatcherName[matcherName]; exists {
+ return nil, d.Errf("duplicate matcher module '%s'", matcherName)
+ }
+ tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
+ }
+
+ for matcherName, tokens := range tokensByMatcherName {
+ dd := caddyfile.NewDispenser(tokens)
+ dd.Next() // consume wrapper name
+
+ unm, err := caddyfile.UnmarshalModule(dd, "layer4.matchers."+matcherName)
+ if err != nil {
+ return nil, err
+ }
+ cm, ok := unm.(ConnMatcher)
+ if !ok {
+ return nil, d.Errf("matcher module '%s' is not a layer4 connection matcher", matcherName)
+ }
+ matcherMap[matcherName] = cm
+ }
+
+ matcherSet := make(caddy.ModuleMap)
+ for name, matcher := range matcherMap {
+ jsonBytes, err := json.Marshal(matcher)
+ if err != nil {
+ return nil, d.Errf("marshaling %T matcher: %v", matcher, err)
+ }
+ matcherSet[name] = jsonBytes
+ }
+
+ return matcherSet, nil
+}
+
+// SetModuleNameInline sets the string value of moduleNameKey to moduleName in raw,
+// where raw must be a JSON encoding of a map, and returns the modified raw.
+// In fact, it is a reverse function for caddy.getModuleNameInline.
+func SetModuleNameInline(moduleNameKey, moduleName string, raw json.RawMessage) (json.RawMessage, error) {
+ // temporarily unmarshal json into a map of string to any
+ var tmp map[string]any
+ err := json.Unmarshal(raw, &tmp)
+ if err != nil {
+ return nil, err
+ }
+
+ // add an inline key with the module name
+ tmp[moduleNameKey] = moduleName
+
+ // re-marshal the map into json
+ result, err := json.Marshal(tmp)
+ if err != nil {
+ return nil, fmt.Errorf("re-encoding module '%s' configuration: %v", moduleName, err)
+ }
+
+ return result, nil
+}
diff --git a/layer4/listener.go b/layer4/listener.go
index de60abe..451e749 100644
--- a/layer4/listener.go
+++ b/layer4/listener.go
@@ -10,6 +10,7 @@ import (
"time"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"go.uber.org/zap"
)
@@ -71,6 +72,43 @@ func (lw *ListenerWrapper) WrapListener(l net.Listener) net.Listener {
return li
}
+// UnmarshalCaddyfile sets up the ListenerWrapper from Caddyfile tokens. Syntax:
+//
+// layer4 {
+// matching_timeout
+// @a []
+// @b {
+// []
+// []
+// }
+// route @a @b {
+// []
+// }
+// @c {
+// []
+// }
+// route @c {
+// []
+// {
+// []
+// }
+// }
+// }
+func (lw *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ d.Next() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ if err := ParseCaddyfileNestedRoutes(d, &lw.Routes, &lw.MatchingTimeout); err != nil {
+ return err
+ }
+
+ return nil
+}
+
type listener struct {
net.Listener
logger *zap.Logger
@@ -185,4 +223,5 @@ func (tc *tlsConnection) ConnectionState() tls.ConnectionState {
var (
_ caddy.Module = (*ListenerWrapper)(nil)
_ caddy.ListenerWrapper = (*ListenerWrapper)(nil)
+ _ caddyfile.Unmarshaler = (*ListenerWrapper)(nil)
)
diff --git a/layer4/matchers.go b/layer4/matchers.go
index 6c049e8..12ea4bb 100644
--- a/layer4/matchers.go
+++ b/layer4/matchers.go
@@ -22,6 +22,7 @@ import (
"strings"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"go.uber.org/zap"
)
@@ -161,6 +162,34 @@ func (m MatchRemoteIP) getRemoteIP(cx *Connection) (netip.Addr, error) {
return ip, nil
}
+// UnmarshalCaddyfile sets up the MatchRemoteIP from Caddyfile tokens. Syntax:
+//
+// ip
+func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // At least one same-line option must be provided
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+
+ prefixes, err := ParseNetworks(d.RemainingArgs())
+ if err != nil {
+ return err
+ }
+
+ for _, prefix := range prefixes {
+ m.Ranges = append(m.Ranges, prefix.String())
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed layer4 connection matcher '%s': blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
// MatchLocalIP matches requests by local IP (or CIDR range).
type MatchLocalIP struct {
Ranges []string `json:"ranges,omitempty"`
@@ -215,6 +244,34 @@ func (m MatchLocalIP) getLocalIP(cx *Connection) (netip.Addr, error) {
return ip, nil
}
+// UnmarshalCaddyfile sets up the MatchLocalIP from Caddyfile tokens. Syntax:
+//
+// local_ip
+func (m *MatchLocalIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // At least one same-line option must be provided
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+
+ prefixes, err := ParseNetworks(d.RemainingArgs())
+ if err != nil {
+ return err
+ }
+
+ for _, prefix := range prefixes {
+ m.Ranges = append(m.Ranges, prefix.String())
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed layer4 connection matcher '%s': blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
// MatchNot matches requests by negating the results of its matcher
// sets. A single "not" matcher takes one or more matcher sets. Each
// matcher set is OR'ed; in other words, if any matcher set returns
@@ -294,20 +351,50 @@ func (m MatchNot) Match(r *Connection) (bool, error) {
return true, nil
}
+// UnmarshalCaddyfile sets up the MatchNot from Caddyfile tokens. Syntax:
+//
+// not {
+// {
+// []
+// }
+//
+// }
+// not {
+// []
+// }
+// not
+//
+// Note: all matchers inside a not block are parsed into a single matcher set, i.e. they are ANDed. Multiple matcher
+// sets, that are ORed, aren't supported. Instead, use multiple named matcher sets, each containing a not matcher.
+func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ d.Next() // consume wrapper name
+
+ matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
+ if err != nil {
+ return err
+ }
+ m.MatcherSetsRaw = append(m.MatcherSetsRaw, matcherSet)
+
+ return nil
+}
+
// Interface guards
var (
- _ caddy.Module = (*MatchRemoteIP)(nil)
- _ ConnMatcher = (*MatchRemoteIP)(nil)
- _ caddy.Provisioner = (*MatchRemoteIP)(nil)
- _ caddy.Module = (*MatchLocalIP)(nil)
- _ ConnMatcher = (*MatchLocalIP)(nil)
- _ caddy.Provisioner = (*MatchLocalIP)(nil)
- _ caddy.Module = (*MatchNot)(nil)
- _ caddy.Provisioner = (*MatchNot)(nil)
- _ ConnMatcher = (*MatchNot)(nil)
+ _ caddy.Module = (*MatchRemoteIP)(nil)
+ _ ConnMatcher = (*MatchRemoteIP)(nil)
+ _ caddy.Provisioner = (*MatchRemoteIP)(nil)
+ _ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
+ _ caddy.Module = (*MatchLocalIP)(nil)
+ _ ConnMatcher = (*MatchLocalIP)(nil)
+ _ caddy.Provisioner = (*MatchLocalIP)(nil)
+ _ caddyfile.Unmarshaler = (*MatchLocalIP)(nil)
+ _ caddy.Module = (*MatchNot)(nil)
+ _ caddy.Provisioner = (*MatchNot)(nil)
+ _ ConnMatcher = (*MatchNot)(nil)
+ _ caddyfile.Unmarshaler = (*MatchNot)(nil)
)
-// ParseNetworks parses a list of string IP addresses or CDIR subnets into a slice of net.IPNet's.
+// ParseNetworks parses a list of string IP addresses or CIDR subnets into a slice of net.IPNet's.
// It accepts for example ["127.0.0.1", "127.0.0.0/8", "::1", "2001:db8::/32"].
func ParseNetworks(networks []string) (ipNets []netip.Prefix, err error) {
for _, str := range networks {
diff --git a/layer4/server.go b/layer4/server.go
index 6563edd..6c5fd2c 100644
--- a/layer4/server.go
+++ b/layer4/server.go
@@ -23,6 +23,7 @@ import (
"time"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"go.uber.org/zap"
)
@@ -176,6 +177,45 @@ func (s Server) handle(conn net.Conn) {
)
}
+// UnmarshalCaddyfile sets up the Server from Caddyfile tokens. Syntax:
+//
+// {
+// matching_timeout
+// @a []
+// @b {
+// []
+// []
+// }
+// route @a @b {
+// []
+// }
+// @c {
+// []
+// }
+// route @c {
+// []
+// {
+// []
+// }
+// }
+// }
+func (s *Server) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ // Wrapper name and all same-line options are treated as network addresses
+ for ok := true; ok; ok = d.NextArg() {
+ addr := d.Val()
+ if _, err := caddy.ParseNetworkAddress(addr); err != nil {
+ return d.Errf("parsing network address '%s': %v", addr, err)
+ }
+ s.Listen = append(s.Listen, addr)
+ }
+
+ if err := ParseCaddyfileNestedRoutes(d, &s.Routes, &s.MatchingTimeout); err != nil {
+ return err
+ }
+
+ return nil
+}
+
type packet struct {
// The underlying bytes slice that was gotten from udpBufPool. It's up to
// packetConn to return it to udpBufPool once it's consumed.
@@ -279,3 +319,6 @@ var udpBufPool = sync.Pool{
return make([]byte, 9000)
},
}
+
+// Interface guard
+var _ caddyfile.Unmarshaler = (*Server)(nil)
diff --git a/modules/l4echo/echo.go b/modules/l4echo/echo.go
index ac80255..e425d6c 100644
--- a/modules/l4echo/echo.go
+++ b/modules/l4echo/echo.go
@@ -18,6 +18,7 @@ import (
"io"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
)
@@ -42,5 +43,27 @@ func (Handler) Handle(cx *layer4.Connection, _ layer4.Handler) error {
return err
}
-// Interface guard
-var _ layer4.NextHandler = (*Handler)(nil)
+// UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax:
+//
+// echo
+func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed layer4 connection handler '%s': blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
+// Interface guards
+var (
+ _ caddyfile.Unmarshaler = (*Handler)(nil)
+ _ layer4.NextHandler = (*Handler)(nil)
+)
diff --git a/modules/l4http/httpmatcher.go b/modules/l4http/httpmatcher.go
index c80fa7b..ed9ac33 100644
--- a/modules/l4http/httpmatcher.go
+++ b/modules/l4http/httpmatcher.go
@@ -24,6 +24,7 @@ import (
"net/url"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/mholt/caddy-l4/layer4"
"github.com/mholt/caddy-l4/modules/l4tls"
@@ -209,10 +210,38 @@ func (m MatchHTTP) handleHttp2WithPriorKnowledge(reader io.Reader, req *http.Req
return err
}
+// UnmarshalCaddyfile sets up the MatchHTTP from Caddyfile tokens. Syntax:
+//
+// http {
+// []
+// not []
+// not {
+// []
+// }
+// }
+// http []
+// http not []
+//
+// Note: as per https://caddyserver.com/docs/json/apps/http/servers/routes/match/,
+// matchers within a set are AND'ed together. Arguments of this http matcher constitute
+// a single matcher set, thus no OR logic is supported. Instead, use multiple http matchers.
+func (m *MatchHTTP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ d.Next() // consume wrapper name
+
+ matcherSet, err := caddyhttp.ParseCaddyfileNestedMatcherSet(d)
+ if err != nil {
+ return err
+ }
+ m.MatcherSetsRaw = append(m.MatcherSetsRaw, matcherSet)
+
+ return nil
+}
+
// Interface guards
var (
- _ layer4.ConnMatcher = (*MatchHTTP)(nil)
- _ caddy.Provisioner = (*MatchHTTP)(nil)
- _ json.Marshaler = (*MatchHTTP)(nil)
- _ json.Unmarshaler = (*MatchHTTP)(nil)
+ _ caddy.Provisioner = (*MatchHTTP)(nil)
+ _ caddyfile.Unmarshaler = (*MatchHTTP)(nil)
+ _ json.Marshaler = (*MatchHTTP)(nil)
+ _ json.Unmarshaler = (*MatchHTTP)(nil)
+ _ layer4.ConnMatcher = (*MatchHTTP)(nil)
)
diff --git a/modules/l4postgres/matcher.go b/modules/l4postgres/matcher.go
index 9bd7444..d5dfc94 100644
--- a/modules/l4postgres/matcher.go
+++ b/modules/l4postgres/matcher.go
@@ -29,6 +29,7 @@ import (
"io"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
)
@@ -129,5 +130,27 @@ func (m MatchPostgres) Match(cx *layer4.Connection) (bool, error) {
return len(startup.Parameters) > 0, nil
}
-// Interface guard
-var _ layer4.ConnMatcher = (*MatchPostgres)(nil)
+// UnmarshalCaddyfile sets up the MatchPostgres from Caddyfile tokens. Syntax:
+//
+// postgres
+func (m *MatchPostgres) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed layer4 connection matcher '%s': blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
+// Interface guards
+var (
+ _ layer4.ConnMatcher = (*MatchPostgres)(nil)
+ _ caddyfile.Unmarshaler = (*MatchPostgres)(nil)
+)
diff --git a/modules/l4proxy/loadbalancing.go b/modules/l4proxy/loadbalancing.go
index 237e572..f246617 100644
--- a/modules/l4proxy/loadbalancing.go
+++ b/modules/l4proxy/loadbalancing.go
@@ -20,10 +20,12 @@ import (
"hash/fnv"
weakrand "math/rand"
"net"
+ "strconv"
"sync/atomic"
"time"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
)
@@ -117,6 +119,25 @@ func (r RandomSelection) Select(pool UpstreamPool, conn *layer4.Connection) *Ups
return randomHost
}
+// UnmarshalCaddyfile sets up the RandomSelection from Caddyfile tokens. Syntax:
+//
+// random
+func (r *RandomSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed %s selection policy: blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
// RandomChoiceSelection is a policy that selects
// two or more available hosts at random, then
// chooses the one with the least load.
@@ -169,6 +190,34 @@ func (r RandomChoiceSelection) Select(pool UpstreamPool, _ *layer4.Connection) *
return leastConns(choices)
}
+// UnmarshalCaddyfile sets up the RandomChoiceSelection from Caddyfile tokens. Syntax:
+//
+// random_choose
+// random_choose
+func (r *RandomChoiceSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // Only one same-line option is supported
+ if d.CountRemainingArgs() > 1 {
+ return d.ArgErr()
+ }
+
+ if d.NextArg() {
+ val, err := strconv.ParseInt(d.Val(), 10, 32)
+ if err != nil {
+ return err
+ }
+ r.Choose = int(val)
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed %s selection policy: blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
// LeastConnSelection is a policy that selects the upstream
// with the least active connections. If multiple upstreams
// have the same fewest number, one is chosen randomly.
@@ -213,6 +262,25 @@ func (LeastConnSelection) Select(pool UpstreamPool, _ *layer4.Connection) *Upstr
return best
}
+// UnmarshalCaddyfile sets up the LeastConnSelection from Caddyfile tokens. Syntax:
+//
+// least_conn
+func (r *LeastConnSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed %s selection policy: blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
// RoundRobinSelection is a policy that selects
// a host based on round-robin ordering.
type RoundRobinSelection struct {
@@ -243,6 +311,25 @@ func (r *RoundRobinSelection) Select(pool UpstreamPool, _ *layer4.Connection) *U
return nil
}
+// UnmarshalCaddyfile sets up the RoundRobinSelection from Caddyfile tokens. Syntax:
+//
+// round_robin
+func (r *RoundRobinSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed %s selection policy: blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
// FirstSelection is a policy that selects
// the first available host.
type FirstSelection struct{}
@@ -265,6 +352,25 @@ func (FirstSelection) Select(pool UpstreamPool, _ *layer4.Connection) *Upstream
return nil
}
+// UnmarshalCaddyfile sets up the FirstSelection from Caddyfile tokens. Syntax:
+//
+// first
+func (r *FirstSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed %s selection policy: blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
// IPHashSelection is a policy that selects a host
// based on hashing the remote IP of the connection.
type IPHashSelection struct{}
@@ -287,6 +393,25 @@ func (IPHashSelection) Select(pool UpstreamPool, conn *layer4.Connection) *Upstr
return hostByHashing(pool, clientIP)
}
+// UnmarshalCaddyfile sets up the IPHashSelection from Caddyfile tokens. Syntax:
+//
+// ip_hash
+func (r *IPHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed %s selection policy: blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
// leastConns returns the upstream with the
// least number of active connections to it.
// If more than one upstream has the same
@@ -351,4 +476,11 @@ var (
_ caddy.Validator = (*RandomChoiceSelection)(nil)
_ caddy.Provisioner = (*RandomChoiceSelection)(nil)
+
+ _ caddyfile.Unmarshaler = (*RandomSelection)(nil)
+ _ caddyfile.Unmarshaler = (*RandomChoiceSelection)(nil)
+ _ caddyfile.Unmarshaler = (*LeastConnSelection)(nil)
+ _ caddyfile.Unmarshaler = (*RoundRobinSelection)(nil)
+ _ caddyfile.Unmarshaler = (*FirstSelection)(nil)
+ _ caddyfile.Unmarshaler = (*IPHashSelection)(nil)
)
diff --git a/modules/l4proxy/proxy.go b/modules/l4proxy/proxy.go
index d976e57..36a1eb5 100644
--- a/modules/l4proxy/proxy.go
+++ b/modules/l4proxy/proxy.go
@@ -17,16 +17,19 @@ package l4proxy
import (
"crypto/tls"
"fmt"
+ "github.com/caddyserver/caddy/v2/caddyconfig"
"io"
"io/ioutil"
"log"
"net"
"runtime/debug"
+ "strconv"
"sync"
"sync/atomic"
"time"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mastercactapus/proxyprotocol"
"github.com/mholt/caddy-l4/layer4"
"github.com/mholt/caddy-l4/modules/l4proxyprotocol"
@@ -368,6 +371,252 @@ func (h *Handler) Cleanup() error {
return nil
}
+// UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax:
+//
+// proxy [] {
+// # active health check options
+// health_interval
+// health_port
+// health_timeout
+//
+// # passive health check options
+// fail_duration
+// max_fails
+// unhealthy_connection_count
+//
+// # load balancing options
+// lb_policy []
+// lb_try_duration
+// lb_try_interval
+//
+// proxy_protocol
+//
+// # multiple upstream options are supported
+// upstream [] {
+// ...
+// }
+// upstream []
+// }
+func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // Treat all same-line options as upstream addresses
+ for i := 0; d.NextArg(); i++ {
+ val := d.Val()
+ _, err := caddy.ParseNetworkAddress(val)
+ if err != nil {
+ return d.Errf("parsing %s upstream on position %d: %v", wrapper, i, err)
+ }
+ h.Upstreams = append(h.Upstreams, &Upstream{Dial: []string{val}})
+ }
+
+ var (
+ hasHealthInterval, hasHealthPort, hasHealthTimeout bool // active health check options
+ hasFailDuration, hasMaxFails, hasUnhealthyConnCount bool // passive health check options
+ hasLBPolicy, hasLBTryDuration, hasLBTryInterval bool // load balancing options
+ hasProxyProtocol bool
+ )
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "health_interval":
+ if hasHealthInterval {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing %s option '%s' duration: %v", wrapper, optionName, err)
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = &HealthChecks{Active: &ActiveHealthChecks{}}
+ } else if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = &ActiveHealthChecks{}
+ }
+ h.HealthChecks.Active.Interval, hasHealthInterval = caddy.Duration(dur), true
+ case "health_port":
+ if hasHealthPort {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ val, err := strconv.ParseInt(d.Val(), 10, 32)
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = &HealthChecks{Active: &ActiveHealthChecks{}}
+ } else if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = &ActiveHealthChecks{}
+ }
+ h.HealthChecks.Active.Port, hasHealthPort = int(val), true
+ case "health_timeout":
+ if hasHealthTimeout {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing %s option '%s' duration: %v", wrapper, optionName, err)
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = &HealthChecks{Active: &ActiveHealthChecks{}}
+ } else if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = &ActiveHealthChecks{}
+ }
+ h.HealthChecks.Active.Timeout, hasHealthTimeout = caddy.Duration(dur), true
+ case "fail_duration":
+ if hasFailDuration {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing %s option '%s' duration: %v", wrapper, optionName, err)
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = &HealthChecks{Passive: &PassiveHealthChecks{}}
+ } else if h.HealthChecks.Passive == nil {
+ h.HealthChecks.Passive = &PassiveHealthChecks{}
+ }
+ h.HealthChecks.Passive.FailDuration, hasFailDuration = caddy.Duration(dur), true
+ case "max_fails":
+ if hasMaxFails {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ val, err := strconv.ParseInt(d.Val(), 10, 32)
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = &HealthChecks{Passive: &PassiveHealthChecks{}}
+ } else if h.HealthChecks.Passive == nil {
+ h.HealthChecks.Passive = &PassiveHealthChecks{}
+ }
+ h.HealthChecks.Passive.MaxFails, hasMaxFails = int(val), true
+ case "unhealthy_connection_count":
+ if hasUnhealthyConnCount {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ val, err := strconv.ParseInt(d.Val(), 10, 32)
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = &HealthChecks{Passive: &PassiveHealthChecks{}}
+ } else if h.HealthChecks.Passive == nil {
+ h.HealthChecks.Passive = &PassiveHealthChecks{}
+ }
+ h.HealthChecks.Passive.UnhealthyConnectionCount, hasUnhealthyConnCount = int(val), true
+ case "lb_policy":
+ if hasLBPolicy {
+ return d.Errf("duplicate proxy load_balancing option '%s'", optionName)
+ }
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ policyName := d.Val()
+
+ unm, err := caddyfile.UnmarshalModule(d, "layer4.proxy.selection_policies."+policyName)
+ if err != nil {
+ return err
+ }
+ us, ok := unm.(Selector)
+ if !ok {
+ return d.Errf("policy module '%s' is not an upstream selector", policyName)
+ }
+ policyRaw := caddyconfig.JSON(us, nil)
+
+ policyRaw, err = layer4.SetModuleNameInline("policy", policyName, policyRaw)
+ if err != nil {
+ return d.Errf("re-encoding module '%s' configuration: %v", policyName, err)
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = &LoadBalancing{}
+ }
+ h.LoadBalancing.SelectionPolicyRaw, hasLBPolicy = policyRaw, true
+ case "lb_try_duration":
+ if hasLBTryDuration {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing %s option '%s' duration: %v", wrapper, optionName, err)
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = &LoadBalancing{}
+ }
+ h.LoadBalancing.TryDuration, hasLBTryDuration = caddy.Duration(dur), true
+ case "lb_try_interval":
+ if hasLBTryInterval {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing %s option '%s' duration: %v", wrapper, optionName, err)
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = &LoadBalancing{}
+ }
+ h.LoadBalancing.TryInterval, hasLBTryInterval = caddy.Duration(dur), true
+ case "proxy_protocol":
+ if hasProxyProtocol {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ _, h.ProxyProtocol, hasProxyProtocol = d.NextArg(), d.Val(), true
+ switch h.ProxyProtocol {
+ case "v1", "v2":
+ continue
+ default:
+ return d.Errf("malformed %s option '%s': unrecognized value '%s'",
+ wrapper, optionName, h.ProxyProtocol)
+ }
+ case "upstream":
+ u := &Upstream{}
+ if err := u.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
+ return err
+ }
+ h.Upstreams = append(h.Upstreams, u)
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ return nil
+}
+
// peers is the global repository for peers that are
// currently in use by active configuration(s). This
// allows the state of remote hosts to be preserved
@@ -376,9 +625,10 @@ var peers = caddy.NewUsagePool()
// Interface guards
var (
- _ layer4.NextHandler = (*Handler)(nil)
- _ caddy.Provisioner = (*Handler)(nil)
- _ caddy.CleanerUpper = (*Handler)(nil)
+ _ caddy.CleanerUpper = (*Handler)(nil)
+ _ caddy.Provisioner = (*Handler)(nil)
+ _ caddyfile.Unmarshaler = (*Handler)(nil)
+ _ layer4.NextHandler = (*Handler)(nil)
)
// Used to properly shutdown half-closed connections (see PR #73).
diff --git a/modules/l4proxy/upstream.go b/modules/l4proxy/upstream.go
index 47d5cb1..d49eaa9 100644
--- a/modules/l4proxy/upstream.go
+++ b/modules/l4proxy/upstream.go
@@ -17,11 +17,16 @@ package l4proxy
import (
"crypto/tls"
"fmt"
+ "strconv"
"strings"
"sync/atomic"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
+ "github.com/caddyserver/caddy/v2/modules/caddytls"
+ "github.com/mholt/caddy-l4/layer4"
)
// UpstreamPool is a collection of upstreams.
@@ -154,6 +159,225 @@ func (u *Upstream) totalConns() int {
return totalConns
}
+// UnmarshalCaddyfile sets up the Upstream from Caddyfile tokens. Syntax:
+//
+// upstream [] {
+// dial []
+// max_connections
+//
+// tls
+// tls_client_auth |
+// tls_curves
+// tls_except_ports
+// tls_insecure_skip_verify
+// tls_renegotiation
+// tls_server_name
+// tls_timeout
+// tls_trust_pool
+//
+// # DEPRECATED:
+// tls_trusted_ca_certs
+// tls_trusted_ca_pool
+// }
+// upstream
+func (u *Upstream) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), "proxy "+d.Val() // consume wrapper name
+
+ // Treat all same-line options as dial arguments
+ shortcutArgs := d.RemainingArgs()
+
+ var (
+ hasMaxConnections, hasTLS bool
+ hasTLSTrustPool, hasTLSClientAuth bool
+ hasTLSInsecureSkipVerify, hasTLSTimeout bool
+ hasTLSRenegotiation, hasTLSServerName bool
+ )
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "dial":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ shortcutArgs = append(shortcutArgs, d.RemainingArgs()...)
+ case "max_connections":
+ if hasMaxConnections {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ val, err := strconv.ParseInt(d.Val(), 10, 32)
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ u.MaxConnections, hasMaxConnections = int(val), true
+ case "tls":
+ if hasTLS {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ hasTLS = true
+ case "tls_client_auth":
+ if hasTLSClientAuth {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ if d.CountRemainingArgs() == 1 {
+ _, u.TLS.ClientCertificateAutomate = d.NextArg(), d.Val()
+ } else if d.CountRemainingArgs() == 2 {
+ _, u.TLS.ClientCertificateFile = d.NextArg(), d.Val()
+ _, u.TLS.ClientCertificateKeyFile = d.NextArg(), d.Val()
+ } else {
+ return d.ArgErr()
+ }
+ hasTLSClientAuth = true
+ case "tls_curves":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ u.TLS.Curves = append(u.TLS.Curves, d.RemainingArgs()...)
+ case "tls_except_ports":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ u.TLS.ExceptPorts = append(u.TLS.ExceptPorts, d.RemainingArgs()...)
+ case "tls_insecure_skip_verify":
+ if hasTLSInsecureSkipVerify {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ u.TLS.InsecureSkipVerify, hasTLSInsecureSkipVerify = true, true
+ case "tls_renegotiation":
+ if hasTLSRenegotiation {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ _, u.TLS.Renegotiation, hasTLSRenegotiation = d.NextArg(), d.Val(), true
+
+ switch u.TLS.Renegotiation {
+ case "never", "once", "freely":
+ continue
+ default:
+ return d.Errf("malformed %s option '%s': unrecognized value '%s'",
+ wrapper, optionName, u.TLS.Renegotiation)
+ }
+ case "tls_server_name":
+ if hasTLSServerName {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ _, u.TLS.ServerName, hasTLSServerName = d.NextArg(), d.Val(), true
+ case "tls_timeout":
+ if hasTLSTimeout {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ val, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing %s option '%s' duration: %v", wrapper, optionName, err)
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ u.TLS.HandshakeTimeout, hasTLSTimeout = caddy.Duration(val), true
+ case "tls_trust_pool":
+ if hasTLSTrustPool {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ _, moduleName := d.NextArg(), d.Val()
+
+ unm, err := caddyfile.UnmarshalModule(d, "tls.ca_pool.source."+moduleName)
+ if err != nil {
+ return err
+ }
+ ca, ok := unm.(caddytls.CA)
+ if !ok {
+ return d.Errf("CA module '%s' is not a certificate pool provider", moduleName)
+ }
+ moduleRaw := caddyconfig.JSON(ca, nil)
+
+ moduleRaw, err = layer4.SetModuleNameInline("provider", moduleName, moduleRaw)
+ if err != nil {
+ return err
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ u.TLS.CARaw, hasTLSTrustPool = moduleRaw, true
+ case "tls_trusted_ca_certs": // DEPRECATED
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ u.TLS.RootCAPEMFiles = append(u.TLS.RootCAPEMFiles, d.RemainingArgs()...)
+ case "tls_trusted_ca_pool": // DEPRECATED
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ if u.TLS == nil {
+ u.TLS = &reverseproxy.TLSConfig{}
+ }
+ u.TLS.RootCAPool = append(u.TLS.RootCAPool, d.RemainingArgs()...)
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ shortcutOptionName := "dial"
+ if len(shortcutArgs) == 0 {
+ return d.Errf("malformed %s block: at least one %s address must be provided", wrapper, shortcutOptionName)
+ }
+ for _, arg := range shortcutArgs {
+ _, err := caddy.ParseNetworkAddress(arg)
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, shortcutOptionName, err)
+ }
+ u.Dial = append(u.Dial, arg)
+ }
+
+ return nil
+}
+
// peer holds the state for a singular proxy backend;
// must not be copied, because peers are singular
// (even if there is more than 1 instance of a config,
@@ -205,3 +429,6 @@ func (p *peer) setHealthy(healthy bool) (bool, error) {
swapped := atomic.CompareAndSwapInt32(&p.unhealthy, compare, unhealthy)
return swapped, nil
}
+
+// Interface guard
+var _ caddyfile.Unmarshaler = (*Upstream)(nil)
diff --git a/modules/l4proxyprotocol/handler.go b/modules/l4proxyprotocol/handler.go
index caee671..ad28be0 100644
--- a/modules/l4proxyprotocol/handler.go
+++ b/modules/l4proxyprotocol/handler.go
@@ -21,6 +21,7 @@ import (
"time"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mastercactapus/proxyprotocol"
"github.com/mholt/caddy-l4/layer4"
"go.uber.org/zap"
@@ -164,6 +165,63 @@ func (h *Handler) Handle(cx *layer4.Connection, next layer4.Handler) error {
return next.Handle(cx.Wrap(conn))
}
+// UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax:
+//
+// proxy_protocol {
+// allow
+// timeout
+// }
+//
+// proxy_protocol
+func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ var hasTimeout bool
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "allow":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ prefixes, err := layer4.ParseNetworks(d.RemainingArgs())
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ for _, prefix := range prefixes {
+ h.Allow = append(h.Allow, prefix.String())
+ }
+ case "timeout":
+ if hasTimeout {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing %s option '%s' duration: %v", wrapper, optionName, err)
+ }
+ h.Timeout, hasTimeout = caddy.Duration(dur), true
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ return nil
+}
+
// GetConn gets the connection which holds the information received from the PROXY protocol.
func GetConn(cx *layer4.Connection) net.Conn {
if val := cx.GetVar("l4.proxy_protocol.conn"); val != nil {
@@ -174,6 +232,7 @@ func GetConn(cx *layer4.Connection) net.Conn {
// Interface guards
var (
- _ caddy.Provisioner = (*Handler)(nil)
- _ layer4.NextHandler = (*Handler)(nil)
+ _ caddy.Provisioner = (*Handler)(nil)
+ _ caddyfile.Unmarshaler = (*Handler)(nil)
+ _ layer4.NextHandler = (*Handler)(nil)
)
diff --git a/modules/l4proxyprotocol/matcher.go b/modules/l4proxyprotocol/matcher.go
index 70d9019..e40028c 100644
--- a/modules/l4proxyprotocol/matcher.go
+++ b/modules/l4proxyprotocol/matcher.go
@@ -19,6 +19,7 @@ import (
"io"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
)
@@ -60,5 +61,27 @@ func (m MatchProxyProtocol) Match(cx *layer4.Connection) (bool, error) {
return false, nil
}
-// Interface guard
-var _ layer4.ConnMatcher = (*MatchProxyProtocol)(nil)
+// UnmarshalCaddyfile sets up the MatchProxyProtocol from Caddyfile tokens. Syntax:
+//
+// proxy_protocol
+func (m *MatchProxyProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed layer4 connection matcher '%s': blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
+// Interface guards
+var (
+ _ layer4.ConnMatcher = (*MatchProxyProtocol)(nil)
+ _ caddyfile.Unmarshaler = (*MatchProxyProtocol)(nil)
+)
diff --git a/modules/l4socks/socks4_matcher.go b/modules/l4socks/socks4_matcher.go
index da9da62..efbae5e 100644
--- a/modules/l4socks/socks4_matcher.go
+++ b/modules/l4socks/socks4_matcher.go
@@ -5,9 +5,11 @@ import (
"fmt"
"io"
"net/netip"
+ "strconv"
"strings"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
)
@@ -117,7 +119,68 @@ func (m *Socks4Matcher) Match(cx *layer4.Connection) (bool, error) {
return true, nil
}
+// UnmarshalCaddyfile sets up the Socks4Matcher from Caddyfile tokens. Syntax:
+//
+// socks4 {
+// commands
+// networks
+// ports
+// }
+//
+// socks4
+func (m *Socks4Matcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "commands":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ m.Commands = append(m.Commands, d.RemainingArgs()...)
+ case "networks":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ cidrs, err := layer4.ParseNetworks(d.RemainingArgs())
+ if err != nil {
+ return err
+ }
+ for _, cidr := range cidrs {
+ m.Networks = append(m.Networks, cidr.String())
+ }
+ case "ports":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ for d.NextArg() {
+ port, err := strconv.ParseUint(d.Val(), 10, 16)
+ if err != nil {
+ return d.WrapErr(err)
+ }
+ m.Ports = append(m.Ports, uint16(port))
+ }
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ return nil
+}
+
var (
- _ layer4.ConnMatcher = (*Socks4Matcher)(nil)
- _ caddy.Provisioner = (*Socks4Matcher)(nil)
+ _ layer4.ConnMatcher = (*Socks4Matcher)(nil)
+ _ caddy.Provisioner = (*Socks4Matcher)(nil)
+ _ caddyfile.Unmarshaler = (*Socks4Matcher)(nil)
)
diff --git a/modules/l4socks/socks5_handler.go b/modules/l4socks/socks5_handler.go
index 8bb1c33..62c1515 100644
--- a/modules/l4socks/socks5_handler.go
+++ b/modules/l4socks/socks5_handler.go
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
"github.com/things-go/go-socks5"
"go.uber.org/zap"
@@ -79,9 +80,75 @@ func (h *Socks5Handler) Handle(cx *layer4.Connection, _ layer4.Handler) error {
return h.server.ServeConn(cx)
}
+// UnmarshalCaddyfile sets up the Socks5Handler from Caddyfile tokens. Syntax:
+//
+// socks5 {
+// bind_ip <...>
+// commands <...>
+// credentials [ ]
+// }
+//
+// Note: multiple commands and credentials options are supported, but bind_ip option can only be provided once.
+// Only plain text passwords are currently supported.
+func (h *Socks5Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ var hasBindIP bool
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "bind_ip":
+ if hasBindIP {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ _, bindIP := d.NextArg(), net.ParseIP(d.Val())
+ if bindIP == nil {
+ return d.Errf("parsing %s option '%s': invalid IP address", wrapper, optionName)
+ }
+ h.BindIP, hasBindIP = bindIP.String(), true
+ case "commands":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ h.Commands = append(h.Commands, d.RemainingArgs()...)
+ case "credentials":
+ if d.CountRemainingArgs() == 0 || d.CountRemainingArgs()%2 != 0 {
+ return d.ArgErr()
+ }
+ if h.Credentials == nil {
+ h.Credentials = make(map[string]string)
+ }
+ for d.NextArg() {
+ username := d.Val()
+ if d.NextArg() {
+ h.Credentials[username] = d.Val()
+ }
+ }
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ return nil
+}
+
var (
- _ caddy.Provisioner = (*Socks5Handler)(nil)
- _ layer4.NextHandler = (*Socks5Handler)(nil)
+ _ caddy.Provisioner = (*Socks5Handler)(nil)
+ _ caddyfile.Unmarshaler = (*Socks5Handler)(nil)
+ _ layer4.NextHandler = (*Socks5Handler)(nil)
)
type socks5Logger struct {
diff --git a/modules/l4socks/socks5_matcher.go b/modules/l4socks/socks5_matcher.go
index f29afa9..4dc80c7 100644
--- a/modules/l4socks/socks5_matcher.go
+++ b/modules/l4socks/socks5_matcher.go
@@ -1,9 +1,12 @@
package l4socks
import (
+ "io"
+ "strconv"
+
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
- "io"
)
func init() {
@@ -15,7 +18,7 @@ func init() {
// use AuthMethods to exactly specify which METHODS you expect your clients to send.
// By default only the most common methods are matched NO AUTH, GSSAPI & USERNAME/PASSWORD.
type Socks5Matcher struct {
- AuthMethods []uint8 `json:"auth_methods,omitempty"`
+ AuthMethods []uint16 `json:"auth_methods,omitempty"`
}
func (Socks5Matcher) CaddyModule() caddy.ModuleInfo {
@@ -27,7 +30,7 @@ func (Socks5Matcher) CaddyModule() caddy.ModuleInfo {
func (m *Socks5Matcher) Provision(_ caddy.Context) error {
if len(m.AuthMethods) == 0 {
- m.AuthMethods = []uint8{0, 1, 2} // NO AUTH, GSSAPI, USERNAME/PASSWORD
+ m.AuthMethods = []uint16{0, 1, 2} // NO AUTH, GSSAPI, USERNAME/PASSWORD
}
return nil
}
@@ -57,7 +60,7 @@ func (m *Socks5Matcher) Match(cx *layer4.Connection) (bool, error) {
// match auth methods
for _, requestedMethod := range methods {
- if !contains(m.AuthMethods, requestedMethod) {
+ if !contains(m.AuthMethods, uint16(requestedMethod)) {
return false, nil
}
}
@@ -65,12 +68,55 @@ func (m *Socks5Matcher) Match(cx *layer4.Connection) (bool, error) {
return true, nil
}
+// UnmarshalCaddyfile sets up the Socks5Matcher from Caddyfile tokens. Syntax:
+//
+// socks5 {
+// auth_methods
+// }
+//
+// socks5
+func (m *Socks5Matcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "auth_methods":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ for d.NextArg() {
+ authMethod, err := strconv.ParseUint(d.Val(), 10, 8)
+ if err != nil {
+ return d.WrapErr(err)
+ }
+ m.AuthMethods = append(m.AuthMethods, uint16(authMethod))
+ }
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ return nil
+}
+
var (
- _ layer4.ConnMatcher = (*Socks5Matcher)(nil)
- _ caddy.Provisioner = (*Socks5Matcher)(nil)
+ _ layer4.ConnMatcher = (*Socks5Matcher)(nil)
+ _ caddy.Provisioner = (*Socks5Matcher)(nil)
+ _ caddyfile.Unmarshaler = (*Socks5Matcher)(nil)
)
-func contains(values []uint8, search uint8) bool {
+func contains(values []uint16, search uint16) bool {
for _, value := range values {
if value == search {
return true
diff --git a/modules/l4socks/socks5_matcher_test.go b/modules/l4socks/socks5_matcher_test.go
index dabf47e..b1ff95d 100644
--- a/modules/l4socks/socks5_matcher_test.go
+++ b/modules/l4socks/socks5_matcher_test.go
@@ -30,14 +30,14 @@ func TestSocks5Matcher_Match(t *testing.T) {
{matcher: &Socks5Matcher{}, data: []byte("Hello World"), shouldMatch: false},
// match only no auth
- {matcher: &Socks5Matcher{AuthMethods: []uint8{0}}, data: curlSocks5Example1, shouldMatch: false},
- {matcher: &Socks5Matcher{AuthMethods: []uint8{0}}, data: curlSocks5Example2, shouldMatch: false},
- {matcher: &Socks5Matcher{AuthMethods: []uint8{0}}, data: firefoxSocks5Example, shouldMatch: true},
+ {matcher: &Socks5Matcher{AuthMethods: []uint16{0}}, data: curlSocks5Example1, shouldMatch: false},
+ {matcher: &Socks5Matcher{AuthMethods: []uint16{0}}, data: curlSocks5Example2, shouldMatch: false},
+ {matcher: &Socks5Matcher{AuthMethods: []uint16{0}}, data: firefoxSocks5Example, shouldMatch: true},
// match custom auth
- {matcher: &Socks5Matcher{AuthMethods: []uint8{129}}, data: curlSocks5Example1, shouldMatch: false},
- {matcher: &Socks5Matcher{AuthMethods: []uint8{129}}, data: firefoxSocks5Example, shouldMatch: false},
- {matcher: &Socks5Matcher{AuthMethods: []uint8{129}}, data: []byte{0x05, 0x01, 0x81}, shouldMatch: true},
+ {matcher: &Socks5Matcher{AuthMethods: []uint16{129}}, data: curlSocks5Example1, shouldMatch: false},
+ {matcher: &Socks5Matcher{AuthMethods: []uint16{129}}, data: firefoxSocks5Example, shouldMatch: false},
+ {matcher: &Socks5Matcher{AuthMethods: []uint16{129}}, data: []byte{0x05, 0x01, 0x81}, shouldMatch: true},
}
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
diff --git a/modules/l4ssh/matcher.go b/modules/l4ssh/matcher.go
index af8aa07..7efbbfa 100644
--- a/modules/l4ssh/matcher.go
+++ b/modules/l4ssh/matcher.go
@@ -19,6 +19,7 @@ import (
"io"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
)
@@ -49,5 +50,27 @@ func (m MatchSSH) Match(cx *layer4.Connection) (bool, error) {
var sshPrefix = []byte("SSH-")
-// Interface guard
-var _ layer4.ConnMatcher = (*MatchSSH)(nil)
+// UnmarshalCaddyfile sets up the MatchSSH from Caddyfile tokens. Syntax:
+//
+// ssh
+func (m *MatchSSH) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed layer4 connection matcher '%s': blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
+// Interface guards
+var (
+ _ layer4.ConnMatcher = (*MatchSSH)(nil)
+ _ caddyfile.Unmarshaler = (*MatchSSH)(nil)
+)
diff --git a/modules/l4subroute/handler.go b/modules/l4subroute/handler.go
index 4bb67d2..2c5cb5e 100644
--- a/modules/l4subroute/handler.go
+++ b/modules/l4subroute/handler.go
@@ -19,6 +19,7 @@ import (
"time"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
"go.uber.org/zap"
)
@@ -75,8 +76,46 @@ func (h *Handler) Handle(cx *layer4.Connection, next layer4.Handler) error {
return subroute.Handle(cx)
}
+// UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax:
+//
+// subroute {
+// matching_timeout
+// @a []
+// @b {
+// []
+// []
+// }
+// route @a @b {
+// []
+// }
+// @c {
+// []
+// }
+// route @c {
+// []
+// {
+// []
+// }
+// }
+// }
+func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ d.Next() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ if err := layer4.ParseCaddyfileNestedRoutes(d, &h.Routes, &h.MatchingTimeout); err != nil {
+ return err
+ }
+
+ return nil
+}
+
// Interface guards
var (
- _ caddy.Provisioner = (*Handler)(nil)
- _ layer4.NextHandler = (*Handler)(nil)
+ _ caddy.Provisioner = (*Handler)(nil)
+ _ caddyfile.Unmarshaler = (*Handler)(nil)
+ _ layer4.NextHandler = (*Handler)(nil)
)
diff --git a/modules/l4tee/tee.go b/modules/l4tee/tee.go
index 73b321c..3193630 100644
--- a/modules/l4tee/tee.go
+++ b/modules/l4tee/tee.go
@@ -20,6 +20,7 @@ import (
"net"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
"go.uber.org/zap"
)
@@ -110,6 +111,27 @@ func (t Handler) Handle(cx *layer4.Connection, next layer4.Handler) error {
return next.Handle(&nextc)
}
+// UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax:
+//
+// tee {
+//
+// []
+// }
+func (t *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ d.Next() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ if err := layer4.ParseCaddyfileNestedHandlers(d, &t.HandlersRaw); err != nil {
+ return err
+ }
+
+ return nil
+}
+
// teeConn is a connection wrapper that reads
// from a different reader.
type teeConn struct {
@@ -138,5 +160,8 @@ func (nc nextConn) Read(p []byte) (n int, err error) {
return
}
-// Interface guard
-var _ layer4.NextHandler = (*Handler)(nil)
+// Interface guards
+var (
+ _ caddyfile.Unmarshaler = (*Handler)(nil)
+ _ layer4.NextHandler = (*Handler)(nil)
+)
diff --git a/modules/l4throttle/throttle.go b/modules/l4throttle/throttle.go
index f4454d3..712642c 100644
--- a/modules/l4throttle/throttle.go
+++ b/modules/l4throttle/throttle.go
@@ -18,9 +18,11 @@ import (
"context"
"fmt"
"net"
+ "strconv"
"time"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
"go.uber.org/zap"
"golang.org/x/time/rate"
@@ -114,6 +116,105 @@ func (h Handler) Handle(cx *layer4.Connection, next layer4.Handler) error {
return next.Handle(cx)
}
+// UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax:
+//
+// throttle {
+// latency
+// read_burst_size
+// read_bytes_per_second
+// total_read_burst_size
+// total_read_bytes_per_second
+// }
+func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ var hasLatency, hasReadBurstSize, hasReadBytesPerSecond, hasTotalReadBurstSize, hasTotalReadBytesPerSecond bool
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "latency":
+ if hasLatency {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg() // consume option value
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing %s option '%s' duration: %v", wrapper, optionName, err)
+ }
+ h.Latency, hasLatency = caddy.Duration(dur), true
+ case "read_burst_size":
+ if hasReadBurstSize {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg() // consume option value
+ val, err := strconv.ParseInt(d.Val(), 10, 32)
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ h.ReadBurstSize, hasReadBurstSize = int(val), true
+ case "read_bytes_per_second":
+ if hasReadBytesPerSecond {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg() // consume option value
+ val, err := strconv.ParseFloat(d.Val(), 64)
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ h.ReadBytesPerSecond, hasReadBytesPerSecond = val, true
+ case "total_read_burst_size":
+ if hasTotalReadBurstSize {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg() // consume option value
+ val, err := strconv.ParseInt(d.Val(), 10, 32)
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ h.TotalReadBurstSize, hasTotalReadBurstSize = int(val), true
+ case "total_read_bytes_per_second":
+ if hasTotalReadBytesPerSecond {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg() // consume option value
+ val, err := strconv.ParseFloat(d.Val(), 64)
+ if err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ h.TotalReadBytesPerSecond, hasTotalReadBytesPerSecond = val, true
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ return nil
+}
+
type throttledConn struct {
net.Conn
ctx context.Context
@@ -163,6 +264,7 @@ func (tc throttledConn) Read(p []byte) (int, error) {
// Interface guards
var (
- _ caddy.Provisioner = (*Handler)(nil)
- _ layer4.NextHandler = (*Handler)(nil)
+ _ caddy.Provisioner = (*Handler)(nil)
+ _ caddyfile.Unmarshaler = (*Handler)(nil)
+ _ layer4.NextHandler = (*Handler)(nil)
)
diff --git a/modules/l4tls/alpn_matcher.go b/modules/l4tls/alpn_matcher.go
index 24bb537..0574317 100644
--- a/modules/l4tls/alpn_matcher.go
+++ b/modules/l4tls/alpn_matcher.go
@@ -18,6 +18,7 @@ import (
"crypto/tls"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
@@ -47,7 +48,31 @@ func (m MatchALPN) Match(hello *tls.ClientHelloInfo) bool {
return false
}
+// UnmarshalCaddyfile sets up the MatchALPN from Caddyfile tokens. Syntax:
+//
+// alpn
+func (m *MatchALPN) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ wrapper := d.Val()
+
+ // At least one same-line option must be provided
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+
+ *m = append(*m, d.RemainingArgs()...)
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
+ }
+ }
+
+ return nil
+}
+
// Interface guards
var (
_ caddytls.ConnectionMatcher = (*MatchALPN)(nil)
+ _ caddyfile.Unmarshaler = (*MatchALPN)(nil)
)
diff --git a/modules/l4tls/handler.go b/modules/l4tls/handler.go
index 7c77412..dd467df 100644
--- a/modules/l4tls/handler.go
+++ b/modules/l4tls/handler.go
@@ -16,9 +16,12 @@ package l4tls
import (
"crypto/tls"
+ "encoding/json"
"fmt"
+ "math/big"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/mholt/caddy-l4/layer4"
"go.uber.org/zap"
@@ -104,6 +107,47 @@ func (t *Handler) Handle(cx *layer4.Connection, next layer4.Handler) error {
return next.Handle(cx.Wrap(tlsConn))
}
+// UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax:
+//
+// tls {
+// connection_policy {
+// ...
+// }
+// connection_policy {
+// ...
+// }
+// }
+// tls
+func (t *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "connection_policy":
+ cp := &caddytls.ConnectionPolicy{}
+ if err := unmarshalCaddyfileConnectionPolicy(d.NewFromNextSegment(), cp); err != nil {
+ return err
+ }
+ t.ConnectionPolicies = append(t.ConnectionPolicies, cp)
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ return nil
+}
+
func appendClientHello(cx *layer4.Connection, chi ClientHelloInfo) {
var clientHellos []ClientHelloInfo
if val := cx.GetVar("tls_client_hellos"); val != nil {
@@ -142,6 +186,227 @@ func GetConnectionStates(cx *layer4.Connection) []*tls.ConnectionState {
// Interface guards
var (
- _ caddy.Provisioner = (*Handler)(nil)
- _ layer4.NextHandler = (*Handler)(nil)
+ _ caddy.Provisioner = (*Handler)(nil)
+ _ caddyfile.Unmarshaler = (*Handler)(nil)
+ _ layer4.NextHandler = (*Handler)(nil)
)
+
+// TODO: move to https://github.com/caddyserver/caddy/tree/master/modules/caddytls/connpolicy.go
+// unmarshalCaddyfileConnectionPolicy sets up the ConnectionPolicy from Caddyfile tokens. Syntax:
+//
+// connection_policy {
+// alpn
+// cert_selection {
+// ...
+// }
+// ciphers
+// client_auth {
+// ...
+// }
+// curves
+// default_sni
+// match {
+// ...
+// }
+// protocols []
+// # EXPERIMENTAL:
+// drop
+// fallback_sni
+// insecure_secrets_log
+// }
+func unmarshalCaddyfileConnectionPolicy(d *caddyfile.Dispenser, cp *caddytls.ConnectionPolicy) error {
+ _, wrapper := d.Next(), "tls "+d.Val()
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ var (
+ hasCertSelection, hasClientAuth, hasDefaultSNI, hasDrop,
+ hasFallbackSNI, hasInsecureSecretsLog, hasMatch, hasProtocols bool
+ )
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "alpn":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ cp.ALPN = append(cp.ALPN, d.RemainingArgs()...)
+ case "cert_selection":
+ if hasCertSelection {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ p := &caddytls.CustomCertSelectionPolicy{}
+ if err := unmarshalCaddyfileCertSelection(d.NewFromNextSegment(), p); err != nil {
+ return err
+ }
+ cp.CertSelection, hasCertSelection = p, true
+ case "client_auth":
+ if hasClientAuth {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ ca := &caddytls.ClientAuthentication{}
+ if err := ca.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
+ return err
+ }
+ cp.ClientAuthentication, hasClientAuth = ca, true
+ case "ciphers":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ cp.CipherSuites = append(cp.CipherSuites, d.RemainingArgs()...)
+ case "curves":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ cp.Curves = append(cp.Curves, d.RemainingArgs()...)
+ case "default_sni":
+ if hasDefaultSNI {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ _, cp.DefaultSNI, hasDefaultSNI = d.NextArg(), d.Val(), true
+ case "drop": // EXPERIMENTAL
+ if hasDrop {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ cp.Drop, hasDrop = true, true
+ case "fallback_sni": // EXPERIMENTAL
+ if hasFallbackSNI {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ _, cp.FallbackSNI, hasFallbackSNI = d.NextArg(), d.Val(), true
+ case "insecure_secrets_log": // EXPERIMENTAL
+ if hasInsecureSecretsLog {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ _, cp.InsecureSecretsLog, hasInsecureSecretsLog = d.NextArg(), d.Val(), true
+ case "match":
+ if hasMatch {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
+ if err != nil {
+ return err
+ }
+ cp.MatchersRaw, hasMatch = matcherSet, true
+ case "protocols":
+ if hasProtocols {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() == 0 || d.CountRemainingArgs() > 2 {
+ return d.ArgErr()
+ }
+ _, cp.ProtocolMin, hasProtocols = d.NextArg(), d.Val(), true
+ if d.NextArg() {
+ cp.ProtocolMax = d.Val()
+ }
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ return nil
+}
+
+// TODO: move to https://github.com/caddyserver/caddy/tree/master/modules/caddytls/certselection.go
+// unmarshalCaddyfileCertSelection sets up the CustomCertSelectionPolicy from Caddyfile tokens. Syntax:
+//
+// cert_selection {
+// all_tags
+// any_tag
+// public_key_algorithm
+// serial_number
+// subject_organization
+// }
+func unmarshalCaddyfileCertSelection(d *caddyfile.Dispenser, p *caddytls.CustomCertSelectionPolicy) error {
+ _, wrapper := d.Next(), "tls connection_policy "+d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ var hasPublicKeyAlgorithm bool
+ serialNumberStrings := make([]string, 0)
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ optionName := d.Val()
+ switch optionName {
+ case "all_tags":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ p.AllTags = append(p.AllTags, d.RemainingArgs()...)
+ case "any_tag":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ p.AnyTag = append(p.AnyTag, d.RemainingArgs()...)
+ case "public_key_algorithm":
+ if hasPublicKeyAlgorithm {
+ return d.Errf("duplicate %s option '%s'", wrapper, optionName)
+ }
+ if d.CountRemainingArgs() != 1 {
+ return d.ArgErr()
+ }
+ d.NextArg()
+ if err := p.PublicKeyAlgorithm.UnmarshalJSON([]byte(d.Val())); err != nil {
+ return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
+ }
+ hasPublicKeyAlgorithm = true
+ case "serial_number":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ for d.NextArg() {
+ val, bi := d.Val(), struct{ big.Int }{}
+ _, ok := bi.SetString(val, 10)
+ if !ok {
+ return d.Errf("parsing %s option '%s': invalid big.int value %s", wrapper, optionName, val)
+ }
+ serialNumberStrings = append(serialNumberStrings, bi.String())
+ }
+ case "subject_organization":
+ if d.CountRemainingArgs() == 0 {
+ return d.ArgErr()
+ }
+ p.SubjectOrganization = append(p.SubjectOrganization, d.RemainingArgs()...)
+ default:
+ return d.ArgErr()
+ }
+
+ // No nested blocks are supported
+ if d.NextBlock(nesting + 1) {
+ return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
+ }
+ }
+
+ // caddytls.bigInt struct is not exported. That's why we can't append directly to SerialNumber list above.
+ // TODO: remove this workaround after the code is moved to caddyserver/caddy repo
+ if len(serialNumberStrings) > 0 {
+ serialNumbersRaw, err := json.Marshal(serialNumberStrings)
+ if err != nil {
+ return err
+ }
+ if err = json.Unmarshal(serialNumbersRaw, &p.SerialNumber); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/modules/l4tls/matcher.go b/modules/l4tls/matcher.go
index 8c20a59..eaea266 100644
--- a/modules/l4tls/matcher.go
+++ b/modules/l4tls/matcher.go
@@ -20,6 +20,7 @@ import (
"io"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/mholt/caddy-l4/layer4"
"go.uber.org/zap"
@@ -122,10 +123,207 @@ func (m MatchTLS) Match(cx *layer4.Connection) (bool, error) {
return true, nil
}
+// UnmarshalCaddyfile sets up the MatchTLS from Caddyfile tokens. Syntax:
+//
+// tls {
+// matcher []
+// matcher []
+// }
+// tls matcher []
+// tls
+func (m *MatchTLS) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ d.Next() // consume wrapper name
+
+ matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
+ if err != nil {
+ return err
+ }
+ m.MatchersRaw = matcherSet
+
+ return nil
+}
+
// Interface guards
var (
- _ layer4.ConnMatcher = (*MatchTLS)(nil)
- _ caddy.Provisioner = (*MatchTLS)(nil)
- _ json.Marshaler = (*MatchTLS)(nil)
- _ json.Unmarshaler = (*MatchTLS)(nil)
+ _ layer4.ConnMatcher = (*MatchTLS)(nil)
+ _ caddy.Provisioner = (*MatchTLS)(nil)
+ _ caddyfile.Unmarshaler = (*MatchTLS)(nil)
+ _ json.Marshaler = (*MatchTLS)(nil)
+ _ json.Unmarshaler = (*MatchTLS)(nil)
)
+
+// ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested
+// matcher set, and returns its raw module map value.
+func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
+ matcherMap := make(map[string]caddytls.ConnectionMatcher)
+
+ tokensByMatcherName := make(map[string][]caddyfile.Token)
+ for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
+ matcherName := d.Val()
+ tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
+ }
+
+ for matcherName, tokens := range tokensByMatcherName {
+ dd := caddyfile.NewDispenser(tokens)
+ dd.Next() // consume wrapper name
+ // TODO: delete this workaround when the corresponding matchers implement caddyfile.Unmarshaler interface
+ if matcherName == "local_ip" {
+ cm, err := unmarshalCaddyfileMatchLocalIP(dd.NewFromNextSegment())
+ if err != nil {
+ return nil, err
+ }
+ matcherMap[matcherName] = cm
+ } else if matcherName == "remote_ip" {
+ cm, err := unmarshalCaddyfileMatchRemoteIP(dd.NewFromNextSegment())
+ if err != nil {
+ return nil, err
+ }
+ matcherMap[matcherName] = cm
+ } else if matcherName == "sni" {
+ cm, err := unmarshalCaddyfileMatchServerName(dd.NewFromNextSegment())
+ if err != nil {
+ return nil, err
+ }
+ matcherMap[matcherName] = cm
+ } else {
+ mod, err := caddy.GetModule("tls.handshake_match." + matcherName)
+ if err != nil {
+ return nil, d.Errf("getting matcher module '%s': %v", matcherName, err)
+ }
+ unm, ok := mod.New().(caddyfile.Unmarshaler)
+ if !ok {
+ return nil, d.Errf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
+ }
+ err = unm.UnmarshalCaddyfile(dd.NewFromNextSegment())
+ if err != nil {
+ return nil, err
+ }
+ cm, ok := unm.(caddytls.ConnectionMatcher)
+ if !ok {
+ return nil, fmt.Errorf("matcher module '%s' is not a connection matcher", matcherName)
+ }
+ matcherMap[matcherName] = cm
+ }
+ }
+
+ matcherSet := make(caddy.ModuleMap)
+ for name, matcher := range matcherMap {
+ jsonBytes, err := json.Marshal(matcher)
+ if err != nil {
+ return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
+ }
+ matcherSet[name] = jsonBytes
+ }
+
+ return matcherSet, nil
+}
+
+// TODO: move to https://github.com/caddyserver/caddy/tree/master/modules/caddytls/matchers.go
+// unmarshalCaddyfileMatchLocalIP sets up the MatchLocalIP from Caddyfile tokens. Syntax:
+//
+// local_ip
+func unmarshalCaddyfileMatchLocalIP(d *caddyfile.Dispenser) (*caddytls.MatchLocalIP, error) {
+ m := caddytls.MatchLocalIP{}
+
+ for d.Next() {
+ wrapper := d.Val()
+
+ // At least one same-line option must be provided
+ if d.CountRemainingArgs() == 0 {
+ return nil, d.ArgErr()
+ }
+
+ prefixes, err := layer4.ParseNetworks(d.RemainingArgs())
+ if err != nil {
+ return nil, err
+ }
+ for _, prefix := range prefixes {
+ m.Ranges = append(m.Ranges, prefix.String())
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return nil, d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
+ }
+ }
+
+ return &m, nil
+}
+
+// TODO: move to https://github.com/caddyserver/caddy/tree/master/modules/caddytls/matchers.go
+// unmarshalCaddyfileMatchRemoteIP sets up the MatchRemoteIP from Caddyfile tokens. Syntax:
+//
+// remote_ip
+//
+// Note: IPs and CIDRs starting with ! symbol are treated as not_ranges
+func unmarshalCaddyfileMatchRemoteIP(d *caddyfile.Dispenser) (*caddytls.MatchRemoteIP, error) {
+ m := caddytls.MatchRemoteIP{}
+
+ for d.Next() {
+ wrapper := d.Val()
+
+ // At least one same-line option must be provided
+ if d.CountRemainingArgs() == 0 {
+ return nil, d.ArgErr()
+ }
+
+ rangesRaw, notRangesRaw := make([]string, 0, d.CountRemainingArgs()), make([]string, 0, d.CountRemainingArgs())
+ for d.NextArg() {
+ val := d.Val()
+ if len(val) > 1 && val[0] == '!' {
+ notRangesRaw = append(notRangesRaw, val[1:])
+ } else {
+ rangesRaw = append(rangesRaw, val)
+ }
+ }
+
+ prefixes, err := layer4.ParseNetworks(rangesRaw)
+ if err != nil {
+ return nil, err
+ }
+ for _, prefix := range prefixes {
+ m.Ranges = append(m.Ranges, prefix.String())
+ }
+
+ notPrefixes, err := layer4.ParseNetworks(notRangesRaw)
+ if err != nil {
+ return nil, err
+ }
+ for _, notPrefix := range notPrefixes {
+ m.NotRanges = append(m.NotRanges, notPrefix.String())
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return nil, d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
+ }
+ }
+
+ return &m, nil
+}
+
+// TODO: move to https://github.com/caddyserver/caddy/tree/master/modules/caddytls/matchers.go
+// unmarshalCaddyfileMatchServerName sets up the MatchServerName from Caddyfile tokens. Syntax:
+//
+// sni
+func unmarshalCaddyfileMatchServerName(d *caddyfile.Dispenser) (*caddytls.MatchServerName, error) {
+ m := caddytls.MatchServerName{}
+
+ for d.Next() {
+ wrapper := d.Val()
+
+ // At least one same-line option must be provided
+ if d.CountRemainingArgs() == 0 {
+ return nil, d.ArgErr()
+ }
+
+ m = append(m, d.RemainingArgs()...)
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return nil, d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
+ }
+ }
+
+ return &m, nil
+}
diff --git a/modules/l4xmpp/matcher.go b/modules/l4xmpp/matcher.go
index b9a2d45..55ce1b1 100644
--- a/modules/l4xmpp/matcher.go
+++ b/modules/l4xmpp/matcher.go
@@ -19,6 +19,7 @@ import (
"strings"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/mholt/caddy-l4/layer4"
)
@@ -47,8 +48,30 @@ func (m MatchXMPP) Match(cx *layer4.Connection) (bool, error) {
return strings.Contains(string(p), xmppWord), nil
}
+// UnmarshalCaddyfile sets up the MatchXMPP from Caddyfile tokens. Syntax:
+//
+// xmpp
+func (m *MatchXMPP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ _, wrapper := d.Next(), d.Val() // consume wrapper name
+
+ // No same-line options are supported
+ if d.CountRemainingArgs() > 0 {
+ return d.ArgErr()
+ }
+
+ // No blocks are supported
+ if d.NextBlock(d.Nesting()) {
+ return d.Errf("malformed layer4 connection matcher '%s': blocks are not supported", wrapper)
+ }
+
+ return nil
+}
+
var xmppWord = "jabber"
var minXmppLength = 50
-// Interface guard
-var _ layer4.ConnMatcher = (*MatchXMPP)(nil)
+// Interface guards
+var (
+ _ layer4.ConnMatcher = (*MatchXMPP)(nil)
+ _ caddyfile.Unmarshaler = (*MatchXMPP)(nil)
+)