mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-13 11:23:42 +00:00
fix: support cache-mode and make low-trust runs cache read-only (#1412)
Low-trust runs like `pull_request_target` on the default branch could poison caches that `push` runs restore. As on GitHub, they now only restore unless `cache-mode` allows saving. This and the Gitea change can be deployed in any order. Refs: https://gitea.com/gitea/actionslib/pulls/33 https://github.com/go-gitea/gitea/pull/40343 _Written by Claude (Opus 5.5)._ Reviewed-on: https://gitea.com/gitea/runner/pulls/1412 Reviewed-by: bircni <[email protected]> Co-authored-by: silverwind <[email protected]>
This commit is contained in:
1 parent
c2046d597c
commit
4f59e3e418
8 files changed
+127
-26
No files matched your search
@@ -62,6 +62,7 @@ type JobCredential struct {
|
||||
Ref string `json:"ref"`
|
||||
BaseRef string `json:"base_ref"`
|
||||
DefaultRef string `json:"default_ref"`
|
||||
CacheMode string `json:"cache_mode"` // empty allows everything
|
||||
|
||||
// Results is the instance whose artifact service this server forwards for the job, and
|
||||
// InsecureTLS how the runner reaches it; see results.go.
|
||||
@@ -218,10 +219,10 @@ func StartHandler(opts Options) (*Handler, error) {
|
||||
}
|
||||
|
||||
router := http.NewServeMux()
|
||||
router.HandleFunc("GET "+apiPath+"/cache", h.bearerAuth(h.find))
|
||||
router.HandleFunc("POST "+apiPath+"/caches", h.bearerAuth(h.reserve))
|
||||
router.HandleFunc("PATCH "+apiPath+"/caches/{id}", h.bearerAuth(h.upload))
|
||||
router.HandleFunc("POST "+apiPath+"/caches/{id}", h.bearerAuth(h.commit))
|
||||
router.HandleFunc("GET "+apiPath+"/cache", h.cacheModeAuth(false, h.find))
|
||||
router.HandleFunc("POST "+apiPath+"/caches", h.cacheModeAuth(true, h.reserve))
|
||||
router.HandleFunc("PATCH "+apiPath+"/caches/{id}", h.cacheModeAuth(true, h.upload))
|
||||
router.HandleFunc("POST "+apiPath+"/caches/{id}", h.cacheModeAuth(true, h.commit))
|
||||
router.HandleFunc("POST "+apiPath+"/clean", h.bearerAuth(h.clean))
|
||||
// Artifact GET is signed via query-string HMAC because @actions/cache
|
||||
// does not attach Authorization when downloading archiveLocation.
|
||||
@@ -956,6 +957,30 @@ func (h *Handler) bearerAuth(handler http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// cacheModeAuth is bearerAuth refusing what the job's cache-mode forbids, in the shapes the toolkit reports as a warning.
|
||||
func (h *Handler) cacheModeAuth(write bool, handler http.HandlerFunc) http.HandlerFunc {
|
||||
return h.bearerAuth(func(w http.ResponseWriter, r *http.Request) {
|
||||
mode := credFromContext(r.Context()).CacheMode
|
||||
access, forbidden := "read", "write-only"
|
||||
if write {
|
||||
access, forbidden = "write", "read"
|
||||
}
|
||||
if mode != "none" && mode != forbidden {
|
||||
handler(w, r)
|
||||
return
|
||||
}
|
||||
message := fmt.Sprintf("cache %s denied: the job's cache-mode is %s", access, mode)
|
||||
switch {
|
||||
case !strings.HasPrefix(r.URL.Path, cacheServiceV2Path):
|
||||
h.responseJSON(w, r, http.StatusForbidden, map[string]any{"message": message})
|
||||
case write:
|
||||
h.responseJSON(w, r, http.StatusOK, map[string]any{"ok": false, "message": message})
|
||||
default:
|
||||
h.twirpError(w, r, twirpDenied, errors.New(message))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) unauthorized(w http.ResponseWriter, r *http.Request, err error) {
|
||||
if strings.HasPrefix(r.URL.Path, cacheServiceV2Path) {
|
||||
h.twirpError(w, r, twirpUnauthenticated, err)
|
||||
|
||||
Reference in new issue
Block a user