今日目的:把 Gitea 的 Push 事件接上先前建立的 Pipeline,並且讓這條路徑具備「只有 Gitea 送得動」的能力。
先備知識:本篇銜接先前〈Workspace/PVC〉與〈Webhook DNS〉兩篇的部署成果。
這篇有兩處是把先前的判斷推翻,不是補充。先攤開來:
| 議題 | 原本的判斷 | 實測結果 |
|---|---|---|
| EventListener 的 RBAC | 必須手動綁 RoleBinding + ClusterRoleBinding,缺一就壞 | 在 OpenShift Pipelines 上兩個都不必綁,Operator 已經處理掉。缺漏的症狀在這台重現不出來 |
| Webhook 簽章驗證 | 已知缺口,要自己想辦法 | 內建的 github ClusterInterceptor 直接認得 Gitea 的簽章,不必自寫 Interceptor |
| 被擋下的請求 | (原本沒討論) | HTTP 一律回 202,EventListener 的 log 也不會記錄原因 |
| 傳 Commit SHA 給 git-clone | 淺層拉取做不到 | 在 Gitea 上做得到。結論不變,但理由要換 |
CRC 2.61.0+6eb443
OpenShift 4.21.14 / Kubernetes v1.34.6(單節點)
Red Hat OpenShift Pipelines Operator 1.23.1(controller v1.12.2)
Tekton Triggers v0.36.0(enable-api-fields: stable)
Pipelines as Code v0.48.1
Gitea 1.27.0
用戶端:Windows 11 Pro + PowerShell 5.1
下面所有結論只對這組版本成立。 Triggers、Operator、Gitea 任何一個換版本都要重驗——本篇至少有兩處結論就是因為版本不同而與先前的敘述相反。
| 角色 | 資源類型 | 職責 |
|---|---|---|
| 事件監聽器 | EventListener | 在叢集內開一個 HTTP 端點(獨立 Pod + Service),接收 Webhook |
| 資料綁定器 | TriggerBinding | 從 JSON Payload 擷取欄位,轉成具名參數 |
| 資源模板 | TriggerTemplate | 接參數,實體化出 PipelineRun/TaskRun |
流程是:EventListener 收請求 ➔ Interceptor 驗證與過濾 ➔ TriggerBinding 擷取 ➔ TriggerTemplate 派生資源。
Interceptor 這一段在原本的敘述裡被略過了,但它是本篇後半的重點——驗證與過濾是兩件事,而且它們跑在同一個地方、寫同一份 log。
四個物件、一個 Webhook,跑通為止。ServiceAccount 沿用 ci 既有的 pipeline,不新建(理由見第三節)。
$OC = (Get-ChildItem "$env:USERPROFILE\.crc\cache" -Recurse -Filter "oc.exe" | Select-Object -First 1).FullName
$kubeconfig = "$env:USERPROFILE\.crc\machines\crc\kubeconfig"
$secretValue = -join ((48..57) + (97..122) | Get-Random -Count 36 | ForEach-Object {[char]$_})
& $OC --kubeconfig $kubeconfig create secret generic d14-webhook-secret `
--from-literal=secretToken="$secretValue" -n ci
Get-Random -Count是不重複取樣,取樣池只有 36 個字元,所以最多只能取 36 個。寫 40 不會報錯,會靜靜給你 36 個。
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
name: d14-gitea-push-binding
namespace: ci
spec:
params:
- name: git-url
value: $(body.repository.clone_url)
- name: git-commit
value: $(body.after)
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: d14-gitea-push-template
namespace: ci
spec:
params:
- name: git-url
- name: git-commit
resourcetemplates:
- apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
generateName: d14-run-
labels:
d14-trigger: "true"
spec:
pipelineRef:
name: d15-happy-path
timeouts:
pipeline: 10m
workspaces:
- name: shared-workspace
persistentVolumeClaim:
claimName: pipeline-source-pvc
三個容易踩的點:
resourcetemplates,全小寫。上游文件在不同頁面出現過兩種寫法。x-kubernetes-preserve-unknown-fields: true,oc explain 走不進去,API Server 也不驗欄位——寫錯只會在套用時被 Triggers 自己的 webhook 擋下,或者根本不擋、執行期才炸。volumeClaimTemplate。StorageClass 若是 Retain,Webhook 每觸發一次就多一顆 Released 的孤兒 PV。apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: d14-gitea-ci
namespace: ci
spec:
serviceAccountName: pipeline
triggers:
- name: gitea-push
interceptors:
- name: "verify gitea signature"
ref:
name: github
params:
- name: secretRef
value:
secretName: d14-webhook-secret
secretKey: secretToken
- name: eventTypes
value: ["push"]
- name: "only main branch"
ref:
name: cel
params:
- name: filter
value: "body.ref == 'refs/heads/main'"
bindings:
- ref: d14-gitea-push-binding
template:
ref: d14-gitea-push-template
套用後約 80 秒:
$ oc get el,deploy,svc -n ci
eventlistener.triggers.tekton.dev/d14-gitea-ci
http://el-d14-gitea-ci.ci.svc.cluster.local:8080 True MinimumReplicasAvailable True
deployment.apps/el-d14-gitea-ci 1/1 1 1 84s
service/el-d14-gitea-ci ClusterIP 10.217.5.28 <none> 8080/TCP,9000/TCP 84s
8080 是事件入口、9000 是 metrics。位址不必自己拼,EventListener 的 status.address.url 直接寫好了。
{
"type": "gitea",
"active": true,
"branch_filter": "*",
"events": ["push"],
"config": {
"url": "http://el-d14-gitea-ci.ci.svc.cluster.local:8080",
"content_type": "json",
"http_method": "post",
"secret": "<與 Secret 相同的值>"
}
}
curl.exe -s -X POST --cacert $CA `
-H "Authorization: token $token" -H "Content-Type: application/json" `
-d "@hook.json" `
"$GITEA/api/v1/repos/gitea_admin/frontend-nx-mono/hooks"
三個坑:
content_type 要填 json,不是 application/json。 填錯不會報錯,但 Body 會變成 form-encoded,所有 Interceptor 一起失效。ALLOWED_HOST_LIST 只放行 external,打向 ClusterIP(私有網段)會被 Gitea 自己擋掉。本系列先前已經設成 loopback,private,external。curl.exe 的 stdin。Windows PowerShell 的 Unicode 編碼一律帶 BOM,Gitea 會回 422 invalid character '\ufeff' at start of value。寫檔用 [System.IO.File]::WriteAllText($path, $json, (New-Object System.Text.UTF8Encoding($false))),再用 -d "@檔名"。PowerShell 7 預設 utf8NoBOM,不會踩到——這是 5.1 專屬的行為。觸發前 Gitea HEAD = d5e9787079056cb7f34320c7f8402f904d9beea1
[21:23:26] d14-run-82c4j Unknown/Running
[21:24:16] d14-run-82c4j True/Succeeded
results.COMMIT = d5e9787079056cb7f34320c7f8402f904d9beea1 ← 相等
順帶一個沒寫卻出現的欄位:
spec:
taskRunTemplate:
serviceAccountName: pipeline # ← TriggerTemplate 裡沒有這一行
這是 TektonConfig .spec.trigger.default-service-account 注入的。TriggerTemplate 看不到,PipelineRun 的 YAML 裡才有——排查「這條 run 到底用什麼身分跑」的時候要往這裡找。
Tekton Triggers 上游提供兩份給 EventListener 用的 ClusterRole,分別對應兩種範疇:
| 綁定方式 | ClusterRole | 授權內容 |
|---|---|---|
| RoleBinding(namespace) | tekton-triggers-eventlistener-roles |
triggers.tekton.dev 五種資源的 get/list/watch;configmaps 讀取;tekton.dev 的 pipelineruns/taskruns create;serviceaccounts impersonate;events create/patch |
| ClusterRoleBinding | tekton-triggers-eventlistener-clusterroles |
clustertriggerbindings / clusterinterceptors 的 get/list/watch;secrets 的 get/list/watch |
有一個細節值得先講清楚:這兩組權限的資源並不重疊。 triggers.tekton.dev 那組只有 get/list/watch,完全沒有 create;create 只給 tekton.dev 的 pipelineruns/taskruns。所以「list/watch 給了但 create 忘了」這種半殘狀態,只有在自己手寫 Role 時才會出現。
另一個細節更值得注意:-clusterroles 那份的 secrets 沒有 resourceNames 限制。
ClusterRole/tekton-triggers-eventlistener-clusterroles
(core) / secrets => get, list, watch ← resourceNames: 無
也就是說,一旦用 ClusterRoleBinding 把它綁給某個 ServiceAccount,那個 SA 就取得全叢集所有 namespace 的 Secret 讀取權。對一個以供應鏈防護為題的環境,這個代價不小。
實測的結果是:EventListener 用 ci 既有的 pipeline SA,沒有建立任何 RoleBinding 或 ClusterRoleBinding,Pod 一次就起來,READY 1/1。
原因是 Operator 已經幫你綁好了,而且不是用你以為的方式。把授權鏈整條追出來:
clusterinterceptors 的來源
ClusterRoleBinding/openshift-pipelines-clusterinterceptors
roleRef : ClusterRole/openshift-pipelines-clusterinterceptors
subjects : ServiceAccount/pipeline (ns=default)
ServiceAccount/pipeline (ns=gitea)
ServiceAccount/pipeline (ns=ci) ← 逐一列舉每個使用者 namespace
…
clustertriggerbindings 的來源
ClusterRoleBinding/tekton-clustertriggerbindings-view-rolebinding-all-users
roleRef : ClusterRole/tekton-clustertriggerbindings-view-role
subjects : Group/system:authenticated ← 所有 SA 都在這個群組裡
clusterinterceptors 與 clustertriggerbindings 都是 cluster-scoped 資源。而 ci 的 pipeline SA 另外有一個 namespace 內的 RoleBinding 綁到 ClusterRole/edit。
於是很容易做出這個推論:「edit 聚合了 Triggers 的權限,所以 pipeline SA 什麼都有。」
這個推論一半是對的,一半是錯的。 namespace 的 RoleBinding 在原理上不可能授予 cluster-scoped 資源的 list/watch——edit 只解釋得了 namespace 內那一半。
而 oc auth can-i 這時候幫不上忙:
& $OC --kubeconfig $kubeconfig auth can-i list clusterinterceptors `
--as=system:serviceaccount:ci:pipeline
# yes
這個 yes 是真的,但它來自上面那兩個 Operator 建立的 ClusterRoleBinding,不是來自 edit。can-i 只告訴你能不能,不告訴你為什麼能。 拿它的答案去反推授權來源,會推錯。
要確認來源,得反過來從 ClusterRoleBinding 掃:
& $OC --kubeconfig $kubeconfig get clusterrolebinding -o json |
ConvertFrom-Json |
Select-Object -ExpandProperty items |
Where-Object { $_.subjects.name -contains 'pipeline' -or $_.subjects.kind -contains 'Group' }
三種情況:
pipeline SA,自己建了一個新的 ServiceAccount——它不在 openshift-pipelines-clusterinterceptors 的 subjects 名單裡。要綁的時候,先想清楚要不要 -clusterroles。 下一節會說明,只要 Secret 放對地方,它可以不綁。
github Interceptor 認得 GiteaGitea 的 Webhook 每次 delivery 都會同時送出三組簽章標頭與三套事件標頭:
已設 secret:
X-Gitea-Signature 前綴=(無) hex 長度=64
X-Gogs-Signature 前綴=(無) hex 長度=64
X-Hub-Signature 前綴=sha1= hex 長度=40
X-Hub-Signature-256 前綴=sha256= hex 長度=64
X-Gitea-Event / X-Github-Event / X-Gogs-Event = push
X-Gitea-Signature 與 X-Hub-Signature-256 是同一個 HMAC-SHA256 值,差別只在前綴慣例。而 Tekton 的 github Interceptor 讀的正是 X-Hub-Signature-256——所以它能驗 Gitea 的簽章,不必自己寫 Interceptor。
社群早年有好幾個自寫的 gitea interceptor 專案,那是因為當時 Gitea 還沒補齊 GitHub 相容標頭。是 Gitea 端補上的,不是 Tekton 改的。(Gitea 從哪一版開始送
X-Hub-Signature-256,我沒有查證,只能說在 1.27.0 上成立。)
未設 secret 時的形態不一樣,而且很關鍵:
X-Gitea-Signature: ""
X-Hub-Signature: "sha1="
X-Hub-Signature-256: "sha256="
標頭還在,前綴還在,只是後面沒有內容。 Interceptor 面對的是「空簽章」而不是「缺標頭」——排查時看到的錯誤訊息會不一樣。
實測結論:Secret 建在 EventListener 所在的 namespace(ci)就夠,不需要綁 -clusterroles,不需要給任何人全叢集的 Secret 讀取權。
這一點原本從 RBAC 佈局推不出來——tekton-triggers-core-interceptors(跑在 openshift-pipelines)與 EventListener 的 SA 兩邊都有不限對象的 secrets 讀取權,兩個候選者權限都夠。只能靠實測分辨,而實測的答案是「放 ci、不綁、照樣通過」。
github Interceptor 的 addChangedFiles 與 githubOwners 會去呼叫 GitHub API。Gitea 沒有對應端點,一開就壞。
這一節是本篇的核心。
CEL 判斷 Payload 的內容,github Interceptor 驗證請求的來源。兩者不能互相取代。要證明這件事,最直接的方式是把同一個偽造請求送兩次,中間只改 EventListener 的設定。
在 default namespace 起一顆臨時 Pod,直接打 ci 的 EventListener Service,Body 裡塞一個 ref: refs/heads/main,不帶任何簽章:
| EventListener 設定 | 請求來源 | 簽章 | HTTP | 產生 PipelineRun |
|---|---|---|---|---|
github + cel |
default 的臨時 Pod |
無 | 202 | 0 |
只有 cel |
同一顆 Pod、同一個 Body | 無 | 202 | 1(跑完並 Succeeded) |
少了簽章驗證,叢集內任何一顆 Pod 都能觸發一條真實的流水線。
而且要注意曝露範圍這件事的實際意義:
$ oc get svc el-d14-gitea-ci -n ci
el-d14-gitea-ci ClusterIP 10.217.5.28 <none> 8080/TCP,9000/TCP
ci 的 Route 數 = 0
Service 是 ClusterIP、沒有 Route,所以攻擊面確實限於叢集內部。但上面那顆 Pod 就在叢集內部,而且在另一個 namespace。「內部」不等於「可信」——網路層對這個 Service 沒有任何阻隔。
上一節的表格裡,兩列的 HTTP 都是 202。這不是巧合。
四種情況——簽章正確、簽章錯誤、無簽章標頭、空值簽章——EventListener 全部回 202 Accepted,連 Response Body 都一樣:
{"eventListener":"d14-gitea-ci","namespace":"ci","eventListenerUID":"…","eventID":"…"}
攻擊者無法從回應分辨自己是被擋還是觸發成功;防守方也不能用回應碼做監控。
這是上游已知的行為。issue #1465(2022-10 提出)描述的一模一樣:EventListener 即使 Interceptor 失敗仍回 202、不產生任何資源。那張 issue 最後被 stale bot 標記 lifecycle/rotten 自動關閉,沒有關聯的修補 PR。從 v0.21.0 到本篇實測的 v0.36.0,行為沒有改變。
範圍還更大一點:issue #1183 顯示連「PipelineRun 建立失敗」也是回 202。實測也重現了——刻意讓 generateName 帶大寫,API Server 拒絕建立,HTTP 仍然是 202。
被擋下的請求,EventListener 自己的 log 裡查不到原因。原因寫在 tekton-triggers-core-interceptors——那是跑在 openshift-pipelines、全叢集共用的一個 Deployment:
{"level":"info","ts":1786228907.9590743,"caller":"server/server.go:157","msg":"Interceptor response is: &{Extensions:map[] Continue:false Status:{Code:FailedPrecondition Message:payload signature check failed}}"}
{"level":"info","ts":1786228908.0291278,"caller":"server/server.go:157","msg":"Interceptor response is: &{Extensions:map[] Continue:false Status:{Code:FailedPrecondition Message:no X-Hub-Signature-256 header set}}"}
只有這裡直接說出「這是簽章問題」。 但它有三個限制。
限制一:這行 log 沒有歸屬資訊。
整行只有四個欄位:level / ts / caller / msg。沒有 namespace、沒有 EventListener 名稱、沒有 eventID。而 caller 對成功與失敗都是同一個 server/server.go:157——通過與被擋走的是同一行程式碼。
在跑著多個 namespace、多個 EventListener 的叢集上,看到這一行不知道是誰。它只能告訴你「這座叢集裡有某個 EventListener 收到一個簽章不對的請求」。
對照之下,EventListener 自己的 log 帶著 namespace 與 eventID,卻沒有失敗原因。帶得動歸屬資訊的元件沒有錯誤內容,有錯誤內容的元件沒有歸屬資訊。
限制二:CEL 的正常過濾寫在同一條流裡。
{"level":"info","ts":1786229292.9724135,"caller":"server/server.go:157","msg":"Interceptor response is: &{Extensions:map[] Continue:false Status:{Code:FailedPrecondition Message:expression body.ref == 'refs/heads/main' did not return true}}"}
同一個 pod、同一個 info 等級、同一個格式、同一個 Code:FailedPrecondition。唯一的差別是 Message: 後面的自由文字。
這代表你只能對非結構化文字做比對,而 CEL 運算式的原文會被完整印進同一個欄位——如果有人的 CEL 條件剛好提到 signature 之類的字,grep 就會誤中。
更麻煩的是比例:正式環境裡 CEL 的正常過濾(只收 main、擋掉 tag 與其他分支)佔絕大多數,簽章失敗是罕見事件。偽造嘗試會淹在正常過濾裡。
限制三:流水線的擁有者沒有權限讀它。
$ oc auth can-i get pods/log -n openshift-pipelines --as=system:serviceaccount:ci:pipeline
no
$ oc auth can-i get pods/log -n ci --as=system:serviceaccount:ci:pipeline
yes
一般已驗證使用者也是 no。唯一能說出原因的訊號,落在流水線擁有者沒有權限的 namespace。
EventListener Service 的 9000 埠有 metrics,但它分不出原因:
四次重放(1 正確 + 3 被攔截):
eventlistener_event_received_total{…,status="succeeded"} 4 ← 四次全是 succeeded
eventlistener_triggered_resources_total 1
那個 status 標籤反映的是 HTTP 層有沒有處理完,不是 trigger 的結果。真正能用的只有兩個 counter 的差值——「收到但什麼都沒產生」。
但差值也分不出原因。簽章被擋、CEL 正常過濾、資源建立失敗,三種在 metrics 上長得一模一樣。而且跟 6.2 的限制二一樣,正常的 CEL 過濾本來就會讓差值一直很大。
這個差值算術還有一個前提沒驗:
triggered_resources_total計的是資源數,一個 TriggerTemplate 若產生兩個資源,它會跳幾?我沒有測。要拿差值做告警的話,這個前提得先確認。
訊號存在,但歸屬資訊與失敗原因分屬兩個元件、兩份 log、兩種 schema,而流水線擁有者只拿得到沒有原因的那一份。
這不是「沒有紀錄」,也不是「去看 core-interceptors 就好」。要在正式環境做到「偵測有人在偽造 Webhook」,這三個斷層都得自己補。
第五節證明了「誰能觸發」是一個攻擊面。這一節是另一個獨立的攻擊面:觸發之後,Pipeline 用的參數是誰給的。
第五節那次成功的偽造觸發,Payload 裡帶的是:
"clone_url": "http://attacker/evil.git"
但實際跑出來的結果是:
URL = http://gitea-http.gitea.svc.cluster.local:3000/gitea_admin/frontend-nx-mono.git
COMMIT = d5e9787079056cb7f34320c7f8402f904d9beea1
偽造的 URL 沒有被採用——因為 d15-happy-path 把 URL 寫死在 Task 層,Pipeline 頂層根本沒有宣告 spec.params。
這是運氣,不是設計。 把 git-url 開成 param、由 TriggerBinding 從 Payload 餵入,是幾乎所有教學的寫法。那樣寫的話,同一個偽造請求就會讓叢集去 clone 攻擊者指定的位址。
「誰能觸發」和「參數從哪來」是兩個獨立的問題,各自要各自的防護。
git-revision 傳分支名,不傳 SHAWebhook Payload 裡有 body.after,直覺上可以直接餵給 git-clone 的 REVISION。
原本的判斷是「DEPTH=1 的淺層拉取認不得孤立的 SHA」。這個判斷在 Gitea 1.27.0 上不成立。
三條 PipelineRun 串行送出(共用同一顆 RWO PVC),REVISION 各給一種值,DEPTH 全部維持 git-clone 的預設值 1:
REVISION=main → COMMIT=d5e9787079056cb7f34320c7f8402f904d9beea1 Succeeded
REVISION=d5e9787…(tip SHA) → COMMIT=d5e9787079056cb7f34320c7f8402f904d9beea1 Succeeded
REVISION=ec99892…(前一個 commit) → COMMIT=ec99892fca81869e7ee37431b18b29b24bf7dd0b Succeeded
三種都成功 checkout,results.COMMIT 精確等於指定的值——包括非 tip 的 SHA,而且是在 DEPTH=1 之下。這比「加大 depth 就能拿到」更強:淺層拉取本身不是障礙。
Gitea 的 app.ini 裡沒有 [git] 段、也沒有 uploadpack.allowAnySHA1InWant 之類的覆寫,所以這是預設行為,不是這台特別開過。
範圍限制:這個 repo 只有 2 個 commit,所以「舊 SHA」只測到往回一個。不要外推成「任意深度的歷史 SHA 都行」——更深的歷史、或伺服器端關掉 SHA1-in-want 的情況都沒有測。
結論不變,但理由要換。 傳分支名的好處不是「協定上非如此不可」,而是不依賴 Git 伺服器的 SHA1-in-want 政策。同一份 Pipeline 搬到 GitHub 或設定較嚴格的 GitLab 上,直接傳 SHA 可能會失敗,傳分支不會。
職責劃分也更乾淨:觸發期只負責「去哪裡拿」,「拿到了什麼」由執行 fetch 的 Task 自己回報:
value: $(tasks.git-clone.results.COMMIT)
用 Gitea 的 Test Delivery 測試時要知道:它與真實 push 的欄位結構完全相同(頂層 10 個、commits[] 子欄位 10 個、repository 子欄位 65 個,一個不多一個不少),但有兩處值的差異會影響行為:
before == after,所以 compare_url 退化成同一個 SHA 比自己commits[].added / modified / removed 是 null 而不是陣列
第二點最重要:要測「只有改到某些檔案才觸發」這種邏輯,Test Delivery 不夠,得真的推一個 commit。
另外,建立新分支的 push 是第三種形態:before 全零、commits 空陣列、total_commits: 0。TriggerBinding 若綁了 $(body.commits[0].id),這種事件會解析失敗。
ref 對得上,resourcetemplates 是全小寫。pipeline SA 通常不必綁任何東西。要綁之前先用 ClusterRoleBinding 反查授權來源,不要用 oc auth can-i 的答案去推論原因。-clusterroles:它的 secrets 沒有 resourceNames 限制,綁下去等於全叢集 Secret 讀取權。Secret 放在 EventListener 所在的 namespace 就夠。github ClusterInterceptor + Gitea 端的 Webhook Secret。只有 CEL 等於沒有驗來源。addChangedFiles / githubOwners:那兩個會呼叫 GitHub API。volumeClaimTemplate + Retain 會讓每次觸發多一顆孤兒 PV。content_type 填 json:填 application/json 不報錯但會壞掉。tekton-triggers-core-interceptors(openshift-pipelines),而且你的流水線 SA 讀不到。上游行為的依據
| 來源 | 用途 |
|---|---|
tektoncd/triggers issue #1465 |
Interceptor 失敗仍回 202、只在 info 等級記錄。2022-10 提出,被 stale bot 標記 lifecycle/rotten 自動關閉,無修補 PR |
tektoncd/triggers issue #1183 |
資源建立失敗同樣靜默回 202;原報告的重現方式就是 generateName 用大寫 |
tektoncd/triggers config/200-clusterrole.yaml |
兩份官方 ClusterRole 的權威定義(含 -clusterroles 的 secrets 無 resourceNames 這件事) |
| Tekton EventListeners 文件 | Service 預設 ClusterIP / 8080、el- 命名規則、202 回應格式、綁 ClusterRole 的官方指引 |
| Tekton Interceptors 文件 | github Interceptor 的 secretRef / eventTypes / addChangedFiles / githubOwners |
| Tekton Triggers Troubleshooting | EventListener 自身 log 的欄位與等級調整方式(第六節的對照組) |
Gitea 端
| 來源 | 用途 |
|---|---|
| Gitea Webhooks 文件 | 三組簽章標頭的定義;未設 secret 時標頭仍在但 digest 為空這件事有官方明文 |
services/webhook/deliver.go |
標頭的產生點原始碼:X-Gitea-Signature 無前綴、X-Hub-Signature 帶 sha1=、X-Hub-Signature-256 帶 sha256= |
Gitea API 文件 / repoCreateHook 原始碼 |
POST /repos/{owner}/{repo}/hooks 的 type / config.content_type / branch_filter 合法值 |
踩坑時會查到、但要小心的
| 來源 | 說明 |
|---|---|
triggers.tekton.dev/old-escape-quotes(另見 issue #1562、issue #257) |
社群用來繞過「JSON/陣列/多行字串塞進 TriggerTemplate 造成 unmarshal 失敗」的 annotation。 |
PowerShell about_Character_Encoding |
見「Character encoding in Windows PowerShell」一節:Windows PowerShell 的 Unicode 編碼一律帶 BOM,PowerShell 6+ 才預設 utf8NoBOM |