无客户端远程桌面网关 Guacamole 安装试用指南

管理的服务器、交换机、VM 数量越来越多,种类也越来越多,时常需要同时打开多个不同的工具来访问不同的目标。比方说 RDP 连着 Windows 的机器、 SSH 连着 Linux 的服务器、 Telnet 连着交换机……

不同的工具还有着不同的连接配置管理方式,没办法统一管理。我也用过 SecureCRT,虽然他很强大,并且在 Windows 上和 Mac 上都很好用,但是他只能连接字符界面。

0x00 Guacamole 简介

Apache Guacamole

Apache Guacamole 是由 Apache 基金会孵化的项目,一个无客户端远程桌面网关(Clientless Remote Desktop Gateway),支持常见的远程桌面协议:

  • VNC
  • RDP
  • SSH
  • Telnet

除了以上的协议支持之外,从 1.1.0 开始,增加了连接 Kubernetes Pod 的支持,连接的 Pod 需要开启 TTy 支持,之后我会详细介绍一下用法。本文着重介绍 Guacamole 的安装和基本使用。

同时, Guacamole 还支持连接分组、窗口多开、剪贴板共享等功能,可以方便的在同一个平台上管理不同协议的终端设备。

服务架构

首先了解一下 Guacamole 的架构

Guacamole Arch

Guacamole Server 主要由两部分组成,Guacamoleguacd

  • Guacamole 是一个 Web 服务,包含前端 guacamole-websiteguacd 的客户端 guacamole-clientguacamole-client 会将用户和服务器连接配置信息存储在数据库中,支持 MySQL 和 PostgreSQL。
  • guacd 是一个无状态的 daemon。guacamole-client 会将存储在数据库中的连接配置信息通过内部的协议传递给 guacd,由 guacd 负责处理不同的远程连接协议,并和服务器建立连接。

0x01 安装

Guacamole 提供了多种不同的安装方式,可以编译源码,也可以下载预编译的 war 包。但同时官方也提供预装了插件的 Docker 镜像可以直接简单配置使用。

这里我提供三种不同的部署形态供选择,可以通过左边的目录直接跳到对应的部分。

我写作这篇文章时,Guacamole 的最新版本为 1.4.0,接下来的安装流程都是基于这个版本的

数据库我选择的是 PostgreSQL。

使用 Docker 部署

Docker 引擎的安装我在这里不过多赘述,可以参照 Docker 官网的安装指南进行安装。

使用 docker-compose 来管理服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
version: "3"
services:
initdb:
image: guacamole/guacamole:1.4.0
user: 0:0
volumes:
- initdb:/docker-entrypoint-initdb.d
command:
- sh
- -c
- /opt/guacamole/bin/initdb.sh --postgres > /docker-entrypoint-initdb.d/initdb.sql
db:
image: postgres:14
restart: always
depends_on:
- initdb
volumes:
- initdb:/docker-entrypoint-initdb.d
- ./db:/var/lib/postgresql/data/pgdata
environment:
- PGDATA=/var/lib/postgresql/data/pgdata
- POSTGRES_USER=guacamole
- POSTGRES_PASSWORD=guacamole
- POSTGRES_HOST_AUTH_METHOD=md5
- POSTGRES_INITDB_ARGS=--auth-host=md5
ports:
- 5432:5432
guacad:
image: guacamole/guacd:1.4.0
restart: always
ports:
- 4822:4822
guacamole:
image: guacamole/guacamole:1.4.0
restart: always
ports:
- 8080:8080
depends_on:
- db
- guacad
environment:
- GUACD_HOSTNAME=guacad
- POSTGRES_HOSTNAME=db
- POSTGRES_DATABASE=guacamole
- POSTGRES_USER=guacamole
- POSTGRES_PASSWORD=guacamole
volumes:
initdb: {}

部署在 Kubernetes 上

如果环境中有 Kubernetes 环境,也可以直接部署在 Kubernetes 上,统一管理。

