Jenkins 相關權限管控

最近在公司導入 GitLab + Jenkins 來做 CI 以及自動化測試,為了提升安全性,因此在導入的過程中研究了一下關於 Jenkins 的相關權限設定,除了必須劃分使用者權限之外,Credential 部分也必須限制只有特定身份的專案(使用者)才能存取,且避免以明文的方式顯示在 Console 或是 Jenkinsfile ,這部分花了一些時間研究,因此使用此篇文章來紀錄目前研究到的相關權限設置方法。

此篇文章主要會探討以下三種情境:

限制 User 只能存取特定的 Project/Pipeline

預設 Jenkins 使用者是沒有限制權限的,只要登入就能做任何操作,包括設置 project, credentials 以及 Jenkins Config 等等。但在實際應用上可能需要依照不同使用者給予不同權限,因此這邊會介紹如何去設置使用者權限。 Jenkins 有許多 Plugin 可以用來設定使用者權限,這邊會介紹比較常見的三種方式: Project-based Matrix Authorization StrategyRole-based Authorization StrategyFolder-based Authorization Strategy

Project-based Matrix Authorization Strategy

Project-based Matrix Authorization Strategy 是 Jenkins 內建就安裝好的 Plugin ,只需要去啟用他就可以了。

在此範例我們有三個使用者 (testuser, normaluser 和 admin) 以及三個 pipeline,並且各自使用者有各自的權限:

Pipelines Allow Access
testproject1 testuser, admin
testproject2 testuser, admin
normalproject normaluser, admin

在此範例 admin 是我們用來管理 Jenkins 的最高權限管理者

  1. Jenkins GUI > Manage Jenkins > Configure Global SecurityEnable security 選項啟用,底下 Authorization 部分選擇 Project-based Matrix Authorization Strategy ,就可以啟用該功能。
  1. 預設底下會有兩個 group: Authenticated Users 以及 Anonymous Users ,這兩個可以不用理會他,因為預設 Jenkins 是沒有 group 的,除非有使用到 LDAP 、 Active Directory 或是後面提到的 Role-based Authorization Strategy 才會有 group 的概念。要新增使用者或是群組權限只需要點擊底下的 Add user or group ,新增 username 或是 group name 後,再勾選需要的權限就可以了。在這裡我們新增 testuser,normaluser 以及 admin (這個是我們希望當管理者的帳號名稱,請自行修改成自己環境的帳號) 三個使用者的權限,除了 admin 全部勾選外,其他兩個使用者權限部分暫時先空白。

    請注意一定要新增一個管理者權限的使用者 (在此範例是 admin ),因為只要開啟了 Project-based Matrix Authorization Strategy 的功能,所有權限都會依照這裡設定的權限,所以原本 admin 的權限也會被抽掉,因此這裡一定也要加入 admin 的權限,不然設定好後管理者自己會被鎖在外面 (解法可參考 What is the default Jenkins password? 來關閉 user security 功能)。

    如果在新增 user 或是 group 發現如下圖的情況,表示 Jenkins 找不到該 user 或是 group ,請確認這個 user 及 group 是否真的存在。

  1. 接下來設定 testuser 以及 normaluser 的權限,你會發現這邊只能設定 global 的權限,也就是只能針對整體去做控管,例如 Jobs 就是要馬就全部可存取,要馬就只能唯讀,並沒辦法限制使用者對於特定 Job, pipeline 或 project 等等的權限。如果想要做更細部的權限設定要到特定物件裡面去做設定,在此範例我們想要針對不同 pipeline 設定使用者權限,必須要到 pipeline 的 configure 去做權限設定,在這裡我們稱 local 權限。請注意, global 權限是會蓋過所有 local 權限的,因此在 global 設定這裡,我們必須先給 testuser 以及 normaluser Overall Read 的權限,讓它至少能登入 Jenkins 。
  1. 接下來到 testproject1 的 configure 去查看,會發現多了一個 Project-based Matrix Authorization Strategy 的選項,這邊就是針對這個 pipeline 去做設定 local 權限。將這個選項打勾,並指定 testuser 能夠針對這個 project 做的所有設定。設定好後也到 testproject2 設定一樣的權限。

