Caddy Vault Storage

At MyWordPress.io, we cannot say enough good things about Caddy. It’s stable, fast, easy to configure, and a breeze to maintain.

For those that are unaware, one of the major facets of our secrets management involves Vault. Like Caddy, using Vault to manage secrets is a breeze–gone are the days of secrets sprawled all of your infrastructure (not to mention developer laptops!).

The Caddy Vault Storage plugin allows all certificates managed by Caddy’s built-in on-demand certificate issuance to be stored in Vault, facilitating easy (and secure) management of distributed Caddy servers to use the same central “certificate storage”.

Getting Started

Let’s build Caddy to include the Vault Storage plugin. After a while, the build completes, and you can now configure your Vault settings for Caddy.

xcaddy build
#
# WARNING: You will need 'go' installed on your $PATH, which is out of
#          scope for this tutorial.
#
kmott@kyle-laptop:/tmp$ xcaddy build --output bin/caddy --with github.com/mywordpress-io/caddy-vault-storage@v0.1.1 --with github.com/mywordpress-io/certmagic-vault-storage@v0.1.1

2024/05/07 17:11:30 [INFO] Temporary folder: /tmp/buildenv_2024-05-07-1711.4062144593
2024/05/07 17:11:30 [INFO] Writing main module: /tmp/buildenv_2024-05-07-1711.4062144593/main.go
package main

import (
	caddycmd "github.com/caddyserver/caddy/v2/cmd"

	// plug in Caddy modules here
	_ "github.com/caddyserver/caddy/v2/modules/standard"
	_ "github.com/mywordpress-io/caddy-vault-storage"
	_ "github.com/mywordpress-io/certmagic-vault-storage"
)

func main() {
	caddycmd.Main()
}
2024/05/07 17:11:30 [INFO] Initializing Go module
2024/05/07 17:11:30 [INFO] exec (timeout=-2562047h47m16.854775808s): /home/kmott/.files/bin/go-1.20.4-linux-amd64/bin/go mod init caddy
go: creating new go.mod: module caddy
go: to add module requirements and sums:
	go mod tidy
