This commit is contained in:
Morten Olsen
2025-12-12 11:10:01 +01:00
commit 277fc459d5
64 changed files with 8625 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1alpha1 contains API Schema definitions for the nuclei v1alpha1 API group.
// +kubebuilder:object:generate=true
// +groupName=nuclei.homelab.mortenolsen.pro
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects.
GroupVersion = schema.GroupVersion{Group: "nuclei.homelab.mortenolsen.pro", Version: "v1alpha1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)

View File

@@ -0,0 +1,224 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// SourceReference identifies the Ingress or VirtualService that triggered this scan
type SourceReference struct {
// APIVersion of the source resource
// +kubebuilder:validation:Required
APIVersion string `json:"apiVersion"`
// Kind of the source resource - Ingress or VirtualService
// +kubebuilder:validation:Enum=Ingress;VirtualService
Kind string `json:"kind"`
// Name of the source resource
// +kubebuilder:validation:Required
Name string `json:"name"`
// Namespace of the source resource
// +kubebuilder:validation:Required
Namespace string `json:"namespace"`
// UID of the source resource for owner reference
// +kubebuilder:validation:Required
UID string `json:"uid"`
}
// NucleiScanSpec defines the desired state of NucleiScan
type NucleiScanSpec struct {
// SourceRef references the Ingress or VirtualService being scanned
// +kubebuilder:validation:Required
SourceRef SourceReference `json:"sourceRef"`
// Targets is the list of URLs to scan, extracted from the source resource
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinItems=1
Targets []string `json:"targets"`
// Templates specifies which Nuclei templates to use
// If empty, uses default templates
// +optional
Templates []string `json:"templates,omitempty"`
// Severity filters scan results by severity level
// +kubebuilder:validation:Enum=info;low;medium;high;critical
// +optional
Severity []string `json:"severity,omitempty"`
// Schedule for periodic rescanning in cron format
// If empty, scan runs once
// +optional
Schedule string `json:"schedule,omitempty"`
// Suspend prevents scheduled scans from running
// +optional
Suspend bool `json:"suspend,omitempty"`
}
// ScanPhase represents the current phase of the scan
// +kubebuilder:validation:Enum=Pending;Running;Completed;Failed
type ScanPhase string
const (
ScanPhasePending ScanPhase = "Pending"
ScanPhaseRunning ScanPhase = "Running"
ScanPhaseCompleted ScanPhase = "Completed"
ScanPhaseFailed ScanPhase = "Failed"
)
// Finding represents a single Nuclei scan finding
type Finding struct {
// TemplateID is the Nuclei template identifier
TemplateID string `json:"templateId"`
// TemplateName is the human-readable template name
// +optional
TemplateName string `json:"templateName,omitempty"`
// Severity of the finding
Severity string `json:"severity"`
// Type of the finding - http, dns, ssl, etc.
// +optional
Type string `json:"type,omitempty"`
// Host that was scanned
Host string `json:"host"`
// MatchedAt is the specific URL or endpoint where the issue was found
// +optional
MatchedAt string `json:"matchedAt,omitempty"`
// ExtractedResults contains any data extracted by the template
// +optional
ExtractedResults []string `json:"extractedResults,omitempty"`
// Description provides details about the finding
// +optional
Description string `json:"description,omitempty"`
// Reference contains URLs to additional information about the finding
// +optional
Reference []string `json:"reference,omitempty"`
// Tags associated with the finding
// +optional
Tags []string `json:"tags,omitempty"`
// Timestamp when the finding was discovered
Timestamp metav1.Time `json:"timestamp"`
// Metadata contains additional template metadata
// +kubebuilder:pruning:PreserveUnknownFields
// +optional
Metadata *runtime.RawExtension `json:"metadata,omitempty"`
}
// ScanSummary provides aggregated statistics about the scan
type ScanSummary struct {
// TotalFindings is the total number of findings
TotalFindings int `json:"totalFindings"`
// FindingsBySeverity breaks down findings by severity level
// +optional
FindingsBySeverity map[string]int `json:"findingsBySeverity,omitempty"`
// TargetsScanned is the number of targets that were scanned
TargetsScanned int `json:"targetsScanned"`
// DurationSeconds is the duration of the scan in seconds
// +optional
DurationSeconds int64 `json:"durationSeconds,omitempty"`
}
// NucleiScanStatus defines the observed state of NucleiScan
type NucleiScanStatus struct {
// Phase represents the current scan phase
// +optional
Phase ScanPhase `json:"phase,omitempty"`
// Conditions represent the latest available observations
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// LastScanTime is when the last scan was initiated
// +optional
LastScanTime *metav1.Time `json:"lastScanTime,omitempty"`
// CompletionTime is when the last scan completed
// +optional
CompletionTime *metav1.Time `json:"completionTime,omitempty"`
// NextScheduledTime is when the next scheduled scan will run
// +optional
NextScheduledTime *metav1.Time `json:"nextScheduledTime,omitempty"`
// Summary provides aggregated scan statistics
// +optional
Summary *ScanSummary `json:"summary,omitempty"`
// Findings contains the array of scan results from Nuclei JSONL output
// Each element is a parsed JSON object from Nuclei output
// +optional
Findings []Finding `json:"findings,omitempty"`
// LastError contains the error message if the scan failed
// +optional
LastError string `json:"lastError,omitempty"`
// ObservedGeneration is the generation observed by the controller
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=ns;nscan
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Findings",type=integer,JSONPath=`.status.summary.totalFindings`
// +kubebuilder:printcolumn:name="Source",type=string,JSONPath=`.spec.sourceRef.kind`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// NucleiScan is the Schema for the nucleiscans API
type NucleiScan struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec NucleiScanSpec `json:"spec,omitempty"`
Status NucleiScanStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// NucleiScanList contains a list of NucleiScan
type NucleiScanList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []NucleiScan `json:"items"`
}
func init() {
SchemeBuilder.Register(&NucleiScan{}, &NucleiScanList{})
}

View File

@@ -0,0 +1,235 @@
//go:build !ignore_autogenerated
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Finding) DeepCopyInto(out *Finding) {
*out = *in
if in.ExtractedResults != nil {
in, out := &in.ExtractedResults, &out.ExtractedResults
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Reference != nil {
in, out := &in.Reference, &out.Reference
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Tags != nil {
in, out := &in.Tags, &out.Tags
*out = make([]string, len(*in))
copy(*out, *in)
}
in.Timestamp.DeepCopyInto(&out.Timestamp)
if in.Metadata != nil {
in, out := &in.Metadata, &out.Metadata
*out = new(runtime.RawExtension)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Finding.
func (in *Finding) DeepCopy() *Finding {
if in == nil {
return nil
}
out := new(Finding)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NucleiScan) DeepCopyInto(out *NucleiScan) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NucleiScan.
func (in *NucleiScan) DeepCopy() *NucleiScan {
if in == nil {
return nil
}
out := new(NucleiScan)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NucleiScan) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NucleiScanList) DeepCopyInto(out *NucleiScanList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]NucleiScan, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NucleiScanList.
func (in *NucleiScanList) DeepCopy() *NucleiScanList {
if in == nil {
return nil
}
out := new(NucleiScanList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NucleiScanList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NucleiScanSpec) DeepCopyInto(out *NucleiScanSpec) {
*out = *in
out.SourceRef = in.SourceRef
if in.Targets != nil {
in, out := &in.Targets, &out.Targets
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Templates != nil {
in, out := &in.Templates, &out.Templates
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Severity != nil {
in, out := &in.Severity, &out.Severity
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NucleiScanSpec.
func (in *NucleiScanSpec) DeepCopy() *NucleiScanSpec {
if in == nil {
return nil
}
out := new(NucleiScanSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NucleiScanStatus) DeepCopyInto(out *NucleiScanStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.LastScanTime != nil {
in, out := &in.LastScanTime, &out.LastScanTime
*out = (*in).DeepCopy()
}
if in.CompletionTime != nil {
in, out := &in.CompletionTime, &out.CompletionTime
*out = (*in).DeepCopy()
}
if in.NextScheduledTime != nil {
in, out := &in.NextScheduledTime, &out.NextScheduledTime
*out = (*in).DeepCopy()
}
if in.Summary != nil {
in, out := &in.Summary, &out.Summary
*out = new(ScanSummary)
(*in).DeepCopyInto(*out)
}
if in.Findings != nil {
in, out := &in.Findings, &out.Findings
*out = make([]Finding, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NucleiScanStatus.
func (in *NucleiScanStatus) DeepCopy() *NucleiScanStatus {
if in == nil {
return nil
}
out := new(NucleiScanStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScanSummary) DeepCopyInto(out *ScanSummary) {
*out = *in
if in.FindingsBySeverity != nil {
in, out := &in.FindingsBySeverity, &out.FindingsBySeverity
*out = make(map[string]int, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScanSummary.
func (in *ScanSummary) DeepCopy() *ScanSummary {
if in == nil {
return nil
}
out := new(ScanSummary)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SourceReference) DeepCopyInto(out *SourceReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceReference.
func (in *SourceReference) DeepCopy() *SourceReference {
if in == nil {
return nil
}
out := new(SourceReference)
in.DeepCopyInto(out)
return out
}