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. Choose how you authenticate
Option A: Browser Login (Google Account) — Recommended
Sign in with your standard Google account directly from the app. No JSON keys to download, manage, or transfer to your phone.
- Ensure your Google user account has the required IAM roles on the project:
gcloud projects add-iam-policy-binding YOUR_PROJECT \ --member "user:YOUR_EMAIL@gmail.com" \ --role roles/iap.tunnelResourceAccessor gcloud projects add-iam-policy-binding YOUR_PROJECT \ --member "user:YOUR_EMAIL@gmail.com" \ --role roles/compute.osLogin - In the app, open Settings → Google Cloud, tap the Browser Login (Google) tab, enter your Project ID, and tap Sign in with Google Account.
- Complete authorization in your browser. The loopback server completes sign-in automatically, or you can switch to Manual Code Entry to paste the redirect URL or authorization code.
Option B: Service Account (JSON)
If you prefer a dedicated machine identity or need automated service account authentication:
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
gcloud iam service-accounts keys create key.json \
--iam-account sshmanager-app@YOUR_PROJECT.iam.gserviceaccount.com
In Settings → Google Cloud, select Service Account (JSON) and paste or import the key.json 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.
Deploy with Terraform / OpenTofu (Zero Public IP VM)
This complete OpenTofu / Terraform blueprint provisions a completely private Debian VM with no external IP, a firewall rule allowing only Google's proxy range, and a service account with minimal IAP and OS Login permissions:
# Network and private subnet
resource "google_compute_network" "vpc" {
name = "sshmanager-net"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "subnet" {
name = "sshmanager-subnet"
ip_cidr_range = "10.10.0.0/24"
region = "us-central1"
network = google_compute_network.vpc.id
}
# Firewall: allow SSH ONLY from Google's IAP proxy range
resource "google_compute_firewall" "iap_ssh" {
name = "allow-iap-ssh"
network = google_compute_network.vpc.name
source_ranges = ["35.235.240.0/20"]
allow {
protocol = "tcp"
ports = ["22"]
}
}
# VM with NO public IP: network_interface has no access_config block
resource "google_compute_instance" "vm" {
name = "sshmanager-vm"
machine_type = "e2-micro"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "debian-cloud/debian-12"
}
}
network_interface {
subnetwork = google_compute_subnetwork.subnet.id
}
metadata = {
enable-oslogin = "TRUE"
}
}
# Service account with only two roles: tunnel and OS login
resource "google_service_account" "app" {
account_id = "sshmanager-app"
display_name = "SSH Manager IAP Service Account"
}
resource "google_project_iam_member" "tunnel" {
project = var.project_id
role = "roles/iap.tunnelResourceAccessor"
member = "serviceAccount:${google_service_account.app.email}"
}
resource "google_project_iam_member" "oslogin" {
project = var.project_id
role = "roles/compute.osLogin"
member = "serviceAccount:${google_service_account.app.email}"
}
resource "google_service_account_key" "key" {
service_account_id = google_service_account.app.name
}
output "instance_name" {
value = google_compute_instance.vm.name
}
output "zone" {
value = google_compute_instance.vm.zone
}
output "service_account_key_json" {
value = base64decode(google_service_account_key.key.private_key)
sensitive = true
}
Apply the configuration and extract the sensitive service account key directly to your clipboard:
tofu init
tofu apply -var project_id=YOUR_PROJECT
tofu output -raw service_account_key_json | pbcopy
Paste the JSON key into Settings → Google Cloud in SSH Manager Pro, add a host with the instance name and zone, and leave the username blank — OS Login assigns your username automatically.
Run tofu destroy -var project_id=YOUR_PROJECT when you want to tear down the resources.
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 or user 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.