【GCP/Terraform】外部IPアドレスを持たないVMインスタンスにCloud IAPを利用してSSH接続する

6 min読了の目安(約5600字TECH技術記事

TL;DR

こんな人向け

  • Cloud IAP について知りたい
  • GCPのVPCとかsubnetとかFirewallとかよくわかんない
  • GCPのVMインスタンスに踏み台サーバーなしで接続したい
  • terraformでGCPさわりたい

解説

CloudIAPの仕組み

  • こちらの図がわかりやすいです
  • GAEやGCEなどで動作するアプリケーションへのアクセスを制御するためのID認証プロキシサービスです
  • なので、VMインスタンスに外部IPアドレスがなくてもSSH接続できるようになります

GCPプロジェクトのCloudIAPのAPIを許可する

$ gcloud services enable iap.googleapis.com

ネットワークとサブネットを作成する

VMインスタンスを設置するためのVPCネットワークとサブネットを作成します。

resource "google_compute_network" "my_network" {
  name                    = "my-network"
  # リージョンごとにサブネットを自動で作成してくれます。今回は使わないのでfalseにします。
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "my_subnetwork" {
  name   = "my-subnetwork"
  region = "asia-northeast1"

  # サブネットで使用したい内部IPアドレスの範囲を指定する
  ip_cidr_range = "10.0.0.0/16"
  network       = google_compute_network.my_network.self_link

  # CloudLoggingにFlowLogログを出力したい場合は設定する
  log_config {
    metadata = "INCLUDE_ALL_METADATA"
  }
}

サブネット内にVMインスタンスを作成する

VMインスタンスに付与するservice accountを作成することをGoogleが推奨しているので、service accountも定義します。
外部IPアドレスは付与しないので、access_configオプションはつけません。

resource "google_service_account" "my_service_account_for_bastion" {
  account_id   = "my-service-account-for-bastion"
  display_name = "My Service Account For Bastion"
}

resource "google_compute_instance" "bastion" {
  # 好きな名前、マシンタイプ、ゾーンに作成してください
  name         = "bastion"
  machine_type = "f1-micro"
  zone         = "asia-northeast1-a"

  # Firewallでタグごとにルールを設定したいので、VMインスタンスにタグを設定しておく
  tags = ["bastion-tag"]

  # すきなディスクを選んでください
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-10"
    }
  }

  network_interface {
    # 自分で定義したsubnetwork内にインスタンスを起動する
    network    = google_compute_network.my_network.name
    subnetwork = google_compute_subnetwork.my_subnetwork.name
    # 外部IPアドレスを割り振る方法は次の2種類ある。静的外部IPアドレス、エフェメラル外部IPアドレス
    # 今回は外部IPアドレスは割り振らないので、access_configはコメントアウトしておく
    # access_config {}
  }

  service_account {
    email  = google_service_account.my_service_account_for_bastion.email
    scopes = ["cloud-platform"]
  }

  scheduling {
    # 料金を抑えるためにプリエンプティブルにしておく
    preemptible = true
    # プリエンプティブルの場合は下のオプションが必須
    automatic_restart = false
  }
}

Firewallルールを設定する

CloudIAPからVMインスタンスへの接続を許可するために、Firewallルールを設定する必要があります。
許可すべきIPアドレス範囲、プロトコル、ポートはこちらの公式ドキュメントに記載してあります。

https://cloud.google.com/iap/docs/using-tcp-forwarding#create-firewall-rule
resource "google_compute_firewall" "my_network" {
  name    = "my-network-firewall"
  network = google_compute_network.my_network.name

  direction = "INGRESS"

  # 通信を許可するprotocolとportを指定する
  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  # 対象のVMインスタンスのタグを指定する
  target_tags = ["bastion-tag"]
  # Cloud IAPのバックエンドIPアドレス範囲を指定する
  # https://cloud.google.com/iap/docs/using-tcp-forwarding#create-firewall-rule
  source_ranges = ["35.235.240.0/20"]

  # CloudLoggingにFlowLogログを出力したい場合は設定する
  log_config {
    metadata = "INCLUDE_ALL_METADATA"
  }
}

SSH接続するユーザーにRoleを付与する

SSH接続するのに必要なRoleは以下になります

resource "google_project_iam_binding" "access_user" {
  role = google_project_iam_custom_role.my_custom_role.id

  members = [
    "user:許可したいGoogleユーザーのメールアドレス",
  ]
}

resource "google_project_iam_custom_role" "my_custom_role" {
  role_id = "MyCustomRole"
  title   = "My Custom Role"

  permissions = [
    # VMインスタンスにSSH接続するのに必要な権限
    "compute.projects.get",
    "compute.instances.get",
    "compute.instances.setMetadata",
    "iam.serviceAccounts.actAs",
    # IAPに必要な権限
    "iap.tunnelInstances.accessViaIAP",
  ]
}

SSH接続する

--tunnel-through-iapのオプションを指定するとCloudIAPを利用しSSH接続できます。

$ gcloud beta compute ssh --zone $ZONE $VM_INSTANCE_NAME --project $GCP_PROJECT_ID --tunnel-through-iap

Cloud IAPを有効化し、アクセス許可している場合は、Cloud ConsoleのSSHボタンからでもログインできるようになります。

CloudIAPのメリット・デメリット

  • メリット
    • 外部IPアドレスのないVMインスタンに接続できるのでメンテが楽になる
    • 踏み台用途などのVMインスタンスに外部IPアドレスを持たせなくても良くなる
  • デメリット
    • どこのネットワークからでもGoogleUserとしての認証さえあればSSH接続できるようになってしまう

実際にリソースを作ってみる

準備

作成してSSH接続してみる

#-------------------
# リポジトリをクローンする
#-------------------
$ git clone https://github.com/nekoshita/gcp-ssh-to-vm-instance-using-iap-example
$ cd gcp-ssh-to-vm-instance-using-iap-example

#-------------------
# terraform applyしてリソースを作成する
#-------------------
$ export GCP_PROJECT_ID="your-gcp-project-id"
$ export GCS_BUCKET_NAME="your-gcs-bucket-name"
$ export USER_MAIL="your-google-user-mail-to-allow-ssh-acccess@gmail.com"
$ bin/apply $GCP_PROJECT_ID $GCS_BUCKET_NAME $USER_MAIL

#-------------------
# SSH接続する
#-------------------
$ export ZONE="asia-northeast1-a"
$ export VM_INSTANCE_NAME="bastion"
# GCPのprojectを作成したgoogleアカウントでログインする(すでにログインしてる場合は不要)
$ gcloud auth login
$ gcloud beta compute ssh --zone $ZONE $VM_INSTANCE_NAME --project $GCP_PROJECT_ID --tunnel-through-iap

#-------------------
# 最後にリソースの削除をお忘れなく!
#-------------------
$ export GCP_PROJECT_ID="your-gcp-project-id"
$ export GCS_BUCKET_NAME="your-gcs-bucket-name"
$ export USER_MAIL="your-google-user-mail-to-allow-ssh-acccess@gmail.com"
$ bin/destroy $GCP_PROJECT_ID $GCS_BUCKET_NAME $USER_MAIL

最後に

公式ドキュメントを元に理解したつもりですが、私の認識が間違っていたらご指摘いただけると嬉しいです!