OpenZiti host level overlay

Hello again :slight_smile:

I’ve run through the quickstart with the http.ziti example from tunneler to service. In this example there is single service set up, specifically from client port to server port; very selective and secure service networking – a good example.

As a next step, I’m trying to understand how to configure a much less granular setup: one where the overlay is at the host level, so if host A and host B are in the overlay, perhaps via a tunneler on each, then any service on host A can reach any service on host B, and vice-versa without having to define all the possible services this might be. Yes, this is pretty much a perimeter model, and similar to a wireguard mesh, but we’d like to give it a try.

Is this possible to do with OpenZiti? Is there some configuration examples or docs available that would help accomplish this or something close to it?

Thanks!

Yep. The goal is to have a list of these sort of quickstarts along with plusses and minuses of the setup. It’s just “on the list of things to do” - like the declarative deployments! :smiley: OpenZiti declarative deployments

What you want to do is DEFINITELY possible with OpenZiti. This is a slide I present to people all the time. On the top is “least zero trust” all the way to “most zero trust” on the bottom. important to note, you can mix/match these models. One side can be ZTNA where the other is ZTAA etc

On top you see ZTNA. Or zero trust network access. This is expertly demonstrated by the great video Robert made. It’s not written up YET, but it will be. Until then you can watch the video.

For a “host to host” type of model, I just did a video for a user on reddit that I think shows you exactly what you want. Over there I show you how to RDP from linux to windows, and how to ssh from windows to linux. I think that’s exactly what you’re looking for. The ONLY difference you’d want is to enable all ports on the intercept (or dial) and host (or bind). That will boil down to these commands:

ziti edge create config "ubussh.host.v1" host.v1 '{"protocol":"tcp", "address":"localhost","port":22}'
ziti edge create config "ubussh.intercept.v1" intercept.v1 '{"protocols":["tcp"],"addresses":["ubu.ssh.ziti"], "portRanges":[{"low":22, "high":22}]}'

You’d just change the low port to 1 and the high port to 65535 (or you know, whatever range you actually want) :slight_smile:

That help? is that what you’re looking for?

3 Likes

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:

  1. 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.
  2. “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)!

2 Likes

Thank you both – these are great answers! I understand pretty much everything you’ve both explained here, at least superficially :). Now I have some work to do to see where I can run with this info!

Thank you!

1 Like