- Đây là quy trình triển khai SSO chạy Keycloak cách ly bằng rootless Docker trên một Linux VM duy nhất, trong khi nginx hệ thống đảm nhận kết thúc TLS và reverse proxy
- Điều kiện tiên quyết gồm quyền truy cập SSH, domain hoặc subdomain cho Keycloak, bản ghi
A, người dùng keycloak không cần mật khẩu, thư mục home /srv/keycloak, và cài đặt Docker rootless
docker-compose.yml sử dụng postgres:16 và quay.io/keycloak/keycloak:25.0.1; Keycloak chỉ bind vào 127.0.0.1:8080, còn mật khẩu và KC_HOSTNAME được quản lý trong .env
- nginx chuyển hướng các yêu cầu HTTP của
your.tld.com sang HTTPS và chuyển tiếp bằng proxy_pass; sau khi cấp chứng chỉ Certbot thì bật KC_PROXY_HEADERS: xforwarded
- Nếu cần theme tùy chỉnh hoặc chạy với
--optimized, hãy tạo image riêng bằng Dockerfile multi-stage; trong vận hành có thể tinh chỉnh thêm kích thước buffer JGroups và cấu hình khôi phục transaction của Quarkus
Cấu trúc triển khai và điều kiện tiên quyết
- Keycloak là một lựa chọn IAM mã nguồn mở có thể dùng khi khó tự triển khai quản lý người dùng và SSO trong ứng dụng web
- Tương tác được với các giao thức SSO chính như
OpenID Connect (OIDC), OAuth 2.0, SAML
- Cấu hình được viết dựa trên Keycloak
23.0.6 và đã được kiểm thử đến 25.0.5
- Kiến trúc tổng thể đặt nginx làm reverse proxy trung tâm, chuyển traffic qua localhost tới namespace Docker rootless của từng dịch vụ
- Traffic web đi vào qua
0.0.0.0:80 và 0.0.0.0:443, rồi nginx chuyển tiếp tới Keycloak tại 127.0.0.1:8080
- Mục tiêu là một cấu hình tiết kiệm, chia sẻ tài nguyên của một host duy nhất nhưng vẫn tách biệt môi trường dịch vụ
Chuẩn bị
- Môi trường cơ bản cần có Linux VM, truy cập từ xa qua SSH, domain hoặc subdomain cho dịch vụ Keycloak
- Domain cần có bản ghi
A, và có thể thêm bản ghi AAAA nếu muốn
- Thiết lập Docker rootless cơ bản làm theo quy trình trong một bài Mastodon riêng
- Tạo người dùng non-root mới
keycloak không cần mật khẩu
- Đặt thư mục home là
/srv/keycloak
- Thêm dải cho người dùng
keycloak vào /etc/subuid và /etc/subgid
- Cài Docker rootless bằng
dockerd-rootless-setuptool.sh
- Cấu hình tự động khởi động service cho người dùng
keycloak
Chạy Keycloak bằng Docker Compose
- Khi vào bằng người dùng
keycloak mới tạo, dùng machinectl shell keycloak@
- Tránh cách
sudo -u keycloak -H bash vì biến môi trường XDG_RUNTIME_DIR chưa được chuẩn bị
- Trước tiên tạo thư mục cho dữ liệu bền vững và các file Docker
- Đường dẫn dữ liệu PostgreSQL là
/srv/keycloak/data/postgres16
- Các file Docker đặt tại
~/docker
docker-compose.yml dùng trực tiếp image Keycloak chính thức và PostgreSQL
- Image PostgreSQL là
postgres:16
- Image Keycloak là
quay.io/keycloak/keycloak:25.0.1
- Trong môi trường production, nên dùng tag image cụ thể thay vì
:latest
- Các thiết lập chính của container Keycloak như sau
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres_db/keycloak
KC_HOSTNAME: ${KC_HOSTNAME:-your.tld.com}
KC_HTTP_ENABLED: true
HTTP_ADDRESS_FORWARDING: true
KEYCLOAK_ADMIN: admin
- Port chỉ bind vào localhost với
'127.0.0.1:8080:8080'
.env chứa các giá trị nhạy cảm hoặc thay đổi theo môi trường
POSTGRES_PASSWORD
KEYCLOAK_ADMIN_PASSWORD
KC_HOSTNAME
- Cú pháp
${KC_HOSTNAME:-your.tld.com} dùng giá trị đó nếu .env có KC_HOSTNAME, nếu không thì dùng your.tld.com làm mặc định
/srv/keycloak/data/postgres16 chứa dữ liệu bền vững của PostgreSQL, nên cần được sao lưu định kỳ
- Nếu muốn khởi tạo lại hoàn toàn, hãy xóa rồi tạo lại thư mục PostgreSQL đó; lần khởi động tiếp theo sẽ tạo lại dữ liệu
- Nếu quản lý
~/docker bằng Git repository, hãy thêm .env vào .gitignore và chỉ commit docker-compose.yml
Kiểm thử cục bộ
- Khởi động stack Docker Compose và xem log
docker compose up -d && docker compose logs --follow
- Để truy cập port Keycloak cục bộ của VM, tạo reverse SSH tunnel
ssh you@111.11.11.11 -L :8080:127.0.0.1:8080 -p 22 -N -v
- Mở
127.0.0.1:8080 trong trình duyệt để kiểm tra màn hình chào mừng của Keycloak
Reverse proxy nginx và TLS
- Sau khi đăng xuất khỏi người dùng
keycloak, tiến hành cấu hình nginx hệ thống
- Thay
your.tld.com bằng domain thực tế, rồi thêm bản ghi A tại nhà đăng ký domain để DNS query trỏ về IP của VM
- Tạo và kích hoạt file cấu hình site nginx
nano /etc/nginx/sites-available/your.tld.com.conf
ln -s /etc/nginx/sites-available/your.tld.com.conf /etc/nginx/sites-enabled/
- Cấu hình nginx chuyển hướng yêu cầu HTTP sang HTTPS, và proxy tới Keycloak trong block server HTTPS
proxy_set_header Host $host
proxy_set_header X-Real-IP $remote_addr
proxy_set_header X-Forwarded-For $remote_addr
proxy_set_header X-Forwarded-Proto $scheme
proxy_pass http://127.0.0.1:8080
- Với cấu hình SSL, khuyến nghị dùng Mozilla SSL configurator for nginx để tạo các giá trị mặc định phù hợp với phiên bản nginx
- Kiểm tra cấu hình rồi reload nginx
nginx -t
systemctl reload nginx
- Khi cấp chứng chỉ bằng Certbot, các dòng cần thiết trong
your.tld.com.conf sẽ được tự động cập nhật
certbot --nginx -d your.tld.com
- Sau đó bật
ssl_trusted_certificate /etc/letsencrypt/live/your.tld.com/chain.pem; và reload nginx
- Để chạy phía sau nginx, bật
KC_PROXY_HEADERS: xforwarded trong docker-compose.yml, rồi khởi động lại Docker stack
docker compose down && docker compose up -d && docker compose logs --follow
Đường dẫn debug
- Sau khi cấu hình, truy cập
your.tld.com và xác nhận đăng nhập người dùng admin bằng mật khẩu trong .env
- Điểm kiểm tra đầu tiên là log Docker Compose
docker compose logs --follow
- Theo dõi log truy cập và log lỗi nginx ở các file sau
tail -f /var/log/nginx/your.tld.com-access.log
tail -f /var/log/nginx/your.tld.com-error.log
- Nếu cần kiểm tra trực tiếp database của Keycloak, vào bằng người dùng
keycloak rồi chạy psql trong container PostgreSQL
machinectl shell keycloak@
cd ~/docker
docker compose exec postgres_db /bin/bash
psql -h localhost -p 5432 -U keycloak keycloak
Image tùy chỉnh và chạy optimized
- Cấu hình cơ bản dùng image build sẵn từ
quay.io
- Nếu cần tùy chỉnh theme hoặc chạy ở chế độ
--optimized, hãy build image riêng
- Dockerfile dùng multi-stage build dựa trên image Keycloak chính thức
- Thiết lập
ENV KC_DB=postgres ở stage builder
- Chạy
/opt/keycloak/bin/kc.sh build
- Sao chép
/opt/keycloak/ sang image cuối
- Chỉ định
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
- Trong
docker-compose.yml, thay đổi như sau
- Xóa hoặc comment dòng
image:
- Bật
build: .
- Thêm
command: start --optimized
- Sau đó hạ Docker stack xuống, tùy chọn chạy build tường minh, rồi khởi động lại
docker compose down
docker compose build
docker compose up -d && docker compose logs --follow
Các mục tinh chỉnh thêm trong môi trường vận hành
- Cảnh báo buffer nhận
MulticastSocket liên quan đến JGroups được xử lý bằng cách thêm giá trị buffer vào /etc/sysctl.conf của host
net.core.rmem_max = 26214400
net.core.wmem_max = 1048576
- Chạy
sysctl -p để áp dụng cấu hình
- Cảnh báo khôi phục transaction XA của Quarkus được xử lý bằng cách thêm volume mount và biến môi trường cho service Keycloak
- Volume:
/srv/keycloak/data/keycloak/ObjectStore/:/ObjectStore/
- Biến môi trường:
QUARKUS_TRANSACTION_MANAGER_ENABLE_RECOVERY: true
- Trước khi khởi động, tạo thư mục ObjectStore trên host và đổi chủ sở hữu
/ObjectStore bên trong container thành user ID 1000
Các bước sau khi cấu hình
- Trạng thái cuối cùng là dịch vụ Keycloak trong rootless Docker chạy phía sau reverse proxy nginx hệ thống, còn nginx đảm nhận SSL termination
- Với cập nhật container tự động, tham khảo quy trình cập nhật tự động trong một bài Mastodon riêng
- Bước cấu hình tiếp theo là thêm email trong console quản trị Keycloak
- Sau đó có thể tiếp tục thêm realm và cấu hình theme
- Có thể dùng keycloakify và keycloakify-starter cho theme
- Dockerfile có chứa một dòng đã được comment để sao chép JAR theme keycloakify vào
/opt/keycloak/providers/
1 bình luận
Ý kiến trên Hacker News
Gần đây khi thêm xác thực cho homelab, tôi đã chọn Authelia
Keycloak cũng hoạt động, nhưng quá đồ sộ; nếu muốn dùng cùng traefik forward auth thì còn cần thêm dịch vụ bổ sung
Authelia không có UI để chỉnh sửa người dùng và cũng không đồng bộ hai chiều với máy chủ LDAP backend, nhưng có thể cấu hình chỉ bằng tệp tĩnh và biến môi trường, nên phù hợp trong nhiều trường hợp
Nếu chỉ là thêm xác thực cho một vài dịch vụ và gắn SSO vào những dịch vụ hỗ trợ, thì đáng bắt đầu thử với Authelia
Nếu cần SSO, cấu hình dễ và UI quản trị, FusionAuth cũng đáng xem xét. UI/UX hơi có cảm giác giữa thập niên 2000, nhưng có thể tải về chạy trực tiếp[0], thân thiện với Docker[1], và cung cấp nhiều cách cấu hình[2], chẳng hạn quản lý OIDC hoặc các thiết lập khác bằng Terraform[3]
Có thể dùng miễn phí, nhưng không phải mã nguồn mở[4]
0: https://fusionauth.io/download
1: https://fusionauth.io/docs/get-started/download-and-install/...
2: https://fusionauth.io/docs/operate/deploy/configuration-mana...
3: https://fusionauth.io/docs/operate/deploy/terraform
4: https://fusionauth.io/license-faq#28
Tôi dùng Caddy làm reverse proxy và tích hợp với Authelia[1], chạy rất ổn
Tôi đã thêm xác thực hai yếu tố vững chắc cho mọi dịch vụ, và cảm thấy đủ an toàn ngay cả khi truy cập các app tự host mà không cần VPN
Tuy nhiên, Authelia đã hơn 1 năm chưa có bản phát hành mới nên tôi hơi lo về mặt bảo mật
[0] https://github.com/lldap/lldap
[1] https://www.authelia.com/integration/proxies/caddy/
[1] https://github.com/greenpau/caddy-security
Đây là một use case rất tốt cho môi trường homelab, đặc biệt nếu bạn không cần hoặc không muốn các tính năng của những giải pháp lớn hơn
Tìm mãi vẫn không thấy dạng nào như schema đơn giản, JSON, HTTP; thật khó tin là hầu như mọi thứ đều đã được người ta làm lại, nhưng LDAP thì không
Theo tôi biết, tiêu chuẩn khác trên thực tế chỉ có Active Directory
Tôi cũng tò mò liệu có tiêu chuẩn hay giao thức nào để chuyển phần kiểm soát truy cập ra bên ngoài không
Authelia có thể kiểm soát truy cập bằng mẫu URL, nhưng chẳng hạn với một file server, tôi sẽ kỳ vọng nó kiểm tra quyền với máy chủ LDAP dựa trên ID người dùng đã xác thực và khóa trong cơ sở dữ liệu
Điều này có vẻ đi theo hướng ngược lại với OAuth2, nơi máy chủ nhận quyền truy cập vào dịch vụ bên thứ ba
Gần đây tôi đã tìm hiểu để dựng một SSO tương đối đơn giản cho homelab, mục tiêu chính là đăng nhập dễ dàng bằng xác thực Google hoặc GitHub
Ở công ty trước đây tôi đã dùng cả JetBrains Hub[1] lẫn Keycloak, và cả hai đều khá phiền khi cấu hình
JetBrains Hub khởi đầu thật sự rất dễ, và trải nghiệm trước đây của tôi cũng vậy, nhưng việc không có thẻ latest trên Docker registry khá bất tiện
Tôi biết ghim phiên bản là tốt, nhưng với mục đích cá nhân thì thường tôi muốn cập nhật tất cả container Docker cùng lúc
Ngược lại, Keycloak rất lằng nhằng khi bắt đầu; ở chế độ phát triển thì dễ, nhưng cấu hình production lại bị vướng
Theo tôi nhớ thì có liên quan đến chứng chỉ wildcard Let's Encrypt, và sau vài giờ tôi đã bỏ cuộc
Cuối cùng tôi chọn Dex[2]. Vì tài liệu thiếu nên tôi đã trì hoãn, nhưng cấu hình thực tế rất dễ: chỉ cần YAML cơ bản, cơ sở dữ liệu SQLite và một subdomain
Tôi kết hợp Dex với OAuth2 Proxy[3] rất tốt và template Nginx Proxy Manager tùy chỉnh để biến cấu hình SSO cho từng dịch vụ nội bộ thành hai dòng, đồng thời cũng tạo template Dex Docker cho unRAID[4]
Ngoài ra, khi truy cập từ ngoài nhà, tôi thêm Cloudflare Access và WAF để tăng cường bảo mật, và chỉ muốn thêm CrowdSec để có thêm một chút insight
Cá nhân tôi thấy đây là lựa chọn đơn giản nhất
https://github.com/lastlogin-io/obligator
Tôi đã làm một bảng so sánh chưa hoàn chỉnh về nhiều máy chủ OpenID Connect có thể tự host[0]
Một trong những điều khiến tôi ngạc nhiên là codebase của Keycloak thật sự khổng lồ
[0]: https://github.com/lastlogin-io/obligator?tab=readme-ov-file...
Ý tưởng rằng Keycloak sẽ giải quyết vấn đề bảo mật cho bạn thật buồn cười
Nhìn vào danh sách CVE là sẽ hiểu ý tôi
Một giải pháp đóng, không minh bạch và không có CVE nào được báo cáo thì cũng thật buồn cười nếu gọi là “an toàn”
Hơn nữa, bản phát hành mới nhất 22.0.2 chỉ có 3 lỗ hổng đã biết
https://www.cvedetails.com/vulnerability-list/vendor_id-25/p...
Ít nhất thì việc bài viết đó nhắc đến https://www.keycloakify.dev/ là rất hữu ích
Nó có vẻ là một lựa chọn thay thế tuyệt vời cho cách tiếp cận theme tiêu chuẩn
Tôi đã dùng 2 năm và chỉ có thể nói những điều tốt đẹp
Maintainer phản hồi rất nhanh, và gần đây cả Keycloak lẫn keycloakify đều đã có các thay đổi giúp cách làm theme tương thích tốt hơn trong tương lai
Hiện tại về mặt chính thức chỉ hỗ trợ app tạo bằng create-react-app, nhưng nhánh vite đang được phát triển và cá nhân tôi đã dùng nó với vite một thời gian rồi
Đang chú ý đến authentik[1] và authelia[2]
Authelia trông rất tốt, nhưng trong khi Keycloak có connector cho Angular thì với Authelia, chẳng hạn, phải tự cấu hình plugin OIDC Angular nên tôi hơi thận trọng
Dù vậy, nếu có cấu hình cho Keycloak thì có vẻ việc bắt đầu sẽ dễ hơn
[1] https://goauthentik.io/
[2] https://www.authelia.com/
Tôi đang bảo vệ thường xuyên hơn 10 dịch vụ trên Docker và Kubernetes, và nếu bạn không thích cấu hình bảo vệ độc lập cho từng dịch vụ thì sẽ khổ với authentik
authentik có một lỗi nghiêm trọng[0]: khi bảo vệ nhiều subdomain (app1.example.com, app2.example.com, v.v.) bằng một cấu hình duy nhất, sau khi phiên hết hạn và xác thực lại, người dùng bị chuyển hướng ngẫu nhiên sang một dịch vụ khác
[0]: https://github.com/goauthentik/authentik/issues/6886
Không thể sửa nếu không có thay đổi phá vỡ tương thích, và nó không hoạt động với nhiều công cụ dùng cc grant
Sau khi thấy chuyện này, tôi đã loại hẳn nó khỏi danh sách ứng viên
https://github.com/goauthentik/authentik/issues/6139
Nếu có gì có thể giúp về cấu hình Angular, tôi sẵn lòng hỗ trợ qua GitHub Discussions
Authentik trông có vẻ tốt hơn, nhưng lỗi được nhắc đến trong các phản hồi khác thì có vẻ khá tệ
Vấn đề của Keycloak là nó là một dự án lâu đời nên đã thay đổi cực kỳ nhiều
Nó bắt đầu như một dự án JBOSS, và tôi nghĩ tính hữu dụng của nó với tư cách IdP tỏa sáng trong xác thực cụm on-premise, nhưng chỉ đến đó thôi
Tôi từng triển khai Keycloak ở quy mô lớn trên AWS ECS cho một bộ phận Fortune 500, và quá trình làm cho clustering chạy đúng chẳng khác gì cuộc chiến nghìn năm
DNS discovery không hoạt động đúng, còn cluster discovery dựa trên UDP nên không dùng được trong môi trường cloud
Đăng nhập có trạng thái trên một server không có trên server khác, nên cân bằng tải đơn giản là bất khả thi; sticky session là lựa chọn duy nhất
Chạy Keycloak bằng
docker runrồi cắm vào dùng như Auth0 thì dễ, nhưng cảm giác giống như mua một chiếc Ford F-150 đời 1996 đã chạy 250.000 dặmNó vẫn lăn bánh và hoạt động, nhưng bảo trì thì thực sự kinh khủng
Theo tôi nhớ, cơ chế discovery mặc định dùng multicast, thứ thường không được bật trong các mạng cloud thông thường hoặc mạng overlay của Swarm/Kubernetes
Tôi cũng thấy database ping dùng RDBMS như một kiểu cơ chế quorum, nhưng trông rất mong manh và giống phương án cuối cùng
Cuối cùng tôi có thể dùng driver cluster Kubernetes để phát hiện node qua DNS của cụm Swarm
Mất khá nhiều công sức để làm cho nó chạy, nhưng sau đó thì theo tôi biết là ổn định
Dạo này hình như cũng có driver networking EC2 native, nhưng tôi chưa tự tìm hiểu
Không biết lần triển khai ECS đó là khi nào
Bên tôi chỉ cho phép UDP cho DNS
Keycloak rất tốt, nhưng có thể khá rối với người mới
Nó có nhiều tính năng và tài liệu thì không phải tốt nhất
Gần đây tôi đã thử Zitadel và thấy dễ dùng hơn nhiều
Tuy nhiên không thể chạy với database tích hợp sẵn, nên tự host sẽ khó hơn một chút
Sắp tới hướng đi là dùng Postgres làm database, nên hy vọng việc triển khai cùng các công cụ khác sẽ dễ hơn
Sếp tôi gần đây gọi Keycloak là “món đồ cứ tiếp tục tặng quà”, nhưng thật ra ý là cứ liên tục có thêm ticket Jira mới để tìm hiểu xem phải làm một việc gì đó như thế nào
Dù vậy, chúng tôi có Terraform để tạo cụm EKS và triển khai Keycloak, tạo SAML/OIDC client, thêm nhà cung cấp danh tính bên ngoài, rồi cấu hình cả AWS IAM Identity Provider
Một khi đã tìm ra nó có thể làm gì, làm trên UI như thế nào, và tự động hóa bằng mrparkers Terraform provider ra sao, thì bản thân việc sử dụng trở nên rất dễ
Một người bạn của tôi đang cần :)
Sau vài năm dùng Keycloak, thành thật mà nói tôi đã chán đủ loại hành vi kỳ quặc và bắt đầu tìm giải pháp thay thế
Nhiều ứng viên như Authentik trông khá ổn, nhưng Zitadel[1] đã lọt vào mắt tôi, và từ đó tôi không ngoái lại nữa
[1] https://zitadel.com/blog/zitadel-vs-keycloak
Tôi tò mò không biết điểm nào đã thu hút bạn một cách quyết định