Update server dependencies
This commit is contained in:
parent
c704ebb042
commit
1794e2680a
39
go.mod
39
go.mod
@ -5,6 +5,8 @@ go 1.14
|
||||
require (
|
||||
github.com/RoaringBitmap/roaring v0.4.23 // indirect
|
||||
github.com/blevesearch/bleve v1.0.7
|
||||
github.com/caddyserver/certmagic v0.10.12
|
||||
github.com/cenkalti/backoff/v4 v4.0.2 // indirect
|
||||
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect
|
||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
|
||||
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect
|
||||
@ -12,38 +14,37 @@ require (
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect
|
||||
github.com/go-acme/lego v2.6.0+incompatible // indirect
|
||||
github.com/go-acme/lego/v3 v3.6.0 // indirect
|
||||
github.com/golang/protobuf v1.4.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/jmhodges/levigo v1.0.0 // indirect
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/kjk/betterguid v0.0.0-20170621091430-c442874ba63a
|
||||
github.com/klauspost/cpuid v1.2.1
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983
|
||||
github.com/mholt/certmagic v0.5.1
|
||||
github.com/miekg/dns v1.1.13 // indirect
|
||||
github.com/klauspost/cpuid v1.2.3
|
||||
github.com/mailru/easyjson v0.7.1
|
||||
github.com/miekg/dns v1.1.29 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.3.0 // indirect
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml v1.4.0 // indirect
|
||||
github.com/pelletier/go-toml v1.7.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190512091148-babf20351dd7 // indirect
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/tdewolff/minify/v2 v2.5.0
|
||||
github.com/spf13/viper v1.6.3
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tdewolff/minify/v2 v2.7.4
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
|
||||
github.com/tinylib/msgp v1.1.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 // indirect
|
||||
golang.org/x/net v0.0.0-20190607181551-461777fb6f67
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc // indirect
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0
|
||||
golang.org/x/sys v0.0.0-20200428200454-593003d681fa // indirect
|
||||
gopkg.in/ini.v1 v1.55.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.0 // indirect
|
||||
)
|
||||
|
456
go.sum
456
go.sum
@ -1,15 +1,64 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
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=
|
||||
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/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=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
|
||||
github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
|
||||
github.com/RoaringBitmap/roaring v0.4.21/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||
github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo=
|
||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.0/go.mod h1:zpDJeKyp9ScW4NNrbdr+Eyxvry3ilGPewKoXw3XGN1k=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8/go.mod h1:aVvklgKsPENRkl29bNwrHISa1F+YLGTHArMxZMBqWM8=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.112/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/blevesearch/bleve v1.0.7 h1:4PspZE7XABMSKcVpzAKp0E05Yer1PIYmTWk+1ngNr/c=
|
||||
github.com/blevesearch/bleve v1.0.7/go.mod h1:3xvmBtaw12Y4C9iA1RTzwWCof5j5HjydjCTiDE2TeE0=
|
||||
github.com/blevesearch/blevex v0.0.0-20190916190636-152f0fe5c040 h1:SjYVcfJVZoCfBlg+fkaq2eoZHTf5HaJfaTeTkOtyfHQ=
|
||||
@ -26,13 +75,24 @@ github.com/blevesearch/zap/v11 v11.0.7 h1:nnmAOP6eXBkqEa1Srq1eqA5Wmn4w+BZjLdjynN
|
||||
github.com/blevesearch/zap/v11 v11.0.7/go.mod h1:bJoY56fdU2m/IP4LLz/1h4jY2thBoREvoqbuJ8zhm9k=
|
||||
github.com/blevesearch/zap/v12 v12.0.7 h1:y8FWSAYkdc4p1dn4YLxNNr1dxXlSUsakJh2Fc/r6cj4=
|
||||
github.com/blevesearch/zap/v12 v12.0.7/go.mod h1:70DNK4ZN4tb42LubeDbfpp6xnm8g3ROYVvvZ6pEoXD8=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/caddyserver/certmagic v0.10.12 h1:aZtgzcIssiMSlP0jDdpDBbBzQ5INf5eKL9T6Nf3YzKM=
|
||||
github.com/caddyserver/certmagic v0.10.12/go.mod h1:Y8jcUBctgk/IhpAzlHKfimZNyXCkfGgRTC0orl8gROQ=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs=
|
||||
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
@ -43,7 +103,10 @@ github.com/couchbase/moss v0.1.0 h1:HCL+xxHUwmOaL44kMM/gU08OW6QGCui1WVFO58bjhNI=
|
||||
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
|
||||
github.com/couchbase/vellum v1.0.1 h1:qrj9ohvZedvc51S5KzPfJ6P6z0Vqzv7Lx7k3mVc2WOk=
|
||||
github.com/couchbase/vellum v1.0.1/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
|
||||
github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok=
|
||||
github.com/cpu/goacmedns v0.0.2/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8=
|
||||
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
|
||||
@ -56,18 +119,30 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
@ -75,23 +150,42 @@ github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a h1:FQqo
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8=
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||
github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
|
||||
github.com/go-acme/lego v2.6.0+incompatible h1:KxcEWOF5hKtgou4xIqPaXSRF9DoO4OJ90ndwdK6YH/k=
|
||||
github.com/go-acme/lego v2.6.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
|
||||
github.com/go-acme/lego/v3 v3.4.0 h1:deB9NkelA+TfjGHVw8J7iKl/rMtffcGMWSMmptvMv0A=
|
||||
github.com/go-acme/lego/v3 v3.4.0/go.mod h1:xYbLDuxq3Hy4bMUT1t9JIuz6GWIWb3m5X+TeTHYaT7M=
|
||||
github.com/go-acme/lego/v3 v3.6.0 h1:Rv0MrX3DpVp9Xg77yR7x+PCksLLph3Ut/69/9Kim8ac=
|
||||
github.com/go-acme/lego/v3 v3.6.0/go.mod h1:sB/T7hfyz0HYIBvPmz/C8jIaxF6scbbiGKTzbQ22V6A=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
@ -102,30 +196,64 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
|
||||
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
@ -136,41 +264,65 @@ github.com/kjk/betterguid v0.0.0-20170621091430-c442874ba63a/go.mod h1:uxRAhHE1n
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/kljensen/snowball v0.6.0 h1:6DZLCcZeL0cLfodx+Md4/OLC6b/bfurWUOUGs1ydfOU=
|
||||
github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
|
||||
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
|
||||
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
|
||||
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
||||
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 h1:wL11wNW7dhKIcRCHSm4sHKPWz0tt4mwBsVodG7+Xyqg=
|
||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8=
|
||||
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/certmagic v0.5.1 h1:8Pf6Hwwlh5sbT3nwn3ovXyXWxHCEM54wvfLzTrQ+UiM=
|
||||
github.com/mholt/certmagic v0.5.1/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY=
|
||||
github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM=
|
||||
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.13 h1:x7DQtkU0cedzeS8TD36tT/w1Hm4rDtfCaYYAHE7TTBI=
|
||||
github.com/miekg/dns v1.1.13/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI=
|
||||
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.0 h1:iDwIio/3gk2QtLLEsqU5lInaMzos0hDTz8a6lazSFVw=
|
||||
github.com/mitchellh/mapstructure v1.3.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nrdcg/auroradns v1.0.0/go.mod h1:6JPXKzIRzZzMqtTDgueIhTi6rFf1QvYE/HzqidhOhjw=
|
||||
github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
@ -181,32 +333,60 @@ github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
|
||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190512091148-babf20351dd7 h1:FUL3b97ZY2EPqg2NbXKuMHs5pXJB9hjj1fDHnF2vl28=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190512091148-babf20351dd7/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
@ -215,6 +395,8 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
@ -230,6 +412,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
|
||||
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
|
||||
github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM=
|
||||
github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -240,93 +424,276 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tdewolff/minify/v2 v2.5.0 h1:OWPdsMnomzKoL5tzgW3HK3t1zVxsHF6SlGxJjUnoMdw=
|
||||
github.com/tdewolff/minify/v2 v2.5.0/go.mod h1:ZLQDMgUYkaNxM5YLYnp151Lb6Ff+rAxNIzLxEkrKImQ=
|
||||
github.com/tdewolff/parse/v2 v2.3.7 h1:DXoTUgrUE2Eap0m7zg1ljCO5C78vhEi7HTc4YnJWrRk=
|
||||
github.com/tdewolff/parse/v2 v2.3.7/go.mod h1:HansaqmN4I/U7L6/tUp0NcwT2tFO0F4EAWYGSDzkYNk=
|
||||
github.com/tdewolff/test v1.0.0 h1:jOwzqCXr5ePXEPGJaq2ivoR6HOCi+D5TPfpoyg8yvmU=
|
||||
github.com/tdewolff/test v1.0.0/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4=
|
||||
github.com/tdewolff/minify/v2 v2.7.4 h1:r0OZQ3QzWeDS5cXq53Bk4IFIBDZ7fiXIkw1a4bHONsw=
|
||||
github.com/tdewolff/minify/v2 v2.7.4/go.mod h1:BkDSm8aMMT0ALGmpt7j3Ra7nLUgZL0qhyrAHXwxcy5w=
|
||||
github.com/tdewolff/parse/v2 v2.4.2 h1:Bu2Qv6wepkc+Ou7iB/qHjAhEImlAP5vedzlQRUdj3BI=
|
||||
github.com/tdewolff/parse/v2 v2.4.2/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok=
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
||||
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
|
||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
|
||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY=
|
||||
github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
|
||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
|
||||
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
|
||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc h1:ZGI/fILM2+ueot/UixBSoj9188jCAxVHEZEGhqq67I4=
|
||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00=
|
||||
golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a h1:Yu34BogBivvmu7SAzHHaB9nZWH5D1C+z3F1jyIaYZSQ=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200428200454-593003d681fa h1:yMbJOvnfYkO1dSAviTu/ZguZWLBTXx4xE3LYrxUCCiA=
|
||||
golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -338,13 +705,24 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
|
||||
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
|
||||
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.0 h1:OZ4sdq+Y+SHfYB7vfthi1Ei8b0vkP8ZPQgUfUwdUSqo=
|
||||
gopkg.in/square/go-jose.v2 v2.5.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
@ -352,4 +730,16 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
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=
|
||||
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=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
@ -2,14 +2,15 @@ package https
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/khlieng/dispatch/pkg/netutil"
|
||||
"github.com/klauspost/cpuid"
|
||||
"github.com/mholt/certmagic"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@ -65,24 +66,29 @@ func Serve(handler http.Handler, cfg Config) error {
|
||||
}
|
||||
}
|
||||
|
||||
certmagic.Default.Agreed = true
|
||||
certmagic.Default.Email = cfg.Email
|
||||
certmagic.Default.MustStaple = true
|
||||
|
||||
magic := certmagic.NewDefault()
|
||||
|
||||
acme := certmagic.NewACMEManager(magic, certmagic.ACMEManager{
|
||||
Agreed: true,
|
||||
Email: cfg.Email,
|
||||
})
|
||||
|
||||
magic.Issuer = acme
|
||||
|
||||
domains := []string{cfg.Domain}
|
||||
if cfg.Domain == "" {
|
||||
domains = []string{}
|
||||
magic.OnDemand = &certmagic.OnDemandConfig{MaxObtain: 3}
|
||||
magic.OnDemand = maxObtain(3)
|
||||
}
|
||||
|
||||
err := magic.Manage(domains)
|
||||
err := magic.ManageSync(domains)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpSrv.Handler = magic.HTTPChallengeHandler(redirect)
|
||||
httpSrv.Handler = acme.HTTPChallengeHandler(redirect)
|
||||
httpsSrv.TLSConfig = TLSConfig(magic.TLSConfig())
|
||||
|
||||
go func() {
|
||||
@ -167,3 +173,24 @@ func defaultCipherSuites() []uint16 {
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}
|
||||
}
|
||||
|
||||
func maxObtain(limit int) *certmagic.OnDemandConfig {
|
||||
requested := []string{}
|
||||
|
||||
return &certmagic.OnDemandConfig{
|
||||
DecisionFunc: func(name string) error {
|
||||
for _, n := range requested {
|
||||
if name == n {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(requested) == limit {
|
||||
return fmt.Errorf("OnDemand cert limit reached")
|
||||
}
|
||||
|
||||
requested = append(requested, name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
<p align="center">
|
||||
<a href="https://godoc.org/github.com/mholt/certmagic"><img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="550"></a>
|
||||
<a href="https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc"><img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="550"></a>
|
||||
</p>
|
||||
<h3 align="center">Easy and Powerful TLS Automation</h3>
|
||||
<p align="center">The same library used by the <a href="https://caddyserver.com">Caddy Web Server</a></p>
|
||||
<p align="center">
|
||||
<a href="https://godoc.org/github.com/mholt/certmagic"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"></a>
|
||||
<a href="https://travis-ci.org/mholt/certmagic"><img src="https://img.shields.io/travis/mholt/certmagic.svg?label=linux+build"></a>
|
||||
<a href="https://ci.appveyor.com/project/mholt/certmagic"><img src="https://img.shields.io/appveyor/ci/mholt/certmagic.svg?label=windows+build"></a>
|
||||
<!--<a href="https://sourcegraph.com/github.com/mholt/certmagic?badge" title="certmagic on Sourcegraph"><img src="https://sourcegraph.com/github.com/mholt/certmagic/-/badge.svg" alt="certmagic on Sourcegraph"></a>-->
|
||||
<a href="https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"></a>
|
||||
<a href="https://dev.azure.com/mholt-dev/CertMagic/_build"><img src="https://img.shields.io/azure-devops/build/mholt-dev/3511431f-630c-43ac-833f-be949b4f4ee7/3.svg?label=cross-platform%20tests"></a>
|
||||
<a href="https://sourcegraph.com/github.com/caddyserver/certmagic?badge"><img src="https://sourcegraph.com/github.com/caddyserver/certmagic/-/badge.svg"></a>
|
||||
</p>
|
||||
|
||||
|
||||
Caddy's automagic TLS features, now for your own Go programs, in one powerful and easy-to-use library!
|
||||
Caddy's automagic TLS features—now for your own Go programs—in one powerful and easy-to-use library!
|
||||
|
||||
CertMagic is the most mature, robust, and capable ACME client integration for Go.
|
||||
CertMagic is the most mature, robust, and capable ACME client integration for Go... and perhaps ever.
|
||||
|
||||
With CertMagic, you can add one line to your Go application to serve securely over TLS, without ever having to touch certificates.
|
||||
|
||||
@ -57,6 +56,7 @@ CertMagic - Automatic HTTPS using Let's Encrypt
|
||||
- [The `Config` type](#the-config-type)
|
||||
- [Defaults](#defaults)
|
||||
- [Providing an email address](#providing-an-email-address)
|
||||
- [Rate limiting](#rate-limiting)
|
||||
- [Development and testing](#development-and-testing)
|
||||
- [Examples](#examples)
|
||||
- [Serving HTTP handlers with HTTPS](#serving-http-handlers-with-https)
|
||||
@ -82,24 +82,34 @@ CertMagic - Automatic HTTPS using Let's Encrypt
|
||||
- Fully automated certificate management including issuance and renewal
|
||||
- One-liner, fully managed HTTPS servers
|
||||
- Full control over almost every aspect of the system
|
||||
- HTTP->HTTPS redirects (for HTTP applications)
|
||||
- HTTP->HTTPS redirects
|
||||
- Solves all 3 ACME challenges: HTTP, TLS-ALPN, and DNS
|
||||
- Most robust error handling of _any_ ACME client
|
||||
- Challenges are randomized to avoid accidental dependence
|
||||
- Challenges are rotated to overcome certain network blockages
|
||||
- Robust retries for up to 30 days
|
||||
- Exponential backoff with carefully-tuned intervals
|
||||
- Retries with optional test/staging CA endpoint instead of production, to avoid rate limits
|
||||
- Over 50 DNS providers work out-of-the-box (powered by [lego](https://github.com/go-acme/lego)!)
|
||||
- Written in Go, a language with memory-safety guarantees
|
||||
- Pluggable storage implementations (default: file system)
|
||||
- Wildcard certificates (requires DNS challenge)
|
||||
- OCSP stapling for each qualifying certificate ([done right](https://gist.github.com/sleevi/5efe9ef98961ecfb4da8#gistcomment-2336055))
|
||||
- Wildcard certificates
|
||||
- Automatic OCSP stapling ([done right](https://gist.github.com/sleevi/5efe9ef98961ecfb4da8#gistcomment-2336055)) [keeps your sites online!](https://twitter.com/caddyserver/status/1234874273724084226)
|
||||
- Will [automatically attempt](https://twitter.com/mholt6/status/1235577699541762048) to replace [revoked certificates](https://community.letsencrypt.org/t/2020-02-29-caa-rechecking-bug/114591/3?u=mholt)!
|
||||
- Staples stored to disk in case of responder outages
|
||||
- Distributed solving of all challenges (works behind load balancers)
|
||||
- Highly efficient, coordinated management in a fleet
|
||||
- Active locking
|
||||
- Smart queueing
|
||||
- Supports "on-demand" issuance of certificates (during TLS handshakes!)
|
||||
- Custom decision functions
|
||||
- Hostname whitelist
|
||||
- Ask an external URL
|
||||
- Rate limiting
|
||||
- Optional event hooks to observe internal behaviors
|
||||
- Caddy / CertMagic pioneered this technology
|
||||
- Custom decision functions to regulate and throttle on-demand behavior
|
||||
- Optional event hooks for observation
|
||||
- Works with any certificate authority (CA) compliant with the ACME specification
|
||||
- Certificate revocation (please, only if private key is compromised)
|
||||
- Must-Staple (optional; not default)
|
||||
- Cross-platform support! Mac, Windows, Linux, BSD, Android...
|
||||
- Scales well to thousands of names/certificates per instance
|
||||
- Scales to hundreds of thousands of names/certificates per instance
|
||||
- Use in conjunction with your own certificates
|
||||
|
||||
|
||||
@ -122,7 +132,7 @@ CertMagic - Automatic HTTPS using Let's Encrypt
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/mholt/certmagic
|
||||
$ go get github.com/caddyserver/certmagic
|
||||
```
|
||||
|
||||
|
||||
@ -142,14 +152,25 @@ The `certmagic.Config` struct is how you can wield the power of this fully armed
|
||||
|
||||
The default `Config` value is called `certmagic.Default`. Change its fields to suit your needs, then call `certmagic.NewDefault()` when you need a valid `Config` value. In other words, `certmagic.Default` is a template and is not valid for use directly.
|
||||
|
||||
You can set the default values easily, for example: `certmagic.Default.Email = ...`.
|
||||
You can set the default values easily, for example: `certmagic.Default.Issuer = ...`.
|
||||
|
||||
The high-level functions in this package (`HTTPS()`, `Listen()`, and `Manage()`) use the default config exclusively. This is how most of you will interact with the package. This is suitable when all your certificates are managed the same way. However, if you need to manage certificates differently depending on their name, you will need to make your own cache and configs (keep reading).
|
||||
Similarly, to configure ACME-specific defaults, use `certmagic.DefaultACME`.
|
||||
|
||||
The high-level functions in this package (`HTTPS()`, `Listen()`, `ManageSync()`, and `ManageAsync()`) use the default config exclusively. This is how most of you will interact with the package. This is suitable when all your certificates are managed the same way. However, if you need to manage certificates differently depending on their name, you will need to make your own cache and configs (keep reading).
|
||||
|
||||
|
||||
#### Providing an email address
|
||||
|
||||
Although not strictly required, this is highly recommended best practice. It allows you to receive expiration emails if your certificates are expiring for some reason, and also allows the CA's engineers to potentially get in touch with you if something is wrong. I recommend setting `certmagic.Default.Email` or always setting the `Email` field of a new `Config` struct.
|
||||
Although not strictly required, this is highly recommended best practice. It allows you to receive expiration emails if your certificates are expiring for some reason, and also allows the CA's engineers to potentially get in touch with you if something is wrong. I recommend setting `certmagic.DefaultACME.Email` or always setting the `Email` field of a new `Config` struct.
|
||||
|
||||
|
||||
#### Rate limiting
|
||||
|
||||
To avoid firehosing the CA's servers, CertMagic has built-in rate limiting. Currently, its default limit is up to 10 transactions (obtain or renew) every 1 minute (sliding window). This can be changed by setting the `RateLimitEvents` and `RateLimitEventsWindow` variables, if desired.
|
||||
|
||||
The CA may still enforce their own rate limits, and there's nothing (well, nothing ethical) CertMagic can do to bypass them for you.
|
||||
|
||||
Additionally, CertMagic will retry failed validations with exponential backoff for up to 30 days, with a reasonable maximum interval between attempts (an "attempt" means trying each enabled challenge type once).
|
||||
|
||||
|
||||
### Development and Testing
|
||||
@ -158,7 +179,7 @@ Note that Let's Encrypt imposes [strict rate limits](https://letsencrypt.org/doc
|
||||
|
||||
While developing your application and testing it, use [their staging endpoint](https://letsencrypt.org/docs/staging-environment/) which has much higher rate limits. Even then, don't hammer it: but it's much safer for when you're testing. When deploying, though, use their production CA because their staging CA doesn't issue trusted certificates.
|
||||
|
||||
To use staging, set `certmagic.Default.CA = certmagic.LetsEncryptStagingCA` or set `CA` of every `Config` struct.
|
||||
To use staging, set `certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA` or set `CA` of every `ACMEManager` struct.
|
||||
|
||||
|
||||
|
||||
@ -166,19 +187,19 @@ To use staging, set `certmagic.Default.CA = certmagic.LetsEncryptStagingCA` or s
|
||||
|
||||
There are many ways to use this library. We'll start with the highest-level (simplest) and work down (more control).
|
||||
|
||||
All these high-level examples use `certmagic.Default` for the config and the default cache and storage for serving up certificates.
|
||||
All these high-level examples use `certmagic.Default` and `certmagic.DefaultACME` for the config and the default cache and storage for serving up certificates.
|
||||
|
||||
First, we'll follow best practices and do the following:
|
||||
|
||||
```go
|
||||
// read and agree to your CA's legal documents
|
||||
certmagic.Default.Agreed = true
|
||||
certmagic.DefaultACME.Agreed = true
|
||||
|
||||
// provide an email address
|
||||
certmagic.Default.Email = "you@yours.com"
|
||||
certmagic.DefaultACME.Email = "you@yours.com"
|
||||
|
||||
// use the staging endpoint while we're developing
|
||||
certmagic.Default.CA = certmagic.LetsEncryptStagingCA
|
||||
certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA
|
||||
```
|
||||
|
||||
For fully-functional program examples, check out [this Twitter thread](https://twitter.com/mholt6/status/1073103805112147968) (or read it [unrolled into a single post](https://threadreaderapp.com/thread/1073103805112147968.html)). (Note that the package API has changed slightly since these posts.)
|
||||
@ -235,14 +256,20 @@ cache := certmagic.NewCache(certmagic.CacheOptions{
|
||||
})
|
||||
|
||||
magic := certmagic.New(cache, certmagic.Config{
|
||||
// any customizations you need go here
|
||||
})
|
||||
|
||||
myACME := certmagic.NewACMEManager(magic, ACMEManager{
|
||||
CA: certmagic.LetsEncryptStagingCA,
|
||||
Email: "you@yours.com",
|
||||
Agreed: true,
|
||||
// plus any other customization you want
|
||||
// plus any other customizations you need
|
||||
})
|
||||
|
||||
magic.Issuer = myACME
|
||||
|
||||
// this obtains certificates or renews them if necessary
|
||||
err := magic.Manage([]string{"example.com", "sub.example.com"})
|
||||
err := magic.ManageSync([]string{"example.com", "sub.example.com"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -262,7 +289,7 @@ myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, tlsalpn01.ACMETLS1Protoc
|
||||
// the HTTP challenge has to be handled by your HTTP server;
|
||||
// if you don't have one, you should have disabled it earlier
|
||||
// when you made the certmagic.Config
|
||||
httpMux = magic.HTTPChallengeHandler(httpMux)
|
||||
httpMux = myACME.HTTPChallengeHandler(httpMux)
|
||||
```
|
||||
|
||||
Great! This example grants you much more flexibility for advanced programs. However, _the vast majority of you will only use the high-level functions described earlier_, especially since you can still customize them by setting the package-level `Default` config.
|
||||
@ -281,7 +308,7 @@ To do so, simply ensure that each instance is using the same Storage. That is th
|
||||
|
||||
The default Storage is implemented using the file system, so mounting the same shared folder is sufficient (see [Storage](#storage) for more on that)! If you need an alternate Storage implementation, feel free to use one, provided that all the instances use the _same_ one. :)
|
||||
|
||||
See [Storage](#storage) and the associated [godoc](https://godoc.org/github.com/mholt/certmagic#Storage) for more information!
|
||||
See [Storage](#storage) and the associated [pkg.go.dev](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#Storage) for more information!
|
||||
|
||||
|
||||
## The ACME Challenges
|
||||
@ -292,7 +319,7 @@ If you're using the high-level convenience functions like `HTTPS()`, `Listen()`,
|
||||
|
||||
The HTTP and TLS-ALPN challenges are the defaults because they don't require configuration from you, but they require that your server is accessible from external IPs on low ports. If that is not possible in your situation, you can enable the DNS challenge, which will disable the HTTP and TLS-ALPN challenges and use the DNS challenge exclusively.
|
||||
|
||||
Technically, only one challenge needs to be enabled for things to work, but using multiple is good for reliability in case a challenge is discontinued by the CA. This happened to the TLS-SNI challenge in early 2018—many popular ACME clients such as Traefik and Autocert broke, resulting in downtime for some sites, until new releases were made and patches deployed, because they used only one challenge; Caddy, however—this library's forerunner—was unaffected because it also used the HTTP challenge. If multiple challenges are enabled, they are chosen randomly to help prevent false reliance on a single challenge type.
|
||||
Technically, only one challenge needs to be enabled for things to work, but using multiple is good for reliability in case a challenge is discontinued by the CA. This happened to the TLS-SNI challenge in early 2018—many popular ACME clients such as Traefik and Autocert broke, resulting in downtime for some sites, until new releases were made and patches deployed, because they used only one challenge; Caddy, however—this library's forerunner—was unaffected because it also used the HTTP challenge. If multiple challenges are enabled, they are chosen randomly to help prevent false reliance on a single challenge type. And if one fails, any remaining enabled challenges are tried before giving up.
|
||||
|
||||
|
||||
### HTTP Challenge
|
||||
@ -309,16 +336,17 @@ mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(w, "Lookit my cool website over HTTPS!")
|
||||
})
|
||||
|
||||
http.ListenAndServe(":80", magic.HTTPChallengeHandler(mux))
|
||||
http.ListenAndServe(":80", myACME.HTTPChallengeHandler(mux))
|
||||
```
|
||||
|
||||
If wrapping your handler is not a good solution, try this inside your `ServeHTTP()` instead:
|
||||
|
||||
```go
|
||||
magic := certmagic.NewDefault()
|
||||
myACME := certmagic.NewACMEManager(magic, certmagic.DefaultACME)
|
||||
|
||||
func ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if magic.HandleHTTPChallenge(w, r) {
|
||||
if myACME.HandleHTTPChallenge(w, r) {
|
||||
return // challenge handled; nothing else to do
|
||||
}
|
||||
...
|
||||
@ -362,17 +390,17 @@ This challenge works by setting a special record in the domain's zone. To do thi
|
||||
To enable it, just set the `DNSProvider` field on a `certmagic.Config` struct, or set the default `certmagic.DNSProvider` variable. For example, if my domains' DNS was served by DNSimple and I set my DNSimple API credentials in environment variables:
|
||||
|
||||
```go
|
||||
import "github.com/go-acme/lego/providers/dns/dnsimple"
|
||||
import "github.com/go-acme/lego/v3/providers/dns/dnsimple"
|
||||
|
||||
provider, err := dnsimple.NewDNSProvider()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certmagic.Default.DNSProvider = provider
|
||||
certmagic.DefaultACME.DNSProvider = provider
|
||||
```
|
||||
|
||||
Now the DNS challenge will be used by default, and I can obtain certificates for wildcard domains. See the [godoc documentation for the provider you're using](https://godoc.org/github.com/go-acme/lego/providers/dns#pkg-subdirectories) to learn how to configure it. Most can be configured by env variables or by passing in a config struct. If you pass a config struct instead of using env variables, you will probably need to set some other defaults (that's just how lego works, currently):
|
||||
Now the DNS challenge will be used by default, and I can obtain certificates for wildcard domains. See the [pkg.go.dev documentation for the provider you're using](https://pkg.go.dev/github.com/go-acme/lego/providers/dns?tab=subdirectories) to learn how to configure it. Most can be configured by env variables or by passing in a config struct. If you pass a config struct instead of using env variables, you will probably need to set some other defaults (that's just how lego works, currently):
|
||||
|
||||
```go
|
||||
PropagationTimeout: dns01.DefaultPollingInterval,
|
||||
@ -385,30 +413,36 @@ Enabling the DNS challenge disables the other challenges for that `certmagic.Con
|
||||
|
||||
## On-Demand TLS
|
||||
|
||||
Normally, certificates are obtained and renewed before a listener starts serving, and then those certificates are maintained throughout the lifetime of the program. In other words, the certificate names are static. But sometimes you don't know all the names ahead of time. This is where On-Demand TLS shines.
|
||||
Normally, certificates are obtained and renewed before a listener starts serving, and then those certificates are maintained throughout the lifetime of the program. In other words, the certificate names are static. But sometimes you don't know all the names ahead of time, or you don't want to manage all the certificates up front. This is where On-Demand TLS shines.
|
||||
|
||||
Originally invented for use in Caddy (which was the first program to use such technology), On-Demand TLS makes it possible and easy to serve certificates for arbitrary names during the lifetime of the server. When a TLS handshake is received, CertMagic will read the Server Name Indication (SNI) value and either load and present that certificate in the ServerHello, or if one does not exist, it will obtain it from a CA right then-and-there.
|
||||
Originally invented for use in Caddy (which was the first program to use such technology), On-Demand TLS makes it possible and easy to serve certificates for arbitrary or specific names during the lifetime of the server. When a TLS handshake is received, CertMagic will read the Server Name Indication (SNI) value and either load and present that certificate in the ServerHello, or if one does not exist, it will obtain it from a CA right then-and-there.
|
||||
|
||||
Of course, this has some obvious security implications. You don't want to DoS a CA or allow arbitrary clients to fill your storage with spammy TLS handshakes. That's why, in order to enable On-Demand issuance, you'll need to set some limits or some policy to allow getting a certificate.
|
||||
Of course, this has some obvious security implications. You don't want to DoS a CA or allow arbitrary clients to fill your storage with spammy TLS handshakes. That's why, when you enable On-Demand issuance, you should set limits or policy to allow getting certificates. CertMagic has an implicit whitelist built-in which is sufficient for nearly everyone, but also has a more advanced way to control on-demand issuance.
|
||||
|
||||
CertMagic provides several ways to enforce decision policies for On-Demand TLS, in descending order of priority:
|
||||
|
||||
- A generic function that you write which will decide whether to allow the certificate request
|
||||
- A name whitelist
|
||||
- The ability to make an HTTP request to a URL for permission
|
||||
- Rate limiting
|
||||
|
||||
The simplest way to enable On-Demand issuance is to set the OnDemand field of a Config (or the default package-level value):
|
||||
The simplest way to enable on-demand issuance is to set the OnDemand field of a Config (or the default package-level value):
|
||||
|
||||
```go
|
||||
certmagic.Default.OnDemand = &certmagic.OnDemandConfig{MaxObtain: 5}
|
||||
certmagic.Default.OnDemand = new(certmagic.OnDemandConfig)
|
||||
```
|
||||
|
||||
This allows only 5 certificates to be requested and is the simplest way to enable On-Demand TLS, but is the least recommended. It prevents abuse, but only in the least helpful way.
|
||||
By setting this to a non-nil value, on-demand TLS is enabled for that config. For convenient security, CertMagic's high-level abstraction functions such as `HTTPS()`, `TLS()`, `ManageSync()`, `ManageAsync()`, and `Listen()` (which all accept a list of domain names) will whitelist those names automatically so only certificates for those names can be obtained when using the Default config. Usually this is sufficient for most users.
|
||||
|
||||
The [godoc](https://godoc.org/github.com/mholt/certmagic#OnDemandConfig) describes how to use the other policies, all of which are much more recommended! :)
|
||||
However, if you require advanced control over which domains can be issued certificates on-demand (for example, if you do not know which domain names you are managing, or just need to defer their operations until later), you should implement your own DecisionFunc:
|
||||
|
||||
If `OnDemand` is set and `Manage()` is called, then the names given to `Manage()` will be whitelisted rather than obtained right away.
|
||||
```go
|
||||
// if the decision function returns an error, a certificate
|
||||
// may not be obtained for that name at that time
|
||||
certmagic.Default.OnDemand = &certmagic.OnDemandConfig{
|
||||
DecisionFunc: func(name string) error {
|
||||
if name != "example.com" {
|
||||
return fmt.Errorf("not allowed")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The [pkg.go.dev](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#OnDemandConfig) describes how to use this in full detail, so please check it out!
|
||||
|
||||
|
||||
## Storage
|
||||
@ -419,9 +453,9 @@ By default, CertMagic stores assets on the local file system in `$HOME/.local/sh
|
||||
|
||||
The notion of a "cluster" or "fleet" of instances that may be serving the same site and sharing certificates, etc, is tied to storage. Simply, any instances that use the same storage facilities are considered part of the cluster. So if you deploy 100 instances of CertMagic behind a load balancer, they are all part of the same cluster if they share the same storage configuration. Sharing storage could be mounting a shared folder, or implementing some other distributed storage system such as a database server or KV store.
|
||||
|
||||
The easiest way to change the storage being used is to set `certmagic.DefaultStorage` to a value that satisfies the [Storage interface](https://godoc.org/github.com/mholt/certmagic#Storage). Keep in mind that a valid `Storage` must be able to implement some operations atomically in order to provide locking and synchronization.
|
||||
The easiest way to change the storage being used is to set `certmagic.DefaultStorage` to a value that satisfies the [Storage interface](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#Storage). Keep in mind that a valid `Storage` must be able to implement some operations atomically in order to provide locking and synchronization.
|
||||
|
||||
If you write a Storage implementation, please add it to the [project wiki](https://github.com/mholt/certmagic/wiki/Storage-Implementations) so people can find it!
|
||||
If you write a Storage implementation, please add it to the [project wiki](https://github.com/caddyserver/certmagic/wiki/Storage-Implementations) so people can find it!
|
||||
|
||||
|
||||
## Cache
|
||||
@ -439,9 +473,9 @@ Again, if you're needing to do this, you've probably over-complicated your appli
|
||||
|
||||
Yes, just call the relevant method on the `Config` to add your own certificate to the cache:
|
||||
|
||||
- [`CacheUnmanagedCertificatePEMBytes()`](https://godoc.org/github.com/mholt/certmagic#Config.CacheUnmanagedCertificatePEMBytes)
|
||||
- [`CacheUnmanagedCertificatePEMFile()`](https://godoc.org/github.com/mholt/certmagic#Config.CacheUnmanagedCertificatePEMFile)
|
||||
- [`CacheUnmanagedTLSCertificate()`](https://godoc.org/github.com/mholt/certmagic#Config.CacheUnmanagedTLSCertificate)
|
||||
- [`CacheUnmanagedCertificatePEMBytes()`](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#Config.CacheUnmanagedCertificatePEMBytes)
|
||||
- [`CacheUnmanagedCertificatePEMFile()`](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#Config.CacheUnmanagedCertificatePEMFile)
|
||||
- [`CacheUnmanagedTLSCertificate()`](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#Config.CacheUnmanagedTLSCertificate)
|
||||
|
||||
Keep in mind that unmanaged certificates are (obviously) not renewed for you, so you'll have to replace them when you do. However, OCSP stapling is performed even for unmanaged certificates that qualify.
|
||||
|
||||
@ -464,7 +498,7 @@ and then you will not need to run with root privileges.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome your contributions! Please see our **[contributing guidelines](https://github.com/mholt/certmagic/blob/master/.github/CONTRIBUTING.md)** for instructions.
|
||||
We welcome your contributions! Please see our **[contributing guidelines](https://github.com/caddyserver/certmagic/blob/master/.github/CONTRIBUTING.md)** for instructions.
|
||||
|
||||
|
||||
## Project History
|
402
vendor/github.com/caddyserver/certmagic/acmeclient.go
generated
vendored
Normal file
402
vendor/github.com/caddyserver/certmagic/acmeclient.go
generated
vendored
Normal file
@ -0,0 +1,402 @@
|
||||
// Copyright 2015 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/certificate"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/lego"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
)
|
||||
|
||||
func init() {
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// acmeClient is a wrapper over lego's acme.Client with
|
||||
// some custom state attached. It is used to obtain,
|
||||
// renew, and revoke certificates with ACME. Use
|
||||
// ACMEManager.newACMEClient() or
|
||||
// ACMEManager.newACMEClientWithRetry() to get a valid
|
||||
// one for real use.
|
||||
type acmeClient struct {
|
||||
caURL string
|
||||
mgr *ACMEManager
|
||||
acmeClient *lego.Client
|
||||
challenges []challenge.Type
|
||||
}
|
||||
|
||||
// newACMEClientWithRetry is the same as newACMEClient, but with
|
||||
// automatic retry capabilities. Sometimes network connections or
|
||||
// HTTP requests fail intermittently, even when requesting the
|
||||
// directory endpoint for example, so we can avoid that by just
|
||||
// retrying once. Failures here are rare and sporadic, usually,
|
||||
// so a simple retry is an easy fix.
|
||||
func (am *ACMEManager) newACMEClientWithRetry(useTestCA bool) (*acmeClient, error) {
|
||||
var client *acmeClient
|
||||
var err error
|
||||
const maxTries = 2
|
||||
for i := 0; i < maxTries; i++ {
|
||||
if i > 0 {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
client, err = am.newACMEClient(useTestCA, false) // TODO: move logic that requires interactivity to way before this part of the process...
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if acmeErr, ok := err.(acme.ProblemDetails); ok {
|
||||
if acmeErr.HTTPStatus == http.StatusTooManyRequests {
|
||||
return nil, fmt.Errorf("too many requests making new ACME client: %+v - aborting", acmeErr)
|
||||
}
|
||||
}
|
||||
log.Printf("[ERROR] Making new ACME client: %v (attempt %d/%d)", err, i+1, maxTries)
|
||||
}
|
||||
return client, err
|
||||
}
|
||||
|
||||
// newACMEClient creates the underlying ACME library client type.
|
||||
// If useTestCA is true, am.TestCA will be used if it is set;
|
||||
// otherwise, the primary CA will still be used.
|
||||
func (am *ACMEManager) newACMEClient(useTestCA, interactive bool) (*acmeClient, error) {
|
||||
acmeClientsMu.Lock()
|
||||
defer acmeClientsMu.Unlock()
|
||||
|
||||
// ensure defaults are filled in
|
||||
certObtainTimeout := am.CertObtainTimeout
|
||||
if certObtainTimeout == 0 {
|
||||
certObtainTimeout = DefaultACME.CertObtainTimeout
|
||||
}
|
||||
var caURL string
|
||||
if useTestCA {
|
||||
caURL = am.TestCA
|
||||
// only use the default test CA if the CA is also
|
||||
// the default CA; no point in testing against
|
||||
// Let's Encrypt's staging server if we are not
|
||||
// using their production server too
|
||||
if caURL == "" && am.CA == DefaultACME.CA {
|
||||
caURL = DefaultACME.TestCA
|
||||
}
|
||||
}
|
||||
if caURL == "" {
|
||||
caURL = am.CA
|
||||
}
|
||||
if caURL == "" {
|
||||
caURL = DefaultACME.CA
|
||||
}
|
||||
|
||||
// ensure endpoint is secure (assume HTTPS if scheme is missing)
|
||||
if !strings.Contains(caURL, "://") {
|
||||
caURL = "https://" + caURL
|
||||
}
|
||||
u, err := url.Parse(caURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "https" && !isLoopback(u.Host) && !isInternal(u.Host) {
|
||||
return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL)
|
||||
}
|
||||
|
||||
// look up or create the user account
|
||||
leUser, err := am.getUser(caURL, am.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if a lego client with this configuration already exists, reuse it
|
||||
clientKey := caURL + leUser.Email
|
||||
client, ok := acmeClients[clientKey]
|
||||
if !ok {
|
||||
// the client facilitates our communication with the CA server
|
||||
legoCfg := lego.NewConfig(leUser)
|
||||
legoCfg.CADirURL = caURL
|
||||
legoCfg.UserAgent = buildUAString()
|
||||
legoCfg.HTTPClient.Timeout = HTTPTimeout
|
||||
legoCfg.Certificate = lego.CertificateConfig{
|
||||
Timeout: am.CertObtainTimeout,
|
||||
}
|
||||
if am.TrustedRoots != nil {
|
||||
if ht, ok := legoCfg.HTTPClient.Transport.(*http.Transport); ok {
|
||||
if ht.TLSClientConfig == nil {
|
||||
ht.TLSClientConfig = new(tls.Config)
|
||||
ht.ForceAttemptHTTP2 = true
|
||||
}
|
||||
ht.TLSClientConfig.RootCAs = am.TrustedRoots
|
||||
}
|
||||
}
|
||||
client, err = lego.NewClient(legoCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acmeClients[clientKey] = client
|
||||
}
|
||||
|
||||
// if not registered, the user must register an account
|
||||
// with the CA and agree to terms
|
||||
if leUser.Registration == nil {
|
||||
if interactive { // can't prompt a user who isn't there
|
||||
termsURL := client.GetToSURL()
|
||||
if !am.Agreed && termsURL != "" {
|
||||
am.Agreed = am.askUserAgreement(client.GetToSURL())
|
||||
}
|
||||
if !am.Agreed && termsURL != "" {
|
||||
return nil, fmt.Errorf("user must agree to CA terms")
|
||||
}
|
||||
}
|
||||
|
||||
var reg *registration.Resource
|
||||
if am.ExternalAccount != nil {
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: am.Agreed,
|
||||
Kid: am.ExternalAccount.KeyID,
|
||||
HmacEncoded: base64.StdEncoding.EncodeToString(am.ExternalAccount.HMAC),
|
||||
})
|
||||
} else {
|
||||
reg, err = client.Registration.Register(registration.RegisterOptions{
|
||||
TermsOfServiceAgreed: am.Agreed,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leUser.Registration = reg
|
||||
|
||||
// persist the user to storage
|
||||
err = am.saveUser(caURL, leUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not save user: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
c := &acmeClient{
|
||||
caURL: caURL,
|
||||
mgr: am,
|
||||
acmeClient: client,
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// initialChallenges returns the initial set of challenges
|
||||
// to try using c.config as a basis.
|
||||
func (c *acmeClient) initialChallenges() []challenge.Type {
|
||||
// if configured, use DNS challenge exclusively
|
||||
if c.mgr.DNSProvider != nil {
|
||||
return []challenge.Type{challenge.DNS01}
|
||||
}
|
||||
|
||||
// otherwise, use HTTP and TLS-ALPN challenges if enabled
|
||||
var chal []challenge.Type
|
||||
if !c.mgr.DisableHTTPChallenge {
|
||||
chal = append(chal, challenge.HTTP01)
|
||||
}
|
||||
if !c.mgr.DisableTLSALPNChallenge {
|
||||
chal = append(chal, challenge.TLSALPN01)
|
||||
}
|
||||
return chal
|
||||
}
|
||||
|
||||
// nextChallenge chooses a challenge randomly from the given list of
|
||||
// available challenges and configures c.acmeClient to use that challenge
|
||||
// according to c.config. It pops the chosen challenge from the list and
|
||||
// returns that challenge along with the new list without that challenge.
|
||||
// If len(available) == 0, this is a no-op.
|
||||
//
|
||||
// Don't even get me started on how dumb it is we need to do this here
|
||||
// instead of the upstream lego library doing it for us. Lego used to
|
||||
// randomize the challenge order, thus allowing another one to be used
|
||||
// if the first one failed. https://github.com/go-acme/lego/issues/842
|
||||
// (It also has an awkward API for adjusting the available challenges.)
|
||||
// At time of writing, lego doesn't try anything other than the TLS-ALPN
|
||||
// challenge, even if the HTTP challenge is also enabled. So we take
|
||||
// matters into our own hands and enable only one challenge at a time
|
||||
// in the underlying client, randomly selected by us.
|
||||
func (c *acmeClient) nextChallenge(available []challenge.Type) (challenge.Type, []challenge.Type) {
|
||||
if len(available) == 0 {
|
||||
return "", available
|
||||
}
|
||||
|
||||
// make sure we choose a challenge randomly, which lego used to do but
|
||||
// the critical feature was surreptitiously removed in ~2018 in a commit
|
||||
// too large to review, oh well - choose one, then remove it from the
|
||||
// list of available challenges so it doesn't get retried
|
||||
randIdx := weakrand.Intn(len(available))
|
||||
randomChallenge := available[randIdx]
|
||||
available = append(available[:randIdx], available[randIdx+1:]...)
|
||||
|
||||
// clean the slate, since we reuse clients
|
||||
c.acmeClient.Challenge.Remove(challenge.HTTP01)
|
||||
c.acmeClient.Challenge.Remove(challenge.TLSALPN01)
|
||||
c.acmeClient.Challenge.Remove(challenge.DNS01)
|
||||
|
||||
switch randomChallenge {
|
||||
case challenge.HTTP01:
|
||||
useHTTPPort := HTTPChallengePort
|
||||
if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
|
||||
useHTTPPort = HTTPPort
|
||||
}
|
||||
if c.mgr.AltHTTPPort > 0 {
|
||||
useHTTPPort = c.mgr.AltHTTPPort
|
||||
}
|
||||
|
||||
c.acmeClient.Challenge.SetHTTP01Provider(distributedSolver{
|
||||
acmeManager: c.mgr,
|
||||
providerServer: &httpSolver{
|
||||
acmeManager: c.mgr,
|
||||
address: net.JoinHostPort(c.mgr.ListenHost, strconv.Itoa(useHTTPPort)),
|
||||
},
|
||||
caURL: c.caURL,
|
||||
})
|
||||
|
||||
case challenge.TLSALPN01:
|
||||
useTLSALPNPort := TLSALPNChallengePort
|
||||
if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort {
|
||||
useTLSALPNPort = HTTPSPort
|
||||
}
|
||||
if c.mgr.AltTLSALPNPort > 0 {
|
||||
useTLSALPNPort = c.mgr.AltTLSALPNPort
|
||||
}
|
||||
|
||||
c.acmeClient.Challenge.SetTLSALPN01Provider(distributedSolver{
|
||||
acmeManager: c.mgr,
|
||||
providerServer: &tlsALPNSolver{
|
||||
config: c.mgr.config,
|
||||
address: net.JoinHostPort(c.mgr.ListenHost, strconv.Itoa(useTLSALPNPort)),
|
||||
},
|
||||
caURL: c.caURL,
|
||||
})
|
||||
|
||||
case challenge.DNS01:
|
||||
if c.mgr.DNSChallengeOption != nil {
|
||||
c.acmeClient.Challenge.SetDNS01Provider(c.mgr.DNSProvider, c.mgr.DNSChallengeOption)
|
||||
} else {
|
||||
c.acmeClient.Challenge.SetDNS01Provider(c.mgr.DNSProvider)
|
||||
}
|
||||
}
|
||||
|
||||
return randomChallenge, available
|
||||
}
|
||||
|
||||
func (c *acmeClient) throttle(ctx context.Context, names []string) error {
|
||||
// throttling is scoped to CA + account email
|
||||
rateLimiterKey := c.caURL + "," + c.mgr.Email
|
||||
rateLimitersMu.Lock()
|
||||
rl, ok := rateLimiters[rateLimiterKey]
|
||||
if !ok {
|
||||
rl = NewRateLimiter(RateLimitEvents, RateLimitEventsWindow)
|
||||
rateLimiters[rateLimiterKey] = rl
|
||||
// TODO: stop rate limiter when it is garbage-collected...
|
||||
}
|
||||
rateLimitersMu.Unlock()
|
||||
log.Printf("[INFO]%v Waiting on rate limiter...", names)
|
||||
err := rl.Wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("[INFO]%v Done waiting", names)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *acmeClient) usingTestCA() bool {
|
||||
return c.mgr.TestCA != "" && c.caURL == c.mgr.TestCA
|
||||
}
|
||||
|
||||
func (c *acmeClient) revoke(_ context.Context, certRes certificate.Resource) error {
|
||||
return c.acmeClient.Certificate.Revoke(certRes.Certificate)
|
||||
}
|
||||
|
||||
func buildUAString() string {
|
||||
ua := "CertMagic"
|
||||
if UserAgent != "" {
|
||||
ua += " " + UserAgent
|
||||
}
|
||||
return ua
|
||||
}
|
||||
|
||||
// These internal rate limits are designed to prevent accidentally
|
||||
// firehosing a CA's ACME endpoints. They are not intended to
|
||||
// replace or replicate the CA's actual rate limits.
|
||||
//
|
||||
// Let's Encrypt's rate limits can be found here:
|
||||
// https://letsencrypt.org/docs/rate-limits/
|
||||
//
|
||||
// Currently (as of December 2019), Let's Encrypt's most relevant
|
||||
// rate limit for large deployments is 300 new orders per account
|
||||
// per 3 hours (on average, or best case, that's about 1 every 36
|
||||
// seconds, or 2 every 72 seconds, etc.); but it's not reasonable
|
||||
// to try to assume that our internal state is the same as the CA's
|
||||
// (due to process restarts, config changes, failed validations,
|
||||
// etc.) and ultimately, only the CA's actual rate limiter is the
|
||||
// authority. Thus, our own rate limiters do not attempt to enforce
|
||||
// external rate limits. Doing so causes problems when the domains
|
||||
// are not in our control (i.e. serving customer sites) and/or lots
|
||||
// of domains fail validation: they clog our internal rate limiter
|
||||
// and nearly starve out (or at least slow down) the other domains
|
||||
// that need certificates. Failed transactions are already retried
|
||||
// with exponential backoff, so adding in rate limiting can slow
|
||||
// things down even more.
|
||||
//
|
||||
// Instead, the point of our internal rate limiter is to avoid
|
||||
// hammering the CA's endpoint when there are thousands or even
|
||||
// millions of certificates under management. Our goal is to
|
||||
// allow small bursts in a relatively short timeframe so as to
|
||||
// not block any one domain for too long, without unleashing
|
||||
// thousands of requests to the CA at once.
|
||||
var (
|
||||
rateLimiters = make(map[string]*RingBufferRateLimiter)
|
||||
rateLimitersMu sync.RWMutex
|
||||
|
||||
// RateLimitEvents is how many new events can be allowed
|
||||
// in RateLimitEventsWindow.
|
||||
RateLimitEvents = 10
|
||||
|
||||
// RateLimitEventsWindow is the size of the sliding
|
||||
// window that throttles events.
|
||||
RateLimitEventsWindow = 1 * time.Minute
|
||||
)
|
||||
|
||||
// Some default values passed down to the underlying lego client.
|
||||
var (
|
||||
UserAgent string
|
||||
HTTPTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// We keep a global cache of ACME clients so that they
|
||||
// can be reused. Since the number of CAs, accounts,
|
||||
// and key types should be fairly limited under best
|
||||
// practices, this map will hardly ever have more than
|
||||
// a few entries at the most. The associated lock
|
||||
// protects access to the map but also ensures that only
|
||||
// one ACME client is created at a time.
|
||||
// TODO: consider using storage for a distributed lock
|
||||
// TODO: consider evicting clients after some time
|
||||
var (
|
||||
acmeClients = make(map[string]*lego.Client)
|
||||
acmeClientsMu sync.Mutex
|
||||
)
|
359
vendor/github.com/caddyserver/certmagic/acmemanager.go
generated
vendored
Normal file
359
vendor/github.com/caddyserver/certmagic/acmemanager.go
generated
vendored
Normal file
@ -0,0 +1,359 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/certificate"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/dns01"
|
||||
)
|
||||
|
||||
// ACMEManager gets certificates using ACME. It implements the PreChecker,
|
||||
// Issuer, and Revoker interfaces.
|
||||
//
|
||||
// It is NOT VALID to use an ACMEManager without calling NewACMEManager().
|
||||
// It fills in default values from DefaultACME as well as setting up
|
||||
// internal state that is necessary for valid use. Always call
|
||||
// NewACMEManager() to get a valid ACMEManager value.
|
||||
type ACMEManager struct {
|
||||
// The endpoint of the directory for the ACME
|
||||
// CA we are to use
|
||||
CA string
|
||||
|
||||
// TestCA is the endpoint of the directory for
|
||||
// an ACME CA to use to test domain validation,
|
||||
// but any certs obtained from this CA are
|
||||
// discarded
|
||||
TestCA string
|
||||
|
||||
// The email address to use when creating or
|
||||
// selecting an existing ACME server account
|
||||
Email string
|
||||
|
||||
// Set to true if agreed to the CA's
|
||||
// subscriber agreement
|
||||
Agreed bool
|
||||
|
||||
// An optional external account to associate
|
||||
// with this ACME account
|
||||
ExternalAccount *ExternalAccountBinding
|
||||
|
||||
// Disable all HTTP challenges
|
||||
DisableHTTPChallenge bool
|
||||
|
||||
// Disable all TLS-ALPN challenges
|
||||
DisableTLSALPNChallenge bool
|
||||
|
||||
// The host (ONLY the host, not port) to listen
|
||||
// on if necessary to start a listener to solve
|
||||
// an ACME challenge
|
||||
ListenHost string
|
||||
|
||||
// The alternate port to use for the ACME HTTP
|
||||
// challenge; if non-empty, this port will be
|
||||
// used instead of HTTPChallengePort to spin up
|
||||
// a listener for the HTTP challenge
|
||||
AltHTTPPort int
|
||||
|
||||
// The alternate port to use for the ACME
|
||||
// TLS-ALPN challenge; the system must forward
|
||||
// TLSALPNChallengePort to this port for
|
||||
// challenge to succeed
|
||||
AltTLSALPNPort int
|
||||
|
||||
// The DNS provider to use when solving the
|
||||
// ACME DNS challenge
|
||||
DNSProvider challenge.Provider
|
||||
|
||||
// The ChallengeOption struct to provide
|
||||
// custom precheck or name resolution options
|
||||
// for DNS challenge validation and execution
|
||||
DNSChallengeOption dns01.ChallengeOption
|
||||
|
||||
// TrustedRoots specifies a pool of root CA
|
||||
// certificates to trust when communicating
|
||||
// over a network to a peer.
|
||||
TrustedRoots *x509.CertPool
|
||||
|
||||
// The maximum amount of time to allow for
|
||||
// obtaining a certificate. If empty, the
|
||||
// default from the underlying lego lib is
|
||||
// used. If set, it must not be too low so
|
||||
// as to cancel orders too early, running
|
||||
// the risk of rate limiting.
|
||||
CertObtainTimeout time.Duration
|
||||
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewACMEManager constructs a valid ACMEManager based on a template
|
||||
// configuration; any empty values will be filled in by defaults in
|
||||
// DefaultACME. The associated config is also required.
|
||||
//
|
||||
// Typically, you'll create the Config first, then call NewACMEManager(),
|
||||
// then assign the return value to the Issuer/Revoker fields of the Config.
|
||||
func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager {
|
||||
if cfg == nil {
|
||||
panic("cannot make valid ACMEManager without an associated CertMagic config")
|
||||
}
|
||||
if template.CA == "" {
|
||||
template.CA = DefaultACME.CA
|
||||
}
|
||||
if template.TestCA == "" {
|
||||
template.TestCA = DefaultACME.TestCA
|
||||
}
|
||||
if template.Email == "" {
|
||||
template.Email = DefaultACME.Email
|
||||
}
|
||||
if !template.Agreed {
|
||||
template.Agreed = DefaultACME.Agreed
|
||||
}
|
||||
if !template.DisableHTTPChallenge {
|
||||
template.DisableHTTPChallenge = DefaultACME.DisableHTTPChallenge
|
||||
}
|
||||
if !template.DisableTLSALPNChallenge {
|
||||
template.DisableTLSALPNChallenge = DefaultACME.DisableTLSALPNChallenge
|
||||
}
|
||||
if template.ListenHost == "" {
|
||||
template.ListenHost = DefaultACME.ListenHost
|
||||
}
|
||||
if template.AltHTTPPort == 0 {
|
||||
template.AltHTTPPort = DefaultACME.AltHTTPPort
|
||||
}
|
||||
if template.AltTLSALPNPort == 0 {
|
||||
template.AltTLSALPNPort = DefaultACME.AltTLSALPNPort
|
||||
}
|
||||
if template.DNSProvider == nil {
|
||||
template.DNSProvider = DefaultACME.DNSProvider
|
||||
}
|
||||
if template.DNSChallengeOption == nil {
|
||||
template.DNSChallengeOption = DefaultACME.DNSChallengeOption
|
||||
}
|
||||
if template.TrustedRoots == nil {
|
||||
template.TrustedRoots = DefaultACME.TrustedRoots
|
||||
}
|
||||
if template.CertObtainTimeout == 0 {
|
||||
template.CertObtainTimeout = DefaultACME.CertObtainTimeout
|
||||
}
|
||||
template.config = cfg
|
||||
return &template
|
||||
}
|
||||
|
||||
// IssuerKey returns the unique issuer key for the
|
||||
// confgured CA endpoint.
|
||||
func (am *ACMEManager) IssuerKey() string {
|
||||
return am.issuerKey(am.CA)
|
||||
}
|
||||
|
||||
func (am *ACMEManager) issuerKey(ca string) string {
|
||||
key := ca
|
||||
if caURL, err := url.Parse(key); err == nil {
|
||||
key = caURL.Host
|
||||
if caURL.Path != "" {
|
||||
// keep the path, but make sure it's a single
|
||||
// component (i.e. no forward slashes, and for
|
||||
// good measure, no backward slashes either)
|
||||
const hyphen = "-"
|
||||
repl := strings.NewReplacer(
|
||||
"/", hyphen,
|
||||
"\\", hyphen,
|
||||
)
|
||||
path := strings.Trim(repl.Replace(caURL.Path), hyphen)
|
||||
if path != "" {
|
||||
key += hyphen + path
|
||||
}
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// PreCheck performs a few simple checks before obtaining or
|
||||
// renewing a certificate with ACME, and returns whether this
|
||||
// batch is eligible for certificates if using Let's Encrypt.
|
||||
// It also ensures that an email address is available.
|
||||
func (am *ACMEManager) PreCheck(names []string, interactive bool) error {
|
||||
letsEncrypt := strings.Contains(am.CA, "api.letsencrypt.org")
|
||||
if letsEncrypt {
|
||||
for _, name := range names {
|
||||
if !SubjectQualifiesForPublicCert(name) {
|
||||
return fmt.Errorf("subject does not qualify for a Let's Encrypt certificate: %s", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return am.getEmail(interactive)
|
||||
}
|
||||
|
||||
// Issue implements the Issuer interface. It obtains a certificate for the given csr using
|
||||
// the ACME configuration am.
|
||||
func (am *ACMEManager) Issue(ctx context.Context, csr *x509.CertificateRequest) (*IssuedCertificate, error) {
|
||||
if am.config == nil {
|
||||
panic("missing config pointer (must use NewACMEManager)")
|
||||
}
|
||||
|
||||
var isRetry bool
|
||||
if attempts, ok := ctx.Value(AttemptsCtxKey).(*int); ok {
|
||||
isRetry = *attempts > 0
|
||||
}
|
||||
|
||||
cert, usedTestCA, err := am.doIssue(ctx, csr, isRetry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// important to note that usedTestCA is not necessarily the same as isRetry
|
||||
// (usedTestCA can be true if the main CA and the test CA happen to be the same)
|
||||
if isRetry && usedTestCA && am.CA != am.TestCA {
|
||||
// succeeded with testing endpoint, so try again with production endpoint
|
||||
// (only if the production endpoint is different from the testing endpoint)
|
||||
// TODO: This logic is imperfect and could benefit from some refinement.
|
||||
// The two CA endpoints likely have different states, which could cause one
|
||||
// to succeed and the other to fail, even if it's not a validation error.
|
||||
// Two common cases would be:
|
||||
// 1) Rate limiter state. This is more likely to cause prod to fail while
|
||||
// staging succeeds, since prod usually has tighter rate limits. Thus, if
|
||||
// initial attempt failed in prod due to rate limit, first retry (on staging)
|
||||
// might succeed, and then trying prod again right way would probably still
|
||||
// fail; normally this would terminate retries but the right thing to do in
|
||||
// this case is to back off and retry again later. We could refine this logic
|
||||
// to stick with the production endpoint on retries unless the error changes.
|
||||
// 2) Cached authorizations state. If a domain validates successfully with
|
||||
// one endpoint, but then the other endpoint is used, it might fail, e.g. if
|
||||
// DNS was just changed or is still propagating. In this case, the second CA
|
||||
// should continue to be retried with backoff, without switching back to the
|
||||
// other endpoint. This is more likely to happen if a user is testing with
|
||||
// the staging CA as the main CA, then changes their configuration once they
|
||||
// think they are ready for the production endpoint.
|
||||
cert, _, err = am.doIssue(ctx, csr, false)
|
||||
if err != nil {
|
||||
// succeeded with test CA but failed just now with the production CA;
|
||||
// either we are observing differing internal states of each CA that will
|
||||
// work out with time, or there is a bug/misconfiguration somewhere
|
||||
// externally; it is hard to tell which! one easy cue is whether the
|
||||
// error is specifically a 429 (Too Many Requests); if so, we should
|
||||
// probably keep retrying
|
||||
var acmeErr acme.ProblemDetails
|
||||
if errors.As(err, &acmeErr) {
|
||||
if acmeErr.HTTPStatus == http.StatusTooManyRequests {
|
||||
// DON'T abort retries; the test CA succeeded (even
|
||||
// if it's cached, it recently succeeded!) so we just
|
||||
// need to keep trying (with backoff) until this CA's
|
||||
// rate limits expire...
|
||||
// TODO: as mentioned in comment above, we would benefit
|
||||
// by pinning the main CA at this point instead of
|
||||
// needlessly retrying with the test CA first each time
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, ErrNoRetry{err}
|
||||
}
|
||||
}
|
||||
|
||||
return cert, err
|
||||
}
|
||||
|
||||
func (am *ACMEManager) doIssue(ctx context.Context, csr *x509.CertificateRequest, useTestCA bool) (*IssuedCertificate, bool, error) {
|
||||
client, err := am.newACMEClientWithRetry(useTestCA)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
usingTestCA := client.usingTestCA()
|
||||
|
||||
nameSet := namesFromCSR(csr)
|
||||
|
||||
if !useTestCA {
|
||||
if err := client.throttle(ctx, nameSet); err != nil {
|
||||
return nil, usingTestCA, err
|
||||
}
|
||||
}
|
||||
|
||||
certRes, err := client.tryAllEnabledChallenges(ctx, csr)
|
||||
if err != nil {
|
||||
return nil, usingTestCA, fmt.Errorf("%v %w", nameSet, err)
|
||||
}
|
||||
|
||||
ic := &IssuedCertificate{
|
||||
Certificate: certRes.Certificate,
|
||||
Metadata: certRes,
|
||||
}
|
||||
|
||||
return ic, usingTestCA, nil
|
||||
}
|
||||
|
||||
func (c *acmeClient) tryAllEnabledChallenges(ctx context.Context, csr *x509.CertificateRequest) (*certificate.Resource, error) {
|
||||
// start with all enabled challenges
|
||||
challenges := c.initialChallenges()
|
||||
if len(challenges) == 0 {
|
||||
return nil, fmt.Errorf("no challenge types enabled")
|
||||
}
|
||||
|
||||
// try while a challenge type is still available
|
||||
var cert *certificate.Resource
|
||||
var err error
|
||||
for len(challenges) > 0 {
|
||||
var chosenChallenge challenge.Type
|
||||
chosenChallenge, challenges = c.nextChallenge(challenges)
|
||||
cert, err = c.acmeClient.Certificate.ObtainForCSR(*csr, true)
|
||||
if err == nil {
|
||||
return cert, nil
|
||||
}
|
||||
log.Printf("[ERROR] %s (challenge=%s remaining=%v)", err, chosenChallenge, challenges)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
return cert, err
|
||||
}
|
||||
|
||||
// Revoke implements the Revoker interface. It revokes the given certificate.
|
||||
func (am *ACMEManager) Revoke(ctx context.Context, cert CertificateResource) error {
|
||||
client, err := am.newACMEClient(false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meta := cert.IssuerData.(map[string]interface{})
|
||||
cr := certificate.Resource{
|
||||
Domain: meta["domain"].(string),
|
||||
CertURL: meta["certUrl"].(string),
|
||||
CertStableURL: meta["certStableURL"].(string),
|
||||
}
|
||||
|
||||
return client.revoke(ctx, cr)
|
||||
}
|
||||
|
||||
// ExternalAccountBinding contains information for
|
||||
// binding an external account to an ACME account.
|
||||
type ExternalAccountBinding struct {
|
||||
KeyID string
|
||||
HMAC []byte
|
||||
}
|
||||
|
||||
// DefaultACME specifies the default settings
|
||||
// to use for ACMEManagers.
|
||||
var DefaultACME = ACMEManager{
|
||||
CA: LetsEncryptProductionCA,
|
||||
TestCA: LetsEncryptStagingCA,
|
||||
}
|
||||
|
||||
// Some well-known CA endpoints available to use.
|
||||
const (
|
||||
LetsEncryptStagingCA = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
LetsEncryptProductionCA = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
)
|
||||
|
||||
// prefixACME is the storage key prefix used for ACME-specific assets.
|
||||
const prefixACME = "acme"
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ PreChecker = (*ACMEManager)(nil)
|
||||
_ Issuer = (*ACMEManager)(nil)
|
||||
_ Revoker = (*ACMEManager)(nil)
|
||||
)
|
162
vendor/github.com/caddyserver/certmagic/async.go
generated
vendored
Normal file
162
vendor/github.com/caddyserver/certmagic/async.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var jm = &jobManager{maxConcurrentJobs: 1000}
|
||||
|
||||
type jobManager struct {
|
||||
mu sync.Mutex
|
||||
maxConcurrentJobs int
|
||||
activeWorkers int
|
||||
queue []namedJob
|
||||
names map[string]struct{}
|
||||
}
|
||||
|
||||
type namedJob struct {
|
||||
name string
|
||||
job func() error
|
||||
}
|
||||
|
||||
// Submit enqueues the given job with the given name. If name is non-empty
|
||||
// and a job with the same name is already enqueued or running, this is a
|
||||
// no-op. If name is empty, no duplicate prevention will occur. The job
|
||||
// manager will then run this job as soon as it is able.
|
||||
func (jm *jobManager) Submit(name string, job func() error) {
|
||||
jm.mu.Lock()
|
||||
defer jm.mu.Unlock()
|
||||
if jm.names == nil {
|
||||
jm.names = make(map[string]struct{})
|
||||
}
|
||||
if name != "" {
|
||||
// prevent duplicate jobs
|
||||
if _, ok := jm.names[name]; ok {
|
||||
return
|
||||
}
|
||||
jm.names[name] = struct{}{}
|
||||
}
|
||||
jm.queue = append(jm.queue, namedJob{name, job})
|
||||
if jm.activeWorkers < jm.maxConcurrentJobs {
|
||||
jm.activeWorkers++
|
||||
go jm.worker()
|
||||
}
|
||||
}
|
||||
|
||||
func (jm *jobManager) worker() {
|
||||
for {
|
||||
jm.mu.Lock()
|
||||
if len(jm.queue) == 0 {
|
||||
jm.activeWorkers--
|
||||
jm.mu.Unlock()
|
||||
return
|
||||
}
|
||||
next := jm.queue[0]
|
||||
jm.queue = jm.queue[1:]
|
||||
jm.mu.Unlock()
|
||||
if err := next.job(); err != nil {
|
||||
log.Printf("[ERROR] %v", err)
|
||||
}
|
||||
if next.name != "" {
|
||||
jm.mu.Lock()
|
||||
delete(jm.names, next.name)
|
||||
jm.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doWithRetry(ctx context.Context, f func(context.Context) error) error {
|
||||
var attempts int
|
||||
ctx = context.WithValue(ctx, AttemptsCtxKey, &attempts)
|
||||
|
||||
// the initial intervalIndex is -1, signaling
|
||||
// that we should not wait for the first attempt
|
||||
start, intervalIndex := time.Now(), -1
|
||||
var err error
|
||||
|
||||
for time.Since(start) < maxRetryDuration {
|
||||
var wait time.Duration
|
||||
if intervalIndex >= 0 {
|
||||
wait = retryIntervals[intervalIndex]
|
||||
}
|
||||
timer := time.NewTimer(wait)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return context.Canceled
|
||||
case <-timer.C:
|
||||
err = f(ctx)
|
||||
attempts++
|
||||
if err == nil || errors.Is(err, context.Canceled) {
|
||||
return err
|
||||
}
|
||||
var errNoRetry ErrNoRetry
|
||||
if errors.As(err, &errNoRetry) {
|
||||
return err
|
||||
}
|
||||
if intervalIndex < len(retryIntervals)-1 {
|
||||
intervalIndex++
|
||||
}
|
||||
if time.Since(start) < maxRetryDuration {
|
||||
log.Printf("[ERROR] attempt %d: %v - retrying in %s (%s/%s elapsed)...",
|
||||
attempts, err, retryIntervals[intervalIndex], time.Since(start), maxRetryDuration)
|
||||
} else {
|
||||
log.Printf("[ERROR] final attempt: %v - giving up (%s/%s elapsed)...",
|
||||
err, time.Since(start), maxRetryDuration)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ErrNoRetry is an error type which signals
|
||||
// to stop retries early.
|
||||
type ErrNoRetry struct{ Err error }
|
||||
|
||||
// Unwrap makes it so that e wraps e.Err.
|
||||
func (e ErrNoRetry) Unwrap() error { return e.Err }
|
||||
func (e ErrNoRetry) Error() string { return e.Err.Error() }
|
||||
|
||||
type retryStateCtxKey struct{}
|
||||
|
||||
// AttemptsCtxKey is the context key for the value
|
||||
// that holds the attempt counter. The value counts
|
||||
// how many times the operation has been attempted.
|
||||
// A value of 0 means first attempt.
|
||||
var AttemptsCtxKey retryStateCtxKey
|
||||
|
||||
// retryIntervals are based on the idea of exponential
|
||||
// backoff, but weighed a little more heavily to the
|
||||
// front. We figure that intermittent errors would be
|
||||
// resolved after the first retry, but any errors after
|
||||
// that would probably require at least a few minutes
|
||||
// to clear up: either for DNS to propagate, for the
|
||||
// administrator to fix their DNS or network properties,
|
||||
// or some other external factor needs to change. We
|
||||
// chose intervals that we think will be most useful
|
||||
// without introducing unnecessary delay. The last
|
||||
// interval in this list will be used until the time
|
||||
// of maxRetryDuration has elapsed.
|
||||
var retryIntervals = []time.Duration{
|
||||
1 * time.Minute,
|
||||
2 * time.Minute,
|
||||
2 * time.Minute,
|
||||
5 * time.Minute, // elapsed: 10 min
|
||||
10 * time.Minute,
|
||||
20 * time.Minute,
|
||||
20 * time.Minute, // elapsed: 1 hr
|
||||
30 * time.Minute,
|
||||
30 * time.Minute, // elapsed: 2 hr
|
||||
1 * time.Hour,
|
||||
3 * time.Hour, // elapsed: 6 hr
|
||||
6 * time.Hour, // for up to maxRetryDuration
|
||||
}
|
||||
|
||||
// maxRetryDuration is the maximum duration to try
|
||||
// doing retries using the above intervals.
|
||||
const maxRetryDuration = 24 * time.Hour * 30
|
85
vendor/github.com/caddyserver/certmagic/azure-pipelines.yml
generated
vendored
Normal file
85
vendor/github.com/caddyserver/certmagic/azure-pipelines.yml
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
linux:
|
||||
imageName: ubuntu-16.04
|
||||
gorootDir: /usr/local
|
||||
mac:
|
||||
imageName: macos-10.13
|
||||
gorootDir: /usr/local
|
||||
windows:
|
||||
imageName: windows-2019
|
||||
gorootDir: C:\
|
||||
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
|
||||
variables:
|
||||
GOROOT: $(gorootDir)/go
|
||||
GOPATH: $(system.defaultWorkingDirectory)/gopath
|
||||
GOBIN: $(GOPATH)/bin
|
||||
modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)'
|
||||
# TODO: modules should be the default in Go 1.13, so this won't be needed
|
||||
#GO111MODULE: on
|
||||
|
||||
steps:
|
||||
- bash: |
|
||||
latestGo=$(curl "https://golang.org/VERSION?m=text")
|
||||
echo "##vso[task.setvariable variable=LATEST_GO]$latestGo"
|
||||
echo "Latest Go version: $latestGo"
|
||||
displayName: "Get latest Go version"
|
||||
|
||||
- bash: |
|
||||
sudo rm -f $(which go)
|
||||
echo '##vso[task.prependpath]$(GOBIN)'
|
||||
echo '##vso[task.prependpath]$(GOROOT)/bin'
|
||||
mkdir -p '$(modulePath)'
|
||||
shopt -s extglob
|
||||
shopt -s dotglob
|
||||
mv !(gopath) '$(modulePath)'
|
||||
displayName: Remove old Go, set GOBIN/GOROOT, and move project into GOPATH
|
||||
|
||||
# Install Go (this varies by platform)
|
||||
|
||||
- bash: |
|
||||
wget "https://dl.google.com/go/$(LATEST_GO).linux-amd64.tar.gz"
|
||||
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).linux-amd64.tar.gz"
|
||||
condition: eq( variables['Agent.OS'], 'Linux' )
|
||||
displayName: Install Go on Linux
|
||||
|
||||
- bash: |
|
||||
wget "https://dl.google.com/go/$(LATEST_GO).darwin-amd64.tar.gz"
|
||||
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).darwin-amd64.tar.gz"
|
||||
condition: eq( variables['Agent.OS'], 'Darwin' )
|
||||
displayName: Install Go on macOS
|
||||
|
||||
- powershell: |
|
||||
Write-Host "Downloading Go... (please be patient, I am very slow)"
|
||||
(New-Object System.Net.WebClient).DownloadFile("https://dl.google.com/go/$(LATEST_GO).windows-amd64.zip", "$(LATEST_GO).windows-amd64.zip")
|
||||
Write-Host "Extracting Go... (I'm slow too)"
|
||||
Expand-Archive "$(LATEST_GO).windows-amd64.zip" -DestinationPath "$(gorootDir)"
|
||||
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
||||
displayName: Install Go on Windows
|
||||
|
||||
# TODO: When this issue is fixed, replace with installer script:
|
||||
# https://github.com/golangci/golangci-lint/issues/472
|
||||
- script: go get -v github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
displayName: Install golangci-lint
|
||||
|
||||
- bash: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
printf "\n\nGo environment:\n\n"
|
||||
go env
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
displayName: Print Go version and environment
|
||||
|
||||
- script: |
|
||||
go get -v -t -d ./...
|
||||
golangci-lint run -E gofmt -E goimports -E misspell
|
||||
go test -race ./...
|
||||
workingDirectory: '$(modulePath)'
|
||||
displayName: Run tests
|
@ -16,6 +16,8 @@ package certmagic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -153,10 +155,10 @@ type CacheOptions struct {
|
||||
RenewCheckInterval time.Duration
|
||||
}
|
||||
|
||||
// ConfigGetter is a function that returns a config that
|
||||
// should be used when managing the given certificate
|
||||
// or its assets.
|
||||
type ConfigGetter func(Certificate) (Config, error)
|
||||
// ConfigGetter is a function that returns a prepared,
|
||||
// valid config that should be used when managing the
|
||||
// given certificate or its assets.
|
||||
type ConfigGetter func(Certificate) (*Config, error)
|
||||
|
||||
// cacheCertificate calls unsyncedCacheCertificate with a write lock.
|
||||
//
|
||||
@ -175,16 +177,16 @@ func (certCache *Cache) cacheCertificate(cert Certificate) {
|
||||
// a write lock on certCache.mu first.
|
||||
func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
|
||||
// no-op if this certificate already exists in the cache
|
||||
if _, ok := certCache.cache[cert.Hash]; ok {
|
||||
if _, ok := certCache.cache[cert.hash]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
// store the certificate
|
||||
certCache.cache[cert.Hash] = cert
|
||||
certCache.cache[cert.hash] = cert
|
||||
|
||||
// update the index so we can access it by name
|
||||
for _, name := range cert.Names {
|
||||
certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.Hash)
|
||||
certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.hash)
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +199,7 @@ func (certCache *Cache) removeCertificate(cert Certificate) {
|
||||
for _, name := range cert.Names {
|
||||
keyList := certCache.cacheIndex[name]
|
||||
for i, cacheKey := range keyList {
|
||||
if cacheKey == cert.Hash {
|
||||
if cacheKey == cert.hash {
|
||||
keyList = append(keyList[:i], keyList[i+1:]...)
|
||||
}
|
||||
}
|
||||
@ -209,7 +211,7 @@ func (certCache *Cache) removeCertificate(cert Certificate) {
|
||||
}
|
||||
|
||||
// delete the actual cert from the cache
|
||||
delete(certCache.cache, cert.Hash)
|
||||
delete(certCache.cache, cert.hash)
|
||||
}
|
||||
|
||||
// replaceCertificate atomically replaces oldCert with newCert in
|
||||
@ -221,24 +223,18 @@ func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) {
|
||||
certCache.removeCertificate(oldCert)
|
||||
certCache.unsyncedCacheCertificate(newCert)
|
||||
certCache.mu.Unlock()
|
||||
log.Printf("[INFO] Replaced certificate in cache for %v (new expiration date: %s)",
|
||||
newCert.Names, newCert.Leaf.NotAfter.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
func (certCache *Cache) getFirstMatchingCert(name string) (Certificate, bool) {
|
||||
certCache.mu.RLock()
|
||||
defer certCache.mu.RUnlock()
|
||||
|
||||
allCertKeys := certCache.cacheIndex[name]
|
||||
if len(allCertKeys) == 0 {
|
||||
return Certificate{}, false
|
||||
all := certCache.getAllMatchingCerts(name)
|
||||
if len(all) == 0 {
|
||||
return all[0], true
|
||||
}
|
||||
|
||||
cert, ok := certCache.cache[allCertKeys[0]]
|
||||
return cert, ok
|
||||
return Certificate{}, false
|
||||
}
|
||||
|
||||
// TODO: This seems unused (but could be useful if TLS
|
||||
// handshakes serve up different certs for a single
|
||||
// name depending on other properties such as key type)
|
||||
func (certCache *Cache) getAllMatchingCerts(name string) []Certificate {
|
||||
certCache.mu.RLock()
|
||||
defer certCache.mu.RUnlock()
|
||||
@ -253,6 +249,16 @@ func (certCache *Cache) getAllMatchingCerts(name string) []Certificate {
|
||||
return certs
|
||||
}
|
||||
|
||||
func (certCache *Cache) getAllCerts() []Certificate {
|
||||
certCache.mu.RLock()
|
||||
defer certCache.mu.RUnlock()
|
||||
certs := make([]Certificate, 0, len(certCache.cache))
|
||||
for _, cert := range certCache.cache {
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
func (certCache *Cache) getConfig(cert Certificate) (*Config, error) {
|
||||
cfg, err := certCache.options.GetConfigForCert(cert)
|
||||
if err != nil {
|
||||
@ -262,7 +268,26 @@ func (certCache *Cache) getConfig(cert Certificate) (*Config, error) {
|
||||
return nil, fmt.Errorf("config returned for certificate %v is not nil and points to different cache; got %p, expected %p (this one)",
|
||||
cert.Names, cfg.certCache, certCache)
|
||||
}
|
||||
return New(certCache, cfg), nil
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// AllMatchingCertificates returns a list of all certificates that could
|
||||
// be used to serve the given SNI name, including exact SAN matches and
|
||||
// wildcard matches.
|
||||
func (certCache *Cache) AllMatchingCertificates(name string) []Certificate {
|
||||
// get exact matches first
|
||||
certs := certCache.getAllMatchingCerts(name)
|
||||
|
||||
// then look for wildcard matches by replacing each
|
||||
// label of the domain name with wildcards
|
||||
labels := strings.Split(name, ".")
|
||||
for i := range labels {
|
||||
labels[i] = "*"
|
||||
candidate := strings.Join(labels, ".")
|
||||
certs = append(certs, certCache.getAllMatchingCerts(candidate)...)
|
||||
}
|
||||
|
||||
return certs
|
||||
}
|
||||
|
||||
var (
|
@ -29,22 +29,23 @@ import (
|
||||
|
||||
// Certificate is a tls.Certificate with associated metadata tacked on.
|
||||
// Even if the metadata can be obtained by parsing the certificate,
|
||||
// we are more efficient by extracting the metadata onto this struct.
|
||||
// we are more efficient by extracting the metadata onto this struct,
|
||||
// but at the cost of slightly higher memory use.
|
||||
type Certificate struct {
|
||||
tls.Certificate
|
||||
|
||||
// Names is the list of names this certificate is written for.
|
||||
// The first is the CommonName (if any), the rest are SAN.
|
||||
// Names is the list of subject names this
|
||||
// certificate is signed for.
|
||||
Names []string
|
||||
|
||||
// NotAfter is when the certificate expires.
|
||||
NotAfter time.Time
|
||||
// Optional; user-provided, and arbitrary.
|
||||
Tags []string
|
||||
|
||||
// OCSP contains the certificate's parsed OCSP response.
|
||||
OCSP *ocsp.Response
|
||||
ocsp *ocsp.Response
|
||||
|
||||
// The hex-encoded hash of this cert's chain's bytes.
|
||||
Hash string
|
||||
hash string
|
||||
|
||||
// Whether this certificate is under our management
|
||||
managed bool
|
||||
@ -53,14 +54,34 @@ type Certificate struct {
|
||||
// NeedsRenewal returns true if the certificate is
|
||||
// expiring soon (according to cfg) or has expired.
|
||||
func (cert Certificate) NeedsRenewal(cfg *Config) bool {
|
||||
if cert.NotAfter.IsZero() {
|
||||
return currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio)
|
||||
}
|
||||
|
||||
// currentlyInRenewalWindow returns true if the current time is
|
||||
// within the renewal window, according to the given start/end
|
||||
// dates and the ratio of the renewal window. If true is returned,
|
||||
// the certificate being considered is due for renewal.
|
||||
func currentlyInRenewalWindow(notBefore, notAfter time.Time, renewalWindowRatio float64) bool {
|
||||
if notAfter.IsZero() {
|
||||
return false
|
||||
}
|
||||
renewDurationBefore := DefaultRenewDurationBefore
|
||||
if cfg.RenewDurationBefore > 0 {
|
||||
renewDurationBefore = cfg.RenewDurationBefore
|
||||
lifetime := notAfter.Sub(notBefore)
|
||||
if renewalWindowRatio == 0 {
|
||||
renewalWindowRatio = DefaultRenewalWindowRatio
|
||||
}
|
||||
return time.Until(cert.NotAfter) < renewDurationBefore
|
||||
renewalWindow := time.Duration(float64(lifetime) * renewalWindowRatio)
|
||||
renewalWindowStart := notAfter.Add(-renewalWindow)
|
||||
return time.Now().After(renewalWindowStart)
|
||||
}
|
||||
|
||||
// HasTag returns true if cert.Tags has tag.
|
||||
func (cert Certificate) HasTag(tag string) bool {
|
||||
for _, t := range cert.Tags {
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CacheManagedCertificate loads the certificate for domain into the
|
||||
@ -76,9 +97,7 @@ func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
|
||||
return cert, err
|
||||
}
|
||||
cfg.certCache.cacheCertificate(cert)
|
||||
if cfg.OnEvent != nil {
|
||||
cfg.OnEvent("cached_managed_cert", cert.Names)
|
||||
}
|
||||
cfg.emit("cached_managed_cert", cert.Names)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
@ -89,7 +108,7 @@ func (cfg *Config) loadManagedCertificate(domain string) (Certificate, error) {
|
||||
if err != nil {
|
||||
return Certificate{}, err
|
||||
}
|
||||
cert, err := makeCertificateWithOCSP(cfg.Storage, certRes.Certificate, certRes.PrivateKey)
|
||||
cert, err := makeCertificateWithOCSP(cfg.Storage, certRes.CertificatePEM, certRes.PrivateKeyPEM)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
@ -102,15 +121,14 @@ func (cfg *Config) loadManagedCertificate(domain string) (Certificate, error) {
|
||||
// the in-memory cache.
|
||||
//
|
||||
// This method is safe for concurrent use.
|
||||
func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string) error {
|
||||
func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string, tags []string) error {
|
||||
cert, err := makeCertificateFromDiskWithOCSP(cfg.Storage, certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.Tags = tags
|
||||
cfg.certCache.cacheCertificate(cert)
|
||||
if cfg.OnEvent != nil {
|
||||
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
|
||||
}
|
||||
cfg.emit("cached_unmanaged_cert", cert.Names)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -118,19 +136,18 @@ func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string) er
|
||||
// It staples OCSP if possible.
|
||||
//
|
||||
// This method is safe for concurrent use.
|
||||
func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate) error {
|
||||
func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate, tags []string) error {
|
||||
var cert Certificate
|
||||
err := fillCertFromLeaf(&cert, tlsCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = stapleOCSP(cfg.Storage, &cert, nil)
|
||||
_, err = stapleOCSP(cfg.Storage, &cert, nil)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Stapling OCSP: %v", err)
|
||||
}
|
||||
if cfg.OnEvent != nil {
|
||||
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
|
||||
}
|
||||
cfg.emit("cached_unmanaged_cert", cert.Names)
|
||||
cert.Tags = tags
|
||||
cfg.certCache.cacheCertificate(cert)
|
||||
return nil
|
||||
}
|
||||
@ -139,15 +156,14 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate) error {
|
||||
// of the certificate and key, then caches it in memory.
|
||||
//
|
||||
// This method is safe for concurrent use.
|
||||
func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) error {
|
||||
func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte, tags []string) error {
|
||||
cert, err := makeCertificateWithOCSP(cfg.Storage, certBytes, keyBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.Tags = tags
|
||||
cfg.certCache.cacheCertificate(cert)
|
||||
if cfg.OnEvent != nil {
|
||||
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
|
||||
}
|
||||
cfg.emit("cached_unmanaged_cert", cert.Names)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -174,7 +190,7 @@ func makeCertificateWithOCSP(storage Storage, certPEMBlock, keyPEMBlock []byte)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
err = stapleOCSP(storage, &cert, certPEMBlock)
|
||||
_, err = stapleOCSP(storage, &cert, certPEMBlock)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Stapling OCSP: %v", err)
|
||||
}
|
||||
@ -204,19 +220,25 @@ func makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// fillCertFromLeaf populates metadata fields on cert from tlsCert.
|
||||
// fillCertFromLeaf populates cert from tlsCert. If it succeeds, it
|
||||
// guarantees that cert.Leaf is non-nil.
|
||||
func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
|
||||
if len(tlsCert.Certificate) == 0 {
|
||||
return fmt.Errorf("certificate is empty")
|
||||
}
|
||||
cert.Certificate = tlsCert
|
||||
|
||||
// the leaf cert should be the one for the site; it has what we need
|
||||
// the leaf cert should be the one for the site; we must set
|
||||
// the tls.Certificate.Leaf field so that TLS handshakes are
|
||||
// more efficient
|
||||
leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.Certificate.Leaf = leaf
|
||||
|
||||
// for convenience, we do want to assemble all the
|
||||
// subjects on the certificate into one list
|
||||
if leaf.Subject.CommonName != "" { // TODO: CommonName is deprecated
|
||||
cert.Names = []string{strings.ToLower(leaf.Subject.CommonName)}
|
||||
}
|
||||
@ -235,14 +257,18 @@ func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
|
||||
cert.Names = append(cert.Names, strings.ToLower(email))
|
||||
}
|
||||
}
|
||||
for _, u := range leaf.URIs {
|
||||
if u.String() != leaf.Subject.CommonName { // TODO: CommonName is deprecated
|
||||
cert.Names = append(cert.Names, u.String())
|
||||
}
|
||||
}
|
||||
if len(cert.Names) == 0 {
|
||||
return fmt.Errorf("certificate has no names")
|
||||
}
|
||||
|
||||
// save the hash of this certificate (chain) and
|
||||
// expiration date, for necessity and efficiency
|
||||
cert.Hash = hashCertificateChain(cert.Certificate.Certificate)
|
||||
cert.NotAfter = leaf.NotAfter
|
||||
cert.hash = hashCertificateChain(cert.Certificate.Certificate)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -261,7 +287,7 @@ func (cfg *Config) managedCertInStorageExpiresSoon(cert Certificate) (bool, erro
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
tlsCert, err := tls.X509KeyPair(certRes.Certificate, certRes.PrivateKey)
|
||||
tlsCert, err := tls.X509KeyPair(certRes.CertificatePEM, certRes.PrivateKeyPEM)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -269,15 +295,16 @@ func (cfg *Config) managedCertInStorageExpiresSoon(cert Certificate) (bool, erro
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
timeLeft := leaf.NotAfter.Sub(time.Now().UTC())
|
||||
return timeLeft < cfg.RenewDurationBefore, nil
|
||||
return currentlyInRenewalWindow(leaf.NotBefore, leaf.NotAfter, cfg.RenewalWindowRatio), nil
|
||||
}
|
||||
|
||||
// reloadManagedCertificate reloads the certificate corresponding to the name(s)
|
||||
// on oldCert into the cache, from storage. This also replaces the old certificate
|
||||
// with the new one, so that all configurations that used the old cert now point
|
||||
// to the new cert.
|
||||
// to the new cert. It assumes that the new certificate for oldCert.Names[0] is
|
||||
// already in storage.
|
||||
func (cfg *Config) reloadManagedCertificate(oldCert Certificate) error {
|
||||
log.Printf("[INFO] Reloading managed certificate for %v", oldCert.Names)
|
||||
newCert, err := cfg.loadManagedCertificate(oldCert.Names[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading managed certificate for %v from storage: %v", oldCert.Names, err)
|
||||
@ -286,29 +313,78 @@ func (cfg *Config) reloadManagedCertificate(oldCert Certificate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// HostQualifies returns true if the hostname alone
|
||||
// appears eligible for automagic TLS. For example:
|
||||
// localhost, empty hostname, and IP addresses are
|
||||
// not eligible because we cannot obtain certificates
|
||||
// for those names. Wildcard names are allowed, as long
|
||||
// as they conform to CABF requirements (only one wildcard
|
||||
// label, and it must be the left-most label).
|
||||
func HostQualifies(hostname string) bool {
|
||||
return hostname != "localhost" && // localhost is ineligible
|
||||
|
||||
// hostname must not be empty
|
||||
strings.TrimSpace(hostname) != "" &&
|
||||
|
||||
// only one wildcard label allowed, and it must be left-most
|
||||
(!strings.Contains(hostname, "*") ||
|
||||
(strings.Count(hostname, "*") == 1 &&
|
||||
strings.HasPrefix(hostname, "*."))) &&
|
||||
// SubjectQualifiesForCert returns true if subj is a name which,
|
||||
// as a quick sanity check, looks like it could be the subject
|
||||
// of a certificate. Requirements are:
|
||||
// - must not be empty
|
||||
// - must not start or end with a dot (RFC 1034)
|
||||
// - must not contain common accidental special characters
|
||||
func SubjectQualifiesForCert(subj string) bool {
|
||||
// must not be empty
|
||||
return strings.TrimSpace(subj) != "" &&
|
||||
|
||||
// must not start or end with a dot
|
||||
!strings.HasPrefix(hostname, ".") &&
|
||||
!strings.HasSuffix(hostname, ".") &&
|
||||
!strings.HasPrefix(subj, ".") &&
|
||||
!strings.HasSuffix(subj, ".") &&
|
||||
|
||||
// cannot be an IP address, see
|
||||
// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
|
||||
net.ParseIP(hostname) == nil
|
||||
// if it has a wildcard, must be a left-most label
|
||||
(!strings.Contains(subj, "*") || strings.HasPrefix(subj, "*.")) &&
|
||||
|
||||
// must not contain other common special characters
|
||||
!strings.ContainsAny(subj, "()[]{}<> \t\n\"\\!@#$%^&|;'+=")
|
||||
}
|
||||
|
||||
// SubjectQualifiesForPublicCert returns true if the subject
|
||||
// name appears eligible for automagic TLS with a public
|
||||
// CA such as Let's Encrypt. For example: localhost and IP
|
||||
// addresses are not eligible because we cannot obtain certs
|
||||
// for those names with a public CA. Wildcard names are
|
||||
// allowed, as long as they conform to CABF requirements (only
|
||||
// one wildcard label, and it must be the left-most label).
|
||||
func SubjectQualifiesForPublicCert(subj string) bool {
|
||||
// must at least qualify for certificate
|
||||
return SubjectQualifiesForCert(subj) &&
|
||||
|
||||
// localhost is ineligible
|
||||
subj != "localhost" &&
|
||||
|
||||
// .localhost TLD is ineligible
|
||||
!strings.HasSuffix(subj, ".localhost") &&
|
||||
|
||||
// .local TLD is ineligible
|
||||
!strings.HasSuffix(subj, ".local") &&
|
||||
|
||||
// only one wildcard label allowed, and it must be left-most
|
||||
(!strings.Contains(subj, "*") ||
|
||||
(strings.Count(subj, "*") == 1 &&
|
||||
len(subj) > 2 &&
|
||||
strings.HasPrefix(subj, "*."))) &&
|
||||
|
||||
// cannot be an IP address (as of yet), see
|
||||
// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
|
||||
net.ParseIP(subj) == nil
|
||||
}
|
||||
|
||||
// MatchWildcard returns true if subject (a candidate DNS name)
|
||||
// matches wildcard (a reference DNS name), mostly according to
|
||||
// RFC-compliant wildcard rules.
|
||||
func MatchWildcard(subject, wildcard string) bool {
|
||||
if subject == wildcard {
|
||||
return true
|
||||
}
|
||||
if !strings.Contains(wildcard, "*") {
|
||||
return false
|
||||
}
|
||||
labels := strings.Split(subject, ".")
|
||||
for i := range labels {
|
||||
if labels[i] == "" {
|
||||
continue // invalid label
|
||||
}
|
||||
labels[i] = "*"
|
||||
candidate := strings.Join(labels, ".")
|
||||
if candidate == wildcard {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -35,18 +35,18 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
)
|
||||
|
||||
// HTTPS serves mux for all domainNames using the HTTP
|
||||
@ -70,10 +70,10 @@ func HTTPS(domainNames []string, mux http.Handler) error {
|
||||
mux = http.DefaultServeMux
|
||||
}
|
||||
|
||||
Default.Agreed = true
|
||||
DefaultACME.Agreed = true
|
||||
cfg := NewDefault()
|
||||
|
||||
err := cfg.Manage(domainNames)
|
||||
err := cfg.ManageSync(domainNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -121,7 +121,9 @@ func HTTPS(domainNames []string, mux http.Handler) error {
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
IdleTimeout: 5 * time.Second,
|
||||
Handler: cfg.HTTPChallengeHandler(http.HandlerFunc(httpRedirectHandler)),
|
||||
}
|
||||
if am, ok := cfg.Issuer.(*ACMEManager); ok {
|
||||
httpServer.Handler = am.HTTPChallengeHandler(http.HandlerFunc(httpRedirectHandler))
|
||||
}
|
||||
httpsServer := &http.Server{
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
@ -143,10 +145,7 @@ func httpRedirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// since we redirect to the standard HTTPS port, we
|
||||
// do not need to include it in the redirect URL
|
||||
requestHost, _, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
requestHost = r.Host // host probably did not contain a port
|
||||
}
|
||||
requestHost := hostOnly(r.Host)
|
||||
|
||||
toURL += requestHost
|
||||
toURL += r.URL.RequestURI()
|
||||
@ -171,10 +170,10 @@ func httpRedirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Calling this function signifies your acceptance to
|
||||
// the CA's Subscriber Agreement and/or Terms of Service.
|
||||
func TLS(domainNames []string) (*tls.Config, error) {
|
||||
Default.Agreed = true
|
||||
Default.DisableHTTPChallenge = true
|
||||
DefaultACME.Agreed = true
|
||||
DefaultACME.DisableHTTPChallenge = true
|
||||
cfg := NewDefault()
|
||||
return cfg.TLSConfig(), cfg.Manage(domainNames)
|
||||
return cfg.TLSConfig(), cfg.ManageSync(domainNames)
|
||||
}
|
||||
|
||||
// Listen manages certificates for domainName and returns a
|
||||
@ -188,17 +187,17 @@ func TLS(domainNames []string) (*tls.Config, error) {
|
||||
// Calling this function signifies your acceptance to
|
||||
// the CA's Subscriber Agreement and/or Terms of Service.
|
||||
func Listen(domainNames []string) (net.Listener, error) {
|
||||
Default.Agreed = true
|
||||
Default.DisableHTTPChallenge = true
|
||||
DefaultACME.Agreed = true
|
||||
DefaultACME.DisableHTTPChallenge = true
|
||||
cfg := NewDefault()
|
||||
err := cfg.Manage(domainNames)
|
||||
err := cfg.ManageSync(domainNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tls.Listen("tcp", fmt.Sprintf(":%d", HTTPSPort), cfg.TLSConfig())
|
||||
}
|
||||
|
||||
// Manage obtains certificates for domainNames and keeps them
|
||||
// ManageSync obtains certificates for domainNames and keeps them
|
||||
// renewed using the Default config.
|
||||
//
|
||||
// This is a slightly lower-level function; you will need to
|
||||
@ -219,171 +218,78 @@ func Listen(domainNames []string) (net.Listener, error) {
|
||||
//
|
||||
// Calling this function signifies your acceptance to
|
||||
// the CA's Subscriber Agreement and/or Terms of Service.
|
||||
func Manage(domainNames []string) error {
|
||||
Default.Agreed = true
|
||||
return NewDefault().Manage(domainNames)
|
||||
func ManageSync(domainNames []string) error {
|
||||
DefaultACME.Agreed = true
|
||||
return NewDefault().ManageSync(domainNames)
|
||||
}
|
||||
|
||||
// OnDemandConfig contains some state relevant for providing
|
||||
// on-demand TLS. Important note: If you are using the
|
||||
// MaxObtain property to limit the maximum number of certs
|
||||
// to be issued, the count of how many certs were issued
|
||||
// will be reset if this struct gets garbage-collected.
|
||||
// ManageAsync is the same as ManageSync, except that
|
||||
// certificates are managed asynchronously. This means
|
||||
// that the function will return before certificates
|
||||
// are ready, and errors that occur during certificate
|
||||
// obtain or renew operations are only logged. It is
|
||||
// vital that you monitor the logs if using this method,
|
||||
// which is only recommended for automated/non-interactive
|
||||
// environments.
|
||||
func ManageAsync(ctx context.Context, domainNames []string) error {
|
||||
DefaultACME.Agreed = true
|
||||
return NewDefault().ManageAsync(ctx, domainNames)
|
||||
}
|
||||
|
||||
// OnDemandConfig configures on-demand TLS (certificate
|
||||
// operations as-needed, like during TLS handshakes,
|
||||
// rather than immediately).
|
||||
//
|
||||
// When this package's high-level convenience functions
|
||||
// are used (HTTPS, Manage, etc., where the Default
|
||||
// config is used as a template), this struct regulates
|
||||
// certificate operations using an implicit whitelist
|
||||
// containing the names passed into those functions if
|
||||
// no DecisionFunc is set. This ensures some degree of
|
||||
// control by default to avoid certificate operations for
|
||||
// aribtrary domain names. To override this whitelist,
|
||||
// manually specify a DecisionFunc. To impose rate limits,
|
||||
// specify your own DecisionFunc.
|
||||
type OnDemandConfig struct {
|
||||
// If set, this function will be the absolute
|
||||
// authority on whether the hostname (according
|
||||
// to SNI) is allowed to try to get a cert.
|
||||
// If set, this function will be called to determine
|
||||
// whether a certificate can be obtained or renewed
|
||||
// for the given name. If an error is returned, the
|
||||
// request will be denied.
|
||||
DecisionFunc func(name string) error
|
||||
|
||||
// If no DecisionFunc is set, this whitelist
|
||||
// is the absolute authority as to whether
|
||||
// a certificate should be allowed to be tried.
|
||||
// Names are compared against SNI value.
|
||||
HostWhitelist []string
|
||||
|
||||
// If no DecisionFunc or HostWhitelist are set,
|
||||
// then an HTTP request will be made to AskURL
|
||||
// to determine if a certificate should be
|
||||
// obtained. If the request fails or the response
|
||||
// is anything other than 2xx status code, the
|
||||
// issuance will be denied.
|
||||
AskURL *url.URL
|
||||
|
||||
// If no DecisionFunc, HostWhitelist, or AskURL
|
||||
// are set, then only this many certificates may
|
||||
// be obtained on-demand; this field is required
|
||||
// if all others are empty, otherwise, all cert
|
||||
// issuances will fail.
|
||||
MaxObtain int32
|
||||
|
||||
// The number of certificates that have been issued on-demand
|
||||
// by this config. It is only safe to modify this count atomically.
|
||||
// If it reaches MaxObtain, on-demand issuances must fail.
|
||||
// Note that this will necessarily be reset to 0 if the
|
||||
// struct leaves scope and/or gets garbage-collected.
|
||||
obtainedCount int32
|
||||
}
|
||||
|
||||
// Allowed returns whether the issuance for name is allowed according to o.
|
||||
func (o *OnDemandConfig) Allowed(name string) error {
|
||||
// The decision function has absolute authority, if set
|
||||
if o.DecisionFunc != nil {
|
||||
return o.DecisionFunc(name)
|
||||
}
|
||||
|
||||
// Otherwise, the host whitelist has decision authority
|
||||
if len(o.HostWhitelist) > 0 {
|
||||
return o.checkWhitelistForObtainingNewCerts(name)
|
||||
}
|
||||
|
||||
// Otherwise, a URL is checked for permission to issue this cert
|
||||
if o.AskURL != nil {
|
||||
return o.checkURLForObtainingNewCerts(name)
|
||||
}
|
||||
|
||||
// Otherwise use the limit defined by the "max_certs" setting
|
||||
return o.checkLimitsForObtainingNewCerts(name)
|
||||
// List of whitelisted hostnames (SNI values) for
|
||||
// deferred (on-demand) obtaining of certificates.
|
||||
// Used only by higher-level functions in this
|
||||
// package to persist the list of hostnames that
|
||||
// the config is supposed to manage. This is done
|
||||
// because it seems reasonable that if you say
|
||||
// "Manage [domain names...]", then only those
|
||||
// domain names should be able to have certs;
|
||||
// we don't NEED this feature, but it makes sense
|
||||
// for higher-level convenience functions to be
|
||||
// able to retain their convenience (alternative
|
||||
// is: the user manually creates a DecisionFunc
|
||||
// that whitelists the same names it already
|
||||
// passed into Manage) and without letting clients
|
||||
// have their run of any domain names they want.
|
||||
// Only enforced if len > 0.
|
||||
hostWhitelist []string
|
||||
}
|
||||
|
||||
func (o *OnDemandConfig) whitelistContains(name string) bool {
|
||||
for _, n := range o.HostWhitelist {
|
||||
if strings.ToLower(n) == strings.ToLower(name) {
|
||||
for _, n := range o.hostWhitelist {
|
||||
if strings.EqualFold(n, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *OnDemandConfig) checkWhitelistForObtainingNewCerts(name string) error {
|
||||
if !o.whitelistContains(name) {
|
||||
return fmt.Errorf("%s: name is not whitelisted", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OnDemandConfig) checkURLForObtainingNewCerts(name string) error {
|
||||
client := http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return fmt.Errorf("following http redirects is not allowed")
|
||||
},
|
||||
}
|
||||
|
||||
// Copy the URL from the config in order to modify it for this request
|
||||
askURL := new(url.URL)
|
||||
*askURL = *o.AskURL
|
||||
|
||||
query := askURL.Query()
|
||||
query.Set("domain", name)
|
||||
askURL.RawQuery = query.Encode()
|
||||
|
||||
resp, err := client.Get(askURL.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error checking %v to deterine if certificate for hostname '%s' should be allowed: %v", o.AskURL, name, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return fmt.Errorf("certificate for hostname '%s' not allowed, non-2xx status code %d returned from %v", name, resp.StatusCode, o.AskURL)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkLimitsForObtainingNewCerts checks to see if name can be issued right
|
||||
// now according the maximum count defined in the configuration. If a non-nil
|
||||
// error is returned, do not issue a new certificate for name.
|
||||
func (o *OnDemandConfig) checkLimitsForObtainingNewCerts(name string) error {
|
||||
if o.MaxObtain == 0 {
|
||||
return fmt.Errorf("%s: no certificates allowed to be issued on-demand", name)
|
||||
}
|
||||
|
||||
// User can set hard limit for number of certs for the process to issue
|
||||
if o.MaxObtain > 0 &&
|
||||
atomic.LoadInt32(&o.obtainedCount) >= o.MaxObtain {
|
||||
return fmt.Errorf("%s: maximum certificates issued (%d)", name, o.MaxObtain)
|
||||
}
|
||||
|
||||
// Make sure name hasn't failed a challenge recently
|
||||
failedIssuanceMu.RLock()
|
||||
when, ok := failedIssuance[name]
|
||||
failedIssuanceMu.RUnlock()
|
||||
if ok {
|
||||
return fmt.Errorf("%s: throttled; refusing to issue cert since last attempt on %s failed", name, when.String())
|
||||
}
|
||||
|
||||
// Make sure, if we've issued a few certificates already, that we haven't
|
||||
// issued any recently
|
||||
lastIssueTimeMu.Lock()
|
||||
since := time.Since(lastIssueTime)
|
||||
lastIssueTimeMu.Unlock()
|
||||
if atomic.LoadInt32(&o.obtainedCount) >= 10 && since < 10*time.Minute {
|
||||
return fmt.Errorf("%s: throttled; last certificate was obtained %v ago", name, since)
|
||||
}
|
||||
|
||||
// Good to go 👍
|
||||
return nil
|
||||
}
|
||||
|
||||
// failedIssuance is a set of names that we recently failed to get a
|
||||
// certificate for from the ACME CA. They are removed after some time.
|
||||
// When a name is in this map, do not issue a certificate for it on-demand.
|
||||
var failedIssuance = make(map[string]time.Time)
|
||||
var failedIssuanceMu sync.RWMutex
|
||||
|
||||
// lastIssueTime records when we last obtained a certificate successfully.
|
||||
// If this value is recent, do not make any on-demand certificate requests.
|
||||
var lastIssueTime time.Time
|
||||
var lastIssueTimeMu sync.Mutex
|
||||
|
||||
// isLoopback returns true if the hostname of addr looks
|
||||
// explicitly like a common local hostname. addr must only
|
||||
// be a host or a host:port combination.
|
||||
func isLoopback(addr string) bool {
|
||||
host, _, err := net.SplitHostPort(strings.ToLower(addr))
|
||||
if err != nil {
|
||||
host = addr // happens if the addr is only a hostname
|
||||
}
|
||||
host := hostOnly(addr)
|
||||
return host == "localhost" ||
|
||||
strings.Trim(host, "[]") == "::1" ||
|
||||
strings.HasPrefix(host, "127.")
|
||||
@ -400,13 +306,7 @@ func isInternal(addr string) bool {
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7",
|
||||
}
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
host = addr // happens if the addr is just a hostname, missing port
|
||||
// if we encounter an error, the brackets need to be stripped
|
||||
// because SplitHostPort didn't do it for us
|
||||
host = strings.Trim(host, "[]")
|
||||
}
|
||||
host := hostOnly(addr)
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return false
|
||||
@ -420,6 +320,104 @@ func isInternal(addr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// hostOnly returns only the host portion of hostport.
|
||||
// If there is no port or if there is an error splitting
|
||||
// the port off, the whole input string is returned.
|
||||
func hostOnly(hostport string) string {
|
||||
host, _, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
return hostport // OK; probably had no port to begin with
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
// PreChecker is an interface that can be optionally implemented by
|
||||
// Issuers. Pre-checks are performed before each call (or batch of
|
||||
// identical calls) to Issue(), giving the issuer the option to ensure
|
||||
// it has all the necessary information/state.
|
||||
type PreChecker interface {
|
||||
PreCheck(names []string, interactive bool) error
|
||||
}
|
||||
|
||||
// Issuer is a type that can issue certificates.
|
||||
type Issuer interface {
|
||||
// Issue obtains a certificate for the given CSR. It
|
||||
// must honor context cancellation if it is long-running.
|
||||
// It can also use the context to find out if the current
|
||||
// call is part of a retry, via AttemptsCtxKey.
|
||||
Issue(ctx context.Context, request *x509.CertificateRequest) (*IssuedCertificate, error)
|
||||
|
||||
// IssuerKey must return a string that uniquely identifies
|
||||
// this particular configuration of the Issuer such that
|
||||
// any certificates obtained by this Issuer will be treated
|
||||
// as identical if they have the same SANs.
|
||||
//
|
||||
// Certificates obtained from Issuers with the same IssuerKey
|
||||
// will overwrite others with the same SANs. For example, an
|
||||
// Issuer might be able to obtain certificates from different
|
||||
// CAs, say A and B. It is likely that the CAs have different
|
||||
// use cases and purposes (e.g. testing and production), so
|
||||
// their respective certificates should not overwrite eaach
|
||||
// other.
|
||||
IssuerKey() string
|
||||
}
|
||||
|
||||
// Revoker can revoke certificates.
|
||||
type Revoker interface {
|
||||
Revoke(ctx context.Context, cert CertificateResource) error
|
||||
}
|
||||
|
||||
// KeyGenerator can generate a private key.
|
||||
type KeyGenerator interface {
|
||||
// GenerateKey generates a private key. The returned
|
||||
// PrivateKey must be able to expose its associated
|
||||
// public key.
|
||||
GenerateKey() (crypto.PrivateKey, error)
|
||||
}
|
||||
|
||||
// IssuedCertificate represents a certificate that was just issued.
|
||||
type IssuedCertificate struct {
|
||||
// The PEM-encoding of DER-encoded ASN.1 data.
|
||||
Certificate []byte
|
||||
|
||||
// Any extra information to serialize alongside the
|
||||
// certificate in storage.
|
||||
Metadata interface{}
|
||||
}
|
||||
|
||||
// CertificateResource associates a certificate with its private
|
||||
// key and other useful information, for use in maintaining the
|
||||
// certificate.
|
||||
type CertificateResource struct {
|
||||
// The list of names on the certificate;
|
||||
// for convenience only.
|
||||
SANs []string `json:"sans,omitempty"`
|
||||
|
||||
// The PEM-encoding of DER-encoded ASN.1 data
|
||||
// for the cert or chain.
|
||||
CertificatePEM []byte `json:"-"`
|
||||
|
||||
// The PEM-encoding of the certificate's private key.
|
||||
PrivateKeyPEM []byte `json:"-"`
|
||||
|
||||
// Any extra information associated with the certificate,
|
||||
// usually provided by the issuer implementation.
|
||||
IssuerData interface{} `json:"issuer_data,omitempty"`
|
||||
}
|
||||
|
||||
// NamesKey returns the list of SANs as a single string,
|
||||
// truncated to some ridiculously long size limit. It
|
||||
// can act as a key for the set of names on the resource.
|
||||
func (cr *CertificateResource) NamesKey() string {
|
||||
sort.Strings(cr.SANs)
|
||||
result := strings.Join(cr.SANs, ",")
|
||||
if len(result) > 1024 {
|
||||
const trunc = "_trunc"
|
||||
result = result[:1024-len(trunc)] + trunc
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Default contains the package defaults for the
|
||||
// various Config fields. This is used as a template
|
||||
// when creating your own Configs with New(), and it
|
||||
@ -436,11 +434,9 @@ func isInternal(addr string) bool {
|
||||
// cache). This is the only Config which can access
|
||||
// the default certificate cache.
|
||||
var Default = Config{
|
||||
CA: LetsEncryptProductionCA,
|
||||
RenewDurationBefore: DefaultRenewDurationBefore,
|
||||
RenewDurationBeforeAtStartup: DefaultRenewDurationBeforeAtStartup,
|
||||
KeyType: certcrypto.EC256,
|
||||
Storage: defaultFileStorage,
|
||||
RenewalWindowRatio: DefaultRenewalWindowRatio,
|
||||
Storage: defaultFileStorage,
|
||||
KeySource: DefaultKeyGenerator,
|
||||
}
|
||||
|
||||
const (
|
||||
@ -453,12 +449,6 @@ const (
|
||||
TLSALPNChallengePort = 443
|
||||
)
|
||||
|
||||
// Some well-known CA endpoints available to use.
|
||||
const (
|
||||
LetsEncryptStagingCA = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
LetsEncryptProductionCA = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
)
|
||||
|
||||
// Port variables must remain their defaults unless you
|
||||
// forward packets from the defaults to whatever these
|
||||
// are set to; otherwise ACME challenges will fail.
|
758
vendor/github.com/caddyserver/certmagic/config.go
generated
vendored
Normal file
758
vendor/github.com/caddyserver/certmagic/config.go
generated
vendored
Normal file
@ -0,0 +1,758 @@
|
||||
// Copyright 2015 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"log"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
)
|
||||
|
||||
// Config configures a certificate manager instance.
|
||||
// An empty Config is not valid: use New() to obtain
|
||||
// a valid Config.
|
||||
type Config struct {
|
||||
// How much of a certificate's lifetime becomes the
|
||||
// renewal window, which is the span of time at the
|
||||
// end of the certificate's validity period in which
|
||||
// it should be renewed; for most certificates, the
|
||||
// global default is good, but for exremely short-
|
||||
// lived certs, you may want to raise this to ~0.5.
|
||||
RenewalWindowRatio float64
|
||||
|
||||
// An optional event callback clients can set
|
||||
// to subscribe to certain things happening
|
||||
// internally by this config; invocations are
|
||||
// synchronous, so make them return quickly!
|
||||
OnEvent func(event string, data interface{})
|
||||
|
||||
// DefaultServerName specifies a server name
|
||||
// to use when choosing a certificate if the
|
||||
// ClientHello's ServerName field is empty
|
||||
DefaultServerName string
|
||||
|
||||
// The state needed to operate on-demand TLS;
|
||||
// if non-nil, on-demand TLS is enabled and
|
||||
// certificate operations are deferred to
|
||||
// TLS handshakes (or as-needed)
|
||||
// TODO: Can we call this feature "Reactive/Lazy/Passive TLS" instead?
|
||||
OnDemand *OnDemandConfig
|
||||
|
||||
// Add the must staple TLS extension to the
|
||||
// CSR generated by lego/acme
|
||||
MustStaple bool
|
||||
|
||||
// The type that issues certificates; the
|
||||
// default Issuer is ACMEManager
|
||||
Issuer Issuer
|
||||
|
||||
// The type that revokes certificates; must
|
||||
// be configured in conjunction with the Issuer
|
||||
// field such that both the Issuer and Revoker
|
||||
// are related (because issuance information is
|
||||
// required for revocation)
|
||||
Revoker Revoker
|
||||
|
||||
// The source of new private keys for certificates;
|
||||
// the default KeySource is StandardKeyGenerator
|
||||
KeySource KeyGenerator
|
||||
|
||||
// CertSelection chooses one of the certificates
|
||||
// with which the ClientHello will be completed;
|
||||
// if not set, DefaultCertificateSelector will
|
||||
// be used
|
||||
CertSelection CertificateSelector
|
||||
|
||||
// The storage to access when storing or
|
||||
// loading TLS assets
|
||||
Storage Storage
|
||||
|
||||
// required pointer to the in-memory cert cache
|
||||
certCache *Cache
|
||||
}
|
||||
|
||||
// NewDefault makes a valid config based on the package
|
||||
// Default config. Most users will call this function
|
||||
// instead of New() since most use cases require only a
|
||||
// single config for any and all certificates.
|
||||
//
|
||||
// If your requirements are more advanced (for example,
|
||||
// multiple configs depending on the certificate), then use
|
||||
// New() instead. (You will need to make your own Cache
|
||||
// first.) If you only need a single Config to manage your
|
||||
// certs (even if that config changes, as long as it is the
|
||||
// only one), customize the Default package variable before
|
||||
// calling NewDefault().
|
||||
//
|
||||
// All calls to NewDefault() will return configs that use the
|
||||
// same, default certificate cache. All configs returned
|
||||
// by NewDefault() are based on the values of the fields of
|
||||
// Default at the time it is called.
|
||||
func NewDefault() *Config {
|
||||
defaultCacheMu.Lock()
|
||||
if defaultCache == nil {
|
||||
defaultCache = NewCache(CacheOptions{
|
||||
// the cache will likely need to renew certificates,
|
||||
// so it will need to know how to do that, which
|
||||
// depends on the certificate being managed and which
|
||||
// can change during the lifetime of the cache; this
|
||||
// callback makes it possible to get the latest and
|
||||
// correct config with which to manage the cert,
|
||||
// but if the user does not provide one, we can only
|
||||
// assume that we are to use the default config
|
||||
GetConfigForCert: func(Certificate) (*Config, error) {
|
||||
return NewDefault(), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
certCache := defaultCache
|
||||
defaultCacheMu.Unlock()
|
||||
|
||||
return newWithCache(certCache, Default)
|
||||
}
|
||||
|
||||
// New makes a new, valid config based on cfg and
|
||||
// uses the provided certificate cache. certCache
|
||||
// MUST NOT be nil or this function will panic.
|
||||
//
|
||||
// Use this method when you have an advanced use case
|
||||
// that requires a custom certificate cache and config
|
||||
// that may differ from the Default. For example, if
|
||||
// not all certificates are managed/renewed the same
|
||||
// way, you need to make your own Cache value with a
|
||||
// GetConfigForCert callback that returns the correct
|
||||
// configuration for each certificate. However, for
|
||||
// the vast majority of cases, there will be only a
|
||||
// single Config, thus the default cache (which always
|
||||
// uses the default Config) and default config will
|
||||
// suffice, and you should use New() instead.
|
||||
func New(certCache *Cache, cfg Config) *Config {
|
||||
if certCache == nil {
|
||||
panic("a certificate cache is required")
|
||||
}
|
||||
if certCache.options.GetConfigForCert == nil {
|
||||
panic("cache must have GetConfigForCert set in its options")
|
||||
}
|
||||
return newWithCache(certCache, cfg)
|
||||
}
|
||||
|
||||
// newWithCache ensures that cfg is a valid config by populating
|
||||
// zero-value fields from the Default Config. If certCache is
|
||||
// nil, this function panics.
|
||||
func newWithCache(certCache *Cache, cfg Config) *Config {
|
||||
if certCache == nil {
|
||||
panic("cannot make a valid config without a pointer to a certificate cache")
|
||||
}
|
||||
|
||||
if cfg.OnDemand == nil {
|
||||
cfg.OnDemand = Default.OnDemand
|
||||
}
|
||||
if cfg.RenewalWindowRatio == 0 {
|
||||
cfg.RenewalWindowRatio = Default.RenewalWindowRatio
|
||||
}
|
||||
if cfg.OnEvent == nil {
|
||||
cfg.OnEvent = Default.OnEvent
|
||||
}
|
||||
if cfg.KeySource == nil {
|
||||
cfg.KeySource = Default.KeySource
|
||||
}
|
||||
if cfg.DefaultServerName == "" {
|
||||
cfg.DefaultServerName = Default.DefaultServerName
|
||||
}
|
||||
if cfg.OnDemand == nil {
|
||||
cfg.OnDemand = Default.OnDemand
|
||||
}
|
||||
if !cfg.MustStaple {
|
||||
cfg.MustStaple = Default.MustStaple
|
||||
}
|
||||
if cfg.Storage == nil {
|
||||
cfg.Storage = Default.Storage
|
||||
}
|
||||
if cfg.Issuer == nil {
|
||||
cfg.Issuer = Default.Issuer
|
||||
if cfg.Issuer == nil {
|
||||
// okay really, we need an issuer,
|
||||
// that's kind of the point; most
|
||||
// people would probably want ACME
|
||||
cfg.Issuer = NewACMEManager(&cfg, DefaultACME)
|
||||
}
|
||||
// issuer and revoker go together; if user
|
||||
// specifies their own issuer, we don't want
|
||||
// to override their revoker, hence we only
|
||||
// do this if Issuer was also nil
|
||||
if cfg.Revoker == nil {
|
||||
cfg.Revoker = Default.Revoker
|
||||
if cfg.Revoker == nil {
|
||||
cfg.Revoker = NewACMEManager(&cfg, DefaultACME)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// absolutely don't allow a nil storage,
|
||||
// because that would make almost anything
|
||||
// a config can do pointless
|
||||
if cfg.Storage == nil {
|
||||
cfg.Storage = defaultFileStorage
|
||||
}
|
||||
|
||||
// ensure the unexported fields are valid
|
||||
cfg.certCache = certCache
|
||||
|
||||
return &cfg
|
||||
}
|
||||
|
||||
// ManageSync causes the certificates for domainNames to be managed
|
||||
// according to cfg. If cfg.OnDemand is not nil, then this simply
|
||||
// whitelists the domain names and defers the certificate operations
|
||||
// to when they are needed. Otherwise, the certificates for each
|
||||
// name are loaded from storage or obtained from the CA. If loaded
|
||||
// from storage, they are renewed if they are expiring or expired.
|
||||
// It then caches the certificate in memory and is prepared to serve
|
||||
// them up during TLS handshakes.
|
||||
//
|
||||
// Note that name whitelisting for on-demand management only takes
|
||||
// effect if cfg.OnDemand.DecisionFunc is not set (is nil); it will
|
||||
// not overwrite an existing DecisionFunc, nor will it overwrite
|
||||
// its decision; i.e. the implicit whitelist is only used if no
|
||||
// DecisionFunc is set.
|
||||
//
|
||||
// This method is synchronous, meaning that certificates for all
|
||||
// domainNames must be successfully obtained (or renewed) before
|
||||
// it returns. It returns immediately on the first error for any
|
||||
// of the given domainNames. This behavior is recommended for
|
||||
// interactive use (i.e. when an administrator is present) so
|
||||
// that errors can be reported and fixed immediately.
|
||||
func (cfg *Config) ManageSync(domainNames []string) error {
|
||||
return cfg.manageAll(nil, domainNames, false)
|
||||
}
|
||||
|
||||
// ManageAsync is the same as ManageSync, except that ACME
|
||||
// operations are performed asynchronously (in the background).
|
||||
// This method returns before certificates are ready. It is
|
||||
// crucial that the administrator monitors the logs and is
|
||||
// notified of any errors so that corrective action can be
|
||||
// taken as soon as possible. Any errors returned from this
|
||||
// method occurred before ACME transactions started.
|
||||
//
|
||||
// As long as logs are monitored, this method is typically
|
||||
// recommended for non-interactive environments.
|
||||
//
|
||||
// If there are failures loading, obtaining, or renewing a
|
||||
// certificate, it will be retried with exponential backoff
|
||||
// for up to about 30 days, with a maximum interval of about
|
||||
// 24 hours. Cancelling ctx will cancel retries and shut down
|
||||
// any goroutines spawned by ManageAsync.
|
||||
func (cfg *Config) ManageAsync(ctx context.Context, domainNames []string) error {
|
||||
return cfg.manageAll(ctx, domainNames, true)
|
||||
}
|
||||
|
||||
func (cfg *Config) manageAll(ctx context.Context, domainNames []string, async bool) error {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
for _, domainName := range domainNames {
|
||||
// if on-demand is configured, defer obtain and renew operations
|
||||
if cfg.OnDemand != nil {
|
||||
if !cfg.OnDemand.whitelistContains(domainName) {
|
||||
cfg.OnDemand.hostWhitelist = append(cfg.OnDemand.hostWhitelist, domainName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// otherwise, begin management immediately
|
||||
err := cfg.manageOne(ctx, domainName, async)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool) error {
|
||||
// first try loading existing certificate from storage
|
||||
cert, err := cfg.CacheManagedCertificate(domainName)
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); !ok {
|
||||
return fmt.Errorf("%s: caching certificate: %v", domainName, err)
|
||||
}
|
||||
// if we don't have one in storage, obtain one
|
||||
obtain := func() error {
|
||||
err := cfg.ObtainCert(ctx, domainName, !async)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: obtaining certificate: %w", domainName, err)
|
||||
}
|
||||
cert, err = cfg.CacheManagedCertificate(domainName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: caching certificate after obtaining it: %v", domainName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if async {
|
||||
// Leave the job name empty so as to allow duplicate 'obtain'
|
||||
// jobs; this is because Caddy calls ManageAsync() before the
|
||||
// previous config is stopped (and before its context is
|
||||
// canceled), which means that if an obtain job is still
|
||||
// running for the same domain, Submit() would not queue the
|
||||
// new one because it is still running, even though it is
|
||||
// (probably) about to be canceled (it might not if the new
|
||||
// config fails to finish loading, however). In any case, we
|
||||
// presume it is safe to enqueue a duplicate obtain job because
|
||||
// either the old one (or sometimes the new one) is about to be
|
||||
// canceled. This seems like reasonable logic for any consumer
|
||||
// of this lib. See https://github.com/caddyserver/caddy/issues/3202
|
||||
jm.Submit("", obtain)
|
||||
return nil
|
||||
}
|
||||
return obtain()
|
||||
}
|
||||
|
||||
// for an existing certificate, make sure it is renewed
|
||||
renew := func() error {
|
||||
err := cfg.RenewCert(ctx, domainName, !async)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: renewing certificate: %w", domainName, err)
|
||||
}
|
||||
// successful renewal, so update in-memory cache
|
||||
err = cfg.reloadManagedCertificate(cert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: reloading renewed certificate into memory: %v", domainName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if cert.NeedsRenewal(cfg) {
|
||||
if async {
|
||||
jm.Submit("renew_"+domainName, renew)
|
||||
return nil
|
||||
}
|
||||
return renew()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObtainCert obtains a certificate for name using cfg, as long
|
||||
// as a certificate does not already exist in storage for that
|
||||
// name. The name must qualify and cfg must be flagged as Managed.
|
||||
// This function is a no-op if storage already has a certificate
|
||||
// for name.
|
||||
//
|
||||
// It only obtains and stores certificates (and their keys),
|
||||
// it does not load them into memory. If interactive is true,
|
||||
// the user may be shown a prompt.
|
||||
// TODO: consider moving interactive param into the Config struct,
|
||||
// and maybe retry settings into the Config struct as well? (same for RenewCert)
|
||||
func (cfg *Config) ObtainCert(ctx context.Context, name string, interactive bool) error {
|
||||
if cfg.storageHasCertResources(name) {
|
||||
return nil
|
||||
}
|
||||
issuer, err := cfg.getPrecheckedIssuer([]string{name}, interactive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if issuer == nil {
|
||||
return nil
|
||||
}
|
||||
return cfg.obtainWithIssuer(ctx, issuer, name, interactive)
|
||||
}
|
||||
|
||||
func (cfg *Config) obtainWithIssuer(ctx context.Context, issuer Issuer, name string, interactive bool) error {
|
||||
log.Printf("[INFO][%s] Obtain certificate; acquiring lock...", name)
|
||||
|
||||
// ensure idempotency of the obtain operation for this name
|
||||
lockKey := cfg.lockKey("cert_acme", name)
|
||||
err := obtainLock(cfg.Storage, lockKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
log.Printf("[INFO][%s] Obtain: Releasing lock", name)
|
||||
if err := releaseLock(cfg.Storage, lockKey); err != nil {
|
||||
log.Printf("[ERROR][%s] Obtain: Unable to unlock '%s': %v", name, lockKey, err)
|
||||
}
|
||||
}()
|
||||
log.Printf("[INFO][%s] Obtain: Lock acquired; proceeding...", name)
|
||||
|
||||
f := func(ctx context.Context) error {
|
||||
// check if obtain is still needed -- might have been obtained during lock
|
||||
if cfg.storageHasCertResources(name) {
|
||||
log.Printf("[INFO][%s] Obtain: Certificate already exists in storage", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
privateKey, err := cfg.KeySource.GenerateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privKeyPEM, err := encodePrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
csr, err := cfg.generateCSR(privateKey, []string{name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issuedCert, err := issuer.Issue(ctx, csr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] Obtain: %w", name, err)
|
||||
}
|
||||
|
||||
// success - immediately save the certificate resource
|
||||
certRes := CertificateResource{
|
||||
SANs: namesFromCSR(csr),
|
||||
CertificatePEM: issuedCert.Certificate,
|
||||
PrivateKeyPEM: privKeyPEM,
|
||||
IssuerData: issuedCert.Metadata,
|
||||
}
|
||||
err = cfg.saveCertResource(certRes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] Obtain: saving assets: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if interactive {
|
||||
err = f(ctx)
|
||||
} else {
|
||||
err = doWithRetry(ctx, f)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.emit("cert_obtained", name)
|
||||
|
||||
log.Printf("[INFO][%s] Certificate obtained successfully", name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenewCert renews the certificate for name using cfg. It stows the
|
||||
// renewed certificate and its assets in storage if successful. It
|
||||
// DOES NOT update the in-memory cache with the new certificate.
|
||||
func (cfg *Config) RenewCert(ctx context.Context, name string, interactive bool) error {
|
||||
issuer, err := cfg.getPrecheckedIssuer([]string{name}, interactive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if issuer == nil {
|
||||
return nil
|
||||
}
|
||||
return cfg.renewWithIssuer(ctx, issuer, name, interactive)
|
||||
}
|
||||
|
||||
func (cfg *Config) renewWithIssuer(ctx context.Context, issuer Issuer, name string, interactive bool) error {
|
||||
log.Printf("[INFO][%s] Renew certificate; acquiring lock...", name)
|
||||
|
||||
// ensure idempotency of the renew operation for this name
|
||||
lockKey := cfg.lockKey("cert_acme", name)
|
||||
err := obtainLock(cfg.Storage, lockKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
log.Printf("[INFO][%s] Renew: Releasing lock", name)
|
||||
if err := releaseLock(cfg.Storage, lockKey); err != nil {
|
||||
log.Printf("[ERROR][%s] Renew: Unable to unlock '%s': %v", name, lockKey, err)
|
||||
}
|
||||
}()
|
||||
log.Printf("[INFO][%s] Renew: Lock acquired; proceeding...", name)
|
||||
|
||||
f := func(ctx context.Context) error {
|
||||
// prepare for renewal (load PEM cert, key, and meta)
|
||||
certRes, err := cfg.loadCertResource(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if renew is still needed - might have been renewed while waiting for lock
|
||||
timeLeft, needsRenew := cfg.managedCertNeedsRenewal(certRes)
|
||||
if !needsRenew {
|
||||
log.Printf("[INFO][%s] Renew: Certificate appears to have been renewed already (expires in %s)", name, timeLeft)
|
||||
return nil
|
||||
}
|
||||
log.Printf("[INFO][%s] Renew: %s remaining", name, timeLeft)
|
||||
|
||||
privateKey, err := decodePrivateKey(certRes.PrivateKeyPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
csr, err := cfg.generateCSR(privateKey, []string{name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issuedCert, err := issuer.Issue(ctx, csr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] Renew: %w", name, err)
|
||||
}
|
||||
|
||||
// success - immediately save the renewed certificate resource
|
||||
newCertRes := CertificateResource{
|
||||
SANs: namesFromCSR(csr),
|
||||
CertificatePEM: issuedCert.Certificate,
|
||||
PrivateKeyPEM: certRes.PrivateKeyPEM,
|
||||
IssuerData: issuedCert.Metadata,
|
||||
}
|
||||
err = cfg.saveCertResource(newCertRes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] Renew: saving assets: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if interactive {
|
||||
err = f(ctx)
|
||||
} else {
|
||||
err = doWithRetry(ctx, f)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.emit("cert_renewed", name)
|
||||
|
||||
log.Printf("[INFO][%s] Certificate renewed successfully", name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string) (*x509.CertificateRequest, error) {
|
||||
csrTemplate := new(x509.CertificateRequest)
|
||||
|
||||
for _, name := range sans {
|
||||
if ip := net.ParseIP(name); ip != nil {
|
||||
csrTemplate.IPAddresses = append(csrTemplate.IPAddresses, ip)
|
||||
} else if strings.Contains(name, "@") {
|
||||
csrTemplate.EmailAddresses = append(csrTemplate.EmailAddresses, name)
|
||||
} else if u, err := url.Parse(name); err == nil && strings.Contains(name, "/") {
|
||||
csrTemplate.URIs = append(csrTemplate.URIs, u)
|
||||
} else {
|
||||
csrTemplate.DNSNames = append(csrTemplate.DNSNames, name)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.MustStaple {
|
||||
csrTemplate.ExtraExtensions = append(csrTemplate.ExtraExtensions, mustStapleExtension)
|
||||
}
|
||||
|
||||
csrDER, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificateRequest(csrDER)
|
||||
}
|
||||
|
||||
// RevokeCert revokes the certificate for domain via ACME protocol. It requires
|
||||
// that cfg.Issuer is properly configured with the same issuer that issued the
|
||||
// certificate being revoked.
|
||||
func (cfg *Config) RevokeCert(ctx context.Context, domain string, interactive bool) error {
|
||||
rev := cfg.Revoker
|
||||
if rev == nil {
|
||||
rev = Default.Revoker
|
||||
}
|
||||
|
||||
certRes, err := cfg.loadCertResource(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issuerKey := cfg.Issuer.IssuerKey()
|
||||
|
||||
if !cfg.Storage.Exists(StorageKeys.SitePrivateKey(issuerKey, domain)) {
|
||||
return fmt.Errorf("private key not found for %s", certRes.SANs)
|
||||
}
|
||||
|
||||
err = rev.Revoke(ctx, certRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.emit("cert_revoked", domain)
|
||||
|
||||
err = cfg.Storage.Delete(StorageKeys.SiteCert(issuerKey, domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("certificate revoked, but unable to delete certificate file: %v", err)
|
||||
}
|
||||
err = cfg.Storage.Delete(StorageKeys.SitePrivateKey(issuerKey, domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("certificate revoked, but unable to delete private key: %v", err)
|
||||
}
|
||||
err = cfg.Storage.Delete(StorageKeys.SiteMeta(issuerKey, domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("certificate revoked, but unable to delete certificate metadata: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TLSConfig is an opinionated method that returns a
|
||||
// recommended, modern TLS configuration that can be
|
||||
// used to configure TLS listeners, which also supports
|
||||
// the TLS-ALPN challenge and serves up certificates
|
||||
// managed by cfg.
|
||||
//
|
||||
// Unlike the package TLS() function, this method does
|
||||
// not, by itself, enable certificate management for
|
||||
// any domain names.
|
||||
//
|
||||
// Feel free to further customize the returned tls.Config,
|
||||
// but do not mess with the GetCertificate or NextProtos
|
||||
// fields unless you know what you're doing, as they're
|
||||
// necessary to solve the TLS-ALPN challenge.
|
||||
func (cfg *Config) TLSConfig() *tls.Config {
|
||||
return &tls.Config{
|
||||
// these two fields necessary for TLS-ALPN challenge
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
NextProtos: []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol},
|
||||
|
||||
// the rest recommended for modern TLS servers
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{
|
||||
tls.X25519,
|
||||
tls.CurveP256,
|
||||
},
|
||||
CipherSuites: preferredDefaultCipherSuites(),
|
||||
PreferServerCipherSuites: true,
|
||||
}
|
||||
}
|
||||
|
||||
// getPrecheckedIssuer returns an Issuer with pre-checks
|
||||
// completed, if it is also a PreChecker. It also checks
|
||||
// that storage is functioning. If a nil Issuer is returned
|
||||
// with a nil error, that means to skip this operation
|
||||
// (not an error, just a no-op).
|
||||
func (cfg *Config) getPrecheckedIssuer(names []string, interactive bool) (Issuer, error) {
|
||||
// ensure storage is writeable and readable
|
||||
// TODO: this is not necessary every time; should only
|
||||
// perform check once every so often for each storage,
|
||||
// which may require some global state...
|
||||
err := cfg.checkStorage()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
|
||||
}
|
||||
if prechecker, ok := cfg.Issuer.(PreChecker); ok {
|
||||
err := prechecker.PreCheck(names, interactive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cfg.Issuer, nil
|
||||
}
|
||||
|
||||
// checkStorage tests the storage by writing random bytes
|
||||
// to a random key, and then loading those bytes and
|
||||
// comparing the loaded value. If this fails, the provided
|
||||
// cfg.Storage mechanism should not be used.
|
||||
func (cfg *Config) checkStorage() error {
|
||||
key := fmt.Sprintf("rw_test_%d", weakrand.Int())
|
||||
contents := make([]byte, 1024*10) // size sufficient for one or two ACME resources
|
||||
_, err := weakrand.Read(contents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cfg.Storage.Store(key, contents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
deleteErr := cfg.Storage.Delete(key)
|
||||
if deleteErr != nil {
|
||||
log.Printf("[ERROR] Deleting test key %s from storage: %v", key, err)
|
||||
}
|
||||
// if there was no other error, make sure
|
||||
// to return any error returned from Delete
|
||||
if err == nil {
|
||||
err = deleteErr
|
||||
}
|
||||
}()
|
||||
loaded, err := cfg.Storage.Load(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(contents, loaded) {
|
||||
return fmt.Errorf("load yielded different value than was stored; expected %d bytes, got %d bytes of differing elements", len(contents), len(loaded))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// storageHasCertResources returns true if the storage
|
||||
// associated with cfg's certificate cache has all the
|
||||
// resources related to the certificate for domain: the
|
||||
// certificate, the private key, and the metadata.
|
||||
func (cfg *Config) storageHasCertResources(domain string) bool {
|
||||
issuerKey := cfg.Issuer.IssuerKey()
|
||||
certKey := StorageKeys.SiteCert(issuerKey, domain)
|
||||
keyKey := StorageKeys.SitePrivateKey(issuerKey, domain)
|
||||
metaKey := StorageKeys.SiteMeta(issuerKey, domain)
|
||||
return cfg.Storage.Exists(certKey) &&
|
||||
cfg.Storage.Exists(keyKey) &&
|
||||
cfg.Storage.Exists(metaKey)
|
||||
}
|
||||
|
||||
// lockKey returns a key for a lock that is specific to the operation
|
||||
// named op being performed related to domainName and this config's CA.
|
||||
func (cfg *Config) lockKey(op, domainName string) string {
|
||||
return fmt.Sprintf("%s_%s_%s", op, domainName, cfg.Issuer.IssuerKey())
|
||||
}
|
||||
|
||||
// managedCertNeedsRenewal returns true if certRes is
|
||||
// expiring soon or already expired, or if the process
|
||||
// of checking the expiration returned an error.
|
||||
func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource) (time.Duration, bool) {
|
||||
cert, err := makeCertificate(certRes.CertificatePEM, certRes.PrivateKeyPEM)
|
||||
if err != nil {
|
||||
return 0, true
|
||||
}
|
||||
return time.Until(cert.Leaf.NotAfter), cert.NeedsRenewal(cfg)
|
||||
}
|
||||
|
||||
func (cfg *Config) emit(eventName string, data interface{}) {
|
||||
if cfg.OnEvent == nil {
|
||||
return
|
||||
}
|
||||
cfg.OnEvent(eventName, data)
|
||||
}
|
||||
|
||||
// CertificateSelector is a type which can select a certificate to use given multiple choices.
|
||||
type CertificateSelector interface {
|
||||
SelectCertificate(*tls.ClientHelloInfo, []Certificate) (Certificate, error)
|
||||
}
|
||||
|
||||
// Constants for PKIX MustStaple extension.
|
||||
var (
|
||||
tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
|
||||
ocspMustStapleFeature = []byte{0x30, 0x03, 0x02, 0x01, 0x05}
|
||||
mustStapleExtension = pkix.Extension{
|
||||
Id: tlsFeatureExtensionOID,
|
||||
Value: ocspMustStapleFeature,
|
||||
}
|
||||
)
|
@ -17,6 +17,9 @@ package certmagic
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
@ -25,8 +28,8 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/certificate"
|
||||
"github.com/klauspost/cpuid"
|
||||
)
|
||||
|
||||
@ -45,20 +48,48 @@ func encodePrivateKey(key crypto.PrivateKey) ([]byte, error) {
|
||||
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)
|
||||
}
|
||||
pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes}
|
||||
return pem.EncodeToMemory(&pemKey), nil
|
||||
}
|
||||
|
||||
// decodePrivateKey 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)
|
||||
func decodePrivateKey(keyPEMBytes []byte) (crypto.PrivateKey, error) {
|
||||
keyBlock, _ := pem.Decode(keyPEMBytes)
|
||||
switch keyBlock.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||
keyBlockDER, _ := pem.Decode(keyPEMBytes)
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
@ -98,23 +129,26 @@ func fastHash(input []byte) string {
|
||||
// saveCertResource saves the certificate resource to disk. This
|
||||
// includes the certificate file itself, the private key, and the
|
||||
// metadata file.
|
||||
func (cfg *Config) saveCertResource(cert *certificate.Resource) error {
|
||||
metaBytes, err := json.MarshalIndent(&cert, "", "\t")
|
||||
func (cfg *Config) saveCertResource(cert CertificateResource) error {
|
||||
metaBytes, err := json.MarshalIndent(cert, "", "\t")
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding certificate metadata: %v", err)
|
||||
}
|
||||
|
||||
issuerKey := cfg.Issuer.IssuerKey()
|
||||
certKey := cert.NamesKey()
|
||||
|
||||
all := []keyValue{
|
||||
{
|
||||
key: StorageKeys.SiteCert(cfg.CA, cert.Domain),
|
||||
value: cert.Certificate,
|
||||
key: StorageKeys.SiteCert(issuerKey, certKey),
|
||||
value: cert.CertificatePEM,
|
||||
},
|
||||
{
|
||||
key: StorageKeys.SitePrivateKey(cfg.CA, cert.Domain),
|
||||
value: cert.PrivateKey,
|
||||
key: StorageKeys.SitePrivateKey(issuerKey, certKey),
|
||||
value: cert.PrivateKeyPEM,
|
||||
},
|
||||
{
|
||||
key: StorageKeys.SiteMeta(cfg.CA, cert.Domain),
|
||||
key: StorageKeys.SiteMeta(issuerKey, certKey),
|
||||
value: metaBytes,
|
||||
},
|
||||
}
|
||||
@ -122,26 +156,27 @@ func (cfg *Config) saveCertResource(cert *certificate.Resource) error {
|
||||
return storeTx(cfg.Storage, all)
|
||||
}
|
||||
|
||||
func (cfg *Config) loadCertResource(domain string) (certificate.Resource, error) {
|
||||
var certRes certificate.Resource
|
||||
certBytes, err := cfg.Storage.Load(StorageKeys.SiteCert(cfg.CA, domain))
|
||||
func (cfg *Config) loadCertResource(certNamesKey string) (CertificateResource, error) {
|
||||
var certRes CertificateResource
|
||||
issuerKey := cfg.Issuer.IssuerKey()
|
||||
certBytes, err := cfg.Storage.Load(StorageKeys.SiteCert(issuerKey, certNamesKey))
|
||||
if err != nil {
|
||||
return certRes, err
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
keyBytes, err := cfg.Storage.Load(StorageKeys.SitePrivateKey(cfg.CA, domain))
|
||||
certRes.CertificatePEM = certBytes
|
||||
keyBytes, err := cfg.Storage.Load(StorageKeys.SitePrivateKey(issuerKey, certNamesKey))
|
||||
if err != nil {
|
||||
return certRes, err
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
metaBytes, err := cfg.Storage.Load(StorageKeys.SiteMeta(cfg.CA, domain))
|
||||
certRes.PrivateKeyPEM = keyBytes
|
||||
metaBytes, err := cfg.Storage.Load(StorageKeys.SiteMeta(issuerKey, certNamesKey))
|
||||
if err != nil {
|
||||
return certRes, err
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
err = json.Unmarshal(metaBytes, &certRes)
|
||||
if err != nil {
|
||||
return certRes, fmt.Errorf("decoding certificate metadata: %v", err)
|
||||
return CertificateResource{}, fmt.Errorf("decoding certificate metadata: %v", err)
|
||||
}
|
||||
certRes.Certificate = certBytes
|
||||
certRes.PrivateKey = keyBytes
|
||||
return certRes, nil
|
||||
}
|
||||
|
||||
@ -156,6 +191,19 @@ func hashCertificateChain(certChain [][]byte) string {
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
func namesFromCSR(csr *x509.CertificateRequest) []string {
|
||||
var nameSet []string
|
||||
nameSet = append(nameSet, csr.DNSNames...)
|
||||
nameSet = append(nameSet, csr.EmailAddresses...)
|
||||
for _, v := range csr.IPAddresses {
|
||||
nameSet = append(nameSet, v.String())
|
||||
}
|
||||
for _, v := range csr.URIs {
|
||||
nameSet = append(nameSet, v.String())
|
||||
}
|
||||
return nameSet
|
||||
}
|
||||
|
||||
// preferredDefaultCipherSuites returns an appropriate
|
||||
// cipher suite to use depending on hardware support
|
||||
// for AES-NI.
|
||||
@ -186,3 +234,46 @@ var (
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
}
|
||||
)
|
||||
|
||||
// StandardKeyGenerator is the standard, in-memory key source
|
||||
// that uses crypto/rand.
|
||||
type StandardKeyGenerator struct {
|
||||
// The type of keys to generate.
|
||||
KeyType KeyType
|
||||
}
|
||||
|
||||
// GenerateKey generates a new private key according to kg.KeyType.
|
||||
func (kg StandardKeyGenerator) GenerateKey() (crypto.PrivateKey, error) {
|
||||
switch kg.KeyType {
|
||||
case ED25519:
|
||||
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
return priv, err
|
||||
case "", P256:
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
case P384:
|
||||
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
case RSA2048:
|
||||
return rsa.GenerateKey(rand.Reader, 2048)
|
||||
case RSA4096:
|
||||
return rsa.GenerateKey(rand.Reader, 4096)
|
||||
case RSA8192:
|
||||
return rsa.GenerateKey(rand.Reader, 8192)
|
||||
}
|
||||
return nil, fmt.Errorf("unrecognized or unsupported key type: %s", kg.KeyType)
|
||||
}
|
||||
|
||||
// DefaultKeyGenerator is the default key source.
|
||||
var DefaultKeyGenerator = StandardKeyGenerator{KeyType: P256}
|
||||
|
||||
// KeyType enumerates the known/supported key types.
|
||||
type KeyType string
|
||||
|
||||
// Constants for all key types we support.
|
||||
const (
|
||||
ED25519 = KeyType("ed25519")
|
||||
P256 = KeyType("p256")
|
||||
P384 = KeyType("p384")
|
||||
RSA2048 = KeyType("rsa2048")
|
||||
RSA4096 = KeyType("rsa4096")
|
||||
RSA8192 = KeyType("rsa8192")
|
||||
)
|
@ -15,7 +15,9 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
@ -123,7 +125,6 @@ func (fs *FileStorage) Filename(key string) string {
|
||||
// Lock obtains a lock named by the given key. It blocks
|
||||
// until the lock can be obtained or an error is returned.
|
||||
func (fs *FileStorage) Lock(key string) error {
|
||||
start := time.Now()
|
||||
filename := fs.lockFilename(key)
|
||||
|
||||
for {
|
||||
@ -139,7 +140,16 @@ func (fs *FileStorage) Lock(key string) error {
|
||||
|
||||
// lock file already exists
|
||||
|
||||
info, err := os.Stat(filename)
|
||||
var meta lockMeta
|
||||
f, err := os.Open(filename)
|
||||
if err == nil {
|
||||
err2 := json.NewDecoder(f).Decode(&meta)
|
||||
f.Close()
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
// must have just been removed; try again to create it
|
||||
@ -149,18 +159,13 @@ func (fs *FileStorage) Lock(key string) error {
|
||||
// unexpected error
|
||||
return fmt.Errorf("accessing lock file: %v", err)
|
||||
|
||||
case fileLockIsStale(info):
|
||||
case fileLockIsStale(meta):
|
||||
// lock file is stale - delete it and try again to create one
|
||||
log.Printf("[INFO][%s] Lock for '%s' is stale; removing then retrying: %s",
|
||||
fs, key, filename)
|
||||
log.Printf("[INFO][%s] Lock for '%s' is stale (created: %s, last update: %s); removing then retrying: %s",
|
||||
fs, key, meta.Created, meta.Updated, filename)
|
||||
removeLockfile(filename)
|
||||
continue
|
||||
|
||||
case time.Since(start) > staleLockDuration*2:
|
||||
// should never happen, hopefully
|
||||
return fmt.Errorf("possible deadlock: %s passed trying to obtain lock for %s",
|
||||
time.Since(start), key)
|
||||
|
||||
default:
|
||||
// lockfile exists and is not stale;
|
||||
// just wait a moment and try again
|
||||
@ -186,35 +191,43 @@ func (fs *FileStorage) lockDir() string {
|
||||
return filepath.Join(fs.Path, "locks")
|
||||
}
|
||||
|
||||
func fileLockIsStale(info os.FileInfo) bool {
|
||||
if info == nil {
|
||||
return true
|
||||
func fileLockIsStale(meta lockMeta) bool {
|
||||
ref := meta.Updated
|
||||
if ref.IsZero() {
|
||||
ref = meta.Created
|
||||
}
|
||||
return time.Since(info.ModTime()) > staleLockDuration
|
||||
// since updates are exactly every lockFreshnessInterval,
|
||||
// add a grace period for the actual file read+write to
|
||||
// take place
|
||||
return time.Since(ref) > lockFreshnessInterval*2
|
||||
}
|
||||
|
||||
// createLockfile atomically creates the lockfile
|
||||
// identified by filename. A successfully created
|
||||
// lockfile should be removed with removeLockfile.
|
||||
func createLockfile(filename string) error {
|
||||
err := atomicallyCreateFile(filename)
|
||||
if err == nil {
|
||||
// if the app crashes in removeLockfile(), there is a
|
||||
// small chance the .unlock file is left behind; it's
|
||||
// safe to simply remove it as it's a guard against
|
||||
// double removal of the .lock file.
|
||||
os.Remove(filename + ".unlock")
|
||||
err := atomicallyCreateFile(filename, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
|
||||
go keepLockfileFresh(filename)
|
||||
|
||||
// if the app crashes in removeLockfile(), there is a
|
||||
// small chance the .unlock file is left behind; it's
|
||||
// safe to simply remove it as it's a guard against
|
||||
// double removal of the .lock file.
|
||||
_ = os.Remove(filename + ".unlock")
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeLockfile atomically removes filename,
|
||||
// which must be a lockfile created by createLockfile.
|
||||
// See discussion in PR #7 for more background:
|
||||
// https://github.com/mholt/certmagic/pull/7
|
||||
// https://github.com/caddyserver/certmagic/pull/7
|
||||
func removeLockfile(filename string) error {
|
||||
unlockFilename := filename + ".unlock"
|
||||
if err := atomicallyCreateFile(unlockFilename); err != nil {
|
||||
if err := atomicallyCreateFile(unlockFilename, false); err != nil {
|
||||
if os.IsExist(err) {
|
||||
// another process is handling the unlocking
|
||||
return nil
|
||||
@ -225,16 +238,85 @@ func removeLockfile(filename string) error {
|
||||
return os.Remove(filename)
|
||||
}
|
||||
|
||||
// keepLockfileFresh continuously updates the lock file
|
||||
// at filename with the current timestamp. It stops
|
||||
// when the file disappears (happy path = lock released),
|
||||
// or when there is an error at any point. Since it polls
|
||||
// every lockFreshnessInterval, this function might
|
||||
// not terminate until up to lockFreshnessInterval after
|
||||
// the lock is released.
|
||||
func keepLockfileFresh(filename string) {
|
||||
for {
|
||||
time.Sleep(lockFreshnessInterval)
|
||||
done, err := updateLockfileFreshness(filename)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Keeping lock file fresh: %v - terminating lock maintenance (lockfile: %s)", err, filename)
|
||||
return
|
||||
}
|
||||
if done {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateLockfileFreshness updates the lock file at filename
|
||||
// with the current timestamp. It returns true if the parent
|
||||
// loop can terminate (i.e. no more need to update the lock).
|
||||
func updateLockfileFreshness(filename string) (bool, error) {
|
||||
f, err := os.OpenFile(filename, os.O_RDWR, 0644)
|
||||
if os.IsNotExist(err) {
|
||||
return true, nil // lock released
|
||||
}
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// read contents
|
||||
metaBytes, err := ioutil.ReadAll(io.LimitReader(f, 2048))
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
var meta lockMeta
|
||||
if err := json.Unmarshal(metaBytes, &meta); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
// truncate file and reset I/O offset to beginning
|
||||
if err := f.Truncate(0); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
// write updated timestamp
|
||||
meta.Updated = time.Now()
|
||||
return false, json.NewEncoder(f).Encode(meta)
|
||||
}
|
||||
|
||||
// atomicallyCreateFile atomically creates the file
|
||||
// identified by filename if it doesn't already exist.
|
||||
func atomicallyCreateFile(filename string) error {
|
||||
// no need to check this, we only really care about the file creation error
|
||||
os.MkdirAll(filepath.Dir(filename), 0700)
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_EXCL, 0644)
|
||||
if err == nil {
|
||||
f.Close()
|
||||
func atomicallyCreateFile(filename string, writeLockInfo bool) error {
|
||||
// no need to check this error, we only really care about the file creation error
|
||||
_ = os.MkdirAll(filepath.Dir(filename), 0700)
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
defer f.Close()
|
||||
if writeLockInfo {
|
||||
now := time.Now()
|
||||
meta := lockMeta{
|
||||
Created: now,
|
||||
Updated: now,
|
||||
}
|
||||
err := json.NewEncoder(f).Encode(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// homeDir returns the best guess of the current user's home
|
||||
@ -264,12 +346,22 @@ func dataDir() string {
|
||||
return filepath.Join(baseDir, "certmagic")
|
||||
}
|
||||
|
||||
// staleLockDuration is the length of time
|
||||
// before considering a lock to be stale.
|
||||
const staleLockDuration = 2 * time.Hour
|
||||
// lockMeta is written into a lock file.
|
||||
type lockMeta struct {
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Updated time.Time `json:"updated,omitempty"`
|
||||
}
|
||||
|
||||
// lockFreshnessInterval is how often to update
|
||||
// a lock's timestamp. Locks with a timestamp
|
||||
// more than this duration in the past (plus a
|
||||
// grace period for latency) can be considered
|
||||
// stale.
|
||||
const lockFreshnessInterval = 5 * time.Second
|
||||
|
||||
// fileLockPollInterval is how frequently
|
||||
// to check the existence of a lock file
|
||||
const fileLockPollInterval = 1 * time.Second
|
||||
|
||||
// Interface guard
|
||||
var _ Storage = (*FileStorage)(nil)
|
9
vendor/github.com/caddyserver/certmagic/go.mod
generated
vendored
Normal file
9
vendor/github.com/caddyserver/certmagic/go.mod
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module github.com/caddyserver/certmagic
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/go-acme/lego/v3 v3.4.0
|
||||
github.com/klauspost/cpuid v1.2.3
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
|
||||
)
|
380
vendor/github.com/caddyserver/certmagic/go.sum
generated
vendored
Normal file
380
vendor/github.com/caddyserver/certmagic/go.sum
generated
vendored
Normal file
@ -0,0 +1,380 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
|
||||
github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.0/go.mod h1:zpDJeKyp9ScW4NNrbdr+Eyxvry3ilGPewKoXw3XGN1k=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY=
|
||||
github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-acme/lego/v3 v3.4.0 h1:deB9NkelA+TfjGHVw8J7iKl/rMtffcGMWSMmptvMv0A=
|
||||
github.com/go-acme/lego/v3 v3.4.0/go.mod h1:xYbLDuxq3Hy4bMUT1t9JIuz6GWIWb3m5X+TeTHYaT7M=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
|
||||
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
|
||||
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
||||
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI=
|
||||
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nrdcg/auroradns v1.0.0/go.mod h1:6JPXKzIRzZzMqtTDgueIhTi6rFf1QvYE/HzqidhOhjw=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
||||
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY=
|
||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
|
||||
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 h1:6KET3Sqa7fkVfD63QnAM81ZeYg5n4HwApOJkufONnHA=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a h1:Yu34BogBivvmu7SAzHHaB9nZWH5D1C+z3F1jyIaYZSQ=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
|
||||
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
@ -15,6 +15,7 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -22,10 +23,9 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/tlsalpn01"
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
)
|
||||
|
||||
// GetCertificate gets a certificate to satisfy clientHello. In getting
|
||||
@ -37,9 +37,7 @@ import (
|
||||
//
|
||||
// This method is safe for use as a tls.Config.GetCertificate callback.
|
||||
func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if cfg.OnEvent != nil {
|
||||
cfg.OnEvent("tls_handshake_started", clientHello)
|
||||
}
|
||||
cfg.emit("tls_handshake_started", clientHello)
|
||||
|
||||
// special case: serve up the certificate for a TLS-ALPN ACME challenge
|
||||
// (https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05)
|
||||
@ -55,22 +53,23 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif
|
||||
// should already have taken care of that when we made the tls.Config)
|
||||
challengeCert, ok, err := cfg.tryDistributedChallengeSolver(clientHello)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR][%s] TLS-ALPN: %v", clientHello.ServerName, err)
|
||||
log.Printf("[ERROR][%s] TLS-ALPN challenge: %v", clientHello.ServerName, err)
|
||||
}
|
||||
if ok {
|
||||
log.Printf("[INFO][%s] Served key authentication certificate (distributed TLS-ALPN challenge)", clientHello.ServerName)
|
||||
return &challengeCert.Certificate, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no certificate to complete TLS-ALPN challenge for SNI name: %s", clientHello.ServerName)
|
||||
}
|
||||
log.Printf("[INFO][%s] Served key authentication certificate (TLS-ALPN challenge)", clientHello.ServerName)
|
||||
return &challengeCert.Certificate, nil
|
||||
}
|
||||
}
|
||||
|
||||
// get the certificate and serve it up
|
||||
cert, err := cfg.getCertDuringHandshake(clientHello, true, true)
|
||||
if err == nil && cfg.OnEvent != nil {
|
||||
cfg.OnEvent("tls_handshake_completed", clientHello)
|
||||
if err == nil {
|
||||
cfg.emit("tls_handshake_completed", clientHello)
|
||||
}
|
||||
return &cert.Certificate, err
|
||||
}
|
||||
@ -94,8 +93,6 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif
|
||||
func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate, matched, defaulted bool) {
|
||||
name := NormalizedName(hello.ServerName)
|
||||
|
||||
var ok bool
|
||||
|
||||
if name == "" {
|
||||
// if SNI is empty, prefer matching IP address
|
||||
if hello.Conn != nil {
|
||||
@ -104,8 +101,8 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
|
||||
if err == nil {
|
||||
addr = ip
|
||||
}
|
||||
if cert, ok = cfg.certCache.getFirstMatchingCert(addr); ok {
|
||||
matched = true
|
||||
cert, matched = cfg.selectCert(hello, addr)
|
||||
if matched {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -113,15 +110,15 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
|
||||
// fall back to a "default" certificate, if specified
|
||||
if cfg.DefaultServerName != "" {
|
||||
normDefault := NormalizedName(cfg.DefaultServerName)
|
||||
if cert, ok = cfg.certCache.getFirstMatchingCert(normDefault); ok {
|
||||
defaulted = true
|
||||
cert, defaulted = cfg.selectCert(hello, normDefault)
|
||||
if defaulted {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if SNI is specified, try an exact match first
|
||||
if cert, ok = cfg.certCache.getFirstMatchingCert(name); ok {
|
||||
matched = true
|
||||
cert, matched = cfg.selectCert(hello, name)
|
||||
if matched {
|
||||
return
|
||||
}
|
||||
|
||||
@ -131,8 +128,8 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
|
||||
for i := range labels {
|
||||
labels[i] = "*"
|
||||
candidate := strings.Join(labels, ".")
|
||||
if cert, ok = cfg.certCache.getFirstMatchingCert(candidate); ok {
|
||||
matched = true
|
||||
cert, matched = cfg.selectCert(hello, candidate)
|
||||
if matched {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -156,7 +153,7 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
|
||||
}
|
||||
|
||||
// otherwise, we're bingo on ammo; see issues
|
||||
// mholt/caddy#2035 and mholt/caddy#1303 (any
|
||||
// caddyserver/caddy#2035 and caddyserver/caddy#1303 (any
|
||||
// change to certificate matching behavior must
|
||||
// account for hosts defined where the hostname
|
||||
// is empty or a catch-all, like ":443" or
|
||||
@ -165,6 +162,53 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
|
||||
return
|
||||
}
|
||||
|
||||
// selectCert uses hello to select a certificate from the
|
||||
// cache for name. If cfg.CertSelection is set, it will be
|
||||
// used to make the decision. Otherwise, the first matching
|
||||
// unexpired cert is returned. As a special case, if no
|
||||
// certificates match name and cfg.CertSelection is set,
|
||||
// then all certificates in the cache will be passed in
|
||||
// for the cfg.CertSelection to make the final decision.
|
||||
func (cfg *Config) selectCert(hello *tls.ClientHelloInfo, name string) (Certificate, bool) {
|
||||
choices := cfg.certCache.getAllMatchingCerts(name)
|
||||
if len(choices) == 0 {
|
||||
if cfg.CertSelection == nil {
|
||||
return Certificate{}, false
|
||||
}
|
||||
choices = cfg.certCache.getAllCerts()
|
||||
}
|
||||
if cfg.CertSelection == nil {
|
||||
cert, err := DefaultCertificateSelector(hello, choices)
|
||||
return cert, err == nil
|
||||
}
|
||||
cert, err := cfg.CertSelection.SelectCertificate(hello, choices)
|
||||
return cert, err == nil
|
||||
}
|
||||
|
||||
// DefaultCertificateSelector is the default certificate selection logic
|
||||
// given a choice of certificates. If there is at least one certificate in
|
||||
// choices, it always returns a certificate without error. It chooses the
|
||||
// first non-expired certificate that the client supports if possible,
|
||||
// otherwise it returns an expired certificate that the client supports,
|
||||
// otherwise it just returns the first certificate in the list of choices.
|
||||
func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificate) (Certificate, error) {
|
||||
if len(choices) == 0 {
|
||||
return Certificate{}, fmt.Errorf("no certificates available")
|
||||
}
|
||||
now := time.Now()
|
||||
best := choices[0]
|
||||
for _, choice := range choices {
|
||||
if err := hello.SupportsCertificate(&choice.Certificate); err != nil {
|
||||
continue
|
||||
}
|
||||
best = choice // at least the client supports it...
|
||||
if now.After(choice.Leaf.NotBefore) && now.Before(choice.Leaf.NotAfter) {
|
||||
return choice, nil // ...and unexpired, great! "Certificate, I choose you!"
|
||||
}
|
||||
}
|
||||
return best, nil // all matching certs are expired or incompatible, oh well
|
||||
}
|
||||
|
||||
// getCertDuringHandshake will get a certificate for hello. It first tries
|
||||
// the in-memory cache. If no certificate for hello is in the cache, the
|
||||
// config most closely corresponding to hello will be loaded. If that config
|
||||
@ -177,14 +221,14 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
|
||||
//
|
||||
// This function is safe for concurrent use.
|
||||
func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) {
|
||||
name := NormalizedName(hello.ServerName)
|
||||
|
||||
// First check our in-memory cache to see if we've already loaded it
|
||||
cert, matched, defaulted := cfg.getCertificate(hello)
|
||||
if matched {
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
name := cfg.getNameFromClientHello(hello)
|
||||
|
||||
// If OnDemand is enabled, then we might be able to load or
|
||||
// obtain a needed certificate
|
||||
if cfg.OnDemand != nil && loadIfNecessary {
|
||||
@ -206,11 +250,6 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
|
||||
return Certificate{}, err
|
||||
}
|
||||
|
||||
// Name has to qualify for a certificate
|
||||
if !HostQualifies(name) {
|
||||
return cert, fmt.Errorf("hostname '%s' does not qualify for certificate", name)
|
||||
}
|
||||
|
||||
// Obtain certificate from the CA
|
||||
return cfg.obtainOnDemandCertificate(hello)
|
||||
}
|
||||
@ -224,14 +263,24 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
|
||||
return Certificate{}, fmt.Errorf("no certificate available for '%s'", name)
|
||||
}
|
||||
|
||||
// checkIfCertShouldBeObtained checks to see if an on-demand tls certificate
|
||||
// should be obtained for a given domain based upon the config settings. If
|
||||
// checkIfCertShouldBeObtained checks to see if an on-demand TLS certificate
|
||||
// should be obtained for a given domain based upon the config settings. If
|
||||
// a non-nil error is returned, do not issue a new certificate for name.
|
||||
func (cfg *Config) checkIfCertShouldBeObtained(name string) error {
|
||||
if cfg.OnDemand == nil {
|
||||
return fmt.Errorf("not configured for on-demand certificate issuance")
|
||||
}
|
||||
return cfg.OnDemand.Allowed(name)
|
||||
if !SubjectQualifiesForCert(name) {
|
||||
return fmt.Errorf("subject name does not qualify for certificate: %s", name)
|
||||
}
|
||||
if cfg.OnDemand.DecisionFunc != nil {
|
||||
return cfg.OnDemand.DecisionFunc(name)
|
||||
}
|
||||
if len(cfg.OnDemand.hostWhitelist) > 0 &&
|
||||
!cfg.OnDemand.whitelistContains(name) {
|
||||
return fmt.Errorf("certificate for '%s' is not managed", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// obtainOnDemandCertificate obtains a certificate for hello.
|
||||
@ -240,7 +289,7 @@ func (cfg *Config) checkIfCertShouldBeObtained(name string) error {
|
||||
//
|
||||
// This function is safe for use by multiple concurrent goroutines.
|
||||
func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certificate, error) {
|
||||
name := NormalizedName(hello.ServerName)
|
||||
name := cfg.getNameFromClientHello(hello)
|
||||
|
||||
// We must protect this process from happening concurrently, so synchronize.
|
||||
obtainCertWaitChansMu.Lock()
|
||||
@ -261,7 +310,10 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif
|
||||
|
||||
// obtain the certificate
|
||||
log.Printf("[INFO] Obtaining new certificate for %s", name)
|
||||
err := cfg.ObtainCert(name, false)
|
||||
// TODO: use a proper context; we use one with timeout because retries are enabled because interactive is false
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 90*time.Second)
|
||||
defer cancel()
|
||||
err := cfg.ObtainCert(ctx, name, false)
|
||||
|
||||
// immediately unblock anyone waiting for it; doing this in
|
||||
// a defer would risk deadlock because of the recursive call
|
||||
@ -272,27 +324,12 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif
|
||||
obtainCertWaitChansMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
// Failed to solve challenge, so don't allow another on-demand
|
||||
// issue for this name to be attempted for a little while.
|
||||
failedIssuanceMu.Lock()
|
||||
failedIssuance[name] = time.Now()
|
||||
go func(name string) {
|
||||
time.Sleep(5 * time.Minute)
|
||||
failedIssuanceMu.Lock()
|
||||
delete(failedIssuance, name)
|
||||
failedIssuanceMu.Unlock()
|
||||
}(name)
|
||||
failedIssuanceMu.Unlock()
|
||||
// shucks; failed to solve challenge on-demand
|
||||
return Certificate{}, err
|
||||
}
|
||||
|
||||
// Success - update counters and stuff
|
||||
atomic.AddInt32(&cfg.OnDemand.obtainedCount, 1)
|
||||
lastIssueTimeMu.Lock()
|
||||
lastIssueTime = time.Now()
|
||||
lastIssueTimeMu.Unlock()
|
||||
|
||||
// certificate is already on disk; now just start over to load it and serve it
|
||||
// success; certificate was just placed on disk, so
|
||||
// we need only restart serving the certificate
|
||||
return cfg.getCertDuringHandshake(hello, true, false)
|
||||
}
|
||||
|
||||
@ -301,24 +338,24 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif
|
||||
// This function is safe for use by multiple concurrent goroutines.
|
||||
func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certificate) (Certificate, error) {
|
||||
// Check cert expiration
|
||||
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
||||
if timeLeft < cfg.RenewDurationBefore {
|
||||
log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", cert.Names, timeLeft)
|
||||
timeLeft := cert.Leaf.NotAfter.Sub(time.Now().UTC())
|
||||
if currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio) {
|
||||
log.Printf("[INFO] Certificate for %v expires in %s; attempting renewal", cert.Names, timeLeft)
|
||||
return cfg.renewDynamicCertificate(hello, cert)
|
||||
}
|
||||
|
||||
// Check OCSP staple validity
|
||||
if cert.OCSP != nil {
|
||||
refreshTime := cert.OCSP.ThisUpdate.Add(cert.OCSP.NextUpdate.Sub(cert.OCSP.ThisUpdate) / 2)
|
||||
if cert.ocsp != nil {
|
||||
refreshTime := cert.ocsp.ThisUpdate.Add(cert.ocsp.NextUpdate.Sub(cert.ocsp.ThisUpdate) / 2)
|
||||
if time.Now().After(refreshTime) {
|
||||
err := stapleOCSP(cfg.Storage, &cert, nil)
|
||||
_, err := stapleOCSP(cfg.Storage, &cert, nil)
|
||||
if err != nil {
|
||||
// An error with OCSP stapling is not the end of the world, and in fact, is
|
||||
// quite common considering not all certs have issuer URLs that support it.
|
||||
log.Printf("[ERROR] Getting OCSP for %s: %v", hello.ServerName, err)
|
||||
}
|
||||
cfg.certCache.mu.Lock()
|
||||
cfg.certCache.cache[cert.Hash] = cert
|
||||
cfg.certCache.cache[cert.hash] = cert
|
||||
cfg.certCache.mu.Unlock()
|
||||
}
|
||||
}
|
||||
@ -333,7 +370,7 @@ func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certifi
|
||||
//
|
||||
// This function is safe for use by multiple concurrent goroutines.
|
||||
func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCert Certificate) (Certificate, error) {
|
||||
name := NormalizedName(hello.ServerName)
|
||||
name := cfg.getNameFromClientHello(hello)
|
||||
|
||||
obtainCertWaitChansMu.Lock()
|
||||
wait, ok := obtainCertWaitChans[name]
|
||||
@ -350,9 +387,22 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
|
||||
obtainCertWaitChans[name] = wait
|
||||
obtainCertWaitChansMu.Unlock()
|
||||
|
||||
// Make sure a certificate for this name should be obtained on-demand
|
||||
err := cfg.checkIfCertShouldBeObtained(name)
|
||||
if err != nil {
|
||||
// if not, remove from cache (it will be deleted from storage later)
|
||||
cfg.certCache.mu.Lock()
|
||||
cfg.certCache.removeCertificate(currentCert)
|
||||
cfg.certCache.mu.Unlock()
|
||||
return Certificate{}, err
|
||||
}
|
||||
|
||||
// renew and reload the certificate
|
||||
log.Printf("[INFO] Renewing certificate for %s", name)
|
||||
err := cfg.RenewCert(name, false)
|
||||
// TODO: use a proper context; we use one with timeout because retries are enabled because interactive is false
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 90*time.Second)
|
||||
defer cancel()
|
||||
err = cfg.RenewCert(ctx, name, false)
|
||||
if err == nil {
|
||||
// even though the recursive nature of the dynamic cert loading
|
||||
// would just call this function anyway, we do it here to
|
||||
@ -384,10 +434,15 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
|
||||
// tryDistributedChallengeSolver is to be called when the clientHello pertains to
|
||||
// a TLS-ALPN challenge and a certificate is required to solve it. This method
|
||||
// checks the distributed store of challenge info files and, if a matching ServerName
|
||||
// is present, it makes a certificate to solve this challenge and returns it.
|
||||
// is present, it makes a certificate to solve this challenge and returns it. For
|
||||
// this to succeed, it requires that cfg.Issuer is of type *ACMEManager.
|
||||
// A boolean true is returned if a valid certificate is returned.
|
||||
func (cfg *Config) tryDistributedChallengeSolver(clientHello *tls.ClientHelloInfo) (Certificate, bool, error) {
|
||||
tokenKey := distributedSolver{config: cfg}.challengeTokensKey(clientHello.ServerName)
|
||||
am, ok := cfg.Issuer.(*ACMEManager)
|
||||
if !ok {
|
||||
return Certificate{}, false, nil
|
||||
}
|
||||
tokenKey := distributedSolver{acmeManager: am, caURL: am.CA}.challengeTokensKey(clientHello.ServerName)
|
||||
chalInfoBytes, err := cfg.Storage.Load(tokenKey)
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); ok {
|
||||
@ -413,6 +468,24 @@ func (cfg *Config) tryDistributedChallengeSolver(clientHello *tls.ClientHelloInf
|
||||
return Certificate{Certificate: *cert}, true, nil
|
||||
}
|
||||
|
||||
// getNameFromClientHello returns a normalized form of hello.ServerName.
|
||||
// If hello.ServerName is empty (i.e. client did not use SNI), then the
|
||||
// associated connection's local address is used to extract an IP address.
|
||||
func (*Config) getNameFromClientHello(hello *tls.ClientHelloInfo) string {
|
||||
name := NormalizedName(hello.ServerName)
|
||||
if name != "" || hello.Conn == nil {
|
||||
return name
|
||||
}
|
||||
|
||||
// if no SNI, try using IP address on the connection
|
||||
localAddr := hello.Conn.LocalAddr().String()
|
||||
localAddrHost, _, err := net.SplitHostPort(localAddr)
|
||||
if err == nil {
|
||||
return localAddrHost
|
||||
}
|
||||
return localAddr
|
||||
}
|
||||
|
||||
// NormalizedName returns a cleaned form of serverName that is
|
||||
// used for consistency when referring to a SNI value.
|
||||
func NormalizedName(serverName string) string {
|
@ -20,7 +20,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/challenge/http01"
|
||||
"github.com/go-acme/lego/v3/challenge/http01"
|
||||
)
|
||||
|
||||
// HTTPChallengeHandler wraps h in a handler that can solve the ACME
|
||||
@ -28,55 +28,57 @@ import (
|
||||
// cache backed by a functional storage facility, since that is where
|
||||
// the challenge state is stored between initiation and solution.
|
||||
//
|
||||
// If a request is not an ACME HTTP challenge, h willl be invoked.
|
||||
func (cfg *Config) HTTPChallengeHandler(h http.Handler) http.Handler {
|
||||
// If a request is not an ACME HTTP challenge, h will be invoked.
|
||||
func (am *ACMEManager) HTTPChallengeHandler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if cfg.HandleHTTPChallenge(w, r) {
|
||||
if am.HandleHTTPChallenge(w, r) {
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// HandleHTTPChallenge uses cfg to solve challenge requests from an ACME
|
||||
// HandleHTTPChallenge uses am to solve challenge requests from an ACME
|
||||
// server that were initiated by this instance or any other instance in
|
||||
// this cluster (being, any instances using the same storage cfg does).
|
||||
// this cluster (being, any instances using the same storage am does).
|
||||
//
|
||||
// If the HTTP challenge is disabled, this function is a no-op.
|
||||
//
|
||||
// If cfg is nil or if cfg does not have a certificate cache backed by
|
||||
// If am is nil or if am does not have a certificate cache backed by
|
||||
// usable storage, solving the HTTP challenge will fail.
|
||||
//
|
||||
// It returns true if it handled the request; if so, the response has
|
||||
// already been written. If false is returned, this call was a no-op and
|
||||
// the request has not been handled.
|
||||
func (cfg *Config) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
|
||||
if cfg == nil {
|
||||
func (am *ACMEManager) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
|
||||
if am == nil {
|
||||
return false
|
||||
}
|
||||
if cfg.DisableHTTPChallenge {
|
||||
if am.DisableHTTPChallenge {
|
||||
return false
|
||||
}
|
||||
if !LooksLikeHTTPChallenge(r) {
|
||||
return false
|
||||
}
|
||||
return cfg.distributedHTTPChallengeSolver(w, r)
|
||||
return am.distributedHTTPChallengeSolver(w, r)
|
||||
}
|
||||
|
||||
// distributedHTTPChallengeSolver checks to see if this challenge
|
||||
// request was initiated by this or another instance which uses the
|
||||
// same storage as cfg does, and attempts to complete the challenge for
|
||||
// same storage as am does, and attempts to complete the challenge for
|
||||
// it. It returns true if the request was handled; false otherwise.
|
||||
func (cfg *Config) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http.Request) bool {
|
||||
if cfg == nil {
|
||||
func (am *ACMEManager) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http.Request) bool {
|
||||
if am == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
tokenKey := distributedSolver{config: cfg}.challengeTokensKey(r.Host)
|
||||
chalInfoBytes, err := cfg.Storage.Load(tokenKey)
|
||||
host := hostOnly(r.Host)
|
||||
|
||||
tokenKey := distributedSolver{acmeManager: am, caURL: am.CA}.challengeTokensKey(host)
|
||||
chalInfoBytes, err := am.config.Storage.Load(tokenKey)
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); !ok {
|
||||
log.Printf("[ERROR][%s] Opening distributed HTTP challenge token file: %v", r.Host, err)
|
||||
log.Printf("[ERROR][%s] Opening distributed HTTP challenge token file: %v", host, err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -84,7 +86,7 @@ func (cfg *Config) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http
|
||||
var chalInfo challengeInfo
|
||||
err = json.Unmarshal(chalInfoBytes, &chalInfo)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR][%s] Decoding challenge token file %s (corrupted?): %v", r.Host, tokenKey, err)
|
||||
log.Printf("[ERROR][%s] Decoding challenge token file %s (corrupted?): %v", host, tokenKey, err)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -97,12 +99,12 @@ func (cfg *Config) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http
|
||||
func answerHTTPChallenge(w http.ResponseWriter, r *http.Request, chalInfo challengeInfo) bool {
|
||||
challengeReqPath := http01.ChallengePath(chalInfo.Token)
|
||||
if r.URL.Path == challengeReqPath &&
|
||||
strings.HasPrefix(r.Host, chalInfo.Domain) &&
|
||||
strings.EqualFold(hostOnly(r.Host), chalInfo.Domain) && // mitigate DNS rebinding attacks
|
||||
r.Method == "GET" {
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
w.Write([]byte(chalInfo.KeyAuth))
|
||||
r.Close = true
|
||||
log.Printf("[INFO][%s] Served key authentication (distributed)", chalInfo.Domain)
|
||||
log.Printf("[INFO][%s] Served key authentication (HTTP challenge)", chalInfo.Domain)
|
||||
return true
|
||||
}
|
||||
return false
|
@ -15,7 +15,13 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
@ -32,24 +38,22 @@ func (certCache *Cache) maintainAssets() {
|
||||
|
||||
log.Printf("[INFO][cache:%p] Started certificate maintenance routine", certCache)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-renewalTicker.C:
|
||||
log.Printf("[INFO][cache:%p] Scanning for expiring certificates", certCache)
|
||||
err := certCache.RenewManagedCertificates(false)
|
||||
err := certCache.RenewManagedCertificates(ctx)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR][cache:%p] Renewing managed certificates: %v", certCache, err)
|
||||
}
|
||||
log.Printf("[INFO][cache:%p] Done scanning certificates", certCache)
|
||||
case <-ocspTicker.C:
|
||||
log.Printf("[INFO][cache:%p] Scanning for stale OCSP staples", certCache)
|
||||
certCache.updateOCSPStaples()
|
||||
// certCache.deleteOldStapleFiles()
|
||||
log.Printf("[INFO][cache:%p] Done checking OCSP staples", certCache)
|
||||
certCache.updateOCSPStaples(ctx)
|
||||
case <-certCache.stopChan:
|
||||
renewalTicker.Stop()
|
||||
ocspTicker.Stop()
|
||||
// TODO: stop any in-progress maintenance operations and clear locks we made
|
||||
// TODO: stop any in-progress maintenance operations and clear locks we made (this might be done now with our use of context)
|
||||
log.Printf("[INFO][cache:%p] Stopped certificate maintenance routine", certCache)
|
||||
close(certCache.doneChan)
|
||||
return
|
||||
@ -60,8 +64,9 @@ func (certCache *Cache) maintainAssets() {
|
||||
// RenewManagedCertificates renews managed certificates,
|
||||
// including ones loaded on-demand. Note that this is done
|
||||
// automatically on a regular basis; normally you will not
|
||||
// need to call this.
|
||||
func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
|
||||
// need to call this. This method assumes non-interactive
|
||||
// mode (i.e. operating in the background).
|
||||
func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
// configs will hold a map of certificate name to the config
|
||||
// to use when managing that certificate
|
||||
configs := make(map[string]*Config)
|
||||
@ -98,10 +103,11 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
|
||||
log.Printf("[ERROR] No configuration associated with certificate for names %v; unable to manage", cert.Names)
|
||||
continue
|
||||
}
|
||||
configs[cert.Names[0]] = cfg
|
||||
|
||||
// if time is up or expires soon, we need to try to renew it
|
||||
if cert.NeedsRenewal(cfg) {
|
||||
configs[cert.Names[0]] = cfg
|
||||
|
||||
// see if the certificate in storage has already been renewed, possibly by another
|
||||
// instance that didn't coordinate with this one; if so, just load it (this
|
||||
// might happen if another instance already renewed it - kinda sloppy but checking disk
|
||||
@ -130,71 +136,74 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
|
||||
|
||||
// Reload certificates that merely need to be updated in memory
|
||||
for _, oldCert := range reloadQueue {
|
||||
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
|
||||
log.Printf("[INFO] Certificate for %v expires in %v, but is already renewed in storage; reloading stored certificate",
|
||||
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC())
|
||||
log.Printf("[INFO] %v Maintenance routine: certificate expires in %s, but is already renewed in storage; reloading stored certificate",
|
||||
oldCert.Names, timeLeft)
|
||||
|
||||
cfg := configs[oldCert.Names[0]]
|
||||
|
||||
// crucially, this happens OUTSIDE a lock on the certCache
|
||||
err := cfg.reloadManagedCertificate(oldCert)
|
||||
if err != nil {
|
||||
if interactive {
|
||||
return err // operator is present, so report error immediately
|
||||
}
|
||||
log.Printf("[ERROR] Loading renewed certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Renewal queue
|
||||
for _, oldCert := range renewQueue {
|
||||
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
|
||||
log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", oldCert.Names, timeLeft)
|
||||
|
||||
cfg := configs[oldCert.Names[0]]
|
||||
|
||||
// Get the name which we should use to renew this certificate;
|
||||
// we only support managing certificates with one name per cert,
|
||||
// so this should be easy.
|
||||
renewName := oldCert.Names[0]
|
||||
|
||||
// perform renewal
|
||||
err := cfg.RenewCert(renewName, interactive)
|
||||
err := certCache.queueRenewalTask(ctx, oldCert, cfg)
|
||||
if err != nil {
|
||||
if interactive {
|
||||
// Certificate renewal failed and the operator is present. See a discussion about
|
||||
// this in issue mholt/caddy#642. For a while, we only stopped if the certificate
|
||||
// was expired, but in reality, there is no difference between reporting it now
|
||||
// versus later, except that there's somebody present to deal with it right now.
|
||||
// Follow-up: See issue mholt/caddy#1680. Only fail in this case if the certificate
|
||||
// is dangerously close to expiration.
|
||||
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
|
||||
if timeLeft < cfg.RenewDurationBeforeAtStartup {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Printf("[ERROR] %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Deletion queue
|
||||
certCache.mu.Lock()
|
||||
for _, cert := range deleteQueue {
|
||||
certCache.removeCertificate(cert)
|
||||
}
|
||||
certCache.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificate, cfg *Config) error {
|
||||
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC())
|
||||
log.Printf("[INFO] %v Maintenance routine: certificate expires in %v; queueing for renewal", oldCert.Names, timeLeft)
|
||||
|
||||
// Get the name which we should use to renew this certificate;
|
||||
// we only support managing certificates with one name per cert,
|
||||
// so this should be easy.
|
||||
renewName := oldCert.Names[0]
|
||||
|
||||
// queue up this renewal job (is a no-op if already active or queued)
|
||||
jm.Submit("renew_"+renewName, func() error {
|
||||
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC())
|
||||
log.Printf("[INFO] %v Maintenance routine: attempting renewal with %v remaining", oldCert.Names, timeLeft)
|
||||
|
||||
// perform renewal - crucially, this happens OUTSIDE a lock on certCache
|
||||
err := cfg.RenewCert(ctx, renewName, false)
|
||||
if err != nil {
|
||||
if cfg.OnDemand != nil {
|
||||
// loaded dynamically, remove dynamically
|
||||
deleteQueue = append(deleteQueue, oldCert)
|
||||
certCache.mu.Lock()
|
||||
certCache.removeCertificate(oldCert)
|
||||
certCache.mu.Unlock()
|
||||
}
|
||||
continue
|
||||
return fmt.Errorf("%v %v", oldCert.Names, err)
|
||||
}
|
||||
|
||||
// successful renewal, so update in-memory cache by loading
|
||||
// renewed certificate so it will be used with handshakes
|
||||
err = cfg.reloadManagedCertificate(oldCert)
|
||||
if err != nil {
|
||||
if interactive {
|
||||
return err // operator is present, so report error immediately
|
||||
}
|
||||
log.Printf("[ERROR] %v", err)
|
||||
return ErrNoRetry{fmt.Errorf("%v %v", oldCert.Names, err)}
|
||||
}
|
||||
}
|
||||
|
||||
// Deletion queue
|
||||
for _, cert := range deleteQueue {
|
||||
certCache.removeCertificate(cert)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -205,31 +214,46 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
|
||||
// OCSP maintenance strives to abide the relevant points on
|
||||
// Ryan Sleevi's recommendations for good OCSP support:
|
||||
// https://gist.github.com/sleevi/5efe9ef98961ecfb4da8
|
||||
func (certCache *Cache) updateOCSPStaples() {
|
||||
// Create a temporary place to store updates
|
||||
// until we release the potentially long-lived
|
||||
// read lock and use a short-lived write lock
|
||||
// on the certificate cache.
|
||||
func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
// temporary structures to store updates or tasks
|
||||
// so that we can keep our locks short-lived
|
||||
type ocspUpdate struct {
|
||||
rawBytes []byte
|
||||
parsed *ocsp.Response
|
||||
}
|
||||
type updateQueueEntry struct {
|
||||
cert Certificate
|
||||
certHash string
|
||||
lastNextUpdate time.Time
|
||||
}
|
||||
updated := make(map[string]ocspUpdate)
|
||||
var updateQueue []updateQueueEntry
|
||||
var renewQueue []Certificate
|
||||
configs := make(map[string]*Config)
|
||||
|
||||
// obtain brief read lock during our scan to see which staples need updating
|
||||
certCache.mu.RLock()
|
||||
for certHash, cert := range certCache.cache {
|
||||
// no point in updating OCSP for expired certificates
|
||||
if time.Now().After(cert.NotAfter) {
|
||||
if time.Now().After(cert.Leaf.NotAfter) {
|
||||
continue
|
||||
}
|
||||
|
||||
var lastNextUpdate time.Time
|
||||
if cert.OCSP != nil {
|
||||
lastNextUpdate = cert.OCSP.NextUpdate
|
||||
if freshOCSP(cert.OCSP) {
|
||||
if cert.ocsp != nil {
|
||||
lastNextUpdate = cert.ocsp.NextUpdate
|
||||
if freshOCSP(cert.ocsp) {
|
||||
continue // no need to update staple if ours is still fresh
|
||||
}
|
||||
}
|
||||
updateQueue = append(updateQueue, updateQueueEntry{cert, certHash, lastNextUpdate})
|
||||
}
|
||||
certCache.mu.RUnlock()
|
||||
|
||||
// perform updates outside of any lock on certCache
|
||||
for _, qe := range updateQueue {
|
||||
cert := qe.cert
|
||||
certHash := qe.certHash
|
||||
lastNextUpdate := qe.lastNextUpdate
|
||||
|
||||
cfg, err := certCache.getConfig(cert)
|
||||
if err != nil {
|
||||
@ -242,9 +266,9 @@ func (certCache *Cache) updateOCSPStaples() {
|
||||
continue
|
||||
}
|
||||
|
||||
err = stapleOCSP(cfg.Storage, &cert, nil)
|
||||
ocspResp, err := stapleOCSP(cfg.Storage, &cert, nil)
|
||||
if err != nil {
|
||||
if cert.OCSP != nil {
|
||||
if cert.ocsp != nil {
|
||||
// if there was no staple before, that's fine; otherwise we should log the error
|
||||
log.Printf("[ERROR] Checking OCSP: %v", err)
|
||||
}
|
||||
@ -254,34 +278,66 @@ func (certCache *Cache) updateOCSPStaples() {
|
||||
// By this point, we've obtained the latest OCSP response.
|
||||
// If there was no staple before, or if the response is updated, make
|
||||
// sure we apply the update to all names on the certificate.
|
||||
if cert.OCSP != nil && (lastNextUpdate.IsZero() || lastNextUpdate != cert.OCSP.NextUpdate) {
|
||||
if cert.ocsp != nil && (lastNextUpdate.IsZero() || lastNextUpdate != cert.ocsp.NextUpdate) {
|
||||
log.Printf("[INFO] Advancing OCSP staple for %v from %s to %s",
|
||||
cert.Names, lastNextUpdate, cert.OCSP.NextUpdate)
|
||||
updated[certHash] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.OCSP}
|
||||
cert.Names, lastNextUpdate, cert.ocsp.NextUpdate)
|
||||
updated[certHash] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.ocsp}
|
||||
}
|
||||
|
||||
// If a managed certificate was revoked, we should attempt
|
||||
// to replace it with a new one. If that fails, oh well.
|
||||
if cert.managed && ocspResp.Status == ocsp.Revoked && len(cert.Names) > 0 {
|
||||
renewQueue = append(renewQueue, cert)
|
||||
configs[cert.Names[0]] = cfg
|
||||
}
|
||||
}
|
||||
certCache.mu.RUnlock()
|
||||
|
||||
// These write locks should be brief since we have all the info we need now.
|
||||
for certKey, update := range updated {
|
||||
certCache.mu.Lock()
|
||||
cert := certCache.cache[certKey]
|
||||
cert.OCSP = update.parsed
|
||||
cert.ocsp = update.parsed
|
||||
cert.Certificate.OCSPStaple = update.rawBytes
|
||||
certCache.cache[certKey] = cert
|
||||
certCache.mu.Unlock()
|
||||
}
|
||||
|
||||
// We attempt to replace any certificates that were revoked.
|
||||
// Crucially, this happens OUTSIDE a lock on the certCache.
|
||||
for _, oldCert := range renewQueue {
|
||||
log.Printf("[INFO] OCSP status for managed certificate %v (expiration=%s) is REVOKED; attempting to replace with new certificate",
|
||||
oldCert.Names, oldCert.Leaf.NotAfter)
|
||||
|
||||
renewName := oldCert.Names[0]
|
||||
cfg := configs[renewName]
|
||||
|
||||
// TODO: consider using a new key in this situation, but we don't know if key storage has been compromised...
|
||||
err := cfg.RenewCert(ctx, renewName, false)
|
||||
if err != nil {
|
||||
// probably better to not serve a revoked certificate at all
|
||||
log.Printf("[ERROR] Obtaining new certificate for %v due to OCSP status of revoked: %v; removing from cache", oldCert.Names, err)
|
||||
certCache.mu.Lock()
|
||||
certCache.removeCertificate(oldCert)
|
||||
certCache.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
err = cfg.reloadManagedCertificate(oldCert)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] After obtaining new certificate due to OCSP status of revoked: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CleanStorageOptions specifies how to clean up a storage unit.
|
||||
type CleanStorageOptions struct {
|
||||
OCSPStaples bool
|
||||
// TODO: long-expired certificates
|
||||
OCSPStaples bool
|
||||
ExpiredCerts bool
|
||||
ExpiredCertGracePeriod time.Duration
|
||||
}
|
||||
|
||||
// CleanStorage tidies up the given storage according to opts; this
|
||||
// generally involves deleting assets which are no longer required.
|
||||
// TODO: We should do this for long-expired certificates, too.
|
||||
// CleanStorage removes assets which are no longer useful,
|
||||
// according to opts.
|
||||
func CleanStorage(storage Storage, opts CleanStorageOptions) {
|
||||
if opts.OCSPStaples {
|
||||
err := deleteOldOCSPStaples(storage)
|
||||
@ -289,6 +345,13 @@ func CleanStorage(storage Storage, opts CleanStorageOptions) {
|
||||
log.Printf("[ERROR] Deleting old OCSP staples: %v", err)
|
||||
}
|
||||
}
|
||||
if opts.ExpiredCerts {
|
||||
err := deleteExpiredCerts(storage, opts.ExpiredCertGracePeriod)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Deleting expired certificates: %v", err)
|
||||
}
|
||||
}
|
||||
// TODO: delete stale locks?
|
||||
}
|
||||
|
||||
func deleteOldOCSPStaples(storage Storage) error {
|
||||
@ -323,16 +386,92 @@ func deleteOldOCSPStaples(storage Storage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteExpiredCerts(storage Storage, gracePeriod time.Duration) error {
|
||||
issuerKeys, err := storage.List(prefixCerts, false)
|
||||
if err != nil {
|
||||
// maybe just hasn't been created yet; no big deal
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, issuerKey := range issuerKeys {
|
||||
siteKeys, err := storage.List(issuerKey, false)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Listing contents of %s: %v", issuerKey, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, siteKey := range siteKeys {
|
||||
siteAssets, err := storage.List(siteKey, false)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Listing contents of %s: %v", siteKey, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, assetKey := range siteAssets {
|
||||
if path.Ext(assetKey) != ".crt" {
|
||||
continue
|
||||
}
|
||||
|
||||
certFile, err := storage.Load(assetKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading certificate file %s: %v", assetKey, err)
|
||||
}
|
||||
block, _ := pem.Decode(certFile)
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
return fmt.Errorf("certificate file %s does not contain PEM-encoded certificate", assetKey)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("certificate file %s is malformed; error parsing PEM: %v", assetKey, err)
|
||||
}
|
||||
|
||||
if expiredTime := time.Since(cert.NotAfter); expiredTime >= gracePeriod {
|
||||
log.Printf("[INFO] Certificate %s expired %s ago; cleaning up", assetKey, expiredTime)
|
||||
baseName := strings.TrimSuffix(assetKey, ".crt")
|
||||
for _, relatedAsset := range []string{
|
||||
assetKey,
|
||||
baseName + ".key",
|
||||
baseName + ".json",
|
||||
} {
|
||||
log.Printf("[INFO] Deleting %s because resource expired", relatedAsset)
|
||||
err := storage.Delete(relatedAsset)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Cleaning up asset related to expired certificate for %s: %s: %v",
|
||||
baseName, relatedAsset, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update listing; if folder is empty, delete it
|
||||
siteAssets, err = storage.List(siteKey, false)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(siteAssets) == 0 {
|
||||
log.Printf("[INFO] Deleting %s because key is empty", siteKey)
|
||||
err := storage.Delete(siteKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting empty site folder %s: %v", siteKey, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// DefaultRenewCheckInterval is how often to check certificates for renewal.
|
||||
DefaultRenewCheckInterval = 12 * time.Hour
|
||||
// DefaultRenewCheckInterval is how often to check certificates for expiration.
|
||||
// Scans are very lightweight, so this can be semi-frequent. This default should
|
||||
// be smaller than <Minimum Cert Lifetime>*DefaultRenewalWindowRatio/3, which
|
||||
// gives certificates plenty of chance to be renewed on time.
|
||||
DefaultRenewCheckInterval = 10 * time.Minute
|
||||
|
||||
// DefaultRenewDurationBefore is how long before expiration to renew certificates.
|
||||
DefaultRenewDurationBefore = (24 * time.Hour) * 30
|
||||
|
||||
// DefaultRenewDurationBeforeAtStartup is how long before expiration to require
|
||||
// a renewed certificate when the process is first starting up (see mholt/caddy#1680).
|
||||
DefaultRenewDurationBeforeAtStartup = (24 * time.Hour) * 7
|
||||
// DefaultRenewalWindowRatio is how much of a certificate's lifetime becomes the
|
||||
// renewal window. The renewal window is the span of time at the end of the
|
||||
// certificate's validity period in which it should be renewed. A default value
|
||||
// of ~1/3 is pretty safe and recommended for most certificates.
|
||||
DefaultRenewalWindowRatio = 1.0 / 3.0
|
||||
|
||||
// DefaultOCSPCheckInterval is how often to check if OCSP stapling needs updating.
|
||||
DefaultOCSPCheckInterval = 1 * time.Hour
|
@ -35,7 +35,10 @@ import (
|
||||
//
|
||||
// Errors here are not necessarily fatal, it could just be that the
|
||||
// certificate doesn't have an issuer URL.
|
||||
func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) error {
|
||||
//
|
||||
// If a status was received, it returns that status. Note that the
|
||||
// returned status is not always stapled to the certificate.
|
||||
func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) (*ocsp.Response, error) {
|
||||
if pemBundle == nil {
|
||||
// we need a PEM encoding only for some function calls below
|
||||
bundle := new(bytes.Buffer)
|
||||
@ -85,7 +88,7 @@ func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) error {
|
||||
// not contain a link to an OCSP server. But we should log it anyway.
|
||||
// There's nothing else we can do to get OCSP for this certificate,
|
||||
// so we can return here with the error.
|
||||
return fmt.Errorf("no OCSP stapling for %v: %v", cert.Names, ocspErr)
|
||||
return nil, fmt.Errorf("no OCSP stapling for %v: %v", cert.Names, ocspErr)
|
||||
}
|
||||
gotNewOCSP = true
|
||||
}
|
||||
@ -94,24 +97,24 @@ func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) error {
|
||||
// the certificate. If the OCSP response was not loaded from
|
||||
// storage, we persist it for next time.
|
||||
if ocspResp.Status == ocsp.Good {
|
||||
if ocspResp.NextUpdate.After(cert.NotAfter) {
|
||||
if ocspResp.NextUpdate.After(cert.Leaf.NotAfter) {
|
||||
// uh oh, this OCSP response expires AFTER the certificate does, that's kinda bogus.
|
||||
// it was the reason a lot of Symantec-validated sites (not Caddy) went down
|
||||
// in October 2017. https://twitter.com/mattiasgeniar/status/919432824708648961
|
||||
return fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)",
|
||||
cert.Names, cert.NotAfter.Sub(ocspResp.NextUpdate))
|
||||
return ocspResp, fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)",
|
||||
cert.Names, cert.Leaf.NotAfter.Sub(ocspResp.NextUpdate))
|
||||
}
|
||||
cert.Certificate.OCSPStaple = ocspBytes
|
||||
cert.OCSP = ocspResp
|
||||
cert.ocsp = ocspResp
|
||||
if gotNewOCSP {
|
||||
err := storage.Store(ocspStapleKey, ocspBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err)
|
||||
return ocspResp, fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return ocspResp, nil
|
||||
}
|
||||
|
||||
// getOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
219
vendor/github.com/caddyserver/certmagic/ratelimiter.go
generated
vendored
Normal file
219
vendor/github.com/caddyserver/certmagic/ratelimiter.go
generated
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewRateLimiter returns a rate limiter that allows up to maxEvents
|
||||
// in a sliding window of size window. If maxEvents and window are
|
||||
// both 0, or if maxEvents is non-zero and window is 0, rate limiting
|
||||
// is disabled. This function panics if maxEvents is less than 0 or
|
||||
// if maxEvents is 0 and window is non-zero, which is considered to be
|
||||
// an invalid configuration, as it would never allow events.
|
||||
func NewRateLimiter(maxEvents int, window time.Duration) *RingBufferRateLimiter {
|
||||
if maxEvents < 0 {
|
||||
panic("maxEvents cannot be less than zero")
|
||||
}
|
||||
if maxEvents == 0 && window != 0 {
|
||||
panic("invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
|
||||
}
|
||||
rbrl := &RingBufferRateLimiter{
|
||||
window: window,
|
||||
ring: make([]time.Time, maxEvents),
|
||||
started: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
ticket: make(chan struct{}),
|
||||
}
|
||||
go rbrl.loop()
|
||||
<-rbrl.started // make sure loop is ready to receive before we return
|
||||
return rbrl
|
||||
}
|
||||
|
||||
// RingBufferRateLimiter uses a ring to enforce rate limits
|
||||
// consisting of a maximum number of events within a single
|
||||
// sliding window of a given duration. An empty value is
|
||||
// not valid; use NewRateLimiter to get one.
|
||||
type RingBufferRateLimiter struct {
|
||||
window time.Duration
|
||||
ring []time.Time // maxEvents == len(ring)
|
||||
cursor int // always points to the oldest timestamp
|
||||
mu sync.Mutex // protects ring, cursor, and window
|
||||
started chan struct{}
|
||||
stopped chan struct{}
|
||||
ticket chan struct{}
|
||||
}
|
||||
|
||||
// Stop cleans up r's scheduling goroutine.
|
||||
func (r *RingBufferRateLimiter) Stop() {
|
||||
close(r.stopped)
|
||||
}
|
||||
|
||||
func (r *RingBufferRateLimiter) loop() {
|
||||
for {
|
||||
// if we've been stopped, return
|
||||
select {
|
||||
case <-r.stopped:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if len(r.ring) == 0 {
|
||||
if r.window == 0 {
|
||||
// rate limiting is disabled; always allow immediately
|
||||
r.permit()
|
||||
continue
|
||||
}
|
||||
panic("invalid configuration: maxEvents = 0 and window != 0 does not allow any events")
|
||||
}
|
||||
|
||||
// wait until next slot is available or until we've been stopped
|
||||
r.mu.Lock()
|
||||
then := r.ring[r.cursor].Add(r.window)
|
||||
r.mu.Unlock()
|
||||
waitDuration := time.Until(then)
|
||||
waitTimer := time.NewTimer(waitDuration)
|
||||
select {
|
||||
case <-waitTimer.C:
|
||||
r.permit()
|
||||
case <-r.stopped:
|
||||
waitTimer.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow returns true if the event is allowed to
|
||||
// happen right now. It does not wait. If the event
|
||||
// is allowed, a ticket is claimed.
|
||||
func (r *RingBufferRateLimiter) Allow() bool {
|
||||
select {
|
||||
case <-r.ticket:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Wait blocks until the event is allowed to occur. It returns an
|
||||
// error if the context is cancelled.
|
||||
func (r *RingBufferRateLimiter) Wait(ctx context.Context) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return context.Canceled
|
||||
case <-r.ticket:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MaxEvents returns the maximum number of events that
|
||||
// are allowed within the sliding window.
|
||||
func (r *RingBufferRateLimiter) MaxEvents() int {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return len(r.ring)
|
||||
}
|
||||
|
||||
// SetMaxEvents changes the maximum number of events that are
|
||||
// allowed in the sliding window. If the new limit is lower,
|
||||
// the oldest events will be forgotten. If the new limit is
|
||||
// higher, the window will suddenly have capacity for new
|
||||
// reservations. It panics if maxEvents is 0 and window size
|
||||
// is not zero.
|
||||
func (r *RingBufferRateLimiter) SetMaxEvents(maxEvents int) {
|
||||
newRing := make([]time.Time, maxEvents)
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if r.window != 0 && maxEvents == 0 {
|
||||
panic("invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
|
||||
}
|
||||
|
||||
// only make the change if the new limit is different
|
||||
if maxEvents == len(r.ring) {
|
||||
return
|
||||
}
|
||||
|
||||
// the new ring may be smaller; fast-forward to the
|
||||
// oldest timestamp that will be kept in the new
|
||||
// ring so the oldest ones are forgotten and the
|
||||
// newest ones will be remembered
|
||||
sizeDiff := len(r.ring) - maxEvents
|
||||
for i := 0; i < sizeDiff; i++ {
|
||||
r.advance()
|
||||
}
|
||||
|
||||
if len(r.ring) > 0 {
|
||||
// copy timestamps into the new ring until we
|
||||
// have either copied all of them or have reached
|
||||
// the capacity of the new ring
|
||||
startCursor := r.cursor
|
||||
for i := 0; i < len(newRing); i++ {
|
||||
newRing[i] = r.ring[r.cursor]
|
||||
r.advance()
|
||||
if r.cursor == startCursor {
|
||||
// new ring is larger than old one;
|
||||
// "we've come full circle"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.ring = newRing
|
||||
r.cursor = 0
|
||||
}
|
||||
|
||||
// Window returns the size of the sliding window.
|
||||
func (r *RingBufferRateLimiter) Window() time.Duration {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.window
|
||||
}
|
||||
|
||||
// SetWindow changes r's sliding window duration to window.
|
||||
// Goroutines that are already blocked on a call to Wait()
|
||||
// will not be affected. It panics if window is non-zero
|
||||
// but the max event limit is 0.
|
||||
func (r *RingBufferRateLimiter) SetWindow(window time.Duration) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if window != 0 && len(r.ring) == 0 {
|
||||
panic("invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
|
||||
}
|
||||
r.window = window
|
||||
}
|
||||
|
||||
// permit allows one event through the throttle. This method
|
||||
// blocks until a goroutine is waiting for a ticket or until
|
||||
// the rate limiter is stopped.
|
||||
func (r *RingBufferRateLimiter) permit() {
|
||||
for {
|
||||
select {
|
||||
case r.started <- struct{}{}:
|
||||
// notify parent goroutine that we've started; should
|
||||
// only happen once, before constructor returns
|
||||
continue
|
||||
case <-r.stopped:
|
||||
return
|
||||
case r.ticket <- struct{}{}:
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if len(r.ring) > 0 {
|
||||
r.ring[r.cursor] = time.Now()
|
||||
r.advance()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// advance moves the cursor to the next position.
|
||||
// It is NOT safe for concurrent use, so it must
|
||||
// be called inside a lock on r.mu.
|
||||
func (r *RingBufferRateLimiter) advance() {
|
||||
r.cursor++
|
||||
if r.cursor >= len(r.ring) {
|
||||
r.cursor = 0
|
||||
}
|
||||
}
|
412
vendor/github.com/caddyserver/certmagic/solvers.go
generated
vendored
Normal file
412
vendor/github.com/caddyserver/certmagic/solvers.go
generated
vendored
Normal file
@ -0,0 +1,412 @@
|
||||
// Copyright 2015 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
)
|
||||
|
||||
// httpSolver solves the HTTP challenge. It must be
|
||||
// associated with a config and an address to use
|
||||
// for solving the challenge. If multiple httpSolvers
|
||||
// are initialized concurrently, the first one to
|
||||
// begin will start the server, and the last one to
|
||||
// finish will stop the server. This solver must be
|
||||
// wrapped by a distributedSolver to work properly,
|
||||
// because the only way the HTTP challenge handler
|
||||
// can access the keyAuth material is by loading it
|
||||
// from storage, which is done by distributedSolver.
|
||||
type httpSolver struct {
|
||||
closed int32 // accessed atomically
|
||||
acmeManager *ACMEManager
|
||||
address string
|
||||
}
|
||||
|
||||
// Present starts an HTTP server if none is already listening on s.address.
|
||||
func (s *httpSolver) Present(domain, token, keyAuth string) error {
|
||||
solversMu.Lock()
|
||||
defer solversMu.Unlock()
|
||||
|
||||
si := getSolverInfo(s.address)
|
||||
si.count++
|
||||
if si.listener != nil {
|
||||
return nil // already be served by us
|
||||
}
|
||||
|
||||
// notice the unusual error handling here; we
|
||||
// only continue to start a challenge server if
|
||||
// we got a listener; in all other cases return
|
||||
ln, err := robustTryListen(s.address)
|
||||
if ln == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// successfully bound socket, so save listener and start key auth HTTP server
|
||||
si.listener = ln
|
||||
go s.serve(si)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serve is an HTTP server that serves only HTTP challenge responses.
|
||||
func (s *httpSolver) serve(si *solverInfo) {
|
||||
defer close(si.done)
|
||||
httpServer := &http.Server{Handler: s.acmeManager.HTTPChallengeHandler(http.NewServeMux())}
|
||||
httpServer.SetKeepAlivesEnabled(false)
|
||||
err := httpServer.Serve(si.listener)
|
||||
if err != nil && atomic.LoadInt32(&s.closed) != 1 {
|
||||
log.Printf("[ERROR] key auth HTTP server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CleanUp cleans up the HTTP server if it is the last one to finish.
|
||||
func (s *httpSolver) CleanUp(domain, token, keyAuth string) error {
|
||||
solversMu.Lock()
|
||||
defer solversMu.Unlock()
|
||||
si := getSolverInfo(s.address)
|
||||
si.count--
|
||||
if si.count == 0 {
|
||||
// last one out turns off the lights
|
||||
atomic.StoreInt32(&s.closed, 1)
|
||||
if si.listener != nil {
|
||||
si.listener.Close()
|
||||
<-si.done
|
||||
}
|
||||
delete(solvers, s.address)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tlsALPNSolver is a type that can solve TLS-ALPN challenges.
|
||||
// It must have an associated config and address on which to
|
||||
// serve the challenge.
|
||||
type tlsALPNSolver struct {
|
||||
config *Config
|
||||
address string
|
||||
}
|
||||
|
||||
// Present adds the certificate to the certificate cache and, if
|
||||
// needed, starts a TLS server for answering TLS-ALPN challenges.
|
||||
func (s *tlsALPNSolver) Present(domain, token, keyAuth string) error {
|
||||
// load the certificate into the cache; this isn't strictly necessary
|
||||
// if we're using the distributed solver since our GetCertificate
|
||||
// function will check storage for the keyAuth anyway, but it seems
|
||||
// like loading it into the cache is the right thing to do
|
||||
cert, err := tlsalpn01.ChallengeCert(domain, keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certHash := hashCertificateChain(cert.Certificate)
|
||||
s.config.certCache.mu.Lock()
|
||||
s.config.certCache.cache[tlsALPNCertKeyName(domain)] = Certificate{
|
||||
Certificate: *cert,
|
||||
Names: []string{domain},
|
||||
hash: certHash, // perhaps not necesssary
|
||||
}
|
||||
s.config.certCache.mu.Unlock()
|
||||
|
||||
// the rest of this function increments the
|
||||
// challenge count for the solver at this
|
||||
// listener address, and if necessary, starts
|
||||
// a simple TLS server
|
||||
|
||||
solversMu.Lock()
|
||||
defer solversMu.Unlock()
|
||||
|
||||
si := getSolverInfo(s.address)
|
||||
si.count++
|
||||
if si.listener != nil {
|
||||
return nil // already be served by us
|
||||
}
|
||||
|
||||
// notice the unusual error handling here; we
|
||||
// only continue to start a challenge server if
|
||||
// we got a listener; in all other cases return
|
||||
ln, err := robustTryListen(s.address)
|
||||
if ln == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// we were able to bind the socket, so make it into a TLS
|
||||
// listener, store it with the solverInfo, and start the
|
||||
// challenge server
|
||||
|
||||
si.listener = tls.NewListener(ln, s.config.TLSConfig())
|
||||
|
||||
go func() {
|
||||
defer close(si.done)
|
||||
for {
|
||||
conn, err := si.listener.Accept()
|
||||
if err != nil {
|
||||
if atomic.LoadInt32(&si.closed) == 1 {
|
||||
return
|
||||
}
|
||||
log.Printf("[ERROR] TLS-ALPN challenge server: accept: %v", err)
|
||||
continue
|
||||
}
|
||||
go s.handleConn(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConn completes the TLS handshake and then closes conn.
|
||||
func (*tlsALPNSolver) handleConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
tlsConn, ok := conn.(*tls.Conn)
|
||||
if !ok {
|
||||
log.Printf("[ERROR] TLS-ALPN challenge server: expected tls.Conn but got %T: %#v", conn, conn)
|
||||
return
|
||||
}
|
||||
err := tlsConn.Handshake()
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] TLS-ALPN challenge server: handshake: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// CleanUp removes the challenge certificate from the cache, and if
|
||||
// it is the last one to finish, stops the TLS server.
|
||||
func (s *tlsALPNSolver) CleanUp(domain, token, keyAuth string) error {
|
||||
s.config.certCache.mu.Lock()
|
||||
delete(s.config.certCache.cache, tlsALPNCertKeyName(domain))
|
||||
s.config.certCache.mu.Unlock()
|
||||
|
||||
solversMu.Lock()
|
||||
defer solversMu.Unlock()
|
||||
si := getSolverInfo(s.address)
|
||||
si.count--
|
||||
if si.count == 0 {
|
||||
// last one out turns off the lights
|
||||
atomic.StoreInt32(&si.closed, 1)
|
||||
if si.listener != nil {
|
||||
si.listener.Close()
|
||||
<-si.done
|
||||
}
|
||||
delete(solvers, s.address)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// tlsALPNCertKeyName returns the key to use when caching a cert
|
||||
// for use with the TLS-ALPN ACME challenge. It is simply to help
|
||||
// avoid conflicts (although at time of writing, there shouldn't
|
||||
// be, since the cert cache is keyed by hash of certificate chain).
|
||||
func tlsALPNCertKeyName(sniName string) string {
|
||||
return sniName + ":acme-tls-alpn"
|
||||
}
|
||||
|
||||
// distributedSolver allows the ACME HTTP-01 and TLS-ALPN challenges
|
||||
// to be solved by an instance other than the one which initiated it.
|
||||
// This is useful behind load balancers or in other cluster/fleet
|
||||
// configurations. The only requirement is that the instance which
|
||||
// initiates the challenge shares the same storage and locker with
|
||||
// the others in the cluster. The storage backing the certificate
|
||||
// cache in distributedSolver.config is crucial.
|
||||
//
|
||||
// Obviously, the instance which completes the challenge must be
|
||||
// serving on the HTTPChallengePort for the HTTP-01 challenge or the
|
||||
// TLSALPNChallengePort for the TLS-ALPN-01 challenge (or have all
|
||||
// the packets port-forwarded) to receive and handle the request. The
|
||||
// server which receives the challenge must handle it by checking to
|
||||
// see if the challenge token exists in storage, and if so, decode it
|
||||
// and use it to serve up the correct response. HTTPChallengeHandler
|
||||
// in this package as well as the GetCertificate method implemented
|
||||
// by a Config support and even require this behavior.
|
||||
//
|
||||
// In short: the only two requirements for cluster operation are
|
||||
// sharing sync and storage, and using the facilities provided by
|
||||
// this package for solving the challenges.
|
||||
type distributedSolver struct {
|
||||
// The config with a certificate cache
|
||||
// with a reference to the storage to
|
||||
// use which is shared among all the
|
||||
// instances in the cluster - REQUIRED.
|
||||
acmeManager *ACMEManager
|
||||
|
||||
// Since the distributedSolver is only a
|
||||
// wrapper over an actual solver, place
|
||||
// the actual solver here.
|
||||
providerServer challenge.Provider
|
||||
|
||||
// The CA endpoint URL associated with
|
||||
// this solver.
|
||||
caURL string
|
||||
}
|
||||
|
||||
// Present invokes the underlying solver's Present method
|
||||
// and also stores domain, token, and keyAuth to the storage
|
||||
// backing the certificate cache of dhs.acmeManager.
|
||||
func (dhs distributedSolver) Present(domain, token, keyAuth string) error {
|
||||
infoBytes, err := json.Marshal(challengeInfo{
|
||||
Domain: domain,
|
||||
Token: token,
|
||||
KeyAuth: keyAuth,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dhs.acmeManager.config.Storage.Store(dhs.challengeTokensKey(domain), infoBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dhs.providerServer.Present(domain, token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("presenting with embedded provider: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp invokes the underlying solver's CleanUp method
|
||||
// and also cleans up any assets saved to storage.
|
||||
func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error {
|
||||
err := dhs.acmeManager.config.Storage.Delete(dhs.challengeTokensKey(domain))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dhs.providerServer.CleanUp(domain, token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cleaning up embedded provider: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// challengeTokensPrefix returns the key prefix for challenge info.
|
||||
func (dhs distributedSolver) challengeTokensPrefix() string {
|
||||
return path.Join(dhs.acmeManager.storageKeyCAPrefix(dhs.caURL), "challenge_tokens")
|
||||
}
|
||||
|
||||
// challengeTokensKey returns the key to use to store and access
|
||||
// challenge info for domain.
|
||||
func (dhs distributedSolver) challengeTokensKey(domain string) string {
|
||||
return path.Join(dhs.challengeTokensPrefix(), StorageKeys.Safe(domain)+".json")
|
||||
}
|
||||
|
||||
type challengeInfo struct {
|
||||
Domain, Token, KeyAuth string
|
||||
}
|
||||
|
||||
// solverInfo associates a listener with the
|
||||
// number of challenges currently using it.
|
||||
type solverInfo struct {
|
||||
closed int32 // accessed atomically
|
||||
count int
|
||||
listener net.Listener
|
||||
done chan struct{} // used to signal when our own solver server is done
|
||||
}
|
||||
|
||||
// getSolverInfo gets a valid solverInfo struct for address.
|
||||
func getSolverInfo(address string) *solverInfo {
|
||||
si, ok := solvers[address]
|
||||
if !ok {
|
||||
si = &solverInfo{done: make(chan struct{})}
|
||||
solvers[address] = si
|
||||
}
|
||||
return si
|
||||
}
|
||||
|
||||
// robustTryListen calls net.Listen for a TCP socket at addr.
|
||||
// This function may return both a nil listener and a nil error!
|
||||
// If it was able to bind the socket, it returns the listener
|
||||
// and no error. If it wasn't able to bind the socket because
|
||||
// the socket is already in use, then it returns a nil listener
|
||||
// and nil error. If it had any other error, it returns the
|
||||
// error. The intended error handling logic for this function
|
||||
// is to proceed if the returned listener is not nil; otherwise
|
||||
// return err (which may also be nil). In other words, this
|
||||
// function ignores errors if the socket is already in use,
|
||||
// which is useful for our challenge servers, where we assume
|
||||
// that whatever is already listening can solve the challenges.
|
||||
func robustTryListen(addr string) (net.Listener, error) {
|
||||
var listenErr error
|
||||
for i := 0; i < 2; i++ {
|
||||
// doesn't hurt to sleep briefly before the second
|
||||
// attempt in case the OS has timing issues
|
||||
if i > 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// if we can bind the socket right away, great!
|
||||
var ln net.Listener
|
||||
ln, listenErr = net.Listen("tcp", addr)
|
||||
if listenErr == nil {
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
// if it failed just because the socket is already in use, we
|
||||
// have no choice but to assume that whatever is using the socket
|
||||
// can answer the challenge already, so we ignore the error
|
||||
connectErr := dialTCPSocket(addr)
|
||||
if connectErr == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// hmm, we couldn't connect to the socket, so something else must
|
||||
// be wrong, right? wrong!! we've had reports across multiple OSes
|
||||
// now that sometimes connections fail even though the OS told us
|
||||
// that the address was already in use; either the listener is
|
||||
// fluctuating between open and closed very, very quickly, or the
|
||||
// OS is inconsistent and contradicting itself; I have been unable
|
||||
// to reproduce this, so I'm now resorting to hard-coding substring
|
||||
// matching in error messages as a really hacky and unreliable
|
||||
// safeguard against this, until we can idenify exactly what was
|
||||
// happening; see the following threads for more info:
|
||||
// https://caddy.community/t/caddy-retry-error/7317
|
||||
// https://caddy.community/t/v2-upgrade-to-caddy2-failing-with-errors/7423
|
||||
if strings.Contains(listenErr.Error(), "address already in use") ||
|
||||
strings.Contains(listenErr.Error(), "one usage of each socket address") {
|
||||
log.Printf("[WARNING] OS reports a contradiction: %v - but we cannot connect to it, with this error: %v; continuing anyway 🤞 (I don't know what causes this... if you do, please help?)", listenErr, connectErr)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("could not start listener for challenge server at %s: %v", addr, listenErr)
|
||||
}
|
||||
|
||||
// dialTCPSocket connects to a TCP address just for the sake of
|
||||
// seeing if it is open. It returns a nil error if a TCP connection
|
||||
// can successfully be made to addr within a short timeout.
|
||||
func dialTCPSocket(addr string) error {
|
||||
conn, err := net.DialTimeout("tcp", addr, 250*time.Millisecond)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// The active challenge solvers, keyed by listener address,
|
||||
// and protected by a mutex. Note that the creation of
|
||||
// solver listeners and the incrementing of their counts
|
||||
// are atomic operations guarded by this mutex.
|
||||
var (
|
||||
solvers = make(map[string]*solverInfo)
|
||||
solversMu sync.Mutex
|
||||
)
|
@ -15,10 +15,11 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"log"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -31,6 +32,9 @@ import (
|
||||
// in order to share certificates and other TLS resources
|
||||
// with the cluster.
|
||||
//
|
||||
// The Load, Delete, List, and Stat methods should return
|
||||
// ErrNotExist if the key does not exist.
|
||||
//
|
||||
// Implementations of Storage must be safe for concurrent use.
|
||||
type Storage interface {
|
||||
// Locker provides atomic synchronization
|
||||
@ -43,7 +47,9 @@ type Storage interface {
|
||||
// Load retrieves the value at key.
|
||||
Load(key string) ([]byte, error)
|
||||
|
||||
// Delete deletes key.
|
||||
// Delete deletes key. An error should be
|
||||
// returned only if the key still exists
|
||||
// when the method returns.
|
||||
Delete(key string) error
|
||||
|
||||
// Exists returns true if the key exists
|
||||
@ -80,7 +86,7 @@ type Locker interface {
|
||||
// To prevent deadlocks, all implementations (where this concern
|
||||
// is relevant) should put a reasonable expiration on the lock in
|
||||
// case Unlock is unable to be called due to some sort of network
|
||||
// or system failure or crash.
|
||||
// failure or system crash.
|
||||
Lock(key string) error
|
||||
|
||||
// Unlock releases the lock for key. This method must ONLY be
|
||||
@ -91,6 +97,12 @@ type Locker interface {
|
||||
}
|
||||
|
||||
// KeyInfo holds information about a key in storage.
|
||||
// Key and IsTerminal are required; Modified and Size
|
||||
// are optional if the storage implementation is not
|
||||
// able to get that information. Setting them will
|
||||
// make certain operations more consistent or
|
||||
// predictable, but it is not crucial to basic
|
||||
// functionality.
|
||||
type KeyInfo struct {
|
||||
Key string
|
||||
Modified time.Time
|
||||
@ -123,62 +135,39 @@ type keyValue struct {
|
||||
// in a Storage implementation.
|
||||
type KeyBuilder struct{}
|
||||
|
||||
// CAPrefix returns the storage key prefix for
|
||||
// the given certificate authority URL.
|
||||
func (keys KeyBuilder) CAPrefix(ca string) string {
|
||||
caURL, err := url.Parse(ca)
|
||||
if err != nil {
|
||||
caURL = &url.URL{Host: ca}
|
||||
}
|
||||
return path.Join(prefixACME, keys.Safe(caURL.Host))
|
||||
// CertsPrefix returns the storage key prefix for
|
||||
// the given certificate issuer.
|
||||
func (keys KeyBuilder) CertsPrefix(issuerKey string) string {
|
||||
return path.Join(prefixCerts, keys.Safe(issuerKey))
|
||||
}
|
||||
|
||||
// SitePrefix returns a key prefix for items associated with
|
||||
// the site using the given CA URL.
|
||||
func (keys KeyBuilder) SitePrefix(ca, domain string) string {
|
||||
return path.Join(keys.CAPrefix(ca), "sites", keys.Safe(domain))
|
||||
// CertsSitePrefix returns a key prefix for items associated with
|
||||
// the site given by domain using the given issuer key.
|
||||
func (keys KeyBuilder) CertsSitePrefix(issuerKey, domain string) string {
|
||||
return path.Join(keys.CertsPrefix(issuerKey), keys.Safe(domain))
|
||||
}
|
||||
|
||||
// SiteCert returns the path to the certificate file for domain.
|
||||
func (keys KeyBuilder) SiteCert(ca, domain string) string {
|
||||
return path.Join(keys.SitePrefix(ca, domain), keys.Safe(domain)+".crt")
|
||||
// SiteCert returns the path to the certificate file for domain
|
||||
// that is associated with the issuer with the given issuerKey.
|
||||
func (keys KeyBuilder) SiteCert(issuerKey, domain string) string {
|
||||
safeDomain := keys.Safe(domain)
|
||||
return path.Join(keys.CertsSitePrefix(issuerKey, domain), safeDomain+".crt")
|
||||
}
|
||||
|
||||
// SitePrivateKey returns the path to domain's private key file.
|
||||
func (keys KeyBuilder) SitePrivateKey(ca, domain string) string {
|
||||
return path.Join(keys.SitePrefix(ca, domain), keys.Safe(domain)+".key")
|
||||
// SitePrivateKey returns the path to the private key file for domain
|
||||
// that is associated with the certificate from the given issuer with
|
||||
// the given issuerKey.
|
||||
func (keys KeyBuilder) SitePrivateKey(issuerKey, domain string) string {
|
||||
safeDomain := keys.Safe(domain)
|
||||
return path.Join(keys.CertsSitePrefix(issuerKey, domain), safeDomain+".key")
|
||||
}
|
||||
|
||||
// SiteMeta returns the path to the domain's asset metadata file.
|
||||
func (keys KeyBuilder) SiteMeta(ca, domain string) string {
|
||||
return path.Join(keys.SitePrefix(ca, domain), keys.Safe(domain)+".json")
|
||||
}
|
||||
|
||||
// UsersPrefix returns a key prefix for items related to
|
||||
// users associated with the given CA URL.
|
||||
func (keys KeyBuilder) UsersPrefix(ca string) string {
|
||||
return path.Join(keys.CAPrefix(ca), "users")
|
||||
}
|
||||
|
||||
// UserPrefix returns a key prefix for items related to
|
||||
// the user with the given email for the given CA URL.
|
||||
func (keys KeyBuilder) UserPrefix(ca, email string) string {
|
||||
if email == "" {
|
||||
email = emptyEmail
|
||||
}
|
||||
return path.Join(keys.UsersPrefix(ca), keys.Safe(email))
|
||||
}
|
||||
|
||||
// UserReg gets the path to the registration file for the user
|
||||
// with the given email address for the given CA URL.
|
||||
func (keys KeyBuilder) UserReg(ca, email string) string {
|
||||
return keys.safeUserKey(ca, email, "registration", ".json")
|
||||
}
|
||||
|
||||
// UserPrivateKey gets the path to the private key file for the
|
||||
// user with the given email address on the given CA URL.
|
||||
func (keys KeyBuilder) UserPrivateKey(ca, email string) string {
|
||||
return keys.safeUserKey(ca, email, "private", ".key")
|
||||
// SiteMeta returns the path to the metadata file for domain that
|
||||
// is associated with the certificate from the given issuer with
|
||||
// the given issuerKey.
|
||||
func (keys KeyBuilder) SiteMeta(issuerKey, domain string) string {
|
||||
safeDomain := keys.Safe(domain)
|
||||
return path.Join(keys.CertsSitePrefix(issuerKey, domain), safeDomain+".json")
|
||||
}
|
||||
|
||||
// OCSPStaple returns a key for the OCSP staple associated
|
||||
@ -194,35 +183,9 @@ func (keys KeyBuilder) OCSPStaple(cert *Certificate, pemBundle []byte) string {
|
||||
return path.Join(prefixOCSP, ocspFileName)
|
||||
}
|
||||
|
||||
// safeUserKey returns a key for the given email, with the default
|
||||
// filename, and the filename ending in the given extension.
|
||||
func (keys KeyBuilder) safeUserKey(ca, email, defaultFilename, extension string) string {
|
||||
if email == "" {
|
||||
email = emptyEmail
|
||||
}
|
||||
email = strings.ToLower(email)
|
||||
filename := keys.emailUsername(email)
|
||||
if filename == "" {
|
||||
filename = defaultFilename
|
||||
}
|
||||
filename = keys.Safe(filename)
|
||||
return path.Join(keys.UserPrefix(ca, email), filename+extension)
|
||||
}
|
||||
|
||||
// emailUsername returns the username portion of an email address (part before
|
||||
// '@') or the original input if it can't find the "@" symbol.
|
||||
func (keys KeyBuilder) emailUsername(email string) string {
|
||||
at := strings.Index(email, "@")
|
||||
if at == -1 {
|
||||
return email
|
||||
} else if at == 0 {
|
||||
return email[1:]
|
||||
}
|
||||
return email[:at]
|
||||
}
|
||||
|
||||
// Safe standardizes and sanitizes str for use as
|
||||
// a storage key. This method is idempotent.
|
||||
// a single component of a storage key. This method
|
||||
// is idempotent.
|
||||
func (keys KeyBuilder) Safe(str string) string {
|
||||
str = strings.ToLower(str)
|
||||
str = strings.TrimSpace(str)
|
||||
@ -232,6 +195,7 @@ func (keys KeyBuilder) Safe(str string) string {
|
||||
" ", "_",
|
||||
"+", "_plus_",
|
||||
"*", "wildcard_",
|
||||
":", "-",
|
||||
"..", "", // prevent directory traversal (regex allows single dots)
|
||||
)
|
||||
str = repl.Replace(str)
|
||||
@ -240,6 +204,50 @@ func (keys KeyBuilder) Safe(str string) string {
|
||||
return safeKeyRE.ReplaceAllLiteralString(str, "")
|
||||
}
|
||||
|
||||
// CleanUpOwnLocks immediately cleans up all
|
||||
// current locks obtained by this process. Since
|
||||
// this does not cancel the operations that
|
||||
// the locks are synchronizing, this should be
|
||||
// called only immediately before process exit.
|
||||
func CleanUpOwnLocks() {
|
||||
locksMu.Lock()
|
||||
defer locksMu.Unlock()
|
||||
for lockKey, storage := range locks {
|
||||
err := storage.Unlock(lockKey)
|
||||
if err == nil {
|
||||
delete(locks, lockKey)
|
||||
} else {
|
||||
log.Printf("[ERROR] Unable to clean up lock: %v (lock=%s storage=%s)",
|
||||
err, lockKey, storage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func obtainLock(storage Storage, lockKey string) error {
|
||||
err := storage.Lock(lockKey)
|
||||
if err == nil {
|
||||
locksMu.Lock()
|
||||
locks[lockKey] = storage
|
||||
locksMu.Unlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func releaseLock(storage Storage, lockKey string) error {
|
||||
err := storage.Unlock(lockKey)
|
||||
if err == nil {
|
||||
locksMu.Lock()
|
||||
delete(locks, lockKey)
|
||||
locksMu.Unlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// locks stores a reference to all the current
|
||||
// locks obtained by this process.
|
||||
var locks = make(map[string]Storage)
|
||||
var locksMu sync.Mutex
|
||||
|
||||
// StorageKeys provides methods for accessing
|
||||
// keys and key prefixes for items in a Storage.
|
||||
// Typically, you will not need to use this
|
||||
@ -249,8 +257,8 @@ func (keys KeyBuilder) Safe(str string) string {
|
||||
var StorageKeys KeyBuilder
|
||||
|
||||
const (
|
||||
prefixACME = "acme"
|
||||
prefixOCSP = "ocsp"
|
||||
prefixCerts = "certificates"
|
||||
prefixOCSP = "ocsp"
|
||||
)
|
||||
|
||||
// safeKeyRE matches any undesirable characters in storage keys.
|
167
vendor/github.com/mholt/certmagic/user.go → vendor/github.com/caddyserver/certmagic/user.go
generated
vendored
167
vendor/github.com/mholt/certmagic/user.go → vendor/github.com/caddyserver/certmagic/user.go
generated
vendored
@ -29,8 +29,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/registration"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
)
|
||||
|
||||
// user represents a Let's Encrypt user account.
|
||||
@ -60,8 +60,8 @@ func (u user) GetPrivateKey() crypto.PrivateKey {
|
||||
// user to disk or register it via ACME. If you want to use
|
||||
// a user account that might already exist, call getUser
|
||||
// instead. It does NOT prompt the user.
|
||||
func (cfg *Config) newUser(email string) (user, error) {
|
||||
user := user{Email: email}
|
||||
func (*ACMEManager) newUser(email string) (*user, error) {
|
||||
user := &user{Email: email}
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
if err != nil {
|
||||
return user, fmt.Errorf("generating private key: %v", err)
|
||||
@ -77,47 +77,47 @@ func (cfg *Config) newUser(email string) (user, error) {
|
||||
// the consequences of an empty email.) This function MAY prompt
|
||||
// the user for input. If allowPrompts is false, the user
|
||||
// will NOT be prompted and an empty email may be returned.
|
||||
func (cfg *Config) getEmail(allowPrompts bool) error {
|
||||
leEmail := cfg.Email
|
||||
func (am *ACMEManager) getEmail(allowPrompts bool) error {
|
||||
leEmail := am.Email
|
||||
|
||||
// First try package default email
|
||||
if leEmail == "" {
|
||||
leEmail = Default.Email
|
||||
leEmail = DefaultACME.Email // TODO: racey with line 108
|
||||
}
|
||||
|
||||
// Then try to get most recent user email from storage
|
||||
var gotRecentEmail bool
|
||||
if leEmail == "" {
|
||||
leEmail, gotRecentEmail = cfg.mostRecentUserEmail()
|
||||
leEmail, gotRecentEmail = am.mostRecentUserEmail(am.CA)
|
||||
}
|
||||
if !gotRecentEmail && leEmail == "" && allowPrompts {
|
||||
// Looks like there is no email address readily available,
|
||||
// so we will have to ask the user if we can.
|
||||
var err error
|
||||
leEmail, err = cfg.promptUserForEmail()
|
||||
leEmail, err = am.promptUserForEmail()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// User might have just signified their agreement
|
||||
cfg.Agreed = Default.Agreed
|
||||
am.Agreed = DefaultACME.Agreed
|
||||
}
|
||||
|
||||
// save the email for later and ensure it is consistent
|
||||
// for repeated use; then update cfg with the email
|
||||
Default.Email = strings.TrimSpace(strings.ToLower(leEmail))
|
||||
cfg.Email = Default.Email
|
||||
DefaultACME.Email = strings.TrimSpace(strings.ToLower(leEmail)) // TODO: this is racey with line 85
|
||||
am.Email = DefaultACME.Email
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) getAgreementURL() (string, error) {
|
||||
func (am *ACMEManager) getAgreementURL() (string, error) {
|
||||
if agreementTestURL != "" {
|
||||
return agreementTestURL, nil
|
||||
}
|
||||
caURL := Default.CA
|
||||
if cfg.CA != "" {
|
||||
caURL = cfg.CA
|
||||
caURL := am.CA
|
||||
if caURL == "" {
|
||||
caURL = DefaultACME.CA
|
||||
}
|
||||
response, err := http.Get(caURL)
|
||||
if err != nil {
|
||||
@ -137,14 +137,14 @@ func (cfg *Config) getAgreementURL() (string, error) {
|
||||
// be the empty string). If no error is returned, then Agreed
|
||||
// will also be set to true, since continuing through the
|
||||
// prompt signifies agreement.
|
||||
func (cfg *Config) promptUserForEmail() (string, error) {
|
||||
agreementURL, err := cfg.getAgreementURL()
|
||||
func (am *ACMEManager) promptUserForEmail() (string, error) {
|
||||
agreementURL, err := am.getAgreementURL()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get Agreement URL: %v", err)
|
||||
}
|
||||
// prompt the user for an email address and terms agreement
|
||||
reader := bufio.NewReader(stdin)
|
||||
cfg.promptUserAgreement(agreementURL)
|
||||
am.promptUserAgreement(agreementURL)
|
||||
fmt.Println("Please enter your email address to signify agreement and to be notified")
|
||||
fmt.Println("in case of issues. You can leave it blank, but we don't recommend it.")
|
||||
fmt.Print(" Email address: ")
|
||||
@ -153,7 +153,7 @@ func (cfg *Config) promptUserForEmail() (string, error) {
|
||||
return "", fmt.Errorf("reading email address: %v", err)
|
||||
}
|
||||
leEmail = strings.TrimSpace(leEmail)
|
||||
Default.Agreed = true
|
||||
DefaultACME.Agreed = true
|
||||
return leEmail, nil
|
||||
}
|
||||
|
||||
@ -162,32 +162,31 @@ func (cfg *Config) promptUserForEmail() (string, error) {
|
||||
// it will create a new one, but it does NOT save new
|
||||
// users to the disk or register them via ACME. It does
|
||||
// NOT prompt the user.
|
||||
func (cfg *Config) getUser(email string) (user, error) {
|
||||
var user user
|
||||
|
||||
regBytes, err := cfg.Storage.Load(StorageKeys.UserReg(cfg.CA, email))
|
||||
func (am *ACMEManager) getUser(ca, email string) (*user, error) {
|
||||
regBytes, err := am.config.Storage.Load(am.storageKeyUserReg(ca, email))
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); ok {
|
||||
// create a new user
|
||||
return cfg.newUser(email)
|
||||
return am.newUser(email)
|
||||
}
|
||||
return user, err
|
||||
return nil, err
|
||||
}
|
||||
keyBytes, err := cfg.Storage.Load(StorageKeys.UserPrivateKey(cfg.CA, email))
|
||||
keyBytes, err := am.config.Storage.Load(am.storageKeyUserPrivateKey(ca, email))
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); ok {
|
||||
// create a new user
|
||||
return cfg.newUser(email)
|
||||
return am.newUser(email)
|
||||
}
|
||||
return user, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(regBytes, &user)
|
||||
var u *user
|
||||
err = json.Unmarshal(regBytes, &u)
|
||||
if err != nil {
|
||||
return user, err
|
||||
return u, err
|
||||
}
|
||||
user.key, err = decodePrivateKey(keyBytes)
|
||||
return user, err
|
||||
u.key, err = decodePrivateKey(keyBytes)
|
||||
return u, err
|
||||
}
|
||||
|
||||
// saveUser persists a user's key and account registration
|
||||
@ -195,7 +194,7 @@ func (cfg *Config) getUser(email string) (user, error) {
|
||||
// or prompt the user. You must also pass in the storage
|
||||
// wherein the user should be saved. It should be the storage
|
||||
// for the CA with which user has an account.
|
||||
func (cfg *Config) saveUser(user user) error {
|
||||
func (am *ACMEManager) saveUser(ca string, user *user) error {
|
||||
regBytes, err := json.MarshalIndent(&user, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
@ -204,25 +203,23 @@ func (cfg *Config) saveUser(user user) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
all := []keyValue{
|
||||
{
|
||||
key: StorageKeys.UserReg(cfg.CA, user.Email),
|
||||
key: am.storageKeyUserReg(ca, user.Email),
|
||||
value: regBytes,
|
||||
},
|
||||
{
|
||||
key: StorageKeys.UserPrivateKey(cfg.CA, user.Email),
|
||||
key: am.storageKeyUserPrivateKey(ca, user.Email),
|
||||
value: keyBytes,
|
||||
},
|
||||
}
|
||||
|
||||
return storeTx(cfg.Storage, all)
|
||||
return storeTx(am.config.Storage, all)
|
||||
}
|
||||
|
||||
// promptUserAgreement simply outputs the standard user
|
||||
// agreement prompt with the given agreement URL.
|
||||
// It outputs a newline after the message.
|
||||
func (cfg *Config) promptUserAgreement(agreementURL string) {
|
||||
func (am *ACMEManager) promptUserAgreement(agreementURL string) {
|
||||
const userAgreementPrompt = `Your sites will be served over HTTPS automatically using Let's Encrypt.
|
||||
By continuing, you agree to the Let's Encrypt Subscriber Agreement at:`
|
||||
fmt.Printf("\n\n%s\n %s\n", userAgreementPrompt, agreementURL)
|
||||
@ -231,8 +228,8 @@ By continuing, you agree to the Let's Encrypt Subscriber Agreement at:`
|
||||
// askUserAgreement prompts the user to agree to the agreement
|
||||
// at the given agreement URL via stdin. It returns whether the
|
||||
// user agreed or not.
|
||||
func (cfg *Config) askUserAgreement(agreementURL string) bool {
|
||||
cfg.promptUserAgreement(agreementURL)
|
||||
func (am *ACMEManager) askUserAgreement(agreementURL string) bool {
|
||||
am.promptUserAgreement(agreementURL)
|
||||
fmt.Print("Do you agree to the terms? (y/n): ")
|
||||
|
||||
reader := bufio.NewReader(stdin)
|
||||
@ -245,24 +242,98 @@ func (cfg *Config) askUserAgreement(agreementURL string) bool {
|
||||
return answer == "y" || answer == "yes"
|
||||
}
|
||||
|
||||
func (am *ACMEManager) storageKeyCAPrefix(caURL string) string {
|
||||
return path.Join(prefixACME, StorageKeys.Safe(am.issuerKey(caURL)))
|
||||
}
|
||||
|
||||
func (am *ACMEManager) storageKeyUsersPrefix(caURL string) string {
|
||||
return path.Join(am.storageKeyCAPrefix(caURL), "users")
|
||||
}
|
||||
|
||||
func (am *ACMEManager) storageKeyUserPrefix(caURL, email string) string {
|
||||
if email == "" {
|
||||
email = emptyEmail
|
||||
}
|
||||
return path.Join(am.storageKeyUsersPrefix(caURL), StorageKeys.Safe(email))
|
||||
}
|
||||
|
||||
func (am *ACMEManager) storageKeyUserReg(caURL, email string) string {
|
||||
return am.storageSafeUserKey(caURL, email, "registration", ".json")
|
||||
}
|
||||
|
||||
func (am *ACMEManager) storageKeyUserPrivateKey(caURL, email string) string {
|
||||
return am.storageSafeUserKey(caURL, email, "private", ".key")
|
||||
}
|
||||
|
||||
// storageSafeUserKey returns a key for the given email, with the default
|
||||
// filename, and the filename ending in the given extension.
|
||||
func (am *ACMEManager) storageSafeUserKey(ca, email, defaultFilename, extension string) string {
|
||||
if email == "" {
|
||||
email = emptyEmail
|
||||
}
|
||||
email = strings.ToLower(email)
|
||||
filename := am.emailUsername(email)
|
||||
if filename == "" {
|
||||
filename = defaultFilename
|
||||
}
|
||||
filename = StorageKeys.Safe(filename)
|
||||
return path.Join(am.storageKeyUserPrefix(ca, email), filename+extension)
|
||||
}
|
||||
|
||||
// emailUsername returns the username portion of an email address (part before
|
||||
// '@') or the original input if it can't find the "@" symbol.
|
||||
func (*ACMEManager) emailUsername(email string) string {
|
||||
at := strings.Index(email, "@")
|
||||
if at == -1 {
|
||||
return email
|
||||
} else if at == 0 {
|
||||
return email[1:]
|
||||
}
|
||||
return email[:at]
|
||||
}
|
||||
|
||||
// mostRecentUserEmail finds the most recently-written user file
|
||||
// in s. Since this is part of a complex sequence to get a user
|
||||
// in storage. Since this is part of a complex sequence to get a user
|
||||
// account, errors here are discarded to simplify code flow in
|
||||
// the caller, and errors are not important here anyway.
|
||||
func (cfg *Config) mostRecentUserEmail() (string, bool) {
|
||||
userList, err := cfg.Storage.List(StorageKeys.UsersPrefix(cfg.CA), false)
|
||||
func (am *ACMEManager) mostRecentUserEmail(caURL string) (string, bool) {
|
||||
userList, err := am.config.Storage.List(am.storageKeyUsersPrefix(caURL), false)
|
||||
if err != nil || len(userList) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// get all the key infos ahead of sorting, because
|
||||
// we might filter some out
|
||||
stats := make(map[string]KeyInfo)
|
||||
for i, u := range userList {
|
||||
keyInfo, err := am.config.Storage.Stat(u)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if keyInfo.IsTerminal {
|
||||
// I found a bug when macOS created a .DS_Store file in
|
||||
// the users folder, and CertMagic tried to use that as
|
||||
// the user email because it was newer than the other one
|
||||
// which existed... sure, this isn't a perfect fix but
|
||||
// frankly one's OS shouldn't mess with the data folder
|
||||
// in the first place.
|
||||
userList = append(userList[:i], userList[i+1:]...)
|
||||
continue
|
||||
}
|
||||
stats[u] = keyInfo
|
||||
}
|
||||
|
||||
sort.Slice(userList, func(i, j int) bool {
|
||||
iInfo, _ := cfg.Storage.Stat(userList[i])
|
||||
jInfo, _ := cfg.Storage.Stat(userList[j])
|
||||
iInfo := stats[userList[i]]
|
||||
jInfo := stats[userList[j]]
|
||||
return jInfo.Modified.Before(iInfo.Modified)
|
||||
})
|
||||
user, err := cfg.getUser(path.Base(userList[0]))
|
||||
|
||||
user, err := am.getUser(caURL, path.Base(userList[0]))
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return user.Email, true
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.7
|
||||
- 1.12
|
||||
- 1.x
|
||||
- tip
|
||||
before_install:
|
@ -9,7 +9,10 @@ The retries exponentially increase and stop increasing when a certain threshold
|
||||
|
||||
## Usage
|
||||
|
||||
See https://godoc.org/github.com/cenkalti/backoff#pkg-examples
|
||||
Import path is `github.com/cenkalti/backoff/v4`. Please note the version part at the end.
|
||||
|
||||
godoc.org does not support modules yet,
|
||||
so you can use https://godoc.org/gopkg.in/cenkalti/backoff.v4 to view the documentation.
|
||||
|
||||
## Contributing
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
|
||||
// BackOffContext is a backoff policy that stops retrying after the context
|
||||
// is canceled.
|
||||
type BackOffContext interface {
|
||||
type BackOffContext interface { // nolint: golint
|
||||
BackOff
|
||||
Context() context.Context
|
||||
}
|
||||
@ -20,7 +20,7 @@ type backOffContext struct {
|
||||
// WithContext returns a BackOffContext with context ctx
|
||||
//
|
||||
// ctx must not be nil
|
||||
func WithContext(b BackOff, ctx context.Context) BackOffContext {
|
||||
func WithContext(b BackOff, ctx context.Context) BackOffContext { // nolint: golint
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
@ -38,11 +38,14 @@ func WithContext(b BackOff, ctx context.Context) BackOffContext {
|
||||
}
|
||||
}
|
||||
|
||||
func ensureContext(b BackOff) BackOffContext {
|
||||
func getContext(b BackOff) context.Context {
|
||||
if cb, ok := b.(BackOffContext); ok {
|
||||
return cb
|
||||
return cb.Context()
|
||||
}
|
||||
return WithContext(b, context.Background())
|
||||
if tb, ok := b.(*backOffTries); ok {
|
||||
return getContext(tb.delegate)
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (b *backOffContext) Context() context.Context {
|
||||
@ -56,7 +59,7 @@ func (b *backOffContext) NextBackOff() time.Duration {
|
||||
default:
|
||||
}
|
||||
next := b.BackOff.NextBackOff()
|
||||
if deadline, ok := b.ctx.Deadline(); ok && deadline.Sub(time.Now()) < next {
|
||||
if deadline, ok := b.ctx.Deadline(); ok && deadline.Sub(time.Now()) < next { // nolint: gosimple
|
||||
return Stop
|
||||
}
|
||||
return next
|
@ -56,9 +56,10 @@ type ExponentialBackOff struct {
|
||||
RandomizationFactor float64
|
||||
Multiplier float64
|
||||
MaxInterval time.Duration
|
||||
// After MaxElapsedTime the ExponentialBackOff stops.
|
||||
// After MaxElapsedTime the ExponentialBackOff returns Stop.
|
||||
// It never stops if MaxElapsedTime == 0.
|
||||
MaxElapsedTime time.Duration
|
||||
Stop time.Duration
|
||||
Clock Clock
|
||||
|
||||
currentInterval time.Duration
|
||||
@ -87,6 +88,7 @@ func NewExponentialBackOff() *ExponentialBackOff {
|
||||
Multiplier: DefaultMultiplier,
|
||||
MaxInterval: DefaultMaxInterval,
|
||||
MaxElapsedTime: DefaultMaxElapsedTime,
|
||||
Stop: Stop,
|
||||
Clock: SystemClock,
|
||||
}
|
||||
b.Reset()
|
||||
@ -103,20 +105,23 @@ func (t systemClock) Now() time.Time {
|
||||
var SystemClock = systemClock{}
|
||||
|
||||
// Reset the interval back to the initial retry interval and restarts the timer.
|
||||
// Reset must be called before using b.
|
||||
func (b *ExponentialBackOff) Reset() {
|
||||
b.currentInterval = b.InitialInterval
|
||||
b.startTime = b.Clock.Now()
|
||||
}
|
||||
|
||||
// NextBackOff calculates the next backoff interval using the formula:
|
||||
// Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval)
|
||||
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
|
||||
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
||||
// Make sure we have not gone over the maximum elapsed time.
|
||||
if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime {
|
||||
return Stop
|
||||
elapsed := b.GetElapsedTime()
|
||||
next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
||||
b.incrementCurrentInterval()
|
||||
if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime {
|
||||
return b.Stop
|
||||
}
|
||||
defer b.incrementCurrentInterval()
|
||||
return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
||||
return next
|
||||
}
|
||||
|
||||
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
|
||||
@ -140,7 +145,7 @@ func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||
}
|
||||
|
||||
// Returns a random value from the following interval:
|
||||
// [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
|
||||
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
|
||||
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||
var delta = randomizationFactor * float64(currentInterval)
|
||||
var minInterval = float64(currentInterval) - delta
|
3
vendor/github.com/cenkalti/backoff/v4/go.mod
generated
vendored
Normal file
3
vendor/github.com/cenkalti/backoff/v4/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/cenkalti/backoff/v4
|
||||
|
||||
go 1.12
|
@ -21,16 +21,31 @@ type Notify func(error, time.Duration)
|
||||
//
|
||||
// Retry sleeps the goroutine for the duration returned by BackOff after a
|
||||
// failed operation returns.
|
||||
func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) }
|
||||
func Retry(o Operation, b BackOff) error {
|
||||
return RetryNotify(o, b, nil)
|
||||
}
|
||||
|
||||
// RetryNotify calls notify function with the error and wait duration
|
||||
// for each failed attempt before sleep.
|
||||
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||
return RetryNotifyWithTimer(operation, b, notify, nil)
|
||||
}
|
||||
|
||||
// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
|
||||
// for each failed attempt before sleep.
|
||||
// A default timer that uses system timer is used when nil is passed.
|
||||
func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
|
||||
var err error
|
||||
var next time.Duration
|
||||
var t *time.Timer
|
||||
if t == nil {
|
||||
t = &defaultTimer{}
|
||||
}
|
||||
|
||||
cb := ensureContext(b)
|
||||
defer func() {
|
||||
t.Stop()
|
||||
}()
|
||||
|
||||
ctx := getContext(b)
|
||||
|
||||
b.Reset()
|
||||
for {
|
||||
@ -42,7 +57,7 @@ func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||
return permanent.Err
|
||||
}
|
||||
|
||||
if next = cb.NextBackOff(); next == Stop {
|
||||
if next = b.NextBackOff(); next == Stop {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -50,17 +65,12 @@ func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||
notify(err, next)
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
t = time.NewTimer(next)
|
||||
defer t.Stop()
|
||||
} else {
|
||||
t.Reset(next)
|
||||
}
|
||||
t.Start(next)
|
||||
|
||||
select {
|
||||
case <-cb.Context().Done():
|
||||
return err
|
||||
case <-t.C:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-t.C():
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -74,6 +84,10 @@ func (e *PermanentError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e *PermanentError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// Permanent wraps the given err in a *PermanentError.
|
||||
func Permanent(err error) *PermanentError {
|
||||
return &PermanentError{
|
@ -1,6 +1,7 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -12,7 +13,9 @@ import (
|
||||
type Ticker struct {
|
||||
C <-chan time.Time
|
||||
c chan time.Time
|
||||
b BackOffContext
|
||||
b BackOff
|
||||
ctx context.Context
|
||||
timer Timer
|
||||
stop chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
@ -24,12 +27,23 @@ type Ticker struct {
|
||||
// provided backoff policy (notably calling NextBackOff or Reset)
|
||||
// while the ticker is running.
|
||||
func NewTicker(b BackOff) *Ticker {
|
||||
return NewTickerWithTimer(b, &defaultTimer{})
|
||||
}
|
||||
|
||||
// NewTickerWithTimer returns a new Ticker with a custom timer.
|
||||
// A default timer that uses system timer is used when nil is passed.
|
||||
func NewTickerWithTimer(b BackOff, timer Timer) *Ticker {
|
||||
if timer == nil {
|
||||
timer = &defaultTimer{}
|
||||
}
|
||||
c := make(chan time.Time)
|
||||
t := &Ticker{
|
||||
C: c,
|
||||
c: c,
|
||||
b: ensureContext(b),
|
||||
stop: make(chan struct{}),
|
||||
C: c,
|
||||
c: c,
|
||||
b: b,
|
||||
ctx: getContext(b),
|
||||
timer: timer,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
t.b.Reset()
|
||||
go t.run()
|
||||
@ -59,7 +73,7 @@ func (t *Ticker) run() {
|
||||
case <-t.stop:
|
||||
t.c = nil // Prevent future ticks from being sent to the channel.
|
||||
return
|
||||
case <-t.b.Context().Done():
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -78,5 +92,6 @@ func (t *Ticker) send(tick time.Time) <-chan time.Time {
|
||||
return nil
|
||||
}
|
||||
|
||||
return time.After(next)
|
||||
t.timer.Start(next)
|
||||
return t.timer.C()
|
||||
}
|
35
vendor/github.com/cenkalti/backoff/v4/timer.go
generated
vendored
Normal file
35
vendor/github.com/cenkalti/backoff/v4/timer.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
type Timer interface {
|
||||
Start(duration time.Duration)
|
||||
Stop()
|
||||
C() <-chan time.Time
|
||||
}
|
||||
|
||||
// defaultTimer implements Timer interface using time.Timer
|
||||
type defaultTimer struct {
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// C returns the timers channel which receives the current time when the timer fires.
|
||||
func (t *defaultTimer) C() <-chan time.Time {
|
||||
return t.timer.C
|
||||
}
|
||||
|
||||
// Start starts the timer to fire after the given duration
|
||||
func (t *defaultTimer) Start(duration time.Duration) {
|
||||
if t.timer == nil {
|
||||
t.timer = time.NewTimer(duration)
|
||||
} else {
|
||||
t.timer.Reset(duration)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop is called when the timer is not used anymore and resources may be freed.
|
||||
func (t *defaultTimer) Stop() {
|
||||
if t.timer != nil {
|
||||
t.timer.Stop()
|
||||
}
|
||||
}
|
@ -20,6 +20,9 @@ type backOffTries struct {
|
||||
}
|
||||
|
||||
func (b *backOffTries) NextBackOff() time.Duration {
|
||||
if b.maxTries == 0 {
|
||||
return Stop
|
||||
}
|
||||
if b.maxTries > 0 {
|
||||
if b.maxTries <= b.numTries {
|
||||
return Stop
|
9
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
9
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
@ -1,5 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
Normal file
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
go.sum linguist-generated
|
20
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
20
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
@ -2,29 +2,35 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- tip
|
||||
- "stable"
|
||||
- "1.11.x"
|
||||
- "1.10.x"
|
||||
- "1.9.x"
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: "stable"
|
||||
env: GOLINT=true
|
||||
allow_failures:
|
||||
- go: tip
|
||||
fast_finish: true
|
||||
|
||||
before_script:
|
||||
- go get -u github.com/golang/lint/golint
|
||||
|
||||
before_install:
|
||||
- if [ ! -z "${GOLINT}" ]; then go get -u golang.org/x/lint/golint; fi
|
||||
|
||||
script:
|
||||
- go test -v --race ./...
|
||||
- go test --race ./...
|
||||
|
||||
after_script:
|
||||
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
|
||||
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||
- if [ ! -z "${GOLINT}" ]; then echo running golint; golint --set_exit_status ./...; else echo skipping golint; fi
|
||||
- go vet ./...
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
2
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
2
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
@ -1,5 +1,5 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2012 fsnotify Authors. All rights reserved.
|
||||
Copyright (c) 2012-2019 fsnotify Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
71
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
71
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
@ -10,16 +10,16 @@ go get -u golang.org/x/sys/...
|
||||
|
||||
Cross platform: Windows, Linux, BSD and macOS.
|
||||
|
||||
|Adapter |OS |Status |
|
||||
|----------|----------|----------|
|
||||
|inotify |Linux 2.6.27 or later, Android\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)|
|
||||
|kqueue |BSD, macOS, iOS\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)|
|
||||
|ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
|
||||
|FSEvents |macOS |[Planned](https://github.com/fsnotify/fsnotify/issues/11)|
|
||||
|FEN |Solaris 11 |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)|
|
||||
|fanotify |Linux 2.6.37+ | |
|
||||
|USN Journals |Windows |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)|
|
||||
|Polling |*All* |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)|
|
||||
| Adapter | OS | Status |
|
||||
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| inotify | Linux 2.6.27 or later, Android\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) |
|
||||
| kqueue | BSD, macOS, iOS\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) |
|
||||
| ReadDirectoryChangesW | Windows | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) |
|
||||
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
|
||||
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/issues/12) |
|
||||
| fanotify | Linux 2.6.37+ | [Planned](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
|
||||
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||
|
||||
\* Android and iOS are untested.
|
||||
|
||||
@ -33,6 +33,53 @@ All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based o
|
||||
|
||||
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
func main() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("event:", event)
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
log.Println("modified file:", event.Name)
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = watcher.Add("/tmp/foo")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
<-done
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
||||
@ -65,6 +112,10 @@ There are OS-specific limits as to how many watches can be created:
|
||||
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
|
||||
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
||||
|
||||
**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?**
|
||||
|
||||
fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications.
|
||||
|
||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
|
4
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
4
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
@ -63,4 +63,6 @@ func (e Event) String() string {
|
||||
}
|
||||
|
||||
// Common errors that can be reported by a watcher
|
||||
var ErrEventOverflow = errors.New("fsnotify queue overflow")
|
||||
var (
|
||||
ErrEventOverflow = errors.New("fsnotify queue overflow")
|
||||
)
|
||||
|
5
vendor/github.com/fsnotify/fsnotify/go.mod
generated
vendored
Normal file
5
vendor/github.com/fsnotify/fsnotify/go.mod
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
module github.com/fsnotify/fsnotify
|
||||
|
||||
go 1.13
|
||||
|
||||
require golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9
|
2
vendor/github.com/fsnotify/fsnotify/go.sum
generated
vendored
Normal file
2
vendor/github.com/fsnotify/fsnotify/go.sum
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
4
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
4
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
@ -40,12 +40,12 @@ func newFdPoller(fd int) (*fdPoller, error) {
|
||||
poller.fd = fd
|
||||
|
||||
// Create epoll fd
|
||||
poller.epfd, errno = unix.EpollCreate1(0)
|
||||
poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)
|
||||
if poller.epfd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
||||
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK)
|
||||
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
2
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
2
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
@ -8,4 +8,4 @@ package fsnotify
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const openMode = unix.O_NONBLOCK | unix.O_RDONLY
|
||||
const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC
|
||||
|
2
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
2
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
@ -9,4 +9,4 @@ package fsnotify
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// note: this constant is not defined on BSD
|
||||
const openMode = unix.O_EVTONLY
|
||||
const openMode = unix.O_EVTONLY | unix.O_CLOEXEC
|
||||
|
0
vendor/github.com/go-acme/lego/LICENSE → vendor/github.com/go-acme/lego/v3/LICENSE
generated
vendored
0
vendor/github.com/go-acme/lego/LICENSE → vendor/github.com/go-acme/lego/v3/LICENSE
generated
vendored
@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
)
|
||||
|
||||
type AccountService service
|
||||
@ -31,12 +31,12 @@ func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
|
||||
func (a *AccountService) NewEAB(accMsg acme.Account, kid string, hmacEncoded string) (acme.ExtendedAccount, error) {
|
||||
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
|
||||
if err != nil {
|
||||
return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %v", err)
|
||||
return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %w", err)
|
||||
}
|
||||
|
||||
eabJWS, err := a.core.signEABContent(a.core.GetDirectory().NewAccountURL, kid, hmac)
|
||||
if err != nil {
|
||||
return acme.ExtendedAccount{}, fmt.Errorf("acme: error signing eab content: %v", err)
|
||||
return acme.ExtendedAccount{}, fmt.Errorf("acme: error signing eab content: %w", err)
|
||||
}
|
||||
accMsg.ExternalAccountBinding = eabJWS
|
||||
|
||||
@ -57,6 +57,20 @@ func (a *AccountService) Get(accountURL string) (acme.Account, error) {
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// Update Updates an account.
|
||||
func (a *AccountService) Update(accountURL string, req acme.Account) (acme.ExtendedAccount, error) {
|
||||
if len(accountURL) == 0 {
|
||||
return acme.ExtendedAccount{}, errors.New("account[update]: empty URL")
|
||||
}
|
||||
|
||||
var account acme.ExtendedAccount
|
||||
_, err := a.core.post(accountURL, req, &account)
|
||||
if err != nil {
|
||||
return acme.ExtendedAccount{}, err
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// Deactivate Deactivates an account.
|
||||
func (a *AccountService) Deactivate(accountURL string) error {
|
||||
if len(accountURL) == 0 {
|
@ -10,12 +10,12 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api/internal/nonces"
|
||||
"github.com/go-acme/lego/acme/api/internal/secure"
|
||||
"github.com/go-acme/lego/acme/api/internal/sender"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api/internal/nonces"
|
||||
"github.com/go-acme/lego/v3/acme/api/internal/secure"
|
||||
"github.com/go-acme/lego/v3/acme/api/internal/sender"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// Core ACME/LE core API.
|
||||
@ -71,7 +71,7 @@ func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response,
|
||||
}
|
||||
|
||||
// postAsGet performs an HTTP POST ("POST-as-GET") request.
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.3
|
||||
// https://tools.ietf.org/html/rfc8555#section-6.3
|
||||
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
|
||||
return a.retrievablePost(uri, []byte{}, response)
|
||||
}
|
||||
@ -93,7 +93,6 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
|
||||
switch err.(type) {
|
||||
// Retry if the nonce was invalidated
|
||||
case *acme.NonceError:
|
||||
log.Infof("nonce error retry: %s", err)
|
||||
return err
|
||||
default:
|
||||
cancel()
|
||||
@ -104,7 +103,11 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backoff.WithContext(bo, ctx))
|
||||
notify := func(err error, duration time.Duration) {
|
||||
log.Infof("retry due to: %v", err)
|
||||
}
|
||||
|
||||
err := backoff.RetryNotify(operation, backoff.WithContext(bo, ctx), notify)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -115,7 +118,7 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
|
||||
func (a *Core) signedPost(uri string, content []byte, response interface{}) (*http.Response, error) {
|
||||
signedContent, err := a.jws.SignContent(uri, content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to post JWS message -> failed to sign content -> %v", err)
|
||||
return nil, fmt.Errorf("failed to post JWS message: failed to sign content: %w", err)
|
||||
}
|
||||
|
||||
signedBody := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
|
||||
@ -152,7 +155,7 @@ func (a *Core) GetDirectory() acme.Directory {
|
||||
func getDirectory(do *sender.Doer, caDirURL string) (acme.Directory, error) {
|
||||
var dir acme.Directory
|
||||
if _, err := do.Get(caDirURL, &dir); err != nil {
|
||||
return dir, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
||||
return dir, fmt.Errorf("get directory at '%s': %w", caDirURL, err)
|
||||
}
|
||||
|
||||
if dir.NewAccountURL == "" {
|
@ -3,7 +3,7 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
)
|
||||
|
||||
type AuthorizationService service
|
@ -7,9 +7,9 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// maxBodySize is the maximum size of body that we will read.
|
||||
@ -71,7 +71,7 @@ func (c *CertificateService) get(certURL string) ([]byte, string, error) {
|
||||
|
||||
// The issuer certificate link may be supplied via an "up" link
|
||||
// in the response headers of a new certificate.
|
||||
// See https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.4.2
|
||||
up := getLink(resp.Header, "up")
|
||||
|
||||
return cert, up, err
|
@ -3,7 +3,7 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
)
|
||||
|
||||
type ChallengeService service
|
@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/go-acme/lego/acme/api/internal/sender"
|
||||
"github.com/go-acme/lego/v3/acme/api/internal/sender"
|
||||
)
|
||||
|
||||
// Manager Manages nonces.
|
||||
@ -57,7 +57,7 @@ func (n *Manager) Nonce() (string, error) {
|
||||
func (n *Manager) getNonce() (string, error) {
|
||||
resp, err := n.do.Head(n.nonceURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %v", err)
|
||||
return "", fmt.Errorf("failed to get nonce from HTTP HEAD: %w", err)
|
||||
}
|
||||
|
||||
return GetFromResponse(resp)
|
||||
@ -71,7 +71,7 @@ func GetFromResponse(resp *http.Response) (string, error) {
|
||||
|
||||
nonce := resp.Header.Get("Replay-Nonce")
|
||||
if nonce == "" {
|
||||
return "", fmt.Errorf("server did not respond with a proper nonce header")
|
||||
return "", errors.New("server did not respond with a proper nonce header")
|
||||
}
|
||||
|
||||
return nonce, nil
|
@ -8,7 +8,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme/api/internal/nonces"
|
||||
"github.com/go-acme/lego/v3/acme/api/internal/nonces"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
@ -65,12 +65,12 @@ func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, e
|
||||
|
||||
signer, err := jose.NewSigner(signKey, &options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create jose signer -> %v", err)
|
||||
return nil, fmt.Errorf("failed to create jose signer: %w", err)
|
||||
}
|
||||
|
||||
signed, err := signer.Sign(content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign content -> %v", err)
|
||||
return nil, fmt.Errorf("failed to sign content: %w", err)
|
||||
}
|
||||
return signed, nil
|
||||
}
|
||||
@ -80,7 +80,7 @@ func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu
|
||||
jwk := jose.JSONWebKey{Key: j.privKey}
|
||||
jwkJSON, err := jwk.Public().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: error encoding eab jwk key: %v", err)
|
||||
return nil, fmt.Errorf("acme: error encoding eab jwk key: %w", err)
|
||||
}
|
||||
|
||||
signer, err := jose.NewSigner(
|
||||
@ -94,12 +94,12 @@ func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %v", err)
|
||||
return nil, fmt.Errorf("failed to create External Account Binding jose signer: %w", err)
|
||||
}
|
||||
|
||||
signed, err := signer.Sign(jwkJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to External Account Binding sign content -> %v", err)
|
||||
return nil, fmt.Errorf("failed to External Account Binding sign content: %w", err)
|
||||
}
|
||||
|
||||
return signed, nil
|
@ -9,7 +9,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
)
|
||||
|
||||
type RequestOption func(*http.Request) error
|
||||
@ -70,7 +70,7 @@ func (d *Doer) Post(url string, body io.Reader, bodyType string, response interf
|
||||
func (d *Doer) newRequest(method, uri string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, uri, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %v", err)
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", d.formatUserAgent())
|
||||
@ -78,7 +78,7 @@ func (d *Doer) newRequest(method, uri string, body io.Reader, opts ...RequestOpt
|
||||
for _, opt := range opts {
|
||||
err = opt(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %v", err)
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, erro
|
||||
|
||||
err = json.Unmarshal(raw, response)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("failed to unmarshal %q to type %T: %v", raw, response, err)
|
||||
return resp, fmt.Errorf("failed to unmarshal %q to type %T: %w", raw, response, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,16 +120,15 @@ func (d *Doer) formatUserAgent() string {
|
||||
|
||||
func checkError(req *http.Request, resp *http.Response) error {
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d :: %s :: %s :: %v", resp.StatusCode, req.Method, req.URL, err)
|
||||
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)
|
||||
}
|
||||
|
||||
var errorDetails *acme.ProblemDetails
|
||||
err = json.Unmarshal(body, &errorDetails)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d ::%s :: %s :: %v :: %s", resp.StatusCode, req.Method, req.URL, err, string(body))
|
||||
return fmt.Errorf("%d ::%s :: %s :: %w :: %s", resp.StatusCode, req.Method, req.URL, err, string(body))
|
||||
}
|
||||
|
||||
errorDetails.Method = req.Method
|
@ -5,7 +5,7 @@ package sender
|
||||
|
||||
const (
|
||||
// ourUserAgent is the User-Agent of this underlying library package.
|
||||
ourUserAgent = "xenolf-acme/2.6.0"
|
||||
ourUserAgent = "xenolf-acme/3.6.0"
|
||||
|
||||
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
||||
// values: detach|release
|
@ -4,7 +4,7 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
)
|
||||
|
||||
type OrderService service
|
@ -1,5 +1,5 @@
|
||||
// Package acme contains all objects related the ACME endpoints.
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16
|
||||
// https://tools.ietf.org/html/rfc8555
|
||||
package acme
|
||||
|
||||
import (
|
||||
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// Challenge statuses
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.6
|
||||
// https://tools.ietf.org/html/rfc8555#section-7.1.6
|
||||
const (
|
||||
StatusPending = "pending"
|
||||
StatusInvalid = "invalid"
|
||||
@ -20,7 +20,7 @@ const (
|
||||
)
|
||||
|
||||
// Directory the ACME directory object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
|
||||
type Directory struct {
|
||||
NewNonceURL string `json:"newNonce"`
|
||||
NewAccountURL string `json:"newAccount"`
|
||||
@ -32,7 +32,7 @@ type Directory struct {
|
||||
}
|
||||
|
||||
// Meta the ACME meta object (related to Directory).
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
|
||||
type Meta struct {
|
||||
// termsOfService (optional, string):
|
||||
// A URL identifying the current terms of service.
|
||||
@ -65,8 +65,8 @@ type ExtendedAccount struct {
|
||||
}
|
||||
|
||||
// Account the ACME account Object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.2
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.2
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.3
|
||||
type Account struct {
|
||||
// status (required, string):
|
||||
// The status of this account.
|
||||
@ -111,7 +111,7 @@ type ExtendedOrder struct {
|
||||
}
|
||||
|
||||
// Order the ACME order Object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.3
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.3
|
||||
type Order struct {
|
||||
// status (required, string):
|
||||
// The status of this order.
|
||||
@ -164,7 +164,7 @@ type Order struct {
|
||||
}
|
||||
|
||||
// Authorization the ACME authorization object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.4
|
||||
type Authorization struct {
|
||||
// status (required, string):
|
||||
// The status of this authorization.
|
||||
@ -206,8 +206,8 @@ type ExtendedChallenge struct {
|
||||
}
|
||||
|
||||
// Challenge the ACME challenge object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.5
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.5
|
||||
// - https://tools.ietf.org/html/rfc8555#section-8
|
||||
type Challenge struct {
|
||||
// type (required, string):
|
||||
// The type of challenge encoded in the object.
|
||||
@ -240,23 +240,23 @@ type Challenge struct {
|
||||
// It MUST NOT contain any characters outside the base64url alphabet,
|
||||
// and MUST NOT include base64 padding characters ("=").
|
||||
// See [RFC4086] for additional information on randomness requirements.
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
|
||||
// https://tools.ietf.org/html/rfc8555#section-8.3
|
||||
// https://tools.ietf.org/html/rfc8555#section-8.4
|
||||
Token string `json:"token"`
|
||||
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.1
|
||||
// https://tools.ietf.org/html/rfc8555#section-8.1
|
||||
KeyAuthorization string `json:"keyAuthorization"`
|
||||
}
|
||||
|
||||
// Identifier the ACME identifier object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-9.7.7
|
||||
// - https://tools.ietf.org/html/rfc8555#section-9.7.7
|
||||
type Identifier struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// CSRMessage Certificate Signing Request
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.4
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.4
|
||||
type CSRMessage struct {
|
||||
// csr (required, string):
|
||||
// A CSR encoding the parameters for the certificate being requested [RFC2986].
|
||||
@ -266,7 +266,7 @@ type CSRMessage struct {
|
||||
}
|
||||
|
||||
// RevokeCertMessage a certificate revocation message
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.6
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.6
|
||||
// - https://tools.ietf.org/html/rfc5280#section-5.3.1
|
||||
type RevokeCertMessage struct {
|
||||
// certificate (required, string):
|
@ -12,7 +12,7 @@ const (
|
||||
|
||||
// ProblemDetails the problem details object
|
||||
// - https://tools.ietf.org/html/rfc7807#section-3.1
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3.3
|
||||
// - https://tools.ietf.org/html/rfc8555#section-7.3.3
|
||||
type ProblemDetails struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
@ -26,7 +26,7 @@ type ProblemDetails struct {
|
||||
}
|
||||
|
||||
// SubProblem a "subproblems"
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.7.1
|
||||
// - https://tools.ietf.org/html/rfc8555#section-6.7.1
|
||||
type SubProblem struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
@ -3,6 +3,7 @@ package certcrypto
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
@ -13,6 +14,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
@ -77,17 +79,35 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||
return certificates, nil
|
||||
}
|
||||
|
||||
// ParsePEMPrivateKey parses a private key from key, which is a PEM block.
|
||||
// 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)
|
||||
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||
keyBlock, _ := pem.Decode(key)
|
||||
keyBlockDER, _ := pem.Decode(key)
|
||||
|
||||
switch keyBlock.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||
default:
|
||||
return nil, errors.New("unknown PEM header value")
|
||||
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, errors.New("failed to parse private key")
|
||||
}
|
||||
|
||||
func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
||||
@ -147,7 +167,7 @@ func PEMBlock(data interface{}) *pem.Block {
|
||||
func pemDecode(data []byte) (*pem.Block, error) {
|
||||
pemBlock, _ := pem.Decode(data)
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("PEM decode did not yield a valid block. Is the certificate in the right format?")
|
||||
return nil, errors.New("PEM decode did not yield a valid block. Is the certificate in the right format?")
|
||||
}
|
||||
|
||||
return pemBlock, nil
|
||||
@ -160,7 +180,7 @@ func PemDecodeTox509CSR(pem []byte) (*x509.CertificateRequest, error) {
|
||||
}
|
||||
|
||||
if pemBlock.Type != "CERTIFICATE REQUEST" {
|
||||
return nil, fmt.Errorf("PEM block is not a certificate request")
|
||||
return nil, errors.New("PEM block is not a certificate request")
|
||||
}
|
||||
|
||||
return x509.ParseCertificateRequest(pemBlock.Bytes)
|
||||
@ -179,7 +199,10 @@ func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
|
||||
}
|
||||
|
||||
func ExtractDomains(cert *x509.Certificate) []string {
|
||||
domains := []string{cert.Subject.CommonName}
|
||||
var domains []string
|
||||
if cert.Subject.CommonName != "" {
|
||||
domains = append(domains, cert.Subject.CommonName)
|
||||
}
|
||||
|
||||
// Check for SAN certificate
|
||||
for _, sanDomain := range cert.DNSNames {
|
||||
@ -193,7 +216,10 @@ func ExtractDomains(cert *x509.Certificate) []string {
|
||||
}
|
||||
|
||||
func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
||||
domains := []string{csr.Subject.CommonName}
|
||||
var domains []string
|
||||
if csr.Subject.CommonName != "" {
|
||||
domains = append(domains, csr.Subject.CommonName)
|
||||
}
|
||||
|
||||
// loop over the SubjectAltName DNS names
|
||||
for _, sanName := range csr.DNSNames {
|
@ -3,8 +3,8 @@ package certificate
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -61,9 +61,21 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
|
||||
}
|
||||
|
||||
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
|
||||
for _, auth := range order.Authorizations {
|
||||
if err := c.core.Authorizations.Deactivate(auth); err != nil {
|
||||
log.Infof("Unable to deactivated authorizations: %s", auth)
|
||||
for _, authzURL := range order.Authorizations {
|
||||
auth, err := c.core.Authorizations.Get(authzURL)
|
||||
if err != nil {
|
||||
log.Infof("Unable to get the authorization for: %s", authzURL)
|
||||
continue
|
||||
}
|
||||
|
||||
if auth.Status == acme.StatusValid {
|
||||
log.Infof("Skipping deactivating of valid auth: %s", authzURL)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Deactivating auth: %s", authzURL)
|
||||
if c.core.Authorizations.Deactivate(authzURL) != nil {
|
||||
log.Infof("Unable to deactivate the authorization: %s", authzURL)
|
||||
}
|
||||
}
|
||||
}
|
@ -12,12 +12,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/wait"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
"github.com/go-acme/lego/v3/platform/wait"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
@ -210,8 +210,8 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
|
||||
// Determine certificate name(s) based on the authorization resources
|
||||
commonName := domains[0]
|
||||
|
||||
// ACME draft Section 7.4 "Applying for Certificate Issuance"
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4
|
||||
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
|
||||
// https://tools.ietf.org/html/rfc8555#section-7.4
|
||||
// says:
|
||||
// Clients SHOULD NOT make any assumptions about the sort order of
|
||||
// "identifiers" or "authorizations" elements in the returned order
|
||||
@ -317,7 +317,7 @@ func (c *Certifier) Revoke(cert []byte) error {
|
||||
|
||||
x509Cert := certificates[0]
|
||||
if x509Cert.IsCA {
|
||||
return fmt.Errorf("certificate bundle starts with a CA certificate")
|
||||
return errors.New("certificate bundle starts with a CA certificate")
|
||||
}
|
||||
|
||||
revokeMsg := acme.RevokeCertMessage{
|
||||
@ -502,7 +502,7 @@ func checkOrderStatus(order acme.Order) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
|
||||
// https://tools.ietf.org/html/rfc8555#section-7.1.4
|
||||
// The domain name MUST be encoded
|
||||
// in the form in which it would appear in a certificate. That is, it
|
||||
// MUST be encoded according to the rules in Section 7 of [RFC5280].
|
@ -10,7 +10,7 @@ import (
|
||||
type obtainError map[string]error
|
||||
|
||||
func (e obtainError) Error() string {
|
||||
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
|
||||
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
|
||||
|
||||
var domains []string
|
||||
for domain := range e {
|
@ -3,22 +3,22 @@ package challenge
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
)
|
||||
|
||||
// Type is a string that identifies a particular challenge type and version of ACME challenge.
|
||||
type Type string
|
||||
|
||||
const (
|
||||
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
|
||||
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.3
|
||||
// Note: ChallengePath returns the URL path to fulfill this challenge
|
||||
HTTP01 = Type("http-01")
|
||||
|
||||
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
|
||||
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.4
|
||||
// Note: GetRecord returns a DNS record which will fulfill this challenge
|
||||
DNS01 = Type("dns-01")
|
||||
|
||||
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05
|
||||
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07
|
||||
TLSALPN01 = Type("tls-alpn-01")
|
||||
)
|
||||
|
@ -8,11 +8,11 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/wait"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
"github.com/go-acme/lego/v3/platform/wait"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@ -93,7 +93,7 @@ func (c *Challenge) PreSolve(authz acme.Authorization) error {
|
||||
|
||||
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %s", domain, err)
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err)
|
||||
}
|
||||
|
||||
return nil
|
@ -1,6 +1,7 @@
|
||||
package dns01
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
@ -16,8 +17,8 @@ const defaultResolvConf = "/etc/resolv.conf"
|
||||
var dnsTimeout = 10 * time.Second
|
||||
|
||||
var (
|
||||
fqdnToZone = map[string]string{}
|
||||
muFqdnToZone sync.Mutex
|
||||
fqdnSoaCache = map[string]*soaCacheEntry{}
|
||||
muFqdnSoaCache sync.Mutex
|
||||
)
|
||||
|
||||
var defaultNameservers = []string{
|
||||
@ -28,11 +29,31 @@ var defaultNameservers = []string{
|
||||
// recursiveNameservers are used to pre-check DNS propagation
|
||||
var recursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
|
||||
|
||||
// soaCacheEntry holds a cached SOA record (only selected fields)
|
||||
type soaCacheEntry struct {
|
||||
zone string // zone apex (a domain name)
|
||||
primaryNs string // primary nameserver for the zone apex
|
||||
expires time.Time // time when this cache entry should be evicted
|
||||
}
|
||||
|
||||
func newSoaCacheEntry(soa *dns.SOA) *soaCacheEntry {
|
||||
return &soaCacheEntry{
|
||||
zone: soa.Hdr.Name,
|
||||
primaryNs: soa.Ns,
|
||||
expires: time.Now().Add(time.Duration(soa.Refresh) * time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// isExpired checks whether a cache entry should be considered expired.
|
||||
func (cache *soaCacheEntry) isExpired() bool {
|
||||
return time.Now().After(cache.expires)
|
||||
}
|
||||
|
||||
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
||||
func ClearFqdnCache() {
|
||||
muFqdnToZone.Lock()
|
||||
fqdnToZone = map[string]string{}
|
||||
muFqdnToZone.Unlock()
|
||||
muFqdnSoaCache.Lock()
|
||||
fqdnSoaCache = map[string]*soaCacheEntry{}
|
||||
muFqdnSoaCache.Unlock()
|
||||
}
|
||||
|
||||
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
|
||||
@ -78,7 +99,7 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
||||
|
||||
zone, err := FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine the zone: %v", err)
|
||||
return nil, fmt.Errorf("could not determine the zone: %w", err)
|
||||
}
|
||||
|
||||
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
|
||||
@ -95,7 +116,23 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
||||
if len(authoritativeNss) > 0 {
|
||||
return authoritativeNss, nil
|
||||
}
|
||||
return nil, fmt.Errorf("could not determine authoritative nameservers")
|
||||
return nil, errors.New("could not determine authoritative nameservers")
|
||||
}
|
||||
|
||||
// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
|
||||
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
|
||||
func FindPrimaryNsByFqdn(fqdn string) (string, error) {
|
||||
return FindPrimaryNsByFqdnCustom(fqdn, recursiveNameservers)
|
||||
}
|
||||
|
||||
// FindPrimaryNsByFqdnCustom determines the primary nameserver of the zone apex for the given fqdn
|
||||
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
|
||||
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return soa.primaryNs, nil
|
||||
}
|
||||
|
||||
// FindZoneByFqdn determines the zone apex for the given fqdn
|
||||
@ -107,14 +144,32 @@ func FindZoneByFqdn(fqdn string) (string, error) {
|
||||
// FindZoneByFqdnCustom determines the zone apex for the given fqdn
|
||||
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
|
||||
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||
muFqdnToZone.Lock()
|
||||
defer muFqdnToZone.Unlock()
|
||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return soa.zone, nil
|
||||
}
|
||||
|
||||
// Do we have it cached?
|
||||
if zone, ok := fqdnToZone[fqdn]; ok {
|
||||
return zone, nil
|
||||
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
muFqdnSoaCache.Lock()
|
||||
defer muFqdnSoaCache.Unlock()
|
||||
|
||||
// Do we have it cached and is it still fresh?
|
||||
if ent := fqdnSoaCache[fqdn]; ent != nil && !ent.isExpired() {
|
||||
return ent, nil
|
||||
}
|
||||
|
||||
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fqdnSoaCache[fqdn] = ent
|
||||
return ent, nil
|
||||
}
|
||||
|
||||
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
var err error
|
||||
var in *dns.Msg
|
||||
|
||||
@ -134,7 +189,6 @@ func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||
switch in.Rcode {
|
||||
case dns.RcodeSuccess:
|
||||
// Check if we got a SOA RR in the answer section
|
||||
|
||||
if len(in.Answer) == 0 {
|
||||
continue
|
||||
}
|
||||
@ -147,20 +201,18 @@ func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||
|
||||
for _, ans := range in.Answer {
|
||||
if soa, ok := ans.(*dns.SOA); ok {
|
||||
zone := soa.Hdr.Name
|
||||
fqdnToZone[fqdn] = zone
|
||||
return zone, nil
|
||||
return newSoaCacheEntry(soa), nil
|
||||
}
|
||||
}
|
||||
case dns.RcodeNameError:
|
||||
// NXDOMAIN
|
||||
default:
|
||||
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
||||
return "", fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
|
||||
return nil, fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
|
||||
return nil, fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
|
||||
}
|
||||
|
||||
// dnsMsgContainsCNAME checks for a CNAME answer in msg
|
184
vendor/github.com/go-acme/lego/v3/challenge/http01/domain_matcher.go
generated
vendored
Normal file
184
vendor/github.com/go-acme/lego/v3/challenge/http01/domain_matcher.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
package http01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A domainMatcher tries to match a domain (the one we're requesting a certificate for)
|
||||
// in the HTTP request coming from the ACME validation servers.
|
||||
// This step is part of DNS rebind attack prevention,
|
||||
// where the webserver matches incoming requests to a list of domain the server acts authoritative for.
|
||||
//
|
||||
// The most simple check involves finding the domain in the HTTP Host header;
|
||||
// this is what hostMatcher does.
|
||||
// Use it, when the http01.ProviderServer is directly reachable from the internet,
|
||||
// or when it operates behind a transparent proxy.
|
||||
//
|
||||
// In many (reverse) proxy setups, Apache and NGINX traditionally move the Host header to a new header named X-Forwarded-Host.
|
||||
// Use arbitraryMatcher("X-Forwarded-Host") in this case,
|
||||
// or the appropriate header name for other proxy servers.
|
||||
//
|
||||
// RFC7239 has standardized the different forwarding headers into a single header named Forwarded.
|
||||
// The header value has a different format, so you should use forwardedMatcher
|
||||
// when the http01.ProviderServer operates behind a RFC7239 compatible proxy.
|
||||
// https://tools.ietf.org/html/rfc7239
|
||||
//
|
||||
// Note: RFC7239 also reminds us, "that an HTTP list [...] may be split over multiple header fields" (section 7.1),
|
||||
// meaning that
|
||||
// X-Header: a
|
||||
// X-Header: b
|
||||
// is equal to
|
||||
// X-Header: a, b
|
||||
//
|
||||
// All matcher implementations (explicitly not excluding arbitraryMatcher!)
|
||||
// have in common that they only match against the first value in such lists.
|
||||
type domainMatcher interface {
|
||||
// matches checks whether the request is valid for the given domain.
|
||||
matches(request *http.Request, domain string) bool
|
||||
|
||||
// name returns the header name used in the check.
|
||||
// This is primarily used to create meaningful error messages.
|
||||
name() string
|
||||
}
|
||||
|
||||
// hostMatcher checks whether (*net/http).Request.Host starts with a domain name.
|
||||
type hostMatcher struct{}
|
||||
|
||||
func (m *hostMatcher) name() string {
|
||||
return "Host"
|
||||
}
|
||||
|
||||
func (m *hostMatcher) matches(r *http.Request, domain string) bool {
|
||||
return strings.HasPrefix(r.Host, domain)
|
||||
}
|
||||
|
||||
// hostMatcher checks whether the specified (*net/http.Request).Header value starts with a domain name.
|
||||
type arbitraryMatcher string
|
||||
|
||||
func (m arbitraryMatcher) name() string {
|
||||
return string(m)
|
||||
}
|
||||
|
||||
func (m arbitraryMatcher) matches(r *http.Request, domain string) bool {
|
||||
return strings.HasPrefix(r.Header.Get(m.name()), domain)
|
||||
}
|
||||
|
||||
// forwardedMatcher checks whether the Forwarded header contains a "host" element starting with a domain name.
|
||||
// See https://tools.ietf.org/html/rfc7239 for details.
|
||||
type forwardedMatcher struct{}
|
||||
|
||||
func (m *forwardedMatcher) name() string {
|
||||
return "Forwarded"
|
||||
}
|
||||
|
||||
func (m *forwardedMatcher) matches(r *http.Request, domain string) bool {
|
||||
fwds, err := parseForwardedHeader(r.Header.Get(m.name()))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(fwds) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
host := fwds[0]["host"]
|
||||
return strings.HasPrefix(host, domain)
|
||||
}
|
||||
|
||||
// parsing requires some form of state machine
|
||||
func parseForwardedHeader(s string) (elements []map[string]string, err error) {
|
||||
cur := make(map[string]string)
|
||||
key := ""
|
||||
val := ""
|
||||
inquote := false
|
||||
|
||||
pos := 0
|
||||
l := len(s)
|
||||
for i := 0; i < l; i++ {
|
||||
r := rune(s[i])
|
||||
|
||||
if inquote {
|
||||
if r == '"' {
|
||||
cur[key] = s[pos:i]
|
||||
key = ""
|
||||
pos = i
|
||||
inquote = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case r == '"': // start of quoted-string
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("unexpected quoted string as pos %d", i)
|
||||
}
|
||||
inquote = true
|
||||
pos = i + 1
|
||||
|
||||
case r == ';': // end of forwarded-pair
|
||||
cur[key] = s[pos:i]
|
||||
key = ""
|
||||
i = skipWS(s, i)
|
||||
pos = i + 1
|
||||
|
||||
case r == '=': // end of token
|
||||
key = strings.ToLower(strings.TrimFunc(s[pos:i], isWS))
|
||||
i = skipWS(s, i)
|
||||
pos = i + 1
|
||||
|
||||
case r == ',': // end of forwarded-element
|
||||
if key != "" {
|
||||
if val == "" {
|
||||
val = s[pos:i]
|
||||
}
|
||||
cur[key] = val
|
||||
}
|
||||
elements = append(elements, cur)
|
||||
cur = make(map[string]string)
|
||||
key = ""
|
||||
val = ""
|
||||
|
||||
i = skipWS(s, i)
|
||||
pos = i + 1
|
||||
case tchar(r) || isWS(r): // valid token character or whitespace
|
||||
continue
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid token character at pos %d: %c", i, r)
|
||||
}
|
||||
}
|
||||
|
||||
if inquote {
|
||||
return nil, fmt.Errorf("unterminated quoted-string at pos %d", len(s))
|
||||
}
|
||||
|
||||
if key != "" {
|
||||
if pos < len(s) {
|
||||
val = s[pos:]
|
||||
}
|
||||
cur[key] = val
|
||||
}
|
||||
if len(cur) > 0 {
|
||||
elements = append(elements, cur)
|
||||
}
|
||||
return elements, nil
|
||||
}
|
||||
|
||||
func tchar(r rune) bool {
|
||||
return strings.ContainsRune("!#$%&'*+-.^_`|~", r) ||
|
||||
'0' <= r && r <= '9' ||
|
||||
'a' <= r && r <= 'z' ||
|
||||
'A' <= r && r <= 'Z'
|
||||
}
|
||||
|
||||
func skipWS(s string, i int) int {
|
||||
for isWS(rune(s[i+1])) {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func isWS(r rune) bool {
|
||||
return strings.ContainsRune(" \t\v\r\n", r)
|
||||
}
|
@ -3,10 +3,10 @@ package http01
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||
@ -51,12 +51,12 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||
|
||||
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %v", domain, err)
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err)
|
||||
}
|
||||
defer func() {
|
||||
err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] acme: error cleaning up: %v", domain, err)
|
||||
log.Warnf("[%s] acme: cleaning up failed: %v", domain, err)
|
||||
}
|
||||
}()
|
||||
|
@ -4,9 +4,10 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// ProviderServer implements ChallengeProvider for `http-01` challenge
|
||||
@ -15,6 +16,7 @@ import (
|
||||
type ProviderServer struct {
|
||||
iface string
|
||||
port string
|
||||
matcher domainMatcher
|
||||
done chan bool
|
||||
listener net.Listener
|
||||
}
|
||||
@ -23,19 +25,19 @@ type ProviderServer struct {
|
||||
// Setting iface and / or port to an empty string will make the server fall back to
|
||||
// the "any" interface and port 80 respectively.
|
||||
func NewProviderServer(iface, port string) *ProviderServer {
|
||||
return &ProviderServer{iface: iface, port: port}
|
||||
if port == "" {
|
||||
port = "80"
|
||||
}
|
||||
|
||||
return &ProviderServer{iface: iface, port: port, matcher: &hostMatcher{}}
|
||||
}
|
||||
|
||||
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
|
||||
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||
if s.port == "" {
|
||||
s.port = "80"
|
||||
}
|
||||
|
||||
var err error
|
||||
s.listener, err = net.Listen("tcp", s.GetAddress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start HTTP server for challenge -> %v", err)
|
||||
return fmt.Errorf("could not start HTTP server for challenge: %w", err)
|
||||
}
|
||||
|
||||
s.done = make(chan bool)
|
||||
@ -57,14 +59,38 @@ func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetProxyHeader changes the validation of incoming requests.
|
||||
// By default, s matches the "Host" header value to the domain name.
|
||||
//
|
||||
// When the server runs behind a proxy server, this is not the correct place to look at;
|
||||
// Apache and NGINX have traditionally moved the original Host header into a new header named "X-Forwarded-Host".
|
||||
// Other webservers might use different names;
|
||||
// and RFC7239 has standadized a new header named "Forwarded" (with slightly different semantics).
|
||||
//
|
||||
// The exact behavior depends on the value of headerName:
|
||||
// - "" (the empty string) and "Host" will restore the default and only check the Host header
|
||||
// - "Forwarded" will look for a Forwarded header, and inspect it according to https://tools.ietf.org/html/rfc7239
|
||||
// - any other value will check the header value with the same name
|
||||
func (s *ProviderServer) SetProxyHeader(headerName string) {
|
||||
switch h := textproto.CanonicalMIMEHeaderKey(headerName); h {
|
||||
case "", "Host":
|
||||
s.matcher = &hostMatcher{}
|
||||
case "Forwarded":
|
||||
s.matcher = &forwardedMatcher{}
|
||||
default:
|
||||
s.matcher = arbitraryMatcher(h)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
||||
path := ChallengePath(token)
|
||||
|
||||
// The handler validates the HOST header and request type.
|
||||
// For validation it then writes the token the server returned with the challenge
|
||||
// The incoming request must will be validated to prevent DNS rebind attacks.
|
||||
// We only respond with the keyAuth, when we're receiving a GET requests with
|
||||
// the "Host" header matching the domain (the latter is configurable though SetProxyHeader).
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.Host, domain) && r.Method == http.MethodGet {
|
||||
if r.Method == http.MethodGet && s.matcher.matches(r, domain) {
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
_, err := w.Write([]byte(keyAuth))
|
||||
if err != nil {
|
||||
@ -73,7 +99,7 @@ func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
||||
}
|
||||
log.Infof("[%s] Served key authentication", domain)
|
||||
} else {
|
||||
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the HOST header properly.", r.Host, r.Method)
|
||||
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the %s header properly.", r.Host, r.Method, s.matcher.name())
|
||||
_, err := w.Write([]byte("TEST"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
@ -10,7 +10,7 @@ import (
|
||||
type obtainError map[string]error
|
||||
|
||||
func (e obtainError) Error() string {
|
||||
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
|
||||
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
|
||||
|
||||
var domains []string
|
||||
for domain := range e {
|
@ -4,9 +4,9 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// Interface for all challenge solvers to implement.
|
||||
@ -167,7 +167,7 @@ func cleanUp(solvr solver, authz acme.Authorization) {
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
err := solvr.CleanUp(authz)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] acme: error cleaning up: %v ", domain, err)
|
||||
log.Warnf("[%s] acme: cleaning up failed: %v ", domain, err)
|
||||
}
|
||||
}
|
||||
}
|
@ -8,14 +8,14 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/challenge/http01"
|
||||
"github.com/go-acme/lego/challenge/tlsalpn01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/dns01"
|
||||
"github.com/go-acme/lego/v3/challenge/http01"
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
type byType []acme.Challenge
|
||||
@ -79,7 +79,7 @@ func (c *SolverManager) chooseSolver(authz acme.Authorization) solver {
|
||||
func validate(core *api.Core, domain string, chlg acme.Challenge) error {
|
||||
chlng, err := core.Challenges.New(chlg.URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initiate challenge: %v", err)
|
||||
return fmt.Errorf("failed to initiate challenge: %w", err)
|
||||
}
|
||||
|
||||
valid, err := checkChallengeStatus(chlng)
|
@ -8,15 +8,15 @@ import (
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
|
||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.1
|
||||
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||
|
||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||
@ -57,12 +57,12 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||
|
||||
err = c.provider.Present(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %v", challenge.GetTargetedDomain(authz), err)
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %w", challenge.GetTargetedDomain(authz), err)
|
||||
}
|
||||
defer func() {
|
||||
err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] acme: error cleaning up: %v", challenge.GetTargetedDomain(authz), err)
|
||||
log.Warnf("[%s] acme: cleaning up failed: %v", challenge.GetTargetedDomain(authz), err)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -83,7 +83,7 @@ func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
|
||||
|
||||
// Add the keyAuth digest as the acmeValidation-v1 extension
|
||||
// (marked as critical such that it won't be used by non-ACME software).
|
||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3
|
||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-3
|
||||
extensions := []pkix.Extension{
|
||||
{
|
||||
Id: idPeAcmeIdentifierV1,
|
@ -7,7 +7,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -60,13 +60,13 @@ func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||
|
||||
// We must set that the `acme-tls/1` application level protocol is supported
|
||||
// so that the protocol negotiation can succeed. Reference:
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-5.2
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.2
|
||||
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
||||
|
||||
// Create the listener with the created tls.Config.
|
||||
s.listener, err = tls.Listen("tcp", s.GetAddress(), tlsConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start HTTPS server for challenge -> %v", err)
|
||||
return fmt.Errorf("could not start HTTPS server for challenge: %w", err)
|
||||
}
|
||||
|
||||
// Shut the server down when we're finished.
|
@ -4,10 +4,10 @@ import (
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/certificate"
|
||||
"github.com/go-acme/lego/challenge/resolver"
|
||||
"github.com/go-acme/lego/registration"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/certificate"
|
||||
"github.com/go-acme/lego/v3/challenge/resolver"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
)
|
||||
|
||||
// Client is the user-friendly way to ACME
|
@ -10,8 +10,8 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
"github.com/go-acme/lego/registration"
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
)
|
||||
|
||||
const (
|
@ -4,19 +4,19 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// For polls the given function 'f', once every 'interval', up to 'timeout'.
|
||||
func For(msg string, timeout, interval time.Duration, f func() (bool, error)) error {
|
||||
log.Infof("Wait for %s [timeout: %s, interval: %s]", msg, timeout, interval)
|
||||
|
||||
var lastErr string
|
||||
var lastErr error
|
||||
timeUp := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-timeUp:
|
||||
return fmt.Errorf("time limit exceeded: last error: %s", lastErr)
|
||||
return fmt.Errorf("time limit exceeded: last error: %w", lastErr)
|
||||
default:
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
lastErr = err.Error()
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
time.Sleep(interval)
|
@ -4,14 +4,14 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// Resource represents all important information about a registration
|
||||
// of which the client needs to keep track itself.
|
||||
// Deprecated: will be remove in the future (acme.ExtendedAccount).
|
||||
// WARNING: will be remove in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
|
||||
type Resource struct {
|
||||
Body acme.Account `json:"body,omitempty"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
@ -115,6 +115,30 @@ func (r *Registrar) QueryRegistration() (*Resource, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateRegistration update the user registration on the ACME server.
|
||||
func (r *Registrar) UpdateRegistration(options RegisterOptions) (*Resource, error) {
|
||||
if r == nil || r.user == nil {
|
||||
return nil, errors.New("acme: cannot update a nil client or user")
|
||||
}
|
||||
|
||||
accMsg := acme.Account{
|
||||
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
|
||||
Contact: []string{},
|
||||
}
|
||||
|
||||
if r.user.GetEmail() != "" {
|
||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
||||
}
|
||||
|
||||
account, err := r.core.Accounts.Update(r.user.GetRegistration().URI, accMsg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Resource{URI: account.Location, Body: account.Account}, nil
|
||||
}
|
||||
|
||||
// DeleteRegistration deletes the client's user registration from the ACME server.
|
||||
func (r *Registrar) DeleteRegistration() error {
|
||||
if r == nil || r.user == nil {
|
19
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
19
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
@ -1,19 +0,0 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.7.x
|
||||
- go: 1.8.x
|
||||
- go: 1.9.x
|
||||
- go: 1.10.x
|
||||
- go: 1.11.x
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go vet $(go list ./... | grep -v /vendor/)
|
||||
- go test -v -race ./...
|
12
vendor/github.com/gorilla/websocket/README.md
generated
vendored
12
vendor/github.com/gorilla/websocket/README.md
generated
vendored
@ -1,14 +1,14 @@
|
||||
# Gorilla WebSocket
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
|
||||
[![CircleCI](https://circleci.com/gh/gorilla/websocket.svg?style=svg)](https://circleci.com/gh/gorilla/websocket)
|
||||
|
||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||
|
||||
[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
|
||||
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
|
||||
|
||||
### Documentation
|
||||
|
||||
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
|
||||
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
|
||||
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
|
||||
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
|
||||
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
|
||||
@ -27,7 +27,7 @@ package API is stable.
|
||||
### Protocol Compliance
|
||||
|
||||
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
||||
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
|
||||
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
|
||||
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
|
||||
|
||||
### Gorilla WebSocket compared with other packages
|
||||
@ -40,7 +40,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
|
||||
</tr>
|
||||
<tr>
|
||||
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
|
||||
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Passes <a href="https://github.com/crossbario/autobahn-testsuite">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
|
||||
|
4
vendor/github.com/gorilla/websocket/client.go
generated
vendored
4
vendor/github.com/gorilla/websocket/client.go
generated
vendored
@ -70,7 +70,7 @@ type Dialer struct {
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
|
||||
// size is zero, then a useful default size is used. The I/O buffer sizes
|
||||
// do not limit the size of the messages that can be sent or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
@ -140,7 +140,7 @@ var nilDialer = *DefaultDialer
|
||||
// Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// The context will be used in the request and in the Dialer
|
||||
// The context will be used in the request and in the Dialer.
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
|
126
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
126
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
@ -244,8 +244,8 @@ type Conn struct {
|
||||
subprotocol string
|
||||
|
||||
// Write fields
|
||||
mu chan bool // used as mutex to protect write to conn
|
||||
writeBuf []byte // frame is constructed in this buffer.
|
||||
mu chan struct{} // used as mutex to protect write to conn
|
||||
writeBuf []byte // frame is constructed in this buffer.
|
||||
writePool BufferPool
|
||||
writeBufSize int
|
||||
writeDeadline time.Time
|
||||
@ -260,10 +260,12 @@ type Conn struct {
|
||||
newCompressionWriter func(io.WriteCloser, int) io.WriteCloser
|
||||
|
||||
// Read fields
|
||||
reader io.ReadCloser // the current reader returned to the application
|
||||
readErr error
|
||||
br *bufio.Reader
|
||||
readRemaining int64 // bytes remaining in current frame.
|
||||
reader io.ReadCloser // the current reader returned to the application
|
||||
readErr error
|
||||
br *bufio.Reader
|
||||
// bytes remaining in current frame.
|
||||
// set setReadRemaining to safely update this value and prevent overflow
|
||||
readRemaining int64
|
||||
readFinal bool // true the current message has more frames.
|
||||
readLength int64 // Message size.
|
||||
readLimit int64 // Maximum message size.
|
||||
@ -300,8 +302,8 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int,
|
||||
writeBuf = make([]byte, writeBufferSize)
|
||||
}
|
||||
|
||||
mu := make(chan bool, 1)
|
||||
mu <- true
|
||||
mu := make(chan struct{}, 1)
|
||||
mu <- struct{}{}
|
||||
c := &Conn{
|
||||
isServer: isServer,
|
||||
br: br,
|
||||
@ -320,6 +322,17 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int,
|
||||
return c
|
||||
}
|
||||
|
||||
// setReadRemaining tracks the number of bytes remaining on the connection. If n
|
||||
// overflows, an ErrReadLimit is returned.
|
||||
func (c *Conn) setReadRemaining(n int64) error {
|
||||
if n < 0 {
|
||||
return ErrReadLimit
|
||||
}
|
||||
|
||||
c.readRemaining = n
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subprotocol returns the negotiated protocol for the connection.
|
||||
func (c *Conn) Subprotocol() string {
|
||||
return c.subprotocol
|
||||
@ -364,7 +377,7 @@ func (c *Conn) read(n int) ([]byte, error) {
|
||||
|
||||
func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error {
|
||||
<-c.mu
|
||||
defer func() { c.mu <- true }()
|
||||
defer func() { c.mu <- struct{}{} }()
|
||||
|
||||
c.writeErrMu.Lock()
|
||||
err := c.writeErr
|
||||
@ -416,7 +429,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
||||
maskBytes(key, 0, buf[6:])
|
||||
}
|
||||
|
||||
d := time.Hour * 1000
|
||||
d := 1000 * time.Hour
|
||||
if !deadline.IsZero() {
|
||||
d = deadline.Sub(time.Now())
|
||||
if d < 0 {
|
||||
@ -431,7 +444,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
||||
case <-timer.C:
|
||||
return errWriteTimeout
|
||||
}
|
||||
defer func() { c.mu <- true }()
|
||||
defer func() { c.mu <- struct{}{} }()
|
||||
|
||||
c.writeErrMu.Lock()
|
||||
err := c.writeErr
|
||||
@ -451,7 +464,8 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) prepWrite(messageType int) error {
|
||||
// beginMessage prepares a connection and message writer for a new message.
|
||||
func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
|
||||
// Close previous writer if not already closed by the application. It's
|
||||
// probably better to return an error in this situation, but we cannot
|
||||
// change this without breaking existing applications.
|
||||
@ -471,6 +485,10 @@ func (c *Conn) prepWrite(messageType int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
mw.c = c
|
||||
mw.frameType = messageType
|
||||
mw.pos = maxFrameHeaderSize
|
||||
|
||||
if c.writeBuf == nil {
|
||||
wpd, ok := c.writePool.Get().(writePoolData)
|
||||
if ok {
|
||||
@ -491,16 +509,11 @@ func (c *Conn) prepWrite(messageType int) error {
|
||||
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
|
||||
// PongMessage) are supported.
|
||||
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
||||
if err := c.prepWrite(messageType); err != nil {
|
||||
var mw messageWriter
|
||||
if err := c.beginMessage(&mw, messageType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mw := &messageWriter{
|
||||
c: c,
|
||||
frameType: messageType,
|
||||
pos: maxFrameHeaderSize,
|
||||
}
|
||||
c.writer = mw
|
||||
c.writer = &mw
|
||||
if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
|
||||
w := c.newCompressionWriter(c.writer, c.compressionLevel)
|
||||
mw.compress = true
|
||||
@ -517,10 +530,16 @@ type messageWriter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *messageWriter) fatal(err error) error {
|
||||
func (w *messageWriter) endMessage(err error) error {
|
||||
if w.err != nil {
|
||||
w.err = err
|
||||
w.c.writer = nil
|
||||
return err
|
||||
}
|
||||
c := w.c
|
||||
w.err = err
|
||||
c.writer = nil
|
||||
if c.writePool != nil {
|
||||
c.writePool.Put(writePoolData{buf: c.writeBuf})
|
||||
c.writeBuf = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -534,7 +553,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
// Check for invalid control frames.
|
||||
if isControl(w.frameType) &&
|
||||
(!final || length > maxControlFramePayloadSize) {
|
||||
return w.fatal(errInvalidControlFrame)
|
||||
return w.endMessage(errInvalidControlFrame)
|
||||
}
|
||||
|
||||
b0 := byte(w.frameType)
|
||||
@ -579,7 +598,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
|
||||
maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
|
||||
if len(extra) > 0 {
|
||||
return c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))
|
||||
return w.endMessage(c.writeFatal(errors.New("websocket: internal error, extra used in client mode")))
|
||||
}
|
||||
}
|
||||
|
||||
@ -600,15 +619,11 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
c.isWriting = false
|
||||
|
||||
if err != nil {
|
||||
return w.fatal(err)
|
||||
return w.endMessage(err)
|
||||
}
|
||||
|
||||
if final {
|
||||
c.writer = nil
|
||||
if c.writePool != nil {
|
||||
c.writePool.Put(writePoolData{buf: c.writeBuf})
|
||||
c.writeBuf = nil
|
||||
}
|
||||
w.endMessage(errWriteClosed)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -706,11 +721,7 @@ func (w *messageWriter) Close() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
if err := w.flushFrame(true, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
w.err = errWriteClosed
|
||||
return nil
|
||||
return w.flushFrame(true, nil)
|
||||
}
|
||||
|
||||
// WritePreparedMessage writes prepared message into connection.
|
||||
@ -742,10 +753,10 @@ func (c *Conn) WriteMessage(messageType int, data []byte) error {
|
||||
if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
|
||||
// Fast path with no allocations and single frame.
|
||||
|
||||
if err := c.prepWrite(messageType); err != nil {
|
||||
var mw messageWriter
|
||||
if err := c.beginMessage(&mw, messageType); err != nil {
|
||||
return err
|
||||
}
|
||||
mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
|
||||
n := copy(c.writeBuf[mw.pos:], data)
|
||||
mw.pos += n
|
||||
data = data[n:]
|
||||
@ -792,7 +803,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
final := p[0]&finalBit != 0
|
||||
frameType := int(p[0] & 0xf)
|
||||
mask := p[1]&maskBit != 0
|
||||
c.readRemaining = int64(p[1] & 0x7f)
|
||||
c.setReadRemaining(int64(p[1] & 0x7f))
|
||||
|
||||
c.readDecompress = false
|
||||
if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
|
||||
@ -826,7 +837,17 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType))
|
||||
}
|
||||
|
||||
// 3. Read and parse frame length.
|
||||
// 3. Read and parse frame length as per
|
||||
// https://tools.ietf.org/html/rfc6455#section-5.2
|
||||
//
|
||||
// The length of the "Payload data", in bytes: if 0-125, that is the payload
|
||||
// length.
|
||||
// - If 126, the following 2 bytes interpreted as a 16-bit unsigned
|
||||
// integer are the payload length.
|
||||
// - If 127, the following 8 bytes interpreted as
|
||||
// a 64-bit unsigned integer (the most significant bit MUST be 0) are the
|
||||
// payload length. Multibyte length quantities are expressed in network byte
|
||||
// order.
|
||||
|
||||
switch c.readRemaining {
|
||||
case 126:
|
||||
@ -834,13 +855,19 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
c.readRemaining = int64(binary.BigEndian.Uint16(p))
|
||||
|
||||
if err := c.setReadRemaining(int64(binary.BigEndian.Uint16(p))); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
case 127:
|
||||
p, err := c.read(8)
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
c.readRemaining = int64(binary.BigEndian.Uint64(p))
|
||||
|
||||
if err := c.setReadRemaining(int64(binary.BigEndian.Uint64(p))); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Handle frame masking.
|
||||
@ -863,6 +890,12 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage {
|
||||
|
||||
c.readLength += c.readRemaining
|
||||
// Don't allow readLength to overflow in the presence of a large readRemaining
|
||||
// counter.
|
||||
if c.readLength < 0 {
|
||||
return noFrame, ErrReadLimit
|
||||
}
|
||||
|
||||
if c.readLimit > 0 && c.readLength > c.readLimit {
|
||||
c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait))
|
||||
return noFrame, ErrReadLimit
|
||||
@ -876,7 +909,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
var payload []byte
|
||||
if c.readRemaining > 0 {
|
||||
payload, err = c.read(int(c.readRemaining))
|
||||
c.readRemaining = 0
|
||||
c.setReadRemaining(0)
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
@ -949,6 +982,7 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
|
||||
c.readErr = hideTempErr(err)
|
||||
break
|
||||
}
|
||||
|
||||
if frameType == TextMessage || frameType == BinaryMessage {
|
||||
c.messageReader = &messageReader{c}
|
||||
c.reader = c.messageReader
|
||||
@ -989,7 +1023,9 @@ func (r *messageReader) Read(b []byte) (int, error) {
|
||||
if c.isServer {
|
||||
c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
|
||||
}
|
||||
c.readRemaining -= int64(n)
|
||||
rem := c.readRemaining
|
||||
rem -= int64(n)
|
||||
c.setReadRemaining(rem)
|
||||
if c.readRemaining > 0 && c.readErr == io.EOF {
|
||||
c.readErr = errUnexpectedEOF
|
||||
}
|
||||
@ -1041,7 +1077,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetReadLimit sets the maximum size for a message read from the peer. If a
|
||||
// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a
|
||||
// message exceeds the limit, the connection sends a close message to the peer
|
||||
// and returns ErrReadLimit to the application.
|
||||
func (c *Conn) SetReadLimit(limit int64) {
|
||||
|
47
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
47
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
@ -151,6 +151,53 @@
|
||||
// checking. The application is responsible for checking the Origin header
|
||||
// before calling the Upgrade function.
|
||||
//
|
||||
// Buffers
|
||||
//
|
||||
// Connections buffer network input and output to reduce the number
|
||||
// of system calls when reading or writing messages.
|
||||
//
|
||||
// Write buffers are also used for constructing WebSocket frames. See RFC 6455,
|
||||
// Section 5 for a discussion of message framing. A WebSocket frame header is
|
||||
// written to the network each time a write buffer is flushed to the network.
|
||||
// Decreasing the size of the write buffer can increase the amount of framing
|
||||
// overhead on the connection.
|
||||
//
|
||||
// The buffer sizes in bytes are specified by the ReadBufferSize and
|
||||
// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
|
||||
// size of 4096 when a buffer size field is set to zero. The Upgrader reuses
|
||||
// buffers created by the HTTP server when a buffer size field is set to zero.
|
||||
// The HTTP server buffers have a size of 4096 at the time of this writing.
|
||||
//
|
||||
// The buffer sizes do not limit the size of a message that can be read or
|
||||
// written by a connection.
|
||||
//
|
||||
// Buffers are held for the lifetime of the connection by default. If the
|
||||
// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
|
||||
// write buffer only when writing a message.
|
||||
//
|
||||
// Applications should tune the buffer sizes to balance memory use and
|
||||
// performance. Increasing the buffer size uses more memory, but can reduce the
|
||||
// number of system calls to read or write the network. In the case of writing,
|
||||
// increasing the buffer size can reduce the number of frame headers written to
|
||||
// the network.
|
||||
//
|
||||
// Some guidelines for setting buffer parameters are:
|
||||
//
|
||||
// Limit the buffer sizes to the maximum expected message size. Buffers larger
|
||||
// than the largest message do not provide any benefit.
|
||||
//
|
||||
// Depending on the distribution of message sizes, setting the buffer size to
|
||||
// a value less than the maximum expected message size can greatly reduce memory
|
||||
// use with a small impact on performance. Here's an example: If 99% of the
|
||||
// messages are smaller than 256 bytes and the maximum message size is 512
|
||||
// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
|
||||
// than a buffer size of 512 bytes. The memory savings is 50%.
|
||||
//
|
||||
// A write buffer pool is useful when the application has a modest number
|
||||
// writes over a large number of connections. when buffers are pooled, a larger
|
||||
// buffer size has a reduced impact on total memory use and has the benefit of
|
||||
// reducing system calls and frame overhead.
|
||||
//
|
||||
// Compression EXPERIMENTAL
|
||||
//
|
||||
// Per message compression extensions (RFC 7692) are experimentally supported
|
||||
|
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
Normal file
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/gorilla/websocket
|
||||
|
||||
go 1.12
|
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
Normal file
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
Normal file
42
vendor/github.com/gorilla/websocket/join.go
generated
vendored
Normal file
42
vendor/github.com/gorilla/websocket/join.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JoinMessages concatenates received messages to create a single io.Reader.
|
||||
// The string term is appended to each message. The returned reader does not
|
||||
// support concurrent calls to the Read method.
|
||||
func JoinMessages(c *Conn, term string) io.Reader {
|
||||
return &joinReader{c: c, term: term}
|
||||
}
|
||||
|
||||
type joinReader struct {
|
||||
c *Conn
|
||||
term string
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (r *joinReader) Read(p []byte) (int, error) {
|
||||
if r.r == nil {
|
||||
var err error
|
||||
_, r.r, err = r.c.NextReader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if r.term != "" {
|
||||
r.r = io.MultiReader(r.r, strings.NewReader(r.term))
|
||||
}
|
||||
}
|
||||
n, err := r.r.Read(p)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
r.r = nil
|
||||
}
|
||||
return n, err
|
||||
}
|
4
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
4
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
@ -73,8 +73,8 @@ func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
|
||||
// Prepare a frame using a 'fake' connection.
|
||||
// TODO: Refactor code in conn.go to allow more direct construction of
|
||||
// the frame.
|
||||
mu := make(chan bool, 1)
|
||||
mu <- true
|
||||
mu := make(chan struct{}, 1)
|
||||
mu <- struct{}{}
|
||||
var nc prepareConn
|
||||
c := &Conn{
|
||||
conn: &nc,
|
||||
|
8
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
8
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
@ -22,18 +22,18 @@ func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
||||
|
||||
func init() {
|
||||
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
|
||||
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type httpProxyDialer struct {
|
||||
proxyURL *url.URL
|
||||
fowardDial func(network, addr string) (net.Conn, error)
|
||||
proxyURL *url.URL
|
||||
forwardDial func(network, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
|
||||
hostPort, _ := hostPortNoPort(hpd.proxyURL)
|
||||
conn, err := hpd.fowardDial(network, hostPort)
|
||||
conn, err := hpd.forwardDial(network, hostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
4
vendor/github.com/gorilla/websocket/server.go
generated
vendored
4
vendor/github.com/gorilla/websocket/server.go
generated
vendored
@ -27,7 +27,7 @@ type Upgrader struct {
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
|
||||
// size is zero, then buffers allocated by the HTTP server are used. The
|
||||
// I/O buffer sizes do not limit the size of the messages that can be sent
|
||||
// or received.
|
||||
@ -153,7 +153,7 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
|
||||
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||
if challengeKey == "" {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank")
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank")
|
||||
}
|
||||
|
||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user