mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-07 15:50:28 +00:00
enhance(runner): add extra HTTP headers (#1269)
Add `runner.extra_headers` for reverse proxies that require request headers on the runner's API requests to Gitea, like registration, polling and log and status reports. Headers the runner sets itself (authentication, User-Agent, protocol headers) stay authoritative. Extra headers are dropped when a redirect changes the hostname or leaves HTTPS, while the runner's own authentication headers follow redirects as before. Invalid or case-insensitively duplicated header names are rejected at config load. Fixes: https://gitea.com/gitea/runner/issues/171 Co-authored-by: silverwind <[email protected]> Reviewed-on: https://gitea.com/gitea/runner/pulls/1269 Co-authored-by: bircni <[email protected]>
This commit is contained in:
1 parent
3802feb3fe
commit
ff30f39b1b
8 files changed
+131
-11
No files matched your search
@@ -83,6 +83,11 @@ runner:
|
||||
# and github_mirror is not empty. In this case,
|
||||
# it replaces https://github.com with the value here, which is useful for some special network environments.
|
||||
#github_mirror: ''
|
||||
# Extra HTTP headers for runner API requests to Gitea, e.g. reverse proxy authentication.
|
||||
# Not used for action clones, artifacts, job summaries or jobs. Runner headers take precedence.
|
||||
# Dropped on redirects to another hostname, or to HTTP if the instance URL uses HTTPS.
|
||||
#extra_headers:
|
||||
# X-Proxy-Token: 'example'
|
||||
# When true (the default), fetch only the requested ref of an action repository (e.g. actions/checkout@v4) at depth 1 instead of cloning every branch's full history.
|
||||
# Set to false to clone the full history.
|
||||
#action_shallow_clone: true
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// RequestTimeout bounds every RPC to Gitea, and with it runner.fetch_timeout.
|
||||
@@ -60,6 +61,7 @@ type Runner struct {
|
||||
Timeout time.Duration `yaml:"timeout"` // Timeout specifies the duration for runner timeout.
|
||||
ShutdownTimeout time.Duration `yaml:"shutdown_timeout"` // ShutdownTimeout specifies the duration to wait for running jobs to complete during a shutdown of the runner.
|
||||
Insecure bool `yaml:"insecure"` // Insecure indicates whether the runner operates in an insecure mode.
|
||||
ExtraHeaders map[string]string `yaml:"extra_headers"` // ExtraHeaders adds HTTP headers to runner API requests to Gitea.
|
||||
FetchTimeout time.Duration `yaml:"fetch_timeout"` // FetchTimeout specifies the timeout duration for fetching resources.
|
||||
FetchInterval time.Duration `yaml:"fetch_interval"` // FetchInterval specifies the interval duration for fetching resources.
|
||||
FetchIntervalMax time.Duration `yaml:"fetch_interval_max"` // FetchIntervalMax specifies the maximum backoff interval when idle.
|
||||
@@ -320,6 +322,9 @@ func LoadDefault(file string) (*Config, error) {
|
||||
if !slices.Contains(ToolCacheModes, cfg.Runner.ToolCacheMode) {
|
||||
return nil, fmt.Errorf("invalid runner.tool_cache_mode %q: must be one of %q", cfg.Runner.ToolCacheMode, ToolCacheModes)
|
||||
}
|
||||
if err := validateExtraHeaders(cfg.Runner.ExtraHeaders); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg.Host.WorkdirParent == "" {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@@ -453,6 +458,21 @@ func definedRunnerConfigKeys(content []byte) (map[string]bool, error) {
|
||||
return defined, nil
|
||||
}
|
||||
|
||||
func validateExtraHeaders(headers map[string]string) error {
|
||||
seen := make(map[string]string, len(headers))
|
||||
for name, value := range headers {
|
||||
if !httpguts.ValidHeaderFieldName(name) || !httpguts.ValidHeaderFieldValue(value) {
|
||||
return fmt.Errorf("invalid runner.extra_headers entry %q", name)
|
||||
}
|
||||
key := strings.ToLower(name)
|
||||
if other, ok := seen[key]; ok {
|
||||
return fmt.Errorf("runner.extra_headers sets %q and %q, which name the same header", other, name)
|
||||
}
|
||||
seen[key] = name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveSecretFile reads key from key_file, so deployments can mount a secret instead of committing it.
|
||||
func resolveSecretFile(key string, secret *string, file string) error {
|
||||
if file == "" {
|
||||
|
||||
@@ -89,6 +89,25 @@ func TestLoadDefault_ToolCacheMode(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "tool_cache_mode")
|
||||
}
|
||||
|
||||
func TestLoadDefault_LoadsExtraHeaders(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||||
require.NoError(t, os.WriteFile(path, []byte("runner:\n extra_headers:\n X-Proxy-Token: proxy-token\n"), 0o600))
|
||||
|
||||
cfg, err := LoadDefault(path)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, map[string]string{"X-Proxy-Token": "proxy-token"}, cfg.Runner.ExtraHeaders)
|
||||
|
||||
for _, headers := range []string{
|
||||
"X-Proxy Token: proxy-token\n",
|
||||
"X-Proxy-Token: |\n proxy-token\n",
|
||||
"X-Proxy-Token: a\n x-proxy-token: b\n",
|
||||
} {
|
||||
require.NoError(t, os.WriteFile(path, []byte("runner:\n extra_headers:\n "+headers), 0o600))
|
||||
_, err = LoadDefault(path)
|
||||
assert.ErrorContains(t, err, "runner.extra_headers", headers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDefault_DefaultsWorkdirCleanupAge(t *testing.T) {
|
||||
cfg, err := LoadDefault("")
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in new issue
Block a user