Files
jarl/internal/acme/acme_test.go
abrown38andClaude Opus 5.5 6b44b92975 Add an httpreq endpoint for lego and Traefik
jarld can now answer lego's httpreq DNS provider
(POST /api/v1/acme/httpreq/present and /cleanup), so lego and Traefik
publish DNS-01 challenges with only environment variables, without the
jarl CLI. lego's username and API token arrive as HTTP Basic auth,
accepted on these routes only.

The endpoint is off by default (acme.httpreq.enabled) and HTTPS-only by
default: jarld's own TLS, or a trusted proxy that says so with
X-Forwarded-Proto or Forwarded (the last value counts). Plain HTTP can be
allowed from loopback only or everywhere. GET /api/v1/acme/httpreq shows
how jarld sees a request, to check a proxy setup.

jarl acme and the endpoint share their code in internal/acme. Documented
in docs/acme.md, configuration.md, api.md and permissions.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:48:47 +00:00

72 lines
2.2 KiB
Go

package acme
import (
"encoding/json"
"testing"
)
func TestZones(t *testing.T) {
zones := []Zone{
{Name: "example.com.", View: "internal"},
{Name: "example.com.", View: "external"},
{Name: "acme.example.com.", View: "external"},
{Name: "example.net."},
}
cases := []struct {
name, view string
want []Zone
}{
{"_acme-challenge.example.com.", "", zones[:2]},
{"_acme-challenge.example.com", "External", zones[1:2]},
{"www.acme.example.com.", "", zones[2:3]},
{"_acme-challenge.WWW.example.net.", "", zones[3:4]},
{"_acme-challenge.example.org.", "", nil},
{"_acme-challenge.notexample.com.", "", nil},
}
for _, c := range cases {
got := Zones(c.name, zones, c.view)
if len(got) != len(c.want) {
t.Errorf("Zones(%s, %q) = %v, want %v", c.name, c.view, got, c.want)
continue
}
for i := range got {
if got[i] != c.want[i] {
t.Errorf("Zones(%s, %q) = %v, want %v", c.name, c.view, got, c.want)
}
}
}
}
func TestRequests(t *testing.T) {
zones := []Zone{{Name: "example.com.", View: "internal"}, {Name: "example.com.", View: "external"}}
ops := func(set bool, values ...string) []string {
var out []string
for _, r := range Requests("_acme-challenge.Example.com", zones, values, set, 60) {
var d map[string]any
json.Unmarshal(r.Data, &d)
if d["name"] != "_acme-challenge.example.com." || d["type"] != "TXT" {
t.Errorf("data = %v", d)
}
s := r.Op + " " + d["view"].(string)
if v, ok := d["data"]; ok {
s += " " + v.(string)
}
out = append(out, s)
}
return out
}
want := []string{"record.delete internal v1", "record.create internal v1", "record.delete external v1", "record.create external v1"}
if got := ops(true, "v1"); len(got) != 4 || got[0] != want[0] || got[1] != want[1] || got[3] != want[3] {
t.Errorf("set = %v", got)
}
if got := ops(false, "v1"); len(got) != 2 || got[0] != "record.delete internal v1" {
t.Errorf("unset = %v", got)
}
if got := ops(false); len(got) != 2 || got[1] != "record.delete external" {
t.Errorf("unset all = %v", got)
}
if d := Description("_acme-challenge.example.com.", true); d != "ACME: set challenge for _acme-challenge.example.com" {
t.Errorf("description = %q", d)
}
}