mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-13 00:49:07 +02:00
* [signal] Add an explicit non-root UBI image variant Keep the existing signal image unchanged while making a separate UBI image available for local certification-readiness checks. Collect the linked Go dependency license terms as portable build inputs, and use SIGINT for the existing graceful stop handler. * [signal] Wire the UBI variant into GoReleaser Build the additive UBI signal variant through the existing release pipeline instead of relying on local Docker commands. Stage its licenses in the per-image build context and restrict the variant to validated AMD64 without changing existing image tags. * [signal] Align the UBI image with the other UBI variants Main now ships UBI images for the client, combined server and proxy with a shared license collector and a common shape, so the signal variant should look the same to reviewers and to Red Hat certification. Signal also listens on port 80 by default, which an arbitrary non-root UID cannot bind on OpenShift or Podman. Use release_files/collect-licenses.sh instead of a signal-only copy, build for amd64 and arm64 like the other UBI entries, and run as 1000:0 with a group-writable /var/lib/netbird that also holds Let's Encrypt data. Default NB_PORT to the legacy gRPC port 10000 so the image starts unprivileged and serves a single listener. * [infrastructure] Certify the signal UBI image on release The signal UBI image is published with every release, but only images listed in the certification workflow reach the Red Hat catalog, so it would otherwise need a manual run each time. Add signal to the dispatch options and the component list, using the same -ubi tag suffix as .goreleaser.yaml. The run reads its Partner Connect ID from REDHAT_CERT_ID_SIGNAL. * [signal] Answer Let's Encrypt challenges on the UBI image's port The challenge listener defaults to :443, which a non-root UID cannot bind on OpenShift or Podman, so Let's Encrypt could not work in the UBI image. Now that the listener address is configurable, default it to empty so the TLS listener on port 10000 answers the challenges itself when public port 443 is forwarded to it.
204 lines
8.1 KiB
YAML
204 lines
8.1 KiB
YAML
name: Red Hat Certification
|
|
|
|
# Certify published UBI images in the Red Hat Ecosystem Catalog. Called by
|
|
# release.yml on stable tags, or run by hand to (re)certify any released
|
|
# version. preflight submits every architecture of an image's manifest list
|
|
# to Pyxis; auto-publish on the component makes it public once certified.
|
|
#
|
|
# Each component's Partner Connect ID comes from the REDHAT_CERT_ID_<NAME>
|
|
# repository variable, e.g. REDHAT_CERT_ID_CLIENT_ROOTLESS. The run fails
|
|
# before certifying anything if a selected component's variable is not set.
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
component:
|
|
type: string
|
|
required: true
|
|
version:
|
|
type: string
|
|
required: true
|
|
secrets:
|
|
PYXIS_API_TOKEN:
|
|
required: true
|
|
workflow_dispatch:
|
|
inputs:
|
|
component:
|
|
description: "Component to certify"
|
|
type: choice
|
|
required: true
|
|
default: all
|
|
options:
|
|
- all
|
|
- client-rootless
|
|
- reverse-proxy
|
|
- netbird-server
|
|
- signal
|
|
version:
|
|
description: "Released version, e.g. v0.80.0"
|
|
type: string
|
|
required: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
resolve:
|
|
name: Resolve components
|
|
runs-on: ubuntu-24.04
|
|
outputs:
|
|
version: ${{ steps.resolve.outputs.version }}
|
|
matrix: ${{ steps.resolve.outputs.matrix }}
|
|
steps:
|
|
- name: Resolve components and images
|
|
id: resolve
|
|
env:
|
|
COMPONENT: ${{ inputs.component }}
|
|
INPUT_VERSION: ${{ inputs.version }}
|
|
REPO_VARS: ${{ toJSON(vars) }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${INPUT_VERSION#v}"
|
|
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "::error::Only stable x.y.z versions are certified, got '${INPUT_VERSION}'"
|
|
exit 1
|
|
fi
|
|
# name, image repository, tag suffix (must match .goreleaser.yaml).
|
|
# Keep the names in sync with the workflow_dispatch options above.
|
|
components=(
|
|
"client-rootless ghcr.io/netbirdio/netbird -rootless-ubi"
|
|
"reverse-proxy ghcr.io/netbirdio/reverse-proxy -ubi"
|
|
"netbird-server ghcr.io/netbirdio/netbird-server -ubi"
|
|
"signal ghcr.io/netbirdio/signal -ubi"
|
|
)
|
|
matrix="[]"
|
|
missing=()
|
|
for c in "${components[@]}"; do
|
|
read -r name repo suffix <<< "$c"
|
|
[[ "$COMPONENT" == all || "$COMPONENT" == "$name" ]] || continue
|
|
var="REDHAT_CERT_ID_${name^^}"; var="${var//-/_}"
|
|
id="$(jq -r --arg v "$var" '.[$v] // empty' <<< "$REPO_VARS")"
|
|
if [[ -z "$id" ]]; then
|
|
missing+=("$var")
|
|
continue
|
|
fi
|
|
matrix="$(jq -c --arg n "$name" --arg t "${version}${suffix}" --arg r "${repo}:${version}${suffix}" --arg i "$id" \
|
|
'. + [{component: $n, tag: $t, ref: $r, component_id: $i}]' <<< "$matrix")"
|
|
done
|
|
if (( ${#missing[@]} )); then
|
|
echo "::error::Set these repository variables to the Partner Connect component IDs: ${missing[*]}"
|
|
exit 1
|
|
fi
|
|
if [[ "$matrix" == "[]" ]]; then
|
|
echo "::error::No component to certify for '${COMPONENT}'"
|
|
exit 1
|
|
fi
|
|
echo "Components to certify: ${matrix}"
|
|
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
|
echo "matrix=${matrix}" >> "$GITHUB_OUTPUT"
|
|
|
|
certify:
|
|
name: "Certify ${{ matrix.component }} UBI image"
|
|
needs: resolve
|
|
runs-on: ubuntu-24.04
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include: ${{ fromJSON(needs.resolve.outputs.matrix) }}
|
|
env:
|
|
PREFLIGHT_VERSION: "1.21.0"
|
|
# sha256 of preflight-linux-amd64 from the 1.21.0 GitHub release.
|
|
# Red Hat publishes no checksum file, so the value is pinned here.
|
|
PREFLIGHT_SHA256: "5e653135503c72f8702bbe31d7643197d12937c68086879133dd6b9650a9a449"
|
|
steps:
|
|
- name: Verify the multi-arch image is on ghcr.io
|
|
env:
|
|
IMAGE_REF: ${{ matrix.ref }}
|
|
run: |
|
|
set -euo pipefail
|
|
docker buildx imagetools inspect "$IMAGE_REF" --raw > manifest.json
|
|
for arch in amd64 arm64; do
|
|
if ! jq -e --arg a "$arch" '.manifests[] | select(.platform.architecture == $a)' manifest.json > /dev/null; then
|
|
echo "::error::${IMAGE_REF} has no ${arch} manifest"
|
|
exit 1
|
|
fi
|
|
done
|
|
echo "Manifest list for ${IMAGE_REF}:"
|
|
jq -r '.manifests[] | "\(.platform.os)/\(.platform.architecture) \(.digest)"' manifest.json
|
|
|
|
- name: Install preflight
|
|
run: |
|
|
set -euo pipefail
|
|
curl -fsSL --proto '=https' --proto-redir '=https' -o preflight \
|
|
"https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/${PREFLIGHT_VERSION}/preflight-linux-amd64"
|
|
echo "${PREFLIGHT_SHA256} preflight" | sha256sum -c -
|
|
chmod +x preflight
|
|
./preflight --version
|
|
|
|
- name: Run preflight checks and submit to Red Hat
|
|
env:
|
|
IMAGE_REF: ${{ matrix.ref }}
|
|
PFLT_PYXIS_API_TOKEN: ${{ secrets.PYXIS_API_TOKEN }}
|
|
PFLT_CERTIFICATION_COMPONENT_ID: ${{ matrix.component_id }}
|
|
PFLT_ARTIFACTS: artifacts
|
|
PFLT_LOGFILE: artifacts/preflight.log
|
|
PFLT_LOGLEVEL: info
|
|
PFLT_JUNIT: "true"
|
|
run: |
|
|
set -euo pipefail
|
|
# No --platform: preflight walks the manifest list and submits every
|
|
# architecture in one run, grouped under one manifest-list digest.
|
|
# preflight does not create the PFLT_LOGFILE directory, and --submit
|
|
# fails if the log file is missing.
|
|
mkdir -p artifacts
|
|
./preflight check container "$IMAGE_REF" --submit
|
|
|
|
- name: Fail if any check did not pass
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob globstar
|
|
results=(artifacts/**/results.json)
|
|
if [[ ${#results[@]} -eq 0 ]]; then
|
|
echo "::error::preflight produced no results.json"
|
|
exit 1
|
|
fi
|
|
status=0
|
|
for f in "${results[@]}"; do
|
|
arch="$(basename "$(dirname "$f")")"
|
|
passed="$(jq -r '.passed' "$f")"
|
|
failed="$(jq -r '[.results.failed[]?.name] | join(", ")' "$f")"
|
|
echo "${arch}: passed=${passed} ${failed:+failed checks: ${failed}}"
|
|
[[ "$passed" == "true" ]] || status=1
|
|
done
|
|
exit $status
|
|
|
|
- name: Upload preflight artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: redhat-preflight-${{ matrix.component }}-${{ needs.resolve.outputs.version }}
|
|
path: artifacts/
|
|
retention-days: 30
|
|
|
|
- name: Wait for Pyxis to mark both architectures certified
|
|
env:
|
|
TAG: ${{ matrix.tag }}
|
|
COMPONENT_ID: ${{ matrix.component_id }}
|
|
PFLT_PYXIS_API_TOKEN: ${{ secrets.PYXIS_API_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Filter on the tag server-side so older versions are found past the first page.
|
|
url="https://catalog.redhat.com/api/containers/v1/projects/certification/id/${COMPONENT_ID}/images?filter=repositories.tags.name==${TAG}&page_size=100"
|
|
for attempt in $(seq 1 20); do
|
|
certified="$(curl -fsS --proto '=https' --proto-redir '=https' -H "X-API-KEY: ${PFLT_PYXIS_API_TOKEN}" "$url" \
|
|
| jq -r --arg t "$TAG" '[.data[] | select(.repositories[]?.tags[]?.name == $t) | select(.certified == true) | .architecture] | unique | join(",")')"
|
|
echo "attempt ${attempt}: certified architectures for ${TAG}: ${certified:-none}"
|
|
if [[ "$certified" == "amd64,arm64" ]]; then
|
|
echo "Both architectures certified. Auto-publish is enabled on the component, so the catalog updates on its own."
|
|
exit 0
|
|
fi
|
|
sleep 30
|
|
done
|
|
echo "::error::Pyxis has not marked both architectures certified after 10 minutes. Check https://connect.redhat.com/component/view/${COMPONENT_ID}/images"
|
|
exit 1
|