2024/05/07 17:11:30 [INFO] Pinning versions
2024/05/07 17:11:30 [INFO] exec (timeout=-2562047h47m16.854775808s): /home/kmott/.files/bin/go-1.20.4-linux-amd64/bin/go get -d -v github.com/caddyserver/caddy/v2
go: downloading github.com/caddyserver/caddy/v2 v2.7.6
go: downloading github.com/caddyserver/caddy v1.0.5
go: downloading github.com/caddyserver/certmagic v0.20.0
go: downloading github.com/quic-go/quic-go v0.40.0
go: downloading github.com/quic-go/qtls-go1-20 v0.4.1
go: added github.com/beorn7/perks v1.0.1
go: added github.com/caddyserver/caddy/v2 v2.7.6
go: added github.com/caddyserver/certmagic v0.20.0
go: added github.com/cespare/xxhash/v2 v2.2.0
go: added github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572
go: added github.com/golang/protobuf v1.5.3
go: added github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1
go: added github.com/google/uuid v1.3.1
go: added github.com/klauspost/cpuid/v2 v2.2.5
go: added github.com/libdns/libdns v0.2.1
go: added github.com/matttproud/golang_protobuf_extensions v1.0.4
go: added github.com/mholt/acmez v1.2.0
go: added github.com/miekg/dns v1.1.55
go: added github.com/onsi/ginkgo/v2 v2.9.5
go: added github.com/prometheus/client_golang v1.15.1
go: added github.com/prometheus/client_model v0.4.0
go: added github.com/prometheus/common v0.42.0
go: added github.com/prometheus/procfs v0.9.0
go: added github.com/quic-go/qpack v0.4.0
go: added github.com/quic-go/qtls-go1-20 v0.4.1
go: added github.com/quic-go/quic-go v0.40.0
go: added github.com/zeebo/blake3 v0.2.3
go: added go.uber.org/mock v0.3.0
go: added go.uber.org/multierr v1.11.0
go: added go.uber.org/zap v1.25.0
go: added golang.org/x/crypto v0.14.0
go: added golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0
go: added golang.org/x/mod v0.11.0
go: added golang.org/x/net v0.17.0
go: added golang.org/x/sys v0.14.0
go: added golang.org/x/term v0.13.0
go: added golang.org/x/text v0.13.0
go: added golang.org/x/tools v0.10.0
go: added google.golang.org/protobuf v1.31.0
2024/05/07 17:11:32 [INFO] exec (timeout=-2562047h47m16.854775808s): /home/kmott/.files/bin/go-1.20.4-linux-amd64/bin/go get -d -v github.com/mywordpress-io/caddy-vault-storage@v0.1.1 github.com/caddyserver/caddy/v2
go: downloading github.com/mywordpress-io/caddy-vault-storage v0.1.1
go: added github.com/mywordpress-io/caddy-vault-storage v0.1.1
go: added github.com/mywordpress-io/certmagic-vault-storage v0.1.1
go: upgraded github.com/onsi/ginkgo/v2 v2.9.5 => v2.12.1
go: upgraded golang.org/x/mod v0.11.0 => v0.12.0
go: upgraded golang.org/x/tools v0.10.0 => v0.12.0
go: added gopkg.in/resty.v1 v1.12.0
2024/05/07 17:11:33 [INFO] exec (timeout=-2562047h47m16.854775808s): /home/kmott/.files/bin/go-1.20.4-linux-amd64/bin/go get -d -v github.com/mywordpress-io/certmagic-vault-storage@v0.1.1 github.com/caddyserver/caddy/v2
2024/05/07 17:11:34 [INFO] exec (timeout=-2562047h47m16.854775808s): /home/kmott/.files/bin/go-1.20.4-linux-amd64/bin/go get -d -v
go: downloading google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b
go: downloading go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0
go: downloading go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
go: downloading go.opentelemetry.io/otel v1.21.0
go: downloading go.opentelemetry.io/otel/sdk v1.21.0
go: downloading go.opentelemetry.io/otel/trace v1.21.0
go: downloading go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0
go: downloading google.golang.org/grpc v1.59.0
go: downloading go.opentelemetry.io/proto/otlp v1.0.0
go: downloading google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b
go: downloading go.opentelemetry.io/otel/metric v1.21.0
go: downloading github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0
go: downloading github.com/golang/glog v1.1.2
2024/05/07 17:11:39 [INFO] Build environment ready
2024/05/07 17:11:39 [INFO] Building Caddy
2024/05/07 17:11:39 [INFO] exec (timeout=-2562047h47m16.854775808s): /home/kmott/.files/bin/go-1.20.4-linux-amd64/bin/go mod tidy
go: downloading cloud.google.com/go/iam v1.1.2
go: downloading google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a
2024/05/07 17:11:40 [INFO] exec (timeout=-2562047h47m16.854775808s): /home/kmott/.files/bin/go-1.20.4-linux-amd64/bin/go build -o /tmp/bin/caddy -ldflags -w -s -trimpath
2024/05/07 17:12:09 [INFO] Build complete: bin/caddy
2024/05/07 17:12:09 [INFO] Cleaning up temporary folder: /tmp/buildenv_2024-05-07-1711.4062144593

Configuration

#
# File: Caddyfile
#
{
	skip_install_trust
	auto_https disable_redirects
	storage vault http://localhost:8200 {
		token dead-beef

		#approle_login_path <value>
		#approle_logout_path <value>
		#approle_role_id <value>
		#approle_secret_id <value>

		secrets_path secret
		path_prefix caddy/certificates

		#insecure_skip_verify <value>

		#lock_timeout <value>
		#lock_polling_interval <value>
	}
}

example.com:10443 {
	tls internal
	respond "Hello, world!"
}

Run Vault

Startup Vault in dev mode:

vault server -dev -dev-root-token-id=dead-beef
#
# Startup Vault in dev mode with a static token--DO NOT RUN IN PRODUCTION!
#
kmott@kyle-laptop:/tmp$ vault server -dev -dev-root-token-id=dead-beef

==> Vault server configuration:

             Api Address: http://127.0.0.1:8200
                     Cgo: disabled
         Cluster Address: https://127.0.0.1:8201
              Go Version: go1.17.5
              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
               Log Level: info
                   Mlock: supported: true, enabled: false
           Recovery Mode: false
                 Storage: inmem
                 Version: Vault v1.9.2
             Version Sha: f4c6d873e2767c0d6853b5d9ffc77b0d297bfbdf

==> Vault server started! Log data will stream in below:

2024-05-08T08:47:09.874-0700 [INFO]  proxy environment: http_proxy="\"\"" https_proxy="\"\"" no_proxy="\"\""
2024-05-08T08:47:09.874-0700 [WARN]  no `api_addr` value specified in config or in VAULT_API_ADDR; falling back to detection if possible, but this value should be manually set
2024-05-08T08:47:09.877-0700 [INFO]  core: Initializing VersionTimestamps for core
2024-05-08T08:47:09.877-0700 [INFO]  core: security barrier not initialized
2024-05-08T08:47:09.877-0700 [INFO]  core: security barrier initialized: stored=1 shares=1 threshold=1
2024-05-08T08:47:09.878-0700 [INFO]  core: post-unseal setup starting
2024-05-08T08:47:09.881-0700 [INFO]  core: loaded wrapping token key
2024-05-08T08:47:09.881-0700 [INFO]  core: Recorded vault version: vault version=1.9.2 upgrade time="2024-05-08 08:47:09.881334307 -0700 PDT m=+0.058396820"
2024-05-08T08:47:09.881-0700 [INFO]  core: successfully setup plugin catalog: plugin-directory="\"\""
2024-05-08T08:47:09.881-0700 [INFO]  core: no mounts; adding default mount table
2024-05-08T08:47:09.882-0700 [INFO]  core: successfully mounted backend: type=cubbyhole path=cubbyhole/
2024-05-08T08:47:09.883-0700 [INFO]  core: successfully mounted backend: type=system path=sys/
2024-05-08T08:47:09.884-0700 [INFO]  core: successfully mounted backend: type=identity path=identity/
2024-05-08T08:47:09.886-0700 [INFO]  core: successfully enabled credential backend: type=token path=token/
2024-05-08T08:47:09.886-0700 [INFO]  rollback: starting rollback manager
2024-05-08T08:47:09.886-0700 [INFO]  core: restoring leases
2024-05-08T08:47:09.888-0700 [INFO]  expiration: lease restore complete
2024-05-08T08:47:09.888-0700 [INFO]  identity: entities restored
2024-05-08T08:47:09.888-0700 [INFO]  identity: groups restored
2024-05-08T08:47:09.888-0700 [INFO]  core: post-unseal setup complete
2024-05-08T08:47:09.889-0700 [INFO]  core: root token generated
2024-05-08T08:47:09.889-0700 [INFO]  core: pre-seal teardown starting
2024-05-08T08:47:09.889-0700 [INFO]  rollback: stopping rollback manager
2024-05-08T08:47:09.889-0700 [INFO]  core: pre-seal teardown complete
2024-05-08T08:47:09.890-0700 [INFO]  core.cluster-listener.tcp: starting listener: listener_address=127.0.0.1:8201
2024-05-08T08:47:09.890-0700 [INFO]  core.cluster-listener: serving cluster requests: cluster_listen_address=127.0.0.1:8201
2024-05-08T08:47:09.890-0700 [INFO]  core: post-unseal setup starting
2024-05-08T08:47:09.890-0700 [INFO]  core: loaded wrapping token key
2024-05-08T08:47:09.890-0700 [INFO]  core: successfully setup plugin catalog: plugin-directory="\"\""
2024-05-08T08:47:09.891-0700 [INFO]  core: successfully mounted backend: type=system path=sys/
2024-05-08T08:47:09.891-0700 [INFO]  core: successfully mounted backend: type=identity path=identity/
2024-05-08T08:47:09.891-0700 [INFO]  core: successfully mounted backend: type=cubbyhole path=cubbyhole/
2024-05-08T08:47:09.892-0700 [INFO]  core: successfully enabled credential backend: type=token path=token/
2024-05-08T08:47:09.892-0700 [INFO]  rollback: starting rollback manager
2024-05-08T08:47:09.892-0700 [INFO]  core: restoring leases
2024-05-08T08:47:09.892-0700 [INFO]  expiration: lease restore complete
2024-05-08T08:47:09.892-0700 [INFO]  identity: entities restored
2024-05-08T08:47:09.892-0700 [INFO]  identity: groups restored
2024-05-08T08:47:09.892-0700 [INFO]  core: post-unseal setup complete
2024-05-08T08:47:09.892-0700 [INFO]  core: vault is unsealed
2024-05-08T08:47:09.894-0700 [INFO]  expiration: revoked lease: lease_id=auth/token/root/hff23aea39ae38c3c446eec9b3e1d4af89e6dcb37cbfb3da136c5dc634db8889b
2024-05-08T08:47:09.897-0700 [INFO]  core: successful mount: namespace="\"\"" path=secret/ type=kv
2024-05-08T08:47:09.908-0700 [INFO]  secrets.kv.kv_7a1cc022: collecting keys to upgrade
2024-05-08T08:47:09.908-0700 [INFO]  secrets.kv.kv_7a1cc022: done collecting keys: num_keys=1
2024-05-08T08:47:09.908-0700 [INFO]  secrets.kv.kv_7a1cc022: upgrading keys finished
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.

