3 điểm bởi GN⁺ 2024-02-12 | 1 bình luận | Chia sẻ qua WhatsApp
  • Đâ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:16quay.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:800.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/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 .envKC_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 keycloakifykeycloakify-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

 
GN⁺ 2024-02-12
Ý 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

    • Tôi đang làm việc tại FusionAuth
      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 đã vận hành Authelia hơn 2 năm và cấu hình tích hợp LDAP bằng LLDAP[0], một triển khai LDAP nhẹ
      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/
    • Gần đây tôi cũng đi theo hướng tương tự, và cá nhân tôi chọn caddy-security[1], một plugin của Caddy
      [1] https://github.com/greenpau/caddy-security
    • Authelia có một ưu điểm rất độc đáo là quy mô cài đặt nhỏ, còn Keycloak hay authentik khó phù hợp với mảng này
      Đâ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
    • Nói bên lề, tôi tò mò liệu hiện nay có giải pháp thay thế LDAP nào không
      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

    1. https://www.jetbrains.com/hub/
    2. https://dexidp.io/
    3. https://github.com/oauth2-proxy/oauth2-proxy
    4. https://github.com/alex3305/unraid-docker-templates
    • Tôi đang dùng obligator làm kho tạm, không có cơ sở dữ liệu, cấu hình 100% dựa trên code
      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 tò mò oauth2-proxy cung cấp tính năng nào mà Dex không có
  • 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

  • Í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

    • Đây là một dự án tuyệt vời
      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
    • Tôi đang làm cho một đối thủ cạnh tranh và chúng tôi đang làm lại hệ thống theme, nên dự án này có vẻ rất đáng để xem xét và lấy làm mô hình cho việc làm lạ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/

    • Nếu ai đang cân nhắc authentik thì tôi muốn cảnh báo rằng “nơi này có rồng ẩn náu”
      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
    • Authentik đã làm hỏng hoàn toàn phần triển khai OAuth client credentials grant
      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
    • Tôi là một trong những maintainer chính của Authelia
      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
    • Tôi cũng đang định đưa ra quyết định tương tự
      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 run rồ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ặm
    Nó vẫn lăn bánh và hoạt động, nhưng bảo trì thì thực sự kinh khủng

    • Tôi đã thử trong Docker Swarm on-premise, quy mô nhỏ hơn, và đúng là rất đau khổ
      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
    • Tôi nghe nói đã có cải thiện sau khi chuyển hẳn sang Quarkus
      Không biết lần triển khai ECS đó là khi nào
    • OAuth, OpenID Connect, và việc có thể thêm app cùng client vào realm chính là cứu cánh của Keycloak, và là lý do duy nhất khiến chúng tôi tiếp tục giữ nó
    • Nghe nói cluster discovery dùng UDP thì thấy đau thật
      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

    • Đúng vậy, nó cần database riêng
      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ễ

    • Không biết Terraform đó có được open source ở đâu không
      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

    • Rất vui vì bạn thích nó
      Tôi tò mò không biết điểm nào đã thu hút bạn một cách quyết định