feat: non root user container (#1202)

* feat: non root user container

* Also chown WorkingDir

* .

* .

* Update docker_run.go

* Add Test

* Update runner_test.go

* Update docker_run.go

* Apply suggestions from code review

Co-authored-by: R <me@hackerc.at>

* remove cruft from master merge

Co-authored-by: R <me@hackerc.at>
Co-authored-by: Casey Lee <cplee@nektos.com>
Co-authored-by: Casey Lee <caseypl@amazon.com>
This commit is contained in:
ChristopherHX 2022-06-21 00:47:39 +02:00 committed by GitHub
parent c30bc824b2
commit b59e6dee6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 6 deletions

View File

@ -12,6 +12,7 @@ import (
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/go-git/go-billy/v5/helper/polyfill"
@ -125,6 +126,13 @@ func (cr *containerReference) Start(attach bool) common.Executor {
cr.attach().IfBool(attach),
cr.start(),
cr.wait().IfBool(attach),
cr.tryReadUID(),
cr.tryReadGID(),
func(ctx context.Context) error {
// If this fails, then folders have wrong permissions on non root container
_ = cr.Exec([]string{"chown", "-R", fmt.Sprintf("%d:%d", cr.UID, cr.GID), cr.input.WorkingDir}, nil, "0", "")(ctx)
return nil
},
).IfNot(common.Dryrun),
)
}
@ -154,8 +162,12 @@ func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common.
func (cr *containerReference) CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor {
return common.NewPipelineExecutor(
common.NewInfoExecutor("%sdocker cp src=%s dst=%s", logPrefix, srcPath, destPath),
cr.Exec([]string{"mkdir", "-p", destPath}, nil, "", ""),
cr.copyDir(destPath, srcPath, useGitIgnore),
func(ctx context.Context) error {
// If this fails, then folders have wrong permissions on non root container
_ = cr.Exec([]string{"chown", "-R", fmt.Sprintf("%d:%d", cr.UID, cr.GID), destPath}, nil, "0", "")(ctx)
return nil
},
).IfNot(common.Dryrun)
}
@ -211,6 +223,8 @@ type containerReference struct {
cli client.APIClient
id string
input *NewContainerInput
UID int
GID int
}
func GetDockerClient(ctx context.Context) (cli client.APIClient, err error) {
@ -581,6 +595,47 @@ func (cr *containerReference) exec(cmd []string, env map[string]string, user, wo
}
}
func (cr *containerReference) tryReadID(opt string, cbk func(id int)) common.Executor {
return func(ctx context.Context) error {
idResp, err := cr.cli.ContainerExecCreate(ctx, cr.id, types.ExecConfig{
Cmd: []string{"id", opt},
AttachStdout: true,
AttachStderr: true,
})
if err != nil {
return nil
}
resp, err := cr.cli.ContainerExecAttach(ctx, idResp.ID, types.ExecStartCheck{})
if err != nil {
return nil
}
defer resp.Close()
sid, err := resp.Reader.ReadString('\n')
if err != nil {
return nil
}
exp := regexp.MustCompile(`\d+\n`)
found := exp.FindString(sid)
id, err := strconv.ParseInt(found[:len(found)-1], 10, 32)
if err != nil {
return nil
}
cbk(int(id))
return nil
}
}
func (cr *containerReference) tryReadUID() common.Executor {
return cr.tryReadID("-u", func(id int) { cr.UID = id })
}
func (cr *containerReference) tryReadGID() common.Executor {
return cr.tryReadID("-g", func(id int) { cr.GID = id })
}
func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal bool, resp types.HijackedResponse, idResp types.IDResponse, user string, workdir string) error {
logger := common.Logger(ctx)
@ -670,6 +725,9 @@ func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgno
SrcPrefix: srcPrefix,
Handler: &tarCollector{
TarWriter: tw,
UID: cr.UID,
GID: cr.GID,
DstDir: dstPath[1:],
},
}
@ -686,7 +744,7 @@ func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgno
if err != nil {
return fmt.Errorf("failed to seek tar archive: %w", err)
}
err = cr.cli.CopyToContainer(ctx, cr.id, dstPath, tarFile, types.CopyToContainerOptions{})
err = cr.cli.CopyToContainer(ctx, cr.id, "/", tarFile, types.CopyToContainerOptions{})
if err != nil {
return fmt.Errorf("failed to copy content to container: %w", err)
}
@ -705,6 +763,8 @@ func (cr *containerReference) copyContent(dstPath string, files ...*FileEntry) c
Name: file.Name,
Mode: file.Mode,
Size: int64(len(file.Body)),
Uid: cr.UID,
Gid: cr.GID,
}
if err := tw.WriteHeader(hdr); err != nil {
return err

View File

@ -23,9 +23,12 @@ type fileCollectorHandler interface {
type tarCollector struct {
TarWriter *tar.Writer
UID int
GID int
DstDir string
}
func (tc tarCollector) WriteFile(path string, fi fs.FileInfo, linkName string, f io.Reader) error {
func (tc tarCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error {
// create a new dir/file header
header, err := tar.FileInfoHeader(fi, linkName)
if err != nil {
@ -33,9 +36,11 @@ func (tc tarCollector) WriteFile(path string, fi fs.FileInfo, linkName string, f
}
// update the name to correctly reflect the desired destination when untaring
header.Name = path
header.Name = path.Join(tc.DstDir, fpath)
header.Mode = int64(fi.Mode())
header.ModTime = fi.ModTime()
header.Uid = tc.UID
header.Gid = tc.GID
// write the header
if err := tc.TarWriter.WriteHeader(header); err != nil {
@ -138,7 +143,7 @@ func (fc *fileCollector) collectFiles(ctx context.Context, submodulePath []strin
}
}
if err == nil && entry.Mode == filemode.Submodule {
err = filepath.Walk(fi.Name(), fc.collectFiles(ctx, split))
err = fc.Fs.Walk(fi.Name(), fc.collectFiles(ctx, split))
if err != nil {
return err
}

View File

@ -180,7 +180,6 @@ func (rc *RunContext) startJobContainer() common.Executor {
rc.JobContainer.Start(false),
rc.JobContainer.UpdateFromImageEnv(&rc.Env),
rc.JobContainer.UpdateFromEnv("/etc/environment", &rc.Env),
rc.JobContainer.Exec([]string{"mkdir", "-m", "0777", "-p", ActPath}, rc.Env, "root", ""),
rc.JobContainer.Copy(ActPath+"/", &container.FileEntry{
Name: "workflow/event.json",
Mode: 0644,

View File

@ -158,6 +158,7 @@ func TestRunEvent(t *testing.T) {
{workdir, "container-hostname", "push", "", platforms},
{workdir, "remote-action-docker", "push", "", platforms},
{workdir, "remote-action-js", "push", "", platforms},
{workdir, "remote-action-js", "push", "", map[string]string{"ubuntu-latest": "ghcr.io/catthehacker/ubuntu:runner-latest"}}, // Test if this works with non root container
{workdir, "matrix", "push", "", platforms},
{workdir, "matrix-include-exclude", "push", "", platforms},
{workdir, "commands", "push", "", platforms},