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>
This commit is contained in:
Matt Holt 2022-02-17 15:40:34 -07:00 committed by GitHub
parent 32aad90938
commit 57a708d189
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 450 additions and 158 deletions

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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 {

10
go.mod
View File

@ -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
)

75
go.sum
View File

@ -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=

View File

@ -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 }

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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)
)

View File

@ -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