Skip to content

Commit

Permalink
Removed dependency on sh/tar from alpine image
Browse files Browse the repository at this point in the history
This commit removes depencency on sh and tar binaries by implementing
the logic in our func-util binary.

Signed-off-by: Matej Vašek <[email protected]>
  • Loading branch information
matejvasek committed Jan 24, 2025
1 parent 079db29 commit 6ace6cc
Show file tree
Hide file tree
Showing 7 changed files with 693 additions and 1 deletion.
1 change: 1 addition & 0 deletions Dockerfile.utils
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ COPY --from=builder /workspace/func-util /usr/local/bin/
RUN ln -s /usr/local/bin/func-util /usr/local/bin/deploy && \
ln -s /usr/local/bin/func-util /usr/local/bin/scaffold && \
ln -s /usr/local/bin/func-util /usr/local/bin/s2i && \
ln -s /usr/local/bin/func-util /usr/local/bin/sh && \
ln -s /usr/local/bin/func-util /usr/local/bin/socat

LABEL \
Expand Down
21 changes: 21 additions & 0 deletions cmd/func-util/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import (
"os"
"os/signal"
"path/filepath"
"slices"
"syscall"

"golang.org/x/sys/unix"

"github.com/openshift/source-to-image/pkg/cmd/cli"
"k8s.io/klog/v2"

Expand All @@ -20,6 +23,7 @@ import (
"knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/knative"
"knative.dev/func/pkg/scaffolding"
"knative.dev/func/pkg/tar"
)

func main() {
Expand All @@ -46,6 +50,8 @@ func main() {
cmd = s2iCmd
case "socat":
cmd = socat
case "sh":
cmd = sh
}

err := cmd(ctx)
Expand Down Expand Up @@ -167,3 +173,18 @@ func (d deployDecorator) UpdateLabels(function fn.Function, labels map[string]st
}
return labels
}

func sh(ctx context.Context) error {
if !slices.Equal(os.Args[1:], []string{"-c", "umask 0000 && exec tar -xmf -"}) {
return fmt.Errorf("this is a fake sh (only for backward compatiblility purposes)")
}

wd, err := os.Getwd()
if err != nil {
return fmt.Errorf("cannot get working directory: %w", err)
}

unix.Umask(0)

return tar.Extract(os.Stdin, wd)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ require (
golang.org/x/net v0.34.0
golang.org/x/oauth2 v0.24.0
golang.org/x/sync v0.10.0
golang.org/x/sys v0.29.0
golang.org/x/term v0.28.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
Expand Down Expand Up @@ -272,7 +273,6 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.7.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
Expand Down
Binary file modified pkg/k8s/testdata/content.tar
Binary file not shown.
97 changes: 97 additions & 0 deletions pkg/tar/tar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package tar

import (
"archive/tar"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
)

func Extract(input io.Reader, targetDir string) error {
var err error

r := tar.NewReader(input)

var first bool = true
for {
var hdr *tar.Header
hdr, err = r.Next()
if err != nil {
if errors.Is(err, io.EOF) {
if first {
// mimic tar output on empty input
return fmt.Errorf("does not look like a tar")
}
return nil
}
return err
}

if strings.Contains(hdr.Name, "..") {
return fmt.Errorf("name contains '..': %s", hdr.Name)
}
if path.IsAbs(hdr.Linkname) {
return fmt.Errorf("absolute symlink: %s->%s", hdr.Name, hdr.Linkname)
}
if strings.HasPrefix(path.Clean(path.Join(path.Dir(hdr.Name), hdr.Linkname)), "..") {
return fmt.Errorf("link target escapes: %s->%s", hdr.Name, hdr.Linkname)
}

var targetPath, rel string
targetPath = filepath.Join(targetDir, filepath.FromSlash(hdr.Name))

Check failure

Code scanning / CodeQL

Arbitrary file write extracting an archive containing symbolic links High

Unresolved path from an archive header, which may point outside the archive root, is used in
symlink creation
.
rel, err = filepath.Rel(targetDir, targetPath)
if err != nil {
return fmt.Errorf("cannot get relative path: %w", err)
}
if strings.HasPrefix(rel, "..") {
return fmt.Errorf("name escapes")
}

// remove if already exists
err = os.Remove(targetPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("cannot remove: %w", err)
}

// ensure parent
err = os.MkdirAll(filepath.Dir(targetPath), os.FileMode(hdr.Mode)&fs.ModePerm|0111)
if err != nil {
return fmt.Errorf("cannot ensure parent: %w", err)
}

first = false
switch {
case hdr.Typeflag == tar.TypeReg:
err = writeRegularFile(targetPath, os.FileMode(hdr.Mode&0777), r)
case hdr.Typeflag == tar.TypeDir:
err = os.MkdirAll(targetPath, os.FileMode(hdr.Mode)&fs.ModePerm)
case hdr.Typeflag == tar.TypeSymlink:
err = os.Symlink(hdr.Linkname, targetPath)
default:
_, _ = fmt.Printf("unsupported type flag: %d\n", hdr.Typeflag)
}
if err != nil {
return fmt.Errorf("cannot create entry: %w", err)
}
}
}

func writeRegularFile(target string, perm os.FileMode, content io.Reader) error {
f, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
if err != nil {
return err
}
defer func(f *os.File) {
_ = f.Close()
}(f)
_, err = io.Copy(f, content)
if err != nil {
return err
}
return nil

Check failure

Code scanning / CodeQL

Arbitrary file write extracting an archive containing symbolic links High

Unresolved path from an archive header, which may point outside the archive root, is used in
symlink creation
.
}
168 changes: 168 additions & 0 deletions pkg/tar/tar_basic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package tar_test

import (
"archive/tar"
"bytes"
"io"
"os"
"path/filepath"
"testing"

tarutil "knative.dev/func/pkg/tar"
)

const (
aTxt1 = "a.txt first revision"
bTxt1 = "b.txt first revision"
aTxt2 = "a.txt second revision"
bTxt2 = "b.txt second revision"
)

func TestExtract(t *testing.T) {
var err error
d := t.TempDir()
err = tarutil.Extract(tarballV1(t), d)
if err != nil {
t.Fatal(err)
}

bs, err := os.ReadFile(filepath.Join(d, "dir/a.txt"))
if err != nil {
t.Fatal(err)
}
s := string(bs)
if s != aTxt1 {
t.Errorf("unexpected data: %s", s)
}
bs, err = os.ReadFile(filepath.Join(d, "dir/b.txt"))
if err != nil {
t.Fatal(err)
}
s = string(bs)
if s != bTxt1 {
t.Errorf("unexpected data: %s", s)
}

err = tarutil.Extract(tarballV2(t), d)
if err != nil {
t.Fatal(err)
}

bs, err = os.ReadFile(filepath.Join(d, "dir/a.txt"))
if err != nil {
t.Fatal(err)
}
s = string(bs)
if s != aTxt2 {
t.Errorf("unexpected data: %s", s)
}
bs, err = os.ReadFile(filepath.Join(d, "dir/b.txt"))
if err != nil {
t.Fatal(err)
}
s = string(bs)
if s != bTxt2 {
t.Errorf("unexpected data: %s", s)
}
}

func tarballV1(t *testing.T) io.Reader {
t.Helper()

var err error
var buff bytes.Buffer

w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)

err = w.WriteHeader(&tar.Header{
Name: "dir/a.txt",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len(aTxt1)),
})
if err != nil {
t.Fatal(err)
}
_, err = w.Write([]byte(aTxt1))
if err != nil {
t.Fatal(err)
}

err = w.WriteHeader(&tar.Header{
Name: "dir/data1",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len(bTxt1)),
})
if err != nil {
t.Fatal(err)
}
_, err = w.Write([]byte(bTxt1))
if err != nil {
t.Fatal(err)
}

err = w.WriteHeader(&tar.Header{
Name: "dir/data2",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len(bTxt2)),
})
if err != nil {
t.Fatal(err)
}
_, err = w.Write([]byte(bTxt2))
if err != nil {
t.Fatal(err)
}

err = w.WriteHeader(&tar.Header{
Name: "dir/b.txt",
Linkname: "data1",
Typeflag: tar.TypeSymlink,
})
if err != nil {
t.Fatal(err)
}

return &buff
}

func tarballV2(t *testing.T) io.Reader {
t.Helper()

var err error
var buff bytes.Buffer

w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)

err = w.WriteHeader(&tar.Header{
Name: "dir/a.txt",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len(aTxt2)),
})
if err != nil {
t.Fatal(err)
}
_, err = w.Write([]byte(aTxt2))
if err != nil {
t.Fatal(err)
}

err = w.WriteHeader(&tar.Header{
Name: "dir/b.txt",
Linkname: "data2",
Typeflag: tar.TypeSymlink,
})
if err != nil {
t.Fatal(err)
}
return &buff
}
Loading

0 comments on commit 6ace6cc

Please sign in to comment.