SSH Manager Pro

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 tunnelResourceAccessor role, 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.