macOS · Identity-Aware Proxy

Reach a VM with no public IP, without running a bastion host.

Ostgate is a native macOS client for Google Compute Engine over Identity-Aware Proxy: SSH terminals, RDP desktops and arbitrary TCP forwards into private instances. It runs entirely on your Mac and talks to Google's APIs as you. There is no Ostgate server in the path.

Download for macOS

Coming with the first release · Apple Silicon · macOS 26.3+ · no account with us, ever

YOUR MAC Ostgate 127.0.0.1:2222 wss://tunnel.cloudproxy.app/v4 Authorization: Bearer <token> frames ≤ 16 KiB, big-endian one socket per TCP connection Identity-Aware Proxy identity ∧ IAM policy GOOGLE CLOUD · europe-west1-b web-01 no external IP

Requirements

Hardware
Apple Silicon Mac, arm64 only
Operating system
macOS 26.3 or later
IAM role
roles/iap.tunnelResourceAccessor on the target project
Project setup
IAP TCP forwarding enabled, with firewall ingress from 35.235.240.0/20

What it does

One window for every way into a private instance.

Project and instance browser

Sign in once and browse the projects, zones and instances your Google account can already see. Multiple Google accounts run side by side as separate profiles, each with its own window, tokens and tunnels.

SSH terminal tabs

A full terminal per instance, opened over an IAP tunnel. Keys are generated on your Mac and published through OS Login or instance metadata; nothing needs a public IP.

Embedded RDP

Windows desktops open as tabs in the same window, with shared-folder redirection, clipboard sync and resize that follows the window.

Tunnel manager

Forward arbitrary TCP ports from an instance to 127.0.0.1: a database, an admin console, an internal web app. Every open tunnel shows up in one list.

And your existing ssh, when you want it

The app is the point, but it does not want to be in the way. One click in Settings writes a single block into your ~/.ssh/config that points at the app bundle, and from then on the alias resolves anywhere ssh does: a terminal, an IDE's remote session, scp, a script, with Ostgate closed.

$ ssh web-01.europe-west1-b.acme-prod.gcp
you@web-01:~$

Bastion forwards, local ports, hosts

Reach anything a chosen instance can see, inspect and clear stuck loopback listeners, and manage /etc/hosts aliases, without dropping to a shell.

Today, with gcloud

$ gcloud compute start-iap-tunnel web-01 22 \
    --local-host-port=localhost:2222 \
    --zone=europe-west1-b --project=acme-prod
# leave that running, open another terminal
$ ssh -p 2222 you@localhost

One terminal held hostage per tunnel, the port picked by hand, and nothing to tell you why it failed when it does.

With Ostgate

$ ssh web-01.europe-west1-b.acme-prod.gcp

Or double-click the instance in the browser. Ports are allocated and released for you, every tunnel is listed in one place, and a failure names the IAM role you are missing.

What this actually unlocks

Anything one VM can see, you can reach from localhost.

The tunnel ends at an instance, but it does not have to stop there. Pick a VM as a jump host and Ostgate forwards whatever that VM can route to, several ports at once, over a single SSH session through IAP. No jump box to maintain, no VPN, nothing exposed to the internet.

Open an internal-only service in your own browser.

Internal Application Load Balancers have no public address by design, and that is exactly the posture Cloud Run with ingress: internal and private GKE services are supposed to have.

IAP tunnelssh -LVPC route127.0.0.1:8443your browserbastion-01no public IP10.0.4.12:443internal ALBCloud Run · GKEingress: internal
screenshot pending
instance browser
Instance browser
screenshot pending
terminal session tab
SSH session tab

How it works

Three steps, none of which pass through a server of ours.

01

You sign in

Ostgate opens your system browser for Google's own consent screen, using a loopback redirect with PKCE. There is no embedded web view. The refresh token it receives is stored in your macOS Keychain.

02

A relay opens as you

To reach an instance, the app opens a WebSocket to Google's IAP tunnel endpoint at tunnel.cloudproxy.app, authorised with your own access token. Google checks your IAM permissions on that instance, not ours.

03

A local port is bound

The tunnel surfaces as a listener on 127.0.0.1. Every connection to it is screened by peer process before it is wired through, so another program on your Mac cannot quietly ride your tunnel.

Under the hood

The relay protocol, written down.

Google does not document the IAP TCP forwarding relay. The two open-source clients that speak it, the gcloud SDK and IAP Desktop, are the de-facto specification, and Ostgate is implemented from them. This is the reference we work from. It can change without notice, so our codec is table-driven and ignores tags it does not know.

Frame layout · DATA

Everything is big-endian. A whole message is therefore at most 6 + 16384 bytes. There is no multiplexing: one WebSocket carries exactly one local TCP connection.

TagNamePayload
1CONNECT_SUCCESS_SIDuint32 length, then the opaque session id
2RECONNECT_SUCCESS_ACKuint64 ack
4DATAuint32 length, then 1 … 16384 bytes
7ACKuint64 cumulative bytes received
10LONG_CLOSEuint32 close code, uint32 length, UTF-8 reason

