Merge pull request 'feat/gitea-actions-standardization' (#1) from feat/gitea-actions-standardization into main

Reviewed-on: #1
Reviewed-by: trivvy <no-reply.trivvy@farh.net>
Reviewed-by: polaris <no-reply.polaris@farh.net>
Reviewed-by: checkov <no-reply.checkov@farh.net>
This commit is contained in:
2026-02-08 10:45:37 -05:00
10 changed files with 976 additions and 10 deletions
+10
View File
@@ -0,0 +1,10 @@
soft-fail: false
quiet: true
compact: true
framework:
- all
skip-check:
- CKV_K8S_21 # Default namespace usage
- CKV_K8S_43 # Image tag validation (using latest tags intentionally)
- CKV_K8S_14 # Image tag should be fixed (same as above)
- CKV_K8S_22 # Read-only filesystem (IRC apps need to write to volumes)
+277
View File
@@ -0,0 +1,277 @@
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-readonlyrootfilesystem \
--ignore-test container-image-tag \
--ignore-test container-security-context-user-group-id \
--ignore-test probe-not-identical \
--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
polaris-pr-review:
name: Polaris 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
- name: Install tools
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/
# Install jq
apt-get update && apt-get install -y jq
- name: Run Polaris and post review
env:
GITEA_TOKEN: ${{ secrets.POLARIS_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
run: |
if [ ! -f "kustomization.yaml" ]; then
echo "No root kustomization.yaml, skipping Polaris review"
exit 0
fi
kubectl kustomize . > manifests.yaml
if [ ! -s manifests.yaml ]; then
echo "Manifests are empty, skipping"
exit 0
fi
polaris audit --audit-path manifests.yaml --format json > polaris-results.json || true
DANGERS=$(jq '.Summary.Dangers // 0' polaris-results.json)
WARNINGS=$(jq '.Summary.Warnings // 0' polaris-results.json)
SCORE=$(jq '.Summary.Score // 0' polaris-results.json)
if [ "$DANGERS" -gt 0 ]; then
REVIEW_STATE="REQUEST_CHANGES"
VERDICT="BLOCKED: ${DANGERS} danger(s) detected. Score: ${SCORE}%"
EXIT_CODE=1
elif [ "$WARNINGS" -gt 0 ]; then
REVIEW_STATE="COMMENT"
VERDICT="WARNING: ${WARNINGS} warning(s) detected. Score: ${SCORE}%"
EXIT_CODE=0
else
REVIEW_STATE="APPROVED"
VERDICT="PASSED: No dangers or warnings. Score: ${SCORE}%"
EXIT_CODE=0
fi
DETAILS=$(jq -r '
.Results[]? |
.Name as $resName | .Kind as $resKind | .Namespace as $resNs |
(
(.PodResult?.Results[]? | {sev: .Severity, msg: .Message, check: .ID, target: "Pod"}),
(.PodResult?.ContainerResults[]? | .Name as $contName | .Results[]? | {sev: .Severity, msg: .Message, check: .ID, target: $contName})
) |
select(.sev == "danger" or .sev == "warning") |
"| \(.sev) | \($resKind)/\($resName) | \(.target) | \(.check) | \(.msg) |"
' polaris-results.json | head -c 4000)
cat > review-body.md << INTERNAL_EOF
## Polaris Audit Results
**${VERDICT}**
### Summary
| Metric | Value |
|--------|-------|
| Score | ${SCORE}% |
| Dangers | ${DANGERS} |
| Warnings | ${WARNINGS} |
<details>
<summary>Issues (click to expand)</summary>
| Severity | Resource | Container | Check | Message |
|----------|----------|-----------|-------|---------|
\${DETAILS}
</details>
---
*Scanned by [Polaris](https://github.com/FairwindsOps/polaris)*
INTERNAL_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}" | jq .
exit $EXIT_CODE
+302
View File
@@ -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}** |
<details>
<summary>Failed Checks (click to expand)</summary>
| Severity | Check ID | Description | File |
|----------|----------|-------------|------|
${DETAILS}
</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}** |
<details>
<summary>Failed Checks (click to expand)</summary>
| Severity | Check ID | Description | File |
|----------|----------|-------------|------|
${DETAILS}
</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
+95
View File
@@ -0,0 +1,95 @@
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 --break-system-packages 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 \
-skip HTTPRoute \
-verbose
fi
+5
View File
@@ -0,0 +1,5 @@
# Avahi daemon in base images often triggers this
CVE-2021-26720
# Common false positives or accepted risks
CVE-2023-52425
+6
View File
@@ -0,0 +1,6 @@
extends: default
rules:
line-length: disable
document-start: disable
truthy: disable
+154
View File
@@ -0,0 +1,154 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## ⚠️ CRITICAL: Flux CD Deployment
**This repository is deployed via Flux CD.** All manifests use Flux variable substitution syntax (e.g., `${VARIABLE_NAME}`).
**DO NOT replace Flux variables with hardcoded values.** Flux substitutes these variables at deployment time from ConfigMaps or Secrets.
## Project Overview
This repository contains Kubernetes manifests for deploying IRC-related applications (The Lounge web client and ZNC bouncer) using Kustomize. The infrastructure is deployed to a Kubernetes cluster with Flux CD and uses Gitea Actions for CI/CD validation and security scanning.
## Architecture
### Kustomize Structure
- **Root kustomization.yaml**: Aggregates all application components (thelounge, znc)
- **Application directories**: Each contains its own kustomization.yaml with associated manifests:
- `thelounge/`: Web-based IRC client (StatefulSet, Service, HTTPRoute, NetworkPolicy)
- `znc/`: IRC bouncer (StatefulSet, Service, NetworkPolicy)
- **Commented applications**: bitlbee and inspircd are mentioned but not currently deployed
### Application Components
Both applications follow the same pattern:
- **StatefulSet**: Deploys the main container with persistent storage via volumeClaimTemplates (4Gi)
- **Service**: Exposes the application (thelounge: 9000, znc: 6501)
- **NetworkPolicy**: Controls network ingress/egress
- **HTTPRoute**: (thelounge only) Gateway API routing configuration
### Resource Configuration
- Priority class: `low-priority` for both applications
- Resource requests/limits: 100m/500m CPU, 256Mi/512Mi memory
- Security: `automountServiceAccountToken: false`, `allowPrivilegeEscalation: false`
- Probes: Both liveness and readiness probes configured for reliability
### Polaris Exemptions
All manifests have Polaris exemptions:
- `runAsRootAllowed-exempt`: Containers need root for their base images
- `tagNotSpecified-exempt`: Using `latest` tags
- `topologySpreadConstraint-exempt`: Single-replica deployments don't need spread constraints
## CI/CD Pipeline
### Gitea Actions Workflows
Located in `.gitea/workflows/`, three main workflows run on push/PR to main:
1. **validate.yaml** - Manifest validation:
- YAML linting with yamllint
- Kustomize build tests (root + individual apps)
- Kubernetes schema validation with kubeconform
- Flux build validation
2. **security.yaml** - Security scanning with PR review automation:
- **Trivy**: Scans for vulnerabilities, posts PR reviews
- **Checkov**: IaC security scanning, posts PR reviews
- PR review states: `REQUEST_CHANGES` (critical), `COMMENT` (high), `APPROVED` (clean)
- Only scans changed YAML/YML/TF files in PRs
3. **best-practices.yaml** - Kubernetes best practices:
- **kube-score**: Best practices analysis
- **Polaris**: Security and reliability audit with PR reviews
- Resource usage analysis
- Polaris enforces minimum 70% score and blocks on dangers
### PR Review System
Security and best practices workflows automatically review PRs:
- **Trivy/Checkov**: Critical findings block, high findings warn
- **Polaris**: Danger findings block, warnings comment
- Reviews posted via Gitea API with detailed tables
- Requires tokens: `TRIVY_GITEA_TOKEN`, `CHECKOV_GITEA_TOKEN`, `POLARIS_GITEA_TOKEN`
## Development Commands
### Local Validation
```bash
# YAML linting
yamllint -c .yamllint.yaml .
# Build and validate root kustomization
kubectl kustomize . > /tmp/manifests.yaml
# Build individual app kustomizations
kubectl kustomize ./thelounge
kubectl kustomize ./znc
# Validate with kubeconform
kubectl kustomize . | kubeconform \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-summary -ignore-missing-schemas
# Flux validation
flux build kustomization irc --path . --dry-run
```
### Security Scanning
```bash
# Run Trivy config scan
trivy config --severity CRITICAL,HIGH --ignorefile .trivyignore .
# Run Checkov scan
checkov -d . --config-file .checkov.yaml --output cli
```
### Best Practices Analysis
```bash
# Run kube-score
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
# Run Polaris audit
kubectl kustomize . | polaris audit --format pretty --set-exit-code-on-danger --set-exit-code-below-score 70
```
## Configuration Files
### Security and Validation
- `.yamllint.yaml`: YAML linting rules (line-length, document-start, truthy disabled)
- `.checkov.yaml`: Checkov configuration, skips CKV_K8S_21 (namespace) and CKV_K8S_43 (image tags)
- `.trivyignore`: Ignores CVE-2021-26720 (Avahi) and CVE-2023-52425 (accepted risks)
- `configmap.yaml.example`: Template for hostname configuration (not tracked in repo)
## Key Patterns
### Adding New Applications
1. Create app directory with kustomization.yaml
2. Add required manifests (statefulset, service, networkpolicy)
3. Reference in root kustomization.yaml resources
4. Include Polaris exemptions if needed
5. Set priorityClassName: low-priority
6. Disable automountServiceAccountToken
7. Configure resource requests/limits and probes
### Modifying Workflows
- All workflows use `catthehacker/ubuntu:act-latest` container for act compatibility
- PR review jobs require fetch-depth: 0 for git diff operations
- Security tokens should use dedicated secrets (not shared GITEA_TOKEN)
- Exit code 1 on critical findings, 0 on warnings/pass
## Commit Message Format
When making commits, include credits:
```
<main commit message>
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
```
+90 -2
View File
@@ -1,3 +1,91 @@
# irc
# IRC Applications
Some useful apps for IRC
Kubernetes manifests for IRC applications, deployed via Flux CD.
## Applications
- **The Lounge** - Modern web IRC client with persistent connections
- **ZNC** - IRC bouncer for persistent IRC presence
## Deployment
This repository is deployed to Kubernetes using **Flux CD** with variable substitution. Configuration variables (e.g., hostnames) are provided via ConfigMaps at deployment time.
**Important:** Manifests use Flux variable syntax (`${VARIABLE_NAME}`). Do not replace these with hardcoded values.
## Architecture
- **Kustomize-based**: Uses Kustomize for manifest organization
- **StatefulSets**: Both apps use StatefulSets with persistent volumes (4Gi each)
- **Security hardened**:
- Run as non-root (UID 1000)
- Seccomp profiles enabled (RuntimeDefault)
- All capabilities dropped
- Network policies configured
- **Resource managed**: CPU and memory limits set, including ephemeral storage
- **Health checks**: Liveness and readiness probes configured
## Local Development
### Validate manifests
```bash
# YAML linting
yamllint -c .yamllint.yaml .
# Test kustomize builds
kubectl kustomize .
kubectl kustomize ./thelounge
kubectl kustomize ./znc
# Validate schemas
kubectl kustomize . | kubeconform \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-skip HTTPRoute \
-ignore-missing-schemas
```
### Security scanning
```bash
# Trivy
trivy config --severity CRITICAL,HIGH --ignorefile .trivyignore .
# Checkov
checkov -d . --config-file .checkov.yaml
```
### Best practices
```bash
# Kube-score
kubectl kustomize . | kube-score score - \
--ignore-test container-image-tag \
--ignore-test container-security-context-readonlyrootfilesystem
# Polaris
kubectl kustomize . | polaris audit --format pretty
```
## CI/CD
Automated validation and security scanning via Gitea Actions:
### Validate Manifests
- YAML linting (yamllint)
- Kustomize build tests
- Kubernetes schema validation (kubeconform, skips HTTPRoute with variables)
### Security Scan
- **Trivy**: Vulnerability scanning with automated PR reviews
- **Checkov**: IaC security scanning with automated PR reviews
- Blocks PRs on critical findings, warns on high severity
### Best Practices
- **kube-score**: Kubernetes best practices analysis
- **Polaris**: Security and reliability audit with automated PR reviews
- **Resource analysis**: CPU/memory configuration review
All workflows run on push/PR to main branch.
## Documentation
See [CLAUDE.md](CLAUDE.md) for comprehensive development documentation.
+19 -3
View File
@@ -6,11 +6,9 @@ metadata:
app.kubernetes.io/name: thelounge
app.kubernetes.io/instance: thelounge
annotations:
polaris.fairwinds.com/runAsRootAllowed-exempt: "true"
polaris.fairwinds.com/tagNotSpecified-exempt: "true"
polaris.fairwinds.com/topologySpreadConstraint-exempt: "true"
spec:
serviceName: thelounge
replicas: 1
selector:
matchLabels:
@@ -24,11 +22,26 @@ spec:
spec:
priorityClassName: low-priority
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: thelounge
image: ghcr.io/thelounge/thelounge:latest
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
ports:
- containerPort: 9000
name: http-9000
@@ -39,11 +52,14 @@ spec:
requests:
cpu: "100m"
memory: "256Mi"
ephemeral-storage: "1Gi"
limits:
cpu: "500m"
memory: "512Mi"
ephemeral-storage: "2Gi"
livenessProbe:
tcpSocket:
httpGet:
path: /
port: 9000
initialDelaySeconds: 20
periodSeconds: 10
+18 -5
View File
@@ -6,7 +6,6 @@ metadata:
app.kubernetes.io/name: znc
app.kubernetes.io/instance: znc
annotations:
polaris.fairwinds.com/runAsRootAllowed-exempt: "true"
polaris.fairwinds.com/tagNotSpecified-exempt: "true"
polaris.fairwinds.com/topologySpreadConstraint-exempt: "true"
spec:
@@ -14,7 +13,6 @@ spec:
matchLabels:
app.kubernetes.io/name: znc
app.kubernetes.io/instance: znc
serviceName: "znc"
replicas: 1
template:
metadata:
@@ -24,6 +22,13 @@ spec:
spec:
priorityClassName: low-priority
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: znc
image: lscr.io/linuxserver/znc:latest
@@ -33,9 +38,15 @@ spec:
name: irc-6501
securityContext:
runAsNonRoot: false
allowPrivilegeEscalation: false
privileged: false
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: config
@@ -45,9 +56,11 @@ spec:
requests:
memory: "256Mi"
cpu: "100m"
ephemeral-storage: "1Gi"
limits:
memory: "512Mi"
cpu: "500m"
ephemeral-storage: "2Gi"
livenessProbe:
tcpSocket:
@@ -73,4 +86,4 @@ spec:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
storage: 4Gi