328 lines
10 KiB
Go
328 lines
10 KiB
Go
package securityscan
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
v1monitoringclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1"
|
|
"github.com/sirupsen/logrus"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
|
|
detector "github.com/rancher/kubernetes-provider-detector"
|
|
"github.com/rancher/wrangler/v3/pkg/apply"
|
|
appsctl "github.com/rancher/wrangler/v3/pkg/generated/controllers/apps"
|
|
appsctlv1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/apps/v1"
|
|
batchctl "github.com/rancher/wrangler/v3/pkg/generated/controllers/batch"
|
|
batchctlv1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/batch/v1"
|
|
corectl "github.com/rancher/wrangler/v3/pkg/generated/controllers/core"
|
|
corectlv1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1"
|
|
"github.com/rancher/wrangler/v3/pkg/start"
|
|
|
|
"sync"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
operatorapiv1 "github.com/rancher/compliance-operator/pkg/apis/compliance.cattle.io/v1"
|
|
operatorctl "github.com/rancher/compliance-operator/pkg/generated/controllers/compliance.cattle.io"
|
|
operatorctlv1 "github.com/rancher/compliance-operator/pkg/generated/controllers/compliance.cattle.io/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
)
|
|
|
|
type Controller struct {
|
|
Namespace string
|
|
Name string
|
|
ClusterProvider string
|
|
KubernetesVersion string
|
|
ImageConfig *operatorapiv1.ScanImageConfig
|
|
|
|
kcs *kubernetes.Clientset
|
|
cfg *rest.Config
|
|
coreFactory *corectl.Factory
|
|
batchFactory *batchctl.Factory
|
|
appsFactory *appsctl.Factory
|
|
complianceFactory *operatorctl.Factory
|
|
apply apply.Apply
|
|
monitoringClient v1monitoringclient.MonitoringV1Interface
|
|
|
|
mu *sync.Mutex
|
|
currentScanName string
|
|
|
|
numTestsFailed *prometheus.GaugeVec
|
|
numScansComplete *prometheus.CounterVec
|
|
numTestsSkipped *prometheus.GaugeVec
|
|
numTestsTotal *prometheus.GaugeVec
|
|
numTestsNA *prometheus.GaugeVec
|
|
numTestsPassed *prometheus.GaugeVec
|
|
numTestsWarn *prometheus.GaugeVec
|
|
|
|
scans operatorctlv1.ClusterScanController
|
|
jobs batchctlv1.JobController
|
|
configmaps corectlv1.ConfigMapController
|
|
configMapCache corectlv1.ConfigMapCache
|
|
services corectlv1.ServiceController
|
|
pods corectlv1.PodController
|
|
podCache corectlv1.PodCache
|
|
daemonsets appsctlv1.DaemonSetController
|
|
daemonsetCache appsctlv1.DaemonSetCache
|
|
securityScanJobTolerations []corev1.Toleration
|
|
}
|
|
|
|
func NewController(ctx context.Context, cfg *rest.Config, namespace, name string,
|
|
imgConfig *operatorapiv1.ScanImageConfig, securityScanJobTolerations []corev1.Toleration) (ctl *Controller, err error) {
|
|
if cfg == nil {
|
|
cfg, err = rest.InClusterConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
ctl = &Controller{
|
|
Namespace: namespace,
|
|
Name: name,
|
|
ImageConfig: imgConfig,
|
|
mu: &sync.Mutex{},
|
|
}
|
|
|
|
ctl.kcs, err = kubernetes.NewForConfig(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctl.cfg = cfg
|
|
|
|
clientset, err := kubernetes.NewForConfig(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ctl.ClusterProvider, err = detectClusterProvider(ctx, clientset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logrus.Infof("ClusterProvider detected %v", ctl.ClusterProvider)
|
|
|
|
ctl.KubernetesVersion, err = detectKubernetesVersion(ctx, clientset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logrus.Infof("KubernetesVersion detected %v", ctl.KubernetesVersion)
|
|
|
|
ctl.apply, err = apply.NewForConfig(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ctl.complianceFactory, err = operatorctl.NewFactoryFromConfig(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error building securityscan NewFactoryFromConfig: %w", err)
|
|
}
|
|
|
|
ctl.batchFactory, err = batchctl.NewFactoryFromConfig(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error building batch NewFactoryFromConfig: %w", err)
|
|
}
|
|
|
|
ctl.coreFactory, err = corectl.NewFactoryFromConfig(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error building core NewFactoryFromConfig: %w", err)
|
|
}
|
|
|
|
ctl.appsFactory, err = appsctl.NewFactoryFromConfig(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error building apps NewFactoryFromConfig: %w", err)
|
|
}
|
|
|
|
ctl.monitoringClient, err = v1monitoringclient.NewForConfig(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error building v1 monitoring client from config: %w", err)
|
|
}
|
|
|
|
err = initializeMetrics(ctl)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error registering Compliance Metrics: %w", err)
|
|
}
|
|
|
|
ctl.scans = ctl.complianceFactory.Compliance().V1().ClusterScan()
|
|
ctl.jobs = ctl.batchFactory.Batch().V1().Job()
|
|
ctl.configmaps = ctl.coreFactory.Core().V1().ConfigMap()
|
|
ctl.configMapCache = ctl.coreFactory.Core().V1().ConfigMap().Cache()
|
|
ctl.services = ctl.coreFactory.Core().V1().Service()
|
|
ctl.pods = ctl.coreFactory.Core().V1().Pod()
|
|
ctl.podCache = ctl.coreFactory.Core().V1().Pod().Cache()
|
|
ctl.daemonsets = ctl.appsFactory.Apps().V1().DaemonSet()
|
|
ctl.daemonsetCache = ctl.appsFactory.Apps().V1().DaemonSet().Cache()
|
|
ctl.securityScanJobTolerations = securityScanJobTolerations
|
|
return ctl, nil
|
|
}
|
|
|
|
func (c *Controller) Start(ctx context.Context, threads int, _ time.Duration) error {
|
|
// register our handlers
|
|
if err := c.handleJobs(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := c.handlePods(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := c.handleClusterScans(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := c.handleScheduledClusterScans(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := c.handleClusterScanMetrics(ctx); err != nil {
|
|
return err
|
|
}
|
|
return start.All(ctx, threads, c.complianceFactory, c.coreFactory, c.batchFactory)
|
|
}
|
|
|
|
func (c *Controller) refreshClusterKubernetesVersion(ctx context.Context) error {
|
|
clusterK8sVersion, err := detectKubernetesVersion(ctx, c.kcs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !strings.EqualFold(clusterK8sVersion, c.KubernetesVersion) {
|
|
c.KubernetesVersion = clusterK8sVersion
|
|
logrus.Infof("New KubernetesVersion detected %v", c.KubernetesVersion)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func detectClusterProvider(ctx context.Context, k8sClient kubernetes.Interface) (string, error) {
|
|
provider, err := detector.DetectProvider(ctx, k8sClient)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return provider, err
|
|
}
|
|
|
|
func detectKubernetesVersion(_ context.Context, k8sClient kubernetes.Interface) (string, error) {
|
|
v, err := k8sClient.Discovery().ServerVersion()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return v.GitVersion, nil
|
|
}
|
|
|
|
func initializeMetrics(ctl *Controller) error {
|
|
ctl.numTestsFailed = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Name: "compliance_scan_num_tests_fail",
|
|
Help: "Number of test failed in the Compliance scans, partioned by scan_name, scan_profile_name",
|
|
},
|
|
[]string{
|
|
// scan_name will be set to "manual" for on-demand manual scans and the actual name set for the scheduled scans
|
|
"scan_name",
|
|
// name of the clusterScanProfile used for scanning
|
|
"scan_profile_name",
|
|
"cluster_name",
|
|
},
|
|
)
|
|
if err := prometheus.Register(ctl.numTestsFailed); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctl.numScansComplete = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Name: "compliance_scan_num_scans_complete",
|
|
Help: "Number of Compliance clusterscans completed, partioned by scan_name, scan_profile_name",
|
|
},
|
|
[]string{
|
|
// scan_name will be set to "manual" for on-demand manual scans and the actual name set for the scheduled scans
|
|
"scan_name",
|
|
// name of the clusterScanProfile used for scanning
|
|
"scan_profile_name",
|
|
"cluster_name",
|
|
},
|
|
)
|
|
if err := prometheus.Register(ctl.numScansComplete); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctl.numTestsTotal = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Name: "compliance_scan_num_tests_total",
|
|
Help: "Total Number of tests run in the Compliance scans, partioned by scan_name, scan_profile_name",
|
|
},
|
|
[]string{
|
|
// scan_name will be set to "manual" for on-demand manual scans and the actual name set for the scheduled scans
|
|
"scan_name",
|
|
// name of the clusterScanProfile used for scanning
|
|
"scan_profile_name",
|
|
"cluster_name",
|
|
},
|
|
)
|
|
if err := prometheus.Register(ctl.numTestsTotal); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctl.numTestsPassed = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Name: "compliance_scan_num_tests_pass",
|
|
Help: "Number of tests passing in the Compliance scans, partioned by scan_name, scan_profile_name",
|
|
},
|
|
[]string{
|
|
// scan_name will be set to "manual" for on-demand manual scans and the actual name set for the scheduled scans
|
|
"scan_name",
|
|
// name of the clusterScanProfile used for scanning
|
|
"scan_profile_name",
|
|
"cluster_name",
|
|
},
|
|
)
|
|
if err := prometheus.Register(ctl.numTestsPassed); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctl.numTestsSkipped = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Name: "compliance_scan_num_tests_skipped",
|
|
Help: "Number of test skipped in the Compliance scans, partioned by scan_name, scan_profile_name",
|
|
},
|
|
[]string{
|
|
// scan_name will be set to "manual" for on-demand manual scans and the actual name set for the scheduled scans
|
|
"scan_name",
|
|
// name of the clusterScanProfile used for scanning
|
|
"scan_profile_name",
|
|
"cluster_name",
|
|
},
|
|
)
|
|
if err := prometheus.Register(ctl.numTestsSkipped); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctl.numTestsNA = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Name: "compliance_scan_num_tests_na",
|
|
Help: "Number of tests not applicable in the Compliance scans, partioned by scan_name, scan_profile_name",
|
|
},
|
|
[]string{
|
|
// scan_name will be set to "manual" for on-demand manual scans and the actual name set for the scheduled scans
|
|
"scan_name",
|
|
// name of the clusterScanProfile used for scanning
|
|
"scan_profile_name",
|
|
"cluster_name",
|
|
},
|
|
)
|
|
if err := prometheus.Register(ctl.numTestsNA); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctl.numTestsWarn = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Name: "compliance_scan_num_tests_warn",
|
|
Help: "Number of tests having warn status in the Compliance scans, partioned by scan_name, scan_profile_name",
|
|
},
|
|
[]string{
|
|
// scan_name will be set to "manual" for on-demand manual scans and the actual name set for the scheduled scans
|
|
"scan_name",
|
|
// name of the clusterScanProfile used for scanning
|
|
"scan_profile_name",
|
|
"cluster_name",
|
|
},
|
|
)
|
|
if err := prometheus.Register(ctl.numTestsWarn); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|