#!/bin/bash # setup-buildkit-client.sh — declarative apply of lian-build's mTLS identity # at the buildkit_lite CA Issuer. # # Source of truth: manifests/buildkit-client-cert.yaml.tmpl (this directory). # Reads NCL-derived config from environment OR from component.json if present. # # Usage: # setup-buildkit-client.sh # apply (idempotent) # setup-buildkit-client.sh export # apply then dump ca/tls.crt/tls.key to # setup-buildkit-client.sh revoke # delete the Certificate (and Secret) # # Cluster access: # The buildkit-ca-issuer typically lives on a remote cluster (e.g. libre-daoshi) # whose API server is not directly reachable from the operator workstation. # This script supports two modes: # (1) Local kubectl — set KUBECONFIG and rely on local 'kubectl' # (2) SSH+remote kubectl — set BUILDKIT_REMOTE_HOST=root@libre-daoshi-0 to run # 'k0s kubectl' over ssh on the node itself set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TMPL="$SCRIPT_DIR/manifests/buildkit-client-cert.yaml.tmpl" # Cluster access wrapper. When BUILDKIT_REMOTE_HOST is set, exec kubectl through # ssh on the node using 'k0s kubectl' (matches the daoshi single-node k0s layout). # Both stdin (manifest piping) and stdout (json/jsonpath output) flow transparently. BUILDKIT_REMOTE_HOST="${BUILDKIT_REMOTE_HOST:-}" BUILDKIT_REMOTE_KCTL="${BUILDKIT_REMOTE_KCTL:-k0s kubectl}" _kctl() { if [ -n "$BUILDKIT_REMOTE_HOST" ]; then # printf %q quotes each arg safely so jsonpath braces/escapes survive the # ssh-shell pass-through (otherwise {.data.ca\.crt} gets expanded as glob). local quoted="" local arg for arg in "$@"; do quoted+=$(printf '%q ' "$arg") done ssh -o BatchMode=yes "$BUILDKIT_REMOTE_HOST" "$BUILDKIT_REMOTE_KCTL $quoted" else kubectl "$@" fi } # Stdin-piping variant — for `envsubst < tmpl | _kctl_stdin apply -f -`. _kctl_stdin() { if [ -n "$BUILDKIT_REMOTE_HOST" ]; then local quoted="" local arg for arg in "$@"; do quoted+=$(printf '%q ' "$arg") done ssh -o BatchMode=yes "$BUILDKIT_REMOTE_HOST" "$BUILDKIT_REMOTE_KCTL $quoted" else kubectl "$@" fi } # Read overrides from environment, falling back to component.json (when present) # for the values configured via NCL. _load_from_component_json() { local key="$1" local default="$2" local f="$SCRIPT_DIR/component.json" if [ -f "$f" ] && command -v jq >/dev/null 2>&1; then local v v=$(jq -r --arg k "$key" '.buildkit_client[$k] // empty' "$f" 2>/dev/null || true) if [ -n "$v" ] && [ "$v" != "null" ]; then printf '%s' "$v" return fi fi printf '%s' "$default" } BUILDKIT_CLIENT_NAME="${BUILDKIT_CLIENT_NAME:-$(_load_from_component_json client_name "lian-build")}" BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE="${BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE:-$(_load_from_component_json ca_issuer_namespace "build-system")}" BUILDKIT_CLIENT_CA_ISSUER_NAME="${BUILDKIT_CLIENT_CA_ISSUER_NAME:-$(_load_from_component_json ca_issuer_name "buildkit-ca-issuer")}" BUILDKIT_CLIENT_DURATION="${BUILDKIT_CLIENT_DURATION:-$(_load_from_component_json duration "2160h")}" BUILDKIT_CLIENT_RENEW_BEFORE="${BUILDKIT_CLIENT_RENEW_BEFORE:-$(_load_from_component_json renew_before "360h")}" export BUILDKIT_CLIENT_NAME BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE BUILDKIT_CLIENT_CA_ISSUER_NAME \ BUILDKIT_CLIENT_DURATION BUILDKIT_CLIENT_RENEW_BEFORE SECRET_NAME="buildkit-client-${BUILDKIT_CLIENT_NAME}-tls" CERT_NAME="buildkit-client-${BUILDKIT_CLIENT_NAME}" _apply() { if [ ! -f "$TMPL" ]; then echo "ERROR: template missing: $TMPL" >&2 exit 1 fi if ! _kctl get issuer "$BUILDKIT_CLIENT_CA_ISSUER_NAME" \ --namespace "$BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE" >/dev/null 2>&1; then echo "ERROR: Issuer $BUILDKIT_CLIENT_CA_ISSUER_NAME not found in $BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE" >&2 echo "Hint: enable mtls.cert_manager on buildkit_lite first" >&2 exit 1 fi envsubst < "$TMPL" | _kctl_stdin apply -f - echo "Applied Certificate $CERT_NAME in namespace $BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE" } _wait_for_secret() { local timeout=60 local elapsed=0 while [ "$elapsed" -lt "$timeout" ]; do if _kctl get secret "$SECRET_NAME" \ --namespace "$BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE" >/dev/null 2>&1; then return 0 fi sleep 2 elapsed=$((elapsed + 2)) done echo "ERROR: secret $SECRET_NAME not issued within ${timeout}s" >&2 return 1 } _export() { local out_dir="${1:-${HOME}/.lian-build/buildkit-client}" out_dir="${out_dir/#\~/$HOME}" mkdir -p "$out_dir" _wait_for_secret _kctl get secret "$SECRET_NAME" --namespace "$BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE" \ -o jsonpath='{.data.ca\.crt}' | base64 -d > "$out_dir/ca.crt" _kctl get secret "$SECRET_NAME" --namespace "$BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE" \ -o jsonpath='{.data.tls\.crt}' | base64 -d > "$out_dir/tls.crt" _kctl get secret "$SECRET_NAME" --namespace "$BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE" \ -o jsonpath='{.data.tls\.key}' | base64 -d > "$out_dir/tls.key" chmod 0600 "$out_dir/tls.key" echo "Exported client cert material to $out_dir" } _revoke() { _kctl delete certificate "$CERT_NAME" \ --namespace "$BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE" --ignore-not-found _kctl delete secret "$SECRET_NAME" \ --namespace "$BUILDKIT_CLIENT_CA_ISSUER_NAMESPACE" --ignore-not-found echo "Revoked $CERT_NAME" } case "${1:-apply}" in apply) _apply ;; export) _apply; _export "${2:-}" ;; export-only) _export "${2:-}" ;; revoke) _revoke ;; *) echo "Usage: $(basename "$0") [apply|export |export-only |revoke]" >&2 exit 1 ;; esac