Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/template-sync-state.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"template_repo": "craftainer/template-docker",
"synced_tag": "v1.8.1"
"synced_tag": "v1.8.2"
}
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ jobs:
# is the separate `publish-packages` job below. `packages: write` lets
# `make publish` push to this repository's own GHCR packages
# (ghcr.io/<owner>/<repo>...) with `GITHUB_TOKEN` (actor
# `GITHUB_ACTOR`) when the OCI_REGISTRY_* secrets are unset. The
# `GITHUB_ACTOR`, a default Actions env var) when the OCI_REGISTRY_*
# secrets are unset and OCI_REGISTRY is ghcr.io (the Makefile's
# REGISTRY_CREDS; any other registry needs the secrets). The
# `release` job has it too, for `make finalize`'s manifest lists.
permissions:
contents: write
Expand Down
30 changes: 25 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,24 @@ sbom: check-arch repo-check
release-assets: check-arch repo-check image-smoke
test -f $(ASSET).tar && test -f $(ASSET).spdx.json

# Registry credentials for publish and finalize, as shell that sets $user and
# $pass. The OCI_REGISTRY_USERNAME/OCI_REGISTRY_PASSWORD secrets win when
# set. Otherwise, on ghcr.io only (a GITHUB_TOKEN is valid nowhere else), the
# workflow's own GITHUB_ACTOR/GITHUB_TOKEN are used (release.yml grants
# `packages: write`). Anywhere else the missing secret is an error.
REGISTRY_CREDS = user="$$OCI_REGISTRY_USERNAME"; pass="$$OCI_REGISTRY_PASSWORD"; \
if [ -z "$$user" ]; then \
if [ "$(OCI_REGISTRY)" != ghcr.io ]; then \
echo "error: OCI_REGISTRY_USERNAME is not set; it and OCI_REGISTRY_PASSWORD are required for registry $(OCI_REGISTRY) (GITHUB_TOKEN works only for ghcr.io)" >&2; exit 1; \
fi; \
user="$$GITHUB_ACTOR"; pass="$$GITHUB_TOKEN"; \
if [ -z "$$user" ] || [ -z "$$pass" ]; then \
echo "error: OCI_REGISTRY_USERNAME is not set and GITHUB_ACTOR/GITHUB_TOKEN are empty; set the OCI_REGISTRY_* secrets or run in GitHub Actions" >&2; exit 1; \
fi; \
elif [ -z "$$pass" ]; then \
echo "error: OCI_REGISTRY_USERNAME is set but OCI_REGISTRY_PASSWORD is empty" >&2; exit 1; \
fi

