diff --git a/.golangci.toml b/.golangci.toml index 66c3e6b6ca..5634bc859a 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -132,6 +132,9 @@ [[issues.exclude-rules]] path = "challenge/dns01/nameserver_test.go" text = "findXByFqdnTestCases is a global variable" + [[issues.exclude-rules]] + path = "challenge/dns01/network.go" + text = "currentNetworkStack is a global variable" [[issues.exclude-rules]] path = "challenge/http01/domain_matcher.go" text = "string `Host` has \\d occurrences, make it a constant" diff --git a/challenge/dns01/nameserver.go b/challenge/dns01/nameserver.go index 4762dc574b..4963d72c1f 100644 --- a/challenge/dns01/nameserver.go +++ b/challenge/dns01/nameserver.go @@ -250,13 +250,17 @@ func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg { } func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) { - udp := &dns.Client{Net: "udp", Timeout: dnsTimeout} - in, _, err := udp.Exchange(m, ns) - - if in != nil && in.Truncated { - tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout} + network := currentNetworkStack.Network("udp") + client := &dns.Client{Net: network, Timeout: dnsTimeout} + in, _, err := client.Exchange(m, ns) + + // We can encounter a net.OpError if the nameserver is not listening + // on UDP at all, i.e. net.Dial could not make a connection. + var opErr *net.OpError + if (in != nil && in.Truncated) || errors.As(err, &opErr) { + client.Net = currentNetworkStack.Network("tcp") // If the TCP request succeeds, the err will reset to nil - in, _, err = tcp.Exchange(m, ns) + in, _, err = client.Exchange(m, ns) } return in, err diff --git a/challenge/dns01/nameserver_test.go b/challenge/dns01/nameserver_test.go index b63ea9e737..26c3236f2d 100644 --- a/challenge/dns01/nameserver_test.go +++ b/challenge/dns01/nameserver_test.go @@ -1,13 +1,133 @@ package dns01 import ( + "net" "sort" + "sync" "testing" + "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func testDNSHandler(writer dns.ResponseWriter, reply *dns.Msg) { + msg := dns.Msg{} + msg.SetReply(reply) + + if reply.Question[0].Qtype == dns.TypeA { + msg.Authoritative = true + domain := msg.Question[0].Name + msg.Answer = append( + msg.Answer, + &dns.A{ + Hdr: dns.RR_Header{ + Name: domain, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 60, + }, + A: net.IPv4(127, 0, 0, 1), + }, + ) + } + + _ = writer.WriteMsg(&msg) +} + +// getTestNameserver constructs a new DNS server on a local address, or set +// of addresses, that responds to an `A` query for `example.com`. +func getTestNameserver(t *testing.T, network string) *dns.Server { + t.Helper() + server := &dns.Server{ + Handler: dns.HandlerFunc(testDNSHandler), + Net: network, + } + switch network { + case "tcp", "udp": + server.Addr = "0.0.0.0:0" + case "tcp4", "udp4": + server.Addr = "127.0.0.1:0" + case "tcp6", "udp6": + server.Addr = "[::1]:0" + } + + waitLock := sync.Mutex{} + waitLock.Lock() + server.NotifyStartedFunc = waitLock.Unlock + + go func() { _ = server.ListenAndServe() }() + + waitLock.Lock() + return server +} + +func startTestNameserver(t *testing.T, stack networkStack, proto string) (shutdown func(), addr string) { + t.Helper() + currentNetworkStack = stack + srv := getTestNameserver(t, currentNetworkStack.Network(proto)) + + shutdown = func() { _ = srv.Shutdown() } + if proto == "tcp" { + addr = srv.Listener.Addr().String() + } else { + addr = srv.PacketConn.LocalAddr().String() + } + return +} + +func TestSendDNSQuery(t *testing.T) { + currentNameservers := recursiveNameservers + + t.Cleanup(func() { + recursiveNameservers = currentNameservers + currentNetworkStack = dualStack + }) + + t.Run("does udp4 only", func(t *testing.T) { + stop, addr := startTestNameserver(t, ipv4only, "udp") + defer stop() + + recursiveNameservers = ParseNameservers([]string{addr}) + msg := createDNSMsg("example.com.", dns.TypeA, true) + result, queryError := sendDNSQuery(msg, addr) + require.NoError(t, queryError) + assert.Equal(t, result.Answer[0].(*dns.A).A.String(), "127.0.0.1") + }) + + t.Run("does udp6 only", func(t *testing.T) { + stop, addr := startTestNameserver(t, ipv6only, "udp") + defer stop() + + recursiveNameservers = ParseNameservers([]string{addr}) + msg := createDNSMsg("example.com.", dns.TypeA, true) + result, queryError := sendDNSQuery(msg, addr) + require.NoError(t, queryError) + assert.Equal(t, result.Answer[0].(*dns.A).A.String(), "127.0.0.1") + }) + + t.Run("does tcp4 and tcp6", func(t *testing.T) { + stop, addr := startTestNameserver(t, dualStack, "tcp") + _, port, _ := net.SplitHostPort(addr) + defer stop() + t.Logf("### port: %s", port) + + addr6 := net.JoinHostPort("::1", port) + recursiveNameservers = ParseNameservers([]string{addr6}) + msg := createDNSMsg("example.com.", dns.TypeA, true) + result, queryError := sendDNSQuery(msg, addr6) + require.NoError(t, queryError) + assert.Equal(t, result.Answer[0].(*dns.A).A.String(), "127.0.0.1") + + addr4 := net.JoinHostPort("127.0.0.1", port) + recursiveNameservers = ParseNameservers([]string{addr4}) + msg = createDNSMsg("example.com.", dns.TypeA, true) + result, queryError = sendDNSQuery(msg, addr4) + require.NoError(t, queryError) + assert.Equal(t, result.Answer[0].(*dns.A).A.String(), "127.0.0.1") + }) +} + func TestLookupNameserversOK(t *testing.T) { testCases := []struct { fqdn string @@ -74,7 +194,7 @@ var findXByFqdnTestCases = []struct { zone string primaryNs string nameservers []string - expectedError string + expectedError string // regular expression }{ { desc: "domain is a CNAME", @@ -109,7 +229,7 @@ var findXByFqdnTestCases = []struct { fqdn: "test.lego.zz.", zone: "lego.zz.", nameservers: []string{"8.8.8.8:53"}, - expectedError: "could not find the start of authority for test.lego.zz.: NXDOMAIN", + expectedError: `^could not find the start of authority for test\.lego\.zz\.: NXDOMAIN`, }, { desc: "several non existent nameservers", @@ -119,18 +239,21 @@ var findXByFqdnTestCases = []struct { nameservers: []string{":7053", ":8053", "8.8.8.8:53"}, }, { - desc: "only non-existent nameservers", - fqdn: "mail.google.com.", - zone: "google.com.", - nameservers: []string{":7053", ":8053", ":9053"}, - expectedError: "could not find the start of authority for mail.google.com.: read udp", + desc: "only non-existent nameservers", + fqdn: "mail.google.com.", + zone: "google.com.", + nameservers: []string{":7053", ":8053", ":9053"}, + // NOTE: On Windows, net.DialContext finds a way down to the ContectEx syscall. + // There a fault is marked as "connectex", not "connect", see + // https://cs.opensource.google/go/go/+/refs/tags/go1.19.5:src/net/fd_windows.go;l=112 + expectedError: `^could not find the start of authority for mail\.google\.com\.: dial tcp :9053: connect(ex)?:`, }, { desc: "no nameservers", fqdn: "test.ldez.com.", zone: "ldez.com.", nameservers: []string{}, - expectedError: "could not find the start of authority for test.ldez.com.", + expectedError: `^could not find the start of authority for test\.ldez\.com\.`, }, } @@ -142,7 +265,7 @@ func TestFindZoneByFqdnCustom(t *testing.T) { zone, err := FindZoneByFqdnCustom(test.fqdn, test.nameservers) if test.expectedError != "" { require.Error(t, err) - assert.Contains(t, err.Error(), test.expectedError) + assert.Regexp(t, test.expectedError, err.Error()) } else { require.NoError(t, err) assert.Equal(t, test.zone, zone) @@ -159,7 +282,7 @@ func TestFindPrimaryNsByFqdnCustom(t *testing.T) { ns, err := FindPrimaryNsByFqdnCustom(test.fqdn, test.nameservers) if test.expectedError != "" { require.Error(t, err) - assert.Contains(t, err.Error(), test.expectedError) + assert.Regexp(t, test.expectedError, err.Error()) } else { require.NoError(t, err) assert.Equal(t, test.primaryNs, ns) diff --git a/challenge/dns01/network.go b/challenge/dns01/network.go new file mode 100644 index 0000000000..26e0ba57b0 --- /dev/null +++ b/challenge/dns01/network.go @@ -0,0 +1,41 @@ +package dns01 + +// networkStack is used to indicate which IP stack should be used for DNS queries. +type networkStack int + +const ( + dualStack networkStack = iota + ipv4only + ipv6only +) + +// currentNetworkStack is used to define which IP stack will be used. The default is +// both IPv4 and IPv6. Set to IPv4Only or IPv6Only to select either version. +var currentNetworkStack = dualStack + +// Network interprets the NetworkStack setting in relation to the desired +// protocol. The proto value should be either "udp" or "tcp". +func (s networkStack) Network(proto string) string { + // The DNS client passes whatever value is set in (*dns.Client).Net to + // the [net.Dialer](https://github.com/miekg/dns/blob/fe20d5d/client.go#L119-L141). + // And the net.Dialer accepts strings such as "udp4" or "tcp6" + // (https://cs.opensource.google/go/go/+/refs/tags/go1.18.9:src/net/dial.go;l=167-182). + switch s { + case ipv4only: + return proto + "4" + case ipv6only: + return proto + "6" + default: + return proto + } +} + +// SetIPv4Only forces DNS queries to only happen over the IPv4 stack. +func SetIPv4Only() { currentNetworkStack = ipv4only } + +// SetIPv6Only forces DNS queries to only happen over the IPv6 stack. +func SetIPv6Only() { currentNetworkStack = ipv6only } + +// SetDualStack indicates that both IPv4 and IPv6 should be allowed. +// This setting lets the OS determine which IP stack to use. +func SetDualStack() { currentNetworkStack = dualStack } diff --git a/challenge/http01/http_challenge_server.go b/challenge/http01/http_challenge_server.go index f69f5ac1f8..8d5e31bb09 100644 --- a/challenge/http01/http_challenge_server.go +++ b/challenge/http01/http_challenge_server.go @@ -33,7 +33,6 @@ func NewProviderServer(iface, port string) *ProviderServer { if port == "" { port = "80" } - return &ProviderServer{network: "tcp", address: net.JoinHostPort(iface, port), matcher: &hostMatcher{}} } @@ -41,6 +40,28 @@ func NewUnixProviderServer(socketPath string, mode fs.FileMode) *ProviderServer return &ProviderServer{network: "unix", address: socketPath, socketMode: mode, matcher: &hostMatcher{}} } +// SetIPv4Only starts the challenge server on an IPv4 address. +// +// Calling this method has no effect if s was created with NewUnixProviderServer. +func (s *ProviderServer) SetIPv4Only() { s.setTCPStack("tcp4") } + +// SetIPv6Only starts the challenge server on an IPv6 address. +// +// Calling this method has no effect if s was created with NewUnixProviderServer. +func (s *ProviderServer) SetIPv6Only() { s.setTCPStack("tcp6") } + +// SetDualStack indicates that both IPv4 and IPv6 should be allowed. +// This setting lets the OS determine which IP stack to use for the challenge server. +// +// Calling this method has no effect if s was created with NewUnixProviderServer. +func (s *ProviderServer) SetDualStack() { s.setTCPStack("tcp") } + +func (s *ProviderServer) setTCPStack(network string) { + if s.network != "unix" { + s.network = network + } +} + // 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 { var err error diff --git a/challenge/http01/http_challenge_test.go b/challenge/http01/http_challenge_test.go index fa42f3740c..372e8c4f72 100644 --- a/challenge/http01/http_challenge_test.go +++ b/challenge/http01/http_challenge_test.go @@ -32,6 +32,7 @@ func TestProviderServer_GetAddress(t *testing.T) { testCases := []struct { desc string server *ProviderServer + network func(*ProviderServer) expected string }{ { @@ -49,6 +50,18 @@ func TestProviderServer_GetAddress(t *testing.T) { server: NewProviderServer("localhost", "8080"), expected: "localhost:8080", }, + { + desc: "TCP4 with host and port", + server: NewProviderServer("localhost", "8080"), + network: func(s *ProviderServer) { s.SetIPv4Only() }, + expected: "localhost:8080", + }, + { + desc: "TCP6 with host and port", + server: NewProviderServer("localhost", "8080"), + network: func(s *ProviderServer) { s.SetIPv6Only() }, + expected: "localhost:8080", + }, { desc: "UDS socket", server: NewUnixProviderServer(sock, fs.ModeSocket|0o666), @@ -61,6 +74,10 @@ func TestProviderServer_GetAddress(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() + if test.network != nil { + test.network(test.server) + } + address := test.server.GetAddress() assert.Equal(t, test.expected, address) }) diff --git a/challenge/tlsalpn01/tls_alpn_challenge_server.go b/challenge/tlsalpn01/tls_alpn_challenge_server.go index e0976a6b0e..ea64b9ed51 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_server.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_server.go @@ -26,6 +26,7 @@ const ( type ProviderServer struct { iface string port string + network string listener net.Listener } @@ -33,9 +34,22 @@ type ProviderServer struct { // Setting iface and / or port to an empty string will make the server fall back to // the "any" interface and port 443 respectively. func NewProviderServer(iface, port string) *ProviderServer { - return &ProviderServer{iface: iface, port: port} + if port == "" { + port = defaultTLSPort + } + return &ProviderServer{iface: iface, port: port, network: "tcp"} } +// SetIPv4Only starts the challenge server on an IPv4 address. +func (s *ProviderServer) SetIPv4Only() { s.network = "tcp4" } + +// SetIPv6Only starts the challenge server on an IPv6 address. +func (s *ProviderServer) SetIPv6Only() { s.network = "tcp6" } + +// SetDualStack indicates that both IPv4 and IPv6 should be allowed. +// This setting lets the OS determine which IP stack to use for the challenge server. +func (s *ProviderServer) SetDualStack() { s.network = "tcp" } + func (s *ProviderServer) GetAddress() string { return net.JoinHostPort(s.iface, s.port) } @@ -65,7 +79,7 @@ func (s *ProviderServer) Present(domain, token, keyAuth string) error { tlsConf.NextProtos = []string{ACMETLS1Protocol} // Create the listener with the created tls.Config. - s.listener, err = tls.Listen("tcp", s.GetAddress(), tlsConf) + s.listener, err = tls.Listen(s.network, s.GetAddress(), tlsConf) if err != nil { return fmt.Errorf("could not start HTTPS server for challenge: %w", err) } diff --git a/challenge/tlsalpn01/tls_alpn_challenge_test.go b/challenge/tlsalpn01/tls_alpn_challenge_test.go index 4bfb47bf90..a894c5dedb 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_test.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_test.go @@ -8,6 +8,7 @@ import ( "crypto/tls" "encoding/asn1" "net/http" + "os" "testing" "github.com/go-acme/lego/v4/acme" @@ -18,6 +19,60 @@ import ( "github.com/stretchr/testify/require" ) +func TestProviderServer_GetAddress(t *testing.T) { + dir := t.TempDir() + t.Cleanup(func() { _ = os.RemoveAll(dir) }) + + testCases := []struct { + desc string + server *ProviderServer + network func(*ProviderServer) + expected string + }{ + { + desc: "TCP default address", + server: NewProviderServer("", ""), + expected: ":443", + }, + { + desc: "TCP with explicit port", + server: NewProviderServer("", "4443"), + expected: ":4443", + }, + { + desc: "TCP with host and port", + server: NewProviderServer("localhost", "4443"), + expected: "localhost:4443", + }, + { + desc: "TCP4 with host and port", + server: NewProviderServer("localhost", "4443"), + network: func(s *ProviderServer) { s.SetIPv4Only() }, + expected: "localhost:4443", + }, + { + desc: "TCP6 with host and port", + server: NewProviderServer("localhost", "4443"), + network: func(s *ProviderServer) { s.SetIPv6Only() }, + expected: "localhost:4443", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + if test.network != nil { + test.network(test.server) + } + + address := test.server.GetAddress() + assert.Equal(t, test.expected, address) + }) + } +} + func TestChallenge(t *testing.T) { _, apiURL := tester.SetupFakeAPI(t) @@ -71,7 +126,7 @@ func TestChallenge(t *testing.T) { solver := NewChallenge( core, mockValidate, - &ProviderServer{port: "23457"}, + &ProviderServer{port: "23457", network: "tcp"}, ) authz := acme.Authorization{ @@ -99,7 +154,7 @@ func TestChallengeInvalidPort(t *testing.T) { solver := NewChallenge( core, func(_ *api.Core, _ string, _ acme.Challenge) error { return nil }, - &ProviderServer{port: "123456"}, + &ProviderServer{port: "123456", network: "tcp"}, ) authz := acme.Authorization{ diff --git a/cmd/flags.go b/cmd/flags.go index 902d3526ea..0bcd0615bd 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -8,6 +8,16 @@ import ( func CreateFlags(defaultPath string) []cli.Flag { return []cli.Flag{ + &cli.BoolFlag{ + Name: "ipv4only", + Aliases: []string{"4"}, + Usage: "Use IPv4 only. This flag is ignored if ipv6only is also specified.", + }, + &cli.BoolFlag{ + Name: "ipv6only", + Aliases: []string{"6"}, + Usage: "Use IPv6 only. This flag is ignored if ipv4only is also specified.", + }, &cli.StringSliceFlag{ Name: "domains", Aliases: []string{"d"}, diff --git a/cmd/setup_challenges.go b/cmd/setup_challenges.go index 938ee74592..14e7de22e0 100644 --- a/cmd/setup_challenges.go +++ b/cmd/setup_challenges.go @@ -41,6 +41,24 @@ func setupChallenges(ctx *cli.Context, client *lego.Client) { } } +type networkStackSetter interface { + SetIPv4Only() + SetIPv6Only() + SetDualStack() +} + +func setNetwork(ctx *cli.Context, srv networkStackSetter) { + switch v4, v6 := ctx.IsSet("ipv4only"), ctx.IsSet("ipv6only"); { + case v4 && !v6: + srv.SetIPv4Only() + case !v4 && v6: + srv.SetIPv6Only() + default: + // setting both --ipv4only and --ipv6only is not an error, just a no-op + srv.SetDualStack() + } +} + func setupHTTPProvider(ctx *cli.Context) challenge.Provider { switch { case ctx.IsSet("http.webroot"): @@ -67,12 +85,14 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider { } srv := http01.NewProviderServer(host, port) + setNetwork(ctx, srv) if header := ctx.String("http.proxy-header"); header != "" { srv.SetProxyHeader(header) } return srv case ctx.Bool("http"): srv := http01.NewProviderServer("", "") + setNetwork(ctx, srv) if header := ctx.String("http.proxy-header"); header != "" { srv.SetProxyHeader(header) } @@ -96,9 +116,13 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider { log.Fatal(err) } - return tlsalpn01.NewProviderServer(host, port) + srv := tlsalpn01.NewProviderServer(host, port) + setNetwork(ctx, srv) + return srv case ctx.Bool("tls"): - return tlsalpn01.NewProviderServer("", "") + srv := tlsalpn01.NewProviderServer("", "") + setNetwork(ctx, srv) + return srv default: log.Fatal("Invalid HTTP challenge options.") return nil @@ -111,6 +135,16 @@ func setupDNS(ctx *cli.Context, client *lego.Client) { log.Fatal(err) } + switch v4, v6 := ctx.IsSet("ipv4only"), ctx.IsSet("ipv6only"); { + case v4 && !v6: + dns01.SetIPv4Only() + case !v4 && v6: + dns01.SetIPv6Only() + default: + // setting both --ipv4only and --ipv6only is not an error, just a no-op + dns01.SetDualStack() + } + servers := ctx.StringSlice("dns.resolvers") err = client.Challenge.SetDNS01Provider(provider, dns01.CondOption(len(servers) > 0, diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index c02f0739f6..15bb1bad3f 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -38,6 +38,8 @@ GLOBAL OPTIONS: --http.port value Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port. (default: ":80") --http.proxy-header value Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy. (default: "Host") --http.webroot value Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge + --ipv4only, -4 Use IPv4 only. This flag is ignored if ipv6only is also specified. (default: false) + --ipv6only, -6 Use IPv6 only. This flag is ignored if ipv4only is also specified. (default: false) --key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384. (default: "ec256") --kid value Key identifier from External CA. Used for External Account Binding. --path value Directory to use for storing the data. (default: "./.lego") [$LEGO_PATH] diff --git a/go.mod b/go.mod index efd0ec70ee..139cd35629 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/sacloud/iaas-api-go v1.3.2 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 github.com/softlayer/softlayer-go v1.0.6 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 github.com/transip/gotransip/v6 v6.17.0 @@ -117,8 +117,8 @@ require ( github.com/smartystreets/assertions v1.0.1 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect - github.com/spf13/cast v1.3.1 // indirect - github.com/stretchr/objx v0.4.0 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.opencensus.io v0.22.3 // indirect go.uber.org/ratelimit v0.2.0 // indirect diff --git a/go.sum b/go.sum index 2c0e9440bb..eb52794b5f 100644 --- a/go.sum +++ b/go.sum @@ -132,6 +132,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -515,8 +516,9 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 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/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -526,8 +528,9 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 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/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -535,8 +538,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=