Multifactor Zero Trust ssh

Multifactor Zero Trust ssh

The previous post revisited the zssh project and demonstrated how to implement a simple ssh client using the extended modules provided by the Golang project. It also modified that simple program and showed what it takes to incorporate OpenZiti, creating a zero trust ssh client. This post focuses on another aspect of zssh and OpenZiti: multi-factor authentication.

OpenZiti has a powerful and flexible authentication system. It allows an OpenZiti overlay network operator to decide what authentication mechanisms any given identity must satisfy before being authenticated to the overlay network. OpenZiti is a zero trust overlay network; authentication is only half of the equation. Once authenticated, identities still require authorization before accessing services secured by the overlay following the zero trust pillar of "least privilege".

Certificate-based Authentication

Perhaps the most common type of authentication, OpenZiti supports authentication to the the OpenZiti overlay network using certificates. It might not be obvious, but a zssh user using an enrolled an OpenZiti identity to authenticate to the overlay network is already implicitly using multi-factor authentication. The first authentication factor is OpenZiti itself. OpenZiti requires connections to be both authenticated and authorized before being allowed to connect to the target service. Once authenticated and authorized, zssh can connect to sshd and attempt to authenticate. Just by using zssh, users are protected with two factors of authentication but zssh (and OpenZiti) offers other factors of authentication as well.

When enrolling an identity, the output of the enrollment flow will be an OpenZiti identity file containing a certificate, key, and CA bundle. This identity can then be used to authenticate connections to the target OpenZiti overlay network. If you are interested in learning how this process works, you can read about it in Andrew's five-part series about bootstrapping trust. Using zssh with an identity file and certificate-based authentication looks something like this (examples are taken directly from the GitHub repo):

zssh \
  -i "${private_key}" \
  -s "${service_name}" \
  -c "${client_identity}.json" \
  "${user_id}@${server_identity}"

In this example, zssh is accepting the ssh key to use to authenticate to sshd (-i), the OpenZiti service to dial (-s), the OpenZiti identity file (-c). Somewhat hidden within this example is the OpenZiti target identity that is binding the service. All zssh connections are made with the expectation that the target identity is provided to the zssh command where one would normal 'host' would be. This allows the OpenZiti overlay network operator to have a single service usable by any identity looking to provide ssh access.

OIDC-based Authentication Only

The zssh executable was recently enhanced to support OIDC-only-based authentication. Using external JWT signers, you can configure an OpenZIti overlay network to trust JWTs from configured IdPs as authentication tokens. This allows the operator to create authentication policies allowing for external, OIDC-based integrations. This is interesting because it allows you to authenticate to the OpenZiti overlay network without needing to enroll the client ahead of time. Instead, zssh users complete an Authorization Code Flow with PKCE. In this scenario, the user will see the familiar flow of a browser window popping up and asking the user to authenticate to an identity provider configured to be trusted by OpenZiti. When the flow completes, the zssh binary will have a JWT that can be used to authenticate to the OpenZiti controller.

zssh \
  -i "${private_key}" \
  -s "${service_name}" \
  -o \
  -a "${oidc_issuer}" \
  -n openziti-client \
  --oidcOnly \
  --controllerUrl https://localhost:1280 \
  "${user_id}@${server_identity}"

In this example, zssh is given the same -i and -s parameters as the certificate-based example above as well as the userid@identity but there are a few others supplied. The -o flag is passed to indicate zssh should obtain a JWT from the specified (-a) OIDC provider. In this example, Keycloak is used as a federated IdP to federate authentication to GitHub. To authenticate to Keycloak, a client id (-n) will need to be provided. This Keycloak client minimally needs to be configured with URLs it's allowed to redirect to after successful authentication. This example uses the --oidcOnly flag, indicating no OpenZiti certificate will be used for authentication. An identity will need to exist in the controller and a matching auth-policy to allow the identity to authenticate. Finally, as no OpenZiti identity is used in this example, zssh must be told where to send the authentication request with the --controllerUrl flag.

Certificate-based + OIDC-based Authentication

OpenZiti can also be configured to support OIDC as a secondary form of authentication. Accordingly, the zssh binary can be configured to use certificate-based authentication as a primary authentication source and an OIDC-based flow for secondary authentication. In the scenario, connecting to the OpenZiti overlay itself would require multiple forms of authentication. These mechanisms are a great way to prove there's both a human and a device. The device provides the certificate, while the human interacts with Keycloak/GitHub's OIDC to verify a human is indeed in the loop.

zssh \
  -i "${private_key}" \
  -s "${service_name}" \
  -o \
  -a "${oidc_issuer}" \
  -n openziti-client \
  -c "${client_identity}.json" \
  "${user_id}@${server_identity}"

In this example, zssh is given the all the same parameters as the OIDC-only example but because it specifies an OpenZiti identity with -c, the --controllerUrl and --oidcOnly flags are not necessary.

TOTP Instead of OIDC

Also added in this release was support for OpenZiti's TOTP-based authentication. Users can now be required to enter their TOTP code before making a connection, allowing for multi-factor authentication to the OpenZiti overlay without using an IdP. Or, if using the OIDC-only based flow with an IdP that doesn't support TOTP (for reasons), OpenZiti's TOTP can be used as a secondary form of authentication to the overlay. For TOTP example usage have a look at the readme on the repository. There, you’ll find the commands shown in the example gif below.

Download zssh/zscp

If you are interested in trying out zssh and it's partner zscp, you can download the latest releases from GitHub at https://github.com/openziti-test-kitchen/zssh/releases/latest, right next to the source code for the project.

Share the Project

If you find this interesting, please consider starring the projects on GitHub. It really does help to support the project! And if you haven't seen it yet, check out zrok.io. It's a totally free sharing platform built on OpenZiti and uses the OpenIti SDK! It uses the OpenZiti Go SDK since it's a ziti-native application. It's also all open source too!

Tell us how you're using OpenZiti on X twitter, reddit, or over at our Discourse. Or, if you prefer, check out our content on YouTube if that's more your speed. Regardless of how, we'd love to hear from you.