iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
IT 管理

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

Day 25 : 邁向上線的最後一道牆 - 撰寫權限政策

  • 分享至 

  • xImage
  •  

簡介

https://ithelp.ithome.com.tw/upload/images/20241004/20128232rCmNaYDWXG.png
隨著我們新增了許多組件與插件,也許部分功能並非對所有使用者開放,有些專案或功能可能只適合特定小組成員才能看到或使用。因此,管理使用者的存取權限成為我們不可或缺的步驟。

Backstage 整合了各類資源和使用者資訊,但在預設情況下沒有啟用任何權限限制。儘管如此,它提供了一個彈性的權限政策框架,讓我們能夠根據需求,自行定義與實作不同的權限管理機制。這樣可以確保使用者根據其角色或職責,存取適當的資源或功能,避免資訊洩露或意外操作。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232GwQ4iPwIDK.png
在後端設定的權限插件中,可以看到預設使用不限制策略

舉例來說,在 Backstage 中加入了 FileBrowser 的插件功能,可以讓使用者在 Backstage 上直接用介面化方式操作遠端 Linux 系統中的文件等等。此功能也許是為了開發團隊在開發時方便操作,並且連線到的特定主機也不能給團隊其他人使用,那麼這個插件的權限管控就非常重要,目前沒有限制的情況下,任何人都可以隨意進入修改資料,這樣明顯有問題。

以 Backstage 的權限運作流程來看,當我們在進入 FileBrowser 插件之前,會先與後端的權限政策檢查,比較常用的案例是:我們定義一個權限政策,設定如果該使用者或所屬的團隊,不存在於該插件的 owner 列表中,則無法存取。

中間的 API Backstage 已經幫我們處理好了,我們唯一只需要專注在 Permission Policy 的定義。透過設定使用者存取權限後,我們能夠更好地控制誰能查看、修改特定資源,加強 Backstage 在管控的功能上。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232QyH8j4mEf6.png
Backstage 權限的運作框架 / 圖片來源 - https://www.gorkem-ercan.com/p/securing-backstage-plugins

啟用權限設定

為了啟用我們自訂的權限政策規則,首先要到 app-config.yaml 中修改啟用 permission 這項設定。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232K2YrLIvHY6.png

限制所有實體讀取權限

首先嘗試一下建立一個擴充模組 permission 的插件,我們可以在此開始撰寫自訂我們的權限規則。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232NGzCv2UPCX.png

做一個最簡單的演示,我們限制所有使用者讀取組件的權限。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232zke03dMnNA.png

import {
  coreServices,
  createBackendModule,
} from '@backstage/backend-plugin-api';

import { PolicyQuery } from '@backstage/plugin-permission-node';
import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha';
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';

import {
  AuthorizeResult,
  PolicyDecision,
} from '@backstage/plugin-permission-common';

class MyPermissionPolicy implements MyPermissionPolicy {
  async handle(
    request: PolicyQuery,
    _user?: BackstageIdentityResponse,
  ): Promise<PolicyDecision> {
    if (request.permission.name === 'catalog.entity.read') {
      return {
        result: AuthorizeResult.DENY,
      };
    }

    return { result: AuthorizeResult.ALLOW };
  }
}

export const permissionModuleCustom = createBackendModule({
  pluginId: 'permission',
  moduleId: 'custom',
  register(reg) {
    reg.registerInit({
      deps: {
        policy: policyExtensionPoint,
        logger: coreServices.logger,
      },
      async init({ logger, policy }) {
        logger.info('自定義權限策略啟用中');
        policy.setPolicy(new MyPermissionPolicy());
      },
    });
  },
});

啟用自訂權限政策

Backstage 預設會使用完全開放的不限制權限政策,我們需要換成剛剛我們新增的擴充來套用規則。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232y8YvGV9PX3.png

因爲我們限制了所有使用者的讀取權限,所以會看到一片空白。但拒絕所有人的訪問並不是我們要實現的功能,我們應該做的是讓擁有所有權的使用者才能讀取,我們接著修改一下程式碼。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232ZbvLfPdH7E.png

限制僅擁有者群組可讀取

我們檢視目前的組件,若是讓專案擁有者改為群組 team-a ,而我們的使用者也在 team-a 之中,則預期修改後的政策程式碼,結果應該只能顯示兩個組件,也就是自己有權限看到自己組別的組件。我們可以藉由設定中的 Ownership Entities 來觀察自己的實體所有權,包含組別也會顯示在這邊。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232x537saTo9O.png
https://ithelp.ithome.com.tw/upload/images/20241004/201282327ca9vBu66I.png

讓我們修改一下程式碼,僅允許實體所有者可以在列表中看到組件。
https://ithelp.ithome.com.tw/upload/images/20241004/201282322TgEWVrzJq.png

import {
  catalogConditions,
  createCatalogConditionalDecision,
} from '@backstage/plugin-catalog-backend/alpha';
import {
  catalogEntityDeletePermission,
} from '@backstage/plugin-catalog-common/alpha';

class OnlyOwnerPermissionPolicy implements OnlyOwnerPermissionPolicy {
  async handle(
    request: PolicyQuery,
    user?: BackstageIdentityResponse,
   ): Promise<PolicyDecision> {
    if (isPermission(request.permission, catalogEntityReadPermission)) {
      return createCatalogConditionalDecision(
        request.permission,
        catalogConditions.isEntityOwner({
          claims: user?.identity.ownershipEntityRefs ?? [],
        }),
      );
    }
     return { result: AuthorizeResult.ALLOW };
  }
}

