Make sure plugins only mount the workspace base in a predefinde location (#3933)

This commit is contained in:
6543 2024-07-18 22:52:22 +02:00 committed by GitHub
parent d6e3ebf051
commit 764329ed1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 75 additions and 24 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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{},
}},

View file

@ -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) {

View file

@ -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
}
}

View file

@ -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) {

View file

@ -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),