DIY Multi-Hop Boundary Sessions without HCP

Creating an unofficial ingress/egress worker setup for HashiCorp Boundary without HCP or enterprise licensing

I treat my homelab as if it has the same security requirements as a production environment because for me it is a production environment. I normally use Tailscale for protected access and avoid exposing services to the internet. Some remote networks block Tailscale or WireGuard connections, so I also need a break-glass option.

As with many of my other posts, you can see I use HashiCorp's suite of tools. Boundary provides identity-based access to systems in my homelab without SSH tunnels or exposing each system to the internet. It is also a single binary that runs without administrative access, which helps when I cannot install VPN software on a machine.

The problem

My homelab budget does not stretch to the enterprise version of Boundary or HCP (HashiCorp Cloud Platform). The community version does not support the ingress and egress workers required for multi-hop sessions.

I want multi-hop sessions so I can keep at least some VLAN isolation without letting the server in the DMZ connect directly to every server in my homelab.

The topology I wanted:

My DIY solution

My workaround depends on Boundary's connection flow:

  1. The client (my laptop) connects to the controller
  2. The controller provides an authorization token
  3. The client connects to the worker port using this token
  4. The worker establishes the session with the target

I run an FRP (Fast Reverse Proxy) server alongside the DMZ Boundary controller and worker. The internal workers connect outward to that server, so I do not need firewall rules that permit inbound connections to the internal workers. After registration, each internal worker gets a Boundary target pointing at a unique frps port on the Boundary server's loopback interface.

The resulting session path:

Setting it up

Following the standard Boundary installation process, expose the DMZ controller API, usually port 9200, and the worker, usually port 9202, to the internet. The endpoint could be boundary.tklk.dev, with Caddy providing TLS for API access. Internal workers also need to reach the cluster worker coordinator on port 9201, but that port does not need public exposure. They can reach it through the same path used for the FRP server.

Installing FRP server

Install FRP on the DMZ server:

  1. Download the appropriate FRP release from GitHub
  2. Extract the archive and locate the frps binary
  3. Create a configuration file as shown below
  4. Set up a systemd service (optional) to ensure it runs automatically

This is the frps configuration I use:

bindPort = 7000
auth.method = "token"
auth.token = "your_secure_token"

# meaning the internal workers can only use this range for listening on
allowPorts = [ {start = 9500, end = 9999}  ]

Internal worker setup

After the FRP server is running, configure the internal workers:

  1. Install and configure a standard Boundary worker

    # minimal worker config
    # you may wish to add more configuration options, such as using a KMS
    listener "tcp" {
      # this is the listener that frpc will connect to over the loop back
      address = "127.0.0.1:9202"
      purpose = "proxy"
    }
    
    worker {
      name = "internal-worker-1"
      # this is the address that the boundary controller will announce to the client
      # so this is what the target session should listen to on your local laptop/client
      public_addr = "127.0.0.1:9500"
      description = "Worker in the internal VLAN"
      controllers = ["boundary.tklk.dev:9201"]
    }
    
  2. Install FRP client (frpc)

  3. Configure frpc to establish a reverse tunnel back to the primary server on a loopback interface

This is the frpc configuration I use:

serverAddr = "boundary.tklk.dev"
serverPort = 7000
auth.method = "token"
auth.token = "your_secure_token"

[[proxies]]
name = "boundary_internal_worker"
type = "tcp"
localIP = "127.0.0.1"
# 9202 is the Boundary worker's default port, but since you may have multiple internal workers, you might want to pick
# a unique port for each worker (which you'll also need to set in the worker config)
# It is strongly recommended that you don't use port 9202 as the DMZ worker will already likely be using this port, causing the frps connection to fail
localPort = 9202
remotePort = 9500

Registering the internal worker as a target

Register each internal worker as a Boundary target pointing to the FRP server's loopback interface and the port selected in the frpc configuration. You can do this with Terraform, the Boundary CLI, or the Boundary UI.

At minimum, the target needs 127.0.0.1 as its host, the port from the frpc configuration, and a default client port that matches the worker port.

An egress worker filter can force the DMZ worker to connect to the frps port. Because the target uses localhost, Boundary may otherwise assign the route to a different worker that cannot reach it.

Using the setup

Authenticate with Boundary, then use boundary connect to establish a connection to the internal worker target.

# Authenticate with Boundary (if not already authenticated)
boundary authenticate

# List available targets
boundary targets list -recursive

Accessing an internal resource takes two connections:

  1. Establish a connection to the internal worker:

    boundary connect -target-id=ttcp_1234567890  # Your internal worker target ID
    

    If nothing else on your local machine is listening on the worker port as defined above, you'll open a connection to the internal worker. In your session list, this connection may be marked as "pending" since no traffic is being sent yet.

  2. Once connected, you can access targets in the internal VLAN:

    boundary connect -target-id=ttcp_0987654321  # Internal resource target ID
    

This avoids HCP or enterprise license fees, but it adds FRP management and a second connection step.

Limitations and considerations

  • You need to manage FRP alongside Boundary
  • The connection process requires an extra step compared with the enterprise solution
  • Nested connections can add a small amount of latency
  • Protect the FRP token to prevent unauthorized access

Single-hop sessions are likely enough for my needs, with multi-hop being a tad over the top. Building this still gave me a better understanding of Boundary's connection flow and another way to debug its networking.

Boundary can also be integrated with Vault to broker SSH credentials. Vault can be used as an SSH certificate authority, and so perhaps in a future post I might connect the two systems.