iT邦幫忙

2024 iThome 鐵人賽

DAY 13
0
IT 管理

Backstage : 打造企業內部開發者整合平台系列 第 13

Day 13 : Backstage 插件開發 - 打造 Discrouse 內部論壇與 SSO 完全整合

  • 分享至 

  • xImage
  •  

簡介

前篇介紹了將 Uptime Kuma 將監控資訊結合到 Backstage 中,讓我們查看專案的同時也能看到一些服務的運作狀態,像這樣整合資訊便是 Backstage 的核心價值。

如果在內部打造一個技術論壇呢?就像 Github 那樣的 issue 功能,過往開發者遇到的問題可能透過討論解決的紀錄,讓往後的人能夠參考借鑑找出線索,最後也跟專案去整合資訊,能夠即時看到與專案相關的討論議題等等。但是為何不直接使用 Github 上的 issue 功能?論壇中的身份又要如何跟公司開發者連結呢?

以我們的案例,Backstage 整合了我們將不同系統的程式碼放置在不同不平台的分散問題,前面提到的 Github、AzDevops或是 Gitlab 多多少少都有組別採用,也許 Discrouse 真的能夠再次整合這些分散開發問題。再透過登入 Backstage 的 SSO 概念也套用到 Discrouse 之中,很幸運的 Discrouse 本身擁有支援 OIDC 的插件,很不幸的面對 IdentityServer 我必須自行擴充 OIDC PKCE 加密方式。

本次實驗對象 Discrouse

首先,我們透過簡單的故事來了解 Discrouse 是什麼:

在科技城鎮的另一端,有一家名為 L·Tech 的創新企業。這家公司聚集了來自各地的頂尖開發者,日夜不停地開發著尖端技術。然而,隨著團隊不斷壯大,內部溝通逐漸成為一個巨大的挑戰。不同部門之間的技術分享變得零散,知識的流動不再像以前那麼順暢,這讓許多開發者感到困惑和無助。

就在這個時候,一位名叫莉娜的技術總監決定尋找解決方案。她偶然間發現了一款名為 Discourse 的開源論壇軟體,這款工具號稱是打造企業內部技術論壇的利器。莉娜覺得這或許是解決公司內部溝通問題的關鍵。

她迅速開始研究 Discourse,並發現它的設計簡潔而且功能強大。Discourse 支援多層次的討論結構,讓團隊可以輕鬆地組織各類主題,從技術討論到項目協作,甚至是日常的學習分享。最讓莉娜驚喜的是,Discourse 還支援各種插件和整合,使得它可以完美融入公司的技術生態系統。

莉娜決定將 Discourse 引入到公司內部,為每個技術團隊設立專屬的論壇板塊。開發者們可以隨時隨地在論壇上發表問題、分享經驗,甚至參與頭腦風暴。Discourse 的強大搜尋功能也讓每個人都能輕鬆找到所需的資訊,避免了知識的流失。

隨著時間的推移,L·Tech 的內部技術論壇成為了公司最重要的知識庫。每位成員都能在這裡學習和成長,團隊之間的合作變得更加順暢。莉娜為這個決定感到自豪,因為 Discourse 不僅提升了公司內部的溝通效率,也為公司培育了一個充滿創新精神的技術社群。

從那以後,L·Tech 的技術團隊越來越強大,而這一切都離不開 Discourse 的支持。對於莉娜和她的團隊來說,Discourse 不僅僅是一個工具,更是一個推動公司進步的重要平台。

若將 Discourse 與 Backstage 整合,將專案相關的討論和技術知識直接嵌入到 Backstage 的專案組件頁面中。當團隊成員瀏覽專案時,還可以即時查看與該專案相關的最新討論、常見問題解答、以及技術文檔,這不僅提高了溝通效率,還讓知識共享變得更加自然與高效。Discourse 若成為內部的知識庫,不僅能推動每個專案前進速度,還能提升團隊整體的技術能力與一致性。

快速啟動 Discrouse

Discourse 是一個功能強大的開源論壇系統,但由於其架構比較複雜,包含多個服務組件,如 PostgreSQL(作為數據庫)、Redis(用於緩存)、以及 Sidekiq(處理後台任務),幸好我們可以通過 Docker Compose 來簡化建置的過程,請看以下設定範例 (包含前篇的 uptime_kuma):