# Copies the tested tar from dist/ to the registry (skopeo copy --all
# oci-archive:... docker://...), records the pushed index digest in
# dist/<asset>.digest for finalize, and logs in through a throwaway authfile
Expand All @@ -136,9 +154,10 @@ ifeq ($(OCI_REGISTRY),)
else
@$(REPO_GUARD)
@test -f $(ASSET).tar || { echo "$(ASSET).tar not found; run 'make build' first." >&2; exit 1; }
@auth="$$(mktemp -d)" && chmod 0777 "$$auth" && trap 'rm -rf "$$auth"' EXIT && \
printf '%s' "$$OCI_REGISTRY_PASSWORD" | bash scripts/skopeo.sh -i -v "$$auth:/auth" -- \
login --authfile /auth/auth.json -u "$$OCI_REGISTRY_USERNAME" --password-stdin "$(OCI_REGISTRY)" && \
@$(REGISTRY_CREDS); \
auth="$$(mktemp -d)" && chmod 0777 "$$auth" && trap 'rm -rf "$$auth"' EXIT && \
printf '%s' "$$pass" | bash scripts/skopeo.sh -i -v "$$auth:/auth" -- \
login --authfile /auth/auth.json -u "$$user" --password-stdin "$(OCI_REGISTRY)" && \
bash scripts/skopeo.sh -v "$$auth:/auth:ro" -- \
copy --all --authfile /auth/auth.json --digestfile /$(ASSET).digest \
oci-archive:/$(ASSET).tar docker://$(OCI_REGISTRY)/$(OCI_NAME):$(RELEASE_VERSION)-$(RELEASE_ARCH)
Expand Down Expand Up @@ -190,9 +209,10 @@ ifeq ($(OCI_REGISTRY),)
@echo "OCI_REGISTRY not set -- no manifest list to create."
else
@$(REPO_GUARD)
@orig="$${DOCKER_CONFIG:-$$HOME/.docker}" && export DOCKER_CONFIG="$$(mktemp -d)" && trap 'rm -rf "$$DOCKER_CONFIG"' EXIT && \
@$(REGISTRY_CREDS); \
orig="$${DOCKER_CONFIG:-$$HOME/.docker}" && export DOCKER_CONFIG="$$(mktemp -d)" && trap 'rm -rf "$$DOCKER_CONFIG"' EXIT && \
if [ -d "$$orig/cli-plugins" ]; then ln -s "$$orig/cli-plugins" "$$DOCKER_CONFIG/cli-plugins"; fi && \
printf '%s' "$$OCI_REGISTRY_PASSWORD" | docker login "$(OCI_REGISTRY)" -u "$$OCI_REGISTRY_USERNAME" --password-stdin && \
printf '%s' "$$pass" | docker login "$(OCI_REGISTRY)" -u "$$user" --password-stdin && \
srcs="" && for a in $(ARCHES); do \
d="$$(cat dist/$(IMAGE)-$(RELEASE_VERSION)-$$a.digest)" || exit 1; \
srcs="$$srcs $(OCI_REGISTRY)/$(OCI_NAME)@$$d"; \
Expand Down
6 changes: 5 additions & 1 deletion docs/TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,11 @@ The explicit secrets remain the way to reach any other registry, or a
package outside that repository's reach, and an instance that sets them
should prefer them. Base's own `publish` and `finalize` are no-ops and log
in nowhere, so each instance implements this fallback in its own
`Makefile`. Trade-off: the token is in the job env, so every per-arch
`Makefile`; template-docker's does: `publish` and `finalize` use the
`OCI_REGISTRY_USERNAME`/`OCI_REGISTRY_PASSWORD` secrets when
`OCI_REGISTRY_USERNAME` is set, else `GITHUB_ACTOR`/`GITHUB_TOKEN` when
`OCI_REGISTRY` is `ghcr.io` (a `GITHUB_TOKEN` is valid nowhere else), else
fail naming the missing secret. Trade-off: the token is in the job env, so every per-arch
target (`build`, `sbom`, `release-assets`), not only `publish`, can write
those packages.

