Skip to content
Open
Show file tree
Hide file tree
Changes from 15 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
16 changes: 16 additions & 0 deletions .github/actions/setup-go/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Setup Go Environment
description: Sets up the Go toolchain with caching. Repository must already be checked out.

inputs:
go-version:
description: The Go version to use (supports semver ranges e.g. ~1.25.7)
required: true

runs:
using: composite
steps:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
id: go
with:
go-version: ${{ inputs.go-version }}
86 changes: 86 additions & 0 deletions .github/workflows/ci-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: ci-checks

on:
workflow_call:
inputs:
go-version:
description: The Go version to use
required: true
type: string
buildtime-base:
description: The Docker image to use as the Go build base for unit tests
required: true
type: string

permissions:
contents: read

jobs:
# Scans for hidden unicode characters (Trojan Source attacks) before any job that may use secrets
ci-unicode-check:
name: ci-unicode-check
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Check for hidden unicode
# Scope the scan to text-based source files only. Binary files (images, PDFs) contain
# arbitrary byte sequences that trigger false positives in BIDI character detection.
run: npx anti-trojan-source --files='**/*.{go,yml,yaml,md,sh,json,toml,mod,sum,txt,svg,ts,js,cjs,cts,py,xml,conf,lock}'

# Runs Golangci-lint on the source code
ci-go-lint:
name: ci-go-lint
needs: ci-unicode-check
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Go
uses: ./.github/actions/setup-go
with:
go-version: ${{ inputs.go-version }}

- name: Lint kube-router code
run: |
make lint

# Executes Unit Tests
ci-unit-tests:
name: ci-unit-tests
needs: ci-unicode-check
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Go
uses: ./.github/actions/setup-go
with:
go-version: ${{ inputs.go-version }}

- name: Run unit tests for kube-router
run: |
make test-pretty
env:
DOCKER_BUILD_IMAGE: ${{ inputs.buildtime-base }}

# Builds Kube-Router binary
ci-build-kube-router:
name: ci-build-kube-router
needs: ci-unicode-check
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Go
uses: ./.github/actions/setup-go
with:
go-version: ${{ inputs.go-version }}

- name: Build kube-router
run: |
make kube-router
218 changes: 218 additions & 0 deletions .github/workflows/ci-container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
name: ci-container

on:
workflow_call:
inputs:
buildtime-base:
description: The Docker image to use as the Go build base
required: true
type: string
runtime-base:
description: The Docker image to use as the container runtime base
required: true
type: string
secrets:
DOCKERHUB_USERNAME:
required: true
DOCKERHUB_TOKEN:
required: true

permissions:
contents: read
id-token: write
attestations: write

jobs:
# Builds and pushes the container image for branch pushes, PRs, and release tags.
# On tag events, also signs the image and attests a signed SBOM to DockerHub via cosign.
ci-build-container:
name: ci-build-container
runs-on: ubuntu-latest
steps:
# Check out the repository so the Dockerfile and source are available to the build.
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

# Register QEMU emulators so Docker Buildx can build non-native architectures (arm, s390x, etc).
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0

# Create a multi-platform-capable Buildx builder instance on the runner.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

# Authenticate to DockerHub so subsequent push steps and cosign attestation pushes succeed.
- name: Login to DockerHub
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

# Install cosign once for all tag-based signing and attestation steps below.
# Uses keyless signing — no private key required; identity is proven via the GitHub Actions
# OIDC token issued to this workflow, rooted in Sigstore Fulcio and logged in Rekor.
- name: Install cosign
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0

# Parse the branch name from GITHUB_REF for use as the image tag on non-tag branch pushes.
- name: Extract branch from github ref - New Push
if: ${{ startsWith(github.ref, 'refs/tags/v') != true && github.event_name != 'pull_request' }}
shell: bash
run: echo "branch=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT"
id: extract_branch

