File placeholder not working as expected with dns acme challenge

1. The problem I’m having:

I’m trying to use the file placeholder in Caddyfile, to configure dns acme challenge. But i doesn’t seem to work.

What i check :
The Personnal Access Token of gandi is valid and working as expected.
From the container file system point of view, the secrets at the path /run/secrets/gandi-api-key is present and have the good linux permission.

2. Error messages and/or full log output:

{
    "level": "error",
    "ts": 1719066921.4045749,
    "logger": "tls.obtain",
    "msg": "could not get certificate from issuer",
    "identifier": "vaultwarden.one4all.icu",
    "issuer": "acme-v02.api.letsencrypt.org-directory",
    "error": "[vaultwarden.one4all.icu] solving challenges: presenting for challenge: adding temporary record for zone \"one4all.icu.\": Get \"https://api.gandi.net/v5/livedns/domains/one4all.icu\": net/http: invalid header field value for \"Authorization\" (order=https://acme-v02.api.letsencrypt.org/acme/order/1796574077/280700285487) (ca=https://acme-v02.api.letsencrypt.org/directory)"
}

3. Caddy version:

v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=

4. How I installed and ran Caddy:

Caddy is running as a container with docker.

a. System environment:

OS architecture

Linux srvone4all 5.4.0-1100-raspi #112-Ubuntu SMP PREEMPT Fri Nov 24 15:35:17 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux

NAME="Ubuntu"
VERSION="20.04.6 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.6 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

Docker version

Client: Docker Engine - Community
 Version:           26.1.4
 API version:       1.45
 Go version:        go1.21.11
 Git commit:        5650f9b
 Built:             Wed Jun  5 11:30:07 2024
 OS/Arch:           linux/arm64
 Context:           rootless

Server: Docker Engine - Community
 Engine:
  Version:          26.1.4
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.21.11
  Git commit:       de5c9cf
  Built:            Wed Jun  5 11:30:07 2024
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.6.33
  GitCommit:        d2d58213f83a351ca8f528a95fbd145f5654e957
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
 rootlesskit:
  Version:          2.0.2
  ApiVersion:       1.1.1
  NetworkDriver:    slirp4netns
  PortDriver:       slirp4netns
  StateDir:         /run/user/1000/dockerd-rootless
 slirp4netns:
  Version:          0.4.3
  GitCommit:        2244b9b6461afeccad1678fac3d6e478c28b4ad6

b. Command:

Caddy is a service and it is build from a Dockerfile

docker compose up -d

c. Service/unit/compose file:

FROM caddy:2.8.4-builder-alpine AS builder
RUN xcaddy build \
    --with github.com/corazawaf/coraza-caddy/v2 \
    --with github.com/caddy-dns/gandi

FROM caddy:2.8.4-alpine

COPY --from=builder /usr/bin/caddy /usr/bin/caddy
RUN set -eux; \
    apk add --no-cache libcap tzdata curl; \
    setcap cap_net_bind_service=ep /usr/bin/caddy; \
    addgroup -g 2023 -S www-caddy; \
    adduser -u 2023 -D -S -G www-caddy www-caddy; \
    chown -R www-caddy /data /var/log

USER www-caddy

WORKDIR /srv

CMD ["/usr/bin/caddy", "run", "--config", "/Caddyfile", "--adapter", "caddyfile"]

d. My complete Caddy config:

{
	debug
	order coraza_waf first
	persist_config off
	log {
		level DEBUG
		output file /var/log/caddy/caddy.log
	}
	servers {
		timeouts {
			idle 10s
			read_body 1m
			read_header 10s
		}
		max_header_size 4KB
	}
	acme_dns gandi {file./run/secrets/gandi-api-key}
}

:443 {
	log
	respond "Not found" 404
}

:80 {
	log
	respond "Not found" 404
}

