Embrace Secure Sharing with Zrok

I'm sure this is not something just happened to me. How many times have you faced the problem of sharing some local service or development version of something you're building to a college?

How many times have you faced the issue to share a file located in your endpoint and having to update it first to a share file service, so your peer, customer, partner, etc can download it? And how many times did you put that in public mode? did you forget to delete it?

Those things happened to me time to time, and even when I became good with my policies on sharing, deleting, etc... what a pain in the rear... And now, I found zrok!
Ziggy-zrok

zrok is an open-source built on top of OpenZiti (Yes, the programmable zero-trust network overlay). As an OpenZiti Native Application, Zrok offers a unique combination of public and private resource sharing, as well as easy web sharing capabilities. As an open-source platform, Zrok can be self-hosted or used through the managed offering provided by NetFoundry at https://zrok.io, keep in mind, at the time I wrote this article they're running a closed beta where you need to register to participate.

In this blog post, we'll host it ourselves and I will dive deep into the inner workings of zrok, exploring its architecture, features, and integration (sort of say) with OpenZiti to provide a comprehensive understanding of this innovative file/service-sharing platform, and of course, how to use it and deploy it.

Let's start by describing the architecturen and components of zrok. Zrok's architecture consists of basically three key components that work together to provide a seamless sharing experience:
  1. Zrok Controller: The Controller manages the sharing links, invites and ephemeral or reserverd resources. It also is the one who handles the "integration" or most likely communication with the OpenZiti to ensure secure, zero-trust connections between clients. The controller doesn't offer TLS, however you can install a reverse proxy.

  2. Zrok Client: The client is responsible for uploading and downloading files, as well as retrieving the sharing links. It communicates with the Zrok Server to create the resources, delete them, or even for administration purposes as environment creation, .

  3. OpenZiti: Zrok uses OpenZiti's programmable zero-trust network overlay to establish secure, encrypted connections between the clients and their sharing resources. This ensures that only authorized users can access shared resource (and once again... did I mention it's Open Source?)
Zrok Architecture*
*Image part of the zrok github documentation.

