secureCodeBox/secureCodeBox-v2-alpha

View on GitHub
operator/controllers/execution/scans/scan_reconciler.go

Summary

Maintainability
C
7 hrs
Test Coverage
package scancontrollers
 
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
 
executionv1 "github.com/secureCodeBox/secureCodeBox-v2/operator/apis/execution/v1"
util "github.com/secureCodeBox/secureCodeBox-v2/operator/utils"
batch "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
 
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
 
Method `ScanReconciler.startScan` has 72 lines of code (exceeds 50 allowed). Consider refactoring.
Method `ScanReconciler.startScan` has 11 return statements (exceeds 4 allowed).
func (r *ScanReconciler) startScan(scan *executionv1.Scan) error {
ctx := context.Background()
namespacedName := fmt.Sprintf("%s/%s", scan.Namespace, scan.Name)
log := r.Log.WithValues("scan_init", namespacedName)
 
jobs, err := r.getJobsForScan(scan, client.MatchingLabels{"securecodebox.io/job-type": "scanner"})
if err != nil {
return err
}
if len(jobs.Items) > 0 {
log.V(8).Info("Job already exists. Doesn't need to be created.")
return nil
}
 
// Add s3 storage finalizer to scan
if !containsString(scan.ObjectMeta.Finalizers, s3StorageFinalizer) {
scan.ObjectMeta.Finalizers = append(scan.ObjectMeta.Finalizers, s3StorageFinalizer)
if err := r.Update(context.Background(), scan); err != nil {
return err
}
}
 
// get the ScanType for the scan
var scanType executionv1.ScanType
if err := r.Get(ctx, types.NamespacedName{Name: scan.Spec.ScanType, Namespace: scan.Namespace}, &scanType); err != nil {
log.V(7).Info("Unable to fetch ScanType")
 
scan.Status.State = "Errored"
scan.Status.ErrorDescription = fmt.Sprintf("Configured ScanType '%s' not found in '%s' namespace. You'll likely need to deploy the ScanType.", scan.Spec.ScanType, scan.Namespace)
if err := r.Status().Update(ctx, scan); err != nil {
r.Log.Error(err, "unable to update Scan status")
return err
}
 
return fmt.Errorf("No ScanType of type '%s' found", scan.Spec.ScanType)
}
log.Info("Matching ScanType Found", "ScanType", scanType.Name)
 
rules := []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get"},
},
}
r.ensureServiceAccountExists(
scan.Namespace,
"lurcher",
"Lurcher is used to extract results from secureCodeBox Scans. It needs rights to get and watch the status of pods to see when the scans have finished.",
rules,
)
 
job, err := r.constructJobForScan(scan, &scanType)
if err != nil {
log.Error(err, "unable to create job object ScanType")
return err
}
 
log.V(7).Info("Constructed Job object", "job args", strings.Join(job.Spec.Template.Spec.Containers[0].Args, ", "))
 
if err := r.Create(ctx, job); err != nil {
log.Error(err, "unable to create Job for Scan", "job", job)
return err
}
 
scan.Status.State = "Scanning"
scan.Status.RawResultType = scanType.Spec.ExtractResults.Type
scan.Status.RawResultFile = filepath.Base(scanType.Spec.ExtractResults.Location)
 
findingsDownloadURL, err := r.PresignedGetURL(scan.UID, "findings.json", 7*24*time.Hour)
if err != nil {
r.Log.Error(err, "Could not get presigned url from s3 or compatible storage provider")
return err
}
scan.Status.FindingDownloadLink = findingsDownloadURL
rawResultDownloadURL, err := r.PresignedGetURL(scan.UID, scan.Status.RawResultFile, 7*24*time.Hour)
if err != nil {
return err
}
scan.Status.RawResultDownloadLink = rawResultDownloadURL
 
if err := r.Status().Update(ctx, scan); err != nil {
log.Error(err, "unable to update Scan status")
return err
}
 
log.V(1).Info("created Job for Scan", "job", job)
return nil
}
 
// Checking if scan has completed
func (r *ScanReconciler) checkIfScanIsCompleted(scan *executionv1.Scan) error {
ctx := context.Background()
 
status, err := r.checkIfJobIsCompleted(scan, client.MatchingLabels{"securecodebox.io/job-type": "scanner"})
if err != nil {
return err
}
 
switch status {
case completed:
r.Log.V(7).Info("Scan is completed")
scan.Status.State = "ScanCompleted"
if err := r.Status().Update(ctx, scan); err != nil {
r.Log.Error(err, "unable to update Scan status")
return err
}
case failed:
scan.Status.State = "Errored"
scan.Status.ErrorDescription = "Failed to run the Scan Container, check k8s Job and its logs for more details"
if err := r.Status().Update(ctx, scan); err != nil {
r.Log.Error(err, "unable to update Scan status")
return err
}
}
// Either Incomplete or Unknown, nothing we can do, other then giving it some more time...
return nil
}
 
