diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 7ee92ce..688cae3 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -45,6 +45,104 @@ jobs: - name: Setup kubectl uses: azure/setup-kubectl@v4 + - name: Get kubeconfig + run: | + set -euo pipefail + echo "=== Runner environment diagnostic ===" + echo "HOME=${HOME:-}" + echo "KUBECONFIG=${KUBECONFIG:-}" + echo "ACTIONS_KUBECONFIG=${ACTIONS_KUBECONFIG:-}" + echo "RUNNER_CONFIG=${RUNNER_CONFIG:-}" + echo "RUNNER_CONFIG_DIR=${RUNNER_CONFIG_DIR:-}" + echo "" + echo "=== Checking known kubeconfig locations ===" + for path in /runner/config /home/runner/.kube/config "${HOME:-}/.kube/config" "${HOME:-}/.kube"; do + if [ -f "$path" ]; then + echo "FOUND kubeconfig at: $path" + elif [ -d "$path" ]; then + echo "DIR exists at: $path, contents:" + ls -la "$path" 2>&1 || echo " (cannot list)" + else + echo "NOT FOUND: $path" + fi + done + echo "" + echo "=== In-cluster service account check ===" + in_cluster=false + if [ -f /var/run/secrets/kubernetes.io/serviceaccount/token ]; then + echo "Service account token present — in-cluster mode available" + echo "KUBERNETES_SERVICE_HOST=${KUBERNETES_SERVICE_HOST:-}" + echo "KUBERNETES_SERVICE_PORT=${KUBERNETES_SERVICE_PORT:-}" + in_cluster=true + else + echo "No service account token at /var/run/secrets/kubernetes.io/serviceaccount/" + fi + echo "" + if [ -f /runner/config ]; then + echo "KUBECONFIG=/runner/config" >> "$GITHUB_ENV" + echo "Using kubeconfig from /runner/config" + elif [ -f /home/runner/.kube/config ]; then + echo "KUBECONFIG=/home/runner/.kube/config" >> "$GITHUB_ENV" + echo "Using kubeconfig from /home/runner/.kube/config" + elif [ -f "${HOME:-}/.kube/config" ]; then + echo "KUBECONFIG=${HOME:-}/.kube/config" >> "$GITHUB_ENV" + echo "Using kubeconfig from HOME" + elif [ "$in_cluster" = true ]; then + echo "No static kubeconfig found — generating in-cluster kubeconfig" + KUBECFG_DIR="${HOME:-}/.kube" + mkdir -p "$KUBECFG_DIR" + kubectl config set-cluster in-cluster \ + --server="https://${KUBERNETES_SERVICE_HOST:-kubernetes.default.svc}:${KUBERNETES_SERVICE_PORT:-443}" \ + --certificate-authority=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \ + --embed-certs=true \ + --kubeconfig="$KUBECFG_DIR/config" 2>&1 + kubectl config set-credentials in-cluster \ + --token="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ + --kubeconfig="$KUBECFG_DIR/config" 2>&1 + kubectl config set-context in-cluster \ + --cluster=in-cluster \ + --user=in-cluster \ + --kubeconfig="$KUBECFG_DIR/config" 2>&1 + kubectl config use-context in-cluster \ + --kubeconfig="$KUBECFG_DIR/config" 2>&1 + echo "KUBECONFIG=$KUBECFG_DIR/config" >> "$GITHUB_ENV" + echo "Generated in-cluster kubeconfig at $KUBECFG_DIR/config" + else + echo "::error::No kubeconfig found in /runner/config, /home/runner/.kube/config, HOME, or in-cluster service account" + exit 1 + fi + + - name: Apply RBAC for E2E pipeline + run: | + set -x + kubectl apply -f deployment/e2e-ci-runner-rbac.yaml --dry-run=server 2>&1 || true + kubectl apply -f deployment/e2e-ci-runner-rbac.yaml 2>&1 + echo "exit code: $?" + echo "Waiting for RBAC propagation..." + sleep 5 + echo "Verifying RBAC resources were created..." + kubectl get role e2e-ci-runner -n headlamp-dev 2>&1 | tail -3 + kubectl get role e2e-ci-runner-polaris -n headlamp-dev 2>&1 | tail -3 + kubectl get rolebinding e2e-ci-runner-binding -n headlamp-dev 2>&1 | tail -3 + set +x + + - name: Apply Polaris dashboard RBAC + run: kubectl apply -f deployment/polaris-rbac.yaml + + - name: RBAC pre-flight check + run: | + echo "Checking RBAC resources..." + MISSING=0 + kubectl get role polaris-dashboard-proxy-reader -n polaris -o name >/dev/null 2>&1 || MISSING=1 + kubectl get rolebinding polaris-dashboard-proxy-reader -n polaris -o name >/dev/null 2>&1 || MISSING=1 + kubectl auth can-i delete configmaps -n "$E2E_NAMESPACE" 2>/dev/null || MISSING=1 + if [ "$MISSING" -eq 0 ]; then + echo "RBAC pre-flight check passed." + else + echo "::error::RBAC pre-flight check failed. Missing required permissions." + exit 1 + fi + - name: Install dependencies run: npm ci diff --git a/SPEC-PRI-324.md b/SPEC-PRI-324.md new file mode 100644 index 0000000..108644f --- /dev/null +++ b/SPEC-PRI-324.md @@ -0,0 +1,98 @@ +# PRI-324 Spec: Make E2E Workflow Self-Sufficient with RBAC + +## Context + +PR #123 introduced an RBAC pre-flight check to the E2E workflow. QA (Nancy, acting as QA) verified the "fails fast without RBAC" path works, but found that the "with RBAC passes" path had no green CI evidence — the workflow did not apply RBAC before the pre-flight check. + +PR #131 attempted to fix this by adding `kubectl apply` steps and extending the CI runner RBAC, but its merge commit (739db6fe) was reverted by the next commit on main (aa1db921) due to a vulnerability fix PR (#128). + +The current E2E workflow on `main` lacks the RBAC apply steps and CI runner permissions needed to make the pre-flight check meaningful. + +## Required Changes + +### 1. `.github/workflows/e2e.yaml` + +Add between the "Setup kubectl" and "Install dependencies" steps: + +```yaml + - name: Apply RBAC for E2E pipeline + run: | + set -x + kubectl apply -f deployment/e2e-ci-runner-rbac.yaml --dry-run=server 2>&1 || true + kubectl apply -f deployment/e2e-ci-runner-rbac.yaml 2>&1 + echo "exit code: $?" + echo "Waiting for RBAC propagation..." + sleep 5 + echo "Verifying CI runner permissions..." + kubectl auth can-i create roles -n headlamp-dev --as="system:serviceaccount:arc-runners:runners-privilegedescalation-gha-rs-no-permission" 2>&1 || { echo "::error::CI runner still lacks roles permission after propagation wait"; exit 1; } + set +x + + - name: Apply Polaris dashboard RBAC + run: kubectl apply -f deployment/polaris-rbac.yaml + + - name: RBAC pre-flight check + run: | + echo "Checking RBAC resources..." + MISSING=0 + kubectl get role polaris-dashboard-proxy-reader -n polaris -o name >/dev/null 2>&1 || MISSING=1 + kubectl get rolebinding polaris-dashboard-proxy-reader -n polaris -o name >/dev/null 2>&1 || MISSING=1 + kubectl auth can-i delete configmaps -n "$E2E_NAMESPACE" --quiet 2>/dev/null || MISSING=1 + if [ "$MISSING" -eq 0 ]; then + echo "RBAC pre-flight check passed." + else + echo "::error::RBAC pre-flight check failed. Missing required permissions." + exit 1 + fi +``` + +### 2. `deployment/e2e-ci-runner-rbac.yaml` + +Add a new Role + RoleBinding for the `polaris` namespace (from PR #131): + +```yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: e2e-ci-runner-polaris + namespace: polaris +rules: + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings"] + verbs: ["get", "list", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: e2e-ci-runner-polaris + namespace: polaris +subjects: + - kind: ServiceAccount + name: runners-privilegedescalation-gha-rs-no-permission + namespace: arc-runners +roleRef: + kind: Role + name: e2e-ci-runner-polaris + apiGroup: rbac.authorization.k8s.io +``` + +And add to the existing `e2e-ci-runner` Role in the `headlamp-dev` namespace: +```yaml + # Apply Polaris dashboard RBAC in the polaris namespace + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings"] + verbs: ["get", "list", "create", "update", "patch", "delete"] +``` + +## Acceptance Criteria + +- [ ] Workflow applies `deployment/e2e-ci-runner-rbac.yaml` before the pre-flight check +- [ ] Workflow applies `deployment/polaris-rbac.yaml` before the pre-flight check +- [ ] CI runner has RBAC to apply the manifests (added via new Role+RoleBinding in polaris namespace) +- [ ] E2E pipeline passes on the PR branch (proof of green path) +- [ ] `kubectl get … --quiet` flag removed (QA nit) +- [ ] `MISSING_ROLE`/`MISSING_ROLEBINDING` collapsed to single `MISSING` flag (QA nit) + +## Definition of Done + +PR #123 QA changes-requested are addressed: the workflow is self-sufficient (applies its own RBAC), the green path is demonstrated, and QA review is re-requested. diff --git a/deployment/e2e-ci-runner-rbac.yaml b/deployment/e2e-ci-runner-rbac.yaml index e6bf4ff..069c5ee 100644 --- a/deployment/e2e-ci-runner-rbac.yaml +++ b/deployment/e2e-ci-runner-rbac.yaml @@ -30,6 +30,34 @@ rules: - apiGroups: [""] resources: ["serviceaccounts/token"] verbs: ["create"] + # Apply Polaris dashboard RBAC in the polaris namespace + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings"] + verbs: ["get", "list", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: e2e-ci-runner-polaris + namespace: polaris +rules: + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings"] + verbs: ["get", "list", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: e2e-ci-runner-polaris + namespace: polaris +subjects: + - kind: ServiceAccount + name: runners-privilegedescalation-gha-rs-no-permission + namespace: arc-runners +roleRef: + kind: Role + name: e2e-ci-runner-polaris + apiGroup: rbac.authorization.k8s.io --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding