2019-08-10 02:05:47 +08: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 (
2020-06-06 02:19:36 +08:00
"encoding/base64"
"encoding/pem"
2019-08-10 02:05:47 +08:00
"fmt"
"html"
2020-06-06 02:19:36 +08:00
"io/ioutil"
2019-08-10 02:05:47 +08:00
"net/http"
2019-08-22 00:46:35 +08:00
"reflect"
2021-01-29 03:59:50 +08:00
"strconv"
2020-01-23 00:24:49 +08:00
"strings"
2019-08-10 02:05:47 +08:00
2019-12-11 04:36:46 +08:00
"github.com/caddyserver/caddy/v2"
2019-08-23 02:26:48 +08:00
"github.com/caddyserver/caddy/v2/caddyconfig"
2020-02-26 13:00:33 +08:00
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
2019-08-23 02:26:48 +08:00
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
2019-08-10 02:05:47 +08:00
"github.com/caddyserver/caddy/v2/modules/caddytls"
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 22:58:06 +08:00
"github.com/caddyserver/certmagic"
2020-07-31 05:18:14 +08:00
"github.com/mholt/acmez/acme"
2020-02-26 13:00:33 +08:00
"go.uber.org/zap/zapcore"
2019-08-10 02:05:47 +08:00
)
2019-08-22 00:46:35 +08:00
func init ( ) {
RegisterDirective ( "bind" , parseBind )
RegisterDirective ( "tls" , parseTLS )
2020-03-21 02:50:36 +08:00
RegisterHandlerDirective ( "root" , parseRoot )
2019-08-22 00:46:35 +08:00
RegisterHandlerDirective ( "redir" , parseRedir )
2019-09-17 01:04:18 +08:00
RegisterHandlerDirective ( "respond" , parseRespond )
2021-01-29 03:54:55 +08:00
RegisterHandlerDirective ( "abort" , parseAbort )
2020-01-10 05:00:32 +08:00
RegisterHandlerDirective ( "route" , parseRoute )
2020-02-29 04:38:12 +08:00
RegisterHandlerDirective ( "handle" , parseHandle )
2020-02-17 13:24:20 +08:00
RegisterDirective ( "handle_errors" , parseHandleErrors )
2020-02-26 13:00:33 +08:00
RegisterDirective ( "log" , parseLog )
2019-08-22 00:46:35 +08:00
}
2019-08-10 02:05:47 +08:00
2020-01-23 00:32:38 +08:00
// parseBind parses the bind directive. Syntax:
//
// bind <addresses...>
//
2019-08-22 00:46:35 +08:00
func parseBind ( h Helper ) ( [ ] ConfigValue , error ) {
var lnHosts [ ] string
for h . Next ( ) {
lnHosts = append ( lnHosts , h . RemainingArgs ( ) ... )
2019-08-10 02:05:47 +08:00
}
2019-08-22 00:46:35 +08:00
return h . NewBindAddresses ( lnHosts ) , nil
2019-08-10 02:05:47 +08:00
}
2020-01-23 00:24:49 +08:00
// parseTLS parses the tls directive. Syntax:
//
2020-03-14 01:06:08 +08:00
// tls [<email>|internal]|[<cert_file> <key_file>] {
2020-01-23 00:24:49 +08:00
// protocols <min> [<max>]
// ciphers <cipher_suites...>
// curves <curves...>
2020-06-06 02:19:36 +08:00
// client_auth {
// mode [request|require|verify_if_given|require_and_verify]
// trusted_ca_cert <base64_der>
// trusted_ca_cert_file <filename>
// trusted_leaf_cert <base64_der>
// trusted_leaf_cert_file <filename>
// }
2020-01-23 00:24:49 +08:00
// alpn <values...>
// load <paths...>
// ca <acme_ca_endpoint>
2020-03-19 05:51:31 +08:00
// ca_root <pem_file>
2020-10-03 04:23:40 +08:00
// dns <provider_name> [...]
2020-03-18 11:00:45 +08:00
// on_demand
2020-08-12 01:26:19 +08:00
// eab <key_id> <mac_key>
2020-10-03 04:23:40 +08:00
// issuer <module_name> [...]
2020-01-23 00:24:49 +08:00
// }
//
2019-08-22 00:46:35 +08:00
func parseTLS ( h Helper ) ( [ ] ConfigValue , error ) {
2020-03-18 11:00:45 +08:00
cp := new ( caddytls . ConnectionPolicy )
2019-08-10 02:05:47 +08:00
var fileLoader caddytls . FileLoader
var folderLoader caddytls . FolderLoader
caddytls: Refactor certificate selection policies (close #1575)
Certificate selection used to be a module, but this seems unnecessary,
especially since the built-in CustomSelectionPolicy allows quite complex
selection logic on a number of fields in certs. If we need to extend
that logic, we can, but I don't think there are SO many possibilities
that we need modules.
This update also allows certificate selection to choose between multiple
matching certs based on client compatibility and makes a number of other
improvements in the default cert selection logic, both here and in the
latest CertMagic.
The hardest part of this was the conn policy consolidation logic
(Caddyfile only, of course). We have to merge connection policies that
we can easily combine, because if two certs are manually loaded in a
Caddyfile site block, that produces two connection policies, and each
cert is tagged with a different tag, meaning only the first would ever
be selected. So given the same matchers, we can merge the two, but this
required improving the Tag selection logic to support multiple tags to
choose from, hence "tags" changed to "any_tag" or "all_tags" (but we
use any_tag in our Caddyfile logic).
Combining conn policies with conflicting settings is impossible, so
that should return an error if two policies with the exact same matchers
have non-empty settings that are not the same (the one exception being
any_tag which we can merge because the logic for them is to OR them).
It was a bit complicated. It seems to work in numerous tests I've
conducted, but we'll see how it pans out in the release candidates.
2020-04-02 10:49:35 +08:00
var certSelector caddytls . CustomCertSelectionPolicy
2020-03-14 01:06:08 +08:00
var acmeIssuer * caddytls . ACMEIssuer
2021-01-07 03:02:58 +08:00
var keyType string
2020-03-14 01:06:08 +08:00
var internalIssuer * caddytls . InternalIssuer
2020-11-17 02:05:55 +08:00
var issuers [ ] certmagic . Issuer
2020-03-18 11:00:45 +08:00
var onDemand bool
2019-09-30 23:11:30 +08:00
2019-08-22 00:46:35 +08:00
for h . Next ( ) {
// file certificate loader
firstLine := h . RemainingArgs ( )
switch len ( firstLine ) {
case 0 :
case 1 :
2020-03-14 01:06:08 +08:00
if firstLine [ 0 ] == "internal" {
internalIssuer = new ( caddytls . InternalIssuer )
} else if ! strings . Contains ( firstLine [ 0 ] , "@" ) {
return nil , h . Err ( "single argument must either be 'internal' or an email address" )
} else {
if acmeIssuer == nil {
acmeIssuer = new ( caddytls . ACMEIssuer )
}
acmeIssuer . Email = firstLine [ 0 ]
2019-08-22 00:46:35 +08:00
}
2020-03-14 01:06:08 +08:00
2019-08-22 00:46:35 +08:00
case 2 :
httpcaddyfile: tls: Load repeated cert files only once, with one tag
See end of issue #3004. Loading the same certificate file multiple times
with different tags will result in it being de-duplicated in the in-
memory cache, because of course they all have the same bytes. This
meant that any certs of the same filename loaded with different tags
would be overwritten by the next certificate of the same filename, and
any conn policies looking for the tags of the previous ones would never
find them, causing connections to fail.
So, now we remember cert filenames and their tags, instead of loading
them multiple times and overwriting previous ones.
A user crafting their own JSON might make this error too... maybe we
won't see it happen. But if it does, one possibility is, when loading
a duplicate cert, instead of discarding it completely, merge the tag
list into the one that's already stored in the cache, then discard.
2020-02-21 01:18:29 +08:00
certFilename := firstLine [ 0 ]
keyFilename := firstLine [ 1 ]
2020-02-07 03:55:26 +08:00
// tag this certificate so if multiple certs match, specifically
// this one that the user has provided will be used, see #2588:
httpcaddyfile: tls: Load repeated cert files only once, with one tag
See end of issue #3004. Loading the same certificate file multiple times
with different tags will result in it being de-duplicated in the in-
memory cache, because of course they all have the same bytes. This
meant that any certs of the same filename loaded with different tags
would be overwritten by the next certificate of the same filename, and
any conn policies looking for the tags of the previous ones would never
find them, causing connections to fail.
So, now we remember cert filenames and their tags, instead of loading
them multiple times and overwriting previous ones.
A user crafting their own JSON might make this error too... maybe we
won't see it happen. But if it does, one possibility is, when loading
a duplicate cert, instead of discarding it completely, merge the tag
list into the one that's already stored in the cache, then discard.
2020-02-21 01:18:29 +08:00
// https://github.com/caddyserver/caddy/issues/2588 ... but we
// must be careful about how we do this; being careless will
// lead to failed handshakes
2020-03-14 01:06:08 +08:00
//
httpcaddyfile: tls: Load repeated cert files only once, with one tag
See end of issue #3004. Loading the same certificate file multiple times
with different tags will result in it being de-duplicated in the in-
memory cache, because of course they all have the same bytes. This
meant that any certs of the same filename loaded with different tags
would be overwritten by the next certificate of the same filename, and
any conn policies looking for the tags of the previous ones would never
find them, causing connections to fail.
So, now we remember cert filenames and their tags, instead of loading
them multiple times and overwriting previous ones.
A user crafting their own JSON might make this error too... maybe we
won't see it happen. But if it does, one possibility is, when loading
a duplicate cert, instead of discarding it completely, merge the tag
list into the one that's already stored in the cache, then discard.
2020-02-21 01:18:29 +08:00
// we need to remember which cert files we've seen, since we
// must load each cert only once; otherwise, they each get a
// different tag... since a cert loaded twice has the same
// bytes, it will overwrite the first one in the cache, and
// only the last cert (and its tag) will survive, so a any conn
// policy that is looking for any tag but the last one to be
// loaded won't find it, and TLS handshakes will fail (see end)
// of issue #3004)
2020-03-14 01:06:08 +08:00
//
2020-03-05 00:58:49 +08:00
// tlsCertTags maps certificate filenames to their tag.
// This is used to remember which tag is used for each
// certificate files, since we need to avoid loading
// the same certificate files more than once, overwriting
// previous tags
tlsCertTags , ok := h . State [ "tlsCertTags" ] . ( map [ string ] string )
if ! ok {
tlsCertTags = make ( map [ string ] string )
h . State [ "tlsCertTags" ] = tlsCertTags
}
httpcaddyfile: tls: Load repeated cert files only once, with one tag
See end of issue #3004. Loading the same certificate file multiple times
with different tags will result in it being de-duplicated in the in-
memory cache, because of course they all have the same bytes. This
meant that any certs of the same filename loaded with different tags
would be overwritten by the next certificate of the same filename, and
any conn policies looking for the tags of the previous ones would never
find them, causing connections to fail.
So, now we remember cert filenames and their tags, instead of loading
them multiple times and overwriting previous ones.
A user crafting their own JSON might make this error too... maybe we
won't see it happen. But if it does, one possibility is, when loading
a duplicate cert, instead of discarding it completely, merge the tag
list into the one that's already stored in the cache, then discard.
2020-02-21 01:18:29 +08:00
tag , ok := tlsCertTags [ certFilename ]
if ! ok {
// haven't seen this cert file yet, let's give it a tag
// and add a loader for it
tag = fmt . Sprintf ( "cert%d" , len ( tlsCertTags ) )
fileLoader = append ( fileLoader , caddytls . CertKeyFilePair {
Certificate : certFilename ,
Key : keyFilename ,
Tags : [ ] string { tag } ,
} )
// remember this for next time we see this cert file
tlsCertTags [ certFilename ] = tag
}
caddytls: Refactor certificate selection policies (close #1575)
Certificate selection used to be a module, but this seems unnecessary,
especially since the built-in CustomSelectionPolicy allows quite complex
selection logic on a number of fields in certs. If we need to extend
that logic, we can, but I don't think there are SO many possibilities
that we need modules.
This update also allows certificate selection to choose between multiple
matching certs based on client compatibility and makes a number of other
improvements in the default cert selection logic, both here and in the
latest CertMagic.
The hardest part of this was the conn policy consolidation logic
(Caddyfile only, of course). We have to merge connection policies that
we can easily combine, because if two certs are manually loaded in a
Caddyfile site block, that produces two connection policies, and each
cert is tagged with a different tag, meaning only the first would ever
be selected. So given the same matchers, we can merge the two, but this
required improving the Tag selection logic to support multiple tags to
choose from, hence "tags" changed to "any_tag" or "all_tags" (but we
use any_tag in our Caddyfile logic).
Combining conn policies with conflicting settings is impossible, so
that should return an error if two policies with the exact same matchers
have non-empty settings that are not the same (the one exception being
any_tag which we can merge because the logic for them is to OR them).
It was a bit complicated. It seems to work in numerous tests I've
conducted, but we'll see how it pans out in the release candidates.
2020-04-02 10:49:35 +08:00
certSelector . AnyTag = append ( certSelector . AnyTag , tag )
2019-08-22 00:46:35 +08:00
default :
return nil , h . ArgErr ( )
2019-08-10 02:05:47 +08:00
}
2019-08-22 00:46:35 +08:00
var hasBlock bool
2020-06-06 02:19:36 +08:00
for nesting := h . Nesting ( ) ; h . NextBlock ( nesting ) ; {
2019-08-22 00:46:35 +08:00
hasBlock = true
2019-08-10 02:05:47 +08:00
2019-08-22 00:46:35 +08:00
switch h . Val ( ) {
2019-08-10 02:05:47 +08:00
case "protocols" :
2019-08-22 00:46:35 +08:00
args := h . RemainingArgs ( )
2019-08-10 02:05:47 +08:00
if len ( args ) == 0 {
2019-08-22 00:46:35 +08:00
return nil , h . SyntaxErr ( "one or two protocols" )
2019-08-10 02:05:47 +08:00
}
if len ( args ) > 0 {
if _ , ok := caddytls . SupportedProtocols [ args [ 0 ] ] ; ! ok {
2019-08-22 00:46:35 +08:00
return nil , h . Errf ( "Wrong protocol name or protocol not supported: '%s'" , args [ 0 ] )
2019-08-10 02:05:47 +08:00
}
cp . ProtocolMin = args [ 0 ]
}
if len ( args ) > 1 {
if _ , ok := caddytls . SupportedProtocols [ args [ 1 ] ] ; ! ok {
2019-08-22 00:46:35 +08:00
return nil , h . Errf ( "Wrong protocol name or protocol not supported: '%s'" , args [ 1 ] )
2019-08-10 02:05:47 +08:00
}
cp . ProtocolMax = args [ 1 ]
}
2020-03-18 11:00:45 +08:00
2019-08-10 02:05:47 +08:00
case "ciphers" :
2019-08-22 00:46:35 +08:00
for h . NextArg ( ) {
2020-04-02 04:09:29 +08:00
if ! caddytls . CipherSuiteNameSupported ( h . Val ( ) ) {
2019-08-22 00:46:35 +08:00
return nil , h . Errf ( "Wrong cipher suite name or cipher suite not supported: '%s'" , h . Val ( ) )
2019-08-10 02:05:47 +08:00
}
2019-08-22 00:46:35 +08:00
cp . CipherSuites = append ( cp . CipherSuites , h . Val ( ) )
2019-08-10 02:05:47 +08:00
}
2020-03-18 11:00:45 +08:00
2019-08-10 02:05:47 +08:00
case "curves" :
2019-08-22 00:46:35 +08:00
for h . NextArg ( ) {
if _ , ok := caddytls . SupportedCurves [ h . Val ( ) ] ; ! ok {
return nil , h . Errf ( "Wrong curve name or curve not supported: '%s'" , h . Val ( ) )
2019-08-10 02:05:47 +08:00
}
2019-08-22 00:46:35 +08:00
cp . Curves = append ( cp . Curves , h . Val ( ) )
2019-08-10 02:05:47 +08:00
}
2020-03-18 11:00:45 +08:00
2020-06-06 02:19:36 +08:00
case "client_auth" :
cp . ClientAuthentication = & caddytls . ClientAuthentication { }
for nesting := h . Nesting ( ) ; h . NextBlock ( nesting ) ; {
subdir := h . Val ( )
switch subdir {
case "mode" :
if ! h . Args ( & cp . ClientAuthentication . Mode ) {
return nil , h . ArgErr ( )
}
if h . NextArg ( ) {
return nil , h . ArgErr ( )
}
case "trusted_ca_cert" ,
"trusted_leaf_cert" :
if ! h . NextArg ( ) {
return nil , h . ArgErr ( )
}
if subdir == "trusted_ca_cert" {
cp . ClientAuthentication . TrustedCACerts = append ( cp . ClientAuthentication . TrustedCACerts , h . Val ( ) )
} else {
cp . ClientAuthentication . TrustedLeafCerts = append ( cp . ClientAuthentication . TrustedLeafCerts , h . Val ( ) )
}
case "trusted_ca_cert_file" ,
"trusted_leaf_cert_file" :
if ! h . NextArg ( ) {
return nil , h . ArgErr ( )
}
filename := h . Val ( )
certDataPEM , err := ioutil . ReadFile ( filename )
if err != nil {
return nil , err
}
block , _ := pem . Decode ( certDataPEM )
if block == nil || block . Type != "CERTIFICATE" {
return nil , h . Errf ( "no CERTIFICATE pem block found in %s" , h . Val ( ) )
}
if subdir == "trusted_ca_cert_file" {
cp . ClientAuthentication . TrustedCACerts = append ( cp . ClientAuthentication . TrustedCACerts ,
base64 . StdEncoding . EncodeToString ( block . Bytes ) )
} else {
cp . ClientAuthentication . TrustedLeafCerts = append ( cp . ClientAuthentication . TrustedLeafCerts ,
base64 . StdEncoding . EncodeToString ( block . Bytes ) )
}
default :
return nil , h . Errf ( "unknown subdirective for client_auth: %s" , subdir )
}
}
2019-08-10 02:05:47 +08:00
case "alpn" :
2019-08-22 00:46:35 +08:00
args := h . RemainingArgs ( )
2019-08-10 02:05:47 +08:00
if len ( args ) == 0 {
2019-08-22 00:46:35 +08:00
return nil , h . ArgErr ( )
2019-08-10 02:05:47 +08:00
}
cp . ALPN = args
2019-08-22 00:46:35 +08:00
case "load" :
folderLoader = append ( folderLoader , h . RemainingArgs ( ) ... )
case "ca" :
arg := h . RemainingArgs ( )
if len ( arg ) != 1 {
return nil , h . ArgErr ( )
}
2020-03-14 01:06:08 +08:00
if acmeIssuer == nil {
acmeIssuer = new ( caddytls . ACMEIssuer )
}
acmeIssuer . CA = arg [ 0 ]
2019-08-22 00:46:35 +08:00
2021-01-07 03:02:58 +08:00
case "key_type" :
arg := h . RemainingArgs ( )
if len ( arg ) != 1 {
return nil , h . ArgErr ( )
}
keyType = arg [ 0 ]
2020-07-31 05:18:14 +08:00
case "eab" :
arg := h . RemainingArgs ( )
if len ( arg ) != 2 {
return nil , h . ArgErr ( )
}
if acmeIssuer == nil {
acmeIssuer = new ( caddytls . ACMEIssuer )
}
acmeIssuer . ExternalAccount = & acme . EAB {
KeyID : arg [ 0 ] ,
MACKey : arg [ 1 ] ,
}
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 22:58:06 +08:00
case "issuer" :
if ! h . NextArg ( ) {
return nil , h . ArgErr ( )
}
modName := h . Val ( )
2021-01-06 05:39:30 +08:00
modID := "tls.issuance." + modName
unm , err := caddyfile . UnmarshalModule ( h . Dispenser , modID )
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 22:58:06 +08:00
if err != nil {
return nil , err
}
2020-11-17 02:05:55 +08:00
issuer , ok := unm . ( 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 22:58:06 +08:00
if ! ok {
2021-01-06 05:39:30 +08:00
return nil , h . Errf ( "module %s (%T) is not a certmagic.Issuer" , modID , unm )
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 22:58:06 +08:00
}
2020-11-17 02:05:55 +08:00
issuers = append ( issuers , 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 22:58:06 +08:00
2020-02-09 07:52:54 +08:00
case "dns" :
2020-05-03 07:23:36 +08:00
if ! h . NextArg ( ) {
2020-02-09 07:52:54 +08:00
return nil , h . ArgErr ( )
}
2020-05-02 00:41:08 +08:00
provName := h . Val ( )
2020-03-14 01:06:08 +08:00
if acmeIssuer == nil {
acmeIssuer = new ( caddytls . ACMEIssuer )
}
if acmeIssuer . Challenges == nil {
acmeIssuer . Challenges = new ( caddytls . ChallengesConfig )
2021-02-03 14:07:50 +08:00
}
if acmeIssuer . Challenges . DNS == nil {
2020-05-01 06:15:20 +08:00
acmeIssuer . Challenges . DNS = new ( caddytls . DNSChallengeConfig )
2020-02-09 07:52:54 +08:00
}
2021-01-06 05:39:30 +08:00
modID := "dns.providers." + provName
unm , err := caddyfile . UnmarshalModule ( h . Dispenser , modID )
2020-02-09 07:52:54 +08:00
if err != nil {
2021-01-06 05:39:30 +08:00
return nil , err
2020-05-02 00:41:08 +08:00
}
2021-01-06 05:39:30 +08:00
acmeIssuer . Challenges . DNS . ProviderRaw = caddyconfig . JSONModuleObject ( unm , "name" , provName , h . warnings )
2020-02-17 13:24:20 +08:00
2021-02-03 14:07:50 +08:00
case "resolvers" :
args := h . RemainingArgs ( )
if len ( args ) == 0 {
return nil , h . ArgErr ( )
}
if acmeIssuer == nil {
acmeIssuer = new ( caddytls . ACMEIssuer )
}
if acmeIssuer . Challenges == nil {
acmeIssuer . Challenges = new ( caddytls . ChallengesConfig )
}
if acmeIssuer . Challenges . DNS == nil {
acmeIssuer . Challenges . DNS = new ( caddytls . DNSChallengeConfig )
}
acmeIssuer . Challenges . DNS . Resolvers = args
2020-02-13 04:07:25 +08:00
case "ca_root" :
arg := h . RemainingArgs ( )
if len ( arg ) != 1 {
return nil , h . ArgErr ( )
}
2020-03-14 01:06:08 +08:00
if acmeIssuer == nil {
acmeIssuer = new ( caddytls . ACMEIssuer )
}
acmeIssuer . TrustedRootsPEMFiles = append ( acmeIssuer . TrustedRootsPEMFiles , arg [ 0 ] )
2020-02-09 07:52:54 +08:00
2020-03-18 11:00:45 +08:00
case "on_demand" :
if h . NextArg ( ) {
return nil , h . ArgErr ( )
}
onDemand = true
2019-09-30 23:11:30 +08:00
default :
return nil , h . Errf ( "unknown subdirective: %s" , h . Val ( ) )
2019-08-10 02:05:47 +08:00
}
}
2019-08-22 00:46:35 +08:00
// a naked tls directive is not allowed
if len ( firstLine ) == 0 && ! hasBlock {
return nil , h . ArgErr ( )
}
}
2020-03-14 01:06:08 +08:00
// begin building the final config values
2020-11-23 05:50:29 +08:00
configVals := [ ] ConfigValue { }
2020-03-14 01:06:08 +08:00
2019-08-22 00:46:35 +08:00
// certificate loaders
if len ( fileLoader ) > 0 {
configVals = append ( configVals , ConfigValue {
2020-05-30 01:49:21 +08:00
Class : "tls.cert_loader" ,
2019-08-22 00:46:35 +08:00
Value : fileLoader ,
} )
}
if len ( folderLoader ) > 0 {
configVals = append ( configVals , ConfigValue {
2020-05-30 01:49:21 +08:00
Class : "tls.cert_loader" ,
2019-08-22 00:46:35 +08:00
Value : folderLoader ,
} )
}
2021-02-03 07:17:26 +08:00
// some tls subdirectives are shortcuts that implicitly configure issuers, and the
// user can also configure issuers explicitly using the issuer subdirective; the
// logic to support both would likely be complex, or at least unintuitive
2020-11-17 02:05:55 +08:00
if len ( issuers ) > 0 && ( acmeIssuer != nil || internalIssuer != nil ) {
return nil , h . Err ( "cannot mix issuer subdirective (explicit issuers) with other issuer-specific subdirectives (implicit issuers)" )
2020-03-14 01:06:08 +08:00
}
2021-02-03 07:17:26 +08:00
if acmeIssuer != nil && internalIssuer != nil {
return nil , h . Err ( "cannot create both ACME and internal certificate issuers" )
2020-11-17 02:05:55 +08:00
}
2021-02-03 07:17:26 +08:00
// now we should either have: explicitly-created issuers, or an implicitly-created
// ACME or internal issuer, or no issuers at all
switch {
case len ( issuers ) > 0 :
for _ , issuer := range issuers {
configVals = append ( configVals , ConfigValue {
Class : "tls.cert_issuer" ,
Value : issuer ,
} )
}
case acmeIssuer != nil :
// implicit ACME issuers (from various subdirectives) - use defaults; there might be more than one
defaultIssuers := caddytls . DefaultIssuers ( )
// if a CA endpoint was set, override multiple implicit issuers since it's a specific one
if acmeIssuer . CA != "" {
defaultIssuers = [ ] certmagic . Issuer { acmeIssuer }
}
for _ , issuer := range defaultIssuers {
switch iss := issuer . ( type ) {
case * caddytls . ACMEIssuer :
issuer = acmeIssuer
case * caddytls . ZeroSSLIssuer :
iss . ACMEIssuer = acmeIssuer
}
configVals = append ( configVals , ConfigValue {
Class : "tls.cert_issuer" ,
Value : issuer ,
} )
}
case internalIssuer != nil :
2019-08-22 00:46:35 +08:00
configVals = append ( configVals , ConfigValue {
2020-03-07 14:15:25 +08:00
Class : "tls.cert_issuer" ,
2020-11-17 02:05:55 +08:00
Value : internalIssuer ,
2019-08-22 00:46:35 +08:00
} )
}
2021-02-03 07:17:26 +08:00
// certificate key type
2021-01-07 03:02:58 +08:00
if keyType != "" {
configVals = append ( configVals , ConfigValue {
Class : "tls.key_type" ,
Value : keyType ,
} )
}
2020-03-18 11:00:45 +08:00
// on-demand TLS
if onDemand {
configVals = append ( configVals , ConfigValue {
Class : "tls.on_demand" ,
Value : true ,
} )
}
caddytls: Refactor certificate selection policies (close #1575)
Certificate selection used to be a module, but this seems unnecessary,
especially since the built-in CustomSelectionPolicy allows quite complex
selection logic on a number of fields in certs. If we need to extend
that logic, we can, but I don't think there are SO many possibilities
that we need modules.
This update also allows certificate selection to choose between multiple
matching certs based on client compatibility and makes a number of other
improvements in the default cert selection logic, both here and in the
latest CertMagic.
The hardest part of this was the conn policy consolidation logic
(Caddyfile only, of course). We have to merge connection policies that
we can easily combine, because if two certs are manually loaded in a
Caddyfile site block, that produces two connection policies, and each
cert is tagged with a different tag, meaning only the first would ever
be selected. So given the same matchers, we can merge the two, but this
required improving the Tag selection logic to support multiple tags to
choose from, hence "tags" changed to "any_tag" or "all_tags" (but we
use any_tag in our Caddyfile logic).
Combining conn policies with conflicting settings is impossible, so
that should return an error if two policies with the exact same matchers
have non-empty settings that are not the same (the one exception being
any_tag which we can merge because the logic for them is to OR them).
It was a bit complicated. It seems to work in numerous tests I've
conducted, but we'll see how it pans out in the release candidates.
2020-04-02 10:49:35 +08:00
// custom certificate selection
if len ( certSelector . AnyTag ) > 0 {
cp . CertSelection = & certSelector
}
2020-03-18 11:00:45 +08:00
// connection policy -- always add one, to ensure that TLS
// is enabled, because this directive was used (this is
// needed, for instance, when a site block has a key of
// just ":5000" - i.e. no hostname, and only on-demand TLS
// is enabled)
configVals = append ( configVals , ConfigValue {
Class : "tls.connection_policy" ,
Value : cp ,
} )
2019-08-22 00:46:35 +08:00
return configVals , nil
}
2020-03-21 02:50:36 +08:00
// parseRoot parses the root directive. Syntax:
//
// root [<matcher>] <path>
//
func parseRoot ( h Helper ) ( caddyhttp . MiddlewareHandler , error ) {
var root string
for h . Next ( ) {
2020-03-23 13:13:08 +08:00
if ! h . NextArg ( ) {
2020-03-21 02:50:36 +08:00
return nil , h . ArgErr ( )
}
root = h . Val ( )
if h . NextArg ( ) {
return nil , h . ArgErr ( )
}
}
return caddyhttp . VarsMiddleware { "root" : root } , nil
}
2020-01-23 00:32:38 +08:00
// parseRedir parses the redir directive. Syntax:
//
// redir [<matcher>] <to> [<code>]
//
2019-08-22 00:46:35 +08:00
func parseRedir ( h Helper ) ( caddyhttp . MiddlewareHandler , error ) {
if ! h . Next ( ) {
return nil , h . ArgErr ( )
}
if ! h . NextArg ( ) {
return nil , h . ArgErr ( )
}
to := h . Val ( )
var code string
if h . NextArg ( ) {
code = h . Val ( )
}
2021-01-29 03:59:50 +08:00
var body string
switch code {
case "permanent" :
2019-08-22 00:46:35 +08:00
code = "301"
2021-01-29 03:59:50 +08:00
case "temporary" , "" :
2020-01-23 00:32:38 +08:00
code = "302"
2021-01-29 03:59:50 +08:00
case "html" :
2019-08-22 00:46:35 +08:00
// Script tag comes first since that will better imitate a redirect in the browser's
// history, but the meta tag is a fallback for most non-JS clients.
const metaRedir = ` < ! DOCTYPE html >
< html >
< head >
< title > Redirecting ... < / title >
< script > window . location . replace ( "%s" ) ; < / script >
< meta http - equiv = "refresh" content = "0; URL='%s'" >
< / head >
< body > Redirecting to < a href = "%s" > % s < / a > ... < / body >
< / html >
`
safeTo := html . EscapeString ( to )
body = fmt . Sprintf ( metaRedir , safeTo , safeTo , safeTo , safeTo )
2021-01-29 03:59:50 +08:00
code = "302"
default :
codeInt , err := strconv . Atoi ( code )
if err != nil {
return nil , h . Errf ( "Not a supported redir code type or not valid integer: '%s'" , code )
}
if codeInt < 300 || codeInt > 399 {
return nil , h . Errf ( "Redir code not in the 3xx range: '%v'" , codeInt )
}
2019-08-10 02:05:47 +08:00
}
2019-08-22 00:46:35 +08:00
return caddyhttp . StaticResponse {
StatusCode : caddyhttp . WeakString ( code ) ,
Headers : http . Header { "Location" : [ ] string { to } } ,
Body : body ,
} , nil
2019-08-10 02:05:47 +08:00
}
2019-09-17 01:04:18 +08:00
2020-01-23 00:32:38 +08:00
// parseRespond parses the respond directive.
2019-09-17 01:04:18 +08:00
func parseRespond ( h Helper ) ( caddyhttp . MiddlewareHandler , error ) {
sr := new ( caddyhttp . StaticResponse )
err := sr . UnmarshalCaddyfile ( h . Dispenser )
if err != nil {
return nil , err
}
return sr , nil
}
2020-01-10 05:00:32 +08:00
2021-01-29 03:54:55 +08:00
// parseAbort parses the abort directive.
func parseAbort ( h Helper ) ( caddyhttp . MiddlewareHandler , error ) {
h . Next ( ) // consume directive
for h . Next ( ) || h . NextBlock ( 0 ) {
return nil , h . ArgErr ( )
}
return & caddyhttp . StaticResponse { Abort : true } , nil
}
2020-01-23 00:32:38 +08:00
// parseRoute parses the route directive.
2020-01-10 05:00:32 +08:00
func parseRoute ( h Helper ) ( caddyhttp . MiddlewareHandler , error ) {
sr := new ( caddyhttp . Subroute )
2020-08-06 03:42:29 +08:00
allResults , err := parseSegmentAsConfig ( h )
if err != nil {
return nil , err
}
2020-01-10 05:00:32 +08:00
2020-08-06 03:42:29 +08:00
for _ , result := range allResults {
switch handler := result . Value . ( type ) {
case caddyhttp . Route :
sr . Routes = append ( sr . Routes , handler )
case caddyhttp . Subroute :
// directives which return a literal subroute instead of a route
// means they intend to keep those handlers together without
// them being reordered; we're doing that anyway since we're in
// the route directive, so just append its handlers
sr . Routes = append ( sr . Routes , handler . Routes ... )
default :
return nil , h . Errf ( "%s directive returned something other than an HTTP route or subroute: %#v (only handler directives can be used in routes)" , result . directive , result . Value )
2020-01-10 05:00:32 +08:00
}
}
return sr , nil
}
2020-01-17 08:08:52 +08:00
func parseHandle ( h Helper ) ( caddyhttp . MiddlewareHandler , error ) {
2020-05-27 05:27:51 +08:00
return ParseSegmentAsSubroute ( h )
2020-02-17 13:24:20 +08:00
}
2020-01-17 08:08:52 +08:00
2020-02-17 13:24:20 +08:00
func parseHandleErrors ( h Helper ) ( [ ] ConfigValue , error ) {
2020-05-27 05:27:51 +08:00
subroute , err := ParseSegmentAsSubroute ( h )
2020-02-17 13:24:20 +08:00
if err != nil {
return nil , err
2020-01-17 08:08:52 +08:00
}
2020-02-17 13:24:20 +08:00
return [ ] ConfigValue {
{
Class : "error_route" ,
Value : subroute ,
} ,
} , nil
2020-01-17 08:08:52 +08:00
}
2020-02-07 03:55:26 +08:00
2020-02-26 13:00:33 +08:00
// parseLog parses the log directive. Syntax:
//
// log {
// output <writer_module> ...
// format <encoder_module> ...
// level <level>
// }
//
func parseLog ( h Helper ) ( [ ] ConfigValue , error ) {
2021-03-13 04:00:02 +08:00
return parseLogHelper ( h , nil )
}
// parseLogHelper is used both for the parseLog directive within Server Blocks,
// as well as the global "log" option for configuring loggers at the global
// level. The parseAsGlobalOption parameter is used to distinguish any differing logic
// between the two.
func parseLogHelper ( h Helper , globalLogNames map [ string ] struct { } ) ( [ ] ConfigValue , error ) {
// When the globalLogNames parameter is passed in, we make
// modifications to the parsing behavior.
parseAsGlobalOption := globalLogNames != nil
2020-02-26 13:00:33 +08:00
var configValues [ ] ConfigValue
for h . Next ( ) {
2021-03-13 04:00:02 +08:00
// Logic below expects that a name is always present when a
// global option is being parsed.
var globalLogName string
if parseAsGlobalOption {
if h . NextArg ( ) {
globalLogName = h . Val ( )
// Only a single argument is supported.
if h . NextArg ( ) {
return nil , h . ArgErr ( )
}
} else {
// If there is no log name specified, we
// reference the default logger. See the
// setupNewDefault function in the logging
// package for where this is configured.
globalLogName = "default"
}
// Verify this name is unused.
_ , used := globalLogNames [ globalLogName ]
if used {
return nil , h . Err ( "duplicate global log option for: " + globalLogName )
}
globalLogNames [ globalLogName ] = struct { } { }
} else {
// No arguments are supported for the server block log directive
if h . NextArg ( ) {
return nil , h . ArgErr ( )
}
2020-05-16 05:57:16 +08:00
}
2020-02-26 13:00:33 +08:00
cl := new ( caddy . CustomLog )
for h . NextBlock ( 0 ) {
switch h . Val ( ) {
case "output" :
if ! h . NextArg ( ) {
return nil , h . ArgErr ( )
}
moduleName := h . Val ( )
// can't use the usual caddyfile.Unmarshaler flow with the
// standard writers because they are in the caddy package
// (because they are the default) and implementing that
// interface there would unfortunately create circular import
var wo caddy . WriterOpener
switch moduleName {
case "stdout" :
wo = caddy . StdoutWriter { }
case "stderr" :
wo = caddy . StderrWriter { }
case "discard" :
wo = caddy . DiscardWriter { }
default :
2021-01-06 05:39:30 +08:00
modID := "caddy.logging.writers." + moduleName
unm , err := caddyfile . UnmarshalModule ( h . Dispenser , modID )
2020-02-26 13:00:33 +08:00
if err != nil {
return nil , err
}
2021-01-06 05:39:30 +08:00
var ok bool
2020-02-26 13:00:33 +08:00
wo , ok = unm . ( caddy . WriterOpener )
if ! ok {
2021-01-06 05:39:30 +08:00
return nil , h . Errf ( "module %s (%T) is not a WriterOpener" , modID , unm )
2020-02-26 13:00:33 +08:00
}
}
cl . WriterRaw = caddyconfig . JSONModuleObject ( wo , "output" , moduleName , h . warnings )
case "format" :
if ! h . NextArg ( ) {
return nil , h . ArgErr ( )
}
moduleName := h . Val ( )
2021-01-06 05:39:30 +08:00
moduleID := "caddy.logging.encoders." + moduleName
unm , err := caddyfile . UnmarshalModule ( h . Dispenser , moduleID )
2020-02-26 13:00:33 +08:00
if err != nil {
return nil , err
}
enc , ok := unm . ( zapcore . Encoder )
if ! ok {
2021-01-06 05:39:30 +08:00
return nil , h . Errf ( "module %s (%T) is not a zapcore.Encoder" , moduleID , unm )
2020-02-26 13:00:33 +08:00
}
cl . EncoderRaw = caddyconfig . JSONModuleObject ( enc , "format" , moduleName , h . warnings )
case "level" :
if ! h . NextArg ( ) {
return nil , h . ArgErr ( )
}
cl . Level = h . Val ( )
if h . NextArg ( ) {
return nil , h . ArgErr ( )
}
2021-03-13 04:00:02 +08:00
case "include" :
// This configuration is only allowed in the global options
if ! parseAsGlobalOption {
return nil , h . ArgErr ( )
}
for h . NextArg ( ) {
cl . Include = append ( cl . Include , h . Val ( ) )
}
case "exclude" :
// This configuration is only allowed in the global options
if ! parseAsGlobalOption {
return nil , h . ArgErr ( )
}
for h . NextArg ( ) {
cl . Exclude = append ( cl . Exclude , h . Val ( ) )
}
2020-02-26 13:00:33 +08:00
default :
return nil , h . Errf ( "unrecognized subdirective: %s" , h . Val ( ) )
}
}
var val namedCustomLog
2021-03-13 04:00:02 +08:00
// Skip handling of empty logging configs
2020-02-26 13:00:33 +08:00
if ! reflect . DeepEqual ( cl , new ( caddy . CustomLog ) ) {
2021-03-13 04:00:02 +08:00
if parseAsGlobalOption {
// Use indicated name for global log options
val . name = globalLogName
val . log = cl
} else {
// Construct a log name for server log streams
logCounter , ok := h . State [ "logCounter" ] . ( int )
if ! ok {
logCounter = 0
}
val . name = fmt . Sprintf ( "log%d" , logCounter )
cl . Include = [ ] string { "http.log.access." + val . name }
val . log = cl
logCounter ++
h . State [ "logCounter" ] = logCounter
2020-03-05 00:58:49 +08:00
}
2020-02-26 13:00:33 +08:00
}
configValues = append ( configValues , ConfigValue {
Class : "custom_log" ,
Value : val ,
} )
}
return configValues , nil
}