secureCodeBox/secureCodeBox

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

Summary

Maintainability
D
1 day
Test Coverage
// SPDX-FileCopyrightText: the secureCodeBox authors
//
// SPDX-License-Identifier: Apache-2.0

package scancontrollers

import (
    "context"
    "fmt"
    "strings"

    executionv1 "github.com/secureCodeBox/secureCodeBox/operator/apis/execution/v1"
    util "github.com/secureCodeBox/secureCodeBox/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"
)

func (r *ScanReconciler) startParser(scan *executionv1.Scan) error {
    ctx := context.Background()
    namespacedName := fmt.Sprintf("%s/%s", scan.Namespace, scan.Name)
    log := r.Log.WithValues("scan_parse", namespacedName)

    jobs, err := r.getJobsForScan(scan, client.MatchingLabels{"securecodebox.io/job-type": "parser"})
    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
    }

    parseType := scan.Status.RawResultType

    // get the parse definition matching the parseType of the scan result
    var parseDefinitionSpec executionv1.ParseDefinitionSpec
    if scan.Spec.ResourceMode == nil || *scan.Spec.ResourceMode == executionv1.NamespaceLocal {
        var parseDefinition executionv1.ParseDefinition
        if err := r.Get(ctx, types.NamespacedName{Name: parseType, Namespace: scan.Namespace}, &parseDefinition); err != nil {
            log.V(7).Info("Unable to fetch ParseDefinition")

            scan.Status.State = "Errored"
            scan.Status.ErrorDescription = fmt.Sprintf("No ParseDefinition for ResultType '%s' found in Scans Namespace.", parseType)
            if err := r.Status().Update(ctx, scan); err != nil {
                r.Log.Error(err, "unable to update Scan status")
                return err
            }

            return fmt.Errorf("No ParseDefinition of type '%s' found", parseType)
        }
        log.Info("Matching ParseDefinition Found", "ParseDefinition", parseType)
        parseDefinitionSpec = parseDefinition.Spec
    } else if *scan.Spec.ResourceMode == executionv1.ClusterWide {
        var clusterParseDefinition executionv1.ClusterParseDefinition
        if err := r.Get(ctx, types.NamespacedName{Name: parseType}, &clusterParseDefinition); err != nil {
            log.V(7).Info("Unable to fetch ClusterParseDefinition")

            scan.Status.State = "Errored"
            scan.Status.ErrorDescription = fmt.Sprintf("No ClusterParseDefinition for ResultType '%s' found.", parseType)
            if err := r.Status().Update(ctx, scan); err != nil {
                r.Log.Error(err, "unable to update Scan status")
                return err
            }

            return fmt.Errorf("No ClusterParseDefinition of type '%s' found", parseType)
        }
        log.Info("Matching ClusterParseDefinition Found", "ClusterParseDefinition", parseType)
        parseDefinitionSpec = clusterParseDefinition.Spec
    }

    urlExpirationDuration, err := util.GetUrlExpirationDuration(util.ParserController)
    if err != nil {
        r.Log.Error(err, "Failed to parse parser url expiration")
        panic(err)
    }

    findingsUploadURL, err := r.PresignedPutURL(*scan, "findings.json", urlExpirationDuration)
    if err != nil {
        r.Log.Error(err, "Could not get presigned url from s3 or compatible storage provider")
        return err
    }
    rawResultDownloadURL, err := r.PresignedGetURL(*scan, scan.Status.RawResultFile, urlExpirationDuration)
    if err != nil {
        return err
    }

    rules := []rbacv1.PolicyRule{
        {
            APIGroups: []string{"execution.securecodebox.io"},
            Resources: []string{"scans"},
            Verbs:     []string{"get"},
        },
        {
            APIGroups: []string{"execution.securecodebox.io"},
            Resources: []string{"scans/status"},
            Verbs:     []string{"get", "patch"},
        },
        {
            APIGroups: []string{"execution.securecodebox.io"},
            Resources: []string{"parsedefinitions"},
            Verbs:     []string{"get"},
        },
    }
    r.ensureServiceAccountExists(
        scan.Namespace,
        "parser",
        "Parser need to access the status of Scans to update how many findings have been identified",
        rules,
    )

    labels := scan.ObjectMeta.DeepCopy().Labels
    if labels == nil {
        labels = make(map[string]string)
    }
    labels["securecodebox.io/job-type"] = "parser"
    automountServiceAccountToken := true
    var backOffLimit int32 = 3
    truePointer := true
    falsePointer := false

    resources := corev1.ResourceRequirements{
        Requests: corev1.ResourceList{
            corev1.ResourceCPU:    resource.MustParse("200m"),
            corev1.ResourceMemory: resource.MustParse("100Mi"),
        },
        Limits: corev1.ResourceList{
            corev1.ResourceCPU:    resource.MustParse("400m"),
            corev1.ResourceMemory: resource.MustParse("200Mi"),
        },
    }
    if len(parseDefinitionSpec.Resources.Requests) != 0 || len(parseDefinitionSpec.Resources.Limits) != 0 {
        resources = parseDefinitionSpec.Resources
    }

    job := &batch.Job{
        ObjectMeta: metav1.ObjectMeta{
            Annotations:  make(map[string]string),
            GenerateName: util.TruncateName(fmt.Sprintf("parse-%s", scan.Name)),
            Namespace:    scan.Namespace,
            Labels:       labels,
        },
        Spec: batch.JobSpec{
            TTLSecondsAfterFinished: parseDefinitionSpec.TTLSecondsAfterFinished,
            BackoffLimit:            &backOffLimit,
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: map[string]string{
                        "app.kubernetes.io/managed-by": "securecodebox",
                    },
                    Annotations: map[string]string{
                        "auto-discovery.securecodebox.io/ignore": "true",
                        "sidecar.istio.io/inject":                "false",
                    },
                },
                Spec: corev1.PodSpec{
                    RestartPolicy:      corev1.RestartPolicyNever,
                    ServiceAccountName: "parser",
                    ImagePullSecrets:   parseDefinitionSpec.ImagePullSecrets,
                    Containers: []corev1.Container{
                        {
                            Name:  "parser",
                            Image: parseDefinitionSpec.Image,
                            Env: []corev1.EnvVar{
                                {
                                    Name: "NAMESPACE",
                                    ValueFrom: &corev1.EnvVarSource{
                                        FieldRef: &corev1.ObjectFieldSelector{
                                            FieldPath: "metadata.namespace",
                                        },
                                    },
                                },
                                {
                                    Name:  "SCAN_NAME",
                                    Value: scan.Name,
                                },
                            },
                            Args: []string{
                                rawResultDownloadURL,
                                findingsUploadURL,
                            },
                            ImagePullPolicy: parseDefinitionSpec.ImagePullPolicy,
                            Resources:       resources,
                            SecurityContext: &corev1.SecurityContext{
                                RunAsNonRoot:             &truePointer,
                                AllowPrivilegeEscalation: &falsePointer,
                                ReadOnlyRootFilesystem:   &truePointer,
                                Privileged:               &falsePointer,
                                Capabilities: &corev1.Capabilities{
                                    Drop: []corev1.Capability{"all"},
                                },
                            },
                        },
                    },
                    AutomountServiceAccountToken: &automountServiceAccountToken,
                },
            },
        },
    }
    job.Spec.Template.Labels = util.MergeStringMaps(job.Spec.Template.Labels, scan.ObjectMeta.DeepCopy().Labels)

    // Merge Env from ParserTemplate
    job.Spec.Template.Spec.Containers[0].Env = append(
        job.Spec.Template.Spec.Containers[0].Env,
        parseDefinitionSpec.Env...,
    )
    // Merge VolumeMounts from ParserTemplate
    job.Spec.Template.Spec.Containers[0].VolumeMounts = append(
        job.Spec.Template.Spec.Containers[0].VolumeMounts,
        parseDefinitionSpec.VolumeMounts...,
    )
    // Merge Volumes from ParserTemplate
    job.Spec.Template.Spec.Volumes = append(
        job.Spec.Template.Spec.Volumes,
        parseDefinitionSpec.Volumes...,
    )

    // Set affinity based on scan, if defined, or parseDefinition if not overridden by scan
    if scan.Spec.Affinity != nil {
        job.Spec.Template.Spec.Affinity = scan.Spec.Affinity
    } else {
        job.Spec.Template.Spec.Affinity = parseDefinitionSpec.Affinity
    }

    // Set tolerations, either from parseDefinition or from scan
    if scan.Spec.Tolerations != nil {
        job.Spec.Template.Spec.Tolerations = scan.Spec.Tolerations
    } else {
        job.Spec.Template.Spec.Tolerations = parseDefinitionSpec.Tolerations
    }

    r.Log.V(8).Info("Configuring customCACerts for Parser")
    injectCustomCACertsIfConfigured(job)

    if err := ctrl.SetControllerReference(scan, job, r.Scheme); err != nil {
        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 Parser", "job", job)
        return err
    }

    scan.Status.State = "Parsing"
    if err := r.Status().Update(ctx, scan); err != nil {
        log.Error(err, "unable to update Scan status")
        return err
    }

    log.V(7).Info("created Parse Job for Scan", "job", job)
    return nil
}

func (r *ScanReconciler) checkIfParsingIsCompleted(scan *executionv1.Scan) error {
    ctx := context.Background()

    status, err := r.checkIfJobIsCompleted(scan, client.MatchingLabels{"securecodebox.io/job-type": "parser"})
    if err != nil {
        return err
    }

    switch status {
    case completed:
        r.Log.V(7).Info("Parsing is completed")
        scan.Status.State = "ParseCompleted"
        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 Parser. This is likely a Bug, we would like to know about. Please open up a Issue on GitHub."
        if err := r.Status().Update(ctx, scan); err != nil {
            r.Log.Error(err, "unable to update Scan status")
            return err
        }
    }

    return nil
}