diff --git a/.bootstrap/kyverno/up.sh b/.bootstrap/kyverno/up.sh new file mode 100755 index 0000000..7008a22 --- /dev/null +++ b/.bootstrap/kyverno/up.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +set -euo pipefail + +# Change to platform cluster +if [[ "$(kubectl config current-context)" != "kind-platform" ]]; then + kubectl config use-context kind-platform || { + echo "Failed to switch context to kind-platform" + exit 1 + } +fi + +NS=kyverno-system +KYVERNO_CHART=kyverno/kyverno +POLICIES_DIR=./kyverno +TIMEOUT=120 +INTERVAL=5 +ELAPSED=0 + +echo "Adding Kyverno Helm repository..." +helm repo add kyverno https://kyverno.github.io/kyverno/ 2>/dev/null || true +helm repo update + +echo "Installing or upgrading Kyverno via Helm..." +helm upgrade --install kyverno "$KYVERNO_CHART" \ + --namespace "$NS" \ + --create-namespace + +echo "Waiting for Kyverno webhook to be ready..." +kubectl wait deployment/kyverno-admission-controller \ + -n "$NS" \ + --for=condition=Available=True \ + --timeout=${TIMEOUT}s + +echo "Ensuring all Kyverno pods are ready..." +while true; do + READY=$(kubectl get pods -n "$NS" -o jsonpath='{.items[*].status.containerStatuses[*].ready}' | tr " " "\n" | grep -c false || true) + if [[ "$READY" -eq 0 ]]; then + break + fi + if [[ "$ELAPSED" -ge "$TIMEOUT" ]]; then + echo "Timeout reached. Kyverno pods are not ready." + exit 1 + fi + echo "Waiting for Kyverno pods to become ready..." + sleep $INTERVAL + ELAPSED=$((ELAPSED + INTERVAL)) +done + +echo "All Kyverno pods are ready!" + +apply_kyverno_policies() { + local retries=20 + local delay=3 + + for i in $(seq 1 $retries); do + if kubectl apply -f "$POLICIES_DIR" --recursive; then + echo "✅ Kyverno policies applied successfully!" + return 0 + else + echo "⚠️ Failed to apply policies, retrying in ${delay}s... (${i}/${retries})" + sleep $delay + fi + done + + echo "❌ Failed to apply Kyverno policies after ${retries} attempts." + exit 1 +} + +echo "Applying Kyverno policies from $POLICIES_DIR..." +apply_kyverno_policies + +echo "✅ Kyverno setup and policy application completed successfully!" diff --git a/.github/workflows/validate-claims.yaml b/.github/workflows/validate-claims.yaml new file mode 100644 index 0000000..129117e --- /dev/null +++ b/.github/workflows/validate-claims.yaml @@ -0,0 +1,48 @@ +name: Validate Crossplane Claims + +on: + pull_request: + paths: + - "crossplane/claims/**/*.yaml" + - "crossplane/claims/**/*.yml" + - "kyverno/**/*.yaml" + - "kyverno/**/*.yml" + +jobs: + validate-claims: + name: Validate Claims YAML with Kyverno + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Kyverno CLI + uses: kyverno/action-install-cli@v0.2.0 + with: + release: "v1.13.4" + + - name: Check install + run: kyverno version + + - name: Run Kyverno policy checks on claims + run: | + echo "## 🛡️ Kyverno Policy Validation Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + set +e + kyverno apply ./kyverno --resource ./crossplane/claims 2>&1 | tee result.txt + KYVERNO_EXIT_CODE=${PIPESTATUS[0]} + set -e + + cat result.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + if [[ $KYVERNO_EXIT_CODE -ne 0 ]]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "❌ One or more Kyverno policies failed. Please fix the issues above." >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "✅ All policies passed." >> $GITHUB_STEP_SUMMARY + fi diff --git a/kyverno/.gitkeep b/crossplane/claims/.gitkeep similarity index 100% rename from kyverno/.gitkeep rename to crossplane/claims/.gitkeep diff --git a/kyverno/validate-xqueue-fields.yaml b/kyverno/validate-xqueue-fields.yaml new file mode 100644 index 0000000..23319be --- /dev/null +++ b/kyverno/validate-xqueue-fields.yaml @@ -0,0 +1,57 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: validate-xqueue-fields +spec: + validationFailureAction: Enforce + background: true + rules: + - name: deny-invalid-location + match: + resources: + kinds: + - platform.hooli.tech/v1alpha1/XQueueClaim + validate: + message: "Invalid location: only 'EU' or 'US' are allowed in spec.location" + deny: + conditions: + all: + - key: "{{ request.object.spec.location }}" + operator: AllNotIn + value: + - "EU" + - "US" + + - name: deny-invalid-max-message-size + match: + resources: + kinds: + - platform.hooli.tech/v1alpha1/XQueueClaim + validate: + message: "Invalid maxMessageSize: must be between 1024 and 262144 (bytes)" + deny: + conditions: + any: + - key: "{{ request.object.spec.maxMessageSize }}" + operator: GreaterThan + value: 262144 + - key: "{{ request.object.spec.maxMessageSize }}" + operator: LessThan + value: 1024 + + - name: deny-invalid-visibility-timeout + match: + resources: + kinds: + - platform.hooli.tech/v1alpha1/XQueueClaim + validate: + message: "Invalid visibilityTimeoutSeconds: must be between 0 and 43200 (seconds)" + deny: + conditions: + any: + - key: "{{ request.object.spec.visibilityTimeoutSeconds }}" + operator: GreaterThan + value: 43200 + - key: "{{ request.object.spec.visibilityTimeoutSeconds }}" + operator: LessThan + value: 0