mirror of
https://gitea.com/gitea/runner.git
synced 2026-08-29 07:17:46 +00:00
fix: keep the runner's own container.options when privileged is off (#1151)
The runner's own `container.options` and the workflow's were joined into one string before parsing, so the host-escape filter added in https://gitea.com/gitea/runner/pulls/1058 dropped the administrator's options along with the workflow's. Setups that need `--device` or `--security-opt` from the config file had no way left to get them short of enabling privileged mode. `NewContainerInput` now carries the two sources apart, as `RunnerOptions` and `WorkflowOptions`, down to the point where the filter runs. With privileged mode off, the host-escape fields are reset to what the runner's own options parse to on their own, so only the workflow's contribution is dropped. Three further ways a workflow's options reached past its container, all resolved on the runner before anything reaches the daemon: 1. `--env-file` and `--label-file` name files that are read on the runner, so any file it could read became container environment or labels. Both are refused from a workflow now, and still serve the runner's own options. 2. A bare `--env NAME` was resolved from the runner's own environment by docker's validator. That lookup is gone, for every source. Use `runner.envs` or `runner.env_file` to pass a variable on. 3. A volume driver decides for itself what it mounts, and the local driver's `device=` option turns a name `valid_volumes` allows into a bind of any host path. A workflow's mounts may no longer carry one. `--isolation`, `--volume-driver` and the two paths `--security-opt systempaths=unconfined` lands in were also missing from the fields a workflow may not set. Last, the `--network and --net in the options will be ignored.` warning fired for every container, because the runner's own network mode is fed into the parsed options before the check runs. Fixes https://gitea.com/gitea/runner/issues/1142 Reviewed-on: https://gitea.com/gitea/runner/pulls/1151 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -236,10 +236,48 @@ func (fakeContainer) Inspect(context.Context) (*container.Info, error) {
|
||||
|
||||
func (fakeContainer) DumpLogs(context.Context) error { return nil }
|
||||
|
||||
// startJobContainerInputs runs startJobContainer against fakeContainer and returns the
|
||||
// inputs it built, one per container.
|
||||
func startJobContainerInputs(t *testing.T, workflowYAML string, cfg *Config) []*container.NewContainerInput {
|
||||
t.Helper()
|
||||
workflow, err := model.ReadWorkflow(strings.NewReader(workflowYAML))
|
||||
require.NoError(t, err)
|
||||
|
||||
var inputs []*container.NewContainerInput
|
||||
origNewContainer := newContainer
|
||||
newContainer = func(input *container.NewContainerInput) container.ExecutionsEnvironment {
|
||||
inputs = append(inputs, input)
|
||||
return fakeContainer{}
|
||||
}
|
||||
t.Cleanup(func() { newContainer = origNewContainer })
|
||||
|
||||
cfg.Workdir = "/tmp"
|
||||
cfg.ContainerNetworkMode = "host" // an explicit network mode creates no network
|
||||
cfg.Env = map[string]string{}
|
||||
cfg.Secrets = map[string]string{}
|
||||
|
||||
rc := &RunContext{
|
||||
Name: "test",
|
||||
Config: cfg,
|
||||
Env: map[string]string{},
|
||||
Run: &model.Run{
|
||||
JobID: "job",
|
||||
Workflow: workflow,
|
||||
},
|
||||
}
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(t.Context())
|
||||
|
||||
// the inputs are built before the missing daemon fails the first call
|
||||
t.Setenv("DOCKER_HOST", "unix:///nonexistent.sock")
|
||||
require.Error(t, rc.startJobContainer()(t.Context()))
|
||||
|
||||
return inputs
|
||||
}
|
||||
|
||||
// Regression test: a service without a `credentials:` block resolves to empty
|
||||
// credentials, which used to overwrite the job container's own credentials.
|
||||
func TestStartJobContainerKeepsJobCredentialsWithServices(t *testing.T) {
|
||||
workflow, err := model.ReadWorkflow(strings.NewReader(`
|
||||
inputs := startJobContainerInputs(t, `
|
||||
name: test
|
||||
on: push
|
||||
jobs:
|
||||
@@ -259,35 +297,7 @@ jobs:
|
||||
username: db-user
|
||||
password: db-password
|
||||
steps: []
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
|
||||
var inputs []*container.NewContainerInput
|
||||
origNewContainer := newContainer
|
||||
newContainer = func(input *container.NewContainerInput) container.ExecutionsEnvironment {
|
||||
inputs = append(inputs, input)
|
||||
return fakeContainer{}
|
||||
}
|
||||
t.Cleanup(func() { newContainer = origNewContainer })
|
||||
|
||||
rc := &RunContext{
|
||||
Name: "test",
|
||||
Config: &Config{
|
||||
Workdir: "/tmp",
|
||||
ContainerNetworkMode: "host",
|
||||
Env: map[string]string{},
|
||||
Secrets: map[string]string{},
|
||||
},
|
||||
Env: map[string]string{},
|
||||
Run: &model.Run{
|
||||
JobID: "job",
|
||||
Workflow: workflow,
|
||||
},
|
||||
}
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(t.Context())
|
||||
|
||||
t.Setenv("DOCKER_HOST", "unix:///nonexistent.sock")
|
||||
require.Error(t, rc.startJobContainer()(t.Context()))
|
||||
`, &Config{})
|
||||
|
||||
credentials := map[string][2]string{}
|
||||
for _, in := range inputs {
|
||||
@@ -301,10 +311,39 @@ jobs:
|
||||
require.Equal(t, [2]string{"", ""}, credentials["redis:latest"])
|
||||
}
|
||||
|
||||
// Only the workflow's options may be stripped later, so the two sources have to reach the
|
||||
// container apart from each other.
|
||||
func TestStartJobContainerKeepsRunnerOptionsApartFromWorkflowOptions(t *testing.T) {
|
||||
inputs := startJobContainerInputs(t, `
|
||||
name: test
|
||||
on: push
|
||||
jobs:
|
||||
job:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: registry.example/job:latest
|
||||
options: --cap-add SYS_PTRACE
|
||||
services:
|
||||
redis:
|
||||
image: redis:latest
|
||||
options: --shm-size 1g
|
||||
steps: []
|
||||
`, &Config{ContainerOptions: "--device /dev/fuse"})
|
||||
|
||||
options := map[string][2]string{}
|
||||
for _, in := range inputs {
|
||||
options[in.Image] = [2]string{in.RunnerOptions, in.WorkflowOptions}
|
||||
}
|
||||
|
||||
require.Equal(t, [2]string{"--device /dev/fuse", "--cap-add SYS_PTRACE"}, options["registry.example/job:latest"])
|
||||
// a service container gets no options from the runner's config today
|
||||
require.Equal(t, [2]string{"", "--shm-size 1g"}, options["redis:latest"])
|
||||
}
|
||||
|
||||
// A service container reaches the internet the same way the job does, so it inherits the
|
||||
// job's proxy; a service that sets the variable itself keeps its own value.
|
||||
func TestStartJobContainerGivesServicesTheJobProxy(t *testing.T) {
|
||||
workflow, err := model.ReadWorkflow(strings.NewReader(`
|
||||
inputs := startJobContainerInputs(t, `
|
||||
name: test
|
||||
on: push
|
||||
jobs:
|
||||
@@ -320,36 +359,7 @@ jobs:
|
||||
env:
|
||||
no_proxy: db-only.example
|
||||
steps: []
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
|
||||
var inputs []*container.NewContainerInput
|
||||
origNewContainer := newContainer
|
||||
newContainer = func(input *container.NewContainerInput) container.ExecutionsEnvironment {
|
||||
inputs = append(inputs, input)
|
||||
return fakeContainer{}
|
||||
}
|
||||
t.Cleanup(func() { newContainer = origNewContainer })
|
||||
|
||||
rc := &RunContext{
|
||||
Name: "test",
|
||||
Config: &Config{
|
||||
Workdir: "/tmp",
|
||||
ContainerNetworkMode: "host",
|
||||
Env: map[string]string{},
|
||||
ProxyEnv: map[string]string{"http_proxy": "http://proxy:3128", "no_proxy": "internal.example"},
|
||||
Secrets: map[string]string{},
|
||||
},
|
||||
Env: map[string]string{},
|
||||
Run: &model.Run{
|
||||
JobID: "job",
|
||||
Workflow: workflow,
|
||||
},
|
||||
}
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(t.Context())
|
||||
|
||||
t.Setenv("DOCKER_HOST", "unix:///nonexistent.sock")
|
||||
require.Error(t, rc.startJobContainer()(t.Context()))
|
||||
`, &Config{ProxyEnv: map[string]string{"http_proxy": "http://proxy:3128", "no_proxy": "internal.example"}})
|
||||
|
||||
env := map[string][]string{}
|
||||
for _, in := range inputs {
|
||||
|
||||
Reference in New Issue
Block a user