Zitadel integration

I have been trying to integrate Zitadel idp as the identity provider for Openziti. This has been very challenging to say the least as I am new to both systems.

The goal is to have Zitadel "Service Users" (non interactive machine accounts), authenticate and be able to access Openziti protected api resources on the overlay network, without creating accounts inside Openziti.

For authorization the plan was to implement Openziti "Tags" and somehow associate to these tags via Zitadels "Metadata" for each user and see if this metadata could also be passed in the JWT.

It appears that this should be possible with Openziti trusting Zitadel as the External JWT signer, but I cant figure out how to get this trust set up. Am I off base here with the plan and my very basic understanding of the two systems?

Are there any guides or videos (ZitiTV?) on how to implement this beast of an integration?

I'm now at a point that I am willing to pay someone who knows more than me to get this set up...

Hi @alexrbct, I'll go take a look at Zitadel IdP and see what I think. Before getting too far into this though, one thing we need to figure out is whether you're writing your own applications or if you're trying to use our tunnelers.

We are adding IdP support to tunnelers on our roadmap, but it's not implemented quite yet. If you're interested in integrating an IdP with code, golang specifically, I can show you working examples right now.

"Service Users" could be either/or, so clarifying that one piece is important.

We are not using tunnelers, this is a background go application that is running on users workstations. "Service Users" are what Zitadel calls "machine users".

Well then we can get this done quickly, I'm sure!!!! :slight_smile:

This sounds like a fun ziti tv too. Let me play around some with zitiadel itself... Actually, do you have some sort of "how to setup zitadel" you want to share that would make it easier for me to show you how? That'd help me get through some zitadel bootstrapping too.

In the meantime, I think this code from a qcon presentation i gave last year is relevant and useful to you: GitHub - dovholuknf/qcon2023: A repo with dev scripts and code for the qcon 2023 presentation

I recorded the whole presentation and put it on our youtube (below). The parts that are really relevant are where I use the jwt provided by SPIRE to authenticate to ziti... It's not exactly the same, but it's very, very close...

The "spire and openziti" bits are what I think are most relevant to you since that's using ext jwt signers: qcon2023/src/part4_spire_and_openziti at main · dovholuknf/qcon2023 · GitHub

Clint you are awesome!
I currently have zitadel is running in docker via:

Thanks, is there anything "particular" you want to see here? Some kind of setup? Federated to google, anything like that? That'll help me narrow my focus.

Take a look at how I integrated OpenZiti with Zitadel in this PR - Feature/add OIDC by potto007 · Pull Request #31 · openziti-test-kitchen/zssh · GitHub

Hope that helps!

Paul Otto

1 Like