You may need to set the following environment variable:

    $ export VAULT_ADDR='http://127.0.0.1:8200'

The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.

Unseal Key: ssiVZNyY3IPxaC7pu74huDzLKO0e7eu9psHpUEXWveI=
Root Token: dead-beef

Development mode should NOT be used in production installations!

Run Caddy

Run Caddy with the new Caddyfile:

bin/caddy run –config Caddyfile
#
# Start 'caddy', pointing at our Caddyfile
#
kmott@kyle-laptop:/tmp$ bin/caddy run --config Caddyfile
2024/05/08 15:58:17.025	INFO	using provided configuration	{"config_file": "Caddyfile", "config_adapter": ""}
2024/05/08 15:58:17.027	INFO	admin	admin endpoint started	{"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2024/05/08 15:58:17.027	INFO	tls.cache.maintenance	started background certificate maintenance	{"cache": "0xc0007df300"}
2024/05/08 15:58:17.038	WARN	http.auto_https	automatic HTTP->HTTPS redirects are disabled	{"server_name": "srv0"}
2024/05/08 15:58:17.039	INFO	pki.ca.local	root certificate trust store installation disabled; unconfigured clients may show warnings	{"path": "storage:pki/authorities/local/root.crt"}
2024/05/08 15:58:17.039	INFO	http	enabling HTTP/3 listener	{"addr": ":10443"}
2024/05/08 15:58:17.039	INFO	failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 2048 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.
2024/05/08 15:58:17.039	INFO	http.log	server running	{"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2024/05/08 15:58:17.039	INFO	http	enabling automatic TLS certificate management	{"domains": ["example.com"]}
2024/05/08 15:58:17.041	INFO	autosaved config (load with --resume flag)	{"file": "/home/kmott/.config/caddy/autosave.json"}
2024/05/08 15:58:17.041	INFO	serving initial configuration
2024/05/08 15:58:17.042	INFO	tls	cleaning storage unit	{"storage": {}}
2024/05/08 15:58:17.044	INFO	tls	finished cleaning storage units
2024/05/08 15:58:17.044	INFO	tls.obtain	acquiring lock	{"identifier": "example.com"}
2024/05/08 15:58:17.045	INFO	tls.obtain	lock acquired	{"identifier": "example.com"}
2024/05/08 15:58:17.045	INFO	tls.obtain	obtaining certificate	{"identifier": "example.com"}
2024/05/08 15:58:17.051	INFO	tls.obtain	certificate obtained successfully	{"identifier": "example.com"}
2024/05/08 15:58:17.051	INFO	tls.obtain	releasing lock	{"identifier": "example.com"}
2024/05/08 15:58:17.056	WARN	tls	stapling OCSP	{"error": "no OCSP stapling for [example.com]: no OCSP server specified in certificate", "identifiers": ["example.com"]}

Verify Caddy Certificate

After starting Vault and Caddy, you should be able to make HTTPS requests to localhost for example.com using curl, which will issue a new internal TLS certificate:

#
# When invoking curl, we need to tell it we have a specific hostname
# + IP address that it must connect to so the SNI headers will be
# set correctly.
#
# We also set '-k' because the TLS certificate is internal, and not
# publicly trusted (for testing purposes)
#
kmott@kyle-laptop:/tmp$ curl -ki https://example.com/test123 --connect-to example.com:443:127.0.0.1:10443

HTTP/2 200
alt-svc: h3=":10443"; ma=2592000
content-type: text/plain; charset=utf-8
server: Caddy
content-length: 13
date: Wed, 08 May 2024 16:21:16 GMT

Hello, world!

Let’s also inspect the TLS certificate that comes back on the wire from Caddy:

#
# We connect to the Caddy listener using openssl, then parse the certificate
# response as an x509 certificate to display information about the cert. 
#
kmott@kyle-laptop:/tmp$ openssl s_client -connect 127.0.0.1:10443 -servername example.com | openssl x509 -noout -text

depth=1 CN = Caddy Local Authority - ECC Intermediate
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0
verify return:1
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            37:0c:47:2f:82:42:66:53:05:a3:b9:6c:11:28:6b:64
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN = Caddy Local Authority - ECC Intermediate
        Validity
            Not Before: May  8 15:58:17 2024 GMT
            Not After : May  9 03:58:17 2024 GMT
        Subject:
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:b3:71:37:7f:4c:89:46:c9:f5:da:54:d4:a4:69:
                    88:c7:5a:c8:89:f4:40:fe:1b:d5:13:85:ba:78:a5:
                    88:6a:7b:2b:9a:68:a2:31:80:e6:9b:2f:a6:25:5e:
                    85:c1:48:44:f2:eb:d0:e4:9b:79:42:a8:5f:93:73:
                    3b:ed:50:c1:65
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Subject Key Identifier:
                92:1C:33:C8:A3:E3:D6:D5:AE:F1:ED:F2:28:3F:33:40:7F:3C:C4:7F
            X509v3 Authority Key Identifier:
                7A:80:EE:67:1D:13:93:CA:0D:23:05:0E:D0:B0:88:A3:06:D0:16:36
            X509v3 Subject Alternative Name: critical
                DNS:example.com
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:45:02:20:11:a1:41:7f:ba:00:5b:cd:26:cd:69:84:7d:06:
        18:5c:02:e8:b4:e5:e5:23:7c:b1:96:d1:b6:a6:7a:27:fc:ab:
        02:21:00:dd:a0:50:4c:35:42:0d:32:2a:ab:1b:3a:ab:c0:f5:
        59:87:4f:9d:1f:a8:b1:e2:19:3a:fb:93:65:44:18:fa:bf

Verify Vault Certificate

This is great, but the whole point of the exercise is to make sure that the issues certificate is stored in Vault. Let’s inspect Vault (which should be stored at secret/caddy/certificate based on our Caddyfile settings:

#
# List the contents of Vault at the certificate storage path
#
kmott@kyle-laptop:/tmp$ export VAULT_ADDR=http://127.0.0.1:8200
kmott@kyle-laptop:/tmp$ export VAULT_TOKEN=dead-beef
kmott@kyle-laptop:/tmp$ vault kv list /secret/caddy/certificates
Keys
----
certificates/
last_clean.json
pki/

kmott@kyle-laptop:/tmp$ vault kv list /secret/caddy/certificates/certificates/local/example.com
Keys
----
example.com.crt
example.com.json
example.com.key

#
# Get the example.com.crt file, which should have fields that matches our previous test connection
# certificate using "curl"
#
kmott@kyle-laptop:/tmp$  vault kv get -format=json "/secret/caddy/certificates/certificates/local/example.com/example.com.crt" | jq -r '.data.data.certmagic.data | @base64d' | openssl x509 -noout -text

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            37:0c:47:2f:82:42:66:53:05:a3:b9:6c:11:28:6b:64
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN = Caddy Local Authority - ECC Intermediate
        Validity
            Not Before: May  8 15:58:17 2024 GMT
            Not After : May  9 03:58:17 2024 GMT
        Subject:
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:b3:71:37:7f:4c:89:46:c9:f5:da:54:d4:a4:69:
                    88:c7:5a:c8:89:f4:40:fe:1b:d5:13:85:ba:78:a5:
                    88:6a:7b:2b:9a:68:a2:31:80:e6:9b:2f:a6:25:5e:
                    85:c1:48:44:f2:eb:d0:e4:9b:79:42:a8:5f:93:73:
                    3b:ed:50:c1:65
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Subject Key Identifier:
                92:1C:33:C8:A3:E3:D6:D5:AE:F1:ED:F2:28:3F:33:40:7F:3C:C4:7F
            X509v3 Authority Key Identifier:
                7A:80:EE:67:1D:13:93:CA:0D:23:05:0E:D0:B0:88:A3:06:D0:16:36
            X509v3 Subject Alternative Name: critical
                DNS:example.com
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:45:02:20:11:a1:41:7f:ba:00:5b:cd:26:cd:69:84:7d:06:
        18:5c:02:e8:b4:e5:e5:23:7c:b1:96:d1:b6:a6:7a:27:fc:ab:
        02:21:00:dd:a0:50:4c:35:42:0d:32:2a:ab:1b:3a:ab:c0:f5:
        59:87:4f:9d:1f:a8:b1:e2:19:3a:fb:93:65:44:18:fa:bf

More Information

If you run into any issues, find bugs, or have questions, please feel free to contact us using the information below, or create a ticket in the GitHub project. We are happy to help!

Related posts