AnsibleでCloudIAPを使ってSSH接続する

AvatarPosted by

こんにちは。SREの川野です。
今回は、AnsibleでVPNを構築せずにCloudIAPを使ってセキュアにSSHするための方法についてまとめました。

CloudIAPとは

GCPが提供しているサービスの1つで、主な特徴としてはVMやアプリケーションにセキュアに接続するための機能を持っています。

Identity-Aware Proxy(IAP)  |  Google Cloud

プライベートネットワーク上で起動したVMに対して踏み台サーバを使って接続するといったようなVPNの構築の実装工数と比べると比較的手軽に認証・認可の仕組みを取り入れることができます。

また、Cloud Identity and Access Management(IAM)によってGoogleアカウントのアクセス制御を一元管理することができます。

経緯について

今回、なぜAnsibleでCloudIAPを使ってSSH接続をしようと考えたのかの経緯について簡単にまとめます。

VMインスタンスへ安全に接続するための構成としていくつか選択肢(VM インスタンスへの安全な接続  |  Compute Engine ドキュメント  |  Google Cloud )があるのですが、その中で最初に考えていたのが以下の2点になります。

  1. プライベートネットワーク上にあるVMに踏み台サーバーを経由
  2. グローバルIPを割り当てて、ファイアウォールで制限

はじめは、以下の理由から踏み台構成が良いのではないかと考えられました。

  • グローバルIPにすると常に攻撃されるリスクを持つ
  • 万が一、攻撃された場合の損失が大きい
  • ファイアウォールだけでも以下の情報が保護されないらしい(これが実際にどう使われて攻撃されるのかまではわからない)

トラフィックを特定のソース IP に制限しても、ログイン認証情報、リソースやファイルを作成または破棄するコマンド、ログなどの機密情報は保護されません

https://cloud.google.com/solutions/connecting-securely?hl=ja

しかし、以下の理由により踏み台サーバーの構成は今回はやめようとなりました。

「グローバルIPにすると常に攻撃されるリスクを持つ」について

1の場合は公開するポートが踏み台サーバーの22番だけ、2の場合は公開するポートはDBに接続するポートだけというような違いしかなく、仮にSSHに脆弱性があれば認証不要で接続されbash_historyなどを見られたらDBに接続されてしまう可能性が考えられます。

■「万が一、攻撃された場合の損失が大きい」について

安全性の高い構成がどこまで本当に必要なのか?ということを考えると過去の運用経験や現状のスケジュール感から実装コストに見合ってないと判断しました。


そこで2でやっていこうと考えてたところで、CloudIAPを使ってSSH接続できる方法がわかったのでそちらを今回やってみようという風になりました。

CloudIAPでSSH接続する際はVMはグローバルIPを持たないで良いので、安全性の高い構成を低い実装コストでつくることができます。

AnsibleでCloudIAPを使ってSSH接続するための設定

CloudIAP有効化

CloudIAPを使用するためにAPIの使用を有効化する必要があるので、以下ドキュメントを参考に実施します。
IAP の有効化 | Google アカウントによるアクセスの管理  |  Identity-Aware Proxy  |  Google Cloud

Googleアカウントの認証

SSH接続するために使用するGoogleアカウントの認証については、以下のコマンドを実行します。

gcloud auth login

実行するとブラウザが起動するので、任意のGoogleアカウントでログインして認証を許可することで、Cloud SDK(gcloudコマンド)の実行に必要な認証情報をローカルに自動登録し、その認証情報を使ってGCPにアクセスできるようになります。

gcloud auth login  |  Cloud SDK のドキュメント  |  Google Cloud

ファイアウォールルールの設定

以下ドキュメントを参考にIAP が TCP 転送に使用する35.235.240.0/20 からの上り(内向き)トラフィックを許可します。

ファイアウォール ルールの作成 | TCP 転送での IAP の使用  |  Identity-Aware Proxy  |  Google Cloud

CloudIAPを使ってVMにSSH接続

VMにCloudIAPを使ってSSHするときは以下のコマンドを実行します。(CloudコンソールのVMインスタンスのSSHボタンからする接続する際にもCloudIAPが使われるようです)

gcloud compute ssh --tunnel-through-iap <ホスト名>

SSH 接続のトンネリング | TCP 転送での IAP の使用  |  Identity-Aware Proxy  |  Google Cloud

Ansibleでダイナミックインベントリの設定

gcp_computeプラグインを使ってGCP上のVMのインベントリホストを取得します。

Ansibleのinventoryに以下のようにgcp.ymlを作成します。

plugin: gcp_compute
projects:
  - <GCPプロジェクト名>
auth_kind: application
groups:
  gce_instance: yes

こうすることで、inventory/group_varsgce_instance.ymlまたは、gce_instance/vars.ymlといった形でインベントリのグループ変数を定義することができるようになります。

以下のコマンドでインベントリホストを取得できていることを確認できます。

$ ansible-inventory --graph
@all:
  |--@gce_instance:
  |  |--<ホスト名1>
  |  |--<ホスト名2>
  |  |--<ホスト名3>
  |--@ungrouped:

インベントリのグループ変数を定義

CloudIAPを使ってSSH接続するためのansible_ssh_common_argsを以下のように定義します。

ansible_ssh_common_args: "-o ProxyCommand='gcloud compute start-iap-tunnel %h %p --listen-on-stdin --zone {{ vars.zone }} --project {{ vars.project }}'"

ansible_ssh_common_args でAnsibleでホストにSSH接続するときのProxyCommandを設定できます。

--listen-on-stdinというオプションを設定しているのですが、これはコマンドヘルプをみてもドキュメントにも載っていない、いわゆる隠しオプションです。

CloudIAPを使ってSSH接続するときに実際にはどういったコマンドオプションが渡されているのか--dry-runを使って以下のように調べて判明しました。

$ gcloud compute ssh --tunnel-through-iap example-hostname --dry-run
No zone specified. Using zone [asia-northeast1-c] for instance: [example-hostname].
/usr/bin/ssh -t -i /Users/example-user/.ssh/google_compute_engine -o CheckHostIP=no -o HostKeyAlias=compute.3866928383721535040 -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/Users/example-user/.ssh/google_compute_known_hosts -o ProxyCommand /Users/example-user/ansible/venv/bin/python3 /Users/example-user/google-cloud-sdk/lib/gcloud.py compute start-iap-tunnel example-hostname %p --listen-on-stdin --project=example-project --zone=asia-northeast1-c --verbosity=warning -o ProxyUseFdpass=no example-user@compute.3866928383721535040

zoneは特に指定していませんが、gcp_computeプラグインが特定のプロジェクトで使用可能なすべてのゾーンから自動検出してくれるようです。

最後に

如何でしたでしょうか。
GCP上でインフラを構築している際には是非ご参考にしていただけると幸いです。


Leave a Reply

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です