في مقال Bastion Host أم Jump Server؟ ذكرنا أن خوادم Windows لا ينبغي أن تفتح منفذ RDP للإنترنت، وأن البديل مفتوح المصدر لبوابة Microsoft هو Apache Guacamole، وفي خريطة أدوات الوصول الآمن وضعناه في عائلة بوابات المتصفح. والفكرة بسيطة، فبدلاً من أن يثبت كل موظف عميل SSH وعميل RDP وعميل VNC على جهازه، وبدلاً من أن تفتح هذه المنافذ لكل جهاز، يفتح المستخدم صفحة ويب واحدة، ويسجل دخوله، ثم يختار السيرفر الذي يحق له الوصول إليه، فيظهر له سطر الأوامر أو سطح المكتب داخل المتصفح نفسه، والسيرفرات خلف البوابة لا ترى إلا سيرفر Guacamole.
وفي هذا الدليل سوف نثبته بأنفسنا على Docker Compose ونشغله فعلاً، وسوف نتناول ما يلي:
- كيف يعمل Guacamole من الداخل، ولماذا ينقسم إلى ثلاثة أجزاء: guacd وتطبيق الويب وقاعدة البيانات.
- التثبيت بإصدارات مثبتة خلف Reverse Proxy، وأول دخول، والتخلص من الحساب الافتراضي.
- اتصالات SSH وRDP وVNC إلى سيرفرات تجريبية، والتحقق من مفتاح السيرفر Host Key.
- المجموعات والصلاحيات، وتسجيل الجلسات Session Recording وإعادة تشغيلها من المتصفح.
- المصادقة الثنائية TOTP، والدخول الموحد SSO عبر OpenID Connect مع authentik.
- التقوية والحماية من التخمين Brute Force، والنسخ الاحتياطي والتحديث، والأخطاء التي ظهرت لنا وحلولها.
متى تختار Guacamole، ومتى لا يناسبك؟
يناسبك Guacamole عندما يكون الوصول المطلوب سطح مكتب RDP أو VNC، أو سطر أوامر SSH لأشخاص لا تريد أن تعطيهم مفاتيح SSH أو VPN، مثل مورد خارجي يدعم نظاماً لديك، أو موظف يعمل من جهاز لا تديره، والسبب أن المستخدم لا يحتاج إلا إلى متصفح، وكل ما يراه صورة الشاشة ونص الطرفية، فلا يصل جهازه إلى الشبكة الداخلية نفسها. ويأتي معه تسجيل الجلسات وسجل الاتصالات والصلاحيات لكل اتصال.
ولا يناسبك إذا كان فريقك يعمل أساساً من سطر الأوامر بأدوات مثل scp وkubectl وpsql، لأن Guacamole يعرض لك الشاشة ولا يمرر منفذاً إلى جهازك، فهنا تحتاج إلى Bastion مع ProxyJump أو إلى بوابة وصول مميز أو شبكة Mesh، وقد قارنا هذه العائلات في أي عائلة تحتاج؟. ولاحظ أيضاً أن Guacamole لا يقدم خزنة أسرار ولا صلاحيات مؤقتة Just-in-Time، فهو بوابة وتسجيل فقط.
كيف يعمل Guacamole من الداخل؟
بحسب صفحة البنية والتنفيذ في التوثيق الرسمي، فإن Guacamole ليس تطبيق ويب واحداً، وإنما ثلاثة أجزاء. الأول تطبيق الويب guacamole، وهو تطبيق Java يعمل داخل Tomcat ويقدم للمتصفح عميلاً مكتوباً بلغة JavaScript، ويتولى تسجيل الدخول والصلاحيات وقائمة الاتصالات، ولكنه لا يفهم RDP ولا VNC ولا SSH إطلاقاً. والثاني خدمة guacd، وهي البرنامج الذي يتكلم بروتوكولات سطح المكتب البعيد فعلاً ويترجمها إلى بروتوكول Guacamole، وهو بروتوكول بسيط للرسم ونقل أحداث لوحة المفاتيح والفأرة. والثالث قاعدة البيانات التي تحفظ المستخدمين والاتصالات وسجل الجلسات. والصورة التالية تبين الطريق كاملاً:

