32

So I have a terraform script that creates instances in Google Cloud Platform, I want to be able to have my terraform script also add my ssh key to the instances I create so that I can provision them through ssh. Here is my current terraform script.

#PROVIDER INFO
provider "google" {
  credentials = "${file("account.json")}"
  project     = "myProject"
  region      = "us-central1"
}


#MAKING CONSUL SERVERS
resource "google_compute_instance" "default" {
  count    =  3
  name     =  "a-consul${count.index}"
  machine_type = "n1-standard-1"
  zone         = "us-central1-a"

  disk {
    image = "ubuntu-1404-trusty-v20160627"
  }

  # Local SSD disk
  disk {
    type    = "local-ssd"
    scratch = true
  }

  network_interface {
    network = "myNetwork"
    access_config {}
  }
}

What do I have to add to this to have my terraform script add my ssh key /Users/myUsername/.ssh/id_rsa.pub?

Saurabh
  • 5,176
  • 4
  • 32
  • 46
Alex Cohen
  • 5,596
  • 16
  • 54
  • 104

10 Answers10

48

I think something like this should work:

  metadata = {
    ssh-keys = "${var.gce_ssh_user}:${file(var.gce_ssh_pub_key_file)}"
  }

https://cloud.google.com/compute/docs/instances/adding-removing-ssh-keys describes the metadata mechanism, and I found this example at https://github.com/hashicorp/terraform/issues/6678

Chris Stryczynski
  • 30,145
  • 48
  • 175
  • 286
mblakele
  • 7,782
  • 27
  • 45
  • Hey I've got a quick followup to this question, it isn't related to adding ssh keys, but it may use the `metadata` function of gcp terraform instances. I want to have terraform add the `--can-ip-forward` tag to my instances, how can I do that? – Alex Cohen Aug 05 '16 at 19:02
  • 1
    Looks like you just add `can_ip_forward = true` to the resource: https://github.com/iostat/mesos-fun/blob/bb08bc4866a1ba4e535cbff4eba6611f1d3838b4/terraform/3-slaves.tf and https://www.terraform.io/docs/providers/google/r/compute_instance.html – mblakele Aug 08 '16 at 19:00
  • 7
    Hi, be careful, `sshKeys` is now deprecated and the current syntax is `ssh-keys` – Jean-Bernard Jansen Mar 06 '19 at 21:52
  • 1
    While using this method, I am able to create instance and login to it but if I go to GCP console and Try to edit instance it shows error in ssh-keys , I think thats probably coz at the end of the key user-name should be present Error : The SSH key is in the wrong format – Abhilasha May 27 '19 at 05:46
  • 1
    Be careful to write `ssh-keys` not `ssh_keys` they both are accepted but they do different hings. You most likley want `ssh-keys` not `ssh_keys`. `ssh_keys` installs the key of your cloud account, and `ssh-keys` installs the key from the specified file. – Christian Hujer Nov 29 '19 at 20:21
  • Why do we need to add the `username` for this? just can't pass the `ssh` key itself without `username` ? – Jananath Banuka Jul 22 '20 at 11:37
  • can you tell me how you got private key path –  Jul 01 '21 at 11:45
18

Just for the record. As of 0.12 it seems the block should look like:

resource "google_compute_instance" "default" {
  # ...

  metadata = {
    ssh-keys = join("\n", [for user, key in var.ssh_keys : "${user}:${key}"])
  }

  # ...
}

(Note = sign after metadata token and ssh-keys vs. sshKeys).

0x416e746f6e
  • 9,872
  • 5
  • 40
  • 68
  • This will work when you supplant "${user}:${key}" for "${var.user}:${var.key}", although I would argue passing a list of keys is overkill - we should be trying to create immutable infrastructure which no one ever needs to ssh into. – volvox Jun 12 '20 at 22:02
  • 1
    Why do we need to add the `username` for this? just can't pass the `ssh` key itself without `username` ? – Jananath Banuka Jul 22 '20 at 11:36
  • @JananathBanuka this is required by GCP because the default username may vary with the image you use (ubuntu, debian, centos, rhel etc ...) – webofmars Jun 28 '21 at 12:23
7

You can use the following

metadata = {
  ssh-keys = "username:${file("username.pub")}"
}

I was struggling to create an instance with the ssh key using terraform & this answer is tested & working as well.

mariux
  • 2,807
  • 12
  • 21
sambit
  • 339
  • 4
  • 12
5

Here is tested one.

  metadata {
    sshKeys = "${var.ssh_user}:${var.ssh_key} \n${var.ssh_user1}:${var.ssh_key1}"
}
4b0
  • 21,981
  • 30
  • 95
  • 142
tanaji yadav
  • 51
  • 1
  • 1
5

If you want multiple keys you can use heredoc like this

  metadata = {
    "ssh-keys" = <<EOT
<user>:<key>
<user>:<key>
EOT
  }

I stayed with the weird formatting here in the post that terraform fmt provided me.

hashier
  • 4,670
  • 1
  • 28
  • 41
3

Just updating for multiple keys in Terraform v0.15.4:

metadata = {
    ssh-keys = join("\n", [for key in var.ssh_keys : "${key.user}:${key.publickey}"])
}

And accoring variables:

