Most of the Ziti tunnelers also support a couple of “tricks” that make this kind of thing a little easier.
The first one that comes to mind after seeing Clint’s examples is using forwardPort
in the host.v1 configuration.
Port Forwarding
ziti edge create config "ubussh.host.v1" host.v1 '{"protocol":"tcp", "address":"localhost","forwardPort": true, "allowedPortRanges":[{"low":1, "high":16384}] }'
You would use this in combination with a port range in the intercept.v1 configuration to make the hosting tunneler dial whatever port the intercepted client was connecting to (as long as it was in the whitelist “allowedPortRanges”).
So, this would let you define a single service to access any port on a single host. You’d still need two services if you wanted to expose ports on host A to host B and vice-versa.
Multiple Intercept Addresses, with Ziti Dial (and Listen) by Name
What if you want to make ports on a set of hosts available to each other, with just a single ziti service? It can be done, using multiple addresses in the intercept configuration in conjunction with a few “tricks” in the host configuration.
Multiple Addresses
You probably noticed that the addresses
field in intercept.v1 uses the plural form (and takes a list). So a very straightforward way to intercept multiple IPs or hostnames with a single Ziti service is to simply list them out in the intercept.v1 “addresses” field:
ziti edge create config "zitilan.intercept.v1" intercept.v1 '{"protocols":["tcp","udp"],"addresses":["johns-pc.ziti","bills-pc.ziti"], "portRanges":[{"low":1, "high":16384}]}'
By the way, we can also use shorthand forms for multiple addresses if that works better for you. We can intercept wildcard domains and/or CIDR blocks:
"addresses": [ "*.ziti", "192.168.0.0/24", ... ]
Of course the addresses field can contain multiple wildcard domains and/or CIDRs and/or IPs/hostnames in any combination. I won’t go into it here because I don’t think it relates well to your question, but I’ll quickly mention the forwardAddress
/allowedAddresses
(and “forwardProtocol”) host,.v1 fields which behave in a similar way to forwardPort
/allowedPortRanges
.
ziti edge create config "zitilan.host.v1" host.v1 '{"forwardProtocol":true, "allowedProtocols":["tcp","udp"], "forwardAddress":true, "allowedAddressess":["johns-pc.ziti","bills-pc.ziti"], "forwardPort":true, "allowedPortRanges":[{"low":1,"high":16384}]}'
You might use this kind of forwarding if you were running a hosting tunneler as a gateway for an entire network instead of a tunneler on each host.
But I’m assuming you want a tunneler on each host, and your hosts may not even be on the same LAN. So your single Ziti service will somehow need to specify which hosting tunneler should be used to complete a connection. This is where Ziti Dial by Identity comes in.
Ziti Dial/Listen by Identity
At the Ziti layer, services are normally addressed by the name of the service. So when a Ziti client (like a tunneler) wants to connect to a Ziti service, it specifies the service name - e.g. “ubussh”. The service name is unambiguous when only a single endpoint is hosting the service, but we need some way to select a specific endpoint when multiple endpoints are hosting the same service.
The intercept.v1 configuration has a “dialOptions” node with an “identity” field. This lets us tell the intercepting tunneler what to use for the identity name when it dials the Ziti service.
Here’s an example intercept.v1 configuration that specifies dialOptions.identity and pulls all of the other “tricks” together:
ziti edge create config "zitilan.intercept.v1" intercept.v1 '{"protocols":["tcp","udp"], "addresses":[ "*.ziti"], "portRanges":[{"low":1, "high":16384}], "dialOptions":{"identity":"$dst_hostname"}}'
Notice this example uses a variable reference in the identity field. The following variables are currently supported in intercept.v1.dialOptions.identity:
variable |
description |
dst_ip |
destination IP address of the intercepted connection |
dst_port |
destination port of the intercepted connection |
dst_hostname |
hostname that the client was connecting to. this is determined by looking up the intercepted IP in the internal DNS server’s hostname map |
The Ziti dial identity needs to match a listener’s identity for all of this to work. We can tell the hosting tunnelers what identity to advertise for a service with the host.v1 listenOptions.identity field:
ziti edge create config "zitilan.host.v1" host.v1 '{"forwardProtocol":true, "allowedProtocols":["tcp","udp"], "address": "127.0.0.1", "forwardPort":true, "allowedPortRanges":[{"low":1,"high":16384}], "listenOptions": {"identity":"$tunneler_id.name"}}'
There are a few things to point out here:
- I replaced the “forwardAddress” configuration with a fixed address of 127.0.0.1. This is convenient because every host has that IP and it always means “on this box”, but using it in the host.v1 configuration obviously assumes that the servers involved (e.g. sshd) are actually listening on the loopback interface.
- “tunneler_id.name” will resolve to the actual name of the enrolled identity that the hosting tunneler used when connecting to the controller. For now this is the only variable that is supported in listenOptions.identity. The setup described here will work if your identity names match the hostnames that they represent. For example:
ziti edge create identity device johns-pc.ziti -o johns-pc.jwt
ziti edge create identity device bills-pc.ziti -o bills-pc.jwt
...
This will line up your hosting identities with the hostnames that are being intercepted (“*.ziti”). You could alternatively use IPs if you choose. In that case you’d name your identities as IP addresses, specify a CIDR in intercept.v1.addresses, and use “$dst_ip” in intercept.v1.dialOptions.identity.
I realize this is a lot to digest. Thanks for asking the question and reading this far (or even skimming)!