woodpecker/pipeline/backend/local/local.go
mscherer c3788d943f
Fix insecure /tmp usage in local backend (#872)
Since /tmp is writable by everybody, a user could precreate
/tmp/woodpecker with 777 permissions, allowing them to modify the
pipeline while it is being run, or preventing the pipeline from running.

And since os.MkdirAll error code wasn't checked, the same attacker
could have precreated the directory where the pipeline is executed to
mess with the run, allowing code execution under the UID of the
agent (who has access to the toke, to communicate with the server, which
mean a attacker could inject a fake agent, steal credentials, etc)
2022-04-06 03:33:00 +02:00

118 lines
2.9 KiB
Go

package local
import (
"context"
"encoding/base64"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"github.com/woodpecker-ci/woodpecker/pipeline/backend/types"
"github.com/woodpecker-ci/woodpecker/server"
)
type local struct {
cmd *exec.Cmd
output io.ReadCloser
workingdir string
}
// make sure local implements Engine
var _ types.Engine = &local{}
// New returns a new local Engine.
func New() types.Engine {
return &local{}
}
func (e *local) Name() string {
return "local"
}
func (e *local) IsAvailable() bool {
return true
}
func (e *local) Load() error {
dir, err := ioutil.TempDir("", "woodpecker-local-*")
e.workingdir = dir
return err
}
// Setup the pipeline environment.
func (e *local) Setup(ctx context.Context, proc *types.Config) error {
return nil
}
// Exec the pipeline step.
func (e *local) Exec(ctx context.Context, proc *types.Step) error {
// Get environment variables
Command := []string{}
for a, b := range proc.Environment {
if a != "HOME" && a != "SHELL" { // Don't override $HOME and $SHELL
Command = append(Command, a+"="+b)
}
}
// Get default clone image
defaultCloneImage := "docker.io/woodpeckerci/plugin-git:latest"
if len(server.Config.Pipeline.DefaultCloneImage) > 0 {
defaultCloneImage = server.Config.Pipeline.DefaultCloneImage
}
if proc.Image == defaultCloneImage {
// Default clone step
Command = append(Command, "CI_WORKSPACE="+e.workingdir+"/"+proc.Environment["CI_REPO"])
Command = append(Command, "plugin-git")
} else {
// Use "image name" as run command
Command = append(Command, proc.Image[18:len(proc.Image)-7])
Command = append(Command, "-c")
// Decode script and delete initial lines
// Deleting the initial lines removes netrc support but adds compatibility for more shells like fish
Script, _ := base64.RawStdEncoding.DecodeString(proc.Environment["CI_SCRIPT"])
Command = append(Command, string(Script)[strings.Index(string(Script), "\n\n")+2:])
}
// Prepare command
e.cmd = exec.CommandContext(ctx, "/bin/env", Command...)
// Prepare working directory
if proc.Image == defaultCloneImage {
e.cmd.Dir = e.workingdir + "/" + proc.Environment["CI_REPO_OWNER"]
} else {
e.cmd.Dir = e.workingdir + "/" + proc.Environment["CI_REPO"]
}
err := os.MkdirAll(e.cmd.Dir, 0o700)
if err != nil {
return err
}
// Get output and redirect Stderr to Stdout
e.output, _ = e.cmd.StdoutPipe()
e.cmd.Stderr = e.cmd.Stdout
return e.cmd.Start()
}
// Wait for the pipeline step to complete and returns
// the completion results.
func (e *local) Wait(context.Context, *types.Step) (*types.State, error) {
return &types.State{
Exited: true,
}, e.cmd.Wait()
}
// Tail the pipeline step logs.
func (e *local) Tail(context.Context, *types.Step) (io.ReadCloser, error) {
return e.output, nil
}
// Destroy the pipeline environment.
func (e *local) Destroy(context.Context, *types.Config) error {
os.RemoveAll(e.cmd.Dir)
return nil
}