沒有啟用 local 的 Project-based Matrix Authorization Strategy 的話,使用者權限會直接吃 global 設定, 而 local 那邊預設操作會全部被禁止。

  1. 接下來到 normalproject 的 configure 一樣去加入 normaluser 的權限。
  1. 好了之後登入看看,可以發現已成功限制使用者權限。

Role-based Authorization Strategy

第二種設定使用者權限的方式是使用 Role-based Authorization Strategy ,這種方式是使用 Role (類似 group) 的概念來分配權限,因此能夠更加彈性,且不需要像 Project-based Matrix Authorization Strategy 需要一個個進到 pipeline configure 去做設定,只需要在一個地方做設定即可。

這邊一樣使用之前的範例來做測試:

Pipelines Allow access
testproject1 testuser, admin
testproject2 testuser, admin
normalproject normaluser, admin
  1. 首先確認已經安裝好 Role-based Authorization Strategy Plugin ,接著一樣到 Jenkins GUI > Manage Jenkins > Configure Global Security > Enable security ,在 Authorization 會發現多了一個 Role-Based Strategy 選項,將它勾選。

  2. 接著就可以在 Manage Jenkins 清單底下發現多了一個 Manage and Assign Roles 的項目,進到這個項目裡,然後點擊 Manage Roles

  3. Manage Roles 是用來新增 Role 的地方,注意這邊並不是指派權限給使用者的地方。可以這裡看到有分三種類型的 Role: Global Roles 就是設定 user 或是 group 的 global 權限,與之前在 Project-based Matrix Authorization Strategy 中提到的一樣; Project Roles 是針對 Project 去做權限設定,也就是之後被指派到這個 Role 的 user 或 group 能夠對該 Project 操作上述設定的權限; 最後是 Slave Roles ,這個是針對 Slave 去做權限設定,也就是之後被指派到這個 Role 的 user 或 group 能夠對該 Slave 操作上述設定的權限。

    可以看到 Global Role 預設已經有一個叫做 admin 的管理者權限,這個是防止當你在 Enable security 那邊設定 Role-Based Strategy 之後,因權限制度改變導致管理者失去權限而被鎖在外面的情況。所以預設會建立一個 admin 管理權限,並直接將你當前的使用者套用這個 Role 。

    由於我們的管理者剛好也叫做 admin ,因此會有將 admin 使用者指派給 admin Role 的繞口情況 xD。

  4. 接下來新增會需要用的 Role ,在此範例我們會建立3種 Role:

  • Global Roles - normal => 設定 Overall Read 的權限,也就是至少能夠登入的權限。
  • Project Roles - fortest => 針對 testproject1 和 testproject2 能夠操作的權限。
  • Project Roles - fornormal => 針對 normalproject 能夠操作的權限。

    首先建立 Global Roles。

    接著建立 Project Roles , Pattern 是吃 Regular Expression 的語法,也就是會透過這個 Pattern 抓到對應的 Project ,例如我們可以使用 testproject.* 來抓到 testproject1 以及 testproject2

    完成之後可以點擊 Pattern 看看有比對到那些 Project。

    都新增好了後,點擊底下的 Save 儲存,這樣就完成 Role 的建立。

  1. 接下來到 Manage Jenkins > Manage and Assign Roles > Assign Roles ,這邊我們會指派 Role 給特定 user 或 group ,只需要加入 user 或是 group ,然後勾選要給它的 Role 就可以了。可以看到預設 admin 使用者已經被賦予 admin 權限,這就是前一點提到的防止管理者被踢出去。

    在這一步中,我們會將 testuser 以及 normaluser 先指派 normal 的 Global Role ,讓他們至少可以登入 Jenkins ,再個別指派對應的 Project Role 給使用者,設定完如下圖:

  1. 完成之後登入看看,可以發現已成功限制使用者權限。

Folder-based Authorization Strategy

Role-based Authorization Strategy 雖然很方便,但是在賦予權限的時候還是需要一個個 project 去設定,當 project 或是 pipeline 數量很大的時候管理上就會比較麻煩,因此還有一種方式是使用 Folder-based Authorization Strategy ,從字面上就可以看出來,它是使用 folder 為單位來設定權限,它的設定跟 Role-based Authorization Strategy 幾乎一模一樣,只是設定對象從 project 變成 folder ,接下來透過範例來測試看看。

