From 5e0237e6639bf3be83d103816488764d91588c0d Mon Sep 17 00:00:00 2001 From: Andrew Martinez Date: Fri, 13 Dec 2024 13:33:48 -0500 Subject: [PATCH] fixes #2591 ziti edge login will not fail with split APIs - ziti edge login now properly probes endpoints and their return content type along with the existing status code check - probed endpoint responses are no longer blindly parsed - the management endpoint is the default initial probe point, falling back to the client version endpoint then the legacy root version endpoint - the SPA/ZAC bindings for web apis has been moved to the webapis folder - improved erroring and messaging for the SPA/ZAC handling --- controller/controller.go | 3 +- .../webapis/generic-http.go | 17 +- controller/webapis/zac.go | 69 +++++++ controller/zac/factory.go | 46 ----- etc/ctrl.with.edge.yml | 185 ++++++++++-------- ziti/util/rest.go | 68 +++++-- zititest/go.mod | 6 +- zititest/go.sum | 12 +- 8 files changed, 243 insertions(+), 163 deletions(-) rename common/spa_handler/handler.go => controller/webapis/generic-http.go (80%) create mode 100644 controller/webapis/zac.go delete mode 100644 controller/zac/factory.go diff --git a/controller/controller.go b/controller/controller.go index 4de3c340b..2621ac72c 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -58,7 +58,6 @@ import ( "github.com/openziti/ziti/controller/xt_smartrouting" "github.com/openziti/ziti/controller/xt_sticky" "github.com/openziti/ziti/controller/xt_weighted" - "github.com/openziti/ziti/controller/zac" "github.com/pkg/errors" "github.com/sirupsen/logrus" "math/big" @@ -309,7 +308,7 @@ func (c *Controller) initWeb() { logrus.WithError(err).Fatalf("failed to create metrics api factory") } - if err = c.xweb.GetRegistry().Add(zac.NewZitiAdminConsoleFactory()); err != nil { + if err = c.xweb.GetRegistry().Add(webapis.NewZitiAdminConsoleFactory()); err != nil { logrus.WithError(err).Fatalf("failed to create single page application factory") } diff --git a/common/spa_handler/handler.go b/controller/webapis/generic-http.go similarity index 80% rename from common/spa_handler/handler.go rename to controller/webapis/generic-http.go index 11d310eba..6d4f96935 100644 --- a/common/spa_handler/handler.go +++ b/controller/webapis/generic-http.go @@ -14,7 +14,7 @@ limitations under the License. */ -package spa_handler +package webapis import ( "net/http" @@ -23,28 +23,29 @@ import ( "strings" ) -type SinglePageAppHandler struct { +type GenericHttpHandler struct { HttpHandler http.Handler BindingKey string + ContextRoot string } -func (spa *SinglePageAppHandler) Binding() string { +func (spa *GenericHttpHandler) Binding() string { return spa.BindingKey } -func (spa *SinglePageAppHandler) Options() map[interface{}]interface{} { +func (spa *GenericHttpHandler) Options() map[interface{}]interface{} { return nil } -func (spa *SinglePageAppHandler) RootPath() string { +func (spa *GenericHttpHandler) RootPath() string { return "/" + spa.BindingKey } -func (spa *SinglePageAppHandler) IsHandler(r *http.Request) bool { - return strings.HasPrefix(r.URL.Path, spa.RootPath()) || strings.HasPrefix(r.URL.Path, "/assets") +func (spa *GenericHttpHandler) IsHandler(r *http.Request) bool { + return strings.HasPrefix(r.URL.Path, spa.ContextRoot) || strings.HasPrefix(r.URL.Path, "/assets") } -func (spa *SinglePageAppHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { +func (spa *GenericHttpHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { spa.HttpHandler.ServeHTTP(writer, request) } diff --git a/controller/webapis/zac.go b/controller/webapis/zac.go new file mode 100644 index 000000000..a6c30e539 --- /dev/null +++ b/controller/webapis/zac.go @@ -0,0 +1,69 @@ +package webapis + +import ( + "fmt" + "github.com/openziti/xweb/v2" + log "github.com/sirupsen/logrus" + "strings" +) + +const ( + Binding = "zac" +) + +type ZitiAdminConsoleFactory struct { +} + +var _ xweb.ApiHandlerFactory = &ZitiAdminConsoleFactory{} + +func NewZitiAdminConsoleFactory() *ZitiAdminConsoleFactory { + return &ZitiAdminConsoleFactory{} +} + +func (factory *ZitiAdminConsoleFactory) Validate(*xweb.InstanceConfig) error { + return nil +} + +func (factory *ZitiAdminConsoleFactory) Binding() string { + return Binding +} + +func (factory *ZitiAdminConsoleFactory) New(_ *xweb.ServerConfig, options map[interface{}]interface{}) (xweb.ApiHandler, error) { + locVal := options["location"] + if locVal == nil || locVal == "" { + return nil, fmt.Errorf("location must be supplied in the %s options", Binding) + } + + loc, ok := locVal.(string) + + if !ok { + return nil, fmt.Errorf("location must be a string for the %s options", Binding) + } + + indexFileVal := options["indexFile"] + indexFile := "index.html" + + if indexFileVal != nil { + newFileVal, ok := indexFileVal.(string) + + if !ok { + return nil, fmt.Errorf("indexFile must be a string for the %s options", Binding) + } + + newFileVal = strings.TrimSpace(newFileVal) + + if newFileVal != "" { + indexFile = newFileVal + } + } + + contextRoot := "/" + Binding + spa := &GenericHttpHandler{ + HttpHandler: SpaHandler(loc, contextRoot, indexFile), + BindingKey: Binding, + ContextRoot: contextRoot, + } + + log.Infof("initializing ZAC SPA Handler from %s", locVal) + return spa, nil +} diff --git a/controller/zac/factory.go b/controller/zac/factory.go deleted file mode 100644 index be2721433..000000000 --- a/controller/zac/factory.go +++ /dev/null @@ -1,46 +0,0 @@ -package zac - -import ( - "github.com/openziti/xweb/v2" - "github.com/openziti/ziti/common/spa_handler" - log "github.com/sirupsen/logrus" -) - -const ( - Binding = "zac" -) - -type ZitiAdminConsoleFactory struct { -} - -var _ xweb.ApiHandlerFactory = &ZitiAdminConsoleFactory{} - -func NewZitiAdminConsoleFactory() *ZitiAdminConsoleFactory { - return &ZitiAdminConsoleFactory{} -} - -func (factory ZitiAdminConsoleFactory) Validate(*xweb.InstanceConfig) error { - return nil -} - -func (factory ZitiAdminConsoleFactory) Binding() string { - return Binding -} - -func (factory ZitiAdminConsoleFactory) New(_ *xweb.ServerConfig, options map[interface{}]interface{}) (xweb.ApiHandler, error) { - loc := options["location"] - if loc == nil || loc == "" { - log.Fatal("location must be supplied in " + Binding + " options") - } - indexFile := options["indexFile"] - if indexFile == nil || indexFile == "" { - indexFile = "index.html" - } - spa := &spa_handler.SinglePageAppHandler{ - HttpHandler: spa_handler.SpaHandler(loc.(string), "/"+Binding, indexFile.(string)), - BindingKey: Binding, - } - - log.Infof("initializing ZAC SPA Handler from %s", loc) - return spa, nil -} diff --git a/etc/ctrl.with.edge.yml b/etc/ctrl.with.edge.yml index dc21f6cf1..f497db943 100644 --- a/etc/ctrl.with.edge.yml +++ b/etc/ctrl.with.edge.yml @@ -79,11 +79,11 @@ ctrl: # connections. The value of newListener must be resolvable both via DNS and validate via certificates #newListener: tls:localhost:6262 -events: - jsonLogger: - subscriptions: - - type: connect - - type: sdk +#events: +# jsonLogger: +# subscriptions: +# - type: connect +# - type: sdk # - type: entityChange # include: # - services @@ -105,10 +105,10 @@ events: # - type: services # - type: edge.entityCounts # interval: 5s - handler: - type: file - format: json - path: /tmp/ziti-events.log +# handler: +# type: file +# format: json +# path: /tmp/ziti-events.log # usageLogger: # subscriptions: # - type: fabric.usage @@ -164,7 +164,7 @@ edge: # address - required # The default address (host:port) to use for enrollment for the Client API. This value must match one of the addresses # defined in a bind point's address field for the `edge-client` API in the web section. - address: 127.0.0.1:1280 + address: 127.0.0.1:443 # enrollment - required # A section containing settings pertaining to enrollment. enrollment: @@ -197,82 +197,103 @@ edge: web: # name - required # Provides a name for this listener, used for logging output. Not required to be unique, but is highly suggested. - - name: all-apis-localhost - # bindPoints - required - # One or more bind points are required. A bind point specifies an interface (interface:port string) that defines - # where on the host machine the webListener will listen and the address (host:port) that should be used to - # publicly address the webListener(i.e. mydomain.com, localhost, 127.0.0.1). This public address may be used for - # incoming address resolution as well as used in responses in the API. + - name: client1 bindPoints: - #interface - required - # A host:port string on which network interface to listen on. 0.0.0.0 will listen on all interfaces - - interface: 127.0.0.1:1280 - - # address - required - # The public address that external incoming requests will be able to resolve. Used in request processing and - # response content that requires full host:port/path addresses. - address: 127.0.0.1:1280 - - # newAddress - optional - # A host:port string which will be sent out as an HTTP header "ziti-new-address" if specified. If the header - # is present, clients should update location configuration to immediately use the new address for future - # connections. The value of newAddress must be resolvable both via DNS and validate via certificates - newAddress: localhost:1280 - # identity - optional - # Allows the webListener to have a specific identity instead of defaulting to the root `identity` section. - # identity: - # cert: ${ZITI_SOURCE}/ziti/etc/ca/intermediate/certs/ctrl-client.cert.pem - # server_cert: ${ZITI_SOURCE}/ziti/etc/ca/intermediate/certs/ctrl-server.cert.pem - # key: ${ZITI_SOURCE}/ziti/etc/ca/intermediate/private/ctrl.key.pem - # ca: ${ZITI_SOURCE}/ziti/etc/ca/intermediate/certs/ca-chain.cert.pem - # options - optional - # Allows the specification of webListener level options - mainly dealing with HTTP/TLS settings. These options are - # used for all http servers started by the current webListener. - options: - # idleTimeout - optional, default 5000ms - # The maximum amount of idle time in milliseconds allowed for pipelined HTTP requests. Setting this too high - # can cause resources on the host to be consumed as clients remain connected and idle. Lowering this value - # will cause clients to reconnect on subsequent HTTPs requests. - idleTimeout: 5000ms #http timeouts, new - - # readTimeout - optional, default 5000ms - # The maximum amount of time in milliseconds http servers will wait to read the first incoming requests. A higher - # value risks consuming resources on the host with clients that are acting bad faith or suffering from high latency - # or packet loss. A lower value can risk losing connections to high latency/packet loss clients. - - readTimeout: 5000ms - # writeTimeout - optional, default 10000ms - # The total maximum time in milliseconds that the http server will wait for a single requests to be received and - # responded too. A higher value can allow long running requests to consume resources on the host. A lower value - # can risk ending requests before the server has a chance to respond. - - writeTimeout: 100000ms - # minTLSVersion - optional, default TSL1.2 - # The minimum version of TSL to support - - minTLSVersion: TLS1.2 - # maxTLSVersion - optional, default TSL1.3 - # The maximum version of TSL to support - - maxTLSVersion: TLS1.3 - # apis - required - # Allows one or more APIs to be bound to this webListener + - interface: 0.0.0.0:443 + address: 127.0.0.1:443 apis: - # binding - required - # Specifies an API to bind to this webListener. Built-in APIs are - # - health-checks - # - edge-management - # - edge-client - # - fabric-management - - binding: health-checks - - binding: fabric - - binding: edge-management - binding: edge-client - - binding: edge-oidc + options: {} + - name: all-the-rest + bindPoints: + - interface: 0.0.0.0:8443 + address: 127.0.0.1:8443 + apis: + - binding: edge-management + options: { } + - binding: fabric + options: { } + - binding: zac options: - redirectURIs: - - "http://localhost:*/auth/callback" - - "http://127.0.0.1:*/auth/callback" + location: C:\Users\andre\repos\openziti\gross + indexFile: index.html +# +# - name: all-apis-localhost +# # bindPoints - required +# # One or more bind points are required. A bind point specifies an interface (interface:port string) that defines +# # where on the host machine the webListener will listen and the address (host:port) that should be used to +# # publicly address the webListener(i.e. mydomain.com, localhost, 127.0.0.1). This public address may be used for +# # incoming address resolution as well as used in responses in the API. +# bindPoints: +# #interface - required +# # A host:port string on which network interface to listen on. 0.0.0.0 will listen on all interfaces +# - interface: 127.0.0.1:1280 +# +# # address - required +# # The public address that external incoming requests will be able to resolve. Used in request processing and +# # response content that requires full host:port/path addresses. +# address: 127.0.0.1:1280 +# +# # newAddress - optional +# # A host:port string which will be sent out as an HTTP header "ziti-new-address" if specified. If the header +# # is present, clients should update location configuration to immediately use the new address for future +# # connections. The value of newAddress must be resolvable both via DNS and validate via certificates +# newAddress: localhost:1280 +# # identity - optional +# # Allows the webListener to have a specific identity instead of defaulting to the root `identity` section. +# # identity: +# # cert: ${ZITI_SOURCE}/ziti/etc/ca/intermediate/certs/ctrl-client.cert.pem +# # server_cert: ${ZITI_SOURCE}/ziti/etc/ca/intermediate/certs/ctrl-server.cert.pem +# # key: ${ZITI_SOURCE}/ziti/etc/ca/intermediate/private/ctrl.key.pem +# # ca: ${ZITI_SOURCE}/ziti/etc/ca/intermediate/certs/ca-chain.cert.pem +# # options - optional +# # Allows the specification of webListener level options - mainly dealing with HTTP/TLS settings. These options are +# # used for all http servers started by the current webListener. +# options: +# # idleTimeout - optional, default 5000ms +# # The maximum amount of idle time in milliseconds allowed for pipelined HTTP requests. Setting this too high +# # can cause resources on the host to be consumed as clients remain connected and idle. Lowering this value +# # will cause clients to reconnect on subsequent HTTPs requests. +# idleTimeout: 5000ms #http timeouts, new +# +# # readTimeout - optional, default 5000ms +# # The maximum amount of time in milliseconds http servers will wait to read the first incoming requests. A higher +# # value risks consuming resources on the host with clients that are acting bad faith or suffering from high latency +# # or packet loss. A lower value can risk losing connections to high latency/packet loss clients. +# +# readTimeout: 5000ms +# # writeTimeout - optional, default 10000ms +# # The total maximum time in milliseconds that the http server will wait for a single requests to be received and +# # responded too. A higher value can allow long running requests to consume resources on the host. A lower value +# # can risk ending requests before the server has a chance to respond. +# +# writeTimeout: 100000ms +# # minTLSVersion - optional, default TSL1.2 +# # The minimum version of TSL to support +# +# minTLSVersion: TLS1.2 +# # maxTLSVersion - optional, default TSL1.3 +# # The maximum version of TSL to support +# +# maxTLSVersion: TLS1.3 +# # apis - required +# # Allows one or more APIs to be bound to this webListener +# apis: +# # binding - required +# # Specifies an API to bind to this webListener. Built-in APIs are +# # - health-checks +# # - edge-management +# # - edge-client +# # - fabric-management +# - binding: health-checks +# - binding: fabric +# - binding: edge-management +# - binding: edge-client +# - binding: edge-oidc +# options: +# redirectURIs: +# - "http://localhost:*/auth/callback" +# - "http://127.0.0.1:*/auth/callback" commandRateLimiter: enabled: true diff --git a/ziti/util/rest.go b/ziti/util/rest.go index 3f50f167d..d6cd6dd68 100644 --- a/ziti/util/rest.go +++ b/ziti/util/rest.go @@ -33,6 +33,7 @@ import ( "net/http" "net/url" "path" + "strings" "time" ) @@ -520,35 +521,70 @@ func EdgeControllerGetManagementApiBasePathWithPool(host string, caPool *x509.Ce return getManagementApiBasePath(host, client) } -func getManagementApiBasePath(host string, client *resty.Client) string { - - // check v1 path first - resp, err := client.R().Get("/edge/client/v1/version") - - if err != nil || resp.StatusCode() != http.StatusOK { - // if v1 path fails, fall back to removed /version path - resp, err = client.R().Get("/version") - - if err != nil || resp.StatusCode() != http.StatusOK { - return host - } - } +func hasJsonContentType(resp *http.Response) bool { + contentType := resp.Header.Get("Content-Type") + return strings.HasPrefix(contentType, "application/json") +} - data, err := gabs.ParseJSON(resp.Body()) +// parseManagementPath will treat body as JSON from the version endpoint and attempt to +// return the management path provided in that data. If the data cannot be parsed, empty string +// is returned. +func parseManagementPath(body []byte) string { + data, err := gabs.ParseJSON(body) if err != nil { - return host + return "" } // controller w/ APIs split if data.ExistsP("data.apiVersions.edge-management") { if respPath, ok := data.Path("data.apiVersions.edge-management.v1.path").Data().(string); !ok { - return host + return "" } else { + return respPath + } + } + + return "" +} + +// getManagementApiBasePath attempts to determine which path prefix to use for the management API. This covers cases +// where only a host is provided w/ no path prefix or where host provided only hosts the client API and the management +// API is hosted somewhere else. API locations are determined by inspecting the versions endpoint. +func getManagementApiBasePath(host string, client *resty.Client) string { + resp, err := client.R().Get("/edge/management/v1/version") + + if err == nil && resp.StatusCode() == http.StatusOK && hasJsonContentType(resp.RawResponse) { + respPath := parseManagementPath(resp.Body()) + + if respPath != "" { + return host + respPath + } + } + + //guessing management first didn't work, maybe we have access to the client API to find the management API + resp, err = client.R().Get("/edge/client/v1/version") + + if err == nil && resp.StatusCode() == http.StatusOK && hasJsonContentType(resp.RawResponse) { + respPath := parseManagementPath(resp.Body()) + + if respPath != "" { + return host + respPath + } + } + + //neither the specific management/client paths worked, maybe the legacy /version path works + resp, err = client.R().Get("/version") + + if err == nil && resp.StatusCode() == http.StatusOK && hasJsonContentType(resp.RawResponse) { + respPath := parseManagementPath(resp.Body()) + + if respPath != "" { return host + respPath } } + //give up return host } diff --git a/zititest/go.mod b/zititest/go.mod index 8ca8e411b..8cd09c63a 100644 --- a/zititest/go.mod +++ b/zititest/go.mod @@ -65,7 +65,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa // indirect github.com/gaissmai/extnetip v1.1.0 // indirect - github.com/go-acme/lego/v4 v4.20.2 // indirect + github.com/go-acme/lego/v4 v4.20.4 // indirect github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -80,7 +80,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect - github.com/go-resty/resty/v2 v2.16.0 // indirect + github.com/go-resty/resty/v2 v2.16.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 // indirect @@ -103,7 +103,7 @@ require ( github.com/influxdata/influxdb-client-go/v2 v2.14.0 // indirect github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect - github.com/jedib0t/go-pretty/v6 v6.6.1 // indirect + github.com/jedib0t/go-pretty/v6 v6.6.2 // indirect github.com/jessevdk/go-flags v1.6.1 // indirect github.com/jinzhu/copier v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/zititest/go.sum b/zititest/go.sum index 31e83993c..8139a3158 100644 --- a/zititest/go.sum +++ b/zititest/go.sum @@ -205,8 +205,8 @@ github.com/gaissmai/extnetip v1.1.0/go.mod h1:Ad+qyjy0r98Uc655JzzWoBTzDW29QR4YZD github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-acme/lego/v4 v4.20.2 h1:ZwO3oLZb8fL6up1OZVJP3yHuvqhozzlEmyqKmhrPchQ= -github.com/go-acme/lego/v4 v4.20.2/go.mod h1:foauPlhnhoq8WUphaWx5U04uDc+JGhk4ZZtPz/Vqsjg= +github.com/go-acme/lego/v4 v4.20.4 h1:yCQGBX9jOfMbriEQUocdYm7EBapdTp8nLXYG8k6SqSU= +github.com/go-acme/lego/v4 v4.20.4/go.mod h1:foauPlhnhoq8WUphaWx5U04uDc+JGhk4ZZtPz/Vqsjg= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 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= @@ -246,8 +246,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= -github.com/go-resty/resty/v2 v2.16.0 h1:qpKalHWI2bpp9BIKlyT8TYWEJXOk1NuKbfiT3RRnzWc= -github.com/go-resty/resty/v2 v2.16.0/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= +github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= +github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -415,8 +415,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/jedib0t/go-pretty/v6 v6.6.1 h1:iJ65Xjb680rHcikRj6DSIbzCex2huitmc7bDtxYVWyc= -github.com/jedib0t/go-pretty/v6 v6.6.1/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= +github.com/jedib0t/go-pretty/v6 v6.6.2 h1:27bLj3nRODzaiA7tPIxy9UVWHoPspFfME9XxgwiiNsM= +github.com/jedib0t/go-pretty/v6 v6.6.2/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=