Tunnels
Control who can visit a tunnel
Let anyone, workspace members, API keys or IP ranges reach each path of a tunnel, from the dashboard, the CLI or Aspire.
Choose who may visit each path
#Visitor access rules are a preview, available in workspaces Groundhog has turned them on for. Without them, anyone with a tunnel’s address can reach it.
Open a tunnel and choose the Access tab. Everywhere else applies to every path no rule matches. Each path rule, such as /webhooks, applies to that path and everything under it, and the longest matching path wins. A rule lets a visitor in when any of its choices fits:
| Choice | Who gets in | Use it for |
|---|---|---|
| Anyone | Everyone with the address. It can’t be combined with the others. | Public pages, and webhooks your app verifies by signature. |
| Workspace members | People who sign in to Groundhog in a browser and belong to this workspace. | Your team trying the app. Scripts can’t sign in this way. |
| API keys | Requests that send one of the tunnel’s API keys in the X-Groundhog-Api-Key header. | CI jobs, scripts, test devices and partner services: anything that can add a header but can’t sign in. |
| IP ranges | Requests from an address in the tunnel’s IP ranges, such as 203.0.113.0/24. | Callers with fixed addresses, such as an office network or a webhook provider that publishes its ranges. |
For example, a shop under development might use:
- Everywhere else: Workspace members, so only your team can browse it.
- /webhooks: Anyone, because the payment provider can’t sign in and your app checks its signatures.
- /api: API keys, so a CI job can run tests against it.
Workspace owners, and whoever created a tunnel, can change its rules. Other members see them.
Sign in as a workspace member
#A browser that opens a members-only path goes to the Groundhog sign-in, then back to the page it asked for. The tunnel keeps the visitor signed in for 12 hours in its own cookie, which your app never receives. Removing someone from the workspace stops their access within a minute.
Scripts and API clients aren’t sent to sign in: they get 401 Unauthorized. Give them an API key instead.
Create and revoke API keys
#An API key lets a caller that can’t sign in, such as a CI job or a partner service, reach the paths whose rules allow API keys. It belongs to the tunnel, not to a person, so give each caller its own key and name it after the caller; you can then revoke one without breaking the others. It isn’t the tunnel’s connection key, which only your client uses to connect.
On the Access tab, under API keys, enter a name such as ci and choose Create. Copy the key straight away: it is shown once, and Groundhog keeps only a hash. A tunnel can have up to 10.
Send it with each request, for example:
curl -H "X-Groundhog-Api-Key: <key>" https://shop-dev.groundhog.unifinity.com/api/healthGroundhog removes the header before the request reaches your app. Revoking a key takes effect at once; you can’t revoke the last key while a rule still allows API keys.
Keep rules in the CLI or Aspire
#Rules can live with your code instead. The client sends them each time it connects, and they replace the stored rules while Set by is Agent:
groundhog host shop-dev --port 5173 --allow members --path /webhooks=anyoneIn an Aspire AppHost, pass the rules to WithGroundhogTunnel; see the Aspire integration reference.
Saving in the dashboard makes the dashboard the source of the rules, and a client that sends other rules is then refused with a message saying so. Choose Agent to hand them back.
Replace an API key without downtime
#To replace an API key without refusing a caller, the tunnel must accept the old and the new key until every caller sends the new one.
For a key created on the Access tab, create the new key, give it to the caller, then revoke the old key once the caller uses the new one.
When the CLI or Aspire declares the API keys, each connection replaces the keys the agent declared before. So declare both keys while callers switch, then declare only the new one. With the CLI, keep each key in its own environment variable:
$env:CI_API_KEY = Read-Host "Current API key" -MaskInput
$env:CI_API_KEY_NEXT = Read-Host "New API key" -MaskInput
groundhog host shop-dev --port 5173 --allow members --path /api=apikeys --api-key-env CI_API_KEY --api-key-env CI_API_KEY_NEXTCI_API_KEY holds the key callers send today and CI_API_KEY_NEXT holds its replacement. The agent sends only their SHA-256 hashes. Once every caller sends the new key, set CI_API_KEY to the new key and host again without --api-key-env CI_API_KEY_NEXT.
In an Aspire AppHost, add a second secret parameter for the new key:
var builder = DistributedApplication.CreateBuilder(args);
// apiKey holds the API key callers send today; nextApiKey holds its replacement.
var apiKey = builder.AddParameter("shop-dev-api-key", secret: true);
var nextApiKey = builder.AddParameter("shop-dev-api-key-next", secret: true);
var web = builder.AddProject<Projects.Web>("web")
.WithGroundhogTunnel("shop-dev", access => access
.Allow(VisitorAllow.Members)
.Path("/api", VisitorAllow.ApiKeys)
.ApiKey(apiKey)
.ApiKey(nextApiKey));
builder.Build().Run();apiKey reads Parameters:shop-dev-api-key and nextApiKey reads Parameters:shop-dev-api-key-next from the AppHost’s user secrets. Once every caller sends the new key, save it as Parameters:shop-dev-api-key, remove nextApiKey and restart the AppHost.