ci: add helm and publish

This commit is contained in:
Morten Olsen
2025-12-12 12:15:48 +01:00
parent 67014b3d16
commit d2288aa527
15 changed files with 1595 additions and 6 deletions

131
.github/workflows/gh-pages.yml vendored Normal file
View File

@@ -0,0 +1,131 @@
name: GitHub Pages
on:
push:
branches:
- main
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: v3.14.0
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Create Helm packages directory
run: mkdir -p _site
- name: Package Helm chart
run: |
helm package charts/nuclei-operator -d _site
- name: Generate Helm repo index
run: |
helm repo index _site --url https://morten-olsen.github.io/homelab-nuclei-operator
- name: Create index.html
run: |
cat > _site/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nuclei Operator Helm Repository</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
}
h1 { color: #333; }
code {
background: #f4f4f4;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-size: 0.9em;
}
pre {
background: #f4f4f4;
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
}
pre code {
background: none;
padding: 0;
}
a { color: #0066cc; }
</style>
</head>
<body>
<h1>🔬 Nuclei Operator Helm Repository</h1>
<p>
This is the Helm chart repository for the
<a href="https://github.com/morten-olsen/homelab-nuclei-operator">Nuclei Operator</a>.
</p>
<h2>Usage</h2>
<p>Add this repository to Helm:</p>
<pre><code>helm repo add nuclei-operator https://morten-olsen.github.io/homelab-nuclei-operator
helm repo update</code></pre>
<p>Install the chart:</p>
<pre><code>helm install nuclei-operator nuclei-operator/nuclei-operator \
--namespace nuclei-operator-system \
--create-namespace</code></pre>
<h2>Available Charts</h2>
<ul>
<li><strong>nuclei-operator</strong> - A Kubernetes operator that automatically scans Ingress and VirtualService resources using Nuclei security scanner</li>
</ul>
<h2>Links</h2>
<ul>
<li><a href="https://github.com/morten-olsen/homelab-nuclei-operator">GitHub Repository</a></li>
<li><a href="./index.yaml">Helm Repository Index</a></li>
</ul>
</body>
</html>
EOF
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: _site
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

165
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,165 @@
name: Release
on:
push:
branches:
- main
tags:
- 'v*'
pull_request:
branches:
- main
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
name: Build and Push Container Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
release-helm-chart:
name: Release Helm Chart
runs-on: ubuntu-latest
needs: build-and-push
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
pages: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: v3.14.0
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Update Chart version and appVersion
run: |
sed -i "s/^version:.*/version: ${{ steps.version.outputs.VERSION }}/" charts/nuclei-operator/Chart.yaml
sed -i "s/^appVersion:.*/appVersion: \"${{ steps.version.outputs.VERSION }}\"/" charts/nuclei-operator/Chart.yaml
- name: Package Helm chart
run: |
helm package charts/nuclei-operator -d .helm-packages
- name: Checkout gh-pages branch
uses: actions/checkout@v4
with:
ref: gh-pages
path: gh-pages
- name: Update Helm repository
run: |
cp .helm-packages/*.tgz gh-pages/
cd gh-pages
helm repo index . --url https://morten-olsen.github.io/homelab-nuclei-operator
git add .
git commit -m "Release Helm chart ${{ steps.version.outputs.VERSION }}"
git push
create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: build-and-push
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: v3.14.0
- name: Install kustomize
run: |
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Generate install manifests
run: |
cd config/manager && kustomize edit set image controller=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
cd ../..
kustomize build config/default > install.yaml
- name: Package Helm chart
run: |
sed -i "s/^version:.*/version: ${{ steps.version.outputs.VERSION }}/" charts/nuclei-operator/Chart.yaml
sed -i "s/^appVersion:.*/appVersion: \"${{ steps.version.outputs.VERSION }}\"/" charts/nuclei-operator/Chart.yaml
helm package charts/nuclei-operator
- name: Create Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: |
install.yaml
nuclei-operator-*.tgz

111
README.md
View File

@@ -51,6 +51,71 @@ The Nuclei Operator watches for Ingress and VirtualService resources in your Kub
## Installation ## Installation
### Using Helm (Recommended)
The easiest way to install the Nuclei Operator is using Helm:
```bash
# Add the Helm repository
helm repo add nuclei-operator https://morten-olsen.github.io/homelab-nuclei-operator
helm repo update
# Install the operator
helm install nuclei-operator nuclei-operator/nuclei-operator \
--namespace nuclei-operator-system \
--create-namespace
```
#### Helm Configuration
You can customize the installation by providing values:
```bash
helm install nuclei-operator nuclei-operator/nuclei-operator \
--namespace nuclei-operator-system \
--create-namespace \
--set replicaCount=1 \
--set resources.limits.memory=4Gi \
--set nuclei.rescanAge=72h
```
Or create a `values.yaml` file:
```yaml
replicaCount: 1
resources:
limits:
cpu: "2"
memory: "4Gi"
requests:
cpu: "500m"
memory: "1Gi"
nuclei:
timeout: "1h"
rescanAge: "72h"
serviceMonitor:
enabled: true
```
```bash
helm install nuclei-operator nuclei-operator/nuclei-operator \
--namespace nuclei-operator-system \
--create-namespace \
-f values.yaml
```
### Using Container Image from GitHub Container Registry
The container images are available at:
```
ghcr.io/morten-olsen/homelab-nuclei-operator:latest
ghcr.io/morten-olsen/homelab-nuclei-operator:v0.1.0 # specific version
```
### Using kubectl/kustomize ### Using kubectl/kustomize
1. **Install the CRDs:** 1. **Install the CRDs:**
@@ -63,7 +128,7 @@ make install
```bash ```bash
# Using the default image # Using the default image
make deploy IMG=ghcr.io/mortenolsen/nuclei-operator:latest make deploy IMG=ghcr.io/morten-olsen/homelab-nuclei-operator:latest
# Or build and deploy your own image # Or build and deploy your own image
make docker-build docker-push IMG=<your-registry>/nuclei-operator:tag make docker-build docker-push IMG=<your-registry>/nuclei-operator:tag
@@ -72,11 +137,18 @@ make deploy IMG=<your-registry>/nuclei-operator:tag
### Using a Single YAML File ### Using a Single YAML File
Generate and apply a consolidated installation manifest: Download and apply the installation manifest from the GitHub release:
```bash
# Download from the latest release
kubectl apply -f https://github.com/morten-olsen/homelab-nuclei-operator/releases/latest/download/install.yaml
```
Or generate it locally:
```bash ```bash
# Generate the installer # Generate the installer
make build-installer IMG=<your-registry>/nuclei-operator:tag make build-installer IMG=ghcr.io/morten-olsen/homelab-nuclei-operator:latest
# Apply to your cluster # Apply to your cluster
kubectl apply -f dist/install.yaml kubectl apply -f dist/install.yaml
@@ -86,8 +158,8 @@ kubectl apply -f dist/install.yaml
```bash ```bash
# Clone the repository # Clone the repository
git clone https://github.com/mortenolsen/nuclei-operator.git git clone https://github.com/morten-olsen/homelab-nuclei-operator.git
cd nuclei-operator cd homelab-nuclei-operator
# Build the binary # Build the binary
make build make build
@@ -103,8 +175,20 @@ make docker-push IMG=<your-registry>/nuclei-operator:tag
### 1. Deploy the Operator ### 1. Deploy the Operator
Using Helm (recommended):
```bash ```bash
make deploy IMG=ghcr.io/mortenolsen/nuclei-operator:latest helm repo add nuclei-operator https://morten-olsen.github.io/homelab-nuclei-operator
helm repo update
helm install nuclei-operator nuclei-operator/nuclei-operator \
--namespace nuclei-operator-system \
--create-namespace
```
Or using kustomize:
```bash
make deploy IMG=ghcr.io/morten-olsen/homelab-nuclei-operator:latest
``` ```
### 2. Create an Ingress Resource ### 2. Create an Ingress Resource
@@ -371,6 +455,21 @@ curl localhost:8080/metrics
## Uninstallation ## Uninstallation
### Using Helm
```bash
# Uninstall the operator
helm uninstall nuclei-operator -n nuclei-operator-system
# Remove the namespace (optional)
kubectl delete namespace nuclei-operator-system
# Remove CRDs (optional - this will delete all NucleiScan resources)
kubectl delete crd nucleiscans.nuclei.homelab.mortenolsen.pro
```
### Using kubectl/kustomize
```bash ```bash
# Remove all NucleiScan resources # Remove all NucleiScan resources
kubectl delete nucleiscans --all --all-namespaces kubectl delete nucleiscans --all --all-namespaces

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,21 @@
apiVersion: v2
name: nuclei-operator
description: A Kubernetes operator that automatically scans Ingress and VirtualService resources using Nuclei security scanner
type: application
version: 0.1.0
appVersion: "0.1.0"
home: https://github.com/morten-olsen/homelab-nuclei-operator
sources:
- https://github.com/morten-olsen/homelab-nuclei-operator
maintainers:
- name: Morten Olsen
url: https://github.com/morten-olsen
keywords:
- kubernetes
- operator
- security
- nuclei
- scanner
- ingress
- virtualservice
kubeVersion: ">=1.26.0-0"

View File

@@ -0,0 +1,233 @@
# Nuclei Operator Helm Chart
A Helm chart for deploying the Nuclei Operator - a Kubernetes operator that automatically scans Ingress and VirtualService resources using Nuclei security scanner.
## Prerequisites
- Kubernetes 1.26+
- Helm 3.0+
## Installation
### Add the Helm Repository
```bash
helm repo add nuclei-operator https://morten-olsen.github.io/homelab-nuclei-operator
helm repo update
```
### Install the Chart
```bash
helm install nuclei-operator nuclei-operator/nuclei-operator \
--namespace nuclei-operator-system \
--create-namespace
```
### Install with Custom Values
```bash
helm install nuclei-operator nuclei-operator/nuclei-operator \
--namespace nuclei-operator-system \
--create-namespace \
-f values.yaml
```
## Configuration
The following table lists the configurable parameters of the Nuclei Operator chart and their default values.
### General
| Parameter | Description | Default |
|-----------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `nameOverride` | Override the name of the chart | `""` |
| `fullnameOverride` | Override the full name of the chart | `""` |
### Image
| Parameter | Description | Default |
|-----------|-------------|---------|
| `image.repository` | Container image repository | `ghcr.io/morten-olsen/homelab-nuclei-operator` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `image.tag` | Image tag (defaults to chart appVersion) | `""` |
| `imagePullSecrets` | Image pull secrets | `[]` |
### Service Account
| Parameter | Description | Default |
|-----------|-------------|---------|
| `serviceAccount.create` | Create a service account | `true` |
| `serviceAccount.annotations` | Service account annotations | `{}` |
| `serviceAccount.name` | Service account name | `""` |
### Pod Configuration
| Parameter | Description | Default |
|-----------|-------------|---------|
| `podAnnotations` | Pod annotations | `{}` |
| `podLabels` | Pod labels | `{}` |
| `podSecurityContext.runAsNonRoot` | Run as non-root | `true` |
| `podSecurityContext.seccompProfile.type` | Seccomp profile type | `RuntimeDefault` |
### Container Security Context
| Parameter | Description | Default |
|-----------|-------------|---------|
| `securityContext.readOnlyRootFilesystem` | Read-only root filesystem | `false` |
| `securityContext.allowPrivilegeEscalation` | Allow privilege escalation | `false` |
| `securityContext.runAsNonRoot` | Run as non-root | `true` |
| `securityContext.runAsUser` | User ID | `65532` |
| `securityContext.capabilities.drop` | Dropped capabilities | `["ALL"]` |
### Resources
| Parameter | Description | Default |
|-----------|-------------|---------|
| `resources.limits.cpu` | CPU limit | `"2"` |
| `resources.limits.memory` | Memory limit | `"2Gi"` |
| `resources.requests.cpu` | CPU request | `"500m"` |
| `resources.requests.memory` | Memory request | `"512Mi"` |
### Scheduling
| Parameter | Description | Default |
|-----------|-------------|---------|
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
### Leader Election
| Parameter | Description | Default |
|-----------|-------------|---------|
| `leaderElection.enabled` | Enable leader election | `true` |
### Health Probes
| Parameter | Description | Default |
|-----------|-------------|---------|
| `healthProbes.livenessProbe.httpGet.path` | Liveness probe path | `/healthz` |
| `healthProbes.livenessProbe.httpGet.port` | Liveness probe port | `8081` |
| `healthProbes.livenessProbe.initialDelaySeconds` | Initial delay | `15` |
| `healthProbes.livenessProbe.periodSeconds` | Period | `20` |
| `healthProbes.readinessProbe.httpGet.path` | Readiness probe path | `/readyz` |
| `healthProbes.readinessProbe.httpGet.port` | Readiness probe port | `8081` |
| `healthProbes.readinessProbe.initialDelaySeconds` | Initial delay | `5` |
| `healthProbes.readinessProbe.periodSeconds` | Period | `10` |
### Metrics
| Parameter | Description | Default |
|-----------|-------------|---------|
| `metrics.enabled` | Enable metrics endpoint | `true` |
| `metrics.service.type` | Metrics service type | `ClusterIP` |
| `metrics.service.port` | Metrics service port | `8443` |
### Nuclei Scanner Configuration
| Parameter | Description | Default |
|-----------|-------------|---------|
| `nuclei.binaryPath` | Path to nuclei binary | `/usr/local/bin/nuclei` |
| `nuclei.templatesPath` | Path to nuclei templates | `/nuclei-templates` |
| `nuclei.timeout` | Scan timeout | `30m` |
| `nuclei.rescanAge` | Age before automatic rescan | `168h` |
| `nuclei.backoff.initial` | Initial backoff interval | `10s` |
| `nuclei.backoff.max` | Maximum backoff interval | `10m` |
| `nuclei.backoff.multiplier` | Backoff multiplier | `2.0` |
### ServiceMonitor (Prometheus Operator)
| Parameter | Description | Default |
|-----------|-------------|---------|
| `serviceMonitor.enabled` | Enable ServiceMonitor | `false` |
| `serviceMonitor.labels` | Additional labels | `{}` |
| `serviceMonitor.interval` | Scrape interval | `30s` |
| `serviceMonitor.scrapeTimeout` | Scrape timeout | `10s` |
### Network Policy
| Parameter | Description | Default |
|-----------|-------------|---------|
| `networkPolicy.enabled` | Enable network policy | `false` |
## Examples
### Basic Installation
```bash
helm install nuclei-operator nuclei-operator/nuclei-operator \
--namespace nuclei-operator-system \
--create-namespace
```
### With Prometheus Monitoring
```yaml
# values.yaml
metrics:
enabled: true
serviceMonitor:
enabled: true
labels:
release: prometheus
```
```bash
helm install nuclei-operator nuclei-operator/nuclei-operator \
--namespace nuclei-operator-system \
--create-namespace \
-f values.yaml
```
### With Custom Resource Limits
```yaml
# values.yaml
resources:
limits:
cpu: "4"
memory: "4Gi"
requests:
cpu: "1"
memory: "1Gi"
nuclei:
timeout: "1h"
rescanAge: "24h"
```
### With Node Affinity
```yaml
# values.yaml
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
- arm64
```
## Uninstallation
```bash
helm uninstall nuclei-operator -n nuclei-operator-system
```
To also remove the CRDs:
```bash
kubectl delete crd nucleiscans.nuclei.homelab.mortenolsen.pro
```
## Links
- [GitHub Repository](https://github.com/morten-olsen/homelab-nuclei-operator)
- [Nuclei Scanner](https://github.com/projectdiscovery/nuclei)

View File

@@ -0,0 +1,44 @@
Thank you for installing {{ .Chart.Name }}!
Your release is named {{ .Release.Name }}.
To learn more about the release, try:
$ helm status {{ .Release.Name }}
$ helm get all {{ .Release.Name }}
The nuclei-operator is now watching for Ingress and VirtualService resources.
To create a NucleiScan manually, you can apply a resource like:
apiVersion: nuclei.homelab.mortenolsen.pro/v1alpha1
kind: NucleiScan
metadata:
name: example-scan
spec:
sourceRef:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: my-ingress
namespace: default
uid: <ingress-uid>
targets:
- https://example.com
To view scan results:
$ kubectl get nucleiscans -A
$ kubectl describe nucleiscan <scan-name>
{{- if .Values.metrics.enabled }}
Metrics are enabled. The metrics service is available at:
{{ include "nuclei-operator.fullname" . }}-metrics-service:{{ .Values.metrics.service.port }}
{{- if .Values.serviceMonitor.enabled }}
A ServiceMonitor has been created for Prometheus Operator integration.
{{- end }}
{{- end }}
For more information, visit:
https://github.com/morten-olsen/homelab-nuclei-operator

View File

@@ -0,0 +1,71 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "nuclei-operator.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "nuclei-operator.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "nuclei-operator.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "nuclei-operator.labels" -}}
helm.sh/chart: {{ include "nuclei-operator.chart" . }}
{{ include "nuclei-operator.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "nuclei-operator.selectorLabels" -}}
app.kubernetes.io/name: {{ include "nuclei-operator.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
control-plane: controller-manager
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "nuclei-operator.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "nuclei-operator.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create the image name
*/}}
{{- define "nuclei-operator.image" -}}
{{- $tag := default .Chart.AppVersion .Values.image.tag }}
{{- printf "%s:%s" .Values.image.repository $tag }}
{{- end }}

View File

@@ -0,0 +1,322 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.19.0
name: nucleiscans.nuclei.homelab.mortenolsen.pro
labels: { { - include "nuclei-operator.labels" . | nindent 4 } }
spec:
group: nuclei.homelab.mortenolsen.pro
names:
kind: NucleiScan
listKind: NucleiScanList
plural: nucleiscans
shortNames:
- ns
- nscan
singular: nucleiscan
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .status.summary.totalFindings
name: Findings
type: integer
- jsonPath: .spec.sourceRef.kind
name: Source
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: NucleiScan is the Schema for the nucleiscans API
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: NucleiScanSpec defines the desired state of NucleiScan
properties:
schedule:
description: |-
Schedule for periodic rescanning in cron format
If empty, scan runs once
type: string
severity:
description: Severity filters scan results by severity level
items:
type: string
type: array
sourceRef:
description:
SourceRef references the Ingress or VirtualService being
scanned
properties:
apiVersion:
description: APIVersion of the source resource
type: string
kind:
description: Kind of the source resource - Ingress or VirtualService
enum:
- Ingress
- VirtualService
type: string
name:
description: Name of the source resource
type: string
namespace:
description: Namespace of the source resource
type: string
uid:
description: UID of the source resource for owner reference
type: string
required:
- apiVersion
- kind
- name
- namespace
- uid
type: object
suspend:
description: Suspend prevents scheduled scans from running
type: boolean
targets:
description:
Targets is the list of URLs to scan, extracted from the
source resource
items:
type: string
minItems: 1
type: array
templates:
description: |-
Templates specifies which Nuclei templates to use
If empty, uses default templates
items:
type: string
type: array
required:
- sourceRef
- targets
type: object
status:
description: NucleiScanStatus defines the observed state of NucleiScan
properties:
completionTime:
description: CompletionTime is when the last scan completed
format: date-time
type: string
conditions:
description: Conditions represent the latest available observations
items:
description:
Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
findings:
description: |-
Findings contains the array of scan results from Nuclei JSONL output
Each element is a parsed JSON object from Nuclei output
items:
description: Finding represents a single Nuclei scan finding
properties:
description:
description: Description provides details about the finding
type: string
extractedResults:
description:
ExtractedResults contains any data extracted by
the template
items:
type: string
type: array
host:
description: Host that was scanned
type: string
matchedAt:
description:
MatchedAt is the specific URL or endpoint where
the issue was found
type: string
metadata:
description: Metadata contains additional template metadata
type: object
x-kubernetes-preserve-unknown-fields: true
reference:
description:
Reference contains URLs to additional information
about the finding
items:
type: string
type: array
severity:
description: Severity of the finding
type: string
tags:
description: Tags associated with the finding
items:
type: string
type: array
templateId:
description: TemplateID is the Nuclei template identifier
type: string
templateName:
description: TemplateName is the human-readable template name
type: string
timestamp:
description: Timestamp when the finding was discovered
format: date-time
type: string
type:
description: Type of the finding - http, dns, ssl, etc.
type: string
required:
- host
- severity
- templateId
- timestamp
type: object
type: array
lastError:
description: LastError contains the error message if the scan failed
type: string
lastRetryTime:
description:
LastRetryTime is when the last availability check retry
occurred
format: date-time
type: string
lastScanTime:
description: LastScanTime is when the last scan was initiated
format: date-time
type: string
nextScheduledTime:
description:
NextScheduledTime is when the next scheduled scan will
run
format: date-time
type: string
observedGeneration:
description:
ObservedGeneration is the generation observed by the
controller
format: int64
type: integer
phase:
description: Phase represents the current scan phase
enum:
- Pending
- Running
- Completed
- Failed
type: string
retryCount:
description: |-
RetryCount tracks the number of consecutive availability check retries
Used for exponential backoff when waiting for targets
type: integer
summary:
description: Summary provides aggregated scan statistics
properties:
durationSeconds:
description: DurationSeconds is the duration of the scan in seconds
format: int64
type: integer
findingsBySeverity:
additionalProperties:
type: integer
description:
FindingsBySeverity breaks down findings by severity
level
type: object
targetsScanned:
description:
TargetsScanned is the number of targets that were
scanned
type: integer
totalFindings:
description: TotalFindings is the total number of findings
type: integer
required:
- targetsScanned
- totalFindings
type: object
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,99 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "nuclei-operator.fullname" . }}-controller-manager
namespace: {{ .Release.Namespace }}
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "nuclei-operator.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: manager
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "nuclei-operator.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "nuclei-operator.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: manager
image: {{ include "nuclei-operator.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- /manager
args:
{{- if .Values.leaderElection.enabled }}
- --leader-elect
{{- end }}
- --health-probe-bind-address=:8081
{{- if .Values.metrics.enabled }}
- --metrics-bind-address=:8443
- --metrics-secure=true
{{- end }}
env:
- name: NUCLEI_BINARY_PATH
value: {{ .Values.nuclei.binaryPath | quote }}
- name: NUCLEI_TEMPLATES_PATH
value: {{ .Values.nuclei.templatesPath | quote }}
- name: NUCLEI_TIMEOUT
value: {{ .Values.nuclei.timeout | quote }}
- name: NUCLEI_RESCAN_AGE
value: {{ .Values.nuclei.rescanAge | quote }}
- name: NUCLEI_BACKOFF_INITIAL
value: {{ .Values.nuclei.backoff.initial | quote }}
- name: NUCLEI_BACKOFF_MAX
value: {{ .Values.nuclei.backoff.max | quote }}
- name: NUCLEI_BACKOFF_MULTIPLIER
value: {{ .Values.nuclei.backoff.multiplier | quote }}
ports: []
securityContext:
{{- toYaml .Values.securityContext | nindent 10 }}
{{- with .Values.healthProbes.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.healthProbes.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 10 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
volumeMounts:
- name: nuclei-templates
mountPath: {{ .Values.nuclei.templatesPath }}
readOnly: true
- name: nuclei-cache
mountPath: /home/nonroot/.nuclei
volumes:
- name: nuclei-templates
emptyDir: {}
- name: nuclei-cache
emptyDir: {}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
terminationGracePeriodSeconds: 10

View File

@@ -0,0 +1,18 @@
{{- if .Values.metrics.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "nuclei-operator.fullname" . }}-metrics-service
namespace: {{ .Release.Namespace }}
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
spec:
type: {{ .Values.metrics.service.type }}
ports:
- name: https
port: {{ .Values.metrics.service.port }}
protocol: TCP
targetPort: 8443
selector:
{{- include "nuclei-operator.selectorLabels" . | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,192 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "nuclei-operator.fullname" . }}-manager-role
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- networking.istio.io
resources:
- virtualservices
verbs:
- get
- list
- watch
- apiGroups:
- networking.istio.io
resources:
- virtualservices/status
verbs:
- get
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses/status
verbs:
- get
- apiGroups:
- nuclei.homelab.mortenolsen.pro
resources:
- nucleiscans
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- nuclei.homelab.mortenolsen.pro
resources:
- nucleiscans/finalizers
verbs:
- update
- apiGroups:
- nuclei.homelab.mortenolsen.pro
resources:
- nucleiscans/status
verbs:
- get
- patch
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "nuclei-operator.fullname" . }}-manager-rolebinding
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "nuclei-operator.fullname" . }}-manager-role
subjects:
- kind: ServiceAccount
name: {{ include "nuclei-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
---
# Leader election role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "nuclei-operator.fullname" . }}-leader-election-role
namespace: {{ .Release.Namespace }}
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "nuclei-operator.fullname" . }}-leader-election-rolebinding
namespace: {{ .Release.Namespace }}
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "nuclei-operator.fullname" . }}-leader-election-role
subjects:
- kind: ServiceAccount
name: {{ include "nuclei-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- if .Values.metrics.enabled }}
---
# Metrics auth role
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "nuclei-operator.fullname" . }}-metrics-auth-role
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
- apiGroups:
- authorization.k8s.io
resources:
- subjectaccessreviews
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "nuclei-operator.fullname" . }}-metrics-auth-rolebinding
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "nuclei-operator.fullname" . }}-metrics-auth-role
subjects:
- kind: ServiceAccount
name: {{ include "nuclei-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
---
# Metrics reader role
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "nuclei-operator.fullname" . }}-metrics-reader
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
rules:
- nonResourceURLs:
- /metrics
verbs:
- get
{{- end }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "nuclei-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,25 @@
{{- if and .Values.metrics.enabled .Values.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "nuclei-operator.fullname" . }}-metrics-monitor
namespace: {{ .Release.Namespace }}
labels:
{{- include "nuclei-operator.labels" . | nindent 4 }}
{{- with .Values.serviceMonitor.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
endpoints:
- path: /metrics
port: https
scheme: https
bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
interval: {{ .Values.serviceMonitor.interval }}
scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }}
tlsConfig:
insecureSkipVerify: true
selector:
matchLabels:
{{- include "nuclei-operator.selectorLabels" . | nindent 6 }}
{{- end }}

View File

@@ -0,0 +1,133 @@
# Default values for nuclei-operator.
# Number of replicas for the controller manager
replicaCount: 1
image:
# Container image repository
repository: ghcr.io/morten-olsen/homelab-nuclei-operator
# Image pull policy
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion
tag: ""
# Image pull secrets for private registries
imagePullSecrets: []
# Override the name of the chart
nameOverride: ""
# Override the full name of the chart
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# Pod annotations
podAnnotations: {}
# Pod labels
podLabels: {}
# Pod security context
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
# Container security context
securityContext:
readOnlyRootFilesystem: false # Nuclei needs to write temporary files
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 65532
capabilities:
drop:
- ALL
# Resource limits and requests
resources:
limits:
cpu: "2"
memory: "2Gi"
requests:
cpu: "500m"
memory: "512Mi"
# Node selector for pod scheduling
nodeSelector: {}
# Tolerations for pod scheduling
tolerations: []
# Affinity rules for pod scheduling
affinity: {}
# Leader election configuration
leaderElection:
enabled: true
# Health probe configuration
healthProbes:
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
# Metrics configuration
metrics:
# Enable metrics endpoint
enabled: true
# Service configuration for metrics
service:
type: ClusterIP
port: 8443
# Nuclei scanner configuration
nuclei:
# Path to nuclei binary inside the container
binaryPath: "/usr/local/bin/nuclei"
# Path to nuclei templates
templatesPath: "/nuclei-templates"
# Scan timeout duration
timeout: "30m"
# Rescan age - how old scan results can be before triggering automatic rescan
# Set to "0" to disable automatic rescans based on age
rescanAge: "168h"
# Backoff configuration for target availability checks
backoff:
# Initial retry interval
initial: "10s"
# Maximum retry interval
max: "10m"
# Multiplier for exponential backoff
multiplier: "2.0"
# ServiceMonitor for Prometheus Operator
serviceMonitor:
# Enable ServiceMonitor creation
enabled: false
# Additional labels for the ServiceMonitor
labels: {}
# Scrape interval
interval: 30s
# Scrape timeout
scrapeTimeout: 10s
# Network policies
networkPolicy:
# Enable network policy
enabled: false