Tags 0, 3, 5, 6 and 8 are unused. Flow control counts cumulative bytes, not messages: an ACK rides along with outgoing data, and a standalone one is forced once the unacknowledged count grows past the window.

Close codes, triaged

How a close code is handled is the difference between a session that silently dies and one that tells you what to fix.

  • Fatal 4001 4002 4003 4033 4047 4051 4074

    4033 is the one you will actually meet: it means you are missing roles/iap.tunnelResourceAccessor, and Ostgate says so in those words instead of reporting a closed socket.

  • Normal end of stream 1000 4009 4010

    The destination stopped reading or writing. That is a logout, not a failure, and it must not trigger a reconnect storm.

  • Retryable 1006 4000 4004 40054008 4013

    Reconnect with the session id and the byte count received so far, then replay everything the server has not acknowledged. 4004 means the token expired; the tunnel survives a refresh.

Data and permissions

Every scope Ostgate asks for, and exactly what it does with it.

Ostgate requests three OAuth scopes at sign-in. It uses them to call Google's APIs as you, from your Mac.

ScopeWhat it is used for
openid Identifies which Google account signed in, so the app can keep several accounts apart as separate profiles.
email Reads your account email address. It is used as the OS Login username when opening an SSH session, and shown in the account list so you can tell profiles apart.
.../auth/cloud-platform Calls Compute Engine, Resource Manager, OS Login and IAP TCP forwarding on your behalf: list the projects, zones and instances you already have access to; open IAP tunnels to an instance; publish your SSH public key through OS Login or instance metadata; and, for Windows RDP, write windows-keys instance metadata and read serial-port output in order to set a session password.

Where your data goes: nowhere but Google. Ostgate has no backend. Your tokens are held in the macOS Keychain on your own machine, every API call goes directly from your Mac to Google, and no usage data, telemetry or crash reporting is collected by us. Signing a profile out deletes its stored credentials from the Keychain.

The single Google Cloud scope is broad because it is the scope that authorises the Compute Engine, Resource Manager, OS Login and IAP TCP forwarding calls listed above.

Security

The parts that would be embarrassing to get wrong.

Credentials in the Keychain

Refresh tokens and key references live in the macOS Keychain, namespaced per profile. Never in preference files, never on disk in the clear, never in logs.

Keys that cannot be exported

SSH user keys are generated in the Secure Enclave as P-256 where the hardware allows it, so the private key never exists in a form any process can copy.

Loopback only, and screened

Tunnel listeners bind 127.0.0.1 and nothing else. Each accepted connection is attributed to a peer process and checked against a policy before it reaches the relay; sharing a tunnel with other programs takes an explicit, per-tunnel confirmation.

OAuth the way Google asks for it

Desktop-type client, your real system browser, loopback redirect, PKCE and a state check. No embedded web views and no custom URI schemes.

Auditable supply chain

The SSH stack resolves from owner-controlled mirrors rather than upstream tags that can move, and every bundled third-party licence ships with the app.

Will ship signed and notarized

Release builds will be signed with a Developer ID certificate and notarized by Apple, so macOS can verify the download before it ever runs.

Download

Ostgate for macOS.

A disk image for Apple Silicon Macs running macOS 26.3 or later. Drag the app to Applications, launch it, and sign in with the Google account that already has access to your projects.

Download the disk image Release notes

Coming with the first release

Apple Silicon · macOS 26.3+

Licensing

Free while in beta.

Ostgate is free to download and use during the beta. If that changes, existing installations will keep working and the terms will be updated here first.

Questions

The six things people ask first.

Does this replace gcloud?

No. It covers the connectivity part (tunnels, SSH, RDP, port forwards) with a native interface instead of a command line. Everything else you do with gcloud, you still do with gcloud, and the two can be signed in to the same account at once.

Do I still need a bastion host?

No, and that is the point. Identity-Aware Proxy carries the connection to instances that have no external IP, so there is no jump box to run, patch or pay for.

What IAM permissions do I need?

roles/iap.tunnelResourceAccessor on the instances you want to reach, plus whatever read access you already have to list projects and instances. For SSH, the usual OS Login roles. Ostgate cannot grant you anything you do not already have. It acts strictly as you.

Does it work with context-aware access?

The relay supports the mutual-TLS tunnel endpoint that context-aware access requires. Whether a given policy admits your device is decided by your organisation's access rules, not by Ostgate.

Can you see my data, my sessions or my machines?

No. There is no Ostgate server for anything to pass through. The app talks straight from your Mac to Google's APIs, and we collect no telemetry.

Where do my tokens live, and how do I get rid of them?

In the macOS Keychain, under a per-profile entry. Signing the profile out removes it. You can also revoke the app's access from your Google Account permissions page at any time, which invalidates the token regardless of what is on disk.