services:

  nginx:
    image: nginx:latest
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
    environment:
      - NODE_TLS_REJECT_UNAUTHORIZED=0
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./cert/:/etc/nginx/ssl/
    networks:
      - app-network

  uptime_kuma:
    image: louislam/uptime-kuma
    container_name: kuma
    restart: always
    ports:
       - "3001:3001"
    environment:
       - UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN=true
    volumes:
       - uptime_kuma:/app/data
    networks:
       - app-network

  postgresql:
    image: docker.io/bitnami/postgresql:16
    container_name: postgresql    
    volumes:
      - 'postgresql_data:/bitnami/postgresql'
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
      - POSTGRESQL_USERNAME=bn_discourse
      - POSTGRESQL_DATABASE=bitnami_discourse
    networks:
       - app-network

  redis:
    image: docker.io/bitnami/redis:7.0
    container_name: redis
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
    volumes:
      - 'redis_data:/bitnami/redis'
    networks:
       - app-network

  discourse:
    image: docker.io/bitnami/discourse:3
    container_name: discourse
    volumes:
      - 'discourse_data:/bitnami/discourse'
    depends_on:
      - postgresql
      - redis
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
      - DISCOURSE_HOST=discourse.local.com
      - DISCOURSE_DATABASE_HOST=postgresql
      - DISCOURSE_DATABASE_PORT_NUMBER=5432
      - DISCOURSE_DATABASE_USER=bn_discourse
      - DISCOURSE_DATABASE_NAME=bitnami_discourse
      - DISCOURSE_REDIS_HOST=redis
      - DISCOURSE_REDIS_PORT_NUMBER=6379
      - POSTGRESQL_CLIENT_POSTGRES_USER=postgres
      - POSTGRESQL_CLIENT_CREATE_DATABASE_NAME=bitnami_discourse
      - POSTGRESQL_CLIENT_CREATE_DATABASE_EXTENSIONS=hstore,pg_trgm
    ports:
      - "3080:3000"
    networks:
       - app-network

  sidekiq:
    image: docker.io/bitnami/discourse:3
    container_name: sidekiq
    depends_on:
      - discourse
    volumes:
      - 'sidekiq_data:/bitnami/discourse'
    command: /opt/bitnami/scripts/discourse-sidekiq/run.sh
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
      - DISCOURSE_HOST=localhost
      - DISCOURSE_DATABASE_HOST=postgresql
      - DISCOURSE_DATABASE_PORT_NUMBER=5432
      - DISCOURSE_DATABASE_USER=bn_discourse
      - DISCOURSE_DATABASE_NAME=bitnami_discourse
      - DISCOURSE_REDIS_HOST=redis
      - DISCOURSE_REDIS_PORT_NUMBER=6379
    networks:
       - app-network

networks:
  app-network:

volumes:
  uptime_kuma: {}
  discourse_data: {}
  sidekiq_data: {}
  postgresql_data: {}
  redis_data: {}

