2019-08-22 14:38:37 -05:00
|
|
|
// 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 (
|
2024-09-25 15:30:56 -05:00
|
|
|
"slices"
|
2019-08-22 14:38:37 -05:00
|
|
|
"strconv"
|
|
|
|
|
2023-08-14 10:41:15 -05:00
|
|
|
"github.com/caddyserver/certmagic"
|
2024-04-13 20:31:43 -05:00
|
|
|
"github.com/mholt/acmez/v2/acme"
|
2023-08-14 10:41:15 -05:00
|
|
|
|
2019-09-19 13:42:36 -05:00
|
|
|
"github.com/caddyserver/caddy/v2"
|
2021-03-12 15:00:02 -05:00
|
|
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
2019-08-22 14:38:37 -05:00
|
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
2024-10-18 10:54:21 -05:00
|
|
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
2020-03-17 22:00:45 -05:00
|
|
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
2019-08-22 14:38:37 -05:00
|
|
|
)
|
|
|
|
|
2020-05-11 16:00:35 -05:00
|
|
|
func init() {
|
|
|
|
RegisterGlobalOption("debug", parseOptTrue)
|
|
|
|
RegisterGlobalOption("http_port", parseOptHTTPPort)
|
|
|
|
RegisterGlobalOption("https_port", parseOptHTTPSPort)
|
2024-09-30 11:55:03 -05:00
|
|
|
RegisterGlobalOption("default_bind", parseOptDefaultBind)
|
2021-05-07 17:18:17 -05:00
|
|
|
RegisterGlobalOption("grace_period", parseOptDuration)
|
2022-08-03 12:04:51 -05:00
|
|
|
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
2020-05-11 16:00:35 -05:00
|
|
|
RegisterGlobalOption("default_sni", parseOptSingleString)
|
2023-05-10 15:29:29 -05:00
|
|
|
RegisterGlobalOption("fallback_sni", parseOptSingleString)
|
2020-05-11 16:00:35 -05:00
|
|
|
RegisterGlobalOption("order", parseOptOrder)
|
|
|
|
RegisterGlobalOption("storage", parseOptStorage)
|
2024-11-05 12:47:41 -05:00
|
|
|
RegisterGlobalOption("storage_check", parseStorageCheck)
|
|
|
|
RegisterGlobalOption("storage_clean_interval", parseStorageCleanInterval)
|
2021-11-28 17:22:26 -05:00
|
|
|
RegisterGlobalOption("renew_interval", parseOptDuration)
|
2022-08-24 12:22:56 -05:00
|
|
|
RegisterGlobalOption("ocsp_interval", parseOptDuration)
|
2020-05-11 16:00:35 -05:00
|
|
|
RegisterGlobalOption("acme_ca", parseOptSingleString)
|
|
|
|
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
|
2021-01-05 16:39:30 -05:00
|
|
|
RegisterGlobalOption("acme_dns", parseOptACMEDNS)
|
2020-06-12 14:37:56 -05:00
|
|
|
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
|
caddytls: Add support for ZeroSSL; add Caddyfile support for issuers (#3633)
* caddytls: Add support for ZeroSSL; add Caddyfile support for issuers
Configuring issuers explicitly in a Caddyfile is not easily compatible
with existing ACME-specific parameters such as email or acme_ca which
infer the kind of issuer it creates (this is complicated now because
the ZeroSSL issuer wraps the ACME issuer)... oh well, we can revisit
that later if we need to.
New Caddyfile global option:
{
cert_issuer <name> ...
}
Or, alternatively, as a tls subdirective:
tls {
issuer <name> ...
}
For example, to use ZeroSSL with an API key:
{
cert_issuser zerossl API_KEY
}
For now, that still uses ZeroSSL's ACME endpoint; it fetches EAB
credentials for you. You can also provide the EAB credentials directly
just like any other ACME endpoint:
{
cert_issuer acme {
eab KEY_ID MAC_KEY
}
}
All these examples use the new global option (or tls subdirective). You
can still use traditional/existing options with ZeroSSL, since it's
just another ACME endpoint:
{
acme_ca https://acme.zerossl.com/v2/DV90
acme_eab KEY_ID MAC_KEY
}
That's all there is to it. You just can't mix-and-match acme_* options
with cert_issuer, because it becomes confusing/ambiguous/complicated to
merge the settings.
* Fix broken test
This test was asserting buggy behavior, oops - glad this branch both
discovers and fixes the bug at the same time!
* Fix broken test (post-merge)
* Update modules/caddytls/acmeissuer.go
Fix godoc comment
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
* Add support for ZeroSSL's EAB-by-email endpoint
Also transform the ACMEIssuer into ZeroSSLIssuer implicitly if set to
the ZeroSSL endpoint without EAB (the ZeroSSLIssuer is needed to
generate EAB if not already provided); this is now possible with either
an API key or an email address.
* go.mod: Use latest certmagic, acmez, and x/net
* Wrap underlying logic rather than repeating it
Oops, duh
* Form-encode email info into request body for EAB endpoint
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2020-08-11 09:58:06 -05:00
|
|
|
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
|
2021-06-07 13:18:49 -05:00
|
|
|
RegisterGlobalOption("skip_install_trust", parseOptTrue)
|
2020-05-11 16:00:35 -05:00
|
|
|
RegisterGlobalOption("email", parseOptSingleString)
|
|
|
|
RegisterGlobalOption("admin", parseOptAdmin)
|
|
|
|
RegisterGlobalOption("on_demand_tls", parseOptOnDemand)
|
|
|
|
RegisterGlobalOption("local_certs", parseOptTrue)
|
|
|
|
RegisterGlobalOption("key_type", parseOptSingleString)
|
2020-05-19 17:59:51 -05:00
|
|
|
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
|
2024-10-18 10:54:21 -05:00
|
|
|
RegisterGlobalOption("metrics", parseMetricsOptions)
|
2020-11-23 14:46:50 -05:00
|
|
|
RegisterGlobalOption("servers", parseServerOptions)
|
2021-01-07 17:52:58 -05:00
|
|
|
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
|
2024-04-24 15:35:14 -05:00
|
|
|
RegisterGlobalOption("cert_lifetime", parseOptDuration)
|
2021-03-12 15:00:02 -05:00
|
|
|
RegisterGlobalOption("log", parseLogOptions)
|
2021-06-08 15:10:37 -05:00
|
|
|
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
2023-01-27 23:31:37 -05:00
|
|
|
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
2020-05-11 16:00:35 -05:00
|
|
|
}
|
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
2020-05-11 16:00:35 -05:00
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptHTTPPort(d *caddyfile.Dispenser, _ any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
2019-08-22 14:38:37 -05:00
|
|
|
var httpPort int
|
2024-01-23 19:36:59 -05:00
|
|
|
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)
|
2019-08-22 14:38:37 -05:00
|
|
|
}
|
|
|
|
return httpPort, nil
|
|
|
|
}
|
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptHTTPSPort(d *caddyfile.Dispenser, _ any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
2019-08-22 14:38:37 -05:00
|
|
|
var httpsPort int
|
2024-01-23 19:36:59 -05:00
|
|
|
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)
|
2019-08-22 14:38:37 -05:00
|
|
|
}
|
|
|
|
return httpsPort, nil
|
|
|
|
}
|
2019-08-22 15:26:33 -05:00
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
2020-01-16 14:09:54 -05:00
|
|
|
|
2024-01-23 19:36:59 -05:00
|
|
|
// 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)
|
|
|
|
}
|
2020-01-16 14:09:54 -05:00
|
|
|
|
2024-01-23 19:36:59 -05:00
|
|
|
// get positional token
|
|
|
|
if !d.Next() {
|
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
2024-03-06 14:41:45 -05:00
|
|
|
pos := Positional(d.Val())
|
2020-01-16 14:09:54 -05:00
|
|
|
|
2024-09-25 15:30:56 -05:00
|
|
|
// if directive already had an order, drop it
|
|
|
|
newOrder := slices.DeleteFunc(directiveOrder, func(d string) bool {
|
|
|
|
return d == dirName
|
|
|
|
})
|
2020-01-16 14:09:54 -05:00
|
|
|
|
2024-09-25 15:30:56 -05:00
|
|
|
// act on the positional; if it's First or Last, we're done right away
|
2024-01-23 19:36:59 -05:00
|
|
|
switch pos {
|
2024-03-06 14:41:45 -05:00
|
|
|
case First:
|
2024-01-23 19:36:59 -05:00
|
|
|
newOrder = append([]string{dirName}, newOrder...)
|
|
|
|
if d.NextArg() {
|
2020-01-16 14:09:54 -05:00
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
2024-01-23 19:36:59 -05:00
|
|
|
directiveOrder = newOrder
|
|
|
|
return newOrder, nil
|
2024-09-25 15:30:56 -05:00
|
|
|
|
2024-03-06 14:41:45 -05:00
|
|
|
case Last:
|
2024-01-23 19:36:59 -05:00
|
|
|
newOrder = append(newOrder, dirName)
|
2019-08-22 15:26:33 -05:00
|
|
|
if d.NextArg() {
|
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
2024-01-23 19:36:59 -05:00
|
|
|
directiveOrder = newOrder
|
|
|
|
return newOrder, nil
|
2024-09-25 15:30:56 -05:00
|
|
|
|
|
|
|
// if it's Before or After, continue
|
2024-03-06 14:41:45 -05:00
|
|
|
case Before:
|
|
|
|
case After:
|
2024-09-25 15:30:56 -05:00
|
|
|
|
2024-01-23 19:36:59 -05:00
|
|
|
default:
|
|
|
|
return nil, d.Errf("unknown positional '%s'", pos)
|
|
|
|
}
|
2020-01-16 14:09:54 -05:00
|
|
|
|
2024-01-23 19:36:59 -05:00
|
|
|
// get name of other directive
|
|
|
|
if !d.NextArg() {
|
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
|
|
|
otherDir := d.Val()
|
|
|
|
if d.NextArg() {
|
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
|
|
|
|
2024-09-25 15:30:56 -05:00
|
|
|
// 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++
|
2019-08-22 15:26:33 -05:00
|
|
|
}
|
2024-09-25 15:30:56 -05:00
|
|
|
// insert the directive into the new order
|
|
|
|
newOrder = slices.Insert(newOrder, targetIndex, dirName)
|
2020-01-16 14:09:54 -05:00
|
|
|
|
|
|
|
directiveOrder = newOrder
|
|
|
|
|
|
|
|
return newOrder, nil
|
2019-08-22 15:26:33 -05:00
|
|
|
}
|
2019-09-19 13:42:36 -05:00
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
|
2020-05-01 10:34:32 -05:00
|
|
|
if !d.Next() { // consume option name
|
2019-09-19 13:42:36 -05:00
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
2020-05-01 10:34:32 -05:00
|
|
|
if !d.Next() { // get storage module name
|
2019-09-19 13:42:36 -05:00
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
2021-01-05 16:39:30 -05:00
|
|
|
modID := "caddy.storage." + d.Val()
|
|
|
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
2019-09-19 13:42:36 -05:00
|
|
|
if err != nil {
|
2021-01-05 16:39:30 -05:00
|
|
|
return nil, err
|
2019-09-19 13:42:36 -05:00
|
|
|
}
|
2021-01-05 16:39:30 -05:00
|
|
|
storage, ok := unm.(caddy.StorageConverter)
|
2019-09-19 13:42:36 -05:00
|
|
|
if !ok {
|
2021-01-05 16:39:30 -05:00
|
|
|
return nil, d.Errf("module %s is not a caddy.StorageConverter", modID)
|
|
|
|
}
|
|
|
|
return storage, nil
|
|
|
|
}
|
|
|
|
|
2024-11-05 12:47:41 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
|
2021-05-02 12:57:28 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptACMEDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
2021-01-05 16:39:30 -05:00
|
|
|
if !d.Next() { // consume option name
|
|
|
|
return nil, d.ArgErr()
|
2019-09-19 13:42:36 -05:00
|
|
|
}
|
2021-01-05 16:39:30 -05:00
|
|
|
if !d.Next() { // get DNS module name
|
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
|
|
|
modID := "dns.providers." + d.Val()
|
|
|
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
2019-09-19 13:42:36 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-04-13 20:31:43 -05:00
|
|
|
prov, ok := unm.(certmagic.DNSProvider)
|
2019-09-19 13:42:36 -05:00
|
|
|
if !ok {
|
2024-04-13 20:31:43 -05:00
|
|
|
return nil, d.Errf("module %s (%T) is not a certmagic.DNSProvider", modID, unm)
|
2019-09-19 13:42:36 -05:00
|
|
|
}
|
2021-01-05 16:39:30 -05:00
|
|
|
return prov, nil
|
2019-09-19 13:42:36 -05:00
|
|
|
}
|
2019-09-30 10:11:30 -05:00
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
|
2020-07-30 16:18:14 -05:00
|
|
|
eab := new(acme.EAB)
|
2024-01-23 19:36:59 -05:00
|
|
|
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()
|
2020-06-12 14:37:56 -05:00
|
|
|
}
|
2024-01-23 19:36:59 -05:00
|
|
|
eab.MACKey = d.Val()
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
2020-06-12 14:37:56 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return eab, nil
|
|
|
|
}
|
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptCertIssuer(d *caddyfile.Dispenser, existing any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
|
|
|
|
2021-01-07 13:01:58 -05:00
|
|
|
var issuers []certmagic.Issuer
|
|
|
|
if existing != nil {
|
|
|
|
issuers = existing.([]certmagic.Issuer)
|
caddytls: Add support for ZeroSSL; add Caddyfile support for issuers (#3633)
* caddytls: Add support for ZeroSSL; add Caddyfile support for issuers
Configuring issuers explicitly in a Caddyfile is not easily compatible
with existing ACME-specific parameters such as email or acme_ca which
infer the kind of issuer it creates (this is complicated now because
the ZeroSSL issuer wraps the ACME issuer)... oh well, we can revisit
that later if we need to.
New Caddyfile global option:
{
cert_issuer <name> ...
}
Or, alternatively, as a tls subdirective:
tls {
issuer <name> ...
}
For example, to use ZeroSSL with an API key:
{
cert_issuser zerossl API_KEY
}
For now, that still uses ZeroSSL's ACME endpoint; it fetches EAB
credentials for you. You can also provide the EAB credentials directly
just like any other ACME endpoint:
{
cert_issuer acme {
eab KEY_ID MAC_KEY
}
}
All these examples use the new global option (or tls subdirective). You
can still use traditional/existing options with ZeroSSL, since it's
just another ACME endpoint:
{
acme_ca https://acme.zerossl.com/v2/DV90
acme_eab KEY_ID MAC_KEY
}
That's all there is to it. You just can't mix-and-match acme_* options
with cert_issuer, because it becomes confusing/ambiguous/complicated to
merge the settings.
* Fix broken test
This test was asserting buggy behavior, oops - glad this branch both
discovers and fixes the bug at the same time!
* Fix broken test (post-merge)
* Update modules/caddytls/acmeissuer.go
Fix godoc comment
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
* Add support for ZeroSSL's EAB-by-email endpoint
Also transform the ACMEIssuer into ZeroSSLIssuer implicitly if set to
the ZeroSSL endpoint without EAB (the ZeroSSLIssuer is needed to
generate EAB if not already provided); this is now possible with either
an API key or an email address.
* go.mod: Use latest certmagic, acmez, and x/net
* Wrap underlying logic rather than repeating it
Oops, duh
* Form-encode email info into request body for EAB endpoint
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2020-08-11 09:58:06 -05:00
|
|
|
}
|
2024-01-23 19:36:59 -05:00
|
|
|
|
|
|
|
// get issuer module name
|
|
|
|
if !d.Next() {
|
|
|
|
return nil, d.ArgErr()
|
caddytls: Add support for ZeroSSL; add Caddyfile support for issuers (#3633)
* caddytls: Add support for ZeroSSL; add Caddyfile support for issuers
Configuring issuers explicitly in a Caddyfile is not easily compatible
with existing ACME-specific parameters such as email or acme_ca which
infer the kind of issuer it creates (this is complicated now because
the ZeroSSL issuer wraps the ACME issuer)... oh well, we can revisit
that later if we need to.
New Caddyfile global option:
{
cert_issuer <name> ...
}
Or, alternatively, as a tls subdirective:
tls {
issuer <name> ...
}
For example, to use ZeroSSL with an API key:
{
cert_issuser zerossl API_KEY
}
For now, that still uses ZeroSSL's ACME endpoint; it fetches EAB
credentials for you. You can also provide the EAB credentials directly
just like any other ACME endpoint:
{
cert_issuer acme {
eab KEY_ID MAC_KEY
}
}
All these examples use the new global option (or tls subdirective). You
can still use traditional/existing options with ZeroSSL, since it's
just another ACME endpoint:
{
acme_ca https://acme.zerossl.com/v2/DV90
acme_eab KEY_ID MAC_KEY
}
That's all there is to it. You just can't mix-and-match acme_* options
with cert_issuer, because it becomes confusing/ambiguous/complicated to
merge the settings.
* Fix broken test
This test was asserting buggy behavior, oops - glad this branch both
discovers and fixes the bug at the same time!
* Fix broken test (post-merge)
* Update modules/caddytls/acmeissuer.go
Fix godoc comment
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
* Add support for ZeroSSL's EAB-by-email endpoint
Also transform the ACMEIssuer into ZeroSSLIssuer implicitly if set to
the ZeroSSL endpoint without EAB (the ZeroSSLIssuer is needed to
generate EAB if not already provided); this is now possible with either
an API key or an email address.
* go.mod: Use latest certmagic, acmez, and x/net
* Wrap underlying logic rather than repeating it
Oops, duh
* Form-encode email info into request body for EAB endpoint
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2020-08-11 09:58:06 -05:00
|
|
|
}
|
2024-01-23 19:36:59 -05:00
|
|
|
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)
|
2021-01-07 13:01:58 -05:00
|
|
|
return issuers, nil
|
caddytls: Add support for ZeroSSL; add Caddyfile support for issuers (#3633)
* caddytls: Add support for ZeroSSL; add Caddyfile support for issuers
Configuring issuers explicitly in a Caddyfile is not easily compatible
with existing ACME-specific parameters such as email or acme_ca which
infer the kind of issuer it creates (this is complicated now because
the ZeroSSL issuer wraps the ACME issuer)... oh well, we can revisit
that later if we need to.
New Caddyfile global option:
{
cert_issuer <name> ...
}
Or, alternatively, as a tls subdirective:
tls {
issuer <name> ...
}
For example, to use ZeroSSL with an API key:
{
cert_issuser zerossl API_KEY
}
For now, that still uses ZeroSSL's ACME endpoint; it fetches EAB
credentials for you. You can also provide the EAB credentials directly
just like any other ACME endpoint:
{
cert_issuer acme {
eab KEY_ID MAC_KEY
}
}
All these examples use the new global option (or tls subdirective). You
can still use traditional/existing options with ZeroSSL, since it's
just another ACME endpoint:
{
acme_ca https://acme.zerossl.com/v2/DV90
acme_eab KEY_ID MAC_KEY
}
That's all there is to it. You just can't mix-and-match acme_* options
with cert_issuer, because it becomes confusing/ambiguous/complicated to
merge the settings.
* Fix broken test
This test was asserting buggy behavior, oops - glad this branch both
discovers and fixes the bug at the same time!
* Fix broken test (post-merge)
* Update modules/caddytls/acmeissuer.go
Fix godoc comment
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
* Add support for ZeroSSL's EAB-by-email endpoint
Also transform the ACMEIssuer into ZeroSSLIssuer implicitly if set to
the ZeroSSL endpoint without EAB (the ZeroSSLIssuer is needed to
generate EAB if not already provided); this is now possible with either
an API key or an email address.
* go.mod: Use latest certmagic, acmez, and x/net
* Wrap underlying logic rather than repeating it
Oops, duh
* Form-encode email info into request body for EAB endpoint
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2020-08-11 09:58:06 -05:00
|
|
|
}
|
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
2019-09-30 10:11:30 -05:00
|
|
|
if !d.Next() {
|
|
|
|
return "", d.ArgErr()
|
|
|
|
}
|
|
|
|
val := d.Val()
|
|
|
|
if d.Next() {
|
|
|
|
return "", d.ArgErr()
|
|
|
|
}
|
|
|
|
return val, nil
|
|
|
|
}
|
2019-10-30 16:12:42 -05:00
|
|
|
|
2024-09-30 11:55:03 -05:00
|
|
|
func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
2024-09-30 11:55:03 -05:00
|
|
|
|
|
|
|
var addresses, protocols []string
|
|
|
|
addresses = d.RemainingArgs()
|
|
|
|
|
|
|
|
if len(addresses) == 0 {
|
|
|
|
addresses = append(addresses, "")
|
2022-05-08 20:32:10 -05:00
|
|
|
}
|
2024-09-30 11:55:03 -05:00
|
|
|
|
|
|
|
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
|
2022-05-08 20:32:10 -05:00
|
|
|
}
|
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
|
|
|
|
2020-08-03 14:44:38 -05:00
|
|
|
adminCfg := new(caddy.AdminConfig)
|
2024-01-23 19:36:59 -05:00
|
|
|
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()
|
2020-08-03 14:44:38 -05:00
|
|
|
}
|
2020-03-10 09:25:26 -05:00
|
|
|
}
|
2024-01-23 19:36:59 -05:00
|
|
|
}
|
|
|
|
for d.NextBlock(0) {
|
|
|
|
switch d.Val() {
|
|
|
|
case "enforce_origin":
|
|
|
|
adminCfg.EnforceOrigin = true
|
2020-08-03 14:44:38 -05:00
|
|
|
|
2024-01-23 19:36:59 -05:00
|
|
|
case "origins":
|
|
|
|
adminCfg.Origins = d.RemainingArgs()
|
2020-08-03 14:44:38 -05:00
|
|
|
|
2024-01-23 19:36:59 -05:00
|
|
|
default:
|
|
|
|
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
2019-10-30 16:12:42 -05:00
|
|
|
}
|
|
|
|
}
|
2020-08-03 14:44:38 -05:00
|
|
|
if adminCfg.Listen == "" && !adminCfg.Disabled {
|
|
|
|
adminCfg.Listen = caddy.DefaultAdminListen
|
|
|
|
}
|
|
|
|
return adminCfg, nil
|
2019-10-30 16:12:42 -05:00
|
|
|
}
|
2020-03-17 22:00:45 -05:00
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
|
|
|
if d.NextArg() {
|
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
|
|
|
|
2020-03-17 22:00:45 -05:00
|
|
|
var ond *caddytls.OnDemandConfig
|
2024-01-30 18:11:29 -05:00
|
|
|
|
|
|
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
2024-01-23 19:36:59 -05:00
|
|
|
switch d.Val() {
|
|
|
|
case "ask":
|
|
|
|
if !d.NextArg() {
|
|
|
|
return nil, d.ArgErr()
|
|
|
|
}
|
|
|
|
if ond == nil {
|
|
|
|
ond = new(caddytls.OnDemandConfig)
|
|
|
|
}
|
2024-04-22 16:47:09 -05:00
|
|
|
if ond.PermissionRaw != nil {
|
|
|
|
return nil, d.Err("on-demand TLS permission module (or 'ask') already specified")
|
|
|
|
}
|
2024-01-30 18:11:29 -05:00
|
|
|
perm := caddytls.PermissionByHTTP{Endpoint: d.Val()}
|
|
|
|
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", "http", nil)
|
2024-01-23 19:36:59 -05:00
|
|
|
|
2024-04-22 16:47:09 -05:00
|
|
|
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)
|
|
|
|
|
2024-01-23 19:36:59 -05:00
|
|
|
case "interval":
|
2024-10-07 16:39:47 -05:00
|
|
|
return nil, d.Errf("the on_demand_tls 'interval' option is no longer supported, remove it from your config")
|
2024-01-23 19:36:59 -05:00
|
|
|
|
|
|
|
case "burst":
|
2024-10-07 16:39:47 -05:00
|
|
|
return nil, d.Errf("the on_demand_tls 'burst' option is no longer supported, remove it from your config")
|
2024-01-23 19:36:59 -05:00
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
2020-03-17 22:00:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if ond == nil {
|
|
|
|
return nil, d.Err("expected at least one config parameter for on_demand_tls")
|
|
|
|
}
|
|
|
|
return ond, nil
|
|
|
|
}
|
2020-05-19 17:59:51 -05:00
|
|
|
|
2023-01-27 23:31:37 -05:00
|
|
|
func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
2023-01-27 23:31:37 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
2024-01-23 19:36:59 -05:00
|
|
|
d.Next() // consume option name
|
2024-10-02 08:31:58 -05:00
|
|
|
val := d.RemainingArgs()
|
|
|
|
if len(val) == 0 {
|
2020-05-19 17:59:51 -05:00
|
|
|
return "", d.ArgErr()
|
|
|
|
}
|
2024-10-02 08:31:58 -05:00
|
|
|
for _, v := range val {
|
|
|
|
switch v {
|
|
|
|
case "off":
|
|
|
|
case "disable_redirects":
|
|
|
|
case "disable_certs":
|
|
|
|
case "ignore_loaded_certs":
|
|
|
|
case "prefer_wildcard":
|
|
|
|
break
|
|
|
|
|
|
|
|
default:
|
|
|
|
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', 'ignore_loaded_certs', or 'prefer_wildcard'")
|
|
|
|
}
|
2020-05-19 17:59:51 -05:00
|
|
|
}
|
|
|
|
return val, nil
|
|
|
|
}
|
2020-11-23 14:46:50 -05:00
|
|
|
|
2024-10-18 10:54:21 -05:00
|
|
|
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
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
2020-11-23 14:46:50 -05:00
|
|
|
return unmarshalCaddyfileServerOptions(d)
|
|
|
|
}
|
2021-01-07 17:52:58 -05:00
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
2021-01-07 17:52:58 -05:00
|
|
|
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
|
|
|
|
}
|
2021-03-12 15:00:02 -05:00
|
|
|
|
|
|
|
// parseLogOptions parses the global log option. Syntax:
|
|
|
|
//
|
2022-09-16 15:05:37 -05:00
|
|
|
// log [name] {
|
|
|
|
// output <writer_module> ...
|
|
|
|
// format <encoder_module> ...
|
|
|
|
// level <level>
|
|
|
|
// include <namespaces...>
|
|
|
|
// exclude <namespaces...>
|
|
|
|
// }
|
2021-03-12 15:00:02 -05:00
|
|
|
//
|
|
|
|
// When the name argument is unspecified, this directive modifies the default
|
|
|
|
// logger.
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseLogOptions(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
2021-03-12 15:00:02 -05:00
|
|
|
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
|
|
|
|
}
|
2021-06-08 15:10:37 -05:00
|
|
|
|
2022-08-02 15:39:09 -05:00
|
|
|
func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
|
2021-06-08 15:10:37 -05:00
|
|
|
d.Next()
|
|
|
|
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
|
|
|
|
}
|