I almost recommended this too! :slight_smile: also a good thing to check out (and believe it or not, i've not forgotten about this PR) lol

Clint, just trying to have a single "service user" user which was created in Zitadel be able to authenticate to OpenZIti, a bonus would be to get permissions via Zitadel metadata value mapped to a tag in the JWT response.

Paul, I will study the linked Pull Request to see if I can make some progress, thank you.

Alright, I played around with Zitadel today and came up with something that I think works... I made a walkthrough video showing the full process. You can watch that here:


Hopefully the video and the commands below will help you piece everything together. The biggest pain, in my opinion, is understanding that the 'sub' (ject) of the jwts is the zitadel ID.

The basic flow is:

  • install zitadel (login/change the password)
  • make a server identity/generate a secret
  • make a client identity /generate a secret
  • capture the zitadel IDs and secrets for use when configuring ziti
  • create an external jwt signer
  • create an auth policy
  • create identities and assign them the auth policy from above
  • create a service and authorize the identities to dial/bind the service

Here are the commands as run during the video for easier copy/pasting (obviously change the values, I left my values in place so they'd match the video). I also left out the ziti cli login step.

establish the varialbles

ZITADEL_URL="http://sg4u22wsl.parkplace-via-dhcp:8080"
JWTCHAT_SERVER_ID="269776127390662660"
JWTCHAT_SERVER_SECRET="UEMcjcCJdVoWnZzSnWcvgmiE2AAfDYuH1WGFzsZcOlYfowhICoBN2SIqykv7oBJI"
JWTCHAT_CLIENT_ID="269776240116776964"
JWTCHAT_CLIENT_SECRET="pKl4ueFaNHWLMElS3dM5CWIZ1S0EydqmW8qQWArtUCWn3tGXoehXORXoq4G4D8NY"

use the variables to configure the OpenZiti overlay with the ziti CLI

jwtsigner=$(ziti edge create ext-jwt-signer jwtchat-idp "${ZITADEL_URL}" -a openziti -u "${ZITADEL_URL}/oauth/v2/keys")
authpolicy=$(ziti edge create auth-policy jwtchat --primary-ext-jwt-allowed --primary-ext-jwt-allowed-signers ${jwtsigner})
ziti edge create identity server_id --external-id "${JWTCHAT_SERVER_ID}" -a jwtchat -P ${authpolicy}
ziti edge create identity client_id --external-id "${JWTCHAT_CLIENT_ID}" -a jwtchat -P ${authpolicy}
ziti edge create service jwtchat -a jwtchat
ziti edge create service-policy jwtchatDial Dial --service-roles '#jwtchat' --identity-roles '#jwtchat'
ziti edge create service-policy jwtchatBind Bind --service-roles '#jwtchat' --identity-roles '#jwtchat'

Clone the golang SDK repo and use the proper branch

(unless it's merged when you read this)

Run the JWT chat server (uses the variables from before)

go run jwtchat-server/server.go \
	--openziti-url="https://localhost:1280" \
	--idp-token-url="${ZITADEL_URL}/oauth/v2/token" \
	--client-id="jwtchat-server" \
	--client-secret="${JWTCHAT_SERVER_SECRET}" \
	--scope="openid urn:zitadel:iam:org:project:id:openziti:aud"

Run the JWT chat client (uses the variables from before)

go run jwtchat-client/client.go \
	--openziti-url="https://localhost:1280" \
	--idp-token-url="${ZITADEL_URL}/oauth/v2/token" \
	--client-id="jwtchat-client" \
	--client-secret="${JWTCHAT_CLIENT_SECRET}" \
	--scope="openid urn:zitadel:iam:org:project:id:openziti:aud"

I hope that gets you what you need!

1 Like

Clint, Thank you, this is extremely helpful especially the video talk through.

@TheLumberjack,

I successfully got your example to work and learned a lot in the process. However, it doesn't completely meet the goal, which is to have the identities live entirely in Zitadel. In this process, we still need to create an identity in OpenZiti and then link it to a Zitadel account via External ID.

Is it possible to modify the configuration to avoid creating the identity in OpenZiti and instead obtain the #attribute from the JWT?

Alternatively, if we need to create a workaround, could we create a shared shell identity and populate each auth request with the External ID (which would now need to be stored with the client)?

Awesome! Glad it helped.

No. The external jwt signers are for authentication exclusively and MUST be mapped to an OpenZiti identity in order to be authorized for services. OpenZiti then is responsible for authorizing the services the for each identity.

Well, while I won't recommend it, sure you could do that but you effectively have a single Identity with access to everything, if I understand what You're going for.

I'd ask the question back though, if I might, I'm interested in understanding why creating identities is a problem, if you'll indulge me?

The goal is to see if OpenZiti can be configured to work with external account management. This way, there is no need to create, update, or delete accounts in OpenZiti individually; it can all be managed in one external place, Zitadel. We would also use this same Zitadel identity to allow the same devices to authentication into other systems, unrelated to OpenZiti.

What I meant by this is that perhaps we could use an OpenZiti identity as a role or permissions template. This way, if a device is authenticated (through Zitadel via External ID and client secret), the device will assume the permissions of this OpenZiti identity.

For instance, when a device logs in to OpenZiti through Zitadel, they authenticate via the External ID and client secret. If successful, they are mapped to a predefined OpenZiti identity which holds the necessary permissions and roles (which services they are allowed to access). If this approach is possible, it would allow centralized account management while leveraging OpenZiti for access control.

This was just an idea, maybe there is a better and more correct way to do this, if at all possible?

:slight_smile: Well imo we know that it can "work with it", but you're trying to drive OpenZiti from that external account management and that feels a bit different to me. I understand better what you're after here though. It might be something we will look to do in the future, but it's not something that's available right now. You could always make a tool that keeps the two in sync and then be a hero to the next OpenZiti community member? :smiley:

I admit that I'm confused here since that's what I thought we demonstrated? Maybe that wasn't clear from the previous example -- or maybe it's only obvious to me since I have used external id's like this in the past, but I think that's what we did here.

This is actually why it's important to predefine the OpenZiti identity, so that all services/access is defined so that when any device that with a client id and secret that maps to an external id, that device gets the predefined/necessary access.

Maybe a real-world use case would be easier to demonstrate? Do you have a real use case to share that might make it easier for me/us to understand what you're going after which might help solidify things for both of us?