وقد يتساءل البعض: لماذا لا يكون كل ذلك في Container واحد؟ والإجابة أن توثيق Docker يوصي صراحة بفصلها، فقاعدة البيانات هي الجزء الوحيد الذي يحمل بيانات يجب أن تبقى بعد التحديث، أما guacd وتطبيق الويب فتحذفهما وتعيد إنشاءهما متى شئت. وهناك سبب أمني أهم، وهو أن التوثيق نفسه يحذر من أن guacd وسيط Proxy لا يتحقق من هوية أحد، فمن وصل إلى منفذه 4822 يستطيع أن يستخدمه للقفز إلى أي جهاز يصله guacd، لذلك لا تنشر هذا المنفذ أبداً، واتركه داخل شبكة Docker الخاصة كما سوف نفعل.
المتطلبات
- سيرفر Linux عليه Docker وDocker Compose، وقد شرحنا تجهيزه في تأمين سيرفر VPS من أول دخول.
- الذاكرة: في تجربتنا استهلك تطبيق الويب نحو 374 MiB وقاعدة البيانات نحو 45 MiB، وguacd نحو 11 MiB وهو خامل، ويزيد استهلاكه مع كل جلسة RDP مفتوحة، لذلك ابدأ بـ 2 GB على الأقل.
- نطاق فرعي Subdomain مثل
guac.example.comوReverse Proxy يصدر شهادة TLS، مثل Nginx Proxy Manager أو Caddy. - طريق شبكي من سيرفر Guacamole إلى السيرفرات التي تريد الوصول إليها، على منافذ SSH أو RDP أو VNC الخاصة بها، دون أن تكون هذه المنافذ مفتوحة للإنترنت.
- للدخول الموحد: مزود هوية يدعم OpenID Connect، ونستخدم هنا authentik كما ثبتناه في دليل authentik.
التثبيت
هيكل المجلد
mkdir -p /opt/guacamole/nginx
cd /opt/guacamole
touch .env docker-compose.yml nginx/guacamole.confملف .env
نولد كلمة مرور قاعدة البيانات عشوائياً بالأمر openssl rand -hex 24، ونضعها في الملف كما يلي:
POSTGRES_PASSWORD=ضع-هنا-ناتج-openssl-rand-hex-24
TOTP_ENABLED=false
OPENID_ENABLED=falseوالمتغيران الأخيران نغير قيمتهما لاحقاً عندما نصل إلى المصادقة الثنائية والدخول الموحد، حتى نرى كل خطوة وحدها.
إعداد nginx أمام Guacamole
هذا هو المكان الذي يخطئ فيه أغلب من يثبت Guacamole لأول مرة، فالتطبيق يعتمد على اتصال WebSocket مفتوح طوال الجلسة، ويتراجع إلى نفق HTTP يبث البيانات باستمرار إذا لم يجد WebSocket، وصفحة ال Reverse Proxy في التوثيق تشدد على إيقاف التخزين المؤقت Buffering، لأن nginx إذا جمع البيانات قبل أن يرسلها فسوف تتجمد الشاشة. والملف nginx/guacamole.conf كما يلي:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name guac.example.com;
location / {
proxy_pass http://guacamole:8080;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
client_max_body_size 1g;
access_log off;
}
}في الإعداد أعلاه لاحظ التالي:
proxy_buffering offهو السطر الذي لا تستغني عنه، والتوثيق يكتبه في تنبيه خاص.- ترويستا
UpgradeوConnectionمعproxy_http_version 1.1تسمحان بترقية الاتصال إلى WebSocket. X-Forwarded-Forينقل عنوان المستخدم الحقيقي إلى Tomcat، وهو ما تعتمد عليه الحماية من التخمين كما سوف نرى، فبدونه يظهر كل المستخدمين بعنوان nginx نفسه.client_max_body_size 1gيسمح برفع الملفات عبر SFTP من المتصفح، وهو المثال الذي يعطيه التوثيق.
وإذا كان لديك Reverse Proxy أصلاً مثل Nginx Proxy Manager، فلا تحتاج إلى nginx إضافي، وإنما اربط Container guacamole بشبكة ال Proxy، وأنشئ Proxy Host إلى guacamole:8080 مع تفعيل خيار Websockets Support، وأضف proxy_buffering off; في الإعدادات المتقدمة Advanced.
ملف docker-compose.yml
هذا الملف مبني على توثيق Docker وتوثيق PostgreSQL للإصدار 1.6.0، بكل ال Images مثبتة على إصدار محدد، وهو كما يلي:
services:
schema:
image: guacamole/guacamole:1.6.0
entrypoint: ["/bin/true"]
volumes:
- initdb:/opt/guacamole/extensions/guacamole-auth-jdbc/postgresql/schema
restart: "no"
network_mode: none
db:
image: postgres:17.11
restart: unless-stopped
depends_on:
schema:
condition: service_completed_successfully
environment:
POSTGRES_DB: guacamole_db
POSTGRES_USER: guacamole_user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
volumes:
- initdb:/docker-entrypoint-initdb.d:ro
- dbdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U guacamole_user -d guacamole_db"]
interval: 10s
timeout: 5s
retries: 10
networks: [backend]
recordings-init:
image: guacamole/guacd:1.6.0
user: "0"
entrypoint: ["/bin/sh", "-c", "chown 1000:1001 /var/lib/guacamole/recordings && chmod 2750 /var/lib/guacamole/recordings"]
volumes:
- recordings:/var/lib/guacamole/recordings
restart: "no"
network_mode: none
guacd:
image: guacamole/guacd:1.6.0
restart: unless-stopped
volumes:
- recordings:/var/lib/guacamole/recordings
depends_on:
recordings-init:
condition: service_completed_successfully
networks: [backend, targets]
guacamole:
image: guacamole/guacamole:1.6.0
restart: unless-stopped
environment:
GUACD_HOSTNAME: guacd
POSTGRESQL_HOSTNAME: db
POSTGRESQL_DATABASE: guacamole_db
POSTGRESQL_USERNAME: guacamole_user
POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
WEBAPP_CONTEXT: ROOT
REMOTE_IP_VALVE_ENABLED: "true"
RECORDING_ENABLED: "true"
TOTP_ENABLED: ${TOTP_ENABLED:-false}
OPENID_ENABLED: ${OPENID_ENABLED:-false}
volumes:
- recordings:/var/lib/guacamole/recordings:ro
depends_on:
db:
condition: service_healthy
guacd:
condition: service_started
networks: [backend, proxy]
proxy:
image: nginx:1.29-alpine
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
volumes:
- ./nginx/guacamole.conf:/etc/nginx/conf.d/default.conf:ro
depends_on: [guacamole]
networks: [proxy]
volumes:
initdb:
dbdata:
recordings:
networks:
backend:
proxy:
targets:في الإعداد أعلاه لاحظ التالي:
- Guacamole لا ينشئ جداوله بنفسه، والتوثيق يقول ذلك صراحة، لذلك نستخدم الطريقة التي يقترحها لـ Docker Compose: Volume اسمه
initdbيأخذ سكربتات إنشاء الجداول من Image guacamole، ثم يقرؤها PostgreSQL من المجلد/docker-entrypoint-initdb.dعند أول تشغيل. والخدمةschemaلا تعمل شيئاً سوى أنها تملأ هذا ال Volume قبل أن يبدأ PostgreSQL، والسبب أن Docker ينسخ محتوى المجلد إلى ال Volume الفارغ عند إنشاء أول Container يستخدمه، فلو بدأ PostgreSQL أولاً لوجد المجلد فارغاً وتجاوز إنشاء الجداول. - الخدمة
recordings-initتضبط مالك مجلد التسجيلات، فguacd يكتب فيه وتطبيق الويب يقرأ منه، والرقمان1000:1001ليسا ما يذكره جدول التوثيق، وسوف نشرح السبب في تسجيل الجلسات. - ثلاث شبكات:
backendبين التطبيق وguacd وقاعدة البيانات، وproxyبين nginx والتطبيق، وtargetsيصل منها guacd إلى السيرفرات، فلا يرى nginx قاعدة البيانات ولا guacd. وفي بيئتك الحقيقية يصل guacd إلى سيرفراتك عبر شبكة السيرفر نفسه، فلا تحتاج إلىtargetsإلا إذا كانت السيرفرات Containers. WEBAPP_CONTEXT: ROOTيجعل التطبيق على المسار/بدلاً من/guacamole/، وهو الأنسب لنطاق فرعي خاص به.REMOTE_IP_VALVE_ENABLEDيفعل RemoteIpValve في Tomcat حتى يقرأ عنوان المستخدم منX-Forwarded-For، وهو مطلوب كما يقول التوثيق كلما كان Guacamole خلف Reverse Proxy.- كل خاصية في
guacamole.propertiesيقابلها متغير بيئة، بتحويل اسمها إلى أحرف كبيرة واستبدال الشرطة-بشرطة سفلية_، وال Image لا يفعل أي إضافة Extension إلا إذا وجد متغيراً يخصها، مثلRECORDING_ENABLEDوTOTP_ENABLED. - المنفذ منشور على
127.0.0.1فقط، وال Reverse Proxy الذي يصدر الشهادة هو الذي يصل إليه، وهذا هو المنفذ الوحيد في الملف كله.
التشغيل ومتابعة السجلات
docker compose up -d
docker compose ps --format 'table {{.Service}}\t{{.Image}}\t{{.Status}}'والمخرج سوف يكون كما يلي، مع اختلاف المدد، ومع الخدمتين التجريبيتين اللتين نضيفهما في السيرفرات التجريبية:
SERVICE IMAGE STATUS
db postgres:17.11 Up 45 minutes (healthy)
desktop-target arabroot-lab-guac-desktop:1 Up 45 minutes
guacd guacamole/guacd:1.6.0 Up 45 minutes (healthy)
proxy nginx:1.29-alpine Up 45 minutes
ssh-target arabroot-lab-guac-ssh:1 Up 45 minutes
guacamole guacamole/guacamole:1.6.0 Up 19 minutesوحتى تتأكد أن الجداول أنشئت فعلاً، اقرأ سجل قاعدة البيانات:
docker compose logs db | grep docker-entrypoint/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/001-create-schema.sql
/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/002-create-admin-user.sql
/usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/upgradeثم سجل تطبيق الويب، وفيه الإضافات التي حملها:
docker compose logs guacamole | grep -E "ExtensionModule|BanningAuth|has started"[ban] "Brute-force Authentication Detection/Prevention" (/tmp/guacamole-home.Ss8KOE6FM0/extensions/guacamole-auth-ban.jar)
[postgresql] "PostgreSQL Authentication" (/tmp/guacamole-home.Ss8KOE6FM0/extensions/guacamole-auth-jdbc-postgresql.jar)
[recording-storage] "Session Recording Storage" (/tmp/guacamole-home.Ss8KOE6FM0/extensions/guacamole-history-recording-storage.jar)
Addresses will be automatically banned for 300 seconds after 5 failed authentication attempts. Up to 10485760 unique addresses will be tracked/banned at any given time.
The Apache Guacamole web application has started.اختصرنا بداية كل سطر من الوقت واسم الكلاس حتى يسهل قراءته. ولاحظ أن إضافة الحماية من التخمين ban محملة دون أن نطلبها، فهي بحسب توثيقها مفعلة افتراضياً في Image Docker منذ الإصدار 1.6.0.
أول دخول، والتخلص من الحساب الافتراضي
افتح http://127.0.0.1:8080/ عبر نفق SSH كما شرحنا في التوجيه المحلي، أو افتح النطاق بعد ربطه بال Reverse Proxy، فتظهر صفحة الدخول:

سكربت إنشاء الجداول يضيف حساب مدير اسمه guacadmin وكلمة مروره guacadmin، وهذا الحساب معروف لكل من قرأ التوثيق، لذلك أنشئ مديراً باسم آخر ثم احذفه، ولا تكتف بتغيير كلمة مروره، والسبب أن من يخمن كلمات المرور يعرف اسم المستخدم مسبقاً. ادخل بـ guacadmin، ثم من القائمة أعلى اليمين اختر Settings ثم Users ثم New User، وأنشئ المستخدم admin بكلمة مرور قوية، وفعل له في قسم Permissions الخيارات Administer system وCreate new users وCreate new user groups وCreate new connections وCreate new connection groups. ثم اخرج وادخل بالحساب الجديد، واحذف guacadmin من قائمة المستخدمين. وبعد الحذف أرسلنا طلب دخول بالحساب القديم من سطر الأوامر عبر واجهة REST API:
curl -s -o /dev/null -w "guacadmin login now: HTTP %{http_code}\n" \
-X POST http://127.0.0.1:8080/api/tokens -d 'username=guacadmin&password=guacadmin'guacadmin login now: HTTP 403إضافة اتصالات SSH وRDP وVNC
الاتصال Connection في Guacamole هو سيرفر واحد ببروتوكول واحد وإعداداته، وتجمع الاتصالات في مجموعات اتصالات Connection Groups حتى تمنح الصلاحية على مجموعة كاملة. أنشئ من Settings ثم Connections مجموعة اسمها Production، ثم أضف داخلها ثلاثة اتصالات بالقيم التالية، وأي حقل غير مذكور يبقى فارغاً:
| الحقل | app-01 (SSH) | desk-01 (RDP) | desk-01 (VNC) |
|---|---|---|---|
| Protocol | SSH | RDP | VNC |
| Hostname / Port | app-01 / 22 | desk-01 / 3389 | desk-01 / 5901 |
| Username / Password | ops وكلمة مروره | desk وكلمة مروره | كلمة مرور VNC فقط |
| إعدادات خاصة | Host key (سوف نشرحه بعد قليل) | Security mode: Any، وIgnore server certificate | لا يوجد |
والصفحة الرئيسية بعد ذلك تعرض المجموعة واتصالاتها:

وعند الضغط على app-01 (SSH) يفتح سطر الأوامر في المتصفح، وكتبنا فيه بعض الأوامر:

وعند الضغط على desk-01 (RDP) يظهر سطح المكتب البعيد، وفتحنا فيه الطرفية:

