---
# System prepended metadata

title: 如何處理 user/group 及其設計
tags: [Yunikorn]

---

# 如何處理 user/group 及其設計

目前 Yunikorn 取得 user 的方法是透過 **pod 的 config yaml 內的 label 屬性**得到的，以下為範例：
```yaml
metadata:
  labels:
    yunikorn.apache.org/username: "john"
```
但是這樣的情況會有冒充身分的問題，且身分組別並不一定與 K8s 所指的 user 相同，因此在以下會討論到的重點為：

- 目標：
    - 建立一種穩定的方式去取得 k8s 的 user 或 group ，並將其與 yunikorn 設定的 user 對應
    - 評估在 shim 裡對於 group 的解析度
    - 定義在 k8s 上的物件的 user 或 group
- 非目標：
    - 不考慮授權或身分驗證的部分，因為那是 yunikorn 或 k8s 的一部分
    - 不改變 shim 與 core 對於身分組資訊的溝通方法

## k8s 內的 user 及 group
k8s 內的 user 分為兩種，一種是 normal user(human) 另一種是 service account(pod內)
而由於 k8s 所需要部署的 yaml 文件內部資訊指儲存了 ServiceAccount ，因此無法直接從 pod 的 config 檔取得對應的 user
至於 user 所屬的 group 則是只分為兩種， `system:authenticated` 及`system:unauthenticated`，但 group 與 user 同理，無法由部署文件得知


## 當前可行方案與挑戰
因為無法從部署文件得知 pod 所屬之 user，因此只能在部署前的更早階段就完成「抓取 user」的動作

