From 2fae86cc7f2a30a1f63e3fd6656a124fd5eb76f3 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 18 Jul 2024 22:52:22 +0200 Subject: [PATCH] Make sure plugins only mount the workspace base in a predefinde location (#3933) --- docs/docs/20-usage/20-workflow-syntax.md | 8 ++++- docs/docs/20-usage/51-plugins/51-overview.md | 5 ++++ pipeline/frontend/yaml/compiler/compiler.go | 9 +++--- .../frontend/yaml/compiler/compiler_test.go | 29 +++++++++++++------ pipeline/frontend/yaml/compiler/convert.go | 25 +++++++++++++--- pipeline/frontend/yaml/compiler/option.go | 4 +-- .../frontend/yaml/compiler/option_test.go | 4 +-- server/pipeline/stepbuilder/stepBuilder.go | 2 +- 8 files changed, 63 insertions(+), 23 deletions(-) diff --git a/docs/docs/20-usage/20-workflow-syntax.md b/docs/docs/20-usage/20-workflow-syntax.md index 7b966fc48c..e826245341 100644 --- a/docs/docs/20-usage/20-workflow-syntax.md +++ b/docs/docs/20-usage/20-workflow-syntax.md @@ -518,7 +518,9 @@ For more details check the [services docs](./60-services.md). ## `workspace` -The workspace defines the shared volume and working directory shared by all workflow steps. The default workspace matches the pattern `/woodpecker/src/github.com/octocat/hello-world`, based on your repository URL. +The workspace defines the shared volume and working directory shared by all workflow steps. +The default workspace base is `/woodpecker` and the path is extended with the repository URL (`src/{url-without-schema}`). +So an example would be `/woodpecker/src/github.com/octocat/hello-world`. The workspace can be customized using the workspace block in the YAML file: @@ -535,6 +537,10 @@ The workspace can be customized using the workspace block in the YAML file: - go test ``` +:::note +Plugins will always have the workspace base at `/woodpecker` +::: + The base attribute defines a shared base volume available to all steps. This ensures your source code, dependencies and compiled binaries are persisted and shared between steps. ```diff diff --git a/docs/docs/20-usage/51-plugins/51-overview.md b/docs/docs/20-usage/51-plugins/51-overview.md index ab8db3df37..de023bb5a1 100644 --- a/docs/docs/20-usage/51-plugins/51-overview.md +++ b/docs/docs/20-usage/51-plugins/51-overview.md @@ -29,6 +29,11 @@ steps: ## Plugin Isolation Plugins are just pipeline steps. They share the build workspace, mounted as a volume, and therefore have access to your source tree. +While normal steps are all about arbitrary code execution, plugins should only allow the functions intended by the plugin author. + +So there are a few limitations, like the workspace base is always mounted at `/woodpecker`, but the working directory is dynamically adjusted acordingly. So as user of a plugin you should not have to care about this. + +Also instead of using environment variables the plugin should only care about one prefixed with `PLUGIN_` witch are the internaml representation of the **settings** ([read more](./20-creating-plugins.md)). ## Finding Plugins diff --git a/pipeline/frontend/yaml/compiler/compiler.go b/pipeline/frontend/yaml/compiler/compiler.go index 5db5665133..9525a3a12a 100644 --- a/pipeline/frontend/yaml/compiler/compiler.go +++ b/pipeline/frontend/yaml/compiler/compiler.go @@ -16,6 +16,7 @@ package compiler import ( "fmt" + "path" backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" @@ -98,8 +99,8 @@ type Compiler struct { networks []string env map[string]string cloneEnv map[string]string - base string - path string + workspaceBase string + workspacePath string metadata metadata.Metadata registries []Registry secrets map[string]Secret @@ -156,10 +157,10 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er // overrides the default workspace paths when specified // in the YAML file. if len(conf.Workspace.Base) != 0 { - c.base = conf.Workspace.Base + c.workspaceBase = path.Clean(conf.Workspace.Base) } if len(conf.Workspace.Path) != 0 { - c.path = conf.Workspace.Path + c.workspacePath = path.Clean(conf.Workspace.Path) } cloneImage := constant.DefaultCloneImage diff --git a/pipeline/frontend/yaml/compiler/compiler_test.go b/pipeline/frontend/yaml/compiler/compiler_test.go index 41e055b366..d391b5506e 100644 --- a/pipeline/frontend/yaml/compiler/compiler_test.go +++ b/pipeline/frontend/yaml/compiler/compiler_test.go @@ -61,13 +61,14 @@ func TestSecretAvailable(t *testing.T) { } func TestCompilerCompile(t *testing.T) { + repoURL := "https://github.com/octocat/hello-world" compiler := New( WithMetadata(metadata.Metadata{ Repo: metadata.Repo{ Owner: "octacat", Name: "hello-world", Private: true, - ForgeURL: "https://github.com/octocat/hello-world", + ForgeURL: repoURL, CloneURL: "https://github.com/octocat/hello-world.git", }, }), @@ -76,6 +77,8 @@ func TestCompilerCompile(t *testing.T) { "COLORED": "true", }), WithPrefix("test"), + // we use "/test" as custom workspace base to ensure the enforcement of the pluginWorkspaceBase is applied + WithWorkspaceFromURL("/test", repoURL), ) defaultNetworks := []*backend_types.Network{{ @@ -92,7 +95,8 @@ func TestCompilerCompile(t *testing.T) { Image: constant.DefaultCloneImage, OnSuccess: true, Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, + Volumes: []string{defaultVolumes[0].Name + ":/woodpecker"}, + WorkingDir: "/woodpecker/src/github.com/octocat/hello-world", Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"clone"}}}, ExtraHosts: []backend_types.HostAlias{}, }}, @@ -137,7 +141,8 @@ func TestCompilerCompile(t *testing.T) { Image: "dummy_img", OnSuccess: true, Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, + Volumes: []string{defaultVolumes[0].Name + ":/woodpecker"}, + WorkingDir: "/woodpecker/src/github.com/octocat/hello-world", Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"dummy"}}}, ExtraHosts: []backend_types.HostAlias{}, }}, @@ -172,7 +177,8 @@ func TestCompilerCompile(t *testing.T) { Commands: []string{"env"}, OnSuccess: true, Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, + Volumes: []string{defaultVolumes[0].Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}}, ExtraHosts: []backend_types.HostAlias{}, }}, @@ -184,7 +190,8 @@ func TestCompilerCompile(t *testing.T) { Commands: []string{"echo 1"}, OnSuccess: true, Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, + Volumes: []string{defaultVolumes[0].Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}}, ExtraHosts: []backend_types.HostAlias{}, }, { @@ -194,7 +201,8 @@ func TestCompilerCompile(t *testing.T) { Commands: []string{"echo 2"}, OnSuccess: true, Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, + Volumes: []string{defaultVolumes[0].Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}}, ExtraHosts: []backend_types.HostAlias{}, }}, @@ -228,7 +236,8 @@ func TestCompilerCompile(t *testing.T) { Commands: []string{"env"}, OnSuccess: true, Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, + Volumes: []string{defaultVolumes[0].Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}}, ExtraHosts: []backend_types.HostAlias{}, }, { @@ -238,7 +247,8 @@ func TestCompilerCompile(t *testing.T) { Commands: []string{"echo 2"}, OnSuccess: true, Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, + Volumes: []string{defaultVolumes[0].Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 2"}}}, ExtraHosts: []backend_types.HostAlias{}, }}, @@ -250,7 +260,8 @@ func TestCompilerCompile(t *testing.T) { Commands: []string{"echo 1"}, OnSuccess: true, Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, + Volumes: []string{defaultVolumes[0].Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 1"}}}, ExtraHosts: []backend_types.HostAlias{}, }}, diff --git a/pipeline/frontend/yaml/compiler/convert.go b/pipeline/frontend/yaml/compiler/convert.go index 92d68fe45f..ec59e48908 100644 --- a/pipeline/frontend/yaml/compiler/convert.go +++ b/pipeline/frontend/yaml/compiler/convert.go @@ -30,6 +30,13 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/utils" ) +const ( + // The pluginWorkspaceBase should not be changed, only if you are sure what you do. + pluginWorkspaceBase = "/woodpecker" + // DefaultWorkspaceBase is set if not altered by the user. + DefaultWorkspaceBase = pluginWorkspaceBase +) + func (c *Compiler) createProcess(container *yaml_types.Container, stepType backend_types.StepType) (*backend_types.Step, error) { var ( uuid = ulid.Make() @@ -37,12 +44,18 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe detached bool workingDir string - workspace = fmt.Sprintf("%s_default:%s", c.prefix, c.base) privileged = container.Privileged networkMode = container.NetworkMode // network = container.Network ) + workspaceBase := c.workspaceBase + if container.IsPlugin() { + // plugins have a predefined workspace base to not tamper with entrypoint executables + workspaceBase = pluginWorkspaceBase + } + workspaceVolume := fmt.Sprintf("%s_default:%s", c.prefix, workspaceBase) + networks := []backend_types.Conn{ { Name: fmt.Sprintf("%s_default", c.prefix), @@ -67,7 +80,7 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe var volumes []string if !c.local { - volumes = append(volumes, workspace) + volumes = append(volumes, workspaceVolume) } volumes = append(volumes, c.volumes...) for _, volume := range container.Volumes.Volumes { @@ -78,7 +91,7 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe environment := map[string]string{} maps.Copy(environment, c.env) - environment["CI_WORKSPACE"] = path.Join(c.base, c.path) + environment["CI_WORKSPACE"] = path.Join(workspaceBase, c.workspacePath) if stepType == backend_types.StepTypeService || container.Detached { detached = true @@ -222,7 +235,11 @@ func (c *Compiler) stepWorkingDir(container *yaml_types.Container) string { if path.IsAbs(container.Directory) { return container.Directory } - return path.Join(c.base, c.path, container.Directory) + base := c.workspaceBase + if container.IsPlugin() { + base = pluginWorkspaceBase + } + return path.Join(base, c.workspacePath, container.Directory) } func convertPort(portDef string) (backend_types.Port, error) { diff --git a/pipeline/frontend/yaml/compiler/option.go b/pipeline/frontend/yaml/compiler/option.go index d222ffaa90..dd2d558e5e 100644 --- a/pipeline/frontend/yaml/compiler/option.go +++ b/pipeline/frontend/yaml/compiler/option.go @@ -97,8 +97,8 @@ func WithNetrc(username, password, machine string) Option { // plugin steps in the pipeline. func WithWorkspace(base, path string) Option { return func(compiler *Compiler) { - compiler.base = base - compiler.path = path + compiler.workspaceBase = base + compiler.workspacePath = path } } diff --git a/pipeline/frontend/yaml/compiler/option_test.go b/pipeline/frontend/yaml/compiler/option_test.go index 4f8e08e5d5..48ed13d480 100644 --- a/pipeline/frontend/yaml/compiler/option_test.go +++ b/pipeline/frontend/yaml/compiler/option_test.go @@ -29,8 +29,8 @@ func TestWithWorkspace(t *testing.T) { "src/github.com/octocat/hello-world", ), ) - assert.Equal(t, "/pipeline", compiler.base) - assert.Equal(t, "src/github.com/octocat/hello-world", compiler.path) + assert.Equal(t, "/pipeline", compiler.workspaceBase) + assert.Equal(t, "src/github.com/octocat/hello-world", compiler.workspacePath) } func TestWithEscalated(t *testing.T) { diff --git a/server/pipeline/stepbuilder/stepBuilder.go b/server/pipeline/stepbuilder/stepBuilder.go index 586fad595c..593ff0fc1a 100644 --- a/server/pipeline/stepbuilder/stepBuilder.go +++ b/server/pipeline/stepbuilder/stepBuilder.go @@ -291,7 +291,7 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml_types.Workflow, envi ), ), compiler.WithProxy(b.ProxyOpts), - compiler.WithWorkspaceFromURL("/woodpecker", b.Repo.ForgeURL), + compiler.WithWorkspaceFromURL(compiler.DefaultWorkspaceBase, b.Repo.ForgeURL), compiler.WithMetadata(metadata), compiler.WithTrusted(b.Repo.IsTrusted), compiler.WithNetrcOnlyTrusted(b.Repo.NetrcOnlyTrusted),