因為接下來要串接 SSO,我們一樣幫它設定本地 SSL,以下為新增的 nginx.conf 設定,記得要再去 hosts 設定新網址:

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    server {
        listen 443 ssl;
        server_name uptimekuma.local.com;

        ssl_certificate /etc/nginx/ssl/localhost.pem;
        ssl_certificate_key /etc/nginx/ssl/localhost-key.pem;

        location / {
            proxy_pass http://uptime_kuma:3001;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
    server {
        listen 443 ssl;
        server_name discourse.local.com;

        ssl_certificate /etc/nginx/ssl/localhost.pem;
        ssl_certificate_key /etc/nginx/ssl/localhost-key.pem;

        location / {
            proxy_pass http://discourse:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

沒問題的話應該可以看到這個畫面,接下來我們必須先手動建立管理員才能修改設定或是安裝插件等等。
https://ithelp.ithome.com.tw/upload/images/20240922/20128232d4DiOHTHtg.png
首先進入 discourse 容器中,再執行以下指令就可以創建管理員

// 進入容器指令 (供參考)
sudo docker exec -it discourse bash

// 進入容器中輸入以下指令![https://ithelp.ithome.com.tw/upload/images/20240922/20128232mO5wkhWahN.png](https://ithelp.ithome.com.tw/upload/images/20240922/20128232mO5wkhWahN.png)
cd /opt/bitnami/discourse
RAILS_ENV=production bundle exec rake admin:create

成功後會顯示 Your account now has Admin privileges! ,接著我們就可以用它來登入 Discourse 囉。
https://ithelp.ithome.com.tw/upload/images/20240922/20128232VHadPO9yzl.png
在登入介面中可以看到目前只支援原生的帳號密碼登入,還有第三方的 Passkey,我們的目標就是新再新增一個 OIDC IdentityServer 的登入方式。首先我們使用剛剛的管理員帳號密碼登入。
https://ithelp.ithome.com.tw/upload/images/20240922/20128232IiLlk0XKTO.png
Discourse 的可以變更的設定非常多,可以先到語言選項調整成繁體中文會比較方便,而這個選項也會影響到整個網站的語言顯示。
https://ithelp.ithome.com.tw/upload/images/20240922/20128232g1YnknFNOK.png

為 Discrouse 安裝 OIDC 插件

Discourse 提供了多種內建的登入方式,涵蓋了大部分主流選項。但它不支援直接的第三方 OIDC 登入方式。不過,別擔心,我們可以通過下載 Discourse 的 OIDC 擴充插件來實現這個功能。
https://ithelp.ithome.com.tw/upload/images/20240922/201282324jIP79H13o.png
Discourse OpenID Connect 插件官方說明 / 圖片來源 - https://meta.discourse.org/t/discourse-openid-connect/103632

安裝方式也非常簡單,一樣要進入到容器內來操作,在插件資料夾直接 clone 插件檔案基本上就安裝完成了。最後可以看到目錄下多了一個 discourse-openid-connect 的資料夾,接著我們需要重啟服務來啟用插件。

cd /opt/bitnami/discourse/plugins
git clone https://github.com/discourse/discourse-openid-connect.git

https://ithelp.ithome.com.tw/upload/images/20240922/20128232F4nHppYxUD.png
重新進入 discourse 先到外掛的地方啟用 Openid Connect 插件,再點擊設定。
https://ithelp.ithome.com.tw/upload/images/20240922/20128232tULA3pHqtZ.png
當為 Discourse 設定 OIDC 之前,請先在 IdentityServer 上創建一個新的客戶端,並使用與當初為 Backstage 設定的客戶端相同的格式。也為 Discourse 也創建一個對應的客戶端,範例如下:
https://ithelp.ithome.com.tw/upload/images/20240922/20128232Dfvdy3e44V.png

⚠️ 以下將展示在生產環境中成功部署的設定案例,特別點出需要注意的設定。

  • 設定 > Openid Connect 插件

這邊為 OIDC 必要的設定,其他選項可以依照需求選擇。
https://ithelp.ithome.com.tw/upload/images/20240922/20128232UyOYxPGT5N.png

  • 設定 > 安全性

當初在這裡這邊卡了很久,最後在官方的討論找到了解決方案。要讓 Discourse 正常使用 OIDC,需要解決跨網域請求、Cookie 政策以及允許讀取其他應用的資料。我們需要允許 Discourse 讀取 IdentityServer 所在的網域與主機位置,並且允許使用 iframe 到 Backstage。
https://ithelp.ithome.com.tw/upload/images/20240922/20128232YJ6ndbJpCM.png
https://ithelp.ithome.com.tw/upload/images/20240922/20128232vieThWMdNj.png

加入 Backstage

利用在上一篇的 iframe 方式,我們一樣為 Discourse 建立一個前端插件,如法炮製一樣的操作流程,一樣加入在左邊的側邊欄,這邊就不再提一次,直接展示實務上最後呈現的效果。

在 Backstage 中,我們已成功實現透過 OIDC 進行使用者登入,並且瀏覽器會通過 Cookie 來保存使用者的登入狀態。接下來的目標是當使用者點擊 Backstage 中的 Discourse 應用時,能夠自動攜帶使用者的登入資訊,實現無縫整合。理想的使用情境是,使用者只需在首次使用時點擊一次登入按鈕,後續系統會自動將相同的使用者資訊傳遞到 Discourse,無需再次登入。這樣的設計可以大大提升用戶體驗,避免重複認證的麻煩。
https://ithelp.ithome.com.tw/upload/images/20240922/20128232Y1WAYfevu6.png
https://ithelp.ithome.com.tw/upload/images/20240922/20128232nLgYxgO0YC.png

(可選項) 為 Discourse OIDC 插件支援 PKCE 驗證

PKCE(Proof Key for Code Exchange,代碼交換證明金鑰)是一種提升 OAuth 2.0 和 OpenID Connect 安全性的技術。它通過在授權碼流程中添加額外的驗證步驟,確保授權碼不會被攔截並非法使用。PKCE 是由 Identity Server 預設啟用的功能,旨在進一步加強對授權過程的保護,特別是在公共或不可信的客戶端環境中。

在最後一步登入時還有遇到了一個錯誤,即 Discourse 的 OIDC 插件不支援 PKCE 驗證,這導致 token 傳遞過程中缺少必要的參數,從而導致驗證失敗。我們可以選擇將該插件 clone 下來進行修改,或者直接修改 Discourse 容器中的檔案。

例如我自己 fork 該專案,並通過安裝 Discourse 插件的方式,從自己的 Github 儲存庫抓取修改後的版本來解決這個問題。由於 PKCE 是可選項,並不是每一款身份驗證解決方案都會啟用,以下修改提供參考。https://github.com/discourse/discourse-openid-connect/pull/80

cd /opt/bitnami/discourse/plugins
git clone https://github.com/jincoco88912/pkce_discourse-openid-connect.git

https://ithelp.ithome.com.tw/upload/images/20240922/20128232h13vHd7OUk.png
https://ithelp.ithome.com.tw/upload/images/20240922/201282324NMsOMRLtR.png
https://ithelp.ithome.com.tw/upload/images/20240922/201282325k00ElPQ9N.png

結論

本篇文章主要聚焦於 Backstage 在串接 SSO 後的應用性,較少著墨在 Backstage 本身的細節。這個案例展示了如何利用 SSO 進一步實現 Backstage 集中資訊得的精神。SSO 作為我們的關鍵工具,能夠有效串聯各種平台,實現無縫銜接和整合。

參考文獻

https://github.com/bitnami/containers/blob/main/bitnami/discourse/README.md
https://meta.discourse.org/t/discourse-openid-connect/103632
https://meta.discourse.org/t/openid-connect-plugin-cant-fetch-configuration/253728
https://github.com/discourse/discourse-openid-connect/pull/80


上一篇
Day 12 : Backstage 插件開發 - 將 Uptime Kuma 監控狀態結合專案頁面
下一篇
Day 14 : Backstage 插件開發 - 結合 Xterm.js 套件實現 Web Terminal
系列文
Backstage : 打造企業內部開發者整合平台30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言