這邊範例情境與之前一樣,只是新增了兩個 folder ( testfolder 和 normalfolder ),並把:

Pipelines Folder Allow access
testproject1 testfolder testuser, admin
testproject2 testfolder testuser, admin
normalproject normalfolder normaluser, admin
  1. 首先確認已經安裝好 Folder-based Authorization Strategy Plugin ,接著一樣到 Jenkins GUI > Manage Jenkins > Configure Global Security > Enable security ,在 Authorization 會發現多了一個 Folder Authorization Strategy 選項,將它勾選。

  2. Manage Jenkins 清單底下也會多了一個 Folder Authorization Strategy 的項目,接著進到這個項目裡。

  3. 進來之後會發現一樣有三種 Role: Global Roles 就是前面提到的 Global 權限; Folder Roles 是針對 Folder 去做權限設定,也就是之後被指派到這個 Role 的 user 或 group 能夠對該 Folder 操作上述設定的權限; 最後一個是 Agent Roles ,這個是針對 Agent(也就是 Slave) 去做權限設定,也就是之後被指派到這個 Role 的 user 或 group 能夠對該 Agent 操作上述設定的權限。

這邊可以看到預設已經將當前的使用者加入到管理者權限的 Global Roles ,防止當啟用 Folder-Based Strategy 之後,因權限制度改變導致管理者失去權限而被鎖在外面的情況。

  1. 接下來新增會需要用的 Role ,在此範例我們會建立3種 Role:
  • Global Roles - normal => 設定 Overall Read 的權限,也就是至少能夠登入的權限。
  • Folder Roles - fortest => 針對 testfolder 能夠操作的權限。
  • Folder Roles - fornormal => 針對 normalfolder 能夠操作的權限。

    新增 Global Roles - normal

    新增 Folder Roles,Apply on 是設定這個 Role 對應到哪個 Folder ,因此這裡可以看到兩個我們建立的 Folder: testfoldernormalfolder

  1. 建立好 Role 之後,接下來就是將這些 Role 指派給 user 或 group ,直接在同個頁面就可以設定指派。只需要在特定 Role 上面的 SID 輸入 user或是 group 名稱即可。

    指派 Global Roles - normal 給 testuser 以及 normaluser。完成後如下圖。

    指派Folder Roles 給使用者,完成後如下圖:

    • Folder Roles - fortest 給 testuser
    • Folder Roles - fornormal 給 normaluser
  1. 完成之後登入看看,可以發現已成功限制使用者權限。
    normaluser


    testuser

如何在 Pipeline 使用 Credentials

在使用 Pipeline 的時候,有時候執行的 Step 可能會需要使用到一些 password, token, secret 之類的敏感資訊,直接將這些敏感資訊寫在 Jenkinsfile 是相當不安全的,就算只是測試用系統也是不建議這樣做。在 Jenkins 裡可以使用 Credentials Binding Plugin (預設已經安裝) 來支援 credentials 的機制,也就是可以在 Jenkins GUI 事先輸入好需要的敏感資訊,然後再直接設定套用這些 credentials 就可以了。

可以看到 Jenkins 支援很多種格式的 credentials:

這些 credentials 不只可以在 Jenkins GUI 去進行存取(例如給 object 權限),也可以在 Pipeline 裡面去呼叫這些 credentials ,只需要使用 withCredentials block 就可以了,實際使用方法如下:

