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
3 changes: 2 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "monthly"
interval: "weekly"
day: "monday"
cooldown:
default-days: 7
30 changes: 30 additions & 0 deletions .github/workflows/govulncheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: govulncheck

permissions: {}

on:
push:
branches: [main]
pull_request:
schedule:
- cron: '0 6 * * 1'

jobs:
govulncheck:
permissions:
contents: read
name: Run on Ubuntu
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod

- name: Run govulncheck
run: make govulncheck
133 changes: 130 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ concurrency:
jobs:
release:
permissions:
contents: write
contents: write # gh release create
id-token: write # keyless cosign + actions/attest OIDC (Fulcio/Rekor)
attestations: write # actions/attest — persist attestations
artifact-metadata: write # actions/attest — create the artifact storage record
name: Build and publish release
runs-on: ubuntu-latest
environment: release
env:
TAG: ${{ github.ref_name }}
IMG_REPO: quay.io/cloudscalech/cluster-api-cloudscale-controller
IMG: quay.io/cloudscalech/cluster-api-cloudscale-controller:${{ github.ref_name }}
# Keyless Sigstore signing identity
ID_REGEXP: '^https://github.com/cloudscale-ch/cluster-api-provider-cloudscale/\.github/workflows/release\.yml@refs/tags/'
ISSUER: https://token.actions.githubusercontent.com
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand All @@ -34,6 +41,11 @@ jobs:
go-version-file: go.mod
cache: false

# Gate the release on reachable vulnerabilities (incl. stdlib/toolchain)
# before we build or push anything.
- name: Run govulncheck
run: make govulncheck

- name: Login to quay.io
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
Expand All @@ -45,11 +57,123 @@ jobs:
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- name: Build and push multi-arch image
run: make docker-buildx IMG=$IMG PLATFORMS=linux/amd64,linux/arm64
id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.IMG }}
build-args: |
VERSION=${{ env.TAG }}
# We attach our own SLSA provenance + SBOM attestations below with
# actions/attest; disable BuildKit's inline provenance so the image
# index stays clean and single-sourced.
provenance: false

- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

# Keyless signature (Sigstore/OIDC) — verifiable with `cosign verify`.
- name: Sign the container image
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: cosign sign --yes "${IMG_REPO}@${DIGEST}"

# Verify the signature we just created.
- name: Verify the container image signature
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
cosign verify "${IMG_REPO}@${DIGEST}" \
--certificate-identity-regexp "$ID_REGEXP" \
--certificate-oidc-issuer "$ISSUER"

