summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Holt <mholt@users.noreply.github.com>2022-02-17 15:40:34 -0700
committerGitHub <noreply@github.com>2022-02-17 15:40:34 -0700
commit57a708d189cfe4ccc20ae92df95dd35e52a434a8 (patch)
tree6a8b2ded2d0d70cea75c7b1cc5bd536b7999e9d8
parent32aad909380f08a40389a33bfe788c8a35b1d850 (diff)
caddytls: Support external certificate Managers (like Tailscale) (#4541)
Huge thank-you to Tailscale (https://tailscale.com) for making this change possible! This is a great feature for Caddy and Tailscale is a great fit for a standard implementation. * caddytls: GetCertificate modules; Tailscale * Caddyfile support for get_certificate Also fix AP provisioning in case of empty subject list (persist loaded module on struct, much like Issuers, to surive reprovisioning). And implement start of HTTP cert getter, still WIP. * Update modules/caddytls/automation.go Co-authored-by: Francis Lavoie <lavofr@gmail.com> * Use tsclient package, check status for name * Implement HTTP cert getter And use reuse CertMagic's PEM functions for private keys. * Remove cache option from Tailscale getter Tailscale does its own caching and we don't need the added complexity... for now, at least. * Several updates - Option to disable cert automation in auto HTTPS - Support multiple cert managers - Remove cache feature from cert manager modules - Minor improvements to auto HTTPS logging * Run go mod tidy * Try to get certificates from Tailscale implicitly Only for domains ending in .ts.net. I think this is really cool! Co-authored-by: Francis Lavoie <lavofr@gmail.com>
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go24
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go11
-rw-r--r--caddyconfig/httpcaddyfile/options.go4
-rw-r--r--caddyconfig/httpcaddyfile/tlsapp.go7
-rw-r--r--go.mod10
-rw-r--r--go.sum75
-rw-r--r--modules/caddyhttp/autohttps.go99
-rw-r--r--modules/caddypki/ca.go12
-rw-r--r--modules/caddypki/certificates.go5
-rw-r--r--modules/caddypki/crypto.go74
-rw-r--r--modules/caddytls/automation.go44
-rw-r--r--modules/caddytls/certmanagers.go208
-rw-r--r--modules/caddytls/folderloader.go35
13 files changed, 450 insertions, 158 deletions
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 7fdd3e8..2606bf3 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -82,6 +82,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// on_demand
// eab <key_id> <mac_key>
// issuer <module_name> [...]
+// get_certificate <module_name> [...]
// }
//
func parseTLS(h Helper) ([]ConfigValue, error) {
@@ -93,6 +94,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
var keyType string
var internalIssuer *caddytls.InternalIssuer
var issuers []certmagic.Issuer
+ var certManagers []certmagic.CertificateManager
var onDemand bool
for h.Next() {
@@ -307,6 +309,22 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}
issuers = append(issuers, issuer)
+ case "get_certificate":
+ if !h.NextArg() {
+ return nil, h.ArgErr()
+ }
+ modName := h.Val()
+ modID := "tls.get_certificate." + modName
+ unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
+ if err != nil {
+ return nil, err
+ }
+ certManager, ok := unm.(certmagic.CertificateManager)
+ if !ok {
+ return nil, h.Errf("module %s (%T) is not a certmagic.CertificateManager", modID, unm)
+ }
+ certManagers = append(certManagers, certManager)
+
case "dns":
if !h.NextArg() {
return nil, h.ArgErr()
@@ -453,6 +471,12 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
Value: true,
})
}
+ for _, certManager := range certManagers {
+ configVals = append(configVals, ConfigValue{
+ Class: "tls.cert_manager",
+ Value: certManager,
+ })
+ }
// custom certificate selection
if len(certSelector.AnyTag) > 0 {
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index 3a54c08..d7716a4 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -446,13 +446,14 @@ func (st *ServerType) serversFromPairings(
// handle the auto_https global option
if autoHTTPS != "on" {
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
- if autoHTTPS == "off" {
+ switch autoHTTPS {
+ case "off":
srv.AutoHTTPS.Disabled = true
- }
- if autoHTTPS == "disable_redirects" {
+ case "disable_redirects":
srv.AutoHTTPS.DisableRedir = true
- }
- if autoHTTPS == "ignore_loaded_certs" {
+ case "disable_certs":
+ srv.AutoHTTPS.DisableCerts = true
+ case "ignore_loaded_certs":
srv.AutoHTTPS.IgnoreLoadedCerts = true
}
}
diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go
index 016d0df..65b0338 100644
--- a/caddyconfig/httpcaddyfile/options.go
+++ b/caddyconfig/httpcaddyfile/options.go
@@ -384,8 +384,8 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ interface{}) (interface{}, erro
if d.Next() {
return "", d.ArgErr()
}
- if val != "off" && val != "disable_redirects" && val != "ignore_loaded_certs" {
- return "", d.Errf("auto_https must be one of 'off', 'disable_redirects' or 'ignore_loaded_certs'")
+ if val != "off" && val != "disable_redirects" && val != "disable_certs" && val != "ignore_loaded_certs" {
+ return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'")
}
return val, nil
}
diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go
index b11addc..48506d8 100644
--- a/caddyconfig/httpcaddyfile/tlsapp.go
+++ b/caddyconfig/httpcaddyfile/tlsapp.go
@@ -133,6 +133,13 @@ func (st ServerType) buildTLSApp(
ap.Issuers = issuers
}
+ // certificate managers
+ if certManagerVals, ok := sblock.pile["tls.cert_manager"]; ok {
+ for _, certManager := range certManagerVals {
+ certGetterName := certManager.Value.(caddy.Module).CaddyModule().ID.Name()
+ ap.ManagersRaw = append(ap.ManagersRaw, caddyconfig.JSONModuleObject(certManager.Value, "via", certGetterName, &warnings))
+ }
+ }
// custom bind host
for _, cfgVal := range sblock.pile["bind"] {
for _, iss := range ap.Issuers {
diff --git a/go.mod b/go.mod
index 05b6b87..7cbbd2f 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,14 @@ module github.com/caddyserver/caddy/v2
go 1.16
require (
+ cloud.google.com/go/kms v1.1.0 // indirect
+ github.com/BurntSushi/toml v0.4.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2
github.com/alecthomas/chroma v0.10.0
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
- github.com/caddyserver/certmagic v0.15.3
+ github.com/caddyserver/certmagic v0.15.4-0.20220217213750-797d29bcf32f
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
+ github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-chi/chi v4.1.2+incompatible
github.com/google/cel-go v0.9.0
github.com/google/uuid v1.3.0
@@ -22,14 +25,17 @@ require (
github.com/smallstep/cli v0.18.0
github.com/smallstep/nosql v0.3.9
github.com/smallstep/truststore v0.10.1
+ github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74
github.com/yuin/goldmark v1.4.4
github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01
go.uber.org/zap v1.20.0
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272
golang.org/x/net v0.0.0-20210913180222-943fd674d43e
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
- google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2
+ google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2
google.golang.org/protobuf v1.27.1
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v2 v2.4.0
+ howett.net/plist v1.0.0 // indirect
)
diff --git a/go.sum b/go.sum
index 14dcb18..9fb1f9f 100644
--- a/go.sum
+++ b/go.sum
@@ -25,8 +25,14 @@ cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPT
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
-cloud.google.com/go v0.83.0 h1:bAMqZidYkmIsUqe6PtkEPT7Q+vfizScn+jfNA6jwK9c=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -36,6 +42,8 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/kms v1.1.0 h1:1yc4rLqCkVDS9Zvc7m+3mJ47kw0Uo5Q5+sMjcmUVUeM=
+cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -84,8 +92,9 @@ github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcP
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
+github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
@@ -180,8 +189,8 @@ github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw=
-github.com/caddyserver/certmagic v0.15.3 h1:ScY3KVV1eMIUfW74i20kDnD4eWL8T0rG6S6Wnc6nc9U=
-github.com/caddyserver/certmagic v0.15.3/go.mod h1:qhkAOthf72ufAcp3Y5jF2RaGE96oip3UbEQRIzwe3/8=
+github.com/caddyserver/certmagic v0.15.4-0.20220217213750-797d29bcf32f h1:sdjilRh0dxXpofiwDSFU7itmxuXetKB6xWd+lNRVq9s=
+github.com/caddyserver/certmagic v0.15.4-0.20220217213750-797d29bcf32f/go.mod h1:qhkAOthf72ufAcp3Y5jF2RaGE96oip3UbEQRIzwe3/8=
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
@@ -294,8 +303,9 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fullstorydev/grpcurl v1.8.0/go.mod h1:Mn2jWbdMrQGJQ8UD62uNyMumT2acsZUCkZIqFxsQf1o=
github.com/fullstorydev/grpcurl v1.8.1/go.mod h1:3BWhvHZwNO7iLXaQlojdg5NA6SxUDePli4ecpK1N7gw=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -431,6 +441,8 @@ github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
@@ -449,8 +461,10 @@ github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/goreleaser/goreleaser v0.134.0/go.mod h1:ZT6Y2rSYa6NxQzIsdfWWNWAlYGXGbreo66NmE+3X3WQ=
@@ -643,6 +657,8 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
+github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@@ -897,6 +913,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74 h1:uFx5aih29p2IaRUF0lJwtVViCXStlvnPPE3NEmM4Ivs=
+github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74/go.mod h1:hL4gB6APAasMR2NNi/JHzqKkxW3EPQlFgLEq9PMi2t0=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
@@ -1169,8 +1187,11 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1266,8 +1287,14 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1361,6 +1388,7 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1400,8 +1428,15 @@ google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk
google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
-google.golang.org/api v0.48.0 h1:RDAPWfNFY06dffEXfn7hZF5Fr1ZbnChzfQZAPyBd1+I=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.58.0 h1:MDkAbYIB1JpSgCTOCYYoIec/coMlKK4oVbpnBLLcyT0=
+google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1473,9 +1508,23 @@ google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQ
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
-google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 h1:NHN4wOCScVzKhPenJ2dt+BTs3X/XkBVI/Rh4iDt55T8=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2 h1:CUp93KYgL06Y/PdI8aRJaFiAHevPIGWQmijSqaUhue8=
+google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
@@ -1508,6 +1557,7 @@ google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
@@ -1529,8 +1579,9 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -1550,6 +1601,7 @@ gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQb
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1573,8 +1625,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
+howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
+howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go
index 6c37d70..3e38d1b 100644
--- a/modules/caddyhttp/autohttps.go
+++ b/modules/caddyhttp/autohttps.go
@@ -31,13 +31,20 @@ import (
// HTTPS is enabled automatically and by default when
// qualifying hostnames are available from the config.
type AutoHTTPSConfig struct {
- // If true, automatic HTTPS will be entirely disabled.
+ // If true, automatic HTTPS will be entirely disabled,
+ // including certificate management and redirects.
Disabled bool `json:"disable,omitempty"`
// If true, only automatic HTTP->HTTPS redirects will
- // be disabled.
+ // be disabled, but other auto-HTTPS features will
+ // remain enabled.
DisableRedir bool `json:"disable_redirects,omitempty"`
+ // If true, automatic certificate management will be
+ // disabled, but other auto-HTTPS features will
+ // remain enabled.
+ DisableCerts bool `json:"disable_certificates,omitempty"`
+
// Hosts/domain names listed here will not be included
// in automatic HTTPS (they will not have certificates
// loaded nor redirects applied).
@@ -104,12 +111,13 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
srv.AutoHTTPS = new(AutoHTTPSConfig)
}
if srv.AutoHTTPS.Disabled {
+ app.logger.Warn("automatic HTTPS is completely disabled for server", zap.String("server_name", srvName))
continue
}
// skip if all listeners use the HTTP port
if !srv.listenersUseAnyPortOtherThan(app.httpPort()) {
- app.logger.Info("server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server",
+ app.logger.Warn("server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server",
zap.String("server_name", srvName),
zap.Int("http_port", app.httpPort()),
)
@@ -166,30 +174,35 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
// for all the hostnames we found, filter them so we have
// a deduplicated list of names for which to obtain certs
- for d := range serverDomainSet {
- if certmagic.SubjectQualifiesForCert(d) &&
- !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
- // if a certificate for this name is already loaded,
- // don't obtain another one for it, unless we are
- // supposed to ignore loaded certificates
- if !srv.AutoHTTPS.IgnoreLoadedCerts &&
- len(app.tlsApp.AllMatchingCertificates(d)) > 0 {
- app.logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
- zap.String("domain", d),
- zap.String("server_name", srvName),
- )
- continue
- }
+ // (only if cert management not disabled for this server)
+ if srv.AutoHTTPS.DisableCerts {
+ app.logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
+ } else {
+ for d := range serverDomainSet {
+ if certmagic.SubjectQualifiesForCert(d) &&
+ !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
+ // if a certificate for this name is already loaded,
+ // don't obtain another one for it, unless we are
+ // supposed to ignore loaded certificates
+ if !srv.AutoHTTPS.IgnoreLoadedCerts &&
+ len(app.tlsApp.AllMatchingCertificates(d)) > 0 {
+ app.logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
+ zap.String("domain", d),
+ zap.String("server_name", srvName),
+ )
+ continue
+ }
- // most clients don't accept wildcards like *.tld... we
- // can handle that, but as a courtesy, warn the user
- if strings.Contains(d, "*") &&
- strings.Count(strings.Trim(d, "."), ".") == 1 {
- app.logger.Warn("most clients do not trust second-level wildcard certificates (*.tld)",
- zap.String("domain", d))
- }
+ // most clients don't accept wildcards like *.tld... we
+ // can handle that, but as a courtesy, warn the user
+ if strings.Contains(d, "*") &&
+ strings.Count(strings.Trim(d, "."), ".") == 1 {
+ app.logger.Warn("most clients do not trust second-level wildcard certificates (*.tld)",
+ zap.String("domain", d))
+ }
- uniqueDomainsForCerts[d] = struct{}{}
+ uniqueDomainsForCerts[d] = struct{}{}
+ }
}
}
@@ -200,12 +213,11 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
// nothing left to do if auto redirects are disabled
if srv.AutoHTTPS.DisableRedir {
+ app.logger.Warn("automatic HTTP->HTTPS redirects are disabled", zap.String("server_name", srvName))
continue
}
- app.logger.Info("enabling automatic HTTP->HTTPS redirects",
- zap.String("server_name", srvName),
- )
+ app.logger.Info("enabling automatic HTTP->HTTPS redirects", zap.String("server_name", srvName))
// create HTTP->HTTPS redirects
for _, addr := range srv.Listen {
@@ -459,6 +471,16 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
}
}
+ // if no external managers were configured, enable
+ // implicit Tailscale support for convenience
+ if ap.Managers == nil {
+ ts, err := implicitTailscale(ctx)
+ if err != nil {
+ return err
+ }
+ ap.Managers = []certmagic.CertificateManager{ts}
+ }
+
// while we're here, is this the catch-all/base policy?
if !foundBasePolicy && len(ap.Subjects) == 0 {
basePolicy = ap
@@ -467,8 +489,14 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
}
if basePolicy == nil {
- // no base policy found, we will make one!
- basePolicy = new(caddytls.AutomationPolicy)
+ // no base policy found, we will make one! (with implicit Tailscale integration)
+ ts, err := implicitTailscale(ctx)
+ if err != nil {
+ return err
+ }
+ basePolicy = &caddytls.AutomationPolicy{
+ Managers: []certmagic.CertificateManager{ts},
+ }
}
// if the basePolicy has an existing ACMEIssuer (particularly to
@@ -482,8 +510,8 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
}
}
if baseACMEIssuer == nil {
- // note that this happens if basePolicy.Issuer is nil
- // OR if it is not nil but is not an ACMEIssuer
+ // note that this happens if basePolicy.Issuers is empty
+ // OR if it is not empty but does not have not an ACMEIssuer
baseACMEIssuer = new(caddytls.ACMEIssuer)
}
@@ -653,4 +681,11 @@ func (app *App) automaticHTTPSPhase2() error {
return nil
}
+// implicitTailscale returns a new and provisioned Tailscale module configured to be optional.
+func implicitTailscale(ctx caddy.Context) (caddytls.Tailscale, error) {
+ ts := caddytls.Tailscale{Optional: true}
+ err := ts.Provision(ctx)
+ return ts, err
+}
+
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
diff --git a/modules/caddypki/ca.go b/modules/caddypki/ca.go
index e3102fb..7fefee6 100644
--- a/modules/caddypki/ca.go
+++ b/modules/caddypki/ca.go
@@ -239,7 +239,7 @@ func (ca CA) loadOrGenRoot() (rootCert *x509.Certificate, rootKey interface{}, e
if err != nil {
return nil, nil, fmt.Errorf("loading root key: %v", err)
}
- rootKey, err = pemDecodePrivateKey(rootKeyPEM)
+ rootKey, err = certmagic.PEMDecodePrivateKey(rootKeyPEM)
if err != nil {
return nil, nil, fmt.Errorf("decoding root key: %v", err)
}
@@ -263,7 +263,7 @@ func (ca CA) genRoot() (rootCert *x509.Certificate, rootKey interface{}, err err
if err != nil {
return nil, nil, fmt.Errorf("saving root certificate: %v", err)
}
- rootKeyPEM, err := pemEncodePrivateKey(rootKey)
+ rootKeyPEM, err := certmagic.PEMEncodePrivateKey(rootKey)
if err != nil {
return nil, nil, fmt.Errorf("encoding root key: %v", err)
}
@@ -275,7 +275,7 @@ func (ca CA) genRoot() (rootCert *x509.Certificate, rootKey interface{}, err err
return rootCert, rootKey, nil
}
-func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey interface{}) (interCert *x509.Certificate, interKey interface{}, err error) {
+func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.PrivateKey) (interCert *x509.Certificate, interKey crypto.PrivateKey, err error) {
interCertPEM, err := ca.storage.Load(ca.storageKeyIntermediateCert())
if err != nil {
if _, ok := err.(certmagic.ErrNotExist); !ok {
@@ -301,7 +301,7 @@ func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey interface
if err != nil {
return nil, nil, fmt.Errorf("loading intermediate key: %v", err)
}
- interKey, err = pemDecodePrivateKey(interKeyPEM)
+ interKey, err = certmagic.PEMDecodePrivateKey(interKeyPEM)
if err != nil {
return nil, nil, fmt.Errorf("decoding intermediate key: %v", err)
}
@@ -310,7 +310,7 @@ func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey interface
return interCert, interKey, nil
}
-func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey interface{}) (interCert *x509.Certificate, interKey interface{}, err error) {
+func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey crypto.PrivateKey) (interCert *x509.Certificate, interKey crypto.PrivateKey, err error) {
repl := ca.newReplacer()
interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey)
@@ -325,7 +325,7 @@ func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey interface{}) (i
if err != nil {
return nil, nil, fmt.Errorf("saving intermediate certificate: %v", err)
}
- interKeyPEM, err := pemEncodePrivateKey(interKey)
+ interKeyPEM, err := certmagic.PEMEncodePrivateKey(interKey)
if err != nil {
return nil, nil, fmt.Errorf("encoding intermediate key: %v", err)
}
diff --git a/modules/caddypki/certificates.go b/modules/caddypki/certificates.go
index a55c165..bd260da 100644
--- a/modules/caddypki/certificates.go
+++ b/modules/caddypki/certificates.go
@@ -15,6 +15,7 @@
package caddypki
import (
+ "crypto"
"crypto/x509"
"time"
@@ -30,7 +31,7 @@ func generateRoot(commonName string) (rootCrt *x509.Certificate, privateKey inte
return newCert(rootProfile)
}
-func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey interface{}) (cert *x509.Certificate, privateKey interface{}, err error) {
+func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey crypto.PrivateKey) (cert *x509.Certificate, privateKey crypto.PrivateKey, err error) {
interProfile, err := x509util.NewIntermediateProfile(commonName, rootCrt, rootKey)
if err != nil {
return
@@ -39,7 +40,7 @@ func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey
return newCert(interProfile)
}
-func newCert(profile x509util.Profile) (cert *x509.Certificate, privateKey interface{}, err error) {
+func newCert(profile x509util.Profile) (cert *x509.Certificate, privateKey crypto.PrivateKey, err error) {
certBytes, err := profile.CreateCertificate()
if err != nil {
return
diff --git a/modules/caddypki/crypto.go b/modules/caddypki/crypto.go
index dbc6f38..386ce62 100644
--- a/modules/caddypki/crypto.go
+++ b/modules/caddypki/crypto.go
@@ -17,14 +17,12 @@ package caddypki
import (
"bytes"
"crypto"
- "crypto/ecdsa"
- "crypto/ed25519"
- "crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
- "strings"
+
+ "github.com/caddyserver/certmagic"
)
func pemDecodeSingleCert(pemDER []byte) (*x509.Certificate, error) {
@@ -45,70 +43,6 @@ func pemEncodeCert(der []byte) ([]byte, error) {
return pemEncode("CERTIFICATE", der)
}
-// pemEncodePrivateKey marshals a EC or RSA private key into a PEM-encoded array of bytes.
-// TODO: this is the same thing as in certmagic. Should we reuse that code somehow? It's unexported.
-func pemEncodePrivateKey(key crypto.PrivateKey) ([]byte, error) {
- var pemType string
- var keyBytes []byte
- switch key := key.(type) {
- case *ecdsa.PrivateKey:
- var err error
- pemType = "EC"
- keyBytes, err = x509.MarshalECPrivateKey(key)
- if err != nil {
- return nil, err
- }
- case *rsa.PrivateKey:
- pemType = "RSA"
- keyBytes = x509.MarshalPKCS1PrivateKey(key)
- case *ed25519.PrivateKey:
- var err error
- pemType = "ED25519"
- keyBytes, err = x509.MarshalPKCS8PrivateKey(key)
- if err != nil {
- return nil, err
- }
- default:
- return nil, fmt.Errorf("unsupported key type: %T", key)
- }
- return pemEncode(pemType+" PRIVATE KEY", keyBytes)
-}
-
-// pemDecodePrivateKey loads a PEM-encoded ECC/RSA private key from an array of bytes.
-// Borrowed from Go standard library, to handle various private key and PEM block types.
-// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
-// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238)
-// TODO: this is the same thing as in certmagic. Should we reuse that code somehow? It's unexported.
-func pemDecodePrivateKey(keyPEMBytes []byte) (crypto.PrivateKey, error) {
- keyBlockDER, _ := pem.Decode(keyPEMBytes)
- if keyBlockDER == nil {
- return nil, fmt.Errorf("no PEM data found")
- }
-
- if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
- return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
- }
-
- if key, err := x509.ParsePKCS1PrivateKey(keyBlockDER.Bytes); err == nil {
- return key, nil
- }
-
- if key, err := x509.ParsePKCS8PrivateKey(keyBlockDER.Bytes); err == nil {
- switch key := key.(type) {
- case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
- return key, nil
- default:
- return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping: %T", key)
- }
- }
-
- if key, err := x509.ParseECPrivateKey(keyBlockDER.Bytes); err == nil {
- return key, nil
- }
-
- return nil, fmt.Errorf("unknown private key type")
-}
-
func pemEncode(blockType string, b []byte) ([]byte, error) {
var buf bytes.Buffer
err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: b})
@@ -137,7 +71,7 @@ type KeyPair struct {
}
// Load loads the certificate and key.
-func (kp KeyPair) Load() (*x509.Certificate, interface{}, error) {
+func (kp KeyPair) Load() (*x509.Certificate, crypto.Signer, error) {
switch kp.Format {
case "", "pem_file":
certData, err := os.ReadFile(kp.Certificate)
@@ -153,7 +87,7 @@ func (kp KeyPair) Load() (*x509.Certificate, interface{}, error) {
if err != nil {
return nil, nil, err
}
- key, err := pemDecodePrivateKey(keyData)
+ key, err := certmagic.PEMDecodePrivateKey(keyData)
if err != nil {
return nil, nil, err
}
diff --git a/modules/caddytls/automation.go b/modules/caddytls/automation.go
index 2a701bf..95b1772 100644
--- a/modules/caddytls/automation.go
+++ b/modules/caddytls/automation.go
@@ -89,6 +89,14 @@ type AutomationPolicy struct {
// zerossl.
IssuersRaw []json.RawMessage `json:"issuers,omitempty" caddy:"namespace=tls.issuance inline_key=module"`
+ // Modules that can get a custom certificate to use for any
+ // given TLS handshake at handshake-time. Custom certificates
+ // can be useful if another entity is managing certificates
+ // and Caddy need only get it and serve it.
+ //
+ // TODO: This is an EXPERIMENTAL feature. It is subject to change or removal.
+ ManagersRaw []json.RawMessage `json:"get_certificate,omitempty" caddy:"namespace=tls.get_certificate inline_key=via"`
+
// If true, certificates will be requested with MustStaple. Not all
// CAs support this, and there are potentially serious consequences
// of enabling this feature without proper threat modeling.
@@ -113,7 +121,7 @@ type AutomationPolicy struct {
// If true, certificates will be managed "on demand"; that is, during
// TLS handshakes or when needed, as opposed to at startup or config
- // load.
+ // load. This enables On-Demand TLS for this policy.
OnDemand bool `json:"on_demand,omitempty"`
// Disables OCSP stapling. Disabling OCSP stapling puts clients at
@@ -129,10 +137,12 @@ type AutomationPolicy struct {
// EXPERIMENTAL. Subject to change.
OCSPOverrides map[string]string `json:"ocsp_overrides,omitempty"`
- // Issuers stores the decoded issuer parameters. This is only
- // used to populate an underlying certmagic.Config's Issuers
- // field; it is not referenced thereafter.
- Issuers []certmagic.Issuer `json:"-"`
+ // Issuers and Managers store the decoded issuer and manager modules;
+ // they are only used to populate an underlying certmagic.Config's
+ // fields during provisioning so that the modules can survive a
+ // re-provisioning.
+ Issuers []certmagic.Issuer `json:"-"`
+ Managers []certmagic.CertificateManager `json:"-"`
magic *certmagic.Config
storage certmagic.Storage
@@ -153,6 +163,7 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
ap.storage = cmStorage
}
+ // on-demand TLS
var ond *certmagic.OnDemandConfig
if ap.OnDemand {
ond = &certmagic.OnDemandConfig{
@@ -176,6 +187,22 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
}
}
+ // we don't store loaded modules directly in the certmagic config since
+ // policy provisioning may happen more than once (during auto-HTTPS) and
+ // loading a module clears its config bytes; thus, load the module and
+ // store them on the policy before putting it on the config
+
+ // load and provision any cert manager modules
+ if ap.ManagersRaw != nil {
+ vals, err := tlsApp.ctx.LoadModule(ap, "ManagersRaw")
+ if err != nil {
+ return fmt.Errorf("loading external certificate manager modules: %v", err)
+ }
+ for _, getCertVal := range vals.([]interface{}) {
+ ap.Managers = append(ap.Managers, getCertVal.(certmagic.CertificateManager))
+ }
+ }
+
// load and provision any explicitly-configured issuer modules
if ap.IssuersRaw != nil {
val, err := tlsApp.ctx.LoadModule(ap, "IssuersRaw")
@@ -225,9 +252,10 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
DisableStapling: ap.DisableOCSPStapling,
ResponderOverrides: ap.OCSPOverrides,
},
- Storage: storage,
- Issuers: issuers,
- Logger: tlsApp.logger,
+ Storage: storage,
+ Issuers: issuers,
+ Managers: ap.Managers,
+ Logger: tlsApp.logger,
}
ap.magic = certmagic.New(tlsApp.certCache, template)
diff --git a/modules/caddytls/certmanagers.go b/modules/caddytls/certmanagers.go
new file mode 100644
index 0000000..653e9f5
--- /dev/null
+++ b/modules/caddytls/certmanagers.go
@@ -0,0 +1,208 @@
+package caddytls
+
+import (
+ "context"
+ "crypto/tls"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+ "github.com/caddyserver/certmagic"
+ "github.com/tailscale/tscert"
+ "go.uber.org/zap"
+)
+
+func init() {
+ caddy.RegisterModule(Tailscale{})
+ caddy.RegisterModule(HTTPCertGetter{})
+}
+
+// Tailscale is a module that can get certificates from the local Tailscale process.
+type Tailscale struct {
+ // If true, this module will operate in "best-effort" mode and
+ // ignore "soft" errors; i.e. try Tailscale, and if it doesn't connect
+ // or return a certificate, oh well. Failure to connect to Tailscale
+ // results in a no-op instead of an error. Intended for the use case
+ // where this module is added implicitly for convenience, even if
+ // Tailscale isn't necessarily running.
+ Optional bool `json:"optional,omitempty"`
+
+ logger *zap.Logger
+}
+
+// CaddyModule returns the Caddy module information.
+func (Tailscale) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ ID: "tls.get_certificate.tailscale",
+ New: func() caddy.Module { return new(Tailscale) },
+ }
+}
+
+func (ts *Tailscale) Provision(ctx caddy.Context) error {
+ ts.logger = ctx.Logger(ts)
+ return nil
+}
+
+func (ts Tailscale) GetCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ canGetCert, err := ts.canHazCertificate(ctx, hello)
+ if err == nil && !canGetCert {
+ return nil, nil // pass-thru: Tailscale can't offer a cert for this name
+ }
+ if err != nil {
+ ts.logger.Warn("could not get status; will try to get certificate anyway", zap.Error(err))
+ }
+ return tscert.GetCertificate(hello)
+}
+
+// canHazCertificate returns true if Tailscale reports it can get a certificate for the given ClientHello.
+func (ts Tailscale) canHazCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (bool, error) {
+ if ts.Optional && !strings.HasSuffix(strings.ToLower(hello.ServerName), tailscaleDomainAliasEnding) {
+ return false, nil
+ }
+ status, err := tscert.GetStatus(ctx)
+ if err != nil {
+ if ts.Optional {
+ return false, nil // ignore error if we don't expect/require it to work anyway
+ }
+ return false, err
+ }
+ for _, domain := range status.CertDomains {
+ if certmagic.MatchWildcard(hello.ServerName, domain) {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+// UnmarshalCaddyfile deserializes Caddyfile tokens into ts.
+//
+// ... tailscale
+//
+func (Tailscale) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ }
+ return nil
+}
+
+// tailscaleDomainAliasEnding is the ending for all Tailscale custom domains.
+const tailscaleDomainAliasEnding = ".ts.net"
+
+// HTTPCertGetter can get a certificate via HTTP(S) request.
+type HTTPCertGetter struct {
+ // The URL from which to download the certificate. Required.
+ //
+ // The URL will be augmented with query string parameters taken
+ // from the TLS handshake:
+ //
+ // - server_name: The SNI value
+ // - signature_schemes: Comma-separated list of hex IDs of signatures
+ // - cipher_suites: Comma-separated list of hex IDs of cipher suites
+ //
+ // To be valid, the response must be HTTP 200 with a PEM body
+ // consisting of blocks for the certificate chain and the private
+ // key.
+ URL string `json:"url,omitempty"`
+
+ ctx context.Context
+}
+
+// CaddyModule returns the Caddy module information.
+func (hcg HTTPCertGetter) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ ID: "tls.get_certificate.http",
+ New: func() caddy.Module { return new(HTTPCertGetter) },
+ }
+}
+
+func (hcg *HTTPCertGetter) Provision(ctx caddy.Context) error {
+ hcg.ctx = ctx
+ if hcg.URL == "" {
+ return fmt.Errorf("URL is required")
+ }
+ return nil
+}
+
+func (hcg HTTPCertGetter) GetCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ sigs := make([]string, len(hello.SignatureSchemes))
+ for i, sig := range hello.SignatureSchemes {
+ sigs[i] = fmt.Sprintf("%x", uint16(sig)) // you won't believe what %x uses if the val is a Stringer
+ }
+ suites := make([]string, len(hello.CipherSuites))
+ for i, cs := range hello.CipherSuites {
+ suites[i] = fmt.Sprintf("%x", cs)
+ }
+
+ parsed, err := url.Parse(hcg.URL)
+ if err != nil {
+ return nil, err
+ }
+ qs := parsed.Query()
+ qs.Set("server_name", hello.ServerName)
+ qs.Set("signature_schemes", strings.Join(sigs, ","))
+ qs.Set("cipher_suites", strings.Join(suites, ","))
+ parsed.RawQuery = qs.Encode()
+
+ req, err := http.NewRequestWithContext(hcg.ctx, http.MethodGet, parsed.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("got HTTP %d", resp.StatusCode)
+ }
+
+ bodyBytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("error reading response body: %v", err)
+ }
+
+ cert, err := tlsCertFromCertAndKeyPEMBundle(bodyBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ return &cert, nil
+}
+
+// UnmarshalCaddyfile deserializes Caddyfile tokens into ts.
+//
+// ... http <url>
+//
+func (hcg *HTTPCertGetter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ hcg.URL = d.Val()
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ return d.Err("block not allowed here")
+ }
+ }
+ return nil
+}
+
+// Interface guards
+var (
+ _ certmagic.CertificateManager = (*Tailscale)(nil)
+ _ caddy.Provisioner = (*Tailscale)(nil)
+ _ caddyfile.Unmarshaler = (*Tailscale)(nil)
+
+ _ certmagic.CertificateManager = (*HTTPCertGetter)(nil)
+ _ caddy.Provisioner = (*HTTPCertGetter)(nil)
+ _ caddyfile.Unmarshaler = (*HTTPCertGetter)(nil)
+)
diff --git a/modules/caddytls/folderloader.go b/modules/caddytls/folderloader.go
index 0ff0629..33b31a5 100644
--- a/modules/caddytls/folderloader.go
+++ b/modules/caddytls/folderloader.go
@@ -61,10 +61,14 @@ func (fl FolderLoader) LoadCertificates() ([]Certificate, error) {
return nil
}
- cert, err := x509CertFromCertAndKeyPEMFile(fpath)
+ bundle, err := os.ReadFile(fpath)
if err != nil {
return err
}
+ cert, err := tlsCertFromCertAndKeyPEMBundle(bundle)
+ if err != nil {
+ return fmt.Errorf("%s: %w", fpath, err)
+ }
certs = append(certs, Certificate{Certificate: cert})
@@ -77,12 +81,7 @@ func (fl FolderLoader) LoadCertificates() ([]Certificate, error) {
return certs, nil
}
-func x509CertFromCertAndKeyPEMFile(fpath string) (tls.Certificate, error) {
- bundle, err := os.ReadFile(fpath)
- if err != nil {
- return tls.Certificate{}, err
- }
-
+func tlsCertFromCertAndKeyPEMBundle(bundle []byte) (tls.Certificate, error) {
certBuilder, keyBuilder := new(bytes.Buffer), new(bytes.Buffer)
var foundKey bool // use only the first key in the file
@@ -96,8 +95,7 @@ func x509CertFromCertAndKeyPEMFile(fpath string) (tls.Certificate, error) {
if derBlock.Type == "CERTIFICATE" {
// Re-encode certificate as PEM, appending to certificate chain
- err = pem.Encode(certBuilder, derBlock)
- if err != nil {
+ if err := pem.Encode(certBuilder, derBlock); err != nil {
return tls.Certificate{}, err
}
} else if derBlock.Type == "EC PARAMETERS" {
@@ -105,18 +103,16 @@ func x509CertFromCertAndKeyPEMFile(fpath string) (tls.Certificate, error) {
// parameters and key (parameter block should come first)
if !foundKey {
// Encode parameters
- err = pem.Encode(keyBuilder, derBlock)
- if err != nil {
+ if err := pem.Encode(keyBuilder, derBlock); err != nil {
return tls.Certificate{}, err
}
// Key must immediately follow
derBlock, bundle = pem.Decode(bundle)
if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" {
- return tls.Certificate{}, fmt.Errorf("%s: expected elliptic private key to immediately follow EC parameters", fpath)
+ return tls.Certificate{}, fmt.Errorf("expected elliptic private key to immediately follow EC parameters")
}
- err = pem.Encode(keyBuilder, derBlock)
- if err != nil {
+ if err := pem.Encode(keyBuilder, derBlock); err != nil {
return tls.Certificate{}, err
}
foundKey = true
@@ -124,28 +120,27 @@ func x509CertFromCertAndKeyPEMFile(fpath string) (tls.Certificate, error) {
} else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") {
// RSA key
if !foundKey {
- err = pem.Encode(keyBuilder, derBlock)
- if err != nil {
+ if err := pem.Encode(keyBuilder, derBlock); err != nil {
return tls.Certificate{}, err
}
foundKey = true
}
} else {
- return tls.Certificate{}, fmt.Errorf("%s: unrecognized PEM block type: %s", fpath, derBlock.Type)
+ return tls.Certificate{}, fmt.Errorf("unrecognized PEM block type: %s", derBlock.Type)
}
}
certPEMBytes, keyPEMBytes := certBuilder.Bytes(), keyBuilder.Bytes()
if len(certPEMBytes) == 0 {
- return tls.Certificate{}, fmt.Errorf("%s: failed to parse PEM data", fpath)
+ return tls.Certificate{}, fmt.Errorf("failed to parse PEM data")
}
if len(keyPEMBytes) == 0 {
- return tls.Certificate{}, fmt.Errorf("%s: no private key block found", fpath)
+ return tls.Certificate{}, fmt.Errorf("no private key block found")
}
cert, err := tls.X509KeyPair(certPEMBytes, keyPEMBytes)
if err != nil {
- return tls.Certificate{}, fmt.Errorf("%s: making X509 key pair: %v", fpath, err)
+ return tls.Certificate{}, fmt.Errorf("making X509 key pair: %v", err)
}
return cert, nil