4 Commits

Author SHA1 Message Date
benvin 7abf91b93e Merge pull request 'ci: authenticate artifactapi rpm upload with a vault token' (#5) from benvin/artifactapi-upload-token into main
Reviewed-on: #5
2026-10-25 22:59:03 +11:00
unkin-agent 11a0aad6f4 ci: authenticate artifactapi rpm upload with a vault token
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
2026-10-25 20:36:20 +11:00
benvin 757e1c069c Merge pull request 'Test uncovered launch-dir refusals' (#4) from benvin/launch-dir-tests into main
Reviewed-on: #4
2026-10-25 01:08:03 +11:00
unkin-agent 588ef2f8c2 Test launch-dir refusals not covered elsewhere
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
2026-10-24 20:43:16 +11:00
2 changed files with 87 additions and 1 deletions
+8 -1
View File
@@ -101,6 +101,13 @@ steps:
- |
HOST="https://artifactapi.k8s.syd1.au.unkin.net"
REPO="rpm-internal"
set -o pipefail
VAULT="https://vault.service.consul:8200"
AUTH=$$(mktemp)
VT=$$(jq -n --rawfile jwt /var/run/secrets/kubernetes.io/serviceaccount/token '{role:"woodpecker_artifactapi_writer",jwt:$$jwt}' \
| curl -sf -d @- "$$VAULT/v1/auth/k8s/au/syd1/login" | jq -er .auth.client_token) || exit 1
printf 'X-Vault-Token: %s\n' "$$VT" | curl -sf -H @- "$$VAULT/v1/identity/oidc/token/artifactapi-writer" \
| jq -er '"Authorization: Bearer " + .data.token' > "$$AUTH" || exit 1
for rpm in dist/*.rpm; do
FILE=$$(basename "$$rpm")
code=$$(curl -s -o /dev/null -w '%{http_code}' "$$HOST/api/v2/remotes/$$REPO/files/Packages/$$FILE" || true)
@@ -109,7 +116,7 @@ steps:
continue
fi
curl -f -X PUT "$$HOST/api/v2/remotes/$$REPO/files/$$FILE" \
-H "Content-Type: application/x-rpm" --data-binary @"$$rpm"
-H "Content-Type: application/x-rpm" -H @"$$AUTH" --data-binary @"$$rpm"
done
depends_on: [package]
backend_options:
+79
View File
@@ -681,3 +681,82 @@ func TestLaunchMountsResolvedSource(t *testing.T) {
}
}
}
func TestCheckLaunchDirDefaultRootForbidden(t *testing.T) {
t.Setenv("HOME", "/tmp/home")
t.Setenv("XDG_CONFIG_HOME", "/nonexistent/cfg")
t.Setenv("XDG_DATA_HOME", "")
t.Setenv("XDG_RUNTIME_DIR", "/nonexistent/rt")
t.Setenv("PATH", "/usr/bin")
_, err := checkLaunchDir("/tmp/home/proj", "/nonexistent/hosts")
check(t, "forbidden default root", "refusing to mount /tmp/home/proj: it overlaps "+realpath("/tmp"), fmt.Sprint(err))
}
// isolatedHome points every sensitive dir outside a fresh $HOME, leaving only
// the rule under test to refuse.
func isolatedHome(t *testing.T) (work, home string) {
work, _ = filepath.EvalSymlinks(t.TempDir())
elsewhere, _ := filepath.EvalSymlinks(t.TempDir())
home = work + "/home"
for _, d := range []string{".ssh", ".gnupg", ".config", ".local/share/containers"} {
_ = os.MkdirAll(filepath.Join(elsewhere, d), 0o755)
_ = os.MkdirAll(filepath.Dir(filepath.Join(home, d)), 0o755)
_ = os.Symlink(filepath.Join(elsewhere, d), filepath.Join(home, d))
}
t.Setenv("HOME", home)
t.Setenv("XDG_CONFIG_HOME", elsewhere+"/cfg")
t.Setenv("XDG_DATA_HOME", "")
t.Setenv("XDG_RUNTIME_DIR", elsewhere+"/rt")
t.Setenv("PATH", "/usr/bin")
origRoots := forbiddenRoots
forbiddenRoots = []string{"/run"}
t.Cleanup(func() { forbiddenRoots = origRoots })
_ = os.MkdirAll(filepath.Dir(allowedPathsFile()), 0o755)
return work, home
}
func TestCheckLaunchDirResolvedAllowedPathsDir(t *testing.T) {
_, home := isolatedHome(t)
dot := home + "/src/dot"
_ = os.MkdirAll(dot, 0o755)
_ = os.WriteFile(dot+"/allowed-paths", []byte("~/src\n"), 0o644)
_ = os.Symlink(dot+"/allowed-paths", allowedPathsFile())
_ = os.MkdirAll(home+"/src/other", 0o755)
_, err := checkLaunchDir(home+"/src/other", "/nonexistent/hosts")
check(t, "sibling allowed", nil, err)
_, err = checkLaunchDir(dot, "/nonexistent/hosts")
check(t, "resolved allowed-paths dir", "refusing to mount "+dot+": it contains "+dot, fmt.Sprint(err))
}
func TestAllowedRootsRejectsHomeAndDotDirs(t *testing.T) {
work, home := isolatedHome(t)
f := allowedPathsFile()
for _, tc := range []struct{ root, want string }{
{"~/.cache", `"~/.cache" is a dot-directory of $HOME`},
{"~", `"~" is or contains $HOME`},
{home, `"` + home + `" is or contains $HOME`},
{work, `"` + work + `" is or contains $HOME`},
} {
_ = os.WriteFile(f, []byte(tc.root+"\n"), 0o644)
_, _, err := allowedRoots(home)
check(t, tc.root, f+": "+tc.want, fmt.Sprint(err))
}
_ = os.WriteFile(f, []byte("~/src\n"), 0o644)
_, _, err := allowedRoots(home)
check(t, "plain root", nil, err)
}
func TestLaunchRefusesColonInResolvedSource(t *testing.T) {
e := setup(t)
_ = os.Mkdir(e.work+"/home/a:b", 0o755)
_ = os.Symlink(e.work+"/home/a:b", e.work+"/home/link")
stderr, _ := os.CreateTemp(t.TempDir(), "stderr")
orig := os.Stderr
os.Stderr = stderr
code := e.launch(t, e.work+"/home/link", "--exec", "true")
os.Stderr = orig
out, _ := os.ReadFile(stderr.Name())
check(t, "exit", 1, code)
check(t, "stderr", "agent-sandbox: cannot mount a directory containing ':'\n", string(out))
check(t, "podman calls", []string(nil), e.fake.calls)
}