1. はじめに
Apache guacamoleはOSSの踏み台サーバーであり、以下のような特徴を持つ。
- ブラウザ経由で接続可能(クライアントにSSH/RDP/VPCクライアントが不要)。実運用では、TLSを有効にし、ポート番号を変えるとか、Client-to-Server VPNと組み合わせるとかはやっておいた方が良さそう。
- SSH/RDP/VNCを利用するバックエンドサーバーに対応。
- 操作履歴を保存可能(SSHならテキスト保存、RDP/VNCなら動画で保存可能)
- SFTPでバックエンドサーバーにファイル転送可能
- 複数人での画面共有が可能
- LDAP/OIDC/SAML等とも連携可能
- 二要素認証も可能
今回は、以下の方針で環境を構築する。
- 色々なパッケージを手動で導入することを避けるため(native installの場合は、非常にたくさんのパッケージをインストールし、コンパイルする必要がある)、dockerで構成する。ただし、一括でコンテナを管理したいため、docker-composeを利用する。
- 外部にはHTTPではなくHTTPSでサービスを公開する。そのため、Nginxをreverse proxyとして構成する。単にApache guacamoleをインストールするとHTTPで通信できてしまうため、Nginxを経由することでHTTPSでアクセスさせる。
- 認証を強化するため、二要素認証を有効にする。
なお、こちらの記事を参考にさせていただきました。
2. 環境確認
環境確認
[root@syasuda-bastion1 ~]# cat /etc/redhat-release
CentOS Stream release 9
[root@syasuda-bastion1 ~]# dnf -y update
[root@syasuda-bastion1 ~]# uname -a
Linux syasuda-bastion1 5.14.0-252.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Feb 1 13:25:18 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
3. dockerおよびdocker-composeのインストール
dockerのインストール
[root@syasuda-bastion1 ~]# curl -fsSL https://get.docker.com -o get-docker.sh
[root@syasuda-bastion1 ~]# sudo sh get-docker.sh
[root@syasuda-bastion1 ~]# systemctl enable docker
[root@syasuda-bastion1 ~]# systemctl start docker
[root@syasuda-bastion1 ~]# docker version
Client: Docker Engine - Community
Version: 23.0.6
API version: 1.42
Go version: go1.19.9
Git commit: ef23cbc
Built: Fri May 5 21:19:37 2023
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 23.0.6
API version: 1.42 (minimum version 1.12)
Go version: go1.19.9
Git commit: 9dbdbd4
Built: Fri May 5 21:18:11 2023
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.21
GitCommit: 3dce8eb055cbb6872793272b4f20ed16117344f8
runc:
Version: 1.1.7
GitCommit: v1.1.7-0-g860f061
docker-init:
Version: 0.19.0
GitCommit: de40ad0
docker-composeのインストール
[root@syasuda-bastion1 ~]# curl -SL https://github.com/docker/compose/releases/download/v2.17.4/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
[root@syasuda-bastion1 ~]# chmod +x /usr/local/bin/docker-compose
[root@syasuda-bastion1 ~]# docker-compose version
Docker Compose version v2.17.3
4. Nginxの設定ファイルの作成と配置
TLS証明書用ディレクトリの作成
[root@syasuda-bastion1 ~]# mkdir -p ./nginx/ssl
TLS証明書の作成
[root@syasuda-bastion1 ~]# openssl req -new -x509 -sha256 -newkey rsa:2048 -days 3650 -nodes -subj "/CN=Guacamole bastion" -out ./nginx/ssl/nginx.pem -keyout ./nginx/ssl/nginx.key
[root@syasuda-bastion1 ~]# ls -l ./nginx/ssl/
total 8
-rw-------. 1 root root 1704 May 9 03:07 nginx.key
-rw-r--r--. 1 root root 1135 May 9 03:07 nginx.pem
[root@syasuda-bastion1 ~]# openssl x509 -in nginx/ssl/nginx.pem -text -noout|grep -i -e signature -e public-key -e CN -e Not
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = Guacamole bastion
Not Before: May 9 07:07:44 2023 GMT
Not After : May 6 07:07:44 2033 GMT
Subject: CN = Guacamole bastion
Public-Key: (2048 bit)
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
構成ファイル用のディレクトリの作成
[root@syasuda-bastion1 ~]# mkdir -p ./nginx/conf.d
./nginx/conf.d/default.conf
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /etc/nginx/ssl/nginx.pem;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
location /guacamole/ {
proxy_pass http://guacamole:8080/guacamole/;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
client_max_body_size 10g;
access_log off;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
-
proxy_buffering off
を忘れないように(とmanualにも書かれている)。 - Nginxではデフォルトで1MBのファイルアップロードの制限があるため、
client_max_body_size
も忘れないように(と、これもmanualにも書かれている)
5. PostgreSQLの初期化ファイルを作成
[root@syasuda-bastion1 ~]# mkdir ./pginit
[root@syasuda-bastion1 ~]# chmod -R +x ./pginit
[root@syasuda-bastion1 ~]# docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgres > ./pginit/initdb.sql
6. TOTP(二要素認証)およびrecording storageのためのjar配置
- 本稿執筆時点では、version 1.5.1が最新。常に最新を利用することが推奨される。
https://guacamole.apache.org/releases/ -
guacamole-auth-totp-x.x.x.jar
が必要。このファイルは、guacamole-auth-totp-x.x.x.tar.gz
に含まれている。
[root@syasuda-bastion1 ~]# mkdir -p ./guacamole/extensions
[root@syasuda-bastion1 ~]# curl -L https://apache.org/dyn/closer.lua/guacamole/1.5.1/binary/guacamole-auth-totp-1.5.1.tar.gz?action=download | tar zxvf - -C ./guacamole
[root@syasuda-bastion1 ~]# cp -p ./guacamole/guacamole-auth-totp-1.5.1/guacamole-auth-totp-1.5.1.jar ./guacamole/extensions/.
[root@syasuda-bastion1 ~]# curl -L https://apache.org/dyn/closer.lua/guacamole/1.5.1/binary/guacamole-history-recording-storage-1.5.1.tar.gz?action=download | tar zxvf - -C ./guacamole
[root@syasuda-bastion1 ~]# cp -p ./guacamole/guacamole-history-recording-storage-1.5.1/guacamole-history-recording-storage-1.5.1.jar ./guacamole/extensions/.
[root@syasuda-bastion1 ~]# ls -l ./guacamole/extensions/
total 4724
-rw-r--r--. 1 vpcuser vpcuser 4819523 Apr 10 13:23 guacamole-auth-totp-1.5.1.jar
-rw-r--r--. 1 vpcuser vpcuser 13697 Apr 10 13:23 guacamole-history-recording-storage-1.5.1.jar
7. Reverse Proxy構成
- ここを参照。この構成をしないと、接続履歴画面で、接続元情報が常にNginxのIPアドレスになってしまい、意味をなさないことになる。
- internalProxiesの値としてはReverse Proxy(今回の例ではNginx)のIPアドレスを指定する必要がある。ホスト名は使えない。CIDR表記はできないが、正規表現で書いても良い。
- 今回は、Nginxに192.168.0.2という固定IPアドレスを明示的に指定することにする。
[root@syasuda-bastion1 ~]# docker run --rm guacamole/guacamole cat /usr/local/tomcat/conf/server.xml > ./guacamole/server.xml
./guacamole/server.xml
(途中略)
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
<Valve className="org.apache.catalina.valves.RemoteIpValve"
internalProxies="192.168.0.2"
remoteIpHeader="x-forwarded-for"
remoteIpProxiesHeader="x-forwarded-by"
protocolHeader="x-forwarded-proto" />
</Host>
</Engine>
</Service>
</Server>
8. recordingログ(SSHやRDPなどのログ)のためのファイル配置
- recordingログはguacdコンテナ上で作成される。よって後から参照するためには永続化が必要である。
- 以下のようにキャプチャーされた動画を参照できるようにするためには、このログはguacamoleコンテナーから読み取る必要がある。guacamoleコンテナーにおけるrecording storage extensionは、デフォルトで
/var/lib/guacamole/recordings
を検索する。コンテナ上には/var/lib/guacamole
さえ存在していない。
-
よって、以下のようなディレクトリ配置にする。
- ホスト上に
./guacd/guacamole/recordings
を作成する - guacdコンテナの
/var/lib/guacamole
が、ホスト上の./guacd/guacamole
をマウントするように構成する。 - guacamoleコンテナの
/var/lib/guacamole
が、ホスト上の./guacd/guacamole
をマウントするように構成する。
- ホスト上に
-
もう一つ難しいのは、権限の問題である。
- guacdコンテナは、ログをユーザー:guacd(UUID=1000), グループ:guacd(GUID=1000)で書き込む。
- guacamoleコンテナーは、guacamole(UUID=1001), グループ:guacamole(GUID=1001)で読み込もうとするため、そのままでは読めない。
[root@syasuda-bastion1 ~]# docker exec -it root-guacamole-1 bash
guacamole@18fe445ac77d:/opt/guacamole$ id
uid=1001(guacamole) gid=1001(guacamole) groups=1001(guacamole)
guacamole@18fe445ac77d:/opt/guacamole$ ls -ltr /var/lib/guacamole/recordings/
total 0
drwxr-x---. 2 1000 1000 115 May 12 03:49 cf97bce1-d5f1-322a-9f02-dde030944c7d
guacamole@18fe445ac77d:/opt/guacamole$ ls -ltr /var/lib/guacamole/recordings/cf97bce1-d5f1-322a-9f02-dde030944c7d
ls: cannot open directory '/var/lib/guacamole/recordings/cf97bce1-d5f1-322a-9f02-dde030944c7d': Permission denied
そのため、以下のような対応を行った(他に良い方法があったら教えてください)。
- ホスト上に各コンテナと同じUID/GIDを持つユーザー・グループを作成する。(-oオプションを付けておけば、既に同じUID/GIDが存在しても重複を許可してユーザ・グループを作成可能)
- ログの書き込みは継続してguacdユーザーが行えるように、ディレクトリーuser ownerはguacdにする。ただし、group ownerはguacamoleにする。
- ディレクトリに対してSGIDを構成する。これにより、その中に作成されるファイルやディレクトリはそのグループを継承することができる。guacamoleコンテナーのユーザーはguacamoleグループに属しているため、そのディレクトリに作成されたディレクトリやファイルにアクセスすることが可能である。
[root@syasuda-bastion1 ~]# docker run --rm guacamole/guacd id
uid=1000(guacd) gid=1000(guacd) groups=1000(guacd)
[root@syasuda-bastion1 ~]# docker run --rm guacamole/guacamole id
uid=1001(guacamole) gid=1001(guacamole) groups=1001(guacamole)
[root@syasuda-bastion1 ~]# groupadd -g 1000 -o guacd
[root@syasuda-bastion1 ~]# groupadd -g 1001 -o guacamole
[root@syasuda-bastion1 ~]# useradd -M -u 1000 -o -g guacd -s /sbin/nologin guacd
[root@syasuda-bastion1 ~]# useradd -M -u 1001 -o -g guacamole -s /sbin/nologin guacamole
[root@syasuda-bastion1 ~]# mkdir -p ./guacd/guacamole/recordings
[root@syasuda-bastion1 ~]# chown -R guacd:guacamole ./guacd/guacamole
[root@syasuda-bastion1 ~]# chmod -R 2775 ./guacd/guacamole
9. docker-compose.ymlの作成
- Nginxをwell knownポートである443で公開するのではなく、8443で公開する。
- guacamoleコンテナーはport 8080でHTTPアクセスをlistenしているが、docker-composeで構成されるnginxからのみアクセスできればいいので、よくサンプルであるように
ports:
- "8080:8080"
のような記述は削除する。
- 先述のセクションで作成した所定のファイルをマウントできるように構成する。
- nginx
- ./nginx/conf.d (構成ファイル)
- ./nginx/ssl (TLS証明書・鍵)
- PostgreSQL
- ./pginit (初期化SQL)
- ./pgdata (永続DBデータ)
- guacamole
- ./guacamole/extensions (TOTP)
- nginx
- Guacamoleのserver.xmlでX-Forwarded-forを有効にするためにNginxのIPアドレスを固定しておく必要がある。今回は192.168.0.2を割り当てている。
- 他のコンテナもIPアドレスを割り当てた。というのは、場合によっては他のコンテナが先に起動して192.168.0.2を取得してしまい、Nginxが192.168.0.2が利用できなくなる(
Error response from daemon: Address already in use
というエラーが出る)可能性があるからである。それだったら、4つぐらいしかないコンテナーなので、全部にIPアドレスを割り当てることにする。
docker-compose.yml
version: "3"
services:
nginx:
image: nginx:latest
restart: unless-stopped
networks:
backend:
ipv4_address: 192.168.0.2
ports:
- "8443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
postgres:
image: postgres:latest
restart: unless-stopped
networks:
backend:
ipv4_address: 192.168.0.3
environment:
PGDATA: /var/lib/postgresql/data/guacamole
POSTGRES_DB: guacamole_db
POSTGRES_PASSWORD: guacamole1234567890
POSTGRES_USER: guacamole_user
volumes:
- ./pginit:/docker-entrypoint-initdb.d
- ./pgdata:/var/lib/postgresql/data
guacd:
image: guacamole/guacd:latest
restart: unless-stopped
networks:
backend:
ipv4_address: 192.168.0.4
volumes:
- ./guacd/guacamole:/var/lib/guacamole
guacamole:
image: guacamole/guacamole:latest
restart: unless-stopped
networks:
backend:
ipv4_address: 192.168.0.5
environment:
GUACD_HOSTNAME: guacd
POSTGRES_DATABASE: guacamole_db
POSTGRES_HOSTNAME: postgres
POSTGRES_PASSWORD: guacamole1234567890
POSTGRES_USER: guacamole_user
GUACAMOLE_HOME: /etc/guacamole
volumes:
- ./guacamole/extensions:/etc/guacamole/extensions
- ./guacamole/server.xml:/usr/local/tomcat/conf/server.xml
- ./guacd/guacamole:/var/lib/guacamole
depends_on:
- postgres
- guacd
networks:
backend:
ipam:
driver: default
config:
- subnet: 192.168.0.0/24
10. docker-composeによるguacamoleの起動
[root@syasuda-bastion1 ~]# docker-compose up -d
[root@syasuda-bastion1 ~]# docker-compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
root-guacamole-1 guacamole/guacamole:latest "/opt/guacamole/bin/…" guacamole 41 seconds ago Up 39 seconds 8080/tcp
root-guacd-1 guacamole/guacd:latest "/bin/sh -c '/opt/gu…" guacd 41 seconds ago Up 40 seconds (health: starting) 4822/tcp
root-nginx-1 nginx:latest "/docker-entrypoint.…" nginx 41 seconds ago Up 40 seconds 80/tcp, 0.0.0.0:8443->443/tcp, :::8443->443/tcp
root-postgres-1 postgres:latest "docker-entrypoint.s…" postgres 41 seconds ago Up 40 seconds 5432/tcp
[root@syasuda-bastion1 ~]# docker inspect -f '{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -q)
/root-guacamole-1 - 192.168.0.5
/root-nginx-1 - 192.168.0.2
/root-guacd-1 - 192.168.0.4
/root-postgres-1 - 192.168.0.3
[root@syasuda-bastion1 ~]# ss -anpt
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=824,fd=3))
LISTEN 0 4096 0.0.0.0:111 0.0.0.0:* users:(("rpcbind",pid=671,fd=4),("systemd",pid=1,fd=69))
LISTEN 0 4096 0.0.0.0:8443 0.0.0.0:* users:(("docker-proxy",pid=73719,fd=4))
ESTAB 0 0 10.0.0.31:22 10.0.0.4:48792 users:(("sshd",pid=72366,fd=4),("sshd",pid=72351,fd=4))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=824,fd=4))
LISTEN 0 4096 [::]:111 [::]:* users:(("rpcbind",pid=671,fd=6),("systemd",pid=1,fd=71))
LISTEN 0 4096 [::]:8443 [::]:* users:(("docker-proxy",pid=73725,fd=4))
11. Apache guacamoleのコンソールへのアクセス試行
-
https://<IP address>:8443/guacamole/
にアクセスする。 - ID/初期パスワードはguacadmin/guacadmin
- 初回ログイン時は、二要素認証の登録が必要となる。QRコードをGoogle Authenticatorで読み込む。
- Google Authenticatorに登録した結果。この認証コードを先ほどの画面に入力する。
- なお、次回ログインからは以下のようにID/Passwordの入力後に認証コードを要求する画面が現れる。
12. ログイン後の各種設定
12-1. 一般の設定
- 初期パスワードをguacadminから他のパスワードに変更しておく。
- あとはユーザーを作ったり、グループを作ったり、接続を作ったり、色々とやっていく。
- Apache guacamoleの設定データはPostgreSQLに保管されるため、
./pgdata
をバックアップしておけば良い。逆に初期化したい時は、./pgdata
を削除してdocker-composeを起動すれば良い。
12-2. Linux接続
-
SSHのスクリプトログの配置先は、以下を指定。
- タイプスクリプトの保存ディレクトリ:
/var/lib/guacamole/recordings/${HISTORY_UUID}
Guacamoleコンソールからレコーディングを参照することはまだサポートされていないが、${HISTORY_UUID}
を使っておくと将来Guacamoleがサポートする時に対応できるらしい。 - タイプスクリプト名:
${GUAC_USERNAME}-${GUAC_DATE}-${GUAC_TIME}-${GUAC_CLIENT_ADDRESS}.log
- タイプスクリプトの保存ディレクトリを自動的に作成する。: チェックを入れる。(保存先ディレクトリが存在しない場合にディレクトリを作成してくれるが、作成するのは最後のサブディレクトリだけであり、その親ディレクトリは予め存在している必要がある)。
- タイプスクリプトの保存ディレクトリ:
-
SSHのスクリーンレコーディングは以下を指定(タイプスクリプトが存在するし、ターミナルの動画なんて後から見直したくもないだろうから、取得する必要はないかもしれないが、まぁ今回はテストも兼ねて・・・)。
12-3. Windows接続
13. ログの確認
[root@syasuda-bastion1 ~]# docker exec root-guacd-1 ls -ltR /var/lib/guacamole/recordings/
/var/lib/guacamole/recordings/:
total 0
drwxr-s--- 2 guacd 1001 166 May 13 14:06 f4fe7bcf-81aa-3763-947c-bb9748fb32af
drwxr-s--- 2 guacd 1001 57 May 13 14:05 78416a47-bd7f-3d9a-89b3-e460ab668404
/var/lib/guacamole/recordings/f4fe7bcf-81aa-3763-947c-bb9748fb32af:
total 900
-rw-r----- 1 guacd 1001 906287 May 13 14:06 guacadmin-20230513-140602-111.xxx.x.129.dat
-rw-r----- 1 guacd 1001 5226 May 13 14:06 guacadmin-20230513-140602-111.xxx.x.129.log
-rw-r----- 1 guacd 1001 456 May 13 14:06 guacadmin-20230513-140602-111.xxx.x.129.log.timing
/var/lib/guacamole/recordings/78416a47-bd7f-3d9a-89b3-e460ab668404:
total 940
-rw-r----- 1 guacd 1001 959199 May 13 14:05 guacadmin-20230513-140528-111.xxx.x.129.dat
- guacamoleコンテナ上のログ
[root@syasuda-bastion1 ~]# docker exec root-guacamole-1 ls -ltR /var/lib/guacamole/recordings/
/var/lib/guacamole/recordings/:
total 0
drwxr-s---. 2 1000 guacamole 166 May 13 14:06 f4fe7bcf-81aa-3763-947c-bb9748fb32af
drwxr-s---. 2 1000 guacamole 57 May 13 14:05 78416a47-bd7f-3d9a-89b3-e460ab668404
/var/lib/guacamole/recordings/f4fe7bcf-81aa-3763-947c-bb9748fb32af:
total 900
-rw-r-----. 1 1000 guacamole 5226 May 13 14:06 guacadmin-20230513-140602-111.xxx.x.129.log
-rw-r-----. 1 1000 guacamole 906287 May 13 14:06 guacadmin-20230513-140602-111.xxx.x.129.dat
-rw-r-----. 1 1000 guacamole 456 May 13 14:06 guacadmin-20230513-140602-111.xxx.x.129.log.timing
/var/lib/guacamole/recordings/78416a47-bd7f-3d9a-89b3-e460ab668404:
total 940
-rw-r-----. 1 1000 guacamole 959199 May 13 14:05 guacadmin-20230513-140528-111.xxx.x.129.dat
- ホスト上のログ
[root@syasuda-bastion1 ~]# ls -ltRn ./guacd/guacamole/recordings/
./guacd/guacamole/recordings/:
total 0
drwxr-s---. 2 1000 1001 166 May 13 10:06 f4fe7bcf-81aa-3763-947c-bb9748fb32af
drwxr-s---. 2 1000 1001 57 May 13 10:05 78416a47-bd7f-3d9a-89b3-e460ab668404
./guacd/guacamole/recordings/f4fe7bcf-81aa-3763-947c-bb9748fb32af:
total 900
-rw-r-----. 1 1000 1001 5226 May 13 10:06 guacadmin-20230513-140602-111.xxx.x.129.log
-rw-r-----. 1 1000 1001 906287 May 13 10:06 guacadmin-20230513-140602-111.xxx.x.129.dat
-rw-r-----. 1 1000 1001 456 May 13 10:06 guacadmin-20230513-140602-111.xxx.x.129.log.timing
./guacd/guacamole/recordings/78416a47-bd7f-3d9a-89b3-e460ab668404:
total 940
-rw-r-----. 1 1000 1001 959199 May 13 10:05 guacadmin-20230513-140528-111.xxx.x.129.dat