GCP Ubuntu VMにリモートデスクトップ接続する

# 目標
[Google Cloud Platform (GCP)](https://www.google.com/aclk?sa=l&ai=DChcSEwizzaK-1K_-AhXKY4sKHbvgDGAYABAAGgJ0bQ&sig=AOD64_3-Wf3xZl1G7DdDHeEIkrL21b5beg&q&adurl&ved=2ahUKEwjSsZq-1K_-AhWImFYBHVzoDloQ0Qx6BAgFEAE)上にUbuntu環境のvirtual machine (VM)インスタンスを作成し、Remote Desktop Protocol (RDP)経由で接続する。

> この手の記事はググると沢山出てくるのだが、それらに従って構築した際にそこそこトラブったので、自分用メモ&トラブルシューティングとして書き残しておく。

# 前提知識
- GCP
- SSH
- RDP

# 環境
- ローカル
  - Windows 11 Pro 22H2
- VM
  - GCPは無料トライアル枠の個人アカウント
  - Ubuntu 18.04 LTS（これから作成）

# 1. GCPでVMインスタンスを作成する

## 1.1 GCPのアカウントとプロジェクトを作成する

[この辺](https://cloud.google.com/resource-manager/docs/creating-managing-projects?hl=ja)を参考にして進める。

## 1.2 VMインスタンスを作成する

[`GCPコンソール`](https://console.cloud.google.com/welcome?hl=ja) > `≡` > `Compute Engine` > `仮想マシン` > [`VMインスタンス`](https://console.cloud.google.com/compute/instances?hl=ja)に行く。
APIを有効にしていなければ有効にする。
`+インスタンスを作成`をクリックし、好きなVMインスタンスを作成する。今回は以下の通りにする。
| 項目 | 値 | 参照 |
|:-:|:-:|:-:|
| 名前 | instance-ubuntu-1 | |
| リージョン | asia-northeast-1 | |
| ゾーン | asia-northeast1-b | |
| ブートディスクOS | Ubuntu 18.04 LTS | |
| ブートディスクサイズ | 50 GB |
| アクセススコープ | すべてのCloud APIに完全アクセス権を許可 | [1.4](#14-iapを有効にする) |
| ファイアウォール | `HTTPトラフィックを許可する`をオン | [1.3](#13-ファイアウォールを作成する) |
| ネットワークタグ | `fw-allow-iap` | [1.3](#13-ファイアウォールを作成する) |

これら以外の項目の値はデフォルトから変更なし。今回はとりあえずRDP接続さえできればいいので、スペック等には拘らず必要最低限の項目のみ設定した。

> 本当はネットワークとサブネットもデフォルトではなく自分で作成したものを使った方がよい。

設定が終わったら`作成`をクリックする。

__結果__
![スクリーンショット 2023-04-17 162455.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3330979/e69dd3e4-efa4-fbbe-f4ea-8cbb631ec030.png)

## 1.3 ファイアウォールを作成する

作成したVMインスタンスに誰でも接続できるのはセキュリティ上よろしくないので、ファイアウォールを利用して接続を制限する。ここではGCPからのSSHとRDPのみを許可する。
[`GCPコンソール`](https://console.cloud.google.com/welcome?hl=ja) > `≡` > `VPCネットワーク` > [`ファイアウォール`](https://console.cloud.google.com/networking/firewalls/list?hl=ja)から`+ファイアウォールルールの作成`をクリックして、下記のルールを作成する。

| 項目 | 値 |
|:-:|:-:|
| 名前 | fw-allow-iap |
| ログ | オン |
| ターゲットタグ | fw-allow-iap |
| 送信元IPv4範囲 | 35.235.240.0/20 |
| プロトコルとポート | TCP 20,3389 |

その他の項目の値はデフォルトのまま。
`ログ`はデバグ用に`オン`にしたが、後でオフにしてよい。
`fw-allow-iap`はIdentity-Aware Proxy (IAP)の設定。IAPとは外部インターネットからVirtual Private Cloud (VPC)に接続するための踏み台みたいなもので、`35.235.240.0/20`から`tcp:20` (SSH)と`tcp:3389` (RDP)の接続を許可する（下図）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3330979/f7602687-de8c-7153-7089-6901d348f059.png)
> 引用元: [踏み台サーバはもういらない。IAP(Identity-Aware Proxy)の便利な使い方](https://blog.g-gen.co.jp/entry/login-your-vm-with-iap)

[1.2](#12-vmインスタンスを作成する)ではSSHとRDPに加えて一般のHTTPトラフィックも有効にしたが、これはVMインスタンスにGUI環境を導入するときに`apt get`等をするため。構築後にHTTPトラフィックをする予定がない場合、構築完了後に無効にした方がよい ([4.2](#42-外部ipアドレスを削除する))。

__結果__
![スクリーンショット 2023-04-17 162714.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3330979/716ac07f-ce55-7d27-004c-fa29b47bf440.png)


## 1.4 IAPを有効にする

[`GCPコンソール`](https://console.cloud.google.com/welcome?hl=ja) > `≡` > `セキュリティ` > [`Identity-Aware Proxy`](https://console.cloud.google.com/security/iap?hl=ja&project=deep-bolt-382805)から`APIを有効にする`をクリックして、IAPのAPIを有効にする。
有効にしたAPIの一覧は`APIとサービス` > [`有効なAPIとサービス`](https://console.cloud.google.com/apis/dashboard?hl=ja)から確認できる。

## 1.5 サービスアカウントにIAMロールを付与する

作成したVMインスタンスをクリック > `APIとIDの管理`から`サービスアカウント`を確認しておく。
[`GCPコンソール`](https://console.cloud.google.com/welcome?hl=ja) > `≡` > `IAMと管理` > [`IAM`](https://console.cloud.google.com/iam-admin/iam?hl=ja)から、上で確認したサービスアカウントの鉛筆アイコンをクリックして、下記のロールを追加する。
- Computeインスタンス管理者 (v1)
- IAPで保護されたトンネルユーザー

> 組織アカウントでVMインスタンスを作成している場合、VMインスタンスのメタデータとして`enable-oslogin`を`TRUE`にしていることがある（[参考](https://cloud.google.com/compute/docs/oslogin/set-up-oslogin?hl=ja)）。その場合は`Compute OSログイン`ロールも追加する。

これで[1.2](#12-vmインスタンスを作成する)で作成したVMインスタンスにSSH接続できるようになった。

## 1.6 VMインスタンスにSSHで接続する

作成したVMインスタンスの`接続` > `SSH`をクリック。

__結果__
![スクリーンショット 2023-04-17 114349.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3330979/d61bde3d-2903-2b96-cd05-d8beeb0fea54.png)

# 2. VMにリモートデスクトップ環境を導入する

[1.6](#16-vmインスタンスにsshで接続する)の画面上で以下の作業を行う。

## 2.1 インストール済みパッケージのアップデート
お決まりのやつ。
```shell
$sudo apt update
$sudo apt -y upgrade
```

## 2.2 ユーザの追加
```shell
$sudo adduser xxxxx(ユーザ名)
```
パスワードの設定を求められるので、好きなパスワードを設定する。続いてフルネームやら電話番号やらの入力もできるが、空白のままエンターキー連打でOK。
次に、作成したユーザに
```shell
$sudo gpasswd -a xxxxx(ユーザ名) sudo
```
で`sudo`権限を与える。

## 2.3 Gnomeデスクトップのインストール
VMインスタンスにGUI環境を導入する。今回は[GNOMEデスクトップ](https://www.gnome.org/)を採用する。
```shell
$sudo apt -y install ubuntu-desktop
```
インストール完了までそこそこ（$>$10分）時間がかかるので注意。完了したら再起動する。
```shell
$sudo reboot
```
（再起動するとSSHウィンドウごと切れるので再び`SSH`をクリックしてSSHする。接続に失敗することがあるが、`再接続`をクリックするとそのうち成功する。）

__結果__
ディスプレイマネージャに`gdm3`が正しく設定されている。
```shell
$cat /etc/X11/default-display-manager
/usr/sbin/gdm3
```

> GNOME以外のデスクトップについては[この辺](https://pc-freedom.net/software/%E3%80%90linux%E5%88%9D%E5%BF%83%E8%80%85%E5%BF%85%E8%A6%8B%E3%80%91%E3%83%87%E3%82%A3%E3%82%B9%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E7%92%B0%E5%A2%83%E3%82%92%E6%AF%94%E8%BC%83%E3%81%97%E3%81%A6/)を参照。

## 2.4 XRDPのインストール
[XRDP](https://wiki.archlinux.jp/index.php/Xrdp)はLinux用のRDPサーバソフトウェア。バックエンドとして[xorgxrdp](https://wiki.archlinux.jp/index.php/Xrdp)を用い、システムの[X Window System (X.org)](https://www.x.org/wiki/)上にデスクトップを表示させる。
```shell
$sudo apt install -y xrdp
```
カーソルの設定を変更する。
```shell
$sudo sed -e 's/^new_cursors=true/new_cursors=false/g' -i /etc/xrdp/xrdp.ini
```
上で作成したユーザに移動して……
```shell
$su xxxxx(上で作成したユーザ名)
$cd
```
`~/.xsession`を作成する。
```shell
$sudo echo "gnome-session" > ~/.xsession
$chmod +x ~/.xsession
```
`~/.xsessionrc`を作成する。
```shell
$DESKTOP=/usr/share/ubuntu:/usr/local/share:/usr/share:/var/lib/snapd/desktop
$cat << EOF > ~/.xsessionrc 
export GNOME_SHELL_SESSION_MODE=ubuntu
export XDG_CURRENT_DESKTOP=ubuntu:GNOME
export XDG_DATA_DIRS=${DESKTOP}
export XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg
EOF
```
認証ダイアログを繰り返し表示させないようにする。
```shell
$cat << EOF | sudo tee /etc/polkit-1/localauthority/50-local.d/xrdp-color-manager.pkla  
[Netowrkmanager]  
Identity=unix-user:*  
Action=org.freedesktop.color-manager.create-device  
ResultAny=no  
ResultInactive=no  
ResultActive=yes  
EOF

$cat << EOF | sudo tee /etc/polkit-1/localauthority/50-local.d/45-allow-colord.pkla
[Allow Colord all Users]
Identity=unix-user:*
Action=org.freedesktop.color-manager.create-device;org.freedesktop.color-manager.create-profile;org.freedesktop.color-manager.delete-device;org.freedesktop.color-manager.delete-profile;org.freedesktop.color-manager.modify-device;org.freedesktop.color-manager.modify-profile
ResultAny=no
ResultInactive=no
ResultActive=yes

[Allow Package Management all Users]
Identity=unix-user:*
Action=org.debian.apt.*;io.snapcraft.*;org.freedesktop.packagekit.*;com.ubuntu.update-notifier.*
ResultAny=no
ResultInactive=no
ResultActive=yes
EOF

$sudo systemctl restart polkit
```
XRDPユーザをssl-certグループに追加する。
```shell
$sudo gpasswd -a xrdp ssl-cert
```
XRDPを再起動する。
```shell
$sudo systemctl restart xrdp
$sudo systemctl enable xrdp.service
$sudo systemctl enable xrdp-sesman.service
```
XRPが起動していることは下記の通り確認できる。
```
user@instance-ubuntu-1:~$ sudo systemctl status xrdp
● xrdp.service - xrdp daemon
   Loaded: loaded (/lib/systemd/system/xrdp.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2023-04-17 04:41:38 UTC; 31s ago
     Docs: man:xrdp(8)
           man:xrdp.ini(5)
 Main PID: 17978 (xrdp)
    Tasks: 1 (limit: 4656)
   CGroup: /system.slice/xrdp.service
           └─17978 /usr/sbin/xrdp

Apr 17 04:41:37 instance-ubuntu-1 systemd[1]: Starting xrdp daemon...
Apr 17 04:41:37 instance-ubuntu-1 xrdp[17977]: (17977)(139958685501248)[DEBUG] Testing if xrdp can listen on 0.
Apr 17 04:41:37 instance-ubuntu-1 xrdp[17977]: (17977)(139958685501248)[DEBUG] Closed socket 7 (AF_INET6 :: por
Apr 17 04:41:37 instance-ubuntu-1 systemd[1]: xrdp.service: Can't open PID file /var/run/xrdp/xrdp.pid (yet?) a
Apr 17 04:41:38 instance-ubuntu-1 systemd[1]: Started xrdp daemon.
Apr 17 04:41:39 instance-ubuntu-1 xrdp[17978]: (17978)(139958685501248)[INFO ] starting xrdp with pid 17978
Apr 17 04:41:39 instance-ubuntu-1 xrdp[17978]: (17978)(139958685501248)[INFO ] listening to port 3389 on 0.0.0.
```

# 3. リモートデスクトップに接続する

今回は外部IPアドレス経由ではなくIAP経由でログインする。

## 3.1 gcloudのインストール

接続元（ローカルPC）のターミナルに[gcloud](https://cloud.google.com/sdk/docs/install?hl=ja)をインストールする。手順は省略。

## 3.2 IAPトンネリング

接続元からVMインスタンスへのIAP接続を確立する。
```
$gcloud compute start-iap-tunnel [インスタンス名] 3389 \
    --zone=[ゾーン名] \
    --project=[プロジェクト名] \
    --local-host-port=localhost:[ポート番号]
```
例えば
- `[インスタンス名]`: 今回は`instance-ubuntu-1`
- `[ゾーン名]`: 今回は`asia-northeast1-b`
- `[ポート番号`]: 接続元のポート番号。今回は`13389`に設定する（`0`を入れれば空いている番号が自動で割り当てられる）。

__結果__
```
$gcloud compute start-iap-tunnel instance-ubuntu-1 3389 \
    --zone="asia-northeast1-b" \
    --project=[project_name] \
    --local-host-port=localhost:13389
WARNING: 

To increase the performance of the tunnel, consider installing NumPy. For instructions,
please see https://cloud.google.com/iap/docs/using-tcp-forwarding#increasing_the_tcp_upload_bandwidth

Testing if tunnel connection works.
Listening on port [13389].

```
これで`localhost:13389`につなげばIAP経由でVMインスタンスにログインできるようになる。

## 3.3 接続を実行する

Windowsにデフォルトで入っている`リモートデスクトップ接続`アプリケーションを使って接続する。
アプリケーションを立ち上げ、`オプションの表示`から

- `ローカルリソース`タブ > `ローカルデバイスとリソース`の`プリンター`
- `エクスペリエンス`タブの`ビットマップのキャッシュを保持`

のチェックを外して、オプションを閉じる。
`コンピュータ`欄に`localhost:13389`を入力し、`接続`をクリックする。
警告は無視して進んでOK。

__結果__
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3330979/75519f89-8f89-2891-fec6-1f1e4ecb6856.png)

## 3.4 ログインする

[2.2](#22-ユーザの追加)で設定したユーザとパスワードを入力して、少しの間待つ。

__結果__
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3330979/7f7b158b-ed9b-412b-cd39-417d05124860.png)

成功！

# 4. 後処理

## 4.1 接続を切る

Ubuntuデスクトップの右上にあるメニューからログオフする。
接続元のターミナルで`Ctrl+C`でIAPを切断する。

## 4.2 外部IPアドレスを削除する

__*この作業は任意です。__

セキュリティ確保のため、今後はIAP経由のログインのみを許可することにする。
[VMインスタンス一覧](https://console.cloud.google.com/compute/instances?hl=ja)に戻る > `instance-ubuntu-1`をクリック > `編集`から

| 項目 | 値 |
|:-:|:-:|
| ネットワークインターフェース | `外部IPv4アドレス`を`なし`に設定 |
| ファイアウォール | `Allow HTTP traffic`のチェックを外す |

のように設定し、`作成`をクリックする。

__結果__
- `外部IP`の値が空白になっており、`ネットワークタグ`が`fw-allow-iap`のみになっている。
- 引き続きSSH ([1.6](#16-vmインスタンスにsshで接続する))とIAP経由のRDP ([3](#3-リモートデスクトップに接続する))で接続できる。

## 4.3 インスタンスを停止する

__インスタンスの起動中は課金が発生するため、使い終わったら停止すること。__
うっかり防止のため[停止をスケジューリング](https://cloud.google.com/compute/docs/instances/schedule-instance-start-stop?hl=ja)してもよい。

# ハマったところ

## IAPにつながらない
__問題__
IAPにつなごうとしても
```
ERROR: (gcloud.compute.start-iap-tunnel) While checking if a connection can be made: Error while connecting [4003: 'failed to connect to backend']. (Failed to connect to port 3389)
```
と表示され、つながらない。

__解決__
ファイアウォールを正しく設定した ([1.3](#13-ファイアウォールを作成する))。

## ログインした瞬間に接続が切れる

__問題__
[3.4](#34-ログインする)に従ってRDP接続すると、アカウントを入力して接続しようとした瞬間に接続が切れる。

__解決__
- XRDPユーザをssl-certグループに追加する ([2.4](#24-xrdpのインストール))。
- `リモートデスクトップ接続`アプリケーションのオプションを適切に設定する ([3.3](#33-接続を実行する))。

前者をサボると、VMインスタンスの`/var/log/xrdp.log`における
```
[WARN ] local keymap file for ~~~~~ found and doesn't match built in keymap, using local keymap file
```
という怒られの原因になる。

## ログインが青い画面のまま進まない

__問題__
[3.4](#34-ログインする)に従ってRDP接続すると、青い画面（緑色の画面）のまま応答がない。5分くらい待つと
```
connecting to sesman ip ~~~~ port 3389
sesman connect ok
sending login info to session manager, please wait...
login successful for display 10
started connecting
connection problem, giving up
some problem
```
と表示され、接続できない。

__解決__
- `~/.xsession`を作成する ([2.4](#24-xrdpのインストール))。
- `$pkill gnome-session`を実行し、ログイン済みの他ユーザのセッションを切断する。

# トラブルシューティング

トラブルの際はVMインスタンスにある下記ファイルをチェックすると解決のヒントになる。
- `/var/log/xrdp.log`
- `/var/log/xrdp-sesman.log`
- `~/.xsession-errors`

# 参考

## 構築の際に参考にしたもの

下記のサイトを中心に参考にしたが、どのサイトの手順も微妙に足りない部分があったため、試行錯誤の末今回の記事のような手順になった。

- [GCPでUbuntu Desktopの環境を作り、従量課金制(10円/h)のGUI PCを作ろう](https://tech.anti-pattern.co.jp/gcpdeubuntu-desktopnohuan-jing-wozuo-ri-cong-liang-ke-jin-zhi-10yuan-h-nogui-pcwozuo-rou/)
- [IAP を使って外部 IP を持たない Compute Engine(WindowsVM)にリモートデスクトップ接続する方法](https://zenn.dev/cloud_ace/articles/gcp-iap-vmrdp)
- [Ubuntu on GCP にリモートデスクトップ接続](https://qiita.com/smiler5617/items/b1fd439299cc58c5f69b)
- [Ubuntu 18.04: GNOMEデスクトップ環境にXRDPで接続する](https://www.hiroom2.com/2018/04/28/ubuntu-1804-xrdp-gnome-ja)
- [Ubuntu18.04LTS(GCP)にWindowsリモートデスクトップ接続](https://flat-kids.net/2020/04/23/ubuntu18-04ltsgcp%E3%81%ABwindows%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%88%E3%83%87%E3%82%B9%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E6%8E%A5%E7%B6%9A/)
- [Ubuntu 20.04 LTS に GUI（GNOME デスクトップ）をインストールする](https://zenn.dev/skksky_tech/scraps/2570c670045498)

## トラブルシューティングで参考になったもの

- [リモートデスクトップ接続がすぐに切れる場合の原因と対処](https://tooljp.com/qa/remote-desktop-disconnected-at-once-CA39.html)
- [Linuxにリモートデスクトップで入った時に出るダイアログを消す](https://qiita.com/HirumaT/items/36a7209814a157f62cec)
- [pkill gnome-session](https://ne-go.hatenablog.com/entry/2020/06/21/000000)
- [screen stays blue for a really long time and then errors out](https://github.com/microsoft/linux-vm-tools/issues/49)
- [xrdpユーザをssl-certに追加する](https://qiita.com/k_takashi0309/items/f12a87a630a22e509798#xrdp%E3%83%A6%E3%83%BC%E3%82%B6%E3%82%92ssl-cert%E3%81%AB%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)

## 今回のケースには当てはまらなかったもの

トラブルシューティングの際に下記の解決策も試したが、結果的に今回のケースには関係ないことが分かり、最終的には __適用しなかった__ もの。

- [`/etc/login.defs`の設定](https://linux-memo.net/petite/rdp_top.html)
- [`/etc/xrdp/startwm.sh`の設定](https://saunterer-reports.com/problem-with-xrdp/)
- [`/etc/X11/Xwrapper.config`の設定](https://blog.treedown.net/entry/2020/09/01/010000)
- [tigervncのインストール](https://qiita.com/_dakc_/items/81ca237dfc4cfec855d9#-tigervnc%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)
- [Ubuntuファイアウォールの設定](https://server-network-note.net/2021/08/ubuntu-server-20-04-lts-firewall-ufw/)
- [xserver-xorg-coreのインストール](https://note.spage.jp/archives/576)