啟用規則後可以看到最後只剩下兩個擁有者為 team-a 的組件在頁面中,由於其他的實體並沒有定義擁有者是誰,最後導致我們也看不見,選單拉下來只會有 Component 有東西,成功是成功了,但還可以再優化一下。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232BFgAx6yyvp.png

僅限制特定實體類型

按照上述作法,會將所有 catalog 類型納入作用範圍,導致無法讀取其他使用者的實體資料,如聯絡資訊等,這不是我們想要的結果。為了解決這個問題,我們可以限定實體類型,例如 kinds: ['API', 'Component'],然後再檢查是否具備擁有者權限,範例程式如下。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232QkZTs7PA4m.png
https://ithelp.ithome.com.tw/upload/images/20241004/20128232dNYql6oig7.png

class CheckKindPermissionPolicy implements CheckKindPermissionPolicy {
  async handle(
    request: PolicyQuery,
    user?: BackstageIdentityResponse,
   ): Promise<PolicyDecision> {

        // 如果請求的權限是讀取 catalog-entity
        if (isPermission(request.permission, catalogEntityReadPermission)) {

          // 創建條件來檢查資源類型是否為 'API' 'Component'
          const apiKindCondition: PermissionCondition<'catalog-entity', PermissionRuleParams> = catalogConditions.isEntityKind({
            kinds: ['API', 'Component'],
          });
    
          // 創建條件來檢查用戶是否為資源的擁有者
          const ownerCondition: PermissionCondition<'catalog-entity', PermissionRuleParams> = catalogConditions.isEntityOwner({
            claims: user?.identity.ownershipEntityRefs ?? [],
          });
    
          // 資源 Kind 不等於 API、Component 直接允許、否則檢查擁有者
          return createCatalogConditionalDecision(
            request.permission,
            {
              anyOf: [
                {
                  not:
                    apiKindCondition,
                },
                {
                  allOf: [
                    apiKindCondition, ownerCondition
                  ]
                }
              ]
            }
          );
        }
        
     return { result: AuthorizeResult.ALLOW };
  }
}

對比上一個規則,我們還能另外看到其他的組件了,並且也可以讀取其他使用者的實體資訊,而非一是同仁的被隱藏掉。透過這樣限制某些類型才進行權限檢查,理論與實務上都是較合理的做法,透過這次的設定,我們可以更靈活的將重要資訊分類在不同的類別之中,再配合權限政策去調整可視度。
https://ithelp.ithome.com.tw/upload/images/20241004/20128232fCHtXNGc0c.png

開發時選用

如果在平時本地開發會使用到訪客身份,可以在權限規則最上方新增一個允許訪客所有權限的限制,其效果就近乎於原先預設的 plugin-permission-backend-module-allow-all-policy 權限,但更方便我們在身份切換來回檢查確認ˋ。

// 如果是遊客則直接允許 (開發用)
if (_user?.identity.ownershipEntityRefs.some(ref => ref.startsWith('user:development/guest'))) {
  return { result: AuthorizeResult.ALLOW };
}

更有效的作法

https://ithelp.ithome.com.tw/upload/images/20241004/20128232vt4torYDYW.png
圖片來源 - https://backstage.spotify.com/marketplace/spotify/plugin/rbac/

以我們設定的寫法來看,短時間內可以達成我們想要的結果,但是在往後如果團隊、人員與專案數量多起來後,各種權限需求的重疊交錯將會是一個複雜的問題。面對這個問題,Spotify 有出一款基於角色來控制權限的插件,我們可以很輕易在介面上修改使用者的權限歸屬,修改權限政策的功能。 Role-Based Access Control (RBAC) 讓管理員可以快速、靈活、輕鬆地建立、發佈、編輯或復原權限策略。可惜的是,這個插件必須另外購買許可證,否則無法使用,所以沒有寫在本篇文章中。

結論

Backstage 提供了靈活的權限管理機制,允許我們根據需求自定義權限策略,從而更精細地控制誰能查看和修改特定資源,增強 Backstage 的系統管控能力。本文從簡單的完全限制開始,逐步展示如何基於實體類型和所有權優化權限控制。實際實施權限策略時,需要考量實際需求,既要確保基本 資訊的可見性,又要保護重要敏感資源,雖然撰寫自定義權限策略能夠解決當前需求,但隨著團隊和專案規模擴大,我們可能需要考慮更高級的解決方案,例如 Spotify 推出的插件:基於角色的訪問控制(RBAC)就是很好的方案。

我們已解決實體權限的限制,接下來將專注於插件的存取權限設定。這部分的配置與實體權限設定相似,但對於插件的權限控制同樣非常重要。在下篇文章中,我們將提出對插件權限的設置方法,並介紹更多有關插件安全性設計的思路。

我們提出了一個三層權限框架,並整合 SSO(單一登入)機制來進行權限管理,進一步提升系統安全性。這不僅能有效管理插件的存取權限,還為未來的權限擴展提供了靈活性和可維護性。隨著團隊和專案的成長,這種精細化的權限管理,和 SSO 的整合將成為 Backstage 權限管控的關鍵概念。

參考文獻

https://www.gorkem-ercan.com/p/securing-backstage-plugins
https://backstage.spotify.com/marketplace/spotify/plugin/rbac/
https://backstage.io/docs/permissions/overview


上一篇
Day 24 : 程式碼即文件 - API 文件整合
下一篇
Day 26 : 邁向上線的最後一道牆 - Backstage 的三層權限框架概念
系列文
Backstage : 打造企業內部開發者整合平台30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言