# SLSA build provenance attestation, pushed to quay and the GitHub
# attestation store (verifiable with `gh attestation verify`).
- name: Attest build provenance
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-name: ${{ env.IMG_REPO }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true

# Verify the provenance attestation we just pushed.
- name: Verify build provenance attestation
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DIGEST: ${{ steps.build.outputs.digest }}
run: |
gh attestation verify "oci://${IMG_REPO}@${DIGEST}" --owner cloudscale-ch \
--predicate-type https://slsa.dev/provenance/v1

- name: Generate SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
image: ${{ env.IMG_REPO }}@${{ steps.build.outputs.digest }}
format: spdx-json
output-file: sbom.spdx.json
upload-artifact: false
upload-release-assets: false

# SBOM attestation (the same action switches to SBOM mode when sbom-path
# is set), pushed to quay and the GitHub attestation store.
- name: Attest SBOM
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-name: ${{ env.IMG_REPO }}
subject-digest: ${{ steps.build.outputs.digest }}
sbom-path: sbom.spdx.json
push-to-registry: true

# Verify the SBOM attestation we just pushed.
- name: Verify SBOM attestation
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DIGEST: ${{ steps.build.outputs.digest }}
run: |
gh attestation verify "oci://${IMG_REPO}@${DIGEST}" --owner cloudscale-ch \
--predicate-type https://spdx.dev/Document/v2.3

- name: Build release manifests
run: make release-manifests IMG=$IMG

# Checksums for the clusterctl artifacts + a keyless signature over them.
- name: Generate and sign checksums
run: |
cd dist
sha256sum *.yaml > checksums.txt
cosign sign-blob --yes \
--bundle checksums.txt.bundle \
checksums.txt

# Verify the files match the checksums, and the signature we just created.
- name: Verify checksums and blob signature
run: |
cd dist
sha256sum -c checksums.txt
cosign verify-blob --bundle checksums.txt.bundle \
--certificate-identity-regexp "$ID_REGEXP" \
--certificate-oidc-issuer "$ISSUER" \
checksums.txt

# SLSA provenance for the release manifest files (GitHub attestation store;
# verifiable with `gh attestation verify <file> --owner cloudscale-ch`).
- name: Attest release manifests provenance
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-path: 'dist/*.yaml'

# Verify the provenance attestations we just created for each manifest.
- name: Verify release manifests provenance
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for f in dist/*.yaml; do
gh attestation verify "$f" --owner cloudscale-ch \
--predicate-type https://slsa.dev/provenance/v1
done

- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -62,4 +186,7 @@ jobs:
gh release create "$TAG" \
--generate-notes \
$PRERELEASE_FLAG \
dist/*.yaml
dist/*.yaml \
dist/checksums.txt \
dist/checksums.txt.bundle \
sbom.spdx.json
Comment thread
mweibel marked this conversation as resolved.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build the manager binary
FROM golang:1.26.3 AS builder
# BUILDPLATFORM is a predefined BuildKit arg. For a single-platform `docker build` it resolves to the host and is a no-op.
FROM --platform=${BUILDPLATFORM} golang:1.26.5 AS builder
ARG TARGETOS
ARG TARGETARCH
ARG VERSION=dev
Expand Down
22 changes: 18 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,20 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
lint-config: golangci-lint ## Verify golangci-lint linter configuration
"$(GOLANGCI_LINT)" config verify

.PHONY: govulncheck
govulncheck: govulncheck-tool ## Run govulncheck to scan for known, reachable vulnerabilities (incl. stdlib/toolchain).
"$(GOVULNCHECK)" ./...

##@ Dependencies

## Location to install dependencies to
LOCALBIN := $(shell pwd)/bin
$(LOCALBIN):
mkdir -p "$(LOCALBIN)"

# Host OS/ARCH used to namespace tool binaries in $(LOCALBIN)
HOST_PLATFORM := $(shell go env GOOS)-$(shell go env GOARCH)

## Tool Binaries
KUBECTL ?= kubectl
KIND ?= kind
Expand All @@ -95,6 +102,7 @@ CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
GINKGO ?= $(LOCALBIN)/ginkgo
GOVULNCHECK ?= $(LOCALBIN)/govulncheck

##@ E2E Testing

Expand Down Expand Up @@ -378,6 +386,7 @@ ENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \
printf '%s\n' "$$v" | sed -E 's/^v?[0-9]+\.([0-9]+).*/1.\1/')

GOLANGCI_LINT_VERSION ?= v2.12.2
GOVULNCHECK_VERSION ?= v1.5.0
.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
$(KUSTOMIZE): $(LOCALBIN)
Expand Down Expand Up @@ -411,20 +420,25 @@ $(GOLANGCI_LINT): $(LOCALBIN)
mv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \
} || true

.PHONY: govulncheck-tool
govulncheck-tool: $(GOVULNCHECK) ## Download govulncheck locally if necessary.
$(GOVULNCHECK): $(LOCALBIN)
$(call go-install-tool,$(GOVULNCHECK),golang.org/x/vuln/cmd/govulncheck,$(GOVULNCHECK_VERSION))

# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary
# $2 - package url which can be installed
# $3 - specific version of package
define go-install-tool
@[ -f "$(1)-$(3)" ] && [ "$$(readlink -- "$(1)" 2>/dev/null)" = "$(1)-$(3)" ] || { \
@[ -f "$(1)-$(3)-$(HOST_PLATFORM)" ] && [ "$$(readlink -- "$(1)" 2>/dev/null)" = "$(1)-$(3)-$(HOST_PLATFORM)" ] || { \
set -e; \
package=$(2)@$(3) ;\
echo "Downloading $${package}" ;\
echo "Downloading $${package} ($(HOST_PLATFORM))" ;\
rm -f "$(1)" ;\
GOBIN="$(LOCALBIN)" go install $${package} ;\
mv "$(LOCALBIN)/$$(basename "$(1)")" "$(1)-$(3)" ;\
mv "$(LOCALBIN)/$$(basename "$(1)")" "$(1)-$(3)-$(HOST_PLATFORM)" ;\
} ;\
ln -sf "$$(realpath "$(1)-$(3)")" "$(1)"
ln -sf "$$(realpath "$(1)-$(3)-$(HOST_PLATFORM)")" "$(1)"
endef

define gomodver
Expand Down
Loading
Loading