{$DOMAIN} {
	log
	tls {
	  dns gandi {file./run/secrets/gandi-api-key}
	}
	coraza_waf {
		directives `
		Include /ruleset/coraza.conf
		Include /ruleset/vaultwarden/crs-setup.conf
		Include /ruleset/coreruleset/rules/*.conf
		`
	}
	request_body {
		max_size 500MB
	}

	@not-api not path /api/sends/*
	request_body @not-api {
		max_size 100KB
	}

	encode gzip

	header {
		Strict-Transport-Security "max-age=15768000;"
		Referrer-Policy "no-referrer"
		X-Robots-Tag "none"
	}

	@insecureadmin {
		not remote_ip 192.168.1.0/24
		path /admin*
	}
	redir @insecureadmin /
	reverse_proxy vaultwarden:60278 {
		header_up X-Real-IP {remote_host}
	}
}

unifi.one4all.icu {
	@postinform {
		method POST
		path /inform
	}
	reverse_proxy @postinform unifi-controller:8080
	reverse_proxy unifi-controller:8443 {
		transport http {
			tls_insecure_skip_verify
		}
	}
	@insecureaccess {
		not remote_ip 192.168.1.0/24
	}
	respond @insecureaccess "Access Forbidden!" 403
}

crowdsec-lapi.one4all.icu {
        reverse_proxy http://crowdsec:8080 {
		header_up X-Real-IP {remote_host}
        }
        @insecureaccess {
                not remote_ip 192.168.1.0/24
        }
        respond @insecureaccess "Access Forbidden!" 403
}

5. Links to relevant resources:

I wonder if you’re running into:

You can validate this by either removing the trailing new-line at the end of the file, or building Caddy this way with a replacement to the PR fork:

xcaddy build \
    --with github.com/caddyserver/caddy/v2=github.com/steffenbusch/caddy/v2
    --with github.com/corazawaf/coraza-caddy/v2 \
    --with github.com/caddy-dns/gandi
1 Like

Hey, nice idea but the file have only 1 line…
Capture d’écran du 2024-06-22 17-22-32

Can you share the docker-compose.yml file? I see in your screenshot the secret file name ends with .txt. Is it mounted without the .txt in the container? Double check the correct file name.

1 Like

This is the compose file, it looks good to me, am i right ?

include:
  - ./vaultwarden/compose.yaml
  - ./unifi/compose.yaml
  - ./crowdsec/compose.yaml

services:
    reverse-proxy:
      # Explain of this configuration can be found here https://hub.docker.com/_/caddy
      container_name: reverse-proxy
      image: coracaddy:latest
      depends_on:
        - "unifi-controller"
        - "vaultwarden"
        - "crowdsec"
      ports:
        - "35689:80"
        - "28453:443"
      volumes:
        - /var/log/caddy:/var/log/caddy
        - ./caddy/Caddyfile:/Caddyfile
        - ./caddy/ruleset:/ruleset
        - caddy_data:/data
      environment:
        # Also used in the building phase
        DOMAIN: "https://vaultwarden.one4all.icu"
        EMAIL: "bbarnoux@gmail.com"
        ACME_AGREE: "true"
        TZ: "Europe/Paris"
      networks:
          - reverse-proxy
      secrets:
          - source: gandi-api-key

secrets:
  gandi-api-key:
     file: ./secrets/gandi-api-key.txt

networks:
  reverse-proxy:

volumes:
  caddy_data:
    external: true
    name: one4all-caddy-data

I guess I’ll need you to try the PR and double-check the file contents. The header should not contain any of these characters, per the HTTP spec. This is the only culprit I can think of.

Source: ASCII Table - ASCII codes, hex, decimal, binary, html

Dec Hex Binary HTML Char Description
0 00 00000000 NUL Null
1 01 00000001 SOH Start of Heading
2 02 00000010 STX Start of Text
3 03 00000011 ETX End of Text
4 04 00000100 EOT End of Transmission
5 05 00000101 ENQ Enquiry
6 06 00000110 ACK Acknowledge
7 07 00000111 BEL Bell
8 08 00001000 BS Backspace
9 09 00001001 HT Horizontal Tab
10 0A 00001010 LF Line Feed
11 0B 00001011 VT Vertical Tab
12 0C 00001100 FF Form Feed
13 0D 00001101 CR Carriage Return
14 0E 00001110 SO Shift Out
15 0F 00001111 SI Shift In
16 10 00010000 DLE Data Link Escape
17 11 00010001 DC1 Device Control 1
18 12 00010010 DC2 Device Control 2
19 13 00010011 DC3 Device Control 3
20 14 00010100 DC4 Device Control 4
21 15 00010101 NAK Negative Acknowledge
22 16 00010110 SYN Synchronize
23 17 00010111 ETB End of Transmission Block
24 18 00011000 CAN Cancel
25 19 00011001 EM End of Medium
26 1A 00011010 SUB Substitute
27 1B 00011011 ESC Escape
28 1C 00011100 FS File Separator
29 1D 00011101 GS Group Separator
30 1E 00011110 RS Record Separator
31 1F 00011111 US Unit Separator
1 Like

Even when wc -l shows 1, the file has a trailing newline (\n) and that is causing the invalid header field value for \"Authorization\". I’ve had the same issue with a trailing newline in my file for my DNS api.

You can verify this with:

diff gandi-api-key.txt <(cat gandi-api-key.txt | tr -d '\n')

If you see \ No newline at end of file in the diff output, you have to remove the trailing newline.

For example:

cp gandi-api-key.txt gandi-api-key.txt.bak && \
cat gandi-api-key.txt.bak | tr -d '\n' > gandi-api-key.txt
3 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.