Expand Down
4 changes: 3 additions & 1 deletion scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
(passive) in its network namespace, with rule levels from the
instance-owned `.zap/rules.tsv`; reports go to `dist/dast/`.
- `test-image-scripts.sh` — the `image-scripts-test` hook: offline tests of
the Makefile's repo derivation and these scripts.
the Makefile's repo derivation, the `publish`/`finalize` registry
credentials (secrets, the ghcr.io `GITHUB_TOKEN` fallback, the non-ghcr
failure, no password in an argv; docker stubbed) and these scripts.
- `image-smoke.sh` — `make image-smoke` (a `release-check` prerequisite):
converts the arch's image in the release OCI tar from `dist/` to a
docker-archive with the pinned skopeo (`docker load` can't read a buildx
Expand Down
51 changes: 51 additions & 0 deletions scripts/test-image-scripts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,57 @@ out="$(sk -- copy a b)" || fail "skopeo.sh: no docker args should work"
out="$(sk -i -- login)" || fail "skopeo.sh: docker args should work"
[[ "$out" == *" -i img login" ]] || fail "skopeo.sh: unexpected command: $out"

# Makefile publish/finalize credentials, with docker stubbed (it records its
# argv and the stdin of any --password-stdin login): the OCI_REGISTRY_* secrets
# win; with them unset ghcr.io falls back to GITHUB_ACTOR/GITHUB_TOKEN; any
# other registry fails naming the missing secret; the password is only ever
# on stdin, never in an argv.
cr="$tmp/creds"
mkdir -p "$cr/bin" "$cr/dist"
ln -s "$root/scripts" "$cr/scripts"
: >"$cr/dist/r-1.0.0-amd64.tar"
cat >"$cr/bin/docker" <<'EOF_STUB'
#!/usr/bin/env bash
printf '%s\n' "$*" >>"$STUB_LOG"
case "$*" in *--password-stdin*) cat >"$STUB_STDIN" ;; esac
EOF_STUB
chmod +x "$cr/bin/docker"
# creds_run TARGET REGISTRY [VAR=value ...]: prints make's output (call it in
# $(...), then read the exit code with creds_rc); the stub's argv log is
# left in $cr/log and its stdin in $cr/stdin.
creds_run() {
local target="$1" registry="$2"
shift 2
printf 'sha256:abc' >"$cr/dist/r-1.0.0-amd64.digest" # finalize deletes it
rm -f "$cr/log" "$cr/stdin"
: >"$cr/log"
local rc=0
(cd "$cr" && env -u OCI_REGISTRY_USERNAME -u OCI_REGISTRY_PASSWORD -u GITHUB_ACTOR -u GITHUB_TOKEN \
PATH="$cr/bin:$PATH" STUB_LOG="$cr/log" STUB_STDIN="$cr/stdin" GITHUB_REPOSITORY=o/r \
RELEASE_VERSION=1.0.0 RELEASE_ARCH=amd64 ARCHES=amd64 "$@" \
make -s -f "$root/Makefile" "$target" OCI_REGISTRY="$registry" 2>&1) || rc=$?
printf '%s' "$rc" >"$cr/rc"
}
creds_rc() { cat "$cr/rc"; }
for target in publish finalize; do
out="$(creds_run "$target" ghcr.io OCI_REGISTRY_USERNAME=u OCI_REGISTRY_PASSWORD=sekret GITHUB_ACTOR=a GITHUB_TOKEN=tok)"
expect_eq "$target secrets: exit" 0 "$(creds_rc)"
expect_eq "$target secrets: stdin" sekret "$(cat "$cr/stdin")"
[[ "$(cat "$cr/log")" == *" -u u "* ]] || fail "$target secrets: username not u: $(cat "$cr/log")"
out="$(creds_run "$target" ghcr.io GITHUB_ACTOR=a GITHUB_TOKEN=tok)"
expect_eq "$target ghcr fallback: exit" 0 "$(creds_rc)"
expect_eq "$target ghcr fallback: stdin" tok "$(cat "$cr/stdin")"
[[ "$(cat "$cr/log")" == *" -u a "* ]] || fail "$target ghcr fallback: username not a: $(cat "$cr/log")"
[[ "$(cat "$cr/log")" != *tok* && "$(cat "$cr/log")" != *sekret* ]] || fail "$target: password in argv: $(cat "$cr/log")"
out="$(creds_run "$target" quay.io GITHUB_ACTOR=a GITHUB_TOKEN=tok)"
[[ "$(creds_rc)" != 0 && "$out" == *OCI_REGISTRY_USERNAME* ]] || fail "$target non-ghcr without secrets should fail naming OCI_REGISTRY_USERNAME (rc=$(creds_rc)): $out"
[[ ! -s "$cr/log" ]] || fail "$target non-ghcr: docker ran: $(cat "$cr/log")"
out="$(creds_run "$target" ghcr.io)"
[[ "$(creds_rc)" != 0 && "$out" == *GITHUB_TOKEN* ]] || fail "$target ghcr without any credential should fail (rc=$(creds_rc)): $out"
out="$(creds_run "$target" ghcr.io OCI_REGISTRY_USERNAME=u GITHUB_ACTOR=a GITHUB_TOKEN=tok)"
[[ "$(creds_rc)" != 0 && "$out" == *OCI_REGISTRY_PASSWORD* ]] || fail "$target username without password should fail naming OCI_REGISTRY_PASSWORD (rc=$(creds_rc)): $out"
done

# image-dast.sh in a scratch copy of the repo (it works relative to its own
# repo root), with docker and curl stubbed: skips without a tar, wires ZAP to
# the app container's network and the rules file, and maps zap-baseline.py's
Expand Down
Loading