diff --git a/.checkov.yaml b/.checkov.yaml
new file mode 100644
index 0000000..cdb21df
--- /dev/null
+++ b/.checkov.yaml
@@ -0,0 +1,8 @@
+soft-fail: false
+quiet: true
+compact: true
+framework:
+ - all
+skip-check:
+ - CKV_K8S_21 # Default namespace usage
+ - CKV_K8S_43 # Image tag validation
diff --git a/.gitea/workflows/best-practices.yaml b/.gitea/workflows/best-practices.yaml
new file mode 100644
index 0000000..96da358
--- /dev/null
+++ b/.gitea/workflows/best-practices.yaml
@@ -0,0 +1,163 @@
+name: Best Practices
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ kube-score:
+ name: Kube-score Analysis
+ runs-on: ubuntu-latest
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install kubectl and kube-score
+ run: |
+ # Install kubectl
+ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
+ chmod +x kubectl
+ mv kubectl /usr/local/bin/
+
+ # Install kube-score
+ wget https://github.com/zegl/kube-score/releases/download/v1.18.0/kube-score_1.18.0_linux_amd64.tar.gz
+ tar -xzf kube-score_1.18.0_linux_amd64.tar.gz
+ chmod +x kube-score
+ mv kube-score /usr/local/bin/
+
+ - name: Run kube-score
+ run: |
+ if [ -f "kustomization.yaml" ]; then
+ kubectl kustomize . | kube-score score - \
+ --ignore-test pod-networkpolicy \
+ --ignore-test deployment-has-poddisruptionbudget \
+ --ignore-test container-security-context-user-group-id \
+ --ignore-test container-security-context-readonlyrootfilesystem \
+ --output-format ci
+ fi
+
+ polaris:
+ name: Polaris Audit
+ runs-on: ubuntu-latest
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install kubectl and polaris
+ run: |
+ # Install kubectl
+ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
+ chmod +x kubectl
+ mv kubectl /usr/local/bin/
+
+ # Install polaris
+ wget https://github.com/FairwindsOps/polaris/releases/download/9.5.0/polaris_linux_amd64.tar.gz
+ tar -xzf polaris_linux_amd64.tar.gz
+ chmod +x polaris
+ mv polaris /usr/local/bin/
+
+ - name: Run Polaris audit
+ run: |
+ if [ -f "kustomization.yaml" ]; then
+ kubectl kustomize . > manifests.yaml
+ polaris audit --audit-path manifests.yaml \
+ --format pretty \
+ --set-exit-code-on-danger \
+ --set-exit-code-below-score 70
+ fi
+
+ resource-analysis:
+ name: Resource Usage Analysis
+ runs-on: ubuntu-latest
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install kubectl and yq
+ run: |
+ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
+ chmod +x kubectl
+ mv kubectl /usr/local/bin/
+
+ wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O yq
+ chmod +x yq
+ mv yq /usr/local/bin/
+
+ - name: Analyze resource requests and limits
+ run: |
+ echo "# Resource Analysis Report"
+ echo ""
+ echo "## Applications Resource Configuration"
+ echo ""
+ echo "| Application | Container | CPU Request | CPU Limit | Memory Request | Memory Limit |"
+ echo "|-------------|-----------|-------------|-----------|----------------|--------------|"
+
+ # Find all directories with kustomization.yaml
+ find . -maxdepth 2 -name "kustomization.yaml" | while read config; do
+ app_dir=$(dirname "$config")
+ if [ "$app_dir" != "." ]; then
+ manifests=$(kubectl kustomize "$app_dir" 2>/dev/null)
+ if [ -n "$manifests" ]; then
+ echo "$manifests" | yq eval-all '
+ select(.kind == "Deployment" or .kind == "StatefulSet") |
+ .spec.template.spec.containers[] |
+ "| '"$app_dir"' | \(.name) | \(.resources.requests.cpu // "none") | \(.resources.limits.cpu // "none") | \(.resources.requests.memory // "none") | \(.resources.limits.memory // "none") |"
+ ' - 2>/dev/null || true
+ fi
+ fi
+ done
+
+ pr-summary:
+ name: PR Summary Report
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+ needs: [kube-score, polaris, resource-analysis]
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Generate PR summary
+ env:
+ GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
+ GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments
+ run: |
+ cat > summary.md << EOF
+ ## Best Practices Validation Summary
+
+ ✅ All validation checks completed
+
+ ### Checks Run:
+ - **kube-score**: Kubernetes best practices analysis
+ - **Polaris**: Security and reliability audit
+ - **Resource Analysis**: CPU and memory configuration review
+
+ See individual job logs for detailed results.
+
+ ---
+ *Automated by Gitea Actions*
+ EOF
+
+ if [ -n "${GITEA_TOKEN}" ]; then
+ jq -n --rawfile body summary.md '{body: $body}' > comment-payload.json
+
+ curl -s -X POST \
+ -H "Authorization: token ${GITEA_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d @comment-payload.json \
+ "${GITEA_API}" || echo "Failed to post comment (token may not be configured)"
+ else
+ echo "GITEA_TOKEN not configured, skipping comment"
+ cat summary.md
+ fi
diff --git a/.gitea/workflows/security.yaml b/.gitea/workflows/security.yaml
new file mode 100644
index 0000000..4399bda
--- /dev/null
+++ b/.gitea/workflows/security.yaml
@@ -0,0 +1,302 @@
+name: Security Scan
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ trivy-scan:
+ name: Trivy Security Scan
+ runs-on: ubuntu-latest
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Trivy
+ run: |
+ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
+
+ - name: Run Trivy config scan
+ run: |
+ trivy config \
+ --severity CRITICAL,HIGH \
+ --ignorefile .trivyignore \
+ --exit-code 0 \
+ --format table \
+ .
+
+ trivy-pr-review:
+ name: Trivy PR Review
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Install Trivy and jq
+ run: |
+ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
+ apt-get update && apt-get install -y jq
+
+ - name: Get changed files
+ id: changed
+ run: |
+ CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- '*.yaml' '*.yml' '*.tf' | tr '\n' ' ')
+ echo "files=${CHANGED_FILES}" >> $GITHUB_OUTPUT
+ echo "Changed files: ${CHANGED_FILES}"
+
+ - name: Run Trivy scan and post review
+ env:
+ GITEA_TOKEN: ${{ secrets.TRIVY_GITEA_TOKEN }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews
+ CHANGED_FILES: ${{ steps.changed.outputs.files }}
+ run: |
+ if [ -z "${CHANGED_FILES}" ]; then
+ echo "No IaC files changed, skipping scan"
+ exit 0
+ fi
+
+ trivy config \
+ --severity CRITICAL,HIGH,MEDIUM,LOW \
+ --ignorefile .trivyignore \
+ --exit-code 0 \
+ --format json \
+ --output trivy-results.json \
+ .
+
+ CHANGED_FILES_JSON=$(echo "${CHANGED_FILES}" | tr ' ' '\n' | sed '/^$/d' | jq -R . | jq -s .)
+
+ CRITICAL=$(jq --argjson files "$CHANGED_FILES_JSON" \
+ '[.Results[]? | select(.Target as $t | $files | any(. as $f | $t | endswith($f))) | .Misconfigurations[]? | select(.Severity == "CRITICAL")] | length' \
+ trivy-results.json)
+ HIGH=$(jq --argjson files "$CHANGED_FILES_JSON" \
+ '[.Results[]? | select(.Target as $t | $files | any(. as $f | $t | endswith($f))) | .Misconfigurations[]? | select(.Severity == "HIGH")] | length' \
+ trivy-results.json)
+ MEDIUM=$(jq --argjson files "$CHANGED_FILES_JSON" \
+ '[.Results[]? | select(.Target as $t | $files | any(. as $f | $t | endswith($f))) | .Misconfigurations[]? | select(.Severity == "MEDIUM")] | length' \
+ trivy-results.json)
+ LOW=$(jq --argjson files "$CHANGED_FILES_JSON" \
+ '[.Results[]? | select(.Target as $t | $files | any(. as $f | $t | endswith($f))) | .Misconfigurations[]? | select(.Severity == "LOW")] | length' \
+ trivy-results.json)
+ TOTAL_FAILED=$(( CRITICAL + HIGH + MEDIUM + LOW ))
+
+ if [ "$CRITICAL" -gt 0 ]; then
+ REVIEW_STATE="REQUEST_CHANGES"
+ VERDICT="BLOCKED: ${CRITICAL} critical finding(s) detected"
+ EXIT_CODE=1
+ elif [ "$HIGH" -gt 0 ]; then
+ REVIEW_STATE="COMMENT"
+ VERDICT="WARNING: ${HIGH} high severity finding(s) detected"
+ EXIT_CODE=0
+ else
+ REVIEW_STATE="APPROVED"
+ VERDICT="PASSED: No critical or high severity findings"
+ EXIT_CODE=0
+ fi
+
+ DETAILS=$(jq -r --argjson files "$CHANGED_FILES_JSON" '
+ [.Results[]? | select(.Target as $t | $files | any(. as $f | $t | endswith($f))) |
+ .Target as $t | .Misconfigurations[]? |
+ "| \(.Severity) | \(.ID) | \(.Title) | \($t) |"
+ ] | join("\n")' trivy-results.json)
+
+ cat > review-body.md << EOF
+ ## Trivy Security Scan Results
+
+ **${VERDICT}**
+
+ > Scanned ${CHANGED_FILES:-"no files"}
+
+ ### Summary
+ | Severity | Count |
+ |----------|-------|
+ | Critical | ${CRITICAL} |
+ | High | ${HIGH} |
+ | Medium | ${MEDIUM} |
+ | Low | ${LOW} |
+ | **Total** | **${TOTAL_FAILED}** |
+
+
+ Failed Checks (click to expand)
+
+ | Severity | Check ID | Description | File |
+ |----------|----------|-------------|------|
+ ${DETAILS}
+
+
+
+ ---
+ *Scanned by [Trivy](https://github.com/aquasecurity/trivy)*
+ EOF
+
+ jq -n \
+ --rawfile body review-body.md \
+ --arg event "$REVIEW_STATE" \
+ '{body: $body, event: $event}' > review-payload.json
+
+ echo "Posting review to: ${GITEA_API}"
+ echo "Review state: ${REVIEW_STATE}"
+
+ curl -s -X POST \
+ -H "Authorization: token ${GITEA_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d @review-payload.json \
+ "${GITEA_API}" || echo "Failed to post review (token may not be configured)"
+
+ exit $EXIT_CODE
+
+ checkov-scan:
+ name: Checkov IaC Scan
+ runs-on: ubuntu-latest
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Checkov
+ run: |
+ python3 -m venv /opt/checkov
+ /opt/checkov/bin/pip install checkov
+
+ - name: Run Checkov scan
+ run: |
+ /opt/checkov/bin/checkov -d . \
+ --config-file .checkov.yaml \
+ --output cli
+
+ checkov-pr-review:
+ name: Checkov PR Review
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Install Checkov and jq
+ run: |
+ python3 -m venv /opt/checkov
+ /opt/checkov/bin/pip install checkov
+ apt-get update && apt-get install -y jq
+
+ - name: Get changed files
+ id: changed
+ run: |
+ CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- '*.yaml' '*.yml' '*.tf' | tr '\n' ' ')
+ echo "files=${CHANGED_FILES}" >> $GITHUB_OUTPUT
+ echo "Changed files: ${CHANGED_FILES}"
+
+ - name: Run Checkov scan and post review
+ env:
+ GITEA_TOKEN: ${{ secrets.CHECKOV_GITEA_TOKEN }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews
+ CHANGED_FILES: ${{ steps.changed.outputs.files }}
+ run: |
+ if [ -z "${CHANGED_FILES}" ]; then
+ echo "No IaC files changed, skipping scan"
+ exit 0
+ fi
+
+ /opt/checkov/bin/checkov -d . \
+ --config-file .checkov.yaml \
+ --output json \
+ > checkov-results.json || true
+
+ CHANGED_FILES_JSON=$(echo "${CHANGED_FILES}" | tr ' ' '\n' | sed '/^$/d' | jq -R . | jq -s .)
+
+ CRITICAL=$(jq --argjson files "$CHANGED_FILES_JSON" \
+ '[.[] | .results.failed_checks[]? | select(.file_path as $fp | $files | any(. as $f | $fp | endswith($f))) | select(.severity == "CRITICAL")] | length' \
+ checkov-results.json)
+ HIGH=$(jq --argjson files "$CHANGED_FILES_JSON" \
+ '[.[] | .results.failed_checks[]? | select(.file_path as $fp | $files | any(. as $f | $fp | endswith($f))) | select(.severity == "HIGH")] | length' \
+ checkov-results.json)
+ MEDIUM=$(jq --argjson files "$CHANGED_FILES_JSON" \
+ '[.[] | .results.failed_checks[]? | select(.file_path as $fp | $files | any(. as $f | $fp | endswith($f))) | select(.severity == "MEDIUM")] | length' \
+ checkov-results.json)
+ LOW=$(jq --argjson files "$CHANGED_FILES_JSON" \
+ '[.[] | .results.failed_checks[]? | select(.file_path as $fp | $files | any(. as $f | $fp | endswith($f))) | select(.severity == "LOW")] | length' \
+ checkov-results.json)
+ UNSET=$(jq --argjson files "$CHANGED_FILES_JSON" \
+ '[.[] | .results.failed_checks[]? | select(.file_path as $fp | $files | any(. as $f | $fp | endswith($f))) | select(.severity == null or .severity == "UNKNOWN" or .severity == "NONE")] | length' \
+ checkov-results.json)
+ TOTAL_FAILED=$(( CRITICAL + HIGH + MEDIUM + LOW + UNSET ))
+
+ if [ "$CRITICAL" -gt 0 ]; then
+ REVIEW_STATE="REQUEST_CHANGES"
+ VERDICT="BLOCKED: ${CRITICAL} critical finding(s) detected"
+ EXIT_CODE=1
+ elif [ "$HIGH" -gt 0 ]; then
+ REVIEW_STATE="COMMENT"
+ VERDICT="WARNING: ${HIGH} high severity finding(s) detected"
+ EXIT_CODE=0
+ else
+ REVIEW_STATE="APPROVED"
+ VERDICT="PASSED: No critical or high severity findings"
+ EXIT_CODE=0
+ fi
+
+ DETAILS=$(jq -r --argjson files "$CHANGED_FILES_JSON" '
+ [.[] | .check_type as $ct |
+ .results.failed_checks[]? |
+ select(.file_path as $fp | $files | any(. as $f | $fp | endswith($f))) |
+ "| \(.severity // "UNSET") | \(.check_id) | \(.check_name) | \(.file_path) |"
+ ] | join("\n")' checkov-results.json)
+
+ cat > review-body.md << EOF
+ ## Checkov IaC Scan Results
+
+ **${VERDICT}**
+
+ > Scanned ${CHANGED_FILES:-"no files"}
+
+ ### Summary
+ | Severity | Count |
+ |----------|-------|
+ | Critical | ${CRITICAL} |
+ | High | ${HIGH} |
+ | Medium | ${MEDIUM} |
+ | Low | ${LOW} |
+ | Unset | ${UNSET} |
+ | **Total** | **${TOTAL_FAILED}** |
+
+
+ Failed Checks (click to expand)
+
+ | Severity | Check ID | Description | File |
+ |----------|----------|-------------|------|
+ ${DETAILS}
+
+
+
+ ---
+ *Scanned by [Checkov](https://github.com/bridgecrewio/checkov)*
+ EOF
+
+ jq -n \
+ --rawfile body review-body.md \
+ --arg event "$REVIEW_STATE" \
+ '{body: $body, event: $event}' > review-payload.json
+
+ curl -s -X POST \
+ -H "Authorization: token ${GITEA_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d @review-payload.json \
+ "${GITEA_API}" || echo "Failed to post review (token may not be configured)"
+
+ exit $EXIT_CODE
diff --git a/.gitea/workflows/validate.yaml b/.gitea/workflows/validate.yaml
new file mode 100644
index 0000000..118428a
--- /dev/null
+++ b/.gitea/workflows/validate.yaml
@@ -0,0 +1,113 @@
+name: Validate Manifests
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ yaml-lint:
+ name: YAML Lint
+ runs-on: ubuntu-latest
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install yamllint
+ run: |
+ python3 -m pip install yamllint
+
+ - name: Run yamllint
+ run: |
+ yamllint -c .yamllint.yaml .
+
+ kustomize-build:
+ name: Kustomize Build Test
+ runs-on: ubuntu-latest
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install kubectl with kustomize
+ run: |
+ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
+ chmod +x kubectl
+ mv kubectl /usr/local/bin/
+
+ - name: Test root kustomization
+ run: |
+ if [ -f "kustomization.yaml" ]; then
+ echo "Building root kustomization..."
+ kubectl kustomize . > /tmp/manifests.yaml
+ echo "✓ Root kustomization builds successfully"
+ else
+ echo "No root kustomization.yaml found"
+ fi
+
+ - name: Test individual app kustomizations
+ run: |
+ find . -maxdepth 2 -name "kustomization.yaml" -not -path "./kustomization.yaml" | while read config; do
+ app_dir=$(dirname "$config")
+ echo "Building $app_dir kustomization..."
+ kubectl kustomize "$app_dir" > /dev/null
+ echo "✓ $app_dir kustomization builds successfully"
+ done
+
+ kubeconform:
+ name: Kubernetes Schema Validation
+ runs-on: ubuntu-latest
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install kubectl and kubeconform
+ run: |
+ # Install kubectl
+ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
+ chmod +x kubectl
+ mv kubectl /usr/local/bin/
+
+ # Install kubeconform
+ curl -L https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz | tar xz
+ chmod +x kubeconform
+ mv kubeconform /usr/local/bin/
+
+ - name: Validate Kubernetes manifests
+ run: |
+ if [ -f "kustomization.yaml" ]; then
+ kubectl kustomize . | kubeconform \
+ -schema-location default \
+ -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
+ -summary \
+ -output text \
+ -ignore-missing-schemas \
+ -verbose
+ fi
+
+ flux-validate:
+ name: Flux Build Validation
+ runs-on: ubuntu-latest
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Flux CLI
+ run: |
+ curl -s https://fluxcd.io/install.sh | bash
+ mv /root/.local/bin/flux /usr/local/bin/
+
+ - name: Validate Flux Kustomization
+ run: |
+ # Use the repository name or 'app' as the kustomization name for validation
+ flux build kustomization irc --path . --dry-run
diff --git a/.trivyignore b/.trivyignore
new file mode 100644
index 0000000..95b2311
--- /dev/null
+++ b/.trivyignore
@@ -0,0 +1,5 @@
+# Avahi daemon in base images often triggers this
+CVE-2021-26720
+
+# Common false positives or accepted risks
+CVE-2023-52425
diff --git a/.yamllint.yaml b/.yamllint.yaml
new file mode 100644
index 0000000..33b82f9
--- /dev/null
+++ b/.yamllint.yaml
@@ -0,0 +1,6 @@
+extends: default
+
+rules:
+ line-length: disable
+ document-start: disable
+ truthy: disable