Google Cloud setup
IAP reaches instances from Google's own network, so the instance needs no public IP — but it does need a firewall rule for the proxy range, and OS Login enabled.
1. Enable the APIs
gcloud services enable compute.googleapis.com iap.googleapis.com \
oslogin.googleapis.com --project YOUR_PROJECT
2. Create a service account
gcloud iam service-accounts create sshmanager-app \
--display-name "SSH Manager app" --project YOUR_PROJECT
gcloud projects add-iam-policy-binding YOUR_PROJECT \
--member "serviceAccount:sshmanager-app@YOUR_PROJECT.iam.gserviceaccount.com" \
--role roles/iap.tunnelResourceAccessor
gcloud projects add-iam-policy-binding YOUR_PROJECT \
--member "serviceAccount:sshmanager-app@YOUR_PROJECT.iam.gserviceaccount.com" \
--role roles/compute.osLogin
Two roles, deliberately. Neither allows changing, stopping or reading the instance — one opens a tunnel, the other publishes a login key.
3. Create a key
gcloud iam service-accounts keys create key.json \
--iam-account sshmanager-app@YOUR_PROJECT.iam.gserviceaccount.com
Paste the contents into Settings → Google Cloud. The console offers several JSON downloads from nearby screens — OAuth client secrets and API keys among them — and only a service account key works. The app checks and tells you if it is the wrong file.
4. Allow the proxy range
IAP connects from a fixed range, and only that range needs access:
gcloud compute firewall-rules create allow-iap-ssh \
--network YOUR_NETWORK --allow tcp:22 \
--source-ranges 35.235.240.0/20 --project YOUR_PROJECT
If this is the only SSH rule, nothing on the public internet can reach port 22 at all — which is the point.
5. Enable OS Login
gcloud compute instances add-metadata YOUR_INSTANCE \
--metadata enable-oslogin=TRUE --zone YOUR_ZONE --project YOUR_PROJECT
Without this the instance only honours keys baked into metadata, and the app's per-connection key is ignored.
6. Add a host
Choose Google Cloud, enter the instance name and zone. Leave the username blank — OS Login assigns one and the app reads it back.
A ready-made test environment
The repository includes OpenTofu configuration that builds a VM with no external IP, the firewall rule, the service account and its roles:
cd terraform/gcp-test
tofu init
tofu apply -var project_id=YOUR_PROJECT
tofu output -raw service_account_key_json
Run tofu destroy when finished.
If it fails
- 403 opening the tunnel
- Almost always the missing
tunnelResourceAccessorrole, rather than a wrong instance name. - "OS Login returned no POSIX account"
- The service account is missing
roles/compute.osLogin. - Tunnel opens, SSH times out
- The firewall rule for 35.235.240.0/20 is missing or on the wrong network.