Skip to content

Commit f77b514

Browse files
committed
Don't allow embedded ssh servers
1 parent ab4d98c commit f77b514

File tree

10 files changed

+168
-56
lines changed

10 files changed

+168
-56
lines changed

CHANGELOG.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* HA Bootstrap Changes
88
* Connect Events
99
* SDK Events
10+
* Ziti Component Management Access (Experimental)
1011
* Bug fixes and other HA work
1112

1213
## New Router Metrics
@@ -211,6 +212,111 @@ events:
211212
}
212213
```
213214

215+
## Ziti Component Management Access
216+
217+
This release contains an experimental feature allowing Ziti Administrators to allow access to management services for ziti components.
218+
219+
This initial release is focused on providing access to SSH, but other management tools could potentially use the same data pipe.
220+
221+
### Why
222+
223+
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
224+
go wrong. This could also be a helpful tool for small installations.
225+
226+
Accessing controllers and routers via the management plane and control plane is bad from a separation of data concerns perspective,
227+
but good from minimizing requirements perspective. To access a Ziti SSH service, An SDK client needs access to the REST API, the
228+
edge router with a control channel connection and links to the public routers. With this solution, only the REST API and the control
229+
channel are needed.
230+
231+
### Security
232+
233+
In order to access a component the following is required:
234+
235+
1. The user must be a Ziti administrator
236+
2. The user must be able to reach the Fabric Management API (which can be locked down)
237+
3. The feature must be enabled on the controller used for access
238+
4. The feature must be enabled on the destination component
239+
5. A destination must be configured on the destination component
240+
6. The destination must be to a port on 127.0.0.1. This can't be used to access external systems.
241+
8. The user must have access to the management component. If SSH, this would be an SSH key or other SSH credentials
242+
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
243+
244+
**Warnings**
245+
1. If you do not intend to use the feature, do not enable it.
246+
2. If you enable the feature, follow best practices for good SSH hygiene (audit logs, locked down permissions, etc)
247+
248+
### What's the Data Flow?
249+
250+
The path for accessing controllers is:
251+
252+
* Ziti CLI to
253+
* Controller Fabric Management API to
254+
* a network service listing on the loopback interface, such as SSH.
255+
256+
The path for accessing routers is:
257+
258+
* Ziti CLI to
259+
* Controller Fabric Management API to
260+
* a router via the control channel to
261+
* a network service listing on the loopback interface, such as SSH.
262+
263+
What does this look like?
264+
265+
Each controller you want to allow access through, must enable the feature.
266+
267+
Example controller config:
268+
269+
```
270+
mgmt:
271+
pipe:
272+
enabled: true
273+
enableExperimentalFeature: true
274+
destination: 127.0.0.1:22
275+
```
276+
277+
Note that if you want to allow access through the controller, but not to the controller itself, you can
278+
leave out the `destination` setting.
279+
280+
The router config is identical.
281+
282+
```
283+
mgmt:
284+
pipe:
285+
enabled: true
286+
enableExperimentalFeature: true
287+
destination: 127.0.0.1:22
288+
```
289+
290+
### SSH Access
291+
292+
If your components are set up to point to an SSH server, you can access them as follows:
293+
294+
295+
```
296+
ziti fabric ssh --key /path/to/keyfile ctrl_client
297+
ziti fabric ssh --key /path/to/keyfile ubuntu@ctrl_client
298+
ziti fabric ssh --key /path/to/keyfile -u ubuntu ctrl_client
299+
```
300+
301+
Using the OpenSSH Client is also supported with the `--proxy-mode` flag. This also opens up access to `scp`.
302+
303+
```
304+
ssh -i ~/.fablab/instances/smoketest/ssh_private_key.pem -o ProxyCommand='ziti fabric ssh router-east-1 --proxy-mode' ubuntu@router-east-1
305+
scp -i ~/.fablab/instances/smoketest/ssh_private_key.pem -o ProxyCommand='ziti fabric ssh ctrl1 --proxy-mode' ubuntu@ctrl1:./fablab/bin/ziti .
306+
```
307+
308+
Note that you must have credentials to the host machine in addition to being a Ziti Administrator.
309+
310+
### Alternate Access
311+
312+
You can use the proxy mode to get a pipe to whatever service you've got configured.
313+
314+
`ziti fabric ssh ctrl1 --proxy-mode`
315+
316+
It's up to you to connect whatever your management client is to that local pipe. Right now it only supports
317+
proxy via the stdin/stdout of the process. Supporting TCP or Unix Domain Socket proxies wouldn't be difficult
318+
if there was use case for them.
319+
214320
## Component Updates and Bug Fixes
215321

216322
* github.com/openziti/channel/v3: [v3.0.5 -> v3.0.7](https://github.com/openziti/channel/compare/v3.0.5...v3.0.7)
@@ -231,6 +337,7 @@ events:
231337
* [Issue #2468](https://github.com/openziti/ziti/issues/2468) - enrollment signing cert is not properly identified
232338

233339

340+
234341
# Release 1.1.15
235342

236343
## What's New

common/datapipe/config.go

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ import (
3131
type LocalAccessType string
3232

3333
const (
34-
LocalAccessTypeNone LocalAccessType = ""
35-
LocalAccessTypePort LocalAccessType = "local-port"
36-
LocalAccessTypeEmbeddedSshServer LocalAccessType = "embedded-ssh-server"
34+
LocalAccessTypeNone LocalAccessType = ""
35+
LocalAccessTypePort LocalAccessType = "local-port"
3736
)
3837

3938
type Config struct {
@@ -53,10 +52,6 @@ func (self *Config) IsLocalPort() bool {
5352
return self.LocalAccessType == LocalAccessTypePort
5453
}
5554

56-
func (self *Config) IsEmbedded() bool {
57-
return self.LocalAccessType == LocalAccessTypeEmbeddedSshServer
58-
}
59-
6055
func (self *Config) LoadConfig(m map[interface{}]interface{}) error {
6156
log := pfxlog.Logger()
6257
if v, ok := m["enabled"]; ok {
@@ -87,33 +82,13 @@ func (self *Config) LoadConfig(m map[interface{}]interface{}) error {
8782
portStr := strings.TrimPrefix(destination, "127.0.0.1:")
8883
port, err := strconv.ParseUint(portStr, 10, 16)
8984
if err != nil {
90-
log.WithError(err).Warn("mgmt.pipe is enabled, but destination not valid. Must be '127.0.0.1:<port>' or 'embedded'")
85+
log.WithError(err).Warn("mgmt.pipe is enabled, but destination not valid; must be '127.0.0.1:<port>'")
9186
self.Enabled = false
9287
return nil
9388
}
9489
self.DestinationPort = uint16(port)
95-
} else if destination == "embedded-ssh-server" {
96-
self.LocalAccessType = LocalAccessTypeEmbeddedSshServer
97-
98-
if v, ok = m["authorizedKeysFile"]; ok {
99-
if keysFile, ok := v.(string); ok {
100-
self.AuthorizedKeysFile = keysFile
101-
} else {
102-
log.Warnf("mgmt.pipe is enabled, but 'embedded' destination configured and authorizedKeysFile configuration is not type string, but %T", v)
103-
self.Enabled = false
104-
return nil
105-
}
106-
}
107-
108-
if v, ok = m["shell"]; ok {
109-
if s, ok := v.(string); ok {
110-
self.ShellPath = s
111-
} else {
112-
log.Warnf("mgmt.pipe is enabled, but 'embedded' destination configured and shell configuration is not type string, but %T", v)
113-
}
114-
}
11590
} else {
116-
log.Warn("mgmt.pipe is enabled, but destination not valid. Must be 'localhost:port' or 'embedded'")
91+
log.Warn("mgmt.pipe is enabled, but destination not valid; must be '127.0.0.1:<port>'")
11792
self.Enabled = false
11893
return nil
11994
}

common/datapipe/ssh.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:build !windows
2+
13
/*
24
Copyright NetFoundry Inc.
35

common/datapipe/ssh_windows.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
Copyright NetFoundry Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package datapipe
18+
19+
import (
20+
"errors"
21+
"github.com/gliderlabs/ssh"
22+
)
23+
24+
type SshRequestHandler struct {
25+
config *Config
26+
options []ssh.Option
27+
}
28+
29+
func (self *SshRequestHandler) HandleSshRequest(conn *EmbeddedSshConn) error {
30+
return errors.New("ssh connection not supported on windows")
31+
}

controller/handler_mgmt/pipe.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ func (handler *mgmtPipeHandler) HandleReceive(msg *channel.Message, ch channel.C
7272
return
7373
}
7474

75+
if handler.pipe != nil {
76+
handler.respondError(msg, "pipe already established on this endpoint, start a new mgmt connection to start a new pipe")
77+
return
78+
}
79+
7580
if request.DestinationType.CheckControllers() {
7681
log.Infof("checking requested destination '%s' against local id '%s'", request.Destination, handler.network.GetAppId())
7782
if request.Destination == handler.network.GetAppId() {
@@ -181,22 +186,17 @@ func (handler *mgmtPipeHandler) pipeToLocalhost(msg *channel.Message) {
181186
return
182187
}
183188

184-
if cfg.IsEmbedded() {
185-
handler.pipeToEmbeddedSshServer(msg, pipeId)
186-
return
187-
}
188-
189-
log.Error("mgmt.pipe misconfigured, enabled, but neither localPort nor embedded enabled")
189+
log.Error("mgmt.pipe misconfigured, enabled, but no local endpoint configured")
190190
handler.respondError(msg, "server is misconfigured, unable to connect pipe")
191191
}
192192

193193
func (handler *mgmtPipeHandler) pipeToLocalPort(msg *channel.Message, pipeId uint32) {
194194
cfg := handler.registry.GetConfig()
195195
log := pfxlog.ContextLogger(handler.ch.Label()).
196-
WithField("destination", fmt.Sprintf("localhost:%d", cfg.DestinationPort)).
196+
WithField("destination", fmt.Sprintf("127.0.0.1:%d", cfg.DestinationPort)).
197197
WithField("pipeId", pipeId)
198198

199-
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", cfg.DestinationPort))
199+
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", cfg.DestinationPort))
200200
if err != nil {
201201
log.WithError(err).Error("failed to connect mgmt pipe")
202202
handler.respondError(msg, err.Error())
@@ -235,7 +235,7 @@ func (handler *mgmtPipeHandler) pipeToLocalPort(msg *channel.Message, pipeId uin
235235
log.Info("started mgmt pipe to local controller")
236236
}
237237

238-
func (handler *mgmtPipeHandler) pipeToEmbeddedSshServer(msg *channel.Message, pipeId uint32) {
238+
func (handler *mgmtPipeHandler) PipeToEmbeddedSshServer(msg *channel.Message, pipeId uint32) {
239239
log := pfxlog.ContextLogger(handler.ch.Label()).
240240
WithField("destination", "embedded-ssh-server").
241241
WithField("pipeId", pipeId)

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ require (
2020
github.com/fatih/color v1.18.0
2121
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa
2222
github.com/gaissmai/extnetip v1.1.0
23-
github.com/gliderlabs/ssh v0.1.1
23+
github.com/gliderlabs/ssh v0.3.7
2424
github.com/go-acme/lego/v4 v4.19.2
2525
github.com/go-openapi/errors v0.22.0
2626
github.com/go-openapi/loads v0.22.0
@@ -106,7 +106,7 @@ require (
106106
github.com/MichaelMure/go-term-text v0.3.1 // indirect
107107
github.com/alecthomas/chroma v0.10.0 // indirect
108108
github.com/andybalholm/brotli v1.1.0 // indirect
109-
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
109+
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
110110
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
111111
github.com/armon/go-metrics v0.4.1 // indirect
112112
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect

go.sum

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
7979
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
8080
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
8181
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
82-
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
8382
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
83+
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
84+
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
8485
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
8586
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
8687
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
@@ -183,7 +184,6 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
183184
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
184185
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
185186
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
186-
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
187187
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
188188
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
189189
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
200200
github.com/gaissmai/extnetip v1.1.0/go.mod h1:Ad+qyjy0r98Uc655JzzWoBTzDW29QR4YZDxzHgqhuqM=
201201
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
202202
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
203-
github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw=
204203
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
204+
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
205+
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
205206
github.com/go-acme/lego/v4 v4.19.2 h1:Y8hrmMvWETdqzzkRly7m98xtPJJivWFsgWi8fcvZo+Y=
206207
github.com/go-acme/lego/v4 v4.19.2/go.mod h1:wtDe3dDkmV4/oI2nydpNXSJpvV10J9RCyZ6MbYxNtlQ=
207208
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=

router/handler_ctrl/pipe.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,21 +79,16 @@ func (handler *ctrlPipeHandler) HandleReceive(msg *channel.Message, ch channel.C
7979
return
8080
}
8181

82-
if handler.env.GetMgmtPipeConfig().IsEmbedded() {
83-
handler.pipeToEmbeddedSshServer(msg, req)
84-
return
85-
}
86-
8782
log.Error("no configured pipe handler")
8883
handler.respondError(msg, "no configured pipe handler")
8984
}
9085

9186
func (handler *ctrlPipeHandler) pipeToLocalPort(msg *channel.Message, req *ctrl_pb.CtrlPipeRequest) {
9287
log := pfxlog.ContextLogger(handler.ch.Label()).
93-
WithField("destination", fmt.Sprintf("localhost:%d", handler.env.GetMgmtPipeConfig().DestinationPort)).
88+
WithField("destination", fmt.Sprintf("127.0.0.1:%d", handler.env.GetMgmtPipeConfig().DestinationPort)).
9489
WithField("pipeId", req.ConnId)
9590

96-
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", handler.env.GetMgmtPipeConfig().DestinationPort))
91+
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", handler.env.GetMgmtPipeConfig().DestinationPort))
9792
if err != nil {
9893
log.WithError(err).Error("failed to dial pipe destination")
9994
handler.respondError(msg, err.Error())
@@ -126,7 +121,7 @@ func (handler *ctrlPipeHandler) pipeToLocalPort(msg *channel.Message, req *ctrl_
126121
go pipe.readLoop()
127122
}
128123

129-
func (handler *ctrlPipeHandler) pipeToEmbeddedSshServer(msg *channel.Message, req *ctrl_pb.CtrlPipeRequest) {
124+
func (handler *ctrlPipeHandler) PipeToEmbeddedSshServer(msg *channel.Message, req *ctrl_pb.CtrlPipeRequest) {
130125
log := pfxlog.ContextLogger(handler.ch.Label()).
131126
WithField("destination", "embedded-ssh-server").
132127
WithField("pipeId", req.ConnId)

zititest/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ require (
3939
github.com/MichaelMure/go-term-text v0.3.1 // indirect
4040
github.com/alecthomas/chroma v0.10.0 // indirect
4141
github.com/andybalholm/brotli v1.1.0 // indirect
42-
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
42+
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
4343
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
4444
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
4545
github.com/armon/go-metrics v0.4.1 // indirect
@@ -67,7 +67,7 @@ require (
6767
github.com/fsnotify/fsnotify v1.7.0 // indirect
6868
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa // indirect
6969
github.com/gaissmai/extnetip v1.1.0 // indirect
70-
github.com/gliderlabs/ssh v0.1.1 // indirect
70+
github.com/gliderlabs/ssh v0.3.7 // indirect
7171
github.com/go-acme/lego/v4 v4.19.2 // indirect
7272
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
7373
github.com/go-logr/logr v1.4.2 // indirect

0 commit comments

Comments
 (0)