Config generation from Consul services Plugin for Caddy v2.
This plugin was inspired by the way Fabio works, and brings automatic and almost instant reverse proxying Consul services with Caddy.
Disclaimer: this plugin is still development stage and, despite being used as the main load balancer for the services of our internal Nomad clusters, probably assumes some configuration parts and miss some features.
As this plugin generates the caddytls
and caddyhttp
apps configuration from Consul services,
it imports and offers a basic integration with the following plugins based on Consul tags:
- Overview
- Loading Caddy's configuration from Consul
- Keeping the configuration up-to-date
- Auto reverse-proxying
- Support for third party plugins
This plugin enables realtime and automatic configuration updates of Caddy v2 by watching Consul K/V store and services.
The plugin registers to two Consul resources:
- a
K/V store key
that stores the global Caddy config part (admin
,logging
,storage
,apps
) - the
services
When the K/V store is updated or the services change, the plugin (re)generates the http
and tls
apps configuration
corresponding to each service containing a specific tag in Consul, merges the two apps configurations to the global one
it got from the K/V store, and pass it to Caddy as JSON as you would do by calling the /load
endpoint.
Since the plugin generates the http
and tls
configuration, all directives from these apps set another way will be
overridden by this plugin.
Let's say with start Caddy with this extremely simple configuration:
{
"apps": {
"consul": {
"consul_global_config_key": "configs/caddy/caddy.json",
"consul_server": {
"address": "127.0.0.1:8500",
"scheme": "http",
}
}
}
}
We only configure Caddy's consul
app (AKA this plugin):
- we specify how to reach our Consul server
- we define the Consul K/V entry that contains the configuration
Now, if the Consul K/V entry contains the following JSON payload:
{
"apps": {
"http": {
"servers": {
"hello": {
"listen": [":2015"],
"routes": [
{
"handle": [{
"handler": "static_response",
"body": "Hello, world!"
}]
}
]
}
}
}
}
}
The plugin will load this content and replace Caddy's running configuration with it.
The new configuration doesn't contain the consul
app, so Caddy will stop it.
This is the equivalent of Caddy's built-in HTTPLoader,
but for Consul.
If we use the same example as before, but store this JSON in our Consul K/V entry:
{
"apps": {
"consul": {
"consul_global_config_key": "configs/caddy/caddy.json",
"consul_server": {
"address": "127.0.0.1:8500",
"scheme": "http",
}
},
"http": {
"servers": {
"hello": {
"listen": [":2015"],
"routes": [
{
"handle": [{
"handler": "static_response",
"body": "Hello, world!"
}]
}
]
}
}
}
}
}
Here, we kept the configuration for the consul
app, which means Caddy will keep it running.
The next time the K/V entry is updated, the plugin will detect it and update the configuration again.
Now, keeping a static configuration up-to-date is nice, but what if we could automatically generate a reverse-proxy configuration from Consul services and their health checks?
Let's add a few things to our configuration:
{
"apps": {
"consul": {
"consul_global_config_key": "configs/caddy/caddy.json",
"consul_server": {
"address": "127.0.0.1:8500",
"scheme": "http",
},
"auto_reverse_proxy": {
"consul_services_tag": "caddy",
"default_http_server_options": {
"zone": "my-awesome-domain.io",
"http_port": 80,
"https_port": 443
},
"tls_issuers": [{
"module": "acme",
"email": "[email protected]",
"challenges": {
"dns": {
"ttl": 0,
"propagation_timeout": 0,
"resolvers": ["1.1.1.1"],
"provider": {
"name": "googleclouddns",
"gcp_project": "my-project-123456"
}
}
}
}]
}
}
}
}
Now, let's say we register a Grafana service in Consul with three instances and give it a tag caddy
:
Note: we used the tight integration between Nomad and Consul to register our service, but this works without Nomad.
As you'll notice, in our configuration, we added the following statement "consul_services_tag": "caddy"
.
This is the services filtering tag that correspond to the tag we attributed to our grafana
service in Consul.
The plugin will have the following default behavior:
- create a new hostname for
grafana
.my-awesome-domain.io
([consul.service_name].[default_http_server_options.zone]) - fetch the healthy instances of the Consul service and set them as the reverse proxy upstreams
- generate the TLS configuration for the domain
grafana.my-awesome-domain.io
and build the certificate with the provider defined in thetls_issuers
block.
Resulting in the following configuration passed to Caddy:
{
"apps": {
"http": {
"servers": {
"http": {
"listen": [":80"]
},
"https": {
"listen": [":443"],
"routes": [{
"match": [{
"host": ["grafana.my-awesome-domain.io"]
}
],
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{
"dial": "127.0.0.1:24250"
},
{
"dial": "127.0.0.1:27372"
},
{
"dial": "127.0.0.1:31830"
}]
}],
"terminal": true
}]
}
}
},
"tls": {
"automation": {
"policies": [{
"subjects": [
"grafana.my-awesome-domain.io"
],
"issuers": [{
"module": "acme",
"email": "[email protected]",
"challenges": {
"dns": {
"ttl": 0,
"propagation_timeout": 0,
"resolvers": [
"1.1.1.1"
],
"provider": {
"name": "googleclouddns",
"gcp_project": "my-project-123456"
}
}
}
}]
}]
}
}
}
}
This default behavior can be extended with more tags associated to the service:
caddy:zone=test.com
: uses domaintest.com
instead of the defaultmy-awesome-domain.io
caddy:name=grafana-overridden
: uses namegrafana-overridden
instead of Consul's service namegrafana
caddy:no-https
: disables the automatic https feature (host will listen on HTTP port, no certificate generation)caddy:no-auto-https-redirect
: disables the automatic http -> https redirection (host will listen on both HTTP and HTTPS ports)caddy:upstreams-scheme
: gets the scheme (http, https or grpc) of the upstreams (default is http; grpc triggers h2c mode)caddy:insecure-tls-upstreams
: sets --insecure tag on https upstreams to skip TLS certificate validationcaddy:upstream-headers
: propagates Caddy's upstream headerscaddy:"buffer-requests"
: overrides Caddy's buffer_requests valuecaddy:"buffer-responses"
: overrides Caddy's buffer_responses valuecaddy:"lb-policy=round_robin"
: overrides Caddy's load balancing policy valuecaddy:lb-try-duration=300ms
: overrides Caddy's load balancing try duration valuecaddy:lb-try-interval=300ms
: overrides Caddy's load balancing try interval valuecaddy:flush-interval=-1
: overrides Caddy's load balancing try interval valuecaddy:max-buffer-size=1024
: overrides Caddy's max buffer size valuecaddy:enable-auth
: enable authentication for this servicecaddy:auth-backend=oauth2/github
: overrides the default auth backend for this service
On top of these Caddy built-in features and parameters, as this plugin generates the configuration, it also has a basic support for other plugins.
This integration automatically generates a request_id
handler before the reverse_proxy
handler and passes
a X-Request-ID header to the upstream and as a response header.
This feature can be enabled like this:
{
"apps": {
"consul": {
"auto_reverse_proxy": {
"use_request_id": true
}
}
}
}
This integration automatically generates the use of caddy-auth-portal, allowing you to easily add authentication to your services.
The configuration encapsulates the one from greenpau's caddy-auth-portal and caddy-auth-jwt. It is enabled as follows:
{
"apps": {
"consul": {
"auto_reverse_proxy": {
"authentication_configuration": {
"enabled": true,
"custom_claims_headers": {
"x-token-user-email": "X-MYCOMPANY-USER"
},
"authentication_domain": "auth.my-awesome-domain.io",
"authp": {
"primary": true,
"cookie_config": {
"domain": "my-awesome-domain.io"
},
"backend_configs": [{
"method": "oauth2",
"provider": "google",
"name": "google",
"realm": "google",
"client_id": "[client_id].apps.googleusercontent.com",
"client_secret": "[client_secret]",
"scopes": ["email"]
},{
"method": "oauth2",
"provider": "github",
"name": "github",
"realm": "github",
"client_id": "[client_id]",
"client_secret": "[client_secret]",
"scopes": ["user"]
}],
"crypto_key_configs": {
"token_name": "TokenName",
"token_secret": "testtesttesttesttesttesttest",
"token_lifetime": 86400,
"usage": "auto",
"algorithm": "hmac",
"source": "config"
}
}
}
}
}
}
}
This will enable the auth-portal on the domain auth.my-awesome-domain.io/auth
, add two oauth2 backends (google
and
github
) and configure the JWT part.
The configuration on top of the ones from the plugins are the following:
enabled: true
: allows to enable or disable authenticationauthentication_domain
: the domain that hosts the authentication portalcustom_claims_headers
: a mapping of caddy-auth-jwt's claims headers to your own
When the authentication is enabled, you just need to add the caddy:enable-auth
tag to a Consul service to require
authentication on that host.