diff --git a/CHANGELOG.md b/CHANGELOG.md index 59c68e704..1e3beca67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,116 @@ ## What's New +* Ziti Component Management Access (Experimental) * [Issue #2468](https://github.com/openziti/ziti/issues/2468) - Controller configuration `edge.api.address` selects the enrollment token signer by matching the DNS SAN of a server certificate. If more than one server certificate matches then the wrong key may sign the tokens causing enrollments to fail with a signature verification error. +## Ziti Component Management Access + +This release contains an experimental feature allowing Ziti Administrators to allow access to management services for ziti components. + +This initial release is focused on providing access to SSH, but other management tools could potentially use the same data pipe. + +### Why + +Ideally one shouldn't use a system to manage itself. However, it can be nice to have a backup way to access a system, when things +go wrong. This could also be a helpful tool for small installations. + +Accessing controllers and routers via the management plane and control plane is bad from a separation of data concerns perspective, +but good from minimizing requirements perspective. To access a Ziti SSH service, An SDK client needs access to the REST API, the +edge router with a control channel connection and links to the public routers. With this solution, only the REST API and the control +channel are needed. + +### Security + +In order to access a component the following is required: + +1. The user must be a Ziti administator +2. The user must be able to reach the Fabric Management API (which can be locked down) +3. The feature must be enabled on the controller used for access +4. The feature must be enabled on the destination component +5. A destination must be configured on the destination component +6. The destination must be to a port on 127.0.0.1. This can't be used to access external systems. +8. The user must have access to the management component. If SSH, this would be an SSH key or other SSH credentials +9. If using SSH, the SSH server only needs to listen on the loopback interface. So SSH doesn't need to be listening on the network + +**Warnings** +1. If you do not intend to use the feature, do not enable it. +2. If you enable the feature, follow best practices for good SSH hygene (audit logs, locked down permissions, etc) + +### What's the Data Flow? + +The path for accessing controllers is: + +* Ziti CLI to +* Controller Fabric Management API to +* a network service listing on the loopback interface, such as SSH. + +The path for accessing routers is: + +* Ziti CLI to +* Controller Fabric Management API to +* a router via the control channel to +* a network service listing on the loopback interface, such as SSH. + +What does this look like? + +Each controller you want to allow access through, must enable the feature. + +Example controller config: + +``` +mgmt: + pipe: + enabled: true + enableExperimentalFeature: true + destination: 127.0.0.1:22 +``` + +Note that if you want to allow access through the controller, but not to the controller itself, you can +leave out the `destination` setting. + +The router config is identical. + +``` +mgmt: + pipe: + enabled: true + enableExperimentalFeature: true + destination: 127.0.0.1:22 +``` + +### SSH Access + +If your components are set up to point to an SSH server, you can access them as follows: + + +``` + ziti fabric ssh --key /path/to/keyfile ctrl_client + ziti fabric ssh --key /path/to/keyfile ubuntu@ctrl_client + ziti fabric ssh --key /path/to/keyfile -u ubuntu ctrl_client +``` + +Using the OpenSSH Client is also supported with the `--proxy-mode` flag. This also opens up access to `scp`. + +``` + ssh -i ~/.fablab/instances/smoketest/ssh_private_key.pem -o ProxyCommand='ziti fabric ssh router-east-1 --proxy-mode' ubuntu@router-east-1 + scp -i ~/.fablab/instances/smoketest/ssh_private_key.pem -o ProxyCommand='ziti fabric ssh ctrl1 --proxy-mode' ubuntu@ctrl1:./fablab/bin/ziti . +``` + +Note that you must have credentials to the host machine in addition to being a Ziti Administrator. + +### Alternate Access + +You can use the proxy mode to get a pipe to whatever service you've got configured. + +`ziti fabric ssh ctrl1 --proxy-mode` + +It's up to you to connect whatever your management client is to that local pipe. Right now it only supports +proxy via the stdin/stdout of the process. Supporting TCP or Unix Domain Socket proxies wouldn't be difficult +if there was use case for them. + # Release 1.1.15 ## What's New diff --git a/common/datapipe/config.go b/common/datapipe/config.go index c15e4857c..c2a6181a8 100644 --- a/common/datapipe/config.go +++ b/common/datapipe/config.go @@ -31,9 +31,8 @@ import ( type LocalAccessType string const ( - LocalAccessTypeNone LocalAccessType = "" - LocalAccessTypePort LocalAccessType = "local-port" - LocalAccessTypeEmbeddedSshServer LocalAccessType = "embedded-ssh-server" + LocalAccessTypeNone LocalAccessType = "" + LocalAccessTypePort LocalAccessType = "local-port" ) type Config struct { @@ -53,10 +52,6 @@ func (self *Config) IsLocalPort() bool { return self.LocalAccessType == LocalAccessTypePort } -func (self *Config) IsEmbedded() bool { - return self.LocalAccessType == LocalAccessTypeEmbeddedSshServer -} - func (self *Config) LoadConfig(m map[interface{}]interface{}) error { log := pfxlog.Logger() if v, ok := m["enabled"]; ok { @@ -87,33 +82,13 @@ func (self *Config) LoadConfig(m map[interface{}]interface{}) error { portStr := strings.TrimPrefix(destination, "127.0.0.1:") port, err := strconv.ParseUint(portStr, 10, 16) if err != nil { - log.WithError(err).Warn("mgmt.pipe is enabled, but destination not valid. Must be '127.0.0.1:' or 'embedded'") + log.WithError(err).Warn("mgmt.pipe is enabled, but destination not valid; must be '127.0.0.1:'") self.Enabled = false return nil } self.DestinationPort = uint16(port) - } else if destination == "embedded-ssh-server" { - self.LocalAccessType = LocalAccessTypeEmbeddedSshServer - - if v, ok = m["authorizedKeysFile"]; ok { - if keysFile, ok := v.(string); ok { - self.AuthorizedKeysFile = keysFile - } else { - log.Warnf("mgmt.pipe is enabled, but 'embedded' destination configured and authorizedKeysFile configuration is not type string, but %T", v) - self.Enabled = false - return nil - } - } - - if v, ok = m["shell"]; ok { - if s, ok := v.(string); ok { - self.ShellPath = s - } else { - log.Warnf("mgmt.pipe is enabled, but 'embedded' destination configured and shell configuration is not type string, but %T", v) - } - } } else { - log.Warn("mgmt.pipe is enabled, but destination not valid. Must be 'localhost:port' or 'embedded'") + log.Warn("mgmt.pipe is enabled, but destination not valid; must be '127.0.0.1:'") self.Enabled = false return nil } diff --git a/common/datapipe/ssh.go b/common/datapipe/ssh.go index 4893c4a95..7bed0ab7c 100644 --- a/common/datapipe/ssh.go +++ b/common/datapipe/ssh.go @@ -1,3 +1,5 @@ +//go:build !windows + /* Copyright NetFoundry Inc. diff --git a/common/datapipe/ssh_windows.go b/common/datapipe/ssh_windows.go new file mode 100644 index 000000000..45208bcbc --- /dev/null +++ b/common/datapipe/ssh_windows.go @@ -0,0 +1,31 @@ +/* + Copyright NetFoundry Inc. + + 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 + + https://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 datapipe + +import ( + "errors" + "github.com/gliderlabs/ssh" +) + +type SshRequestHandler struct { + config *Config + options []ssh.Option +} + +func (self *SshRequestHandler) HandleSshRequest(conn *EmbeddedSshConn) error { + return errors.New("ssh connection not supported on windows") +} diff --git a/controller/handler_mgmt/pipe.go b/controller/handler_mgmt/pipe.go index 524dc0657..c2db2d9e3 100644 --- a/controller/handler_mgmt/pipe.go +++ b/controller/handler_mgmt/pipe.go @@ -72,6 +72,11 @@ func (handler *mgmtPipeHandler) HandleReceive(msg *channel.Message, ch channel.C return } + if handler.pipe != nil { + handler.respondError(msg, "pipe already established on this endpoint, start a new mgmt connection to start a new pipe") + return + } + if request.DestinationType.CheckControllers() { log.Infof("checking requested destination '%s' against local id '%s'", request.Destination, handler.network.GetAppId()) if request.Destination == handler.network.GetAppId() { @@ -181,22 +186,17 @@ func (handler *mgmtPipeHandler) pipeToLocalhost(msg *channel.Message) { return } - if cfg.IsEmbedded() { - handler.pipeToEmbeddedSshServer(msg, pipeId) - return - } - - log.Error("mgmt.pipe misconfigured, enabled, but neither localPort nor embedded enabled") + log.Error("mgmt.pipe misconfigured, enabled, but no local endpoint configured") handler.respondError(msg, "server is misconfigured, unable to connect pipe") } func (handler *mgmtPipeHandler) pipeToLocalPort(msg *channel.Message, pipeId uint32) { cfg := handler.registry.GetConfig() log := pfxlog.ContextLogger(handler.ch.Label()). - WithField("destination", fmt.Sprintf("localhost:%d", cfg.DestinationPort)). + WithField("destination", fmt.Sprintf("127.0.0.1:%d", cfg.DestinationPort)). WithField("pipeId", pipeId) - conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", cfg.DestinationPort)) + conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", cfg.DestinationPort)) if err != nil { log.WithError(err).Error("failed to connect mgmt pipe") handler.respondError(msg, err.Error()) @@ -235,7 +235,7 @@ func (handler *mgmtPipeHandler) pipeToLocalPort(msg *channel.Message, pipeId uin log.Info("started mgmt pipe to local controller") } -func (handler *mgmtPipeHandler) pipeToEmbeddedSshServer(msg *channel.Message, pipeId uint32) { +func (handler *mgmtPipeHandler) PipeToEmbeddedSshServer(msg *channel.Message, pipeId uint32) { log := pfxlog.ContextLogger(handler.ch.Label()). WithField("destination", "embedded-ssh-server"). WithField("pipeId", pipeId) diff --git a/go.mod b/go.mod index 390544f28..ace823b43 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/fatih/color v1.17.0 github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa github.com/gaissmai/extnetip v1.1.0 - github.com/gliderlabs/ssh v0.1.1 + github.com/gliderlabs/ssh v0.3.7 github.com/go-acme/lego/v4 v4.18.0 github.com/go-openapi/errors v0.22.0 github.com/go-openapi/loads v0.22.0 @@ -106,7 +106,7 @@ require ( github.com/MichaelMure/go-term-text v0.3.1 // indirect github.com/alecthomas/chroma v0.10.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect diff --git a/go.sum b/go.sum index 92678d184..d1616e7df 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,9 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= @@ -183,7 +184,6 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= @@ -200,8 +200,9 @@ github.com/gaissmai/extnetip v1.1.0 h1:ZWEPVPUtw1o//CWh69/Eo79gWEKVYB1STrJjpTR4Q github.com/gaissmai/extnetip v1.1.0/go.mod h1:Ad+qyjy0r98Uc655JzzWoBTzDW29QR4YZDxzHgqhuqM= 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 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-acme/lego/v4 v4.18.0 h1:2hH8KcdRBSb+p5o9VZIm61GAOXYALgILUCSs1Q+OYsk= github.com/go-acme/lego/v4 v4.18.0/go.mod h1:Blkg3izvXpl3zxk7WKngIuwR2I/hvYVP3vRnvgBp7m8= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= diff --git a/router/handler_ctrl/pipe.go b/router/handler_ctrl/pipe.go index 99b477f3c..4e827a8b3 100644 --- a/router/handler_ctrl/pipe.go +++ b/router/handler_ctrl/pipe.go @@ -79,21 +79,16 @@ func (handler *ctrlPipeHandler) HandleReceive(msg *channel.Message, ch channel.C return } - if handler.env.GetMgmtPipeConfig().IsEmbedded() { - handler.pipeToEmbeddedSshServer(msg, req) - return - } - log.Error("no configured pipe handler") handler.respondError(msg, "no configured pipe handler") } func (handler *ctrlPipeHandler) pipeToLocalPort(msg *channel.Message, req *ctrl_pb.CtrlPipeRequest) { log := pfxlog.ContextLogger(handler.ch.Label()). - WithField("destination", fmt.Sprintf("localhost:%d", handler.env.GetMgmtPipeConfig().DestinationPort)). + WithField("destination", fmt.Sprintf("127.0.0.1:%d", handler.env.GetMgmtPipeConfig().DestinationPort)). WithField("pipeId", req.ConnId) - conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", handler.env.GetMgmtPipeConfig().DestinationPort)) + conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", handler.env.GetMgmtPipeConfig().DestinationPort)) if err != nil { log.WithError(err).Error("failed to dial pipe destination") handler.respondError(msg, err.Error()) @@ -126,7 +121,7 @@ func (handler *ctrlPipeHandler) pipeToLocalPort(msg *channel.Message, req *ctrl_ go pipe.readLoop() } -func (handler *ctrlPipeHandler) pipeToEmbeddedSshServer(msg *channel.Message, req *ctrl_pb.CtrlPipeRequest) { +func (handler *ctrlPipeHandler) PipeToEmbeddedSshServer(msg *channel.Message, req *ctrl_pb.CtrlPipeRequest) { log := pfxlog.ContextLogger(handler.ch.Label()). WithField("destination", "embedded-ssh-server"). WithField("pipeId", req.ConnId) diff --git a/zititest/go.mod b/zititest/go.mod index 4edb9a8db..339f12dfb 100644 --- a/zititest/go.mod +++ b/zititest/go.mod @@ -39,7 +39,7 @@ require ( github.com/MichaelMure/go-term-text v0.3.1 // indirect github.com/alecthomas/chroma v0.10.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect @@ -67,7 +67,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/gliderlabs/ssh v0.1.1 // indirect + github.com/gliderlabs/ssh v0.3.7 // indirect github.com/go-acme/lego/v4 v4.18.0 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.4.2 // indirect diff --git a/zititest/go.sum b/zititest/go.sum index 824c9bb7b..a4e2d077b 100644 --- a/zititest/go.sum +++ b/zititest/go.sum @@ -80,8 +80,9 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= @@ -188,7 +189,6 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= @@ -205,8 +205,9 @@ github.com/gaissmai/extnetip v1.1.0 h1:ZWEPVPUtw1o//CWh69/Eo79gWEKVYB1STrJjpTR4Q github.com/gaissmai/extnetip v1.1.0/go.mod h1:Ad+qyjy0r98Uc655JzzWoBTzDW29QR4YZDxzHgqhuqM= 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 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-acme/lego/v4 v4.18.0 h1:2hH8KcdRBSb+p5o9VZIm61GAOXYALgILUCSs1Q+OYsk= github.com/go-acme/lego/v4 v4.18.0/go.mod h1:Blkg3izvXpl3zxk7WKngIuwR2I/hvYVP3vRnvgBp7m8= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=