mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
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:
parent
32aad90938
commit
57a708d189
13 changed files with 450 additions and 158 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
10
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
|
||||
)
|
||||
|
|
75
go.sum
75
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=
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
208
modules/caddytls/certmanagers.go
Normal file
208
modules/caddytls/certmanagers.go
Normal 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)
|
||||
)
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue