Disalow to set arbitrary environments for plugins (#3909)

This commit is contained in:
6543 2024-07-14 14:35:19 -07:00 committed by GitHub
parent 904d8da51a
commit 8aa3e5ec82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 225 additions and 16 deletions

View file

@ -100,5 +100,5 @@ func toOpenApi3(input, output string) error {
return err return err
} }
return os.WriteFile(output, data, 0644) return os.WriteFile(output, data, 0o644)
} }

View file

@ -117,7 +117,7 @@ func (l *Linter) lintContainers(config *WorkflowConfig, area string) error {
linterErr = multierr.Append(linterErr, err) linterErr = multierr.Append(linterErr, err)
} }
} }
if err := l.lintCommands(config, container, area); err != nil { if err := l.lintSettings(config, container, area); err != nil {
linterErr = multierr.Append(linterErr, err) linterErr = multierr.Append(linterErr, err)
} }
} }
@ -132,16 +132,18 @@ func (l *Linter) lintImage(config *WorkflowConfig, c *types.Container, area stri
return nil return nil
} }
func (l *Linter) lintCommands(config *WorkflowConfig, c *types.Container, field string) error { func (l *Linter) lintSettings(config *WorkflowConfig, c *types.Container, field string) error {
if len(c.Commands) == 0 { if len(c.Settings) == 0 {
return nil return nil
} }
if len(c.Settings) != 0 { if len(c.Commands) != 0 {
var keys []string return newLinterError("Cannot configure both commands and settings", config.File, fmt.Sprintf("%s.%s", field, c.Name), false)
for key := range c.Settings { }
keys = append(keys, key) if len(c.Entrypoint) != 0 {
} return newLinterError("Cannot configure both entrypoint and settings", config.File, fmt.Sprintf("%s.%s", field, c.Name), false)
return newLinterError(fmt.Sprintf("Cannot configure both commands and custom attributes %v", keys), config.File, fmt.Sprintf("%s.%s", field, c.Name), false) }
if len(c.Environment) != 0 {
return newLinterError("Cannot configure both environment and settings", config.File, fmt.Sprintf("%s.%s", field, c.Name), false)
} }
return nil return nil
} }

View file

@ -157,6 +157,18 @@ func TestLintErrors(t *testing.T) {
from: "steps: { build: { image: golang, network_mode: 'container:name' } }", from: "steps: { build: { image: golang, network_mode: 'container:name' } }",
want: "Insufficient privileges to use network_mode", want: "Insufficient privileges to use network_mode",
}, },
{
from: "steps: { build: { image: golang, settings: { test: 'true' }, commands: [ 'echo ja', 'echo nein' ] } }",
want: "Cannot configure both commands and settings",
},
{
from: "steps: { build: { image: golang, settings: { test: 'true' }, entrypoint: [ '/bin/fish' ] } }",
want: "Cannot configure both entrypoint and settings",
},
{
from: "steps: { build: { image: golang, settings: { test: 'true' }, environment: [ 'TEST=true' ] } }",
want: "Cannot configure both environment and settings",
},
} }
for _, test := range testdata { for _, test := range testdata {

View file

@ -0,0 +1,8 @@
steps:
publish:
image: plugins/docker
settings:
repo: foo/bar
tags: latest
environment:
CGO: 0

View file

@ -0,0 +1,8 @@
steps:
publish:
image: plugins/docker
settings:
repo: foo/bar
tags: latest
commands:
- env

View file

@ -18,7 +18,7 @@ steps:
image: alpine image: alpine
entrypoint: ['some_entry', '--some-flag'] entrypoint: ['some_entry', '--some-flag']
singla-entrypoint: single-entrypoint:
image: alpine image: alpine
entrypoint: some_entry entrypoint: some_entry

View file

@ -304,10 +304,24 @@
} }
}, },
"step": { "step": {
"description": "A step of your workflow executes either arbitrary commands or uses a plugin. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#steps",
"oneOf": [
{
"$ref": "#/definitions/commands_step"
},
{
"$ref": "#/definitions/entrypoint_step"
},
{
"$ref": "#/definitions/plugin_step"
}
]
},
"commands_step": {
"description": "Every step of your pipeline executes arbitrary commands inside a specified docker container. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#steps", "description": "Every step of your pipeline executes arbitrary commands inside a specified docker container. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#steps",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["image"], "required": ["image", "commands"],
"properties": { "properties": {
"name": { "name": {
"description": "The name of the step. Can be used if using the array style steps list.", "description": "The name of the step. Can be used if using the array style steps list.",
@ -334,9 +348,6 @@
"secrets": { "secrets": {
"$ref": "#/definitions/step_secrets" "$ref": "#/definitions/step_secrets"
}, },
"settings": {
"$ref": "#/definitions/step_settings"
},
"when": { "when": {
"$ref": "#/definitions/step_when" "$ref": "#/definitions/step_when"
}, },
@ -392,6 +403,160 @@
} }
} }
}, },
"entrypoint_step": {
"description": "Every step of your pipeline executes arbitrary commands inside a specified docker container. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#steps",
"type": "object",
"additionalProperties": false,
"required": ["image", "entrypoint"],
"properties": {
"name": {
"description": "The name of the step. Can be used if using the array style steps list.",
"type": "string"
},
"image": {
"$ref": "#/definitions/step_image"
},
"privileged": {
"$ref": "#/definitions/step_privileged"
},
"pull": {
"$ref": "#/definitions/step_pull"
},
"commands": {
"$ref": "#/definitions/step_commands"
},
"environment": {
"$ref": "#/definitions/step_environment"
},
"directory": {
"$ref": "#/definitions/step_directory"
},
"secrets": {
"$ref": "#/definitions/step_secrets"
},
"when": {
"$ref": "#/definitions/step_when"
},
"volumes": {
"$ref": "#/definitions/step_volumes"
},
"group": {
"description": "deprecated, use depends_on",
"type": "string"
},
"depends_on": {
"description": "Execute a step after another step has finished.",
"oneOf": [
{
"type": "array",
"minLength": 1,
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"detach": {
"description": "Detach a step to run in background until pipeline finishes. Read more: https://woodpecker-ci.org/docs/usage/services#detachment",
"type": "boolean"
},
"failure": {
"description": "How to handle the failure of this step. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#failure",
"type": "string",
"enum": ["fail", "ignore"],
"default": "fail"
},
"backend_options": {
"$ref": "#/definitions/step_backend_options"
},
"entrypoint": {
"description": "Defines container entrypoint.",
"oneOf": [
{
"type": "array",
"minLength": 1,
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
}
}
},
"plugin_step": {
"description": "Plugins let you execute predefined functions in a more secure context. Read more: https://woodpecker-ci.org/docs/usage/plugins/overview",
"type": "object",
"additionalProperties": false,
"required": ["image"],
"properties": {
"name": {
"description": "The name of the step. Can be used if using the array style steps list.",
"type": "string"
},
"image": {
"$ref": "#/definitions/step_image"
},
"privileged": {
"$ref": "#/definitions/step_privileged"
},
"pull": {
"$ref": "#/definitions/step_pull"
},
"directory": {
"$ref": "#/definitions/step_directory"
},
"secrets": {
"$ref": "#/definitions/step_secrets"
},
"settings": {
"$ref": "#/definitions/step_settings"
},
"when": {
"$ref": "#/definitions/step_when"
},
"volumes": {
"$ref": "#/definitions/step_volumes"
},
"group": {
"description": "deprecated, use depends_on",
"type": "string"
},
"depends_on": {
"description": "Execute a step after another step has finished.",
"oneOf": [
{
"type": "array",
"minLength": 1,
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"detach": {
"description": "Detach a step to run in background until pipeline finishes. Read more: https://woodpecker-ci.org/docs/usage/services#detachment",
"type": "boolean"
},
"failure": {
"description": "How to handle the failure of this step. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#failure",
"type": "string",
"enum": ["fail", "ignore"],
"default": "fail"
},
"backend_options": {
"$ref": "#/definitions/step_backend_options"
}
}
},
"step_when": { "step_when": {
"description": "Steps can be skipped based on conditions. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#when---conditional-execution", "description": "Steps can be skipped based on conditions. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#when---conditional-execution",
"oneOf": [ "oneOf": [

View file

@ -116,6 +116,16 @@ func TestSchema(t *testing.T) {
testFile: ".woodpecker/test-custom-backend.yaml", testFile: ".woodpecker/test-custom-backend.yaml",
fail: false, fail: false,
}, },
{
name: "Broken Plugin by environment",
testFile: ".woodpecker/test-broken-plugin.yaml",
fail: true,
},
{
name: "Broken Plugin by commands",
testFile: ".woodpecker/test-broken-plugin2.yaml",
fail: true,
},
} }
for _, tt := range testTable { for _, tt := range testTable {

View file

@ -123,7 +123,9 @@ func (c *ContainerList) UnmarshalYAML(value *yaml.Node) error {
} }
func (c *Container) IsPlugin() bool { func (c *Container) IsPlugin() bool {
return len(c.Commands) == 0 && len(c.Entrypoint) == 0 return len(c.Commands) == 0 &&
len(c.Entrypoint) == 0 &&
len(c.Environment) == 0
} }
func (c *Container) IsTrustedCloneImage() bool { func (c *Container) IsTrustedCloneImage() bool {

View file

@ -131,6 +131,8 @@ func TestCopyLineByLineSizeLimit(t *testing.T) {
if _, err := w.Write([]byte("67\n89")); err != nil { if _, err := w.Write([]byte("67\n89")); err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
// wait for writer to write
time.Sleep(time.Millisecond)
writes = testWriter.GetWrites() writes = testWriter.GetWrites()
assert.Lenf(t, testWriter.GetWrites(), 2, "expected 2 writes, got: %v", writes) assert.Lenf(t, testWriter.GetWrites(), 2, "expected 2 writes, got: %v", writes)