Now, that we already know its architecture, it's a good idea review the great capabilities Zrok can offer us:
  1. Public and Private Resource Sharing. zrok offers the ability to share resources publicly, "similar" to a reverse proxy, making it easy to collaborate with others. Plus, it provides private sharing capabilities by leveraging OpenZiti to support zero-trust and peer-to-peer connections without the need for any open ports on the internet (Sweet, isn't it?).

  2. Web Sharing. zrok simplifies file sharing by allowing users to easily share documents, images, or basically any kind of files, with others using a single command. This web sharing feature streamlines the sharing process and ensures a user-friendly experience.

  3. OpenZiti-powered Zero Trust. Because zrok is built on top of OpenZiti, Zrok benefits from the programmable zero-trust network overlay's security features that Ziti has to offer us. This ensure that only authenticated and authorized users, that we define,  can access shared resources, providing an additional layer of security.

  4. Open Source and Self-Hosted Options. As an open-source platform, Zrok offers the flexibility to be self-hosted or to utilize the managed offering provided by NetFoundry at https://zrok.io (not the scope of the current blog). This flexibility allows users to choose the option that best meets their needs and requirements In my case, I'm building amd hosting my own instance. 
If you got as pump up as I did when I first met zrok, let's review the installation steps, but you can always follow the documentation for the Installation or see the video available with the step-by-step. Keep in mind I'm assuming you already have your OpenZiti instance up and running, if that's not the case you can always spin up something following the Quickstart on their Documentation.
  1. Add a *.zrok.mydomain.me DNS A record in your dns pointing to the IP of where Zrok will be installed. Subsitute Y.Y.Y.Y for the real IP of your server.
  2. Example of * DNS Record

  3. Create a directory where you'll save all zrok artifacts. We'll refer to this directory as ZROK_HOME. Download zrok and unzip it there. 

  4. Create an etc directory in inside the $ZROK_HOME and create a file called ctrl.yml with the following content (It's commented so you can understand each section):
#    _____ __ ___ | | __
#   |_  / '__/ _ \| |/ /
#    / /| | | (_) |   <
#   /___|_|  \___/|_|\_\
# controller configuration

##################################### MAIN AND REQUIRED CONFIGURATION ############################
v:                  2

# I used uuidgen to create a uniq random password to administrate zrok
admin:
  secrets:
    -               487551ff-6006-4ada-91e6-38f3e944e936

# If you're going to use a reverse proxy in front of the zrok controller you can bind to localhost
# You can decide what port you want to bind as well.
endpoint:
  host:             0.0.0.0
  port:             12345

# The backend mode we'll use to save configuration and basically all zrok configuration and data. At the moment of writing this, they currently support sqlite3 and postgreSQL.
# The path will be related to $ZROK_HOME
store:
  path:             zrok.db
  type:             sqlite3

# This section is the OpenZiti configuration. API Endpoint referst to the OpenZiti controller address.
# You'll need an admin user and his password to connect to this one. As a best practice you can create a custom user for zrok instead of using the admin one.
ziti:
  api_endpoint:     "https://127.0.0.1:1280"
  username:         admin
  password:         "F26A5502B6BEF1DD0DCB1F39C6365110"

##################################### OPTIONAL CONFIGURATION #####################################
# The configuration file has many other options you can use to configure zrok, I'll try to       #
# explain each one here wwith some basic usage.                                                  #
##################################################################################################

# E-Mail Section
# zrok can connect to an SMTP server to send the invites so it can make the process of enrollment easier.
email:
  host:        127.0.0.1
  port:        25
  username:    zrok_email_user@mydomain.me
  password:    "My_Super_Secret_Password"
  from:        zrok@mydomain.me

# InfluxDB Section
# zrok can send telemetry data among some other useful information to an influxdb you may have installed.
influx:
  url:        127.0.0.1:8086
  bucket:     zrok_bucket
  org:        my_org
  token:      MY_SECURE_TOKEN_FOR_INFLUXDB
  1. In order to use the zrok command line, you'll need to add a couple environment variables to define the zrok controller endpoint we defined in the previuos step, and the admin_token we also defined previously.
export ZROK_API_ENDPOINT=http://127.0.0.1:12345
export ZROK_ADMIN_TOKEN=487551ff-6006-4ada-91e6-38f3e944e936
  1. With the configuration in place, it's time to let zrok to build its internal artifacts, for it we'll run ./zrok admin bootstrap etc/ctrl.yml, I'm assuming you're currently located in your $ZROK_HOME directory. You'll see a bunch of messages related to the creation of the different artifacts that zrok needs. 
./zrok admin bootstrap etc/ctrl.yml

[ 0.011] INFO zrok/controller/store.Open: database connected
[ 0.085] INFO zrok/controller/store.(*Store).migrate: applied 8 migrations
[ 0.085] INFO zrok/controller.Bootstrap: connecting to the ziti edge management api
[ 0.165] INFO zrok/controller.Bootstrap: creating identity for controller ziti access
[ 0.290] INFO sdk-golang/ziti/enroll.generateRSAKey: generating 4096 bit RSA key
...
[ 4.949] INFO zrok/controller.assertErpForIdentity: creating erp for 'frontend' (22a1a6a1b)
...
 
[ 5.025] INFO main.(*adminBootstrap).run: bootstrap complete! 
In the previous outout, save the identity id generated for the frontend, in this case you can see it's 22a1a6a1b, as we'll need it later.
  1. After the bootstrap process is completed, the next step is start the controller:
./zrok controller etc/ctrl.yml

It's possible to create a systemd file to control your services with the always good fashion systemctl this is an example of mine.
[Unit]
Description=Zrok-Controller
After=network.target

[Service]
User=openziti
WorkingDirectory=/opt/openziti/zrok
ExecStart="/opt/openziti/zrok/zrok" controller "/opt/openziti/zrok/etc/ctrl.yml"
Restart=always
RestartSec=2
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
  1. Zrok offers a frontend that it's used whenever a new shared resource is created. But before use it, we need to instruct zrok to create it, for that we'll need the IdentityID we saved from the step 5 among with our domain. 
./zrok admin create frontend 22a1a6a1b public https://{token}.zrok.mydomain.me
[WARNING]: unable to open zrokdir metadata; ignoring
[ 0.151] INFO main.(*adminCreateFrontendCommand).run: created global public frontend 'kSSseoZKUPS6'
If you look the previous command you'll notice it uses the URL https://{token}.zrok.mydomain.me, this is because internally zrok will substitute {token} for a random ID each time a new shared resource is created.
  1. Create the frontend configuration file in ZROK_HOME/etc/ named http-frontend.yml which will contain the listening port, along with the hostname that zrok will use externally.
host_match: zrok.mydomain.me
address: 0.0.0.0:8080
  1. Start the zrok-frontend:
./zrok access public etc/http-frontend.yml

In the same way as we did with the Ziti-Controller, the frontend can also use a systemd file to control your service with the always good fashion systemctl, this is an example of mine.
[Unit]
Description=Zrok-Frontend
After=network.target

[Service]
User=openziti
WorkingDirectory=/opt/openziti/zrok
ExecStart="/opt/openziti/zrok/zrok" access public "/opt/openziti/zrok/etc/http-frontend.yml"
Restart=always
RestartSec=2
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target

If you don't want to use SSL with zrok, you can skip this section and jump on to the step 16.
    1. Install certbot and use it to get a certificate you can deploy internally (of course you can use any other public CA. The certificate must match the DNS record created in step 1.
    certbot certonly --manual

    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    Plugins selected: Authenticator manual, Installer None
    Please enter in your domain name(s) (comma and/or space separated) (Enter 'c'
    to cancel): *.zrok.mydomain.me
    ...
    - Congratulations! Your certificate and chain have been saved at:
    /etc/letsencrypt/live/subdomain.mydomain.me/fullchain.pem
    Your key file has been saved at:
    /etc/letsencrypt/live/subdomain.mydomain.me/privkey.pem
    ...
    Keep in mind you'll need the certificate chain as well as the key in the following steps.
    1. Install nginx

    2. Create a site for nginx in /etc/nginx/sites-available and enable it creating a soft link into /etc/nginx/sites-enabled. This is my site (called zrok):
    server {
        listen              443 ssl default_server;
        server_name         api.zrok.mydomain.me;
        ssl_certificate     /etc/letsencrypt/live/subdomain.mydomain.me/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/subdomain.mydomain.me/privkey.pem;
        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         HIGH:!aNULL:!MD5;

    # This section will route the traffic to our zrok controller
        location / {
          proxy_pass      http://127.0.0.1:12345;
          error_log       /var/log/nginx/zrok-controller.log;
        }

    }

    # This server name is related to the * A DNS record we created
    server {
        listen              443 ssl;
        server_name         *.zrok.mydomain.me;
        ssl_certificate     /etc/letsencrypt/live/subdomain.mydomain.me/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/subdomain.mydomain.me/privkey.pem;
        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         HIGH:!aNULL:!MD5;

    # This section will route the traffic to our zrok frontend
        location / {
          proxy_pass       http://127.0.0.1:8080;
          proxy_set_header Host $host;
          error_log        /var/log/nginx/zrok-frontend.log;
          proxy_busy_buffers_size   512k;
          proxy_buffers    4 512k;
          proxy_buffer_size   256k;

        }
    }
    1. Start nginx.

    2. Reconfigure the zrok FrontEnd so it's aware there's a reverse proxy in front of it. In order to update it, we need to get the token associated for it we'll use the zrok cli to get the frontends configured
    zrok admin list frontends
    [WARNING]: unable to open zrokdir metadata; ignoring
    TOKEN ZID PUBLIC NAME URL TEMPLATE CREATED AT UPDATED AT
    OPTseoZKUPS6 l9o9c7v6e public https://{token}.zrok.mydomain.me 2023-04-14 18:45:46.994 +0000 UTC 2023-04-14 18:45:46.994 +0000 UTC
    Then, we'll update it with the URL created in the nginx:
    zrok admin update frontend OPTseoZKUPS6 --url-template https://{token}.zrok.mydomain.me:443
    [WARNING]: unable to open zrokdir metadata; ignoring
    [ 0.022] INFO main.(*adminUpdateFrontendCommand).run: updated global frontend 'OPTseoZKUPS6'
    1. Remember open the port for the nginx, you don't need to enable any other zrok port if you're using the reverse proxy.

    2. After the zrok backend is completely functional, it's time to invite ourselves and the users, for it we'll need the zrok cli. (If you received authentication errors, or zrok erros make sure you have the environmental variables set, as we saw in step 4.
    zrok invite
    [WARNING]: unable to open zrokdir metadata; ignoring
    enter and confirm your email address...
    > natas@mydomain.me
    > natas@mydomain.me
    [_Submit_]

    invitation sent to 'natas@mydomain.me'!

    If you configured the email settings properly, you should get an email with the URL to finish the registration, if you didn't then you can see the zrok controller logs to get the token as we'll need it to access the URL and finish the registration
    journalctl -u zrok-controller -f
    ...{"file":"/home/runner/work/zrok/zrok/controller/invite.go:100","func":"github.com/openziti/zrok/controller.(*inviteHandler).Handle","level":"info","msg":"account request for 'natas@mydomain.me' has registration token 'ABCDEFG12345678'","time":"2023-04-14T21:12:53.065Z"}
    1. If you got the mail, just click on the URL received, otherwise you can build the URL as follows: https://zrok_controller_url:zrok_controller_port/register/ABCDEFG12345678 for example https://api.zrok.mydomain.me/register/ABCDEFG12345678 
    In the page set a password for the user we just created

    zrok registration

    After setting the password, it will display our zrok environment ID so we can start using zrok to share resources:
    zrok registration complete
    zrok registration complete

    Until this point we have finally finished and zrok is ready. This newly created user will work to show how to share a resource. For that I'll use a windows machine.
    1. As usual download the zrok version for our endpoint. In this example I'll use a windows machine.
    2. Uncompress it into some destination, I'll do it into C:\Program Files\NetFoundry\Zrok\
    3. Open a terminal (cmd or powershell) and either add the zrok directory into your PATH or cd into that directory, I'm using the PATH option.
    4. Set the zrok controller endpoint we'll use.
    C:\Users\natas>zrok config set apiEndpoint https://api.zrok.mydomain.me
    [WARNING]: unable to open zrokdir metadata; ignoring

    zrok configuration updated
      1. Enable the terminal with the token we received when we registed our user:
      C:\Users\natas>zrok enable a2342dGMUURr
      ⢿  the zrok environment was successfully enabled...
      1. Share the resources you want; in this particular case I'll share a directory so anyone can access it from anywhere
      C:\Users\natas>zrok share public ZrokTest --backend-mode web

      The previous command will show us the URL we must share with those we want they have access to our resources
      Shared Resource
      Shared Resource CLI

      We can access the URL from anywhere (because it's a public one):
      Shared Resources

      Accessing a file


        There're many options you can use zrok, sharing internal servicers, make them private, so only openziti users can access the resources, but those will be part of a different post. In the meantime I highly recomend you to read the official documentation.

        In conclusion, Zrok is a powerful, OpenZiti-powered file/service-sharing platform that provides secure, easy-to-use public and private capabilities. 

        IMHO, thanks to its innovative approach to secure sharing, Zrok is an amazing tool that can become a go-to solution for those of us (individuals or organizations) who are looking for open source solutions to improve our security.

        Happy Hacking!

        Comments

        Popular posts from this blog

        Enhance your Network Security with Zero Trust and OTP

        ZDBC: The Future of Private Database Access