![image](https://hackmd.io/_uploads/rJWNV8bzR.png)

如上圖所示，由 Admission Controller 去取得 k8s 的 user 設定，並在新增(Admission Controller 有新增、修改、刪除等等權力) 一個 pod 時，**攔截**並對其 config 檔做更動

但對於「非直接新增」 pod 的方法，如 deployment 、 job 等等方法， Admission Controller 所抓取到得 user 資訊為 deployment 或 job 之 ServiceAccount
針對這個問題， Yunikorn 給出的解法是「新增 API 取得『需監聽的 object』」

下例為 Admission Controller 抓取「直接新增」之 pod ：
```yaml
INFO  webhook/admission_controller.go:180  AdmissionReview  
{
    "Namespace": "default", 
    "UID": "3f8c8df5-0272-4c85-874f-0aa1ece74fbc", 
    "Operation": "CREATE", 
    "UserInfo": {
        "username":"minikube-user",
        "groups":[
            "system:masters",
            "system:authenticated"
        ]
    }
}
```
下例為未使用 API 時， Admission Controller 抓取「非直接新增」之 pod：
```yaml
INFO  webhook/admission_controller.go:177  AdmissionReview  
{
    "Namespace": "default", 
    "UID": "be94ea46-c1d2-442a-a46a-60589d582abd", 
    "Operation": "CREATE", 
    "UserInfo": {
        "username":"system:serviceaccount:kube-system:job-controller",
        "uid":"32abc062-eb53-4dca-9669-55251d687939",
        "groups":[
            "system:serviceaccounts",
            "system:serviceaccounts:kube-system",
            "system:authenticated"
        ]
    }
}
```
下例為使用 API 時， Admission Controller 抓取「非直接新增」之 pod 的結果與上例的差別：
```yaml
INFO  webhook/admission_controller.go:483  Deployment validation - pass through  
{
    "User": {
        "username":"minikube-user",
        "groups":[
            "system:masters",
            "system:authenticated"
        ]
    }
}
```

此方法之可行性請參考 [Git Commit](https://github.com/pbacsko/incubator-yunikorn-k8shim/commit/478d688675a1ca98afb895672295d61b47172d19)
由於此方法的大致方向為「攔截並修改其部署檔案」，因此出現另一個問題為「Admission Controller 是否會覆蓋其原本的 user 或 ServiceAccount」，這個敘述將在[之後](#規避身分冒充問題)詳細定義

## 以 Yunikorn 取得 group
Yunikorn core 是有群組查找功能的，有兩種方法：基於操作系統的簡單查找及虛擬查找

「基於操作系統的簡單查找」就像使用 api 去取得 container 內的資訊，進而得出 group
「虛擬查找」就像在 container 內模擬一個新的 unix user 進而得出 group

此功能是可插拔的，並且以虛擬查找作為預設方式，但由於 contianer 內的作業系統常常為不完整的，因此群組查找通常都是失敗的

Yunikorn core 只有在 shim 沒給出 group 的資訊時才會執行查找，但如一開始說的， shim 預設只傳送 username 給 core ，所以基本上 core 都會執行群組查找；而由於群組查找通常都失敗，因此 group 的取得應該在 shim 那邊做完

## user 資訊物件
在 Admission Controller 取得 user、group 等資訊後，使用 annotation 將資訊加進部署文件內(不使用 label 的原因是可用字長太短)
而 annotation 的名字為 `yunikorn.apache.org/user.info`

在 scheduler interface 內的 `UserGroupInfo` 定義如下
```go
message UserGroupInformation {
// the user name
string user = 1;
// the list of groups of the user, can be empty
repeated string groups = 2;
}
```

用 json 可以表達 user 及 group，且 group 的順序不會亂掉，因此 annotation 內使用 json 表達，以下為例子：
```yaml
yunikorn.apache.org/user.info: "
{
  username: \"joe\",
  groups: [
    \"developers\",
    \"system:authenticated\"
  ]
}"
```
`system:authenticated` 的部分會在 k8s 的授權階段中加入，所以不會特別加

由於 `yunikorn.apache.org/user.info` 這個 annotation 是 yunikorn 所需要的，因此需避免 user 修改已建立的 pod 的部署文件時的更動，畢竟 k8s **不保證**更動時會通知到 shim
避免更動有以下兩個方法：

在**更動**後，將 annotation 修改為自己需要的
在**驗證**時，如果 annotation 被修改過，則駁回

更動的部分， Admission Controller 已在 pod "create" 時監聽，其方法可擴展至 "update"
驗證的部分則在 configMap 註冊一個 hook ，其方法可擴展至 pod
建議為**使用驗證方法**

對於向後兼容性，目前的 label 方法將會持續到下個主要版本(2.0.0)，在此之前，1.x 版本都還在支援的範圍裡面
但如果部署文件裡有舊的方法(label)和新的方法(annotation)時， Yunikorn 將以新方法為準，且**不支援兩者合併**

## 規避身分冒充問題
這個問題由上面提到的 Admission Controller 不該覆蓋掉 user 所給定的 user 資訊延伸而出
對於給定的 user 資訊應該加以管理，因此以上面的圖做為基底，改為下圖

![image](https://hackmd.io/_uploads/By9W2bMMC.png)

對於 controller 部署的 pod ，會有以下兩種結果

開啟 `--use-service-account-credentials` 時，使用的 user 為經過 k8s 認證的 ServiceAccount
(預設)關閉時，使用的 user 資訊變為 `kube-controller-manager`

對於以上兩種情況，設置文件應該都要支援，但推薦只用上面那一種

對於外部創建(如 job 或 deployment)的 pod ，Admission Controller 也應新增 annotation，但對於安全性，與上面 controller 不同的是，此功能應該支持對 user 或 group 進行限制，因此對於外部創建的 pod 之配置應與上述 controller 部署的 pod 分開配置

## 設置文件
依據上面的敘述，會有以下的屬性需要新增，注意，前綴都包含 `ADMISSION_CONTROLLER_`
改變以下屬性**並不會**觸發自動更新

|Name |	Description	| Default|
| -------- | -------- | -------- |
| `BYPASS_AUTH` | Allow external users to create pods with user information set. Type: boolean | false
|BYPASS_CONTROLLERS | Allow controller users to create pods with user information set. Type: boolean | true|
SYSTEM_USERS | Regular expression for the allowed controller service account list. Type: string | "system:serviceaccount:kube-system:\*"|
EXTERNAL_USERS | Regular expression for the allowed external user list. Type: string | "" |
EXTERNAL_GROUPS | Regular expression for the allowed external group list. Type: string | "" |

特定屬性的決策過程以如下 pseudo code 表達：
```
if BYPASS_AUTH 
    if user label and annotation not be setted
        add annotation
else
    add annotation
```
而以上過程又會根據 `EXTERNAL_USERS` 及 `EXTERNAL_GROUPS` 的值來決定是否覆蓋已指定的 user 資訊

--- 
[官方文檔連結](https://yunikorn.apache.org/docs/design/user_group)