// Username and Password
withCredentials([usernamePassword(credentialsId: 'gitlab-jenkins', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
sh 'echo account=$USERNAME, password=$PASSWORD'
...
}

// Token
withCredentials([string(credentialsId: 'mytoken', variable: 'TOKEN')]) {
sh '''
set +x
curl -H "Token: $TOKEN" https://some.api/
'''
}

// Secret Text
withCredentials([string(credentialsId: 'secret', variable: 'MYSECRET')]) {
sh '''
set +x
curl -H "Token: $MYSECRET" https://some.api/
'''
}

// Certificate or Secret File
withCredentials([file(credentialsId: 'secret', variable: 'My_CERT_FILE')]) {
sh 'use $My_CERT_FILE'
}

以上列出幾種存取 credentials 的方式,他的格式為 withCredentials(<要存取的credentials 格式>[需要的參數1,需要的參數2, ... ]) ,存取方式非常彈性,可以存取帳密的型態的 credentials、單純一串字串(string())的或是能夠直接取得檔案 (file()),例如一些憑證檔案或是認證檔之類的。 credentialsId 就是你在建立 credentials 時設定的名稱,而 variable 是自行定義一個變數名稱來代表這個 credential,讓你的 pipeline 可以透過這個變數來存取 credential,這個變數只會使用在 當前 的 pipeline,因此是不會影響到其他的 pipeline 的,如果想要如何存取其他格式的 credentials 可以參考 Credentials Binding Plugin

在存取 credentials 之前請記得一定要先在 Jenkins GUI 建立好你的 credentials ,不然跑 pipeline 的時候,會跳出找不到 credentials 的錯誤。

withCredentials block 必須放在 steps 裡面,因此完整的使用範例如下:

pipeline {
agent { ... }

stages {
stage("MyStage") {
steps {
withCredentials([usernamePassword(credentialsId: 'gitlab-jenkins', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
sh 'echo account=$USERNAME, password=$PASSWORD'
...
}
}
}
}
}

這種存取 credentials 的方式非常彈性,且支援許多方式的認證機制,例如 Kubernetes, Docker 或 Vault 等等,非常方便,但是到這裡你可能會想一個問題: 是不是任一 pipeline 只要使用 withCredentials 及知道 credentials id 就可以隨意存取到所有的 credentials 了呢? 例如 A 專案去存取 B 專案的 credentials ,B 專案去存取 C 專案的 credentials 等等。 實際上的確是會有這個問題,為了避免這種情況,因此接下來會介紹如何限制 project 只能 存取特定的 credentials 。

限制 Project 只能存取特定的 Credentials

預設 Jenkins 是沒有針對 credentials 去做權限控管的,這樣子等於任何人都能存取 credentials ,這相當的不安全,因此在這理會介紹如何限制 pipeline 只能存取特定的 credentials 。

這裡會需要使用到 Jenkins folder ,只需要將 pipeline 或是 project 加到特定 folder 裡,接著建立 folder-level 的 credentials 這樣就可以了。

在此範例中,我們有兩個資料夾,分別是: fortest 和 fornormal ,兩個資料夾裡包含各自的 project (testproject1, testproject2, normalproject),我們希望限制只有 fortest 資料夾裡的專案可以存取 testing-secret 這個 credential。

Pipelines Folder Credentials
testproject1 fortest Can access testing-secret
testproject2 fortest Can access testing-secret
normalproject fornormal Can not access testing-secret
  1. 首先進到 fortest 資料夾,可以看到已經有兩個 pipeline。

  2. 接著直接在左邊點擊 Credentials > Folder,建立 folder-level credentials。

  3. 接著新增 testing-secret credential,這樣就建立好 folder-level 的 credentials。

  4. 接下來就來實際測試一下, 以下為測試的 pipeline script ,我們分別跑在 testproject1 以及 normalproject 看看結果如何。

    pipeline {
    agent { ... }

    stages {
    stage("MyStage") {
    steps {
    withCredentials([usernamePassword(credentialsId: 'testing-secret', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
    sh 'echo account=$USERNAME, password=$PASSWORD'
    ...
    }
    }
    }
    }
    }

    testproject1: 可以看到正常的抓到 credentails。

    normalproject: 執行失敗,找不到 credentials,由此可以判定已成功限制 credentials 存取權限。

Summary

在此篇文章我們介紹了許多種方式來限制使用者和 credentials 的權限,在使用者權限管理方面,筆者在公司採用的是 Folder-based Authorization Strategy,除了方便後續專案越來越多比較好管理之外,也可以使用 Folder-based 機制來限制 credentials 權限,比較方便。

最後,由於 Jenkins 有許多的 Plugin 可以使用,因此也許還有更多種方式可以去對權限做管理,這部分如果後續有研究到,再補充上來。

參考資料

限制 User 只能存取特定的 Project

在 pipeline 使用 credentials

限制 Project 只能存取特定的 Credentials

文章內容的轉載、重製、發佈,請註明出處: https://pohsienshih.github.io