首先创建数据库实例,利用 initContainer 来创建数据库初始化脚本。(注意修改一下数据库密码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
apiVersion: v1
kind: Service
metadata:
name: guacamole-db
spec:
selector:
app: guacamole
component: db
ports:
- port: 5432
targetPort: 5432
---
apiVersion: v1
kind: Secret
metadata:
name: guacamole-db
type: Opaque
data:
POSTGRES_PASSWORD: Z3VhY2Ftb2xlCg== # guacamole
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: guacamole-db
spec:
selector:
matchLabels:
app: guacamole
component: db
serviceName: guacamole-db
replicas: 1
template:
metadata:
labels:
app: guacamole
component: db
spec:
initContainers:
- name: database-init
image: guacamole/guacamole:1.4.0
volumeMounts:
- name: init
mountPath: /docker-entrypoint-initdb.d
command:
- sh
args:
- "-c"
- "/opt/guacamole/bin/initdb.sh --postgres > /docker-entrypoint-initdb.d/initdb.sql"
containers:
- name: postgres
image: postgres:14
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5432
name: db
volumeMounts:
- name: init
mountPath: /docker-entrypoint-initdb.d
- name: data
mountPath: /var/lib/postgresql/data
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_USER
value: guacamole
- name: POSTGRES_HOST_AUTH_METHOD
value: md5
- name: POSTGRES_INITDB_ARGS
value: --auth-host=md5
envFrom:
- secretRef:
name: guacamole-db
resources:
limits:
cpu: 500m
memory: 500Mi
volumes:
- name: init
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 5Gi

再部署 Guacamole 服务,这里我将 guacdGuacamole 部署在一起。实践中可以将 guacdGuacamole 分开部署,再在创建连接时指定不同的 guacd 服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
---
apiVersion: v1
kind: Service
metadata:
name: guacamole
spec:
selector:
app: guacamole
compoent: app
ports:
- port: 8080
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacamole
spec:
selector:
matchLabels:
app: guacamole
compoent: app
template:
metadata:
labels:
app: guacamole
compoent: app
spec:
containers:
- name: guacd
image: guacamole/guacd:1.4.0
resources:
limits:
memory: "1Gi"
cpu: "500m"
ports:
- containerPort: 4822
- name: guacamole
image: guacamole/guacamole:1.4.0
imagePullPolicy: IfNotPresent
resources:
limits:
memory: "1Gi"
cpu: "500m"
ports:
- containerPort: 8080
name: web
env:
- name: GUACD_HOSTNAME
value: "127.0.0.1"
- name: POSTGRES_HOSTNAME
value: guacamole-db
- name: POSTGRES_DATABASE
value: guacamole
- name: POSTGRES_USER
value: guacamole
envFrom:
- secretRef:
name: guacamole-db

0x02 安全配置

LDAP 登录

例子中我使用 Windows 的域控作为 LDAP 服务。

Guacamole 的镜像中已经预装了 LDAP 认证插件,我们只需要简单的设置好参数就可以接入 LDAP 服务。

需要配置如下的环境变量:

1
2
3
4
5
6
7
8
LDAP_HOSTNAME=<ldap server hostname or ip>
LDAP_SEARCH_BIND_DN=<adminitrator dn>
LDAP_SEARCH_BIND_PASSWORD=<administrator password>
LDAP_USER_BASE_DN=<user base dn>
LDAP_USER_SEARCH_FILTER=<the filter to find user>
LDAP_USERNAME_ATTRIBUTE=<attribute name in person object bind to username>
LDAP_GROUP_BASE_DN=<user group base dn>
LDAP_GROUP_SEARCH_FILTER=<the filter to find group>

我的实例:

1
2
3
4
5
6
7
8
LDAP_HOSTNAME="c.lo"
LDAP_SEARCH_BIND_DN="CN=Administrator,CN=Users,DC=c,DC=lo"
LDAP_SEARCH_BIND_PASSWORD=PASSWORD
LDAP_USER_BASE_DN="OU=Home,DC=c,DC=lo"
LDAP_USER_SEARCH_FILTER="(objectClass=person)"
LDAP_USERNAME_ATTRIBUTE="name"
LDAP_GROUP_BASE_DN="OU=Home,DC=c,DC=lo"
LDAP_GROUP_SEARCH_FILTER="(objectClass=group)"

其他的参数定制可以参考 官方文档

需要注意,官方文档中的配置参数为蛇形命名,转化为环境变量需要全部转为大写并把 “-” 换成 ”_“。

config env
ldap-port LDAP_PORT
ldap-encryption-method LDAP_ENCRYPTION_METHOD
ldap-max-search-results LDAP_MAX_SEARCH_RESULTS
ldap-search-bind-dn LDAP_SEARCH_BIND_DN
ldap-user-attributes LDAP_USER_ATTRIBUTES
ldap-search-bind-password LDAP_SEARCH_BIND_PASSWORD
ldap-username-attribute LDAP_USERNAME_ATTRIBUTE
ldap-member-attribute LDAP_MEMBER_ATTRIBUTE
ldap-user-search-filter LDAP_USER_SEARCH_FILTER
ldap-config-base-dn LDAP_CONFIG_BASE_DN
ldap-group-base-dn LDAP_GROUP_BASE_DN
ldap-group-search-filter LDAP_GROUP_SEARCH_FILTER
ldap-member-attribute-type LDAP_MEMBER_ATTRIBUTE_TYPE
ldap-group-name-attribute LDAP_GROUP_NAME_ATTRIBUTE
ldap-dereference-aliases LDAP_DEREFERENCE_ALIASES
ldap-follow-referrals LDAP_FOLLOW_REFERRALS
ldap-max-referral-hops LDAP_MAX_REFERRAL_HOPS
ldap-operation-timeout LDAP_OPERATION_TIMEOUT

Guacamole 的默认管理员用户名为 guacadmin,可以在域中添加这个用户,让默认管理员用户也使用 LDAP 进行登录。

0x03 使用指南

访问 http://127.0.0.1:8080/guacamole

Login

使用默认用户名 guacadmin 和默认用户名密码 guacadmin 进行登录。(如果使用 LDAP 登录默认用户,则输入 LDAP 中 guacadmin 用户的密码)

Main Page

新建连接

点击用户名,再点击”设置“,可以进到后台管理

Setting

导航到”连接“,点击”新建连接“

Setting

SSH

填写”名称“,选择 SSH 协议

SHH Connection Name

滚动屏幕到下面的参数,填入目标服务器的 IP、端口、用户名和密码或是私钥,再按需调整下方的参数

SHH Connection Info

滚动屏幕到最下方,点击”保存“

Save SSH Connection

RDP

填写”名称“,选择 RDP 协议

RDP Connection Name

滚动屏幕到下面的参数,填入目标服务器的 IP、端口、用户名、密码,勾选”忽略服务器证书“,再按需调整下方的参数

RDP Connection Info

滚动屏幕到最下方,点击 ”保存“

Save RDP Connection

Telnet

填写”名称“,选择 Telnet 协议

Telnet Connection Name

滚动屏幕到下面的参数,填入目标服务器的 IP、端口、用户名、密码

Telnet Connection Info

关于正则表达式的填写我们先来看下面的分析,这个是我 Telnet 登录 HomeLab 的路由器的过程

先看橙色框中,可以看到当 Username 出现的时候,就需要键入用户名,同理,当看到 Password 的时候,就需要键入密码,所以我们将 Username 填入 ”用户名正则表达式“,Password 填入 ”密码正则表达式“,让 Guacamole 自动输入用户名密码

Guacamole 要怎么判断登录成功还是失败呢?这时候我们看到红框

当出现 Error 的时候,表示登录失败了,而出现了命令提示符 Edge-Router 时,表示登录成功了,所以我就在 ”登录成功正则表达式“ 中填入 Edge-Router,在 ”登录失败正则表达式“ 中填入 Error

Telnet target

(以上是我的示例配置,请根据实际情况修改)

滚动屏幕到最下方,点击 ”保存“

Save Telnet Connection

Kubernetes

连接 Kubernetes 上的容器,需要容器开启 tty 选项,这里提供一个示例的 yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
spec:
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: demo
image: alpine:latest
resources:
limits:
memory: "128Mi"
cpu: "500m"
stdin: true
tty: true

填写”名称“,选择 Kubernetes 协议

Kubernetes Connection Name

滚动屏幕到 ”参数“,填入 Kubernetes API Server 的地址和端口,勾选 ”使用SSL/TLS“ 和 ”忽略服务器证书“
在 ”证书颁发机构证书“ 中填入从 kube config 的 clusters 字段中提取到的 certificate-authority-data,注意要先 base64 解码一下
在下面的认证方式中填入从 kube config 的 users 字段中提取到的 client-certificate-dataclient-key-data,同样需要进行 base64 解码

在”容器“信息中填入 Pod 的信息,以及要执行的命令,此处我打算执行 sh 命令来启动一个 Shell

Kubernetes Connection Info

滚动屏幕到最下面,点击”保存“

Save Kubernetes Connection

VNC

填写”名称“,选择 VNC 协议

VNC Connection Name

滚动屏幕到 “参数”,填入正确的主机名,端口,用户名,密码

VNC Connection Info

滚动屏幕到最下面,点击“保存”

Save VNC Connection

连接服务器

新建完连接,点击右上角用户名,在下拉菜单中点击“主页”,即可回到一开始登录的页面

Go to home

点击下面的链接,即可连接到目标,最近使用过的连接会以缩略图的形式显示在上面,点击他也可以连接到服务器

Connect

等待片刻,即可连上

Connecting

Connect success

如果想要断开连接,按下键盘上的 Ctrl+Alt+Shift 即可呼出边栏,在边栏的右上角点击用户名,在下拉菜单中点击断开连接即可

disconnect

如果不是点击“断开连接”而是点击“首页”,将会把当前连接最小化并悬挂在右下角

如果想要关闭边栏,再次按下 Ctrl+Alt+Shift,边栏就会隐藏

连接多个终端(v1.4.0 以上)

有这样一个场景,我需要同时操作多个终端,开多个网页窗口可以实现,但如果我想在不同的同时执行相同的命令,多个网页的方案就不可行了

Guacamole1.4.0 引入了 Connection tiling and keyboard broadcasting 功能,允许在一个页面上同时连接多个终端,和广播键盘消息到所有的连接

按下 Ctrl+Alt+Shift 呼出边栏,点击左上角连接名称,在下拉菜单中,勾选想要同时连接的终端,即可连接多个终端

Multi connect

想要在多个连接中同时输入,需要按住 Shift 或者 Ctrl 点击要接收输入的连接的标题栏,变为蓝色表示已被选中

Broadcast keyboard

0x04 尾巴

本文仅是 Guacamole 的使用入门,是 Guacamole 功能的冰山一脚,Guacamole 还支持如接入 2FA 认证、OpenID 登录、SFTP、WoL 网络唤醒、屏幕录制等大量功能,每种不同的连接协议又都有许多可以定制的参数。

再次感谢开发者对社区做出的巨大贡献!