لا تترك SSH دون التحقق من مفتاح السيرفر
عندما فتحنا اتصال SSH لأول مرة دون أن نضع Host key، كتب guacd في سجله التحذير التالي:
guacd[195]: WARNING: No known host keys provided, host identity will not be verified.وهذا يعني أن guacd سوف يتصل بأي جهاز يرد على العنوان app-01، حتى لو كان جهازاً آخر انتحل العنوان، فيأخذ منه كلمة المرور. والتوثيق في قسم SSH Host Verification يذكر طريقتين: ملف ssh_known_hosts داخل GUACAMOLE_HOME، أو المعامل host-key في كل اتصال، وصيغته سطر واحد من ملف known_hosts. ولكن انتبه لكلمة «سطر واحد»، فقد وضعنا أولاً مفتاح ed25519 فرفض guacd الاتصال:
guacd[24]: ERROR: Host key does not match known hosts entry for app-01
guacd[24]: ERROR: Host key did not match any provided known host keys.ثم وضعنا المفاتيح الثلاثة في أسطر متتالية، فظهر الخطأ نفسه، والسبب في كود guacd نفسه، فهو يقرأ المعامل بالدالة libssh2_knownhost_readline التي تقرأ سطراً واحداً فقط، وguacd في الإصدار 1.6.0 تفاوض على مفتاح ECDSA في تجربتنا. لذلك خذ مفتاح ECDSA من السيرفر:
ssh-keyscan -t ecdsa app-01# app-01:22 SSH-2.0-OpenSSH_10.0p2 Debian-7+deb13u4
app-01 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLrQWcD1Vae9XuUEsYGLXKB7uVufYy2yakdyOt0Uh1hAi5/q1oCnNWPuTDZV/zg4kE/IrXlbVLkScngHYq3SKis=وضع السطر الثاني في حقل Host key من قسم Network في الاتصال، وبعدها اتصل guacd وكتب SSH connection successful دون تحذير. ونفذ ssh-keyscan من جهاز موثوق داخل الشبكة، أو اقرأ الملف /etc/ssh/ssh_host_ecdsa_key.pub من السيرفر نفسه، حتى لا تأخذ المفتاح من الجهاز المنتحل.
السيرفرات التجريبية
إذا أردت أن تجرب قبل أن تربط سيرفراتك الحقيقية، فقد أضفنا إلى ملف Compose نفسه على الشبكة targets خدمتين: سيرفر SSH باسم app-01، وسطح مكتب XFCE باسم desk-01 يقدم RDP عبر xrdp وVNC عبر TigerVNC، وكلاهما مبني على Debian 13:
ssh-target:
build: ./targets/ssh
image: arabroot-lab-guac-ssh:1
hostname: app-01
restart: unless-stopped
networks:
targets:
aliases: [app-01]
desktop-target:
build: ./targets/desktop
image: arabroot-lab-guac-desktop:1
hostname: desk-01
restart: unless-stopped
shm_size: 512mb
networks:
targets:
aliases: [desk-01]وملف targets/desktop/Dockerfile كما يلي، أما سيرفر SSH فهو Debian مع الحزمة openssh-server ومستخدم ops:
FROM debian:13
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
xrdp xorgxrdp xfce4 xfce4-terminal dbus-x11 procps tigervnc-standalone-server tigervnc-tools ssl-cert \
&& rm -rf /var/lib/apt/lists/* \
&& useradd -m -s /bin/bash -G ssl-cert desk && echo 'desk:Lab-Desk-2026' | chpasswd \
&& echo xfce4-session > /home/desk/.xsession && chown desk:desk /home/desk/.xsession \
&& adduser xrdp ssl-cert
COPY start.sh /start.sh
RUN chmod +x /start.sh
EXPOSE 3389 5901
CMD ["/start.sh"]والسكربت start.sh يشغل سطح مكتب VNC على الشاشة :1 ثم xrdp:
#!/bin/sh
D=/home/desk/.config/tigervnc
mkdir -p $D
printf 'Lab-Vnc1\n' | vncpasswd -f > $D/passwd
chmod 600 $D/passwd && chown -R desk:desk /home/desk/.config
su desk -c "vncserver :1 -localhost no -geometry 1280x800 -SecurityTypes VncAuth -xstartup /usr/bin/startxfce4" || true
/usr/sbin/xrdp-sesman
exec /usr/sbin/xrdp --nodaemonولاحظ أن الأداة vncpasswd في Debian 13 موجودة في الحزمة tigervnc-tools لا في tigervnc-standalone-server، وأن TigerVNC 1.15 ينقل إعداداته من ~/.vnc إلى ~/.config/tigervnc، فقد فشل التشغيل الأول عندنا بالرسالتين vncpasswd: not found وCould not migrate /home/desk/.vnc حتى صححنا الأمرين. وهذه السيرفرات للتجربة فقط بكلمات مرور ثابتة، فلا تستخدمها خارج المختبر.
المجموعات والصلاحيات لكل مستخدم
الصلاحيات في Guacamole تمنح على الاتصال أو على مجموعة الاتصالات، لمستخدم أو لمجموعة مستخدمين User Group، والطريقة الأسهل في الإدارة أن تمنحها للمجموعات فقط، ثم تضيف الأشخاص إلى المجموعات، كما شرحنا في التفويض Authorization. أنشأنا من Settings ثم Groups مجموعة guac-ops، وأعطيناها الاتصالين app-01 (SSH) وdesk-01 (RDP) دون VNC، ثم أنشأنا المستخدم omar دون أي صلاحية مباشرة، وأضفناه إلى المجموعة:

ثم تحققنا مما يراه omar عبر ال API، بعد أن أخذنا له توكن Token بالطلب POST /api/tokens:
curl -s -H "Guacamole-Token: $TOKEN" \
http://127.0.0.1:8080/api/session/data/postgresql/connectionGroups/ROOT/treeوبعد تبسيط ال JSON إلى شجرة، كان المخرج كما يلي:
ROOT
Production
app-01 (SSH) [ssh]
desk-01 (RDP) [rdp]وماذا لو حاول omar أن يفتح اتصال VNC برقمه مباشرة؟
curl -s -H "Guacamole-Token: $TOKEN" \
http://127.0.0.1:8080/api/session/data/postgresql/connections/3{"message":"Not found: \"3\"","translatableMessage":{"key":"APP.TEXT_UNTRANSLATED","variables":{"MESSAGE":"Not found: \"3\""}},"statusCode":null,"expected":null,"patches":null,"type":"NOT_FOUND"}لاحظ أن الرد 404 لا 403، فالاتصال الذي لا تملك صلاحيته غير موجود بالنسبة لك أصلاً، فلا يعرف المستخدم حتى أسماء السيرفرات الأخرى. ومن الإعدادات المفيدة في كل اتصال قسم Concurrency Limits، فالحقل Maximum number of connections per user يمنع أن يفتح الشخص نفسه الجلسة مرتين.
تسجيل الجلسات وإعادة تشغيلها من المتصفح
تسجيل الجلسات في Guacamole له جزءان، والتوثيق في صفحة التسجيل يشرحهما: guacd هو الذي يكتب ملف التسجيل أثناء الجلسة، وإضافة recording-storage في تطبيق الويب هي التي تبحث عن الملف وتعرض زر View في سجل الاتصالات. لذلك يحتاج المجلد أن يكون Volume مشتركاً بين guacd وتطبيق الويب كما في الصورة التالية:

وهنا وقعنا في خطأ يستحق أن نذكره، فالتوثيق يقول إن guacd يعمل بالمستخدم 1001 وتطبيق الويب بالمستخدم 1000، فضبطنا المجلد على 1001:1000 كما يقترح، ثم فتحنا جلسة SSH، فكتب guacd:
guacd[41]: ERROR: Creation of recording failed: Permission denied
guacd[41]: ERROR: Creation of typescript failed: Permission deniedوالجلسة نفسها عملت، ولكن دون أي تسجيل، وهذا أخطر ما في الأمر، لأنك قد لا تنتبه إلا عندما تحتاج التسجيل. فسألنا كل Container عن مستخدمه:
docker compose exec guacd id
docker compose exec guacamole iduid=1000(guacd) gid=1000(guacd) groups=1000(guacd)
uid=1001(guacamole) gid=1001(guacamole) groups=1001(guacamole)أي أن الرقمين معكوسان في Images الإصدار 1.6.0 عما في جدول التوثيق، لذلك جعلنا المالك guacd والمجموعة مجموعة تطبيق الويب، أي chown 1000:1001 مع chmod 2750، وهو ما في ملف Compose أعلاه. والرقم 2 في 2750 هو بت setgid الذي يجعل كل مجلد وملف جديد يرث مجموعة المجلد الأب، فيستطيع تطبيق الويب أن يقرأ التسجيلات الجديدة. وتحقق دائماً بالأمر id قبل أن تثق بأي رقم مكتوب.
ثم فعلنا التسجيل في كل اتصال من قسمي Typescript وScreen Recording، والقيمة التي يوصي بها التوثيق للمسار هي ${HISTORY_PATH}/${HISTORY_UUID} مع تفعيل Automatically create path، فينشئ guacd مجلداً لكل جلسة باسم معرفها في السجل:

في الإعداد أعلاه لاحظ التالي:
- Screen Recording يسجل ما ظهر على الشاشة، ويعمل مع SSH وRDP وVNC.
- Typescript خاص بالاتصالات النصية مثل SSH، ويحفظ نص الطرفية مع ملف توقيت، فتقرؤه مثل أي ملف نصي.
- Include key events يضيف ضغطات المفاتيح إلى التسجيل، فيظهر في المشغل سجل بما كتبه المستخدم، ولكن انتبه، فهذا يعني أن كل ما كتبه المستخدم في الجلسة، ومنه كلمة مرور sudo إن كتبها، محفوظ في الملف، لذلك عامل التسجيلات معاملة البيانات الحساسة.
وبعد الجلسات يعرض سجل الاتصالات من Settings ثم History زر View بجانب كل جلسة لها تسجيل، ولاحظ الجلسات الأولى التي لا زر لها، فهي الجلسات التي فشل فيها التسجيل بسبب المالك الخاطئ أو رفضها guacd بسبب مفتاح السيرفر:

وعند الضغط على View يفتح المشغل، ومنذ الإصدار 1.6.0 يعرض سجل المفاتيح Keystroke Log بجانب الشاشة مع رسم لنشاط الشاشة ولوحة المفاتيح على شريط التقدم، فتعرف أين تبحث في جلسة طويلة:

والملفات نفسها على ال Volume، وهذا محتوى مجلد جلسة SSH:
docker compose exec guacd ls -l /var/lib/guacamole/recordings/85d86ec5-3205-399c-b874-f3dcbc0f862btotal 80
-rw-r----- 1 guacd 1001 70446 Oct 8 14:43 recording
-rw-r----- 1 guacd 1001 505 Oct 8 14:43 typescript
-rw-r----- 1 guacd 1001 1217 Oct 8 14:43 typescript.timingوملف typescript نص عادي، وبعد حذف رموز التحكم في الطرفية منه بالأمر sed يظهر كما يلي:
docker compose exec guacd sh -c "sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g' /var/lib/guacamole/recordings/85d86ec5-3205-399c-b874-f3dcbc0f862b/typescript | tr -d '\r'"[BEGIN TYPESCRIPT]
Last login: Thu Oct 8 14:41:42 2026 from 192.168.112.4
ops@app-01:~$ whoami; hostname; cat /etc/debian_version
ops
app-01
13.7
ops@app-01:~$ ps -o pid,user,cmd -C sshd
PID USER CMD
1 root sshd: /usr/sbin/sshd -D -e [listener] 0 of 10-100 startups
ops@app-01:~$ sudo -n true || echo no-sudo-here
-bash: sudo: command not found
no-sudo-here
ops@app-01:~$ exit
logout
[END TYPESCRIPT]ولاحظ أن عنوان الدخول في السطر الأول هو عنوان guacd، لا عنوان المستخدم، فالسيرفر لا يرى إلا البوابة، والذي يربط الجلسة بالشخص هو سجل Guacamole. وأحجام تسجيلات الشاشة عندنا كانت 72K لجلسة SSH قصيرة و328K لجلسة RDP مدتها دقيقة ونصف، وهي تكبر مع حركة الشاشة، لذلك ضع للتسجيلات سياسة حفظ وحذف واضحة، وانقلها إلى تخزين خارج السيرفر كما في النسخ الاحتياطي.
المصادقة الثنائية TOTP
إضافة TOTP تضيف رمزاً من تطبيق المصادقة بعد كلمة المرور، وتحتاج إلى قاعدة بيانات لأنها تحفظ مفتاح كل مستخدم فيها. غير السطر في .env إلى TOTP_ENABLED=true ثم أعد إنشاء ال Container:
docker compose up -d guacamole
docker compose logs guacamole | grep totp[totp] "TOTP TFA Authentication Backend" (/tmp/guacamole-home.kpZJFo7ODe/extensions/guacamole-auth-totp.jar)
Extension "TOTP TFA Authentication Backend" (totp) loaded.وعند الدخول التالي يطلب Guacamole من كل مستخدم أن يسجل تطبيق المصادقة برمز QR، ولا يدخل حتى يكتب رمزاً صحيحاً:

وبعد التسجيل يرفض Guacamole الدخول بكلمة المرور وحدها، وهذا رد ال API:
{
"message": "A TOTP authentication code is required before login can continue",
"translatableMessage": {
"key": "TOTP.INFO_CODE_REQUIRED",
"variables": null
},
"statusCode": null,
"expected": [
{
"name": "guac-totp",
"type": "GUAC_TOTP_CODE"
}
],
"patches": null,
"type": "INSUFFICIENT_CREDENTIALS"
}ومنذ الإصدار 1.6.0 تستطيع أن تلغي TOTP لمستخدم أو لمجموعة بعينها، وأن تفرضه أو تتجاوزه حسب عنوان المستخدم بالمتغيرين TOTP_ENFORCE_HOSTS وTOTP_BYPASS_HOSTS. وإذا فقد مستخدم هاتفه، فمن صفحة المستخدم اختر Clear TOTP secret، فيسجل من جديد عند دخوله التالي.
الدخول الموحد عبر OpenID Connect مع authentik
إذا كان لديك مزود هوية كما في دليل authentik، فالأفضل أن يدخل الموظفون إلى Guacamole بحساباتهم نفسها، فإذا غادر أحدهم أغلقت حسابه في مكان واحد. وقد شرحنا OpenID Connect نفسه في OAuth وOpenID Connect للمبتدئين، والصورة التالية تبين ما يحدث هنا بالتحديد:

لاحظ الخطوة الثانية، فعندما طلبنا رابط الدخول من Guacamole كان التحويل إلى authentik بالمعامل response_type=id_token، أي أن إضافة OpenID في Guacamole 1.6.0 تستخدم التدفق الضمني Implicit Flow، وتوثيق authentik يصف هذا التدفق بأنه قديم Legacy ولا يوصي به للتطبيقات الجديدة، ولكنه ما يدعمه Guacamole اليوم.
الإعداد في authentik
من لوحة الإدارة أنشئ Application باسم Guacamole ومعه Provider من النوع OAuth2/OpenID Provider بالقيم التالية:
- Client type: Public، لأن التدفق الضمني لا يستخدم Client Secret.
- Client ID: قيمة تختارها، مثل
guacamole. - Redirect URI: نوع Strict بالقيمة
https://guac.example.com/، أي العنوان الذي يكتبه المستخدم بالضبط. - Authorization flow:
default-provider-authorization-implicit-consent. - Scopes: openid وemail وprofile، وsigning key: الشهادة الافتراضية authentik Self-signed Certificate.
- Grant types: تأكد أن Implicit مفعل، وهو مفعل افتراضياً في الواجهة.
ثم أنشئ في authentik مجموعة باسم guac-ops، أي الاسم نفسه الذي أنشأناه في Guacamole، وأضف إليها المستخدمين.
الإعداد في Guacamole
غير في .env السطر إلى OPENID_ENABLED=true، وأضف إلى environment في خدمة guacamole المتغيرات التالية، وقيمها كلها من الملف https://auth.example.com/application/o/guacamole/.well-known/openid-configuration:
OPENID_AUTHORIZATION_ENDPOINT: https://auth.example.com/application/o/authorize/
OPENID_JWKS_ENDPOINT: https://auth.example.com/application/o/guacamole/jwks/
OPENID_ISSUER: https://auth.example.com/application/o/guacamole/
OPENID_CLIENT_ID: guacamole
OPENID_REDIRECT_URI: https://guac.example.com/
OPENID_USERNAME_CLAIM_TYPE: preferred_username
EXTENSION_PRIORITY: "*, openid"في الإعداد أعلاه لاحظ التالي:
OPENID_ISSUERيجب أن يطابق حرفاً بحرف قيمةissuerفي ملف الاكتشاف، بما فيها الشرطة المائلة في آخره.OPENID_JWKS_ENDPOINTيطلبه Container guacamole نفسه لا المتصفح، فيجب أن يصل إليه من داخل الشبكة. وفي تجربتنا كان authentik على السيرفر نفسه، فوضعنا للمفاتيح عنوانه الداخلي في شبكة Docker، وأبقينا المصدر Issuer وعنوان التحويل على العنوان الذي يراه المتصفح.- الافتراضي لاسم المستخدم هو claim
email، وغيرناه إلىpreferred_usernameحتى يظهر الاسمsaraلا البريد. والمجموعات تقرأ افتراضياً من claimgroups، وauthentik يضعه في التوكن. EXTENSION_PRIORITY: "*, openid"يجعل OpenID آخر الإضافات، فتبقى صفحة الدخول العادية ومعها رابط Sign in with OpenID، وهذا مهم حتى يبقى حساب المدير المحلي يعمل إذا توقف authentik. وإذا جعلت القيمةopenidوحدها، فيحول كل زائر إلى authentik مباشرة.
ثم docker compose up -d guacamole، فتظهر صفحة الدخول ومعها الرابط في أسفلها:


وبعد الدخول بالمستخدم sara عاد بنا authentik إلى Guacamole، فظهر لها الاتصالان اللذان تملكهما المجموعة guac-ops، مع أن sara ليس لها أي حساب في قاعدة بيانات Guacamole:

وهذا ما كتبه Guacamole في سجله:
INFO o.a.g.auth.jdbc.user.UserService - Data cannot be stored for user "sara" as they do not have an account within the database. If this is unexpected, consider allowing automatic creation of user accounts.
INFO o.a.g.event.EventLoggingListener - User "sara" (authenticated by "openid") successfully authenticated from 192.168.80.1والسطر الأول مهم جداً، فإضافة TOTP كانت مفعلة، ومع ذلك لم يطلب Guacamole من sara رمزاً، والسبب أن TOTP يحفظ مفتاح المستخدم في قاعدة البيانات، وsara ليس لها حساب فيها. لذلك عندما تستخدم الدخول الموحد، اجعل المصادقة الثنائية إلزامية في authentik نفسه كما شرحنا في فرض المصادقة الثنائية، ولا تعتمد على TOTP في Guacamole لمستخدمي SSO.
كيف تقوي Guacamole؟
Guacamole باب إلى كل سيرفراتك، فما يصل إليه المخترق منه يصل إلى كل ما خلفه، لذلك عامله كما تعامل ال Bastion في كيف تقوي ال Bastion، وفيما يلي ما يخصه:
لا تنشر غير منفذ ال Reverse Proxy
لا تنشر 8080 ولا 4822 ولا منفذ قاعدة البيانات، وإذا كان المستخدمون موظفين فقط، فضع Guacamole نفسه خلف VPN أو خلف Forward Auth حتى لا تظهر صفحة دخوله للإنترنت أصلاً.
الحماية من التخمين Brute Force مفعلة، فتأكد أنها ترى العنوان الصحيح
أرسلنا ست كلمات مرور خاطئة متتالية:
for i in 1 2 3 4 5 6; do
curl -s -o /dev/null -w "attempt $i: HTTP %{http_code}\n" \
-X POST http://127.0.0.1:8080/api/tokens -d 'username=admin&password=wrong-guess'
doneattempt 1: HTTP 403
attempt 2: HTTP 403
attempt 3: HTTP 403
attempt 4: HTTP 403
attempt 5: HTTP 429
attempt 6: HTTP 429وكتب Guacamole في سجله:
Authentication has failed for address "192.168.80.1" (current total failures: 5/5).
Blocking authentication attempt from address "192.168.80.1" due to number of authentication failures.بعد الفشل الخامس أصبح الرد 429 Too Many Requests، والحظر لخمس دقائق بحسب الإعداد الافتراضي، وتغيره بالمتغيرين BAN_MAX_INVALID_ATTEMPTS وBAN_ADDRESS_DURATION. ولاحظ أن الحظر مبني على العنوان، فإذا لم يصل X-Forwarded-For أو لم يفعل RemoteIpValve فسوف يرى Guacamole كل المستخدمين بعنوان ال Proxy، فيحظرهم جميعاً بسبب شخص واحد. ولاحظ أيضاً أن الحظر محفوظ في الذاكرة، فإعادة تشغيل ال Container تمسحه.
كلمات مرور الاتصالات محفوظة كما هي في قاعدة البيانات
هذا ما وجدناه عندما استعلمنا الجدول مباشرة:
docker compose exec db psql -U guacamole_user -d guacamole_db -c \
"select c.connection_name, p.parameter_name, p.parameter_value from guacamole_connection_parameter p join guacamole_connection c using(connection_id) where p.parameter_name='password';" connection_name | parameter_name | parameter_value
-----------------+----------------+-----------------
desk-01 (RDP) | password | Lab-Desk-2026
desk-01 (VNC) | password | Lab-Vnc1
app-01 (SSH) | password | Lab-Ops-2026
(3 rows)لذلك من يصل إلى قاعدة البيانات أو إلى نسخها الاحتياطية يصل إلى كل السيرفرات. والبديل أن تترك حقل كلمة المرور فارغاً فيسأل Guacamole المستخدم عنها عند الاتصال، أو أن تستخدم متغيرات المعاملات مثل ${GUAC_USERNAME} و${GUAC_PASSWORD} التي يشرحها قسم Parameter tokens، فيدخل كل شخص بحسابه هو على السيرفر، وهذا أفضل للتدقيق أيضاً، لأن الحساب المشترك ops لا يقول لك من نفذ الأمر.
حدث باستمرار
صفحة التقارير الأمنية تذكر مثلاً الثغرة CVE-2024-35164 التي أصلحها الإصدار 1.6.0، وفيها أن محاكي الطرفية في 1.5.5 وما قبله لا يتحقق من رموز التحكم القادمة من سيرفر SSH، فيستطيع مستخدم له اتصال نصي أن يرسل تسلسلاً يشغل كوداً بصلاحيات guacd. والمعنى العملي أن guacd يعالج بيانات قادمة من السيرفرات نفسها، فسيرفر مخترق قد يصبح طريقاً إلى البوابة.
مدة الجلسة Session Timeout
السجل يذكر Sessions will expire after 60 minutes of inactivity، وتغيرها بالمتغير API_SESSION_TIMEOUT المقابل للخاصية api-session-timeout.
قلل ما يسمح به كل اتصال
في كل اتصال خيارات مثل تعطيل الحافظة Clipboard وتعطيل رفع الملفات وتنزيلها عبر SFTP، فأغلق ما لا يحتاجه المستخدم، خاصة للموردين الخارجيين.
التحقق من النجاح
docker compose psيعرض db وguacd بحالة healthy، والخدمتان schema وrecordings-init بحالةExited (0).- من جهاز خارجي، لا يرد السيرفر على المنافذ 8080 و4822 و5432، ويرد فقط على 443 عبر ال Reverse Proxy.
- الدخول بـ
guacadminيعطي HTTP 403. - سجل guacd لا يحوي
No known host keys providedولاPermission deniedبعد فتح جلسة. - كل جلسة جديدة في History لها زر View.
- الدخول بكلمة المرور وحدها يعطي
TOTP.INFO_CODE_REQUIRED، والدخول عبر OpenID يطلب MFA من authentik.
النسخ الاحتياطي والاستعادة
ما تحتاج إلى نسخه شيئان: قاعدة البيانات، وفيها المستخدمون والاتصالات وكلمات مرورها وسجل الجلسات ومفاتيح TOTP، و Volume التسجيلات. أما guacd وتطبيق الويب فلا يحملان بيانات. والأوامر كما يلي:
mkdir -p backup
docker compose exec -T db pg_dump -U guacamole_user -d guacamole_db --clean --if-exists | gzip > backup/guacamole-db.sql.gz
docker run --rm -v guacamole_recordings:/data:ro -v "$PWD/backup:/backup" debian:13 \
tar -C /data -czf /backup/recordings.tar.gz .
du -h backup/*8.0K backup/guacamole-db.sql.gz
320K backup/recordings.tar.gzاسم ال Volume يبدأ باسم مشروع Compose، وهو اسم المجلد افتراضياً، فتأكد منه بالأمر docker volume ls. والنسخة التي لم تستعدها مرة لا تعرف هل تعمل، لذلك استعدناها في Container PostgreSQL مؤقت وتحققنا من محتواها:
docker run -d --name guac-restore-test -e POSTGRES_PASSWORD=restore-test \
-e POSTGRES_USER=guacamole_user -e POSTGRES_DB=guacamole_db postgres:17.11
gunzip -c backup/guacamole-db.sql.gz | docker exec -i guac-restore-test psql -q -U guacamole_user -d guacamole_db
docker exec guac-restore-test psql -U guacamole_user -d guacamole_db \
-c "select connection_name, protocol from guacamole_connection order by connection_id;"
docker rm -f guac-restore-test connection_name | protocol
-----------------+----------
app-01 (SSH) | ssh
desk-01 (RDP) | rdp
desk-01 (VNC) | vnc
(3 rows)وبما أن النسخة تحوي كلمات مرور الاتصالات كما رأينا ومفاتيح TOTP، فشفرها قبل أن ترسلها خارج السيرفر، وطبق عليها قاعدة 3-2-1 كما في النسخ الاحتياطي بقاعدة 3-2-1.
التحديث إلى إصدار أحدث
- اقرأ ملاحظات الإصدار الجديد في صفحة الإصدارات، وصفحة التقارير الأمنية.
- خذ نسخة احتياطية من قاعدة البيانات كما في القسم السابق.
- غير الوسم في الخدمات الثلاث
schemaوguacdوguacamoleمعاً، ولا تستخدمlatest، حتى يبقى guacd وتطبيق الويب على الإصدار نفسه. - سكربتات إنشاء الجداول لا تعمل إلا على قاعدة بيانات فارغة، أما الترقية فبحسب توثيق PostgreSQL تحتاج إلى تشغيل سكربتات
upgrade-pre-VERSION.sqlبالترتيب من المجلدschema/upgradeلكل إصدار أحدث من إصدارك، ثم إعادة منح الصلاحيات على الجداول الجديدة. والإصدار 1.6.0 نفسه فيه السكربتupgrade-pre-1.6.0.sql، فمن يرقي من 1.5.x يحتاج إليه. docker compose pull && docker compose up -d، ثم راجع السجلات وافتح جلسة وتأكد من التسجيل.
مشكلات شائعة وحلولها
الجلسة تعمل ولكن لا يوجد تسجيل، وفي سجل guacd «Permission denied»
مالك مجلد التسجيلات لا يطابق مستخدم guacd، والسبب غالباً أنك أخذت الأرقام من جدول التوثيق. شغل id داخل guacd وguacamole، واجعل المالك مستخدم guacd والمجموعة مجموعة تطبيق الويب، وفي 1.6.0 هي 1000:1001 مع chmod 2750.
«Host key does not match known hosts entry»
إما أن مفتاح السيرفر تغير فعلاً، وهنا توقف وتحقق قبل أن تغير الحقل، وإما أنك وضعت نوع مفتاح غير الذي يتفاوض عليه guacd أو أكثر من سطر. ضع سطر ECDSA واحداً كما في التحقق من مفتاح السيرفر.
authentik يعيدك إلى Guacamole برسالة «The request is otherwise malformed»
عندما أنشأنا المزود عبر ال API دون أن نحدد أنواع المنح Grant types، عاد المتصفح إلى Guacamole بالرابط #error=invalid_request&error_description=The%20request%20is%20otherwise%20malformed، وكان في سجل authentik:
{'event': 'Invalid grant_type for provider', 'grant_type': 'implicit', 'level': 'warning'}
{'event': 'The request is otherwise malformed', 'level': 'warning'}فعل Implicit في Grant types للمزود، لأن Guacamole يطلب response_type=id_token.
صفحة الدخول تظهر ولكن الجلسة سوداء أو تتجمد
هذا ما يحذر منه توثيق ال Reverse Proxy عندما يجمع ال Proxy البيانات قبل إرسالها، فتأكد من proxy_buffering off ومن ترويستي Upgrade وConnection، وفي Nginx Proxy Manager من خيار Websockets Support.
الدخول يفشل دائماً بعد تثبيت جديد
إذا لم تنشأ الجداول، فلن تجد في سجل db سطري 001-create-schema.sql و002-create-admin-user.sql. والسبب المعتاد أن قاعدة البيانات بدأت قبل أن يمتلئ Volume initdb، أو أن Volume dbdata كان موجوداً من تجربة سابقة، فسكربتات /docker-entrypoint-initdb.d لا تعمل إلا على مجلد بيانات فارغ.
كل المستخدمين محظورون بعد أن أخطأ شخص واحد
Guacamole يرى عنوان ال Reverse Proxy بدل عناوين المستخدمين، فتأكد من REMOTE_IP_VALVE_ENABLED: "true" ومن أن ال Proxy يرسل X-Forwarded-For، وإذا كان ال Proxy خارج الشبكات الخاصة فحدد عنوانه في PROXY_ALLOWED_IPS_REGEX.
اتصال RDP يفشل فوراً
جرب Security mode بالقيمة Any، وفعل Ignore server certificate إذا كانت شهادة السيرفر موقعة ذاتياً، ثم اقرأ سجل guacd بالأمر docker compose logs guacd، فهو يذكر سبب الرفض. وتأكد أن guacd يصل إلى المنفذ 3389 من شبكته، لا من جهازك.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- Guacamole ثلاثة أجزاء: تطبيق ويب يتولى الدخول والصلاحيات، وguacd يتكلم SSH وRDP وVNC، وقاعدة بيانات، والمتصفح لا يتصل بالسيرفرات أبداً.
- لا تنشر غير منفذ ال Reverse Proxy، وأوقف Buffering وفعل WebSocket، وفعل RemoteIpValve حتى يرى Guacamole عناوين المستخدمين الحقيقية.
- احذف
guacadminبعد إنشاء مدير باسم آخر، وامنح الصلاحيات لمجموعات لا لأشخاص. - ضع مفتاح السيرفر في Host key لكل اتصال SSH، وفي 1.6.0 سطراً واحداً من نوع ECDSA.
- التسجيل يحتاج مجلداً مشتركاً مالكه guacd ومجموعته تطبيق الويب، وتحقق من الأرقام بالأمر
id، وراجع أن كل جلسة لها زر View. - فعل TOTP للحسابات المحلية، أما مستخدمو SSO فاجعل MFA إلزامية في مزود الهوية، لأن Guacamole لا يحفظ مفتاح TOTP لمن ليس له حساب محلي.
- قاعدة البيانات تحوي كلمات مرور الاتصالات كما هي، فاحمها وشفر نسخها، أو اجعل المستخدم يكتب كلمة مروره عند الاتصال.
دليلBastion Host أم Jump Server؟ نقطة الدخول الآمنة إلى سيرفراتكلماذا لا يفتح كل سيرفر منفذ الإدارة للإنترنت، وكيف تبني نقطة دخول واحدة مقواة تمر عبرها إلى سيرفراتك بأمان.
دليلأدوات الوصول الآمن إلى السيرفرات: خريطة مفتوحة المصدرخريطة لأدوات الوصول الآمن مفتوحة المصدر إلى السيرفرات، ماذا تحل كل عائلة منها وما ترخيصها، وكيف تختار ما يناسب فريقك.
دليلتثبيت authentik للدخول الموحد SSO إلى تطبيقاتككيف تثبت authentik حتى يدخل كل موظف إلى تطبيقات الفريق بحساب واحد، مع فرض المصادقة الثنائية على الجميع.
دليلإدارة الهوية والوصول IAM والوصول المميز PAMمن يملك صلاحية root على سيرفراتك ومنذ متى، وكيف تضبط حسابات المديرين بصلاحيات مؤقتة وجلسات مسجلة.
دليلالنسخ الاحتياطي بقاعدة 3-2-1 واختبار الاستعادةكيف تحدد كم بيانات تقبل أن تخسر، وتبني نسخاً لا يستطيع المخترق حذفها، وتتأكد أنك تستطيع استعادتها فعلاً.سجل التحديثات
- أكتوبر 2026: كتابة الدليل واختباره على Apache Guacamole 1.6.0 مع PostgreSQL 17.11 وnginx 1.29.8 وauthentik 2026.8.3، وسيرفرات تجريبية على Debian 13.7، وDocker Engine 29.8.0 مع Docker Compose v5.5.1.