# Parse the version tag from GITHUB_REF for use as the image tag on tag pushes.
- name: Extract tag from github ref - New Release
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
shell: bash
run: echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
id: extract_tag

# Build and push a multi-arch image tagged with the branch name on direct pushes to tracked
# branches (master, v*, prep-v*). Not run on PRs or tag pushes.
- name: Build and push - New Push
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
if: ${{ startsWith(github.ref, 'refs/tags/v') != true && github.event_name != 'pull_request' }}
with:
context: .
platforms: |
linux/amd64
linux/arm64
linux/arm/v7
linux/s390x
linux/ppc64le
push: true
build-args: |
BUILDTIME_BASE=${{ inputs.buildtime-base }}
RUNTIME_BASE=${{ inputs.runtime-base }}
tags: cloudnativelabs/kube-router-git:${{ steps.extract_branch.outputs.branch }}

# Build and push a single-arch (amd64) image tagged with the PR number for pull requests.
# Multi-arch is skipped here as it adds 30+ minutes to PR feedback time.
- name: Build and push - New PR
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
if: github.event_name == 'pull_request'
with:
context: .
platforms: linux/amd64
push: true
provenance: false
build-args: |
BUILDTIME_BASE=${{ inputs.buildtime-base }}
RUNTIME_BASE=${{ inputs.runtime-base }}
tags: cloudnativelabs/kube-router-git:PR-${{ github.event.pull_request.number }}

# -----------------------------------------------------------------------------------------
# Release Candidate tag (e.g. v2.8.0-rc1): build, sign, generate SBOM, and attest to registry.
# The `latest` tag is NOT updated for release candidates.
# -----------------------------------------------------------------------------------------

# Build and push the multi-arch release candidate image. The step id captures the digest
# so subsequent signing and attestation steps can reference the exact immutable image.
- name: Build and push - New Tag (Release Candidate)
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
id: push_rc
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-rc') }}
with:
context: .
platforms: |
linux/amd64
linux/arm64
linux/arm/v7
linux/s390x
linux/ppc64le
push: true
build-args: |
BUILDTIME_BASE=${{ inputs.buildtime-base }}
RUNTIME_BASE=${{ inputs.runtime-base }}
tags: |
cloudnativelabs/kube-router:${{ steps.extract_tag.outputs.tag }}

# Sign the RC image digest with cosign (keyless). The signature is pushed to DockerHub as a
# sibling OCI artifact and the signing event is recorded in the Rekor transparency log.
# Verify with: cosign verify --certificate-identity-regexp "https://github.com/cloudnativelabs/.*"
# --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
# cloudnativelabs/kube-router:<rc-tag>
- name: Sign container image - New Tag (Release Candidate)
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-rc') }}
run: cosign sign --yes cloudnativelabs/kube-router@${{ steps.push_rc.outputs.digest }}

# Generate an SPDX-JSON SBOM for the RC image using Syft. The SBOM is written to a local
# file for the attestation step and also uploaded as a workflow artifact.
- name: Generate SBOM for container image - New Tag (Release Candidate)
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
id: sbom_rc
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-rc') }}
with:
image: cloudnativelabs/kube-router@${{ steps.push_rc.outputs.digest }}
format: spdx-json
artifact-name: kube-router-${{ github.ref_name }}-image-sbom.spdx.json
output-file: ./container-sbom-rc.spdx.json

# Wrap the SBOM in a signed in-toto attestation and push it to DockerHub alongside the image.
# Users can retrieve and verify it with:
# cosign verify-attestation --type spdxjson
# --certificate-identity-regexp "https://github.com/cloudnativelabs/.*"
# --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
# cloudnativelabs/kube-router:<rc-tag> | jq -r '.payload' | base64 -d | jq '.predicate'
- name: Attest SBOM to container image - New Tag (Release Candidate)
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-rc') }}
run: |
cosign attest --yes \
--predicate ./container-sbom-rc.spdx.json \
--type spdxjson \
cloudnativelabs/kube-router@${{ steps.push_rc.outputs.digest }}

# -----------------------------------------------------------------------------------------
# Production release tag (e.g. v2.8.0): build, sign, generate SBOM, and attest to registry.
# Both the versioned tag and `latest` are updated.
# -----------------------------------------------------------------------------------------

# Build and push the multi-arch production release image. Updates both the versioned tag and
# `latest`. The step id captures the digest for signing and attestation.
- name: Build and push - New Tag
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
id: push_release
if: ${{ startsWith(github.ref, 'refs/tags/v') && ! contains(github.ref, '-rc') }}
with:
context: .
platforms: |
linux/amd64
linux/arm64
linux/arm/v7
linux/s390x
linux/ppc64le
push: true
build-args: |
BUILDTIME_BASE=${{ inputs.buildtime-base }}
RUNTIME_BASE=${{ inputs.runtime-base }}
tags: |
cloudnativelabs/kube-router:${{ steps.extract_tag.outputs.tag }}
cloudnativelabs/kube-router:latest

# Sign the production release image digest with cosign (keyless). Same verification command
# as the RC step above, using the production release tag.
- name: Sign container image - New Tag
if: ${{ startsWith(github.ref, 'refs/tags/v') && ! contains(github.ref, '-rc') }}
run: cosign sign --yes cloudnativelabs/kube-router@${{ steps.push_release.outputs.digest }}

# Generate an SPDX-JSON SBOM for the production release image using Syft.
- name: Generate SBOM for container image - New Tag
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
id: sbom_release
if: ${{ startsWith(github.ref, 'refs/tags/v') && ! contains(github.ref, '-rc') }}
with:
image: cloudnativelabs/kube-router@${{ steps.push_release.outputs.digest }}
format: spdx-json
artifact-name: kube-router-${{ github.ref_name }}-image-sbom.spdx.json
output-file: ./container-sbom-release.spdx.json

# Wrap the SBOM in a signed in-toto attestation and push it to DockerHub alongside the image.
- name: Attest SBOM to container image - New Tag
if: ${{ startsWith(github.ref, 'refs/tags/v') && ! contains(github.ref, '-rc') }}
run: |
cosign attest --yes \
--predicate ./container-sbom-release.spdx.json \
--type spdxjson \
cloudnativelabs/kube-router@${{ steps.push_release.outputs.digest }}
61 changes: 61 additions & 0 deletions .github/workflows/ci-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: ci-release

on:
workflow_call:
inputs:
go-version:
description: The Go version to use
required: true
type: string

permissions:
contents: read

jobs:
# Runs GoReleaser to publish binaries, generate a binary SBOM, and produce SLSA provenance
ci-goreleaser-tag:
name: ci-goreleaser-tag
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
# Check out the repository so GoReleaser has access to the full git history and source.
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

# Install the Go toolchain at the pinned version with module caching enabled.
- name: Set up Go
uses: ./.github/actions/setup-go
with:
go-version: ${{ inputs.go-version }}

# Build multi-arch binaries, create archives, generate checksums, and publish a GitHub release.
# The release is created as a draft (see .goreleaser.yml) pending manual review before publish.
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with:
version: "~> v2"
distribution: goreleaser
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Generate a CycloneDX SBOM covering all release binaries in dist/ and attach it
# to the GitHub release as a downloadable asset for binary consumers.
- name: Generate SBOM for release binaries
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
path: ./dist
format: cyclonedx-json
artifact-name: kube-router-${{ github.ref_name }}-sbom.cyclonedx.json
upload-release-assets: true

# Generate SLSA Build Level 2 provenance for the release binaries.
# Uses GitHub's native attestation (first-party action, GA since June 2024).
# Consumers can verify with: gh attestation verify <binary> --repo cloudnativelabs/kube-router
- name: Generate SLSA provenance for release binaries
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
with:
subject-checksums: ./dist/checksums.txt
Loading
Loading