variable "ssh_keys" {
  type = list(object({
    publickey = string
    user = string
  }))
  description = "list of public ssh keys that have access to the VM"
  default = [
      {
        user = "username"
        publickey = "ssh-rsa yourkeyabc username@PC"
      }
  ]
}
Daniel Habenicht
  • 2,133
  • 1
  • 15
  • 36
  • can you please tell me how you got private key path –  Jul 01 '21 at 11:44
  • @RohanKumarJangid which private key path? I am not handling any private keys. – Daniel Habenicht Jul 01 '21 at 11:47
  • in order to connection with gcp machine for that there is a required parameter in private key so i dont understood how can i use this should i have to create or what i have to do for proper question please refer https://stackoverflow.com/questions/68206325/unable-to-run-metadata-startup-script-in-terraform –  Jul 01 '21 at 11:51
2

I have below working for me: for all vms a single ssh key

resource "google_compute_project_metadata" "my_ssh_key" {
  metadata = {
    ssh-keys = <<EOF
      terakey:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICqaF7TqtimTUtqLdZIspKjuTXXXXnkbW7N9TQBPXazu terakey
      
    EOF
  }
}
Arnie97
  • 1,020
  • 7
  • 19
Abdul Fahad
  • 264
  • 1
  • 5
0

I tested below ways of injecting ssh public key to a google compute instance and its working for me.

  metadata = {
    ssh-keys = "${var.ssh_user}:${file("./gcp_instance_ssh_key.pub")}"
  OR 
    ssh-keys  = "${var.ssh_user}:${file(var.public_key_path)}"

  OR
    ssh-keys  = "${var.ssh_user}:${file("${var.public_key_path}")}"
   
  }

variable "public_key_path" {
    default = "./gcp_instance_ssh_key.pub"   ##public key with path
}

Please note to use ssh-keys instead of ssh_keys (with underscore)

surya
  • 1
  • 2
0

First, you'll need a compute instance:

resource "google_compute_instance" "website_server" {
  name                      = "webserver"
  description               = "Web Server"
  machine_type              = "f1-micro"
  allow_stopping_for_update = true
  deletion_protection       = false

  tags = ["webserver-instance"]

  shielded_instance_config {
    enable_secure_boot          = true
    enable_vtpm                 = true
    enable_integrity_monitoring = true
  }

  scheduling {
    provisioning_model  = "STANDARD"
    on_host_maintenance = "TERMINATE"
    automatic_restart   = true
  }

  boot_disk {
    mode        = "READ_WRITE"
    auto_delete = true
    initialize_params {
      image = "ubuntu-minimal-2204-jammy-v20220816"
      type  = "pd-balanced"
    }
  }

  network_interface {
    network = "default"

    access_config {
      network_tier = "PREMIUM"
    }
  }

  metadata = {
    ssh-keys               = "${var.ssh_user}:${local_file.public_key.content}"
    block-project-ssh-keys = true
  }

  labels = {
    terraform = "true"
    purpose   = "host-static-files"
  }

  service_account {
    # Custom service account with restricted permissions
    email  = data.google_service_account.myaccount.email
    scopes = ["compute-rw"]
  }

}

Note that ssh-keys field in the metadata needs the public key data in "Authorized Keys" format, i.e., the open SSH public key. This is similar to doing a pbcopy < ~/.ssh/id_ed25519.pub

You'll need a firewall rule to allow SSH on (default) port 22:

resource "google_compute_firewall" "webserver_ssh" {
  name    = "webserver-firewall"
  network = "default"

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  target_tags   = ["webserver-instance"]
  source_ranges = ["0.0.0.0/0"]
}

Your public and private keys can be ephemeral to make things more seamless:

resource "tls_private_key" "webserver_access" {
  algorithm = "ED25519"
}

resource "local_file" "public_key" {
  filename        = "server_public_openssh"
  content         = trimspace(tls_private_key.webserver_access.public_key_openssh)
  file_permission = "0400"
}

resource "local_sensitive_file" "private_key" {
  filename = "server_private_openssh"
  # IMPORTANT: Newline is required at end of open SSH private key file
  content         = tls_private_key.webserver_access.private_key_openssh
  file_permission = "0400"
}

And finally, to login you would need a connection string based on:

output "instance_connection_string" {
  description = "Command to connect to the compute instance"
  value       = "ssh -i ${local_sensitive_file.private_key.filename} ${var.ssh_user}@${google_compute_instance.website_server.network_interface.0.access_config.0.nat_ip} ${var.host_check} ${var.ignore_known_hosts}"
  sensitive   = false
}

where the variable file could look like:

variable "ssh_user" {
  type        = string
  description = "SSH user for compute instance"
  default     = "myusername"
  sensitive   = false
}

variable "host_check" {
  type        = string
  description = "Dont add private key to known_hosts"
  default     = "-o StrictHostKeyChecking=no"
  sensitive   = false
}

variable "ignore_known_hosts" {
  type        = string
  description = "Ignore (many) keys stored in the ssh-agent; use explicitly declared keys"
  default     = "-o IdentitiesOnly=yes"
  sensitive   = false
}

Saurabh
  • 5,176
  • 4
  • 32
  • 46
0

One thing to know is that if there's higher level project, you may need to add to your metadata an option to disable os login.

metadata = {
    "enable-oslogin" = false
    "ssh-keys"       = ...
}

Only then authorized_keys file will be created

Also you may need to add "can_ip_forward = true" option to enable external traffic.

MTR
  • 1
  • 1