

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用 OPA 實作 PDP
<a name="opa"></a>

Open Policy Agent (OPA) 是開放原始碼的一般用途政策引擎。OPA 有許多使用案例，但與 PDP 實作相關的使用案例是能夠從應用程式分離授權邏輯。這稱為*政策解耦*。OPA 在實作 PDP 方面很有用，原因有幾個。它使用稱為 Rego 的高階宣告性語言來草擬政策和規則。這些政策和規則與應用程式分開存在，可以呈現授權決策，而不需要任何應用程式特定的邏輯。OPA 也會公開 RESTful API，讓擷取授權決策變得簡單明瞭。若要做出授權決策，應用程式會使用 JSON 輸入查詢 OPA，而 OPA 會根據指定的政策評估輸入，以傳回 JSON 中的存取決策。OPA 也能夠匯入可能與進行授權決策相關的外部資料。

![\[使用 OPA 實作 PDP\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/saas-multitenant-api-access-authorization/images/opa.png)


相較於自訂政策引擎，OPA 有幾個優點：
+ OPA 及其使用 Rego 進行的政策評估提供彈性的預先建置政策引擎，只需要插入政策和做出授權決策所需的任何資料。此政策評估邏輯必須在自訂政策引擎解決方案中重新建立。
+ OPA 透過使用宣告性語言撰寫政策來簡化授權邏輯。您可以獨立於任何應用程式程式碼來修改和管理這些政策和規則，無需應用程式開發技能。
+ OPA 公開 RESTful API，可簡化與政策強制執行點 (PEPs整合。
+ OPA 提供內建支援，以驗證和解碼 JSON Web Token JWTs)。
+ OPA 是公認的授權標準，這表示如果您需要協助或研究來解決特定問題，文件和範例會非常豐富。
+ 採用授權標準，例如 OPA，可讓以 Rego 撰寫的政策跨團隊共用，無論團隊應用程式使用的程式設計語言為何。

OPA 不會自動提供兩件事：
+ OPA 沒有用於更新和管理政策的強大控制平面。OPA 透過公開管理 API 來提供實作政策更新、監控和日誌彙總的一些基本模式，但與此 API 的整合必須由 OPA 使用者處理。根據最佳實務，您應該使用持續整合和持續部署 (CI/CD) 管道來管理、修改和追蹤政策版本，並在 OPA 中管理政策。
+ 根據預設，OPA 無法從外部來源擷取資料。授權決策的外部資料來源可以是保留使用者屬性的資料庫。將外部資料提供給 OPA 的方式有一些彈性 – 在請求授權決策時，可以事先在本機快取，或從 API 動態擷取 – 但取得此資訊不是 OPA 可以代表您執行的動作。

# 撤銷概觀
<a name="rego"></a>

Rego 是一種一般用途的政策語言，這表示它適用於堆疊的任何圖層和任何網域。Rego 的主要目的是接受評估的 JSON/YAML 輸入和資料，以對基礎設施資源、身分和操作做出已啟用政策的決策。Rego 可讓您撰寫有關堆疊或網域任何層的政策，而不需要變更或擴充語言。以下是 Rego 可以做出的一些決策範例：
+ 是否允許或拒絕此 API 請求？
+ 此應用程式的備份伺服器的主機名稱是什麼？
+ 此提議基礎設施變更的風險分數是多少？
+ 為了實現高可用性，此容器應該部署到哪些叢集？
+ 此微服務應使用哪些路由資訊？

為了回答這些問題，Rego 採用如何做出這些決策的基本理念。在 Rego 中草擬政策時的兩個關鍵原則為：
+ 每個資源、身分或操作都可以以 JSON 或 YAML 資料表示。
+ 政策是套用至資料的邏輯。

Rego 透過定義如何評估 JSON/YAML 資料輸入的邏輯，協助軟體系統做出授權決策。C、Java、Go 和 Python 等程式設計語言是這個問題的常用解決方案，但 Rego 旨在專注於代表您系統的資料和輸入，以及使用此資訊做出政策決策的邏輯。

# 範例 1：具有 OPA 和 Rego 的基本 ABAC
<a name="opa-abac-examples"></a>

本節說明使用 OPA 對哪些使用者允許存取虛構薪資微服務中的資訊進行存取決策的情況。提供 Rego 程式碼片段，示範如何使用 Rego 來呈現存取控制決策。這些範例既不會詳盡，也不會完整探索 Rego 和 OPA 功能。如需 Rego 的更完整概觀，建議您參閱 OPA 網站上的 [Rego 文件](https://www.openpolicyagent.org/docs/latest/#rego)。

![\[具有 OPA 和 Rego 的基本 ABAC\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/saas-multitenant-api-access-authorization/images/opa-example-1.png)


## 基本 OPA 規則範例
<a name="basic-rules"></a>

在上圖中，OPA 針對薪資微服務強制執行的其中一個存取控制規則為：

*員工可以讀取自己的薪資。*

如果 Bob 嘗試存取薪資微服務以查看自己的薪資，薪資微服務可以將 API 呼叫重新導向至 OPA RESTful API 以做出存取決策。薪資服務會使用下列 JSON 輸入來查詢 OPA 是否有決策：

```
{
    "user": "bob",
    "method": "GET",
    "path": ["getSalary", "bob"]
}
```

OPA 會根據查詢選取政策。在此情況下，以下以 Rego 撰寫的政策會評估 JSON 輸入。

```
default allow = false
allow = true {
    input.method == "GET"
    input.path = ["getSalary", user]
    input.user == user
}
```

此政策預設拒絕存取。然後，它會將查詢繫結至全域變數 來評估查詢中的輸入`input`。點運算子會與此變數搭配使用，以存取變數的值。如果規則中的表達式也是 true，則 Rego 規則會`allow`傳回 true。Rego 規則會驗證輸入`method` 中的 是否等於 GET。然後，它會驗證清單中的第一個元素`path` 是在將清單中的第二個元素指派給變數 `getSalary`之前`user`。最後，它會檢查要存取的路徑是否`/getSalary/bob` 與`user`提出請求 `input.user`符合`user`變數。規則`allow`套用 if-then 邏輯以傳回布林值，如輸出所示：

```
{
    "allow": true
}
```

## 使用外部資料的部分規則
<a name="partial-rules"></a>

若要示範其他 OPA 功能，您可以將需求新增至您強制執行的存取規則。假設您想要在上圖的內容中強制執行此存取控制要求： 

*員工可以讀取向他們報告的任何人員的薪資。*

在此範例中，OPA 可存取可匯入的外部資料，以協助做出存取決策：

```
"managers": {
        "bob": ["dave", "john"],
        "carol": ["alice"]
}
```

 您可以在 OPA 中建立部分規則來產生任意 JSON 回應，這會傳回一組值，而不是固定的回應。這是部分規則的範例：

```
direct_report[user_ids] {
    user_ids = data.managers[input.user][_]
}
```

 此規則會傳回一組回報為 值的所有使用者`input.user`，在此情況下，該值為 `bob`。規則中的 `[_]` 建構用於反覆運算集合的值。這是規則的輸出：

```
{
    "direct_report": [
      "dave",
      "john"
    ]
}
```

擷取此資訊有助於判斷使用者是否為經理的直屬員工。對於某些應用程式，傳回動態 JSON 優於傳回簡單的布林值回應。

## 整合練習
<a name="abac-combination"></a>

最後一個存取需求比前兩個更複雜，因為它結合了這兩個需求中指定的條件：

*員工可以讀取自己的薪資以及向其報告的任何人的薪資。*

若要滿足此要求，您可以使用此 Rego 政策：

```
default allow = false
 
allow = true {
    input.method == "GET"
    input.path = ["getSalary", user]
    input.user == user
}
 
allow = true {
    input.method == "GET"
    input.path = ["getSalary", user]
    managers := data.managers[input.user][_]
    contains(managers, user)
}
```

政策中的第一個規則允許任何嘗試查看自己薪資資訊的使用者存取 ，如前所述。在 Rego 中具有兩個名稱相同的規則，即 `allow`做為邏輯**或**運算子。第二個規則會擷取與 相關聯的所有直接報告清單 `input.user`（來自上圖中的資料），並將此清單指派給`managers`變數。最後，規則會驗證其名稱是否包含在`managers`變數中，`input.user`藉此檢查嘗試查看其薪資的使用者是否為 的直屬報告。

本節中的範例非常基本，不提供完整或徹底探索 Rego 和 OPA 的功能。如需詳細資訊，請檢閱 [OPA 文件](https://www.openpolicyagent.org/docs/latest/)，請參閱 [OPA GitHub README](https://github.com/open-policy-agent/opa) 檔案，然後在 [Rego 遊樂場](https://play.openpolicyagent.org/)中實驗。

# 範例 2：具有 OPA 和 Rego 的多租戶存取控制和使用者定義的 RBAC
<a name="opa-rbac-examples"></a>

此範例使用 OPA 和 Rego 來示範如何使用租用戶使用者定義的自訂角色，在多租用戶應用程式的 API 上實作存取控制。它也會示範如何根據租用戶限制存取。此模型顯示 OPA 如何根據高階角色提供的資訊做出精細的許可決策。

![\[具有 OPA 和 Rego 的使用者定義 RBAC\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/saas-multitenant-api-access-authorization/images/opa-example-2.png)


租用戶的角色存放在外部資料 (RBAC 資料） 中，用於對 OPA 進行存取決策：

```
{
    "roles": {
        "tenant_a": {
            "all_access_role": ["viewData", "updateData"]
        },
        "tenant_b": {
            "update_data_role": ["updateData"],
            "view_data_role": ["viewData"]
        }
    }
}
```

這些角色由租戶使用者定義時，應存放在外部資料來源或身分提供者 (IdP) 中，當將租戶定義的角色映射至許可和租戶本身時，可做為事實來源。 

此範例使用 OPA 中的兩個政策來做出授權決策，並檢查這些政策如何強制執行租用戶隔離。這些政策使用先前定義的 RBAC 資料。

```
default allowViewData = false
allowViewData = true {
    input.method == "GET"
    input.path = ["viewData", tenant_id]
    input.tenant_id == tenant_id
    role_permissions := data.roles[input.tenant_id][input.role][_]
    contains(role_permissions, "viewData")
}
```

若要顯示此規則的運作方式，請考慮具有下列輸入的 OPA 查詢：

```
{
    "tenant_id": "tenant_a",
    "role": "all_access_role",
    "path": ["viewData", "tenant_a"],
    "method": "GET"
}
```

此 API 呼叫的授權決策會結合 *RBAC 資料*、*OPA 政策和* *OPA 查詢輸入*，如下所示：

1. 的使用者對 `Tenant A`進行 API 呼叫`/viewData/tenant_a`。

1. 資料微服務會接收呼叫並查詢`allowViewData`規則，傳遞 OPA 查詢輸入範例中顯示的輸入。

1. OPA 使用 OPA 政策中的查詢規則來評估提供的輸入。OPA 也會使用來自 RBAC 資料的資料來評估輸入。OPA 會執行下列動作：

   1. 驗證用於進行 API 呼叫的 方法為 `GET`。

   1. 驗證請求的路徑是否為 `viewData`。

   1. 檢查路徑`tenant_id`中的 是否等於與使用者`input.tenant_id`相關聯的 。這可確保保持租戶隔離。即使具有相同角色，另一個租戶也無法獲得進行此 API 呼叫的授權。

   1. 從角色的外部資料提取角色許可清單，並將其指派給變數 `role_permissions`。使用與 中的使用者相關聯的租戶定義角色來擷取此清單 `input.role.`

   1. 檢查`role_permissions`是否包含 許可 `viewData.`

1. OPA 會將下列決策傳回資料微服務：

```
{
    "allowViewData": true
}
```

此程序顯示 RBAC 和租戶意識如何有助於使用 OPA 做出授權決策。若要進一步說明這一點，請考慮`/viewData/tenant_b`使用下列查詢輸入對 進行 API 呼叫：

```
{
    "tenant_id": "tenant_b",
    "role": "view_data_role",
    "path": ["viewData", "tenant_b"],
    "method": "GET"
}
```

此規則會傳回與 OPA 查詢輸入相同的輸出，但適用於具有不同角色的不同租用戶。這是因為此呼叫適用於 ，`/tenant_b`且 RBAC 資料`view_data_role`中的 仍有與其相關聯的`viewData`許可。若要對 強制執行相同類型的存取控制`/updateData`，您可以使用類似的 OPA 規則：

```
default allowUpdateData = false
allowUpdateData = true {
    input.method == "POST"
    input.path = ["updateData", tenant_id]
    input.tenant_id == tenant_id
    role_permissions := data.roles[input.tenant_id][input.role][_]
    contains(role_permissions, "updateData")
}
```

此規則的功能與`allowViewData`規則相同，但會驗證不同的路徑和輸入方法。此規則仍會確保租戶隔離，並檢查租戶定義的角色是否授予 API 發起人許可。若要查看如何強制執行這項操作，請針對對 的 API 呼叫檢查下列查詢輸入`/updateData/tenant_b`：

```
{
    "tenant_id": "tenant_b",
    "role": "view_data_role",
    "path": ["updateData", "tenant_b"],
    "method": "POST"
}
```

當使用 `allowUpdateData`規則評估時，此查詢輸入會傳回下列授權決策：

```
{
    "allowUpdateData": false
}
```

此呼叫將不會獲得授權。雖然 API 呼叫者與正確的 相關聯，`tenant_id`並且使用核准的方法呼叫 API，但 `input.role`是租戶定義的 `view_data_role`。`view_data_role` 沒有 `updateData`許可；因此，對 的呼叫`/updateData`未經授權。對於擁有 `tenant_b`的使用者，此呼叫將會成功`update_data_role`。

# 範例 3：具有 OPA 和 Rego 的 RBAC 和 ABAC 的多租戶存取控制
<a name="opa-abac-rbac-examples"></a>

若要增強上一節中的 RBAC 範例，您可以將屬性新增至使用者。 

![\[RBAC 和 ABAC 搭配 OPA 和 Rego\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/saas-multitenant-api-access-authorization/images/opa-example-3.png)


此範例包含上一個範例中的相同角色，但會新增使用者屬性 `account_lockout_flag`。這是未與任何特定角色相關聯的使用者特定屬性。您可以使用先前用於此範例的相同 RBAC 外部資料： 

```
 {
    "roles": {
        "tenant_a": {
            "all_access_role": ["viewData", "updateData"]
        },
        "tenant_b": {
            "update_data_role": ["updateData"],
            "view_data_role": ["viewData"]
        }
    }
}
```

`account_lockout_flag` 使用者屬性可做為使用者 Bob `/viewData/tenant_a`的 OPA 查詢輸入的一部分傳遞至資料服務：

```
 {
    "tenant_id": "tenant_a",
    "role": "all_access_role",
    "path": ["viewData", "tenant_a"],
    "method": "GET",
    "account_lockout_flag": "true"
}
```

查詢存取決策的規則類似於上述範例，但包含要檢查`account_lockout_flag`屬性的額外行：

```
default allowViewData = false
allowViewData = true {
    input.method == "GET"
    input.path = ["viewData", tenant_id]
    input.tenant_id == tenant_id
    role_permissions := data.roles[input.tenant_id][input.role][_]
    contains(role_permissions, "viewData")
    input.account_lockout_flag == "false"
}
```

此查詢會傳回 的授權決策`false`。這是因為 `account_lockout_flag attribute``true`適用於 Bob，而 Rego 規則`allowViewData`拒絕存取，雖然 Bob 具有正確的角色和租用戶。

# 範例 4：使用 OPA 和 Rego 進行 UI 篩選
<a name="opa-ui-filtering-examples"></a>

OPA 和 Rego 的彈性支援篩選 UI 元素的能力。下列範例示範 OPA 部分規則如何針對 RBAC 的 UI 中應顯示的元素進行授權決策。此方法是使用 OPA 篩選 UI 元素的許多不同方法之一。

![\[使用 OPA 和 Rego 進行 UI 篩選\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/saas-multitenant-api-access-authorization/images/opa-example-4.png)


在此範例中，單一頁面 Web 應用程式有四個按鈕。假設您想要篩選 Bob、Shirley 和 Alice 的 UI，以便他們只能看到與其角色對應的按鈕。當 UI 收到來自使用者的請求時，它會查詢 OPA 部分規則，以判斷哪些按鈕應該顯示在 UI 中。當 Bob （使用角色 `viewer`) 向 UI 發出請求時，查詢會將以下內容做為輸入傳遞給 OPA：

```
{
    "role": "viewer"
}
```

OPA 使用 RBAC 結構化的外部資料來做出存取決策：

```
{
    "roles": {
        "viewer": ["viewUsers", "viewData"],
        "dataViewOnly": ["viewData"],
        "admin": ["viewUsers", "viewData", "updateUsers", "updateData"]
    }
}
```

OPA 部分規則同時使用外部資料和輸入來產生允許的動作清單：

```
user_permissions[permissions] {
    permissions := data.roles[input.role][_]
}
```

在部分規則中，OPA 會使用`input.role`指定的 做為查詢的一部分，來判斷應該顯示哪些按鈕。Bob 具有角色 `viewer`，而外部資料指定檢視器有兩個許可： `viewUsers`和 `viewData`。因此，Bob （以及具有檢視器角色的任何其他使用者） 此規則的輸出如下所示：

```
{
    "user_permissions": [
        "viewData",
        "viewUsers"
    ]
}
```

具有 `dataViewOnly`角色的 Shirley 輸出會包含許可按鈕：`viewData`。具有 `admin`角色的 Alice 輸出將包含所有這些許可。查詢 OPA 時，這些回應會傳回 UI`user_permissions`。然後，應用程式可以使用此回應來隱藏或顯示 `viewUsersButton`、`updateUsersButton`、 `viewDataButton`和 `updateDataButton`。