Project-wide SSH keys grant their holders login access to every Linux VM in the project that does not block them, which makes them a single point of compromise. Switch to instance-level keys or, better, OS Login, and set block-project-ssh-keys on your instances.
SSH key management on Google Cloud is one of those settings that quietly accumulates risk. A key added at the project level seems convenient at first, since one key works everywhere. That convenience is also the problem. This check flags GCP projects that have project-wide SSH keys configured in their common instance metadata, because those keys give broad access that is hard to track and easy to abuse.
What this check detects
In GCP, SSH public keys can be stored in two places:
- Project-wide metadata — keys stored under the project's common instance metadata. Any VM in the project that does not explicitly opt out will accept these keys.
- Instance-level metadata — keys stored on a single VM. They only grant access to that one machine.
The compute_projectwidekeys check looks at your project's common instance metadata and fails when it finds an ssh-keys entry. In other words, it tells you that one or more SSH keys are configured to work across the entire project rather than being scoped to individual instances.
Note: GCP supports three approaches to SSH access: project-wide metadata keys, instance-level metadata keys, and OS Login. OS Login ties SSH access to IAM identities instead of raw key files and is the recommended option for most environments.
Why it matters
A project-wide SSH key is a master key. Once it exists, anyone holding the matching private key can log into every Linux VM in the project that has not explicitly blocked project keys. That breaks the principle of least privilege in a few concrete ways.
Blast radius
Imagine a developer's laptop is compromised, or a private key ends up committed to a Git repository. If that key was added at the project level, the attacker now has SSH access to your bastion hosts, your application servers, and that one forgotten staging box that still has production credentials on it. An instance-level key would have limited the damage to a single machine.
Hard to audit
Project-wide keys live in one metadata blob, and it grows over time as engineers come and go. Stale keys for former employees or decommissioned CI runners tend to linger because nobody owns the cleanup. When access is not tied to an identity, you cannot easily answer the question "who can SSH into this server right now?"
No per-user accountability
Metadata keys are just text. They do not map to IAM users, so login activity shows up as a generic Linux user rather than a named identity. That makes incident response slower and forensics murkier.
Warning: Removing project-wide keys can lock people out of instances if those people rely on them for access. Confirm that everyone who needs SSH access has an alternative path (OS Login or an instance-level key) before you delete anything.
How to fix it
There are two goals here: stop using project-wide keys, and make sure your instances ignore them going forward.
Step 1: Inspect the current project-wide keys
Before changing anything, see what is actually configured.
gcloud compute project-info describe \
--project=YOUR_PROJECT_ID \
--format="value(commonInstanceMetadata.items)"
Look for an item with the key ssh-keys. The value is a newline-separated list of entries in the form username:ssh-rsa AAAA... comment.
Step 2: Move required keys to the instance level
For any key that genuinely needs to stay, attach it to the specific instances that need it instead.
# Add a single key to one instance
gcloud compute instances add-metadata INSTANCE_NAME \
--zone=ZONE \
--metadata-from-file ssh-keys=instance-keys.txt
The file instance-keys.txt should contain only the keys that instance needs, one per line.
Step 3: Block project-wide keys on your instances
Set the block-project-ssh-keys metadata flag to true so the instance ignores project metadata keys entirely, even if they exist.
gcloud compute instances add-metadata INSTANCE_NAME \
--zone=ZONE \
--metadata block-project-ssh-keys=TRUE
To apply this across many instances at once, loop over them:
for instance in $(gcloud compute instances list --format="value(name,zone)" | tr '\t' ','); do
name=$(echo "$instance" | cut -d',' -f1)
zone=$(echo "$instance" | cut -d',' -f2)
gcloud compute instances add-metadata "$name" \
--zone="$zone" \
--metadata block-project-ssh-keys=TRUE
done
Step 4: Remove the project-wide keys
Danger: This command deletes all project-wide SSH keys at once. Anyone who relies solely on a project-wide key, and whose instances do not block project keys, will lose SSH access immediately. Make sure Steps 2 and 3 are done first.
gcloud compute project-info remove-metadata \
--project=YOUR_PROJECT_ID \
--keys=ssh-keys
The better path: enable OS Login
Rather than juggling instance-level keys, enable OS Login at the project level. OS Login binds SSH access to IAM, so you grant access with roles like roles/compute.osLogin and revoke it the same way you revoke any other IAM permission.
# Enable OS Login for the whole project
gcloud compute project-info add-metadata \
--project=YOUR_PROJECT_ID \
--metadata enable-oslogin=TRUE
# Grant a user SSH access
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="user:[email protected]" \
--role="roles/compute.osLogin"
Note: When OS Login is enabled, GCP ignores SSH keys in both project and instance metadata. Access is controlled entirely through IAM, which is why it sidesteps the whole metadata-key problem.
How to prevent it from happening again
Fixing the project once is not enough. Engineers will keep reaching for project-wide keys unless you put guardrails in place.
Enforce with an Organization Policy
GCP has a built-in constraint that requires OS Login on all new and existing instances. Apply it at the org or folder level so it covers every project.
gcloud resource-manager org-policies enable-enforce \
compute.requireOsLogin \
--organization=YOUR_ORG_ID
With this in place, metadata SSH keys stop working entirely, which removes the temptation to add project-wide keys in the first place.
Codify instance config in Terraform
If you provision VMs through Terraform, set the blocking flag and OS Login in your module so it is correct by default.
resource "google_compute_instance" "app" {
name = "app-server"
machine_type = "e2-medium"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "debian-cloud/debian-12"
}
}
network_interface {
network = "default"
}
metadata = {
enable-oslogin = "TRUE"
block-project-ssh-keys = "TRUE"
}
}
Tip: Add a policy-as-code check to your pipeline so a pull request that introduces project metadata ssh-keys fails CI. With OPA Conftest or terraform-compliance, you can block the merge before the key ever reaches GCP.
Gate it in CI/CD
A simple Conftest policy can catch project-wide keys in a Terraform plan:
# Generate a plan in JSON and test it
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_compute_project_metadata"
resource.change.after.metadata["ssh-keys"]
msg := "Project-wide SSH keys are not allowed. Use OS Login or instance-level keys."
}
Best practices
- Prefer OS Login over metadata keys. It ties access to IAM identities, supports two-factor authentication, and gives you per-user audit logs in Cloud Audit Logs.
- Never store private keys in source control. Scan repos with a tool like Gitleaks so a leaked key cannot quietly hand out project-wide access.
- Use short-lived access. Combine OS Login with IAM Conditions to grant time-bound SSH access rather than standing permissions.
- Rotate and review. If you must keep metadata keys somewhere, schedule a regular review to remove stale entries for departed users and retired automation.
- Default to deny. Set
block-project-ssh-keys=TRUEon every instance image and module so a forgotten project key can never grant access by accident.
Project-wide SSH keys trade a small amount of setup time for a large, hard-to-see security liability. Move to OS Login, block project keys at the instance level, and enforce both with org policy. Once that is in place, this check stays green without anyone having to think about it.

