Compare commits

...

15 Commits

Author SHA1 Message Date
Mohammed Al Sahaf 08aee56c57 remove unused function
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-05-10 19:10:29 +03:00
Mohammed Al Sahaf aea4013a56 cmd: remove {add,remove}-package and upgrade commands
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-05-10 17:50:13 +03:00
Mohammed Al Sahaf 44d078b670 acme_server: fix policy parsing in caddyfile (#7006)
Tests / test (./cmd/caddy/caddy, ~1.24.1, ubuntu-latest, 0, 1.24, linux) (push) Failing after 1m55s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.24.1, 1.24, aix) (push) Successful in 1m12s
Cross-Build / build (~1.24.1, 1.24, darwin) (push) Successful in 1m14s
Cross-Build / build (~1.24.1, 1.24, dragonfly) (push) Successful in 1m12s
Cross-Build / build (~1.24.1, 1.24, freebsd) (push) Successful in 1m14s
Cross-Build / build (~1.24.1, 1.24, illumos) (push) Successful in 1m12s
Cross-Build / build (~1.24.1, 1.24, linux) (push) Successful in 1m13s
Cross-Build / build (~1.24.1, 1.24, netbsd) (push) Successful in 1m15s
Cross-Build / build (~1.24.1, 1.24, openbsd) (push) Successful in 1m19s
Cross-Build / build (~1.24.1, 1.24, solaris) (push) Successful in 1m13s
Cross-Build / build (~1.24.1, 1.24, windows) (push) Successful in 1m15s
Lint / lint (ubuntu-latest, linux) (push) Successful in 2m2s
Lint / govulncheck (push) Successful in 1m32s
Tests / test (./cmd/caddy/caddy, ~1.24.1, macos-14, 0, 1.24, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.24.1, windows-latest, True, 1.24, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-05-08 11:54:07 -06:00
Jimmy Lipham 051e73aefc core: Replace admin server later in provisionContext (#7004) 2025-05-08 11:52:55 -06:00
Mohammed Al Sahaf 9f7148392a log: default logger should respect {in,ex}clude (#6995)
Tests / test (./cmd/caddy/caddy, ~1.24.1, ubuntu-latest, 0, 1.24, linux) (push) Failing after 1m48s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.24.1, 1.24, aix) (push) Successful in 1m36s
Cross-Build / build (~1.24.1, 1.24, darwin) (push) Successful in 1m16s
Cross-Build / build (~1.24.1, 1.24, dragonfly) (push) Successful in 1m14s
Cross-Build / build (~1.24.1, 1.24, freebsd) (push) Successful in 1m14s
Cross-Build / build (~1.24.1, 1.24, illumos) (push) Successful in 1m13s
Cross-Build / build (~1.24.1, 1.24, linux) (push) Successful in 1m14s
Cross-Build / build (~1.24.1, 1.24, netbsd) (push) Successful in 1m15s
Cross-Build / build (~1.24.1, 1.24, openbsd) (push) Successful in 1m14s
Cross-Build / build (~1.24.1, 1.24, solaris) (push) Successful in 1m15s
Cross-Build / build (~1.24.1, 1.24, windows) (push) Successful in 1m16s
Lint / lint (ubuntu-latest, linux) (push) Successful in 1m49s
Lint / govulncheck (push) Successful in 1m16s
Tests / test (./cmd/caddy/caddy, ~1.24.1, macos-14, 0, 1.24, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.24.1, windows-latest, True, 1.24, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
* log: default logger should respect `{in,ex}clude`

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* add tests

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-05-06 22:06:09 +00:00
Jimmy Lipham 320c57291d admin: Make sure that any admin routers are provisioned when local/re… (#6997)
* admin: Make sure that any admin routers are provisioned when local/remote admin servers are replaced at runtime.

* admin: check for provisioning errors during admin server replacements
2025-05-06 15:28:38 -06:00
WeidiDeng aa3d20be3e reverseproxy: Use DialTLSContext if ServerName has placeholder (#6955)
Tests / test (./cmd/caddy/caddy, ~1.24.1, ubuntu-latest, 0, 1.24, linux) (push) Failing after 1m56s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.24.1, 1.24, aix) (push) Successful in 1m12s
Cross-Build / build (~1.24.1, 1.24, darwin) (push) Successful in 1m11s
Cross-Build / build (~1.24.1, 1.24, dragonfly) (push) Successful in 1m11s
Cross-Build / build (~1.24.1, 1.24, freebsd) (push) Successful in 1m11s
Cross-Build / build (~1.24.1, 1.24, illumos) (push) Successful in 1m11s
Cross-Build / build (~1.24.1, 1.24, linux) (push) Successful in 1m17s
Cross-Build / build (~1.24.1, 1.24, netbsd) (push) Successful in 1m12s
Cross-Build / build (~1.24.1, 1.24, openbsd) (push) Successful in 1m12s
Cross-Build / build (~1.24.1, 1.24, solaris) (push) Successful in 1m14s
Cross-Build / build (~1.24.1, 1.24, windows) (push) Successful in 1m13s
Lint / lint (ubuntu-latest, linux) (push) Successful in 2m9s
Lint / govulncheck (push) Successful in 1m30s
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy, ~1.24.1, macos-14, 0, 1.24, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.24.1, windows-latest, True, 1.24, windows) (push) Has been cancelled
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-04-28 09:14:09 -06:00
Steffen Busch 54d03ced48 fileserver: Add support for .avif image format (#6988) 2025-04-28 08:32:59 -06:00
Indra Gunawan 89ed5f44de fix: Remove nil arg from zapslog.NewHandler call (#6984) 2025-04-28 08:31:10 -06:00
Matthew Holt 105eee671c caddytls: Set local_ip, not remote_ip (#6952)
Follow-up on 35c8c2d92d where I was a dum-dum
2025-04-21 18:32:51 -06:00
Mohammed Al Sahaf 737936c06b reverseproxy: reference correct field name in LoadModule (#6978)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-04-21 08:43:27 -06:00
Marten Seemann a6d488a15b go.mod: update quic-go to v0.51.0 (#6972) 2025-04-20 07:39:00 -06:00
Matthew Holt fb22a26b1a caddytls: Allow missing ECH meta file 2025-04-18 12:20:21 -06:00
Matt Holt 1bfa111552 caddytls: Prefer managed wildcard certs over individual subdomain certs (#6959)
* caddytls: Prefer managed wildcard certs over individual subdomain certs

* Repurpose force_automate as no_wildcard

* Fix a couple bugs

* Restore force_automate and use automate loader as wildcard override
2025-04-18 11:44:23 -06:00
Matthew Holt 35c8c2d92d caddytls: Add remote_ip to HTTP cert manager (close #6952) 2025-04-17 16:43:06 -06:00
32 changed files with 683 additions and 1063 deletions
+14
View File
@@ -424,6 +424,13 @@ func replaceLocalAdminServer(cfg *Config, ctx Context) error {
handler := cfg.Admin.newAdminHandler(addr, false, ctx)
// run the provisioners for loaded modules to make sure local
// state is properly re-initialized in the new admin server
err = cfg.Admin.provisionAdminRouters(ctx)
if err != nil {
return err
}
ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{})
if err != nil {
return err
@@ -545,6 +552,13 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
// because we are using TLS authentication instead
handler := cfg.Admin.newAdminHandler(addr, true, ctx)
// run the provisioners for loaded modules to make sure local
// state is properly re-initialized in the new admin server
err = cfg.Admin.provisionAdminRouters(ctx)
if err != nil {
return err
}
// create client certificate pool for TLS mutual auth, and extract public keys
// so that we can enforce access controls at the application layer
clientCertPool := x509.NewCertPool()
+8 -8
View File
@@ -505,14 +505,6 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
return ctx, err
}
// start the admin endpoint (and stop any prior one)
if replaceAdminServer {
err = replaceLocalAdminServer(newCfg, ctx)
if err != nil {
return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
}
}
// create the new filesystem map
newCfg.fileSystems = &filesystems.FileSystemMap{}
@@ -544,6 +536,14 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
return ctx, err
}
// start the admin endpoint (and stop any prior one)
if replaceAdminServer {
err = replaceLocalAdminServer(newCfg, ctx)
if err != nil {
return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
}
}
// Load and Provision each app and their submodules
err = func() error {
for appName := range newCfg.AppsRaw {
+39 -31
View File
@@ -633,12 +633,6 @@ func (st *ServerType) serversFromPairings(
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
}
srv.AutoHTTPS.IgnoreLoadedCerts = true
case "prefer_wildcard":
if srv.AutoHTTPS == nil {
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
}
srv.AutoHTTPS.PreferWildcard = true
}
}
@@ -706,16 +700,6 @@ func (st *ServerType) serversFromPairings(
return specificity(iLongestHost) > specificity(jLongestHost)
})
// collect all hosts that have a wildcard in them
wildcardHosts := []string{}
for _, sblock := range p.serverBlocks {
for _, addr := range sblock.parsedKeys {
if strings.HasPrefix(addr.Host, "*.") {
wildcardHosts = append(wildcardHosts, addr.Host[2:])
}
}
}
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled
@@ -801,7 +785,13 @@ func (st *ServerType) serversFromPairings(
cp.FallbackSNI = fallbackSNI
}
// only append this policy if it actually changes something
// only append this policy if it actually changes something,
// or if the configuration explicitly automates certs for
// these names (this is necessary to hoist a connection policy
// above one that may manually load a wildcard cert that would
// otherwise clobber the automated one; the code that appends
// policies that manually load certs comes later, so they're
// lower in the list)
if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) {
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
hasCatchAllTLSConnPolicy = len(hosts) == 0
@@ -841,18 +831,6 @@ func (st *ServerType) serversFromPairings(
addressQualifiesForTLS = true
}
// If prefer wildcard is enabled, then we add hosts that are
// already covered by the wildcard to the skip list
if addressQualifiesForTLS && srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard {
baseDomain := addr.Host
if idx := strings.Index(baseDomain, "."); idx != -1 {
baseDomain = baseDomain[idx+1:]
}
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
srv.AutoHTTPS.SkipCerts = append(srv.AutoHTTPS.SkipCerts, addr.Host)
}
}
// predict whether auto-HTTPS will add the conn policy for us; if so, we
// may not need to add one for this server
autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy &&
@@ -1083,11 +1061,40 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
// if they're exactly equal in every way, just keep one of them
if reflect.DeepEqual(cps[i], cps[j]) {
cps = append(cps[:j], cps[j+1:]...)
cps = slices.Delete(cps, j, j+1)
i--
break
}
// as a special case, if there are adjacent TLS conn policies that are identical except
// by their matchers, and the matchers are specifically just ServerName ("sni") matchers
// (by far the most common), we can combine them into a single policy
if i == j-1 && len(cps[i].MatchersRaw) == 1 && len(cps[j].MatchersRaw) == 1 {
if iSNIMatcherJSON, ok := cps[i].MatchersRaw["sni"]; ok {
if jSNIMatcherJSON, ok := cps[j].MatchersRaw["sni"]; ok {
// position of policies and the matcher criteria check out; if settings are
// the same, then we can combine the policies; we have to unmarshal and
// remarshal the matchers though
if cps[i].SettingsEqual(*cps[j]) {
var iSNIMatcher caddytls.MatchServerName
if err := json.Unmarshal(iSNIMatcherJSON, &iSNIMatcher); err == nil {
var jSNIMatcher caddytls.MatchServerName
if err := json.Unmarshal(jSNIMatcherJSON, &jSNIMatcher); err == nil {
iSNIMatcher = append(iSNIMatcher, jSNIMatcher...)
cps[i].MatchersRaw["sni"], err = json.Marshal(iSNIMatcher)
if err != nil {
return nil, fmt.Errorf("recombining SNI matchers: %v", err)
}
cps = slices.Delete(cps, j, j+1)
i--
break
}
}
}
}
}
}
// if they have the same matcher, try to reconcile each field: either they must
// be identical, or we have to be able to combine them safely
if reflect.DeepEqual(cps[i].MatchersRaw, cps[j].MatchersRaw) {
@@ -1189,12 +1196,13 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
}
}
cps = append(cps[:j], cps[j+1:]...)
cps = slices.Delete(cps, j, j+1)
i--
break
}
}
}
return cps, nil
}
+7 -7
View File
@@ -92,11 +92,9 @@ func (st ServerType) buildTLSApp(
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
}
// collect all hosts that have a wildcard in them, and arent HTTP
wildcardHosts := []string{}
// hosts that have been explicitly marked to be automated,
// even if covered by another wildcard
forcedAutomatedNames := make(map[string]struct{})
var wildcardHosts []string // collect all hosts that have a wildcard in them, and aren't HTTP
forcedAutomatedNames := make(map[string]struct{}) // explicitly configured to be automated, even if covered by a wildcard
for _, p := range pairings {
var addresses []string
for _, addressWithProtocols := range p.addressesWithProtocols {
@@ -153,7 +151,7 @@ func (st ServerType) buildTLSApp(
ap.OnDemand = true
}
// collect hosts that are forced to be automated
// collect hosts that are forced to have certs automated for their specific name
if _, ok := sblock.pile["tls.force_automate"]; ok {
for _, host := range sblockHosts {
forcedAutomatedNames[host] = struct{}{}
@@ -375,7 +373,9 @@ func (st ServerType) buildTLSApp(
return nil, warnings, err
}
for _, cfg := range ech.Configs {
ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
if cfg.PublicName != "" {
ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
}
}
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
@@ -0,0 +1,72 @@
{
pki {
ca custom-ca {
name "Custom CA"
}
}
}
acme.example.com {
acme_server {
ca custom-ca
allow {
domains host-1.internal.example.com host-2.internal.example.com
}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"acme.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "custom-ca",
"handler": "acme_server",
"policy": {
"allow": {
"domains": [
"host-1.internal.example.com",
"host-2.internal.example.com"
]
}
}
}
]
}
]
}
],
"terminal": true
}
]
}
}
},
"pki": {
"certificate_authorities": {
"custom-ca": {
"name": "Custom CA"
}
}
}
}
}
@@ -0,0 +1,80 @@
{
pki {
ca custom-ca {
name "Custom CA"
}
}
}
acme.example.com {
acme_server {
ca custom-ca
allow {
domains host-1.internal.example.com host-2.internal.example.com
}
deny {
domains dc.internal.example.com
}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"acme.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "custom-ca",
"handler": "acme_server",
"policy": {
"allow": {
"domains": [
"host-1.internal.example.com",
"host-2.internal.example.com"
]
},
"deny": {
"domains": [
"dc.internal.example.com"
]
}
}
}
]
}
]
}
],
"terminal": true
}
]
}
}
},
"pki": {
"certificate_authorities": {
"custom-ca": {
"name": "Custom CA"
}
}
}
}
}
@@ -0,0 +1,71 @@
{
pki {
ca custom-ca {
name "Custom CA"
}
}
}
acme.example.com {
acme_server {
ca custom-ca
deny {
domains dc.internal.example.com
}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"acme.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "custom-ca",
"handler": "acme_server",
"policy": {
"deny": {
"domains": [
"dc.internal.example.com"
]
}
}
}
]
}
]
}
],
"terminal": true
}
]
}
}
},
"pki": {
"certificate_authorities": {
"custom-ca": {
"name": "Custom CA"
}
}
}
}
}
@@ -1,109 +0,0 @@
{
auto_https prefer_wildcard
}
*.example.com {
tls {
dns mock
}
respond "fallback"
}
foo.example.com {
respond "foo"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"foo.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"skip_certificates": [
"foo.example.com"
],
"prefer_wildcard": true
}
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"*.example.com"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"name": "mock"
}
}
},
"module": "acme"
}
]
}
]
}
}
}
}
@@ -1,268 +0,0 @@
{
auto_https prefer_wildcard
}
# Covers two domains
*.one.example.com {
tls {
dns mock
}
respond "one fallback"
}
# Is covered, should not get its own AP
foo.one.example.com {
respond "foo one"
}
# This one has its own tls config so it doesn't get covered (escape hatch)
bar.one.example.com {
respond "bar one"
tls bar@bar.com
}
# Covers nothing but AP gets consolidated with the first
*.two.example.com {
tls {
dns mock
}
respond "two fallback"
}
# Is HTTP so it should not cover
http://*.three.example.com {
respond "three fallback"
}
# Has no wildcard coverage so it gets an AP
foo.three.example.com {
respond "foo three"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"foo.three.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo three",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"foo.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo one",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"bar.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "bar one",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "one fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.two.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "two fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"skip_certificates": [
"foo.one.example.com",
"bar.one.example.com"
],
"prefer_wildcard": true
}
},
"srv1": {
"listen": [
":80"
],
"routes": [
{
"match": [
{
"host": [
"*.three.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "three fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"prefer_wildcard": true
}
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"foo.three.example.com"
]
},
{
"subjects": [
"bar.one.example.com"
],
"issuers": [
{
"email": "bar@bar.com",
"module": "acme"
},
{
"ca": "https://acme.zerossl.com/v2/DV90",
"email": "bar@bar.com",
"module": "acme"
}
]
},
{
"subjects": [
"*.one.example.com",
"*.two.example.com"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"name": "mock"
}
}
},
"module": "acme"
}
]
}
]
}
}
}
}
@@ -131,13 +131,7 @@ shadowed.example.com {
{
"match": {
"sni": [
"automated1.example.com"
]
}
},
{
"match": {
"sni": [
"automated1.example.com",
"automated2.example.com"
]
}
-6
View File
@@ -809,9 +809,3 @@ func configFileWithRespectToDefault(logger *zap.Logger, configFile string) (stri
// default config file does not exist or is irrelevant
return configFile, nil
}
type moduleInfo struct {
caddyModuleID string
goModule *debug.Module
err error
}
-45
View File
@@ -395,51 +395,6 @@ is always printed to stdout.
},
})
RegisterCommand(Command{
Name: "upgrade",
Short: "Upgrade Caddy (EXPERIMENTAL)",
Long: `
Downloads an updated Caddy binary with the same modules/plugins at the
latest versions. EXPERIMENTAL: May be changed or removed.
`,
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
cmd.RunE = WrapCommandFuncForCobra(cmdUpgrade)
},
})
RegisterCommand(Command{
Name: "add-package",
Usage: "<package[@version]...>",
Short: "Adds Caddy packages (EXPERIMENTAL)",
Long: `
Downloads an updated Caddy binary with the specified packages (module/plugin)
added, with an optional version specified (e.g., "package@version"). Retains
existing packages. Returns an error if any of the specified packages are already
included. EXPERIMENTAL: May be changed or removed.
`,
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
cmd.RunE = WrapCommandFuncForCobra(cmdAddPackage)
},
})
RegisterCommand(Command{
Name: "remove-package",
Func: cmdRemovePackage,
Usage: "<packages...>",
Short: "Removes Caddy packages (EXPERIMENTAL)",
Long: `
Downloads an updated Caddy binaries without the specified packages (module/plugin).
Returns an error if any of the packages are not included.
EXPERIMENTAL: May be changed or removed.
`,
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
cmd.RunE = WrapCommandFuncForCobra(cmdRemovePackage)
},
})
defaultFactory.Use(func(rootCmd *cobra.Command) {
rootCmd.AddCommand(caddyCmdToCobra(Command{
Name: "manpage",
+66
View File
@@ -0,0 +1,66 @@
package caddycmd
import (
"fmt"
"reflect"
"runtime/debug"
"strings"
"github.com/caddyserver/caddy/v2"
)
type moduleInfo struct {
caddyModuleID string
goModule *debug.Module
err error
}
func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
bi, ok := debug.ReadBuildInfo()
if !ok {
err = fmt.Errorf("no build info")
return
}
for _, modID := range caddy.Modules() {
modInfo, err := caddy.GetModule(modID)
if err != nil {
// that's weird, shouldn't happen
unknown = append(unknown, moduleInfo{caddyModuleID: modID, err: err})
continue
}
// to get the Caddy plugin's version info, we need to know
// the package that the Caddy module's value comes from; we
// can use reflection but we need a non-pointer value (I'm
// not sure why), and since New() should return a pointer
// value, we need to dereference it first
iface := any(modInfo.New())
if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr {
iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface()
}
modPkgPath := reflect.TypeOf(iface).PkgPath()
// now we find the Go module that the Caddy module's package
// belongs to; we assume the Caddy module package path will
// be prefixed by its Go module path, and we will choose the
// longest matching prefix in case there are nested modules
var matched *debug.Module
for _, dep := range bi.Deps {
if strings.HasPrefix(modPkgPath, dep.Path) {
if matched == nil || len(dep.Path) > len(matched.Path) {
matched = dep
}
}
}
caddyModGoMod := moduleInfo{caddyModuleID: modID, goModule: matched}
if strings.HasPrefix(modPkgPath, caddy.ImportPath) {
standard = append(standard, caddyModGoMod)
} else {
nonstandard = append(nonstandard, caddyModGoMod)
}
}
return
}
-354
View File
@@ -1,354 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddycmd
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"runtime/debug"
"strings"
"go.uber.org/zap"
"github.com/caddyserver/caddy/v2"
)
func cmdUpgrade(fl Flags) (int, error) {
_, nonstandard, _, err := getModules()
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("unable to enumerate installed plugins: %v", err)
}
pluginPkgs, err := getPluginPackages(nonstandard)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
return upgradeBuild(pluginPkgs, fl)
}
func splitModule(arg string) (module, version string, err error) {
const versionSplit = "@"
// accommodate module paths that have @ in them, but we can only tolerate that if there's also
// a version, otherwise we don't know if it's a version separator or part of the file path
lastVersionSplit := strings.LastIndex(arg, versionSplit)
if lastVersionSplit < 0 {
module = arg
} else {
module, version = arg[:lastVersionSplit], arg[lastVersionSplit+1:]
}
if module == "" {
err = fmt.Errorf("module name is required")
}
return
}
func cmdAddPackage(fl Flags) (int, error) {
if len(fl.Args()) == 0 {
return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified")
}
_, nonstandard, _, err := getModules()
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("unable to enumerate installed plugins: %v", err)
}
pluginPkgs, err := getPluginPackages(nonstandard)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
for _, arg := range fl.Args() {
module, version, err := splitModule(arg)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
}
// only allow a version to be specified if it's different from the existing version
if _, ok := pluginPkgs[module]; ok && !(version != "" && pluginPkgs[module].Version != version) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added")
}
pluginPkgs[module] = pluginPackage{Version: version, Path: module}
}
return upgradeBuild(pluginPkgs, fl)
}
func cmdRemovePackage(fl Flags) (int, error) {
if len(fl.Args()) == 0 {
return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified")
}
_, nonstandard, _, err := getModules()
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("unable to enumerate installed plugins: %v", err)
}
pluginPkgs, err := getPluginPackages(nonstandard)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
for _, arg := range fl.Args() {
module, _, err := splitModule(arg)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
}
if _, ok := pluginPkgs[module]; !ok {
// package does not exist
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added")
}
delete(pluginPkgs, arg)
}
return upgradeBuild(pluginPkgs, fl)
}
func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) {
l := caddy.Log()
thisExecPath, err := os.Executable()
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("determining current executable path: %v", err)
}
thisExecStat, err := os.Stat(thisExecPath)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("retrieving current executable permission bits: %v", err)
}
if thisExecStat.Mode()&os.ModeSymlink == os.ModeSymlink {
symSource := thisExecPath
// we are a symlink; resolve it
thisExecPath, err = filepath.EvalSymlinks(thisExecPath)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("resolving current executable symlink: %v", err)
}
l.Info("this executable is a symlink", zap.String("source", symSource), zap.String("target", thisExecPath))
}
l.Info("this executable will be replaced", zap.String("path", thisExecPath))
// build the request URL to download this custom build
qs := url.Values{
"os": {runtime.GOOS},
"arch": {runtime.GOARCH},
}
for _, pkgInfo := range pluginPkgs {
qs.Add("p", pkgInfo.String())
}
// initiate the build
resp, err := downloadBuild(qs)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("download failed: %v", err)
}
defer resp.Body.Close()
// back up the current binary, in case something goes wrong we can replace it
backupExecPath := thisExecPath + ".tmp"
l.Info("build acquired; backing up current executable",
zap.String("current_path", thisExecPath),
zap.String("backup_path", backupExecPath))
err = os.Rename(thisExecPath, backupExecPath)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("backing up current binary: %v", err)
}
defer func() {
if err != nil {
err2 := os.Rename(backupExecPath, thisExecPath)
if err2 != nil {
l.Error("restoring original executable failed; will need to be restored manually",
zap.String("backup_path", backupExecPath),
zap.String("original_path", thisExecPath),
zap.Error(err2))
}
}
}()
// download the file; do this in a closure to close reliably before we execute it
err = writeCaddyBinary(thisExecPath, &resp.Body, thisExecStat)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
l.Info("download successful; displaying new binary details", zap.String("location", thisExecPath))
// use the new binary to print out version and module info
fmt.Print("\nModule versions:\n\n")
if err = listModules(thisExecPath); err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute 'caddy list-modules': %v", err)
}
fmt.Println("\nVersion:")
if err = showVersion(thisExecPath); err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute 'caddy version': %v", err)
}
fmt.Println()
// clean up the backup file
if !fl.Bool("keep-backup") {
if err = removeCaddyBinary(backupExecPath); err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to clean up backup binary: %v", err)
}
} else {
l.Info("skipped cleaning up the backup file", zap.String("backup_path", backupExecPath))
}
l.Info("upgrade successful; please restart any running Caddy instances", zap.String("executable", thisExecPath))
return caddy.ExitCodeSuccess, nil
}
func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
bi, ok := debug.ReadBuildInfo()
if !ok {
err = fmt.Errorf("no build info")
return
}
for _, modID := range caddy.Modules() {
modInfo, err := caddy.GetModule(modID)
if err != nil {
// that's weird, shouldn't happen
unknown = append(unknown, moduleInfo{caddyModuleID: modID, err: err})
continue
}
// to get the Caddy plugin's version info, we need to know
// the package that the Caddy module's value comes from; we
// can use reflection but we need a non-pointer value (I'm
// not sure why), and since New() should return a pointer
// value, we need to dereference it first
iface := any(modInfo.New())
if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr {
iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface()
}
modPkgPath := reflect.TypeOf(iface).PkgPath()
// now we find the Go module that the Caddy module's package
// belongs to; we assume the Caddy module package path will
// be prefixed by its Go module path, and we will choose the
// longest matching prefix in case there are nested modules
var matched *debug.Module
for _, dep := range bi.Deps {
if strings.HasPrefix(modPkgPath, dep.Path) {
if matched == nil || len(dep.Path) > len(matched.Path) {
matched = dep
}
}
}
caddyModGoMod := moduleInfo{caddyModuleID: modID, goModule: matched}
if strings.HasPrefix(modPkgPath, caddy.ImportPath) {
standard = append(standard, caddyModGoMod)
} else {
nonstandard = append(nonstandard, caddyModGoMod)
}
}
return
}
func listModules(path string) error {
cmd := exec.Command(path, "list-modules", "--versions", "--skip-standard")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func showVersion(path string) error {
cmd := exec.Command(path, "version")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func downloadBuild(qs url.Values) (*http.Response, error) {
l := caddy.Log()
l.Info("requesting build",
zap.String("os", qs.Get("os")),
zap.String("arch", qs.Get("arch")),
zap.Strings("packages", qs["p"]))
resp, err := http.Get(fmt.Sprintf("%s?%s", downloadPath, qs.Encode()))
if err != nil {
return nil, fmt.Errorf("secure request failed: %v", err)
}
if resp.StatusCode >= 400 {
var details struct {
StatusCode int `json:"status_code"`
Error struct {
Message string `json:"message"`
ID string `json:"id"`
} `json:"error"`
}
err2 := json.NewDecoder(resp.Body).Decode(&details)
if err2 != nil {
return nil, fmt.Errorf("download and error decoding failed: HTTP %d: %v", resp.StatusCode, err2)
}
return nil, fmt.Errorf("download failed: HTTP %d: %s (id=%s)", resp.StatusCode, details.Error.Message, details.Error.ID)
}
return resp, nil
}
func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) {
pluginPkgs := make(map[string]pluginPackage)
for _, mod := range modules {
if mod.goModule.Replace != nil {
return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s",
mod.goModule.Path, mod.goModule.Replace.Path)
}
pluginPkgs[mod.goModule.Path] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path}
}
return pluginPkgs, nil
}
func writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.FileInfo) error {
l := caddy.Log()
destFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileInfo.Mode())
if err != nil {
return fmt.Errorf("unable to open destination file: %v", err)
}
defer destFile.Close()
l.Info("downloading binary", zap.String("destination", path))
_, err = io.Copy(destFile, *body)
if err != nil {
return fmt.Errorf("unable to download file: %v", err)
}
err = destFile.Sync()
if err != nil {
return fmt.Errorf("syncing downloaded file to device: %v", err)
}
return nil
}
const downloadPath = "https://caddyserver.com/api/download"
type pluginPackage struct {
Version string
Path string
}
func (p pluginPackage) String() string {
if p.Version == "" {
return p.Path
}
return p.Path + "@" + p.Version
}
-29
View File
@@ -1,29 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !windows
package caddycmd
import (
"os"
)
// removeCaddyBinary removes the Caddy binary at the given path.
//
// On any non-Windows OS, this simply calls os.Remove, since they should
// probably not exhibit any issue with processes deleting themselves.
func removeCaddyBinary(path string) error {
return os.Remove(path)
}
-39
View File
@@ -1,39 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddycmd
import (
"os"
"path/filepath"
"syscall"
)
// removeCaddyBinary removes the Caddy binary at the given path.
//
// On Windows, this uses a syscall to indirectly remove the file,
// because otherwise we get an "Access is denied." error when trying
// to delete the binary while Caddy is still running and performing
// the upgrade. "cmd.exe /C" executes a command specified by the
// following arguments, i.e. "del" which will run as a separate process,
// which avoids the "Access is denied." error.
func removeCaddyBinary(path string) error {
var sI syscall.StartupInfo
var pI syscall.ProcessInformation
argv, err := syscall.UTF16PtrFromString(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe") + " /C del " + path)
if err != nil {
return err
}
return syscall.CreateProcess(nil, argv, nil, nil, true, 0, nil, nil, &sI, &pI)
}
+2 -2
View File
@@ -577,11 +577,11 @@ func (ctx Context) Slogger() *slog.Logger {
if err != nil {
panic("config missing, unable to create dev logger: " + err.Error())
}
return slog.New(zapslog.NewHandler(l.Core(), nil))
return slog.New(zapslog.NewHandler(l.Core()))
}
mod := ctx.Module()
if mod == nil {
return slog.New(zapslog.NewHandler(Log().Core(), nil))
return slog.New(zapslog.NewHandler(Log().Core()))
}
return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(),
zapslog.WithName(string(mod.CaddyModule().ID)),
+1 -1
View File
@@ -19,7 +19,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.10
github.com/mholt/acmez/v3 v3.1.2
github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.50.1
github.com/quic-go/quic-go v0.51.0
github.com/smallstep/certificates v0.26.1
github.com/smallstep/nosql v0.6.1
github.com/smallstep/truststore v0.13.0
+2 -2
View File
@@ -397,8 +397,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/quic-go/quic-go v0.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc=
github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+22
View File
@@ -0,0 +1,22 @@
package internal
import "fmt"
// MaxSizeSubjectsListForLog returns the keys in the map as a slice of maximum length
// maxToDisplay. It is useful for logging domains being managed, for example, since a
// map is typically needed for quick lookup, but a slice is needed for logging, and this
// can be quite a doozy since there may be a huge amount (hundreds of thousands).
func MaxSizeSubjectsListForLog(subjects map[string]struct{}, maxToDisplay int) []string {
numberOfNamesToDisplay := min(len(subjects), maxToDisplay)
domainsToDisplay := make([]string, 0, numberOfNamesToDisplay)
for domain := range subjects {
domainsToDisplay = append(domainsToDisplay, domain)
if len(domainsToDisplay) >= numberOfNamesToDisplay {
break
}
}
if len(subjects) > maxToDisplay {
domainsToDisplay = append(domainsToDisplay, fmt.Sprintf("(and %d more...)", len(subjects)-maxToDisplay))
}
return domainsToDisplay
}
+6 -5
View File
@@ -20,6 +20,7 @@ import (
"io"
"log"
"os"
"slices"
"strings"
"sync"
"time"
@@ -161,7 +162,9 @@ func (logging *Logging) setupNewDefault(ctx Context) error {
if err != nil {
return fmt.Errorf("setting up default log: %v", err)
}
newDefault.logger = zap.New(newDefault.CustomLog.core, options...)
filteringCore := &filteringCore{newDefault.CustomLog.core, newDefault.CustomLog}
newDefault.logger = zap.New(filteringCore, options...)
// redirect the default caddy logs
defaultLoggerMu.Lock()
@@ -490,10 +493,8 @@ func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
if len(cl.Include) > 0 && len(cl.Exclude) > 0 {
// prevent intersections
for _, allow := range cl.Include {
for _, deny := range cl.Exclude {
if allow == deny {
return fmt.Errorf("include and exclude must not intersect, but found %s in both lists", allow)
}
if slices.Contains(cl.Exclude, allow) {
return fmt.Errorf("include and exclude must not intersect, but found %s in both lists", allow)
}
}
+106
View File
@@ -0,0 +1,106 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddy
import "testing"
func TestCustomLog_loggerAllowed(t *testing.T) {
type fields struct {
BaseLog BaseLog
Include []string
Exclude []string
}
type args struct {
name string
isModule bool
}
tests := []struct {
name string
fields fields
args args
want bool
}{
{
name: "include",
fields: fields{
Include: []string{"foo"},
},
args: args{
name: "foo",
isModule: true,
},
want: true,
},
{
name: "exclude",
fields: fields{
Exclude: []string{"foo"},
},
args: args{
name: "foo",
isModule: true,
},
want: false,
},
{
name: "include and exclude",
fields: fields{
Include: []string{"foo"},
Exclude: []string{"foo"},
},
args: args{
name: "foo",
isModule: true,
},
want: false,
},
{
name: "include and exclude (longer namespace)",
fields: fields{
Include: []string{"foo.bar"},
Exclude: []string{"foo"},
},
args: args{
name: "foo.bar",
isModule: true,
},
want: true,
},
{
name: "excluded module is not printed",
fields: fields{
Include: []string{"admin.api.load"},
Exclude: []string{"admin.api"},
},
args: args{
name: "admin.api",
isModule: false,
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cl := &CustomLog{
BaseLog: tt.fields.BaseLog,
Include: tt.fields.Include,
Exclude: tt.fields.Exclude,
}
if got := cl.loggerAllowed(tt.args.name, tt.args.isModule); got != tt.want {
t.Errorf("CustomLog.loggerAllowed() = %v, want %v", got, tt.want)
}
})
}
}
+1 -1
View File
@@ -152,7 +152,7 @@ type App struct {
tlsApp *caddytls.TLS
// used temporarily between phases 1 and 2 of auto HTTPS
allCertDomains []string
allCertDomains map[string]struct{}
}
// CaddyModule returns the Caddy module information.
+8 -43
View File
@@ -25,6 +25,7 @@ import (
"go.uber.org/zap"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/internal"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
@@ -65,12 +66,6 @@ type AutoHTTPSConfig struct {
// enabled. To force automated certificate management
// regardless of loaded certificates, set this to true.
IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
// If true, automatic HTTPS will prefer wildcard names
// and ignore non-wildcard names if both are available.
// This allows for writing a config with top-level host
// matchers without having those names produce certificates.
PreferWildcard bool `json:"prefer_wildcard,omitempty"`
}
// automaticHTTPSPhase1 provisions all route matchers, determines
@@ -163,33 +158,8 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
}
}
// trim the list of domains covered by wildcards, if configured
if srv.AutoHTTPS.PreferWildcard {
wildcards := make(map[string]struct{})
for d := range serverDomainSet {
if strings.HasPrefix(d, "*.") {
wildcards[d[2:]] = struct{}{}
}
}
for d := range serverDomainSet {
if strings.HasPrefix(d, "*.") {
continue
}
base := d
if idx := strings.Index(d, "."); idx != -1 {
base = d[idx+1:]
}
if _, ok := wildcards[base]; ok {
delete(serverDomainSet, d)
}
}
}
// build the list of domains that could be used with ECH (if enabled)
// so the TLS app can know to publish ECH configs for them; we do this
// after trimming domains covered by wildcards because, presumably,
// if the user wants to use wildcard certs, they also want to use the
// wildcard for ECH, rather than individual subdomains
// so the TLS app can know to publish ECH configs for them
echDomains := make([]string, 0, len(serverDomainSet))
for d := range serverDomainSet {
echDomains = append(echDomains, d)
@@ -295,19 +265,10 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
}
}
// we now have a list of all the unique names for which we need certs;
// turn the set into a slice so that phase 2 can use it
app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
// we now have a list of all the unique names for which we need certs
var internal, tailscale []string
uniqueDomainsLoop:
for d := range uniqueDomainsForCerts {
if !isTailscaleDomain(d) {
// whether or not there is already an automation policy for this
// name, we should add it to the list to manage a cert for it,
// unless it's a Tailscale domain, because we don't manage those
app.allCertDomains = append(app.allCertDomains, d)
}
// some names we've found might already have automation policies
// explicitly specified for them; we should exclude those from
// our hidden/implicit policy, since applying a name to more than
@@ -346,6 +307,7 @@ uniqueDomainsLoop:
}
if isTailscaleDomain(d) {
tailscale = append(tailscale, d)
delete(uniqueDomainsForCerts, d) // not managed by us; handled separately
} else if shouldUseInternal(d) {
internal = append(internal, d)
}
@@ -475,6 +437,9 @@ redirServersLoop:
}
}
// persist the domains/IPs we're managing certs for through provisioning/startup
app.allCertDomains = uniqueDomainsForCerts
logger.Debug("adjusted config",
zap.Reflect("tls", app.tlsApp),
zap.Reflect("http", app))
@@ -777,7 +742,7 @@ func (app *App) automaticHTTPSPhase2() error {
return nil
}
app.logger.Info("enabling automatic TLS certificate management",
zap.Strings("domains", app.allCertDomains),
zap.Strings("domains", internal.MaxSizeSubjectsListForLog(app.allCertDomains, 1000)),
)
err := app.tlsApp.Manage(app.allCertDomains)
if err != nil {
+3 -3
View File
@@ -26,7 +26,7 @@
<path d="M9 7l4 0"/>
<path d="M9 11l4 0"/>
</svg>
{{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic" ".svg"}}
{{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic" ".svg" ".avif"}}
{{- if eq .Tpl.Layout "grid"}}
<img loading="lazy" src="{{.Name | pathEscape}}">
{{- else}}
@@ -802,7 +802,7 @@ footer {
<b>{{.NumFiles}}</b> file{{if ne 1 .NumFiles}}s{{end}}
</span>
<span class="meta-item">
<b>{{.HumanTotalFileSize}}</b> total
<b>{{.HumanTotalFileSize}}</b> total
</span>
{{- if ne 0 .Limit}}
<span class="meta-item">
@@ -868,7 +868,7 @@ footer {
</svg>
</a>
{{- end}}
{{- if and (eq .Sort "name") (ne .Order "desc")}}
<a href="?sort=name&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">
Name
+34 -40
View File
@@ -353,7 +353,7 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
h.NetworkProxyRaw = caddyconfig.JSONModuleObject(u, "from", "url", nil)
}
if len(h.NetworkProxyRaw) != 0 {
proxyMod, err := caddyCtx.LoadModule(h, "ForwardProxyRaw")
proxyMod, err := caddyCtx.LoadModule(h, "NetworkProxyRaw")
if err != nil {
return nil, fmt.Errorf("failed to load network_proxy module: %v", err)
}
@@ -382,6 +382,36 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
if err != nil {
return nil, fmt.Errorf("making TLS client config: %v", err)
}
// servername has a placeholder, so we need to replace it
if strings.Contains(h.TLS.ServerName, "{") {
rt.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
// reuses the dialer from above to establish a plaintext connection
conn, err := dialContext(ctx, network, addr)
if err != nil {
return nil, err
}
// but add our own handshake logic
repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
tlsConfig := rt.TLSClientConfig.Clone()
tlsConfig.ServerName = repl.ReplaceAll(tlsConfig.ServerName, "")
tlsConn := tls.Client(conn, tlsConfig)
// complete the handshake before returning the connection
if rt.TLSHandshakeTimeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, rt.TLSHandshakeTimeout)
defer cancel()
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
_ = tlsConn.Close()
return nil, err
}
return tlsConn, nil
}
}
}
if h.KeepAlive != nil {
@@ -453,45 +483,9 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
return rt, nil
}
// replaceTLSServername checks TLS servername to see if it needs replacing
// if it does need replacing, it creates a new cloned HTTPTransport object to avoid any races
// and does the replacing of the TLS servername on that and returns the new object
// if no replacement is necessary it returns the original
func (h *HTTPTransport) replaceTLSServername(repl *caddy.Replacer) *HTTPTransport {
// check whether we have TLS and need to replace the servername in the TLSClientConfig
if h.TLSEnabled() && strings.Contains(h.TLS.ServerName, "{") {
// make a new h, "copy" the parts we don't need to touch, add a new *tls.Config and replace servername
newtransport := &HTTPTransport{
Resolver: h.Resolver,
TLS: h.TLS,
KeepAlive: h.KeepAlive,
Compression: h.Compression,
MaxConnsPerHost: h.MaxConnsPerHost,
DialTimeout: h.DialTimeout,
FallbackDelay: h.FallbackDelay,
ResponseHeaderTimeout: h.ResponseHeaderTimeout,
ExpectContinueTimeout: h.ExpectContinueTimeout,
MaxResponseHeaderSize: h.MaxResponseHeaderSize,
WriteBufferSize: h.WriteBufferSize,
ReadBufferSize: h.ReadBufferSize,
Versions: h.Versions,
Transport: h.Transport.Clone(),
h2cTransport: h.h2cTransport,
}
newtransport.Transport.TLSClientConfig.ServerName = repl.ReplaceAll(newtransport.Transport.TLSClientConfig.ServerName, "")
return newtransport
}
return h
}
// RoundTrip implements http.RoundTripper.
func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// Try to replace TLS servername if needed
repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
transport := h.replaceTLSServername(repl)
transport.SetScheme(req)
h.SetScheme(req)
// use HTTP/3 if enabled (TODO: This is EXPERIMENTAL)
if h.h3Transport != nil {
@@ -507,7 +501,7 @@ func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return h.h2cTransport.RoundTrip(req)
}
return transport.Transport.RoundTrip(req)
return h.Transport.RoundTrip(req)
}
// SetScheme ensures that the outbound request req
@@ -652,7 +646,7 @@ func (t *TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error)
return nil, fmt.Errorf("getting tls app: %v", err)
}
tlsApp := tlsAppIface.(*caddytls.TLS)
err = tlsApp.Manage([]string{t.ClientCertificateAutomate})
err = tlsApp.Manage(map[string]struct{}{t.ClientCertificateAutomate: {}})
if err != nil {
return nil, fmt.Errorf("managing client certificate: %v", err)
}
+22 -26
View File
@@ -91,19 +91,17 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
acmeServer.Policy.AllowWildcardNames = true
case "allow":
r := &RuleSet{}
for h.Next() {
for h.NextBlock(h.Nesting() - 1) {
if h.CountRemainingArgs() == 0 {
return nil, h.ArgErr() // TODO:
}
switch h.Val() {
case "domains":
r.Domains = append(r.Domains, h.RemainingArgs()...)
case "ip_ranges":
r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
default:
return nil, h.Errf("unrecognized 'allow' subdirective: %s", h.Val())
}
for nesting := h.Nesting(); h.NextBlock(nesting); {
if h.CountRemainingArgs() == 0 {
return nil, h.ArgErr() // TODO:
}
switch h.Val() {
case "domains":
r.Domains = append(r.Domains, h.RemainingArgs()...)
case "ip_ranges":
r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
default:
return nil, h.Errf("unrecognized 'allow' subdirective: %s", h.Val())
}
}
if acmeServer.Policy == nil {
@@ -112,19 +110,17 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
acmeServer.Policy.Allow = r
case "deny":
r := &RuleSet{}
for h.Next() {
for h.NextBlock(h.Nesting() - 1) {
if h.CountRemainingArgs() == 0 {
return nil, h.ArgErr() // TODO:
}
switch h.Val() {
case "domains":
r.Domains = append(r.Domains, h.RemainingArgs()...)
case "ip_ranges":
r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
default:
return nil, h.Errf("unrecognized 'deny' subdirective: %s", h.Val())
}
for nesting := h.Nesting(); h.NextBlock(nesting); {
if h.CountRemainingArgs() == 0 {
return nil, h.ArgErr() // TODO:
}
switch h.Val() {
case "domains":
r.Domains = append(r.Domains, h.RemainingArgs()...)
case "ip_ranges":
r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
default:
return nil, h.Errf("unrecognized 'deny' subdirective: %s", h.Val())
}
}
if acmeServer.Policy == nil {
+1 -1
View File
@@ -220,7 +220,7 @@ func (iss *ACMEIssuer) makeIssuerTemplate(ctx caddy.Context) (certmagic.ACMEIssu
}
if len(iss.NetworkProxyRaw) != 0 {
proxyMod, err := ctx.LoadModule(iss, "ForwardProxyRaw")
proxyMod, err := ctx.LoadModule(iss, "NetworkProxyRaw")
if err != nil {
return template, fmt.Errorf("failed to load network_proxy module: %v", err)
}
+5
View File
@@ -5,6 +5,7 @@ import (
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
@@ -143,6 +144,10 @@ func (hcg HTTPCertGetter) GetCertificate(ctx context.Context, hello *tls.ClientH
qs.Set("server_name", hello.ServerName)
qs.Set("signature_schemes", strings.Join(sigs, ","))
qs.Set("cipher_suites", strings.Join(suites, ","))
localIP, _, err := net.SplitHostPort(hello.Conn.LocalAddr().String())
if err == nil && localIP != "" {
qs.Set("local_ip", localIP)
}
parsed.RawQuery = qs.Encode()
req, err := http.NewRequestWithContext(hcg.ctx, http.MethodGet, parsed.String(), nil)
+9
View File
@@ -24,6 +24,7 @@ import (
"fmt"
"io"
"os"
"reflect"
"strings"
"github.com/mholt/acmez/v3"
@@ -461,6 +462,14 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
p.InsecureSecretsLog == ""
}
// SettingsEmpty returns true if p's settings (fields
// except the matchers) are the same as q.
func (p ConnectionPolicy) SettingsEqual(q ConnectionPolicy) bool {
p.MatchersRaw = nil
q.MatchersRaw = nil
return reflect.DeepEqual(p, q)
}
// UnmarshalCaddyfile sets up the ConnectionPolicy from Caddyfile tokens. Syntax:
//
// connection_policy {
+21 -16
View File
@@ -138,7 +138,6 @@ func (ech *ECH) Provision(ctx caddy.Context) ([]string, error) {
// all existing configs are now loaded; see if we need to make any new ones
// based on the input configuration, and also mark the most recent one(s) as
// current/active, so they can be used for ECH retries
for _, cfg := range ech.Configs {
publicName := strings.ToLower(strings.TrimSpace(cfg.PublicName))
@@ -279,7 +278,7 @@ func (t *TLS) publishECHConfigs() error {
// if all the (inner) domains have had this ECH config list published
// by this publisher, then try the next publication config
if len(serverNamesSet) == 0 {
logger.Debug("ECH config list already published by publisher for associated domains",
logger.Debug("ECH config list already published by publisher for associated domains (or no domains to publish for)",
zap.Uint8s("config_ids", configIDs),
zap.String("publisher", publisherKey))
continue
@@ -300,7 +299,7 @@ func (t *TLS) publishECHConfigs() error {
err := publisher.PublishECHConfigList(t.ctx, dnsNamesToPublish, echCfgListBin)
if err == nil {
t.logger.Info("published ECH configuration list",
zap.Strings("domains", publication.Domains),
zap.Strings("domains", dnsNamesToPublish),
zap.Uint8s("config_ids", configIDs),
zap.Error(err))
// update publication history, so that we don't unnecessarily republish every time
@@ -390,27 +389,33 @@ func loadECHConfig(ctx caddy.Context, configID string) (echConfig, error) {
return echConfig{}, nil
}
metaBytes, err := storage.Load(ctx, metaKey)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
logger.Warn("ECH config metadata file missing; will recreate at next publication",
zap.String("config_id", configID),
zap.Error(err))
} else if err != nil {
delErr := storage.Delete(ctx, cfgIDKey)
if delErr != nil {
return echConfig{}, fmt.Errorf("error loading ECH metadata (%v) and cleaning up parent storage key %s: %v", err, cfgIDKey, delErr)
return echConfig{}, fmt.Errorf("error loading ECH config metadata (%v) and cleaning up parent storage key %s: %v", err, cfgIDKey, delErr)
}
logger.Warn("could not load ECH metadata; deleted its config folder",
logger.Warn("could not load ECH config metadata; deleted its folder",
zap.String("config_id", configID),
zap.Error(err))
return echConfig{}, nil
}
var meta echConfigMeta
if err := json.Unmarshal(metaBytes, &meta); err != nil {
// even though it's just metadata, reset the whole config since we can't reliably maintain it
delErr := storage.Delete(ctx, cfgIDKey)
if delErr != nil {
return echConfig{}, fmt.Errorf("error decoding ECH metadata (%v) and cleaning up parent storage key %s: %v", err, cfgIDKey, delErr)
if len(metaBytes) > 0 {
if err := json.Unmarshal(metaBytes, &meta); err != nil {
// even though it's just metadata, reset the whole config since we can't reliably maintain it
delErr := storage.Delete(ctx, cfgIDKey)
if delErr != nil {
return echConfig{}, fmt.Errorf("error decoding ECH metadata (%v) and cleaning up parent storage key %s: %v", err, cfgIDKey, delErr)
}
logger.Warn("could not JSON-decode ECH metadata; deleted its config folder",
zap.String("config_id", configID),
zap.Error(err))
return echConfig{}, nil
}
logger.Warn("could not JSON-decode ECH metadata; deleted its config folder",
zap.String("config_id", configID),
zap.Error(err))
return echConfig{}, nil
}
cfg.privKeyBin = privKeyBytes
@@ -701,7 +706,7 @@ nextName:
// HTTPS and SVCB RRs: RFC 9460 (https://www.rfc-editor.org/rfc/rfc9460)
Scheme: "https",
Name: relName,
TTL: 1 * time.Minute, // TODO: for testing only
TTL: 5 * time.Minute, // TODO: low hard-coded value only temporary; change to a higher value once more field-tested and key rotation is implemented
Priority: 2, // allows a manual override with priority 1
Target: ".",
Params: params,
+82 -20
View File
@@ -33,6 +33,7 @@ import (
"go.uber.org/zap/zapcore"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/internal"
"github.com/caddyserver/caddy/v2/modules/caddyevents"
)
@@ -55,8 +56,10 @@ type TLS struct {
//
// The "automate" certificate loader module can be used to
// specify a list of subjects that need certificates to be
// managed automatically. The first matching automation
// policy will be applied to manage the certificate(s).
// managed automatically, including subdomains that may
// already be covered by a managed wildcard certificate.
// The first matching automation policy will be used
// to manage automated certificate(s).
//
// All loaded certificates get pooled
// into the same cache and may be used to complete TLS
@@ -123,7 +126,7 @@ type TLS struct {
dns any // technically, it should be any/all of the libdns interfaces (RecordSetter, RecordAppender, etc.)
certificateLoaders []CertificateLoader
automateNames []string
automateNames map[string]struct{}
ctx caddy.Context
storageCleanTicker *time.Ticker
storageCleanStop chan struct{}
@@ -218,12 +221,13 @@ func (t *TLS) Provision(ctx caddy.Context) error {
// special case; these will be loaded in later using our automation facilities,
// which we want to avoid doing during provisioning
if automateNames, ok := modIface.(*AutomateLoader); ok && automateNames != nil {
repl := caddy.NewReplacer()
subjects := make([]string, len(*automateNames))
for i, sub := range *automateNames {
subjects[i] = repl.ReplaceAll(sub, "")
if t.automateNames == nil {
t.automateNames = make(map[string]struct{})
}
repl := caddy.NewReplacer()
for _, sub := range *automateNames {
t.automateNames[repl.ReplaceAll(sub, "")] = struct{}{}
}
t.automateNames = append(t.automateNames, subjects...)
} else {
return fmt.Errorf("loading certificates with 'automate' requires array of strings, got: %T", modIface)
}
@@ -283,7 +287,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
if err != nil {
return fmt.Errorf("provisioning default public automation policy: %v", err)
}
for _, n := range t.automateNames {
for n := range t.automateNames {
// if any names specified by the "automate" loader do not qualify for a public
// certificate, we should initialize a default internal automation policy
// (but we don't want to do this unnecessarily, since it may prompt for password!)
@@ -339,8 +343,14 @@ func (t *TLS) Provision(ctx caddy.Context) error {
// outer names should have certificates to reduce client brittleness
for _, outerName := range outerNames {
if outerName == "" {
continue
}
if !t.HasCertificateForSubject(outerName) {
t.automateNames = append(t.automateNames, outerNames...)
if t.automateNames == nil {
t.automateNames = make(map[string]struct{})
}
t.automateNames[outerName] = struct{}{}
}
}
}
@@ -449,7 +459,8 @@ func (t *TLS) Cleanup() error {
// app instance (which is being stopped) that are not managed or loaded by the
// new app instance (which just started), and remove them from the cache
var noLongerManaged []certmagic.SubjectIssuer
var reManage, noLongerLoaded []string
var noLongerLoaded []string
reManage := make(map[string]struct{})
for subj, currentIssuerKey := range t.managing {
// It's a bit nuanced: managed certs can sometimes be different enough that we have to
// swap them out for a different one, even if they are for the same subject/domain.
@@ -467,7 +478,7 @@ func (t *TLS) Cleanup() error {
// then, if the next app is managing a cert for this name, but with a different issuer, re-manage it
if ok && nextIssuerKey != currentIssuerKey {
reManage = append(reManage, subj)
reManage[subj] = struct{}{}
}
}
}
@@ -488,7 +499,7 @@ func (t *TLS) Cleanup() error {
if err := nextTLSApp.Manage(reManage); err != nil {
if c := t.logger.Check(zapcore.ErrorLevel, "re-managing unloaded certificates with new config"); c != nil {
c.Write(
zap.Strings("subjects", reManage),
zap.Strings("subjects", internal.MaxSizeSubjectsListForLog(reManage, 1000)),
zap.Error(err),
)
}
@@ -509,17 +520,31 @@ func (t *TLS) Cleanup() error {
return nil
}
// Manage immediately begins managing names according to the
// matching automation policy.
func (t *TLS) Manage(names []string) error {
// Manage immediately begins managing subjects according to the
// matching automation policy. The subjects are given in a map
// to prevent duplication and also because quick lookups are
// needed to assess wildcard coverage, if any, depending on
// certain config parameters (with lots of subjects, computing
// wildcard coverage over a slice can be highly inefficient).
func (t *TLS) Manage(subjects map[string]struct{}) error {
// for a large number of names, we can be more memory-efficient
// by making only one certmagic.Config for all the names that
// use that config, rather than calling ManageAsync once for
// every name; so first, bin names by AutomationPolicy
policyToNames := make(map[*AutomationPolicy][]string)
for _, name := range names {
ap := t.getAutomationPolicyForName(name)
policyToNames[ap] = append(policyToNames[ap], name)
for subj := range subjects {
ap := t.getAutomationPolicyForName(subj)
// by default, if a wildcard that covers the subj is also being
// managed, either by a previous call to Manage or by this one,
// prefer using that over individual certs for its subdomains;
// but users can disable this and force getting a certificate for
// subdomains by adding the name to the 'automate' cert loader
if t.managingWildcardFor(subj, subjects) {
if _, ok := t.automateNames[subj]; !ok {
continue
}
}
policyToNames[ap] = append(policyToNames[ap], subj)
}
// now that names are grouped by policy, we can simply make one
@@ -530,7 +555,7 @@ func (t *TLS) Manage(names []string) error {
if err != nil {
const maxNamesToDisplay = 100
if len(names) > maxNamesToDisplay {
names = append(names[:maxNamesToDisplay], fmt.Sprintf("(%d more...)", len(names)-maxNamesToDisplay))
names = append(names[:maxNamesToDisplay], fmt.Sprintf("(and %d more...)", len(names)-maxNamesToDisplay))
}
return fmt.Errorf("automate: manage %v: %v", names, err)
}
@@ -555,6 +580,43 @@ func (t *TLS) Manage(names []string) error {
return nil
}
// managingWildcardFor returns true if the app is managing a certificate that covers that
// subject name (including consideration of wildcards), either from its internal list of
// names that it IS managing certs for, or from the otherSubjsToManage which includes names
// that WILL be managed.
func (t *TLS) managingWildcardFor(subj string, otherSubjsToManage map[string]struct{}) bool {
// TODO: we could also consider manually-loaded certs using t.HasCertificateForSubject(),
// but that does not account for how manually-loaded certs may be restricted as to which
// hostnames or ClientHellos they can be used with by tags, etc; I don't *think* anyone
// necessarily wants this anyway, but I thought I'd note this here for now (if we did
// consider manually-loaded certs, we'd probably want to rename the method since it
// wouldn't be just about managed certs anymore)
// IP addresses must match exactly
if ip := net.ParseIP(subj); ip != nil {
_, managing := t.managing[subj]
return managing
}
// replace labels of the domain with wildcards until we get a match
labels := strings.Split(subj, ".")
for i := range labels {
if labels[i] == "*" {
continue
}
labels[i] = "*"
candidate := strings.Join(labels, ".")
if _, ok := t.managing[candidate]; ok {
return true
}
if _, ok := otherSubjsToManage[candidate]; ok {
return true
}
}
return false
}
// RegisterServerNames registers the provided DNS names with the TLS app.
// This is currently used to auto-publish Encrypted ClientHello (ECH)
// configurations, if enabled. Use of this function by apps using the TLS