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
24 changes: 23 additions & 1 deletion .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -537,14 +537,18 @@ jobs:
runs-on: blacksmith-8vcpu-ubuntu-2204
needs: [prepare, build-setup, build-platforms, build-binaries, test, build-docs, docker-build]
permissions:
contents: write # `welder run tag-release` pushes the release git tag
contents: write # `welder run tag-release` pushes the release git tag
id-token: write # OIDC for keyless cosign sign-blob of sc.sh
attestations: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: fregante/setup-git-user@024bc0b8e177d7e77203b48dab6fb45666854b35 # v2.0.2
- name: install sc + welder tools
uses: simple-container-com/actions/setup-sc-tooling@0af5a697f24ea484991660619d0ae42d50343b9d # main
- name: Install attestation tools (cosign for sc.sh sign-blob)
uses: simple-container-com/actions/install-attest-tools@0af5a697f24ea484991660619d0ae42d50343b9d # main
- name: prepare secrets for build
run: |
cat << EOF > ./.sc/cfg.default.yaml
Expand Down Expand Up @@ -636,6 +640,24 @@ jobs:
cp sc.sh .sc/stacks/dist/bundle/sc.sh
sed -i -e 's/VERSION="0\.0\.0"/VERSION="${VERSION}"/g' .sc/stacks/dist/bundle/sc.sh
echo "${VERSION}" > .sc/stacks/dist/bundle/version
# Sign the post-template sc.sh that will land on dist.simple-container.com.
# Signing the *post-sed* artifact is intentional: consumers verify the
# bytes they actually fetch. Identity matches the tarball regex in
# docs/SECURITY.md ("Verifying sc.sh itself") — push.yaml@refs/heads/main.
#
# Hard-fail on purpose (no continue-on-error): if signing breaks, the
# `welder deploy` step that publishes sc.sh + sc.sh.cosign-bundle never
# runs, so consumers never observe an sc.sh whose bundle is missing or
# stale. Atomic w.r.t. the script — both publish or neither.
- name: Cosign sign-blob (keyless, bundle) for sc.sh
env:
COSIGN_EXPERIMENTAL: "1"
run: |
set -euo pipefail
cd .sc/stacks/dist/bundle
cosign sign-blob --yes \
--bundle sc.sh.cosign-bundle \
sc.sh
- name: Run tag-release task after images are built
env:
VERSION: ${{ needs.prepare.outputs.version }}
Expand Down
47 changes: 47 additions & 0 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,53 @@ end-to-end supply-chain integrity should install cosign before
bootstrapping (https://docs.sigstore.dev/system_config/installation/).
The commands above remain the manual / out-of-band verification path.

### Verifying sc.sh itself

`sc.sh` verifies the tarball it downloads, but a CDN compromise (or
bucket write that bypasses the publish workflow) could swap `sc.sh`
itself for a version whose `verify_sc_tarball` is stripped or
hard-coded to succeed. Consumers running `bash <(curl ... sc.sh)`
would then execute an unverified script, silently regressing the
in-script protection.

The publish workflow therefore signs `sc.sh` itself with the same
cosign keyless scheme used for the tarballs. The bundle ships next to
the script:

- `https://dist.simple-container.com/sc.sh`
- `https://dist.simple-container.com/sc.sh.cosign-bundle`

Identity matches the tarball identity (push.yaml@refs/heads/main), so
the same regex verifies both. Recommended pattern for consumers who
want the script itself verified before execution:

```bash
curl -fsSL https://dist.simple-container.com/sc.sh \
-o sc.sh
curl -fsSL https://dist.simple-container.com/sc.sh.cosign-bundle \
-o sc.sh.cosign-bundle
cosign verify-blob \
--bundle sc.sh.cosign-bundle \
--certificate-identity-regexp '^https://github\.com/simple-container-com/api/\.github/workflows/push\.yaml@refs/heads/main$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
sc.sh
bash sc.sh
```

Verification is signing-side only: the publish workflow hard-fails the
release if `cosign sign-blob` does not succeed for `sc.sh`, so a new
`sc.sh` is never uploaded without a matching `.cosign-bundle`. We
deliberately do NOT teach `sc.sh` to self-verify before executing —
the script doing so would be the same code an attacker would tamper
with. Verification has to happen out-of-band, in the consumer's
runner, before the script is invoked.

Consumers who cannot install cosign (e.g., minimal CI images) keep
working: the existing piped form (`bash <(curl ... sc.sh)`) still
works and still gives them the Phase 2c in-script tarball
verification. They simply don't gain the additional script-bytes
guarantee that the verify-then-bash pattern above provides.

#### Installing preview / branch-preview builds

Default `sc.sh` accepts only production-signed tarballs (signed by
Expand Down
Loading