Method `ScanReconciler.constructJobForScan` has 133 lines of code (exceeds 50 allowed). Consider refactoring.
func (r *ScanReconciler) constructJobForScan(scan *executionv1.Scan, scanType *executionv1.ScanType) (*batch.Job, error) {
filename := filepath.Base(scanType.Spec.ExtractResults.Location)
resultUploadURL, err := r.PresignedPutURL(scan.UID, filename, defaultPresignDuration)
if err != nil {
r.Log.Error(err, "Could not get presigned url from s3 or compatible storage provider")
return nil, err
}
 
if len(scanType.Spec.JobTemplate.Spec.Template.Spec.Containers) < 1 {
return nil, errors.New("ScanType must at least contain one container in which the scanner is running")
}
 
labels := scan.ObjectMeta.DeepCopy().Labels
if labels == nil {
labels = make(map[string]string)
}
labels["securecodebox.io/job-type"] = "scanner"
job := &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
GenerateName: util.TruncateName(fmt.Sprintf("scan-%s", scan.Name)),
Namespace: scan.Namespace,
},
Spec: *scanType.Spec.JobTemplate.Spec.DeepCopy(),
}
 
podAnnotations := scanType.Spec.JobTemplate.DeepCopy().Annotations
if podAnnotations == nil {
podAnnotations = make(map[string]string)
}
podAnnotations["securecodebox.io/job-type"] = "scanner"
// Ensuring that istio doesn't inject a sidecar proxy.
podAnnotations["sidecar.istio.io/inject"] = "false"
job.Spec.Template.Annotations = podAnnotations
 
job.Spec.Template.Spec.ServiceAccountName = "lurcher"
 
// merging volume definition from ScanType (if existing) with standard results volume
if job.Spec.Template.Spec.Containers[0].VolumeMounts == nil || len(job.Spec.Template.Spec.Containers[0].VolumeMounts) == 0 {
job.Spec.Template.Spec.Volumes = []corev1.Volume{}
}
job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
Name: "scan-results",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
})
 
// merging volume mounts (for the primary scanner container) from ScanType (if existing) with standard results volume mount
if job.Spec.Template.Spec.Containers[0].VolumeMounts == nil || len(job.Spec.Template.Spec.Containers[0].VolumeMounts) == 0 {
job.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{}
}
job.Spec.Template.Spec.Containers[0].VolumeMounts = append(
job.Spec.Template.Spec.Containers[0].VolumeMounts,
corev1.VolumeMount{
Name: "scan-results",
MountPath: "/home/securecodebox/",
},
)
 
// Get lurcher image config from env
lurcherImage := os.Getenv("LURCHER_IMAGE")
if lurcherImage == "" {
lurcherImage = "securecodebox/lurcher:latest"
}
lurcherPullPolicyRaw := os.Getenv("LURCHER_PULL_POLICY")
var lurcherPullPolicy corev1.PullPolicy
switch lurcherPullPolicyRaw {
case "Always":
lurcherPullPolicy = corev1.PullAlways
case "IfNotPresent":
lurcherPullPolicy = corev1.PullIfNotPresent
case "Never":
lurcherPullPolicy = corev1.PullNever
case "":
lurcherPullPolicy = corev1.PullAlways
default:
return nil, fmt.Errorf("Unknown imagePull Policy for lurcher: %s", lurcherPullPolicyRaw)
}
 
falsePointer := false
truePointer := true
 
lurcherSidecar := &corev1.Container{
Name: "lurcher",
Image: lurcherImage,
ImagePullPolicy: lurcherPullPolicy,
Args: []string{
"--container",
job.Spec.Template.Spec.Containers[0].Name,
"--file",
scanType.Spec.ExtractResults.Location,
"--url",
resultUploadURL,
},
Env: []corev1.EnvVar{
{
Name: "NAMESPACE",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("20m"),
corev1.ResourceMemory: resource.MustParse("20Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("100m"),
corev1.ResourceMemory: resource.MustParse("100Mi"),
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "scan-results",
MountPath: "/home/securecodebox/",
ReadOnly: true,
},
},
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: &truePointer,
AllowPrivilegeEscalation: &falsePointer,
ReadOnlyRootFilesystem: &truePointer,
Privileged: &falsePointer,
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"all"},
},
},
}
 
job.Spec.Template.Spec.Containers = append(job.Spec.Template.Spec.Containers, *lurcherSidecar)
 
if err := ctrl.SetControllerReference(scan, job, r.Scheme); err != nil {
return nil, err
}
 
command := append(
scanType.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command,
scan.Spec.Parameters...,
)
 
// Merge Env from ScanTemplate with Env defined in scan
job.Spec.Template.Spec.Containers[0].Env = append(
job.Spec.Template.Spec.Containers[0].Env,
scan.Spec.Env...,
)
 
// Using command over args
job.Spec.Template.Spec.Containers[0].Command = command
job.Spec.Template.Spec.Containers[0].Args = nil
 
return job, nil
}