github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/flag/kubernetes_flags.go (about)

     1  package flag
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/samber/lo"
     9  	corev1 "k8s.io/api/core/v1"
    10  )
    11  
    12  var (
    13  	ClusterContextFlag = Flag{
    14  		Name:       "context",
    15  		ConfigName: "kubernetes.context",
    16  		Default:    "",
    17  		Usage:      "specify a context to scan",
    18  		Aliases: []Alias{
    19  			{Name: "ctx"},
    20  		},
    21  	}
    22  	K8sNamespaceFlag = Flag{
    23  		Name:       "namespace",
    24  		ConfigName: "kubernetes.namespace",
    25  		Shorthand:  "n",
    26  		Default:    "",
    27  		Usage:      "specify a namespace to scan",
    28  	}
    29  	KubeConfigFlag = Flag{
    30  		Name:       "kubeconfig",
    31  		ConfigName: "kubernetes.kubeconfig",
    32  		Default:    "",
    33  		Usage:      "specify the kubeconfig file path to use",
    34  	}
    35  	ComponentsFlag = Flag{
    36  		Name:       "components",
    37  		ConfigName: "kubernetes.components",
    38  		Default: []string{
    39  			"workload",
    40  			"infra",
    41  		},
    42  		Values: []string{
    43  			"workload",
    44  			"infra",
    45  		},
    46  		Usage: "specify which components to scan",
    47  	}
    48  	K8sVersionFlag = Flag{
    49  		Name:       "k8s-version",
    50  		ConfigName: "kubernetes.k8s.version",
    51  		Default:    "",
    52  		Usage:      "specify k8s version to validate outdated api by it (example: 1.21.0)",
    53  	}
    54  	TolerationsFlag = Flag{
    55  		Name:       "tolerations",
    56  		ConfigName: "kubernetes.tolerations",
    57  		Default:    []string{},
    58  		Usage:      "specify node-collector job tolerations (example: key1=value1:NoExecute,key2=value2:NoSchedule)",
    59  	}
    60  	AllNamespaces = Flag{
    61  		Name:       "all-namespaces",
    62  		ConfigName: "kubernetes.all.namespaces",
    63  		Shorthand:  "A",
    64  		Default:    false,
    65  		Usage:      "fetch resources from all cluster namespaces",
    66  	}
    67  	NodeCollectorNamespace = Flag{
    68  		Name:       "node-collector-namespace",
    69  		ConfigName: "node.collector.namespace",
    70  		Default:    "trivy-temp",
    71  		Usage:      "specify the namespace in which the node-collector job should be deployed",
    72  	}
    73  	ExcludeOwned = Flag{
    74  		Name:       "exclude-owned",
    75  		ConfigName: "kubernetes.exclude.owned",
    76  		Default:    false,
    77  		Usage:      "exclude resources that have an owner reference",
    78  	}
    79  	ExcludeNodes = Flag{
    80  		Name:       "exclude-nodes",
    81  		ConfigName: "exclude.nodes",
    82  		Default:    []string{},
    83  		Usage:      "indicate the node labels that the node-collector job should exclude from scanning (example: kubernetes.io/arch:arm64,team:dev)",
    84  	}
    85  )
    86  
    87  type K8sFlagGroup struct {
    88  	ClusterContext         *Flag
    89  	Namespace              *Flag
    90  	KubeConfig             *Flag
    91  	Components             *Flag
    92  	K8sVersion             *Flag
    93  	Tolerations            *Flag
    94  	AllNamespaces          *Flag
    95  	NodeCollectorNamespace *Flag
    96  	ExcludeOwned           *Flag
    97  	ExcludeNodes           *Flag
    98  }
    99  
   100  type K8sOptions struct {
   101  	ClusterContext         string
   102  	Namespace              string
   103  	KubeConfig             string
   104  	Components             []string
   105  	K8sVersion             string
   106  	Tolerations            []corev1.Toleration
   107  	AllNamespaces          bool
   108  	NodeCollectorNamespace string
   109  	ExcludeOwned           bool
   110  	ExcludeNodes           map[string]string
   111  }
   112  
   113  func NewK8sFlagGroup() *K8sFlagGroup {
   114  	return &K8sFlagGroup{
   115  		ClusterContext:         &ClusterContextFlag,
   116  		Namespace:              &K8sNamespaceFlag,
   117  		KubeConfig:             &KubeConfigFlag,
   118  		Components:             &ComponentsFlag,
   119  		K8sVersion:             &K8sVersionFlag,
   120  		Tolerations:            &TolerationsFlag,
   121  		AllNamespaces:          &AllNamespaces,
   122  		NodeCollectorNamespace: &NodeCollectorNamespace,
   123  		ExcludeOwned:           &ExcludeOwned,
   124  		ExcludeNodes:           &ExcludeNodes,
   125  	}
   126  }
   127  
   128  func (f *K8sFlagGroup) Name() string {
   129  	return "Kubernetes"
   130  }
   131  
   132  func (f *K8sFlagGroup) Flags() []*Flag {
   133  	return []*Flag{
   134  		f.ClusterContext,
   135  		f.Namespace,
   136  		f.KubeConfig,
   137  		f.Components,
   138  		f.K8sVersion,
   139  		f.Tolerations,
   140  		f.AllNamespaces,
   141  		f.NodeCollectorNamespace,
   142  		f.ExcludeOwned,
   143  		f.ExcludeNodes,
   144  	}
   145  }
   146  
   147  func (f *K8sFlagGroup) ToOptions() (K8sOptions, error) {
   148  	tolerations, err := optionToTolerations(getStringSlice(f.Tolerations))
   149  	if err != nil {
   150  		return K8sOptions{}, err
   151  	}
   152  
   153  	exludeNodeLabels := make(map[string]string)
   154  	exludeNodes := getStringSlice(f.ExcludeNodes)
   155  	for _, exludeNodeValue := range exludeNodes {
   156  		excludeNodeParts := strings.Split(exludeNodeValue, ":")
   157  		if len(excludeNodeParts) != 2 {
   158  			return K8sOptions{}, fmt.Errorf("exclude node %s must be a key:value", exludeNodeValue)
   159  		}
   160  		exludeNodeLabels[excludeNodeParts[0]] = excludeNodeParts[1]
   161  	}
   162  
   163  	return K8sOptions{
   164  		ClusterContext:         getString(f.ClusterContext),
   165  		Namespace:              getString(f.Namespace),
   166  		KubeConfig:             getString(f.KubeConfig),
   167  		Components:             getStringSlice(f.Components),
   168  		K8sVersion:             getString(f.K8sVersion),
   169  		Tolerations:            tolerations,
   170  		AllNamespaces:          getBool(f.AllNamespaces),
   171  		NodeCollectorNamespace: getString(f.NodeCollectorNamespace),
   172  		ExcludeOwned:           getBool(f.ExcludeOwned),
   173  		ExcludeNodes:           exludeNodeLabels,
   174  	}, nil
   175  }
   176  
   177  func optionToTolerations(tolerationsOptions []string) ([]corev1.Toleration, error) {
   178  	var tolerations []corev1.Toleration
   179  	for _, toleration := range tolerationsOptions {
   180  		tolerationParts := strings.Split(toleration, ":")
   181  		if len(tolerationParts) < 2 {
   182  			return []corev1.Toleration{}, fmt.Errorf("toleration must include key and effect")
   183  		}
   184  		if corev1.TaintEffect(tolerationParts[1]) != corev1.TaintEffectNoSchedule &&
   185  			corev1.TaintEffect(tolerationParts[1]) != corev1.TaintEffectPreferNoSchedule &&
   186  			corev1.TaintEffect(tolerationParts[1]) != corev1.TaintEffectNoExecute {
   187  			return []corev1.Toleration{}, fmt.Errorf("toleration effect must be a valid value")
   188  		}
   189  		keyValue := strings.Split(tolerationParts[0], "=")
   190  		operator := corev1.TolerationOpEqual
   191  		if keyValue[1] == "" {
   192  			operator = corev1.TolerationOpExists
   193  		}
   194  		toleration := corev1.Toleration{
   195  			Key:      keyValue[0],
   196  			Value:    keyValue[1],
   197  			Operator: operator,
   198  			Effect:   corev1.TaintEffect(tolerationParts[1]),
   199  		}
   200  		var tolerationSec int
   201  		var err error
   202  		if len(tolerationParts) == 3 {
   203  			tolerationSec, err = strconv.Atoi(tolerationParts[2])
   204  			if err != nil {
   205  				return nil, fmt.Errorf("TolerationSeconds must must be a number")
   206  			}
   207  			toleration.TolerationSeconds = lo.ToPtr(int64(tolerationSec))
   208  		}
   209  		tolerations = append(tolerations, toleration)
   210  	}
   211  	return tolerations, nil
   212  }