repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
services/luci/internal/buildctx/buildctx_test.go
Rawpackage buildctx
import (
"os"
"path/filepath"
"strings"
"syscall"
"testing"
)
func TestStageCopiesOnlySafeUnignoredContext(t *testing.T) {
checkout := t.TempDir()
write(t, filepath.Join(checkout, ".ci", "Containerfile"), "FROM alpine\nCOPY . /src\n")
write(t, filepath.Join(checkout, ".ci", "image", ".containerignore"), "ignored\n*.secret\n")
write(t, filepath.Join(checkout, ".ci", "image", "keep"), "ok")
write(t, filepath.Join(checkout, ".ci", "image", "ignored", "file"), "no")
write(t, filepath.Join(checkout, ".ci", "image", "token.secret"), "no")
containerfile, context, err := Stage(checkout, ".ci/Containerfile", ".ci/image", filepath.Join(t.TempDir(), "stage"), 100, 100)
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(containerfile); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(context, "keep")); err != nil {
t.Fatal(err)
}
for _, name := range []string{"ignored", "token.secret"} {
if _, err := os.Stat(filepath.Join(context, name)); !os.IsNotExist(err) {
t.Fatalf("ignored %s exists: %v", name, err)
}
}
}
func TestStageRejectsEscapesMetadataSpecialFilesAndExternalLinks(t *testing.T) {
for name, setup := range map[string]func(t *testing.T, root string){
"traversal": func(t *testing.T, root string) { write(t, filepath.Join(root, ".ci", "Containerfile"), "FROM scratch") },
"containerfile-git": func(t *testing.T, root string) {
write(t, filepath.Join(root, ".ci", "image", ".git", "Containerfile"), "FROM scratch")
},
"external-link": func(t *testing.T, root string) {
if err := os.Symlink("/etc/passwd", filepath.Join(root, ".ci", "image", "link")); err != nil {
t.Fatal(err)
}
},
"special": func(t *testing.T, root string) {
if err := syscall.Mkfifo(filepath.Join(root, ".ci", "image", "pipe"), 0o600); err != nil {
t.Fatal(err)
}
},
} {
t.Run(name, func(t *testing.T) {
root := t.TempDir()
write(t, filepath.Join(root, ".ci", "Containerfile"), "FROM scratch")
if err := os.MkdirAll(filepath.Join(root, ".ci", "image"), 0o755); err != nil {
t.Fatal(err)
}
setup(t, root)
containerfile := ".ci/Containerfile"
if name == "traversal" {
containerfile = "../Containerfile"
}
if name == "containerfile-git" {
containerfile = ".ci/image/.git/Containerfile"
}
_, _, err := Stage(root, containerfile, ".ci/image", filepath.Join(t.TempDir(), "stage"), 100, 100)
if err == nil {
t.Fatal("unsafe input accepted")
}
})
}
}
func TestStageBoundsBytesAndAllowsInternalLink(t *testing.T) {
root := t.TempDir()
write(t, filepath.Join(root, ".ci", "Containerfile"), "FROM scratch")
write(t, filepath.Join(root, ".ci", "image", "file"), strings.Repeat("x", 40))
if err := os.Symlink("file", filepath.Join(root, ".ci", "image", "link")); err != nil {
t.Fatal(err)
}
_, context, err := Stage(root, ".ci/Containerfile", ".ci/image", filepath.Join(t.TempDir(), "stage"), 20, 100)
if err == nil || !strings.Contains(err.Error(), "byte limit") || context != "" {
t.Fatalf("err=%v context=%q", err, context)
}
_, context, err = Stage(root, ".ci/Containerfile", ".ci/image", filepath.Join(t.TempDir(), "stage-ok"), 100, 100)
if err != nil {
t.Fatal(err)
}
if target, err := os.Readlink(filepath.Join(context, "link")); err != nil || target != "file" {
t.Fatalf("link=%q err=%v", target, err)
}
}
func TestStageAllowsRootContextExcludesGitAndHonorsNegation(t *testing.T) {
root := t.TempDir()
write(t, filepath.Join(root, "Containerfile"), "FROM scratch")
write(t, filepath.Join(root, ".git", "config"), "private")
write(t, filepath.Join(root, ".containerignore"), "ignored/\n!ignored/keep\n")
write(t, filepath.Join(root, "ignored", "drop"), "no")
write(t, filepath.Join(root, "ignored", "keep"), "yes")
_, context, err := Stage(root, "Containerfile", ".", filepath.Join(t.TempDir(), "stage"), 1000, 100)
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(context, ".git")); !os.IsNotExist(err) {
t.Fatalf("Git metadata staged: %v", err)
}
if _, err := os.Stat(filepath.Join(context, "ignored", "keep")); err != nil {
t.Fatalf("negated path missing: %v", err)
}
if _, err := os.Stat(filepath.Join(context, "ignored", "drop")); !os.IsNotExist(err) {
t.Fatalf("ignored path staged: %v", err)
}
}
func TestStageBoundsFilesAndRejectsIgnoreSymlink(t *testing.T) {
root := t.TempDir()
write(t, filepath.Join(root, "Containerfile"), "FROM scratch")
write(t, filepath.Join(root, "one"), "1")
write(t, filepath.Join(root, "two"), "2")
if _, _, err := Stage(root, "Containerfile", ".", filepath.Join(t.TempDir(), "limited"), 100, 2); err == nil || !strings.Contains(err.Error(), "file limit") {
t.Fatalf("file limit err=%v", err)
}
ignoreRoot := t.TempDir()
write(t, filepath.Join(ignoreRoot, "Containerfile"), "FROM scratch")
write(t, filepath.Join(ignoreRoot, "outside"), "x")
if err := os.Symlink("outside", filepath.Join(ignoreRoot, ".containerignore")); err != nil {
t.Fatal(err)
}
if _, _, err := Stage(ignoreRoot, "Containerfile", ".", filepath.Join(t.TempDir(), "ignore-link"), 100, 100); err == nil || !strings.Contains(err.Error(), "not a regular") {
t.Fatalf("ignore symlink err=%v", err)
}
}
func TestStageCountsContainerfileInByteLimit(t *testing.T) {
root := t.TempDir()
write(t, filepath.Join(root, ".ci", "Containerfile"), strings.Repeat("x", 60))
write(t, filepath.Join(root, ".ci", "image", "file"), strings.Repeat("x", 50))
_, _, err := Stage(root, ".ci/Containerfile", ".ci/image", filepath.Join(t.TempDir(), "stage"), 100, 100)
if err == nil || !strings.Contains(err.Error(), "byte limit") {
t.Fatalf("err=%v", err)
}
}
func write(t *testing.T, path, value string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(value), 0o644); err != nil {
t.Fatal(err)
}
}