mirror of
https://github.com/caddyserver/caddy.git
synced 2026-02-18 01:00:00 -05:00
Some checks failed
Tests / test (./cmd/caddy/caddy, ~1.26.0, ubuntu-latest, 0, 1.26, linux) (push) Failing after 15s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.26.0, 1.26, aix) (push) Failing after 14s
Cross-Build / build (~1.26.0, 1.26, darwin) (push) Failing after 14s
Cross-Build / build (~1.26.0, 1.26, dragonfly) (push) Failing after 15s
Cross-Build / build (~1.26.0, 1.26, freebsd) (push) Failing after 15s
Cross-Build / build (~1.26.0, 1.26, illumos) (push) Failing after 15s
Cross-Build / build (~1.26.0, 1.26, linux) (push) Failing after 14s
Cross-Build / build (~1.26.0, 1.26, netbsd) (push) Failing after 25s
Cross-Build / build (~1.26.0, 1.26, openbsd) (push) Failing after 20s
Cross-Build / build (~1.26.0, 1.26, solaris) (push) Failing after 15s
Cross-Build / build (~1.26.0, 1.26, windows) (push) Failing after 34s
Lint / lint (ubuntu-latest, linux) (push) Failing after 15s
Lint / govulncheck (push) Successful in 1m32s
Lint / dependency-review (push) Failing after 16s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 24s
Tests / test (./cmd/caddy/caddy, ~1.26.0, macos-14, 0, 1.26, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.26.0, windows-latest, True, 1.26, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
* caddyfile: Add renewal_window_ratio global option
Adds support for configuring the TLS certificate renewal window ratio
directly in the Caddyfile global options block. This allows users to
customize when certificates should be renewed without needing to use
JSON configuration.
Example usage:
{
renewal_window_ratio 0.1666
}
Fixes #7467
* caddyfile: Add renewal_window_ratio to tls directive and tests
Adds support for renewal_window_ratio in the tls directive (not just
global options) and adds caddyfile adapt tests for both the global
option and tls directive.
* fix: inherit global renewal_window_ratio in site policies
* fix: correct test expected output for policy consolidation
* fix: properly inherit global renewal_window_ratio without removing other code
647 lines
17 KiB
Go
647 lines
17 KiB
Go
// 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 httpcaddyfile
|
|
|
|
import (
|
|
"slices"
|
|
"strconv"
|
|
|
|
"github.com/caddyserver/certmagic"
|
|
"github.com/libdns/libdns"
|
|
"github.com/mholt/acmez/v3/acme"
|
|
|
|
"github.com/caddyserver/caddy/v2"
|
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
|
)
|
|
|
|
func init() {
|
|
RegisterGlobalOption("debug", parseOptTrue)
|
|
RegisterGlobalOption("http_port", parseOptHTTPPort)
|
|
RegisterGlobalOption("https_port", parseOptHTTPSPort)
|
|
RegisterGlobalOption("default_bind", parseOptDefaultBind)
|
|
RegisterGlobalOption("grace_period", parseOptDuration)
|
|
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
|
RegisterGlobalOption("default_sni", parseOptSingleString)
|
|
RegisterGlobalOption("fallback_sni", parseOptSingleString)
|
|
RegisterGlobalOption("order", parseOptOrder)
|
|
RegisterGlobalOption("storage", parseOptStorage)
|
|
RegisterGlobalOption("storage_check", parseStorageCheck)
|
|
RegisterGlobalOption("storage_clean_interval", parseStorageCleanInterval)
|
|
RegisterGlobalOption("renew_interval", parseOptDuration)
|
|
RegisterGlobalOption("ocsp_interval", parseOptDuration)
|
|
RegisterGlobalOption("acme_ca", parseOptSingleString)
|
|
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
|
|
RegisterGlobalOption("acme_dns", parseOptDNS)
|
|
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
|
|
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
|
|
RegisterGlobalOption("skip_install_trust", parseOptTrue)
|
|
RegisterGlobalOption("email", parseOptSingleString)
|
|
RegisterGlobalOption("admin", parseOptAdmin)
|
|
RegisterGlobalOption("on_demand_tls", parseOptOnDemand)
|
|
RegisterGlobalOption("local_certs", parseOptTrue)
|
|
RegisterGlobalOption("key_type", parseOptSingleString)
|
|
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
|
|
RegisterGlobalOption("metrics", parseMetricsOptions)
|
|
RegisterGlobalOption("servers", parseServerOptions)
|
|
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
|
|
RegisterGlobalOption("cert_lifetime", parseOptDuration)
|
|
RegisterGlobalOption("log", parseLogOptions)
|
|
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
|
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
|
RegisterGlobalOption("dns", parseOptDNS)
|
|
RegisterGlobalOption("ech", parseOptECH)
|
|
RegisterGlobalOption("renewal_window_ratio", parseOptRenewalWindowRatio)
|
|
}
|
|
|
|
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
|
|
|
func parseOptHTTPPort(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
var httpPort int
|
|
var httpPortStr string
|
|
if !d.AllArgs(&httpPortStr) {
|
|
return 0, d.ArgErr()
|
|
}
|
|
var err error
|
|
httpPort, err = strconv.Atoi(httpPortStr)
|
|
if err != nil {
|
|
return 0, d.Errf("converting port '%s' to integer value: %v", httpPortStr, err)
|
|
}
|
|
return httpPort, nil
|
|
}
|
|
|
|
func parseOptHTTPSPort(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
var httpsPort int
|
|
var httpsPortStr string
|
|
if !d.AllArgs(&httpsPortStr) {
|
|
return 0, d.ArgErr()
|
|
}
|
|
var err error
|
|
httpsPort, err = strconv.Atoi(httpsPortStr)
|
|
if err != nil {
|
|
return 0, d.Errf("converting port '%s' to integer value: %v", httpsPortStr, err)
|
|
}
|
|
return httpsPort, nil
|
|
}
|
|
|
|
func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
|
|
// get directive name
|
|
if !d.Next() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
dirName := d.Val()
|
|
if _, ok := registeredDirectives[dirName]; !ok {
|
|
return nil, d.Errf("%s is not a registered directive", dirName)
|
|
}
|
|
|
|
// get positional token
|
|
if !d.Next() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
pos := Positional(d.Val())
|
|
|
|
// if directive already had an order, drop it
|
|
newOrder := slices.DeleteFunc(directiveOrder, func(d string) bool {
|
|
return d == dirName
|
|
})
|
|
|
|
// act on the positional; if it's First or Last, we're done right away
|
|
switch pos {
|
|
case First:
|
|
newOrder = append([]string{dirName}, newOrder...)
|
|
if d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
directiveOrder = newOrder
|
|
return newOrder, nil
|
|
|
|
case Last:
|
|
newOrder = append(newOrder, dirName)
|
|
if d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
directiveOrder = newOrder
|
|
return newOrder, nil
|
|
|
|
// if it's Before or After, continue
|
|
case Before:
|
|
case After:
|
|
|
|
default:
|
|
return nil, d.Errf("unknown positional '%s'", pos)
|
|
}
|
|
|
|
// get name of other directive
|
|
if !d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
otherDir := d.Val()
|
|
if d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
|
|
// get the position of the target directive
|
|
targetIndex := slices.Index(newOrder, otherDir)
|
|
if targetIndex == -1 {
|
|
return nil, d.Errf("directive '%s' not found", otherDir)
|
|
}
|
|
// if we're inserting after, we need to increment the index to go after
|
|
if pos == After {
|
|
targetIndex++
|
|
}
|
|
// insert the directive into the new order
|
|
newOrder = slices.Insert(newOrder, targetIndex, dirName)
|
|
|
|
directiveOrder = newOrder
|
|
|
|
return newOrder, nil
|
|
}
|
|
|
|
func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
if !d.Next() { // consume option name
|
|
return nil, d.ArgErr()
|
|
}
|
|
if !d.Next() { // get storage module name
|
|
return nil, d.ArgErr()
|
|
}
|
|
modID := "caddy.storage." + d.Val()
|
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
storage, ok := unm.(caddy.StorageConverter)
|
|
if !ok {
|
|
return nil, d.Errf("module %s is not a caddy.StorageConverter", modID)
|
|
}
|
|
return storage, nil
|
|
}
|
|
|
|
func parseStorageCheck(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
if !d.Next() {
|
|
return "", d.ArgErr()
|
|
}
|
|
val := d.Val()
|
|
if d.Next() {
|
|
return "", d.ArgErr()
|
|
}
|
|
if val != "off" {
|
|
return "", d.Errf("storage_check must be 'off'")
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
func parseStorageCleanInterval(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
if !d.Next() {
|
|
return "", d.ArgErr()
|
|
}
|
|
val := d.Val()
|
|
if d.Next() {
|
|
return "", d.ArgErr()
|
|
}
|
|
if val == "off" {
|
|
return false, nil
|
|
}
|
|
dur, err := caddy.ParseDuration(d.Val())
|
|
if err != nil {
|
|
return nil, d.Errf("failed to parse storage_clean_interval, must be a duration or 'off' %w", err)
|
|
}
|
|
return caddy.Duration(dur), nil
|
|
}
|
|
|
|
func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
if !d.Next() { // consume option name
|
|
return nil, d.ArgErr()
|
|
}
|
|
if !d.Next() { // get duration value
|
|
return nil, d.ArgErr()
|
|
}
|
|
dur, err := caddy.ParseDuration(d.Val())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return caddy.Duration(dur), nil
|
|
}
|
|
|
|
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
eab := new(acme.EAB)
|
|
d.Next() // consume option name
|
|
if d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
for d.NextBlock(0) {
|
|
switch d.Val() {
|
|
case "key_id":
|
|
if !d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
eab.KeyID = d.Val()
|
|
|
|
case "mac_key":
|
|
if !d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
eab.MACKey = d.Val()
|
|
|
|
default:
|
|
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
|
}
|
|
}
|
|
return eab, nil
|
|
}
|
|
|
|
func parseOptCertIssuer(d *caddyfile.Dispenser, existing any) (any, error) {
|
|
d.Next() // consume option name
|
|
|
|
var issuers []certmagic.Issuer
|
|
if existing != nil {
|
|
issuers = existing.([]certmagic.Issuer)
|
|
}
|
|
|
|
// get issuer module name
|
|
if !d.Next() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
modID := "tls.issuance." + d.Val()
|
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
iss, ok := unm.(certmagic.Issuer)
|
|
if !ok {
|
|
return nil, d.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm)
|
|
}
|
|
issuers = append(issuers, iss)
|
|
return issuers, nil
|
|
}
|
|
|
|
func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
if !d.Next() {
|
|
return "", d.ArgErr()
|
|
}
|
|
val := d.Val()
|
|
if d.Next() {
|
|
return "", d.ArgErr()
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
|
|
var addresses, protocols []string
|
|
addresses = d.RemainingArgs()
|
|
|
|
if len(addresses) == 0 {
|
|
addresses = append(addresses, "")
|
|
}
|
|
|
|
for d.NextBlock(0) {
|
|
switch d.Val() {
|
|
case "protocols":
|
|
protocols = d.RemainingArgs()
|
|
if len(protocols) == 0 {
|
|
return nil, d.Errf("protocols requires one or more arguments")
|
|
}
|
|
default:
|
|
return nil, d.Errf("unknown subdirective: %s", d.Val())
|
|
}
|
|
}
|
|
|
|
return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{
|
|
addresses: addresses,
|
|
protocols: protocols,
|
|
}}}, nil
|
|
}
|
|
|
|
func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
|
|
adminCfg := new(caddy.AdminConfig)
|
|
if d.NextArg() {
|
|
listenAddress := d.Val()
|
|
if listenAddress == "off" {
|
|
adminCfg.Disabled = true
|
|
if d.Next() { // Do not accept any remaining options including block
|
|
return nil, d.Err("No more option is allowed after turning off admin config")
|
|
}
|
|
} else {
|
|
adminCfg.Listen = listenAddress
|
|
if d.NextArg() { // At most 1 arg is allowed
|
|
return nil, d.ArgErr()
|
|
}
|
|
}
|
|
}
|
|
for d.NextBlock(0) {
|
|
switch d.Val() {
|
|
case "enforce_origin":
|
|
adminCfg.EnforceOrigin = true
|
|
|
|
case "origins":
|
|
adminCfg.Origins = d.RemainingArgs()
|
|
|
|
default:
|
|
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
|
}
|
|
}
|
|
if adminCfg.Listen == "" && !adminCfg.Disabled {
|
|
adminCfg.Listen = caddy.DefaultAdminListen
|
|
}
|
|
return adminCfg, nil
|
|
}
|
|
|
|
func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
if d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
|
|
var ond *caddytls.OnDemandConfig
|
|
|
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
|
switch d.Val() {
|
|
case "ask":
|
|
if !d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
if ond == nil {
|
|
ond = new(caddytls.OnDemandConfig)
|
|
}
|
|
if ond.PermissionRaw != nil {
|
|
return nil, d.Err("on-demand TLS permission module (or 'ask') already specified")
|
|
}
|
|
perm := caddytls.PermissionByHTTP{Endpoint: d.Val()}
|
|
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", "http", nil)
|
|
|
|
case "permission":
|
|
if !d.NextArg() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
if ond == nil {
|
|
ond = new(caddytls.OnDemandConfig)
|
|
}
|
|
if ond.PermissionRaw != nil {
|
|
return nil, d.Err("on-demand TLS permission module (or 'ask') already specified")
|
|
}
|
|
modName := d.Val()
|
|
modID := "tls.permission." + modName
|
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
perm, ok := unm.(caddytls.OnDemandPermission)
|
|
if !ok {
|
|
return nil, d.Errf("module %s (%T) is not an on-demand TLS permission module", modID, unm)
|
|
}
|
|
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", modName, nil)
|
|
|
|
case "interval":
|
|
return nil, d.Errf("the on_demand_tls 'interval' option is no longer supported, remove it from your config")
|
|
|
|
case "burst":
|
|
return nil, d.Errf("the on_demand_tls 'burst' option is no longer supported, remove it from your config")
|
|
|
|
default:
|
|
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
|
}
|
|
}
|
|
if ond == nil {
|
|
return nil, d.Err("expected at least one config parameter for on_demand_tls")
|
|
}
|
|
return ond, nil
|
|
}
|
|
|
|
func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
if !d.Next() {
|
|
return "", d.ArgErr()
|
|
}
|
|
val := d.Val()
|
|
if d.Next() {
|
|
return "", d.ArgErr()
|
|
}
|
|
if val != "off" {
|
|
return "", d.Errf("persist_config must be 'off'")
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
val := d.RemainingArgs()
|
|
if len(val) == 0 {
|
|
return "", d.ArgErr()
|
|
}
|
|
for _, v := range val {
|
|
switch v {
|
|
case "off":
|
|
case "disable_redirects":
|
|
case "disable_certs":
|
|
case "ignore_loaded_certs":
|
|
default:
|
|
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'")
|
|
}
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) {
|
|
d.Next() // consume option name
|
|
metrics := new(caddyhttp.Metrics)
|
|
for d.NextBlock(0) {
|
|
switch d.Val() {
|
|
case "per_host":
|
|
metrics.PerHost = true
|
|
case "observe_catchall_hosts":
|
|
metrics.ObserveCatchallHosts = true
|
|
default:
|
|
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
|
|
}
|
|
}
|
|
return metrics, nil
|
|
}
|
|
|
|
func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
return unmarshalCaddyfileMetricsOptions(d)
|
|
}
|
|
|
|
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
return unmarshalCaddyfileServerOptions(d)
|
|
}
|
|
|
|
func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
var val string
|
|
if !d.AllArgs(&val) {
|
|
return nil, d.ArgErr()
|
|
}
|
|
if val != "off" {
|
|
return nil, d.Errf("invalid argument '%s'", val)
|
|
}
|
|
return certmagic.OCSPConfig{
|
|
DisableStapling: val == "off",
|
|
}, nil
|
|
}
|
|
|
|
// parseLogOptions parses the global log option. Syntax:
|
|
//
|
|
// log [name] {
|
|
// output <writer_module> ...
|
|
// format <encoder_module> ...
|
|
// level <level>
|
|
// include <namespaces...>
|
|
// exclude <namespaces...>
|
|
// }
|
|
//
|
|
// When the name argument is unspecified, this directive modifies the default
|
|
// logger.
|
|
func parseLogOptions(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
|
currentNames := make(map[string]struct{})
|
|
if existingVal != nil {
|
|
innerVals, ok := existingVal.([]ConfigValue)
|
|
if !ok {
|
|
return nil, d.Errf("existing log values of unexpected type: %T", existingVal)
|
|
}
|
|
for _, rawVal := range innerVals {
|
|
val, ok := rawVal.Value.(namedCustomLog)
|
|
if !ok {
|
|
return nil, d.Errf("existing log value of unexpected type: %T", existingVal)
|
|
}
|
|
currentNames[val.name] = struct{}{}
|
|
}
|
|
}
|
|
|
|
var warnings []caddyconfig.Warning
|
|
// Call out the same parser that handles server-specific log configuration.
|
|
configValues, err := parseLogHelper(
|
|
Helper{
|
|
Dispenser: d,
|
|
warnings: &warnings,
|
|
},
|
|
currentNames,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(warnings) > 0 {
|
|
return nil, d.Errf("warnings found in parsing global log options: %+v", warnings)
|
|
}
|
|
|
|
return configValues, nil
|
|
}
|
|
|
|
func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next()
|
|
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
|
|
}
|
|
|
|
func parseOptDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
optName := d.Val()
|
|
|
|
// get DNS module name
|
|
if !d.Next() {
|
|
// this is allowed if this is the "acme_dns" option since it may refer to the globally-configured "dns" option's value
|
|
if optName == "acme_dns" {
|
|
return nil, nil
|
|
}
|
|
return nil, d.ArgErr()
|
|
}
|
|
modID := "dns.providers." + d.Val()
|
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch unm.(type) {
|
|
case libdns.RecordGetter,
|
|
libdns.RecordSetter,
|
|
libdns.RecordAppender,
|
|
libdns.RecordDeleter:
|
|
default:
|
|
return nil, d.Errf("module %s (%T) is not a libdns provider", modID, unm)
|
|
}
|
|
return unm, nil
|
|
}
|
|
|
|
func parseOptECH(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
|
|
ech := new(caddytls.ECH)
|
|
|
|
publicNames := d.RemainingArgs()
|
|
for _, publicName := range publicNames {
|
|
ech.Configs = append(ech.Configs, caddytls.ECHConfiguration{
|
|
PublicName: publicName,
|
|
})
|
|
}
|
|
if len(ech.Configs) == 0 {
|
|
return nil, d.ArgErr()
|
|
}
|
|
|
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
|
switch d.Val() {
|
|
case "dns":
|
|
if !d.Next() {
|
|
return nil, d.ArgErr()
|
|
}
|
|
providerName := d.Val()
|
|
modID := "dns.providers." + providerName
|
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ech.Publication = append(ech.Publication, &caddytls.ECHPublication{
|
|
Configs: publicNames,
|
|
PublishersRaw: caddy.ModuleMap{
|
|
"dns": caddyconfig.JSON(caddytls.ECHDNSPublisher{
|
|
ProviderRaw: caddyconfig.JSONModuleObject(unm, "name", providerName, nil),
|
|
}, nil),
|
|
},
|
|
})
|
|
default:
|
|
return nil, d.Errf("ech: unrecognized subdirective '%s'", d.Val())
|
|
}
|
|
}
|
|
|
|
return ech, nil
|
|
}
|
|
|
|
func parseOptRenewalWindowRatio(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
d.Next() // consume option name
|
|
if !d.Next() {
|
|
return 0, d.ArgErr()
|
|
}
|
|
val := d.Val()
|
|
ratio, err := strconv.ParseFloat(val, 64)
|
|
if err != nil {
|
|
return 0, d.Errf("parsing renewal_window_ratio: %v", err)
|
|
}
|
|
if ratio <= 0 || ratio >= 1 {
|
|
return 0, d.Errf("renewal_window_ratio must be between 0 and 1 (exclusive)")
|
|
}
|
|
if d.Next() {
|
|
return 0, d.ArgErr()
|
|
}
|
|
return ratio, nil
|
|
}
|