You've already forked woodpecker
							
							
				mirror of
				https://github.com/woodpecker-ci/woodpecker.git
				synced 2025-10-30 23:27:39 +02:00 
			
		
		
		
	Make sure plugins only mount the workspace base in a predefinde location (#3933)
This commit is contained in:
		| @@ -523,7 +523,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: | ||||
|  | ||||
| @@ -540,6 +542,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 | ||||
|   | ||||
| @@ -47,6 +47,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 | ||||
|  | ||||
|   | ||||
| @@ -523,7 +523,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: | ||||
|  | ||||
| @@ -540,6 +542,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 | ||||
|   | ||||
| @@ -47,6 +47,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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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{}, | ||||
| 					}}, | ||||
|   | ||||
| @@ -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,11 +44,17 @@ 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 | ||||
| 	) | ||||
|  | ||||
| 	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), | ||||
| @@ -66,7 +79,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 { | ||||
| @@ -77,7 +90,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 | ||||
| @@ -219,7 +232,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) { | ||||
|   | ||||
| @@ -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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user