Binding a service to multiple hosts

First off - I have given this a go as it appears to make sense in my head, but not when I give it a go. The outcome, is that I will likely have a number of 1-1 mappings of users to their desktop machines at work from their home device. In my mind, I think I should be able to create one service, and through service policies apply this to different identity bind/dial configurations, but this does not seem to work. I always end up on the first configured (OK, I have only defined two - one working and then to play with).

I have a host dial as this:
image

An intercept of this:

and a service configuration of

To me, these look like generic configuration that based on service policies should just work.

I then have two service policy definitions


But using an RDP client I am ending up on the desktop I should not be ending up on. I confirm this by getting the computer name from the log-in screen.

Am I going about this the correct way? Is there a better way to do this?

If I understand what you’re after correctly. You can do this with one ‘bind’ service, but I think you’ll need ‘n’ dial services (one per identity).

You give the binding identities the ability to host a single service, offloaded to localhost 3389 as shown, but it needs to “bindUsingEdgeIdentity:true”… Then you give the dial ability for the specific user desktop per user and indicate which identity to dial per user.

I don’t think there’s a way to do exactly what you’re I think you’re going for, yet. I might be wrong and there might be some feature I’m not aware of.

That give you enough info? I’m traveling tomorrow but I’ll follow up if not

I think addressable terminators can help with this case. Basically you set dialOptions.identity in the intercept configuration to tell the dialing tunneler which terminator to dial. Here’s an example from an ssh service that I use:

    ziti edge create config ssh-intercept.v1 intercept.v1 '{
      "protocols": ["tcp"],
      "addresses": ["zet.shawns-m1-mbp","zde.shawns-m1-mbp","zet.fedora-37-vm"],
      "portRanges": [{"low":22,"high":22}],
      "dialOptions": {
        "identity": "$dst_hostname"
      }
    }'

And you also need to tell the hosting tunnelers what their terminator name is. You can do this with listenOptions.identity or listenOptions.bindUsingEdgeIdentity. Here’s the remainder of my ssh example. This assumes that the identities are named exactly the same as the hostnames that they are addressed by:

ziti edge create config ssh-host.v1 host.v1 '{
      "protocol": "tcp",
      "address": "127.0.0.1",
      "port": 22,
      "listenOptions": {
        "bindUsingEdgeIdentity": true
      }
    }'

At this time the following variables are supported in dialOptions.identity:

  • $dst_protocol
  • $dst_ip
  • $dst_port
  • $dst_hostname

See support domain substitutions · Issue #540 · openziti/ziti-tunnel-sdk-c · GitHub for some ideas that might be implemented in the future, and of course feel free to comment on the issue with any thoughts.

listenOptions.identity currently only supports $tunneler_id.name, which is effectively the same as setting bindUsingEdgeIdentity to true.

Thank you both for the guidance to date. Just come back to look at this. I am starting to work through this. Currently getting to understand the bindUsingEdgeIdentity goodness, and a few gotchas which I will detail here, before I head onto trying to do what I want to do using what I have found here.

ZAC Goodness
This might have been addressed with the latest ZAC, but I am using v2.5.4, I could not create a host.v1 configuration with the GUI with bindUsingEdgeIdentity. While there is no switch to enable it, I thought that I could use the JSON button in the top right hand corner to supply the JSON that I wanted.

Clicking Save does not come back with an error. It seems to drop the “ListenOptions” altogether.
Found this out when looking at the command line. Perhaps if one of you are running Jeremys latest version, see if this is a problem, and maybe I can log a bug for this?

ID
The second part of a problem I have suspected from other articles. That is, Windows clients need a FQDN name to resolve and zoom through the ziti network. This is where some more of those variables will come in handy, such as that proposed by $dst_host from here: support domain substitutions · Issue #540 · openziti/ziti-tunnel-sdk-c (github.com). I need to create all identities with a FQDN identity for any service that I want to use BindUsingEdgeIdentity for this to work. ie server1.id instead of just the hostname of server1. When $dst_host becomes defined then this will not be necessary (use case for this variable).

Intercept
With the above accounted for, from what I can see in the zssh tutorial ( Revisiting zssh - Aug 2022 - YouTube), the intercept configuration is not necessary, but it appears it is in my case, and what @scareything provided above. I do need the intercept otherwise I end up going nowhere.

…now to try and piece this together and work out how I might do this…more to come.

In my playing around, it appears that I need to create a service for each 1-1 mapping, using a generic host.v1 configuration with BindUsingEdgeIdentity. Then, I create an intercept for each user (and put that into the service) along with a bind and dial policy to specify exactly what they get to.

That is the only way I can seem to get it sorted.

I like the 1-many configuration that @scarything has detailed and can use that for IT Sys engineers to RDP to various servers. I changed the address in the intercept to be a wildcard, and that worked well along with the $dst_domain - again using a FQDN for the identity terminator, and not an ID without.

Were you thinking it could be done differently?

You’ve got a few different issues all in this one thread. I’ll give it a look tomorrow early my time and try to reply.

I see the exact same behavior with 2.5.4 and I can confirm that it seems fixed in “the latest”.

All of the tunnelers indicate that a fullyqualified domain name is required for intercepts to function properly. “Sometimes” they work correctly with a naked/non-fully qualified name (like a host name) but that “sometimes” is why we state that you really need a fully qualified domain name for intercepts to work properly. Windows is really finicky here between DNS, WINS, LMHOSTS, NETBIOS… it’s finicky… Other OS’s are differently finicky. For now, we do require that intercepted names be fully-qualified.

I believe you shouldn’t need to do that, with the expectation that the name of the identity does match the intercepted address you’re dialing. I expect you to only need one intercept v1 config and one host v1 config.

You’ll have to change over to using the ZAC latest or the ziti CLI to try this out though. I assume you did that, and when you did, you weren’t successful. Were there any specific errors in either log that popped out?

I think I am missing the ‘hot sauce’ for this. Service policies bind services to ‘endpoints’. The host.v1 is generic and so depending upon on what the intercept is listening to will pump it too the host.v1. So for each client I need to change the intercept listen address. Which means I need to have a separate service definition to have the 1-1 relationship? Is there a different way? Sorry not In front of machine.

The sauce is two-fold. Firstly there’s the bind side and bindUsingEdgeIdentity. This instructs the SDK to create a terminator for the service for the identity coming online that has the bind policy. So any identity that has a bind policy for this service will be able to be ‘dialed’ but will NEED to be dialed by identity…

Then the “listenOptions” in the intercept config, use the “$dst_hostname” token to dial the identity named by the intercepted address…

So to illustrate that a bit… I made these two ziti identities:

ziti edge create identity device ctrl.zrok -o ctrl.zrok.jwt -a ssh-binders
ziti edge create identity device client.zrok -o client.zrok.jwt -a ssh-dialers

Then I made these configs/service/policies:

ziti edge create config ssh-intercept.v1 intercept.v1 '{
  "protocols": ["tcp"],
  "addresses": ["ctrl.zrok"],
  "portRanges": [{"low":22,"high":22}],
  "dialOptions": {
    "identity": "$dst_hostname"
  }
}'

ziti edge create config ssh-host.v1 host.v1 '{
  "protocol": "tcp",
  "address": "127.0.0.1",
  "port": 22,
  "listenOptions": {
    "bindUsingEdgeIdentity": true
  }
}'

ziti edge create service ssh -c ssh-intercept.v1,ssh-host.v1

ziti edge create service-policy ssh-dial Dial --identity-roles '#ssh-dialers' --service-roles '@ssh'
ziti edge create service-policy ssh-bind Bind --identity-roles '#ssh-binders' --service-roles '@ssh'

I downloaded the ‘client’ jwt and enrolled it. Then I could do the following:

ssh ubuntu@ctrl.zrok -i ~/.ssh/my.pem

The magic is that I named the identity the same name that i want to intercept… That make sense?

Yep. I get that and understand. Now I want to do another 1-1 binding for another client server pair. I gather I would need to duplicate this setup if doing another 1-1 mapping which would then negate using this style of configuration and could revert to the traditional style. I am trying to find an easy way to configure 1-1 mappings without duplicating all the hosts, intercepts, and bindings.

You keep having one intercept and one host config, but you need to update the config. notice that I added “aws.server.ziti” to the addresses field here:

ziti edge update config ssh-intercept.v1 -d '{
  "protocols": ["tcp"],
  "addresses": ["ctrl.zrok","aws.server.ziti"],
  "portRanges": [{"low":22,"high":22}],
  "dialOptions": {
    "identity": "$dst_hostname"
  }
}'

then made/enrolled/ran the identity:

ziti edge create identity device aws.server.ziti -o aws.server.ziti.jwt -a ssh-binders
./ziti-edge-tunnel enroll -j aws.server.ziti.jwt -i aws.server.ziti.json
sudo ./ziti-edge-tunnel run -i aws.server.ziti.json

and now i can ssh to “aws.server.ziti”…
image

Correct. But I only want one user to go to one machine. What you have there is a 1-many configuration. Ie if I give multiple identities the #service attribute then all those identities will be able to go to both those endpoints. I want 1 client to one service only. In this case each user can only rdp to their machine and not to any other machine.

Aaaaahhhh. Now I understand what you mean by 1-1. I thought you meant 1 dial, 1 bind config! Yeah for that you could use x-1 or 1-x where only certain users can dial a certain machine or where only certain users can bind a particular machine.

If it were me, I would go for 1 dial policy per user and allow all identities to bind 3389. You still need the dial policy to use it, and you still need the password to the machine to access it as well. So not quite as many policies as 1 to 1. You could certainly do it that way of course. I can’t think of any other way to do it myself…