github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/kubernetes/scanner_test.go (about)

     1  package kubernetes
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/aquasecurity/defsec/pkg/framework"
    10  	"github.com/aquasecurity/defsec/pkg/scan"
    11  	"github.com/aquasecurity/defsec/pkg/scanners/options"
    12  	"github.com/aquasecurity/trivy-iac/test/testutil"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func Test_BasicScan(t *testing.T) {
    18  
    19  	fs := testutil.CreateFS(t, map[string]string{
    20  		"/code/example.yaml": `
    21  apiVersion: v1
    22  kind: Pod
    23  metadata: 
    24    name: hello-cpu-limit
    25  spec: 
    26    containers: 
    27    - command: ["sh", "-c", "echo 'Hello' && sleep 1h"]
    28      image: busybox
    29      name: hello
    30  `,
    31  		"/rules/lib.k8s.rego": `
    32   package lib.kubernetes
    33  
    34   default is_gatekeeper = false
    35  
    36   is_gatekeeper {
    37   	has_field(input, "review")
    38   	has_field(input.review, "object")
    39   }
    40  
    41   object = input {
    42   	not is_gatekeeper
    43   }
    44  
    45   object = input.review.object {
    46   	is_gatekeeper
    47   }
    48  
    49   format(msg) = gatekeeper_format {
    50   	is_gatekeeper
    51   	gatekeeper_format = {"msg": msg}
    52   }
    53  
    54   format(msg) = msg {
    55   	not is_gatekeeper
    56   }
    57  
    58   name = object.metadata.name
    59  
    60   default namespace = "default"
    61  
    62   namespace = object.metadata.namespace
    63  
    64   #annotations = object.metadata.annotations
    65  
    66   kind = object.kind
    67  
    68   is_pod {
    69   	kind = "Pod"
    70   }
    71  
    72   is_cronjob {
    73   	kind = "CronJob"
    74   }
    75  
    76   default is_controller = false
    77  
    78   is_controller {
    79   	kind = "Deployment"
    80   }
    81  
    82   is_controller {
    83   	kind = "StatefulSet"
    84   }
    85  
    86   is_controller {
    87   	kind = "DaemonSet"
    88   }
    89  
    90   is_controller {
    91   	kind = "ReplicaSet"
    92   }
    93  
    94   is_controller {
    95   	kind = "ReplicationController"
    96   }
    97  
    98   is_controller {
    99   	kind = "Job"
   100   }
   101  
   102   split_image(image) = [image, "latest"] {
   103   	not contains(image, ":")
   104   }
   105  
   106   split_image(image) = [image_name, tag] {
   107   	[image_name, tag] = split(image, ":")
   108   }
   109  
   110   pod_containers(pod) = all_containers {
   111   	keys = {"containers", "initContainers"}
   112   	all_containers = [c | keys[k]; c = pod.spec[k][_]]
   113   }
   114  
   115   containers[container] {
   116   	pods[pod]
   117   	all_containers = pod_containers(pod)
   118   	container = all_containers[_]
   119   }
   120  
   121   containers[container] {
   122   	all_containers = pod_containers(object)
   123   	container = all_containers[_]
   124   }
   125  
   126   pods[pod] {
   127   	is_pod
   128   	pod = object
   129   }
   130  
   131   pods[pod] {
   132   	is_controller
   133   	pod = object.spec.template
   134   }
   135  
   136   pods[pod] {
   137   	is_cronjob
   138   	pod = object.spec.jobTemplate.spec.template
   139   }
   140  
   141   volumes[volume] {
   142   	pods[pod]
   143   	volume = pod.spec.volumes[_]
   144   }
   145  
   146   dropped_capability(container, cap) {
   147   	container.securityContext.capabilities.drop[_] == cap
   148   }
   149  
   150   added_capability(container, cap) {
   151   	container.securityContext.capabilities.add[_] == cap
   152   }
   153  
   154   has_field(obj, field) {
   155   	obj[field]
   156   }
   157  
   158   no_read_only_filesystem(c) {
   159   	not has_field(c, "securityContext")
   160   }
   161  
   162   no_read_only_filesystem(c) {
   163   	has_field(c, "securityContext")
   164   	not has_field(c.securityContext, "readOnlyRootFilesystem")
   165   }
   166  
   167   privilege_escalation_allowed(c) {
   168   	not has_field(c, "securityContext")
   169   }
   170  
   171   privilege_escalation_allowed(c) {
   172   	has_field(c, "securityContext")
   173   	has_field(c.securityContext, "allowPrivilegeEscalation")
   174   }
   175  
   176   annotations[annotation] {
   177   	pods[pod]
   178   	annotation = pod.metadata.annotations
   179   }
   180  
   181   host_ipcs[host_ipc] {
   182   	pods[pod]
   183   	host_ipc = pod.spec.hostIPC
   184   }
   185  
   186   host_networks[host_network] {
   187   	pods[pod]
   188   	host_network = pod.spec.hostNetwork
   189   }
   190  
   191   host_pids[host_pid] {
   192   	pods[pod]
   193   	host_pid = pod.spec.hostPID
   194   }
   195  
   196   host_aliases[host_alias] {
   197   	pods[pod]
   198   	host_alias = pod.spec
   199   }
   200   `,
   201  		"/rules/lib.util.rego": `
   202   package lib.utils
   203  
   204   has_key(x, k) {
   205   	_ = x[k]
   206   }`,
   207  		"/rules/rule.rego": `
   208  package builtin.kubernetes.KSV011
   209  
   210  import data.lib.kubernetes
   211  import data.lib.utils
   212  
   213  default failLimitsCPU = false
   214  
   215  __rego_metadata__ := {
   216  	"id": "KSV011",
   217  	"avd_id": "AVD-KSV-0011",
   218  	"title": "CPU not limited",
   219  	"short_code": "limit-cpu",
   220  	"version": "v1.0.0",
   221  	"severity": "LOW",
   222  	"type": "Kubernetes Security Check",
   223  	"description": "Enforcing CPU limits prevents DoS via resource exhaustion.",
   224  	"recommended_actions": "Set a limit value under 'containers[].resources.limits.cpu'.",
   225  	"url": "https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits",
   226  }
   227  
   228  __rego_input__ := {
   229  	"combine": false,
   230  	"selector": [{"type": "kubernetes"}],
   231  }
   232  
   233  # getLimitsCPUContainers returns all containers which have set resources.limits.cpu
   234  getLimitsCPUContainers[container] {
   235  	allContainers := kubernetes.containers[_]
   236  	utils.has_key(allContainers.resources.limits, "cpu")
   237  	container := allContainers.name
   238  }
   239  
   240  # getNoLimitsCPUContainers returns all containers which have not set
   241  # resources.limits.cpu
   242  getNoLimitsCPUContainers[container] {
   243  	container := kubernetes.containers[_].name
   244  	not getLimitsCPUContainers[container]
   245  }
   246  
   247  # failLimitsCPU is true if containers[].resources.limits.cpu is not set
   248  # for ANY container
   249  failLimitsCPU {
   250  	count(getNoLimitsCPUContainers) > 0
   251  }
   252  
   253  deny[res] {
   254  	failLimitsCPU
   255  
   256  	msg := kubernetes.format(sprintf("Container '%s' of %s '%s' should set 'resources.limits.cpu'", [getNoLimitsCPUContainers[_], kubernetes.kind, kubernetes.name]))
   257  
   258  	res := {
   259  		"msg": msg,
   260  		"id": __rego_metadata__.id,
   261  		"title": __rego_metadata__.title,
   262  		"severity": __rego_metadata__.severity,
   263  		"type": __rego_metadata__.type,
   264          "startline": 6,
   265          "endline": 10,
   266  	}
   267  }
   268  `,
   269  	})
   270  
   271  	scanner := NewScanner(options.ScannerWithPolicyDirs("rules"))
   272  
   273  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
   274  	require.NoError(t, err)
   275  
   276  	require.Len(t, results.GetFailed(), 1)
   277  
   278  	assert.Equal(t, scan.Rule{
   279  		AVDID:          "AVD-KSV-0011",
   280  		Aliases:        []string{"KSV011"},
   281  		ShortCode:      "limit-cpu",
   282  		Summary:        "CPU not limited",
   283  		Explanation:    "Enforcing CPU limits prevents DoS via resource exhaustion.",
   284  		Impact:         "",
   285  		Resolution:     "Set a limit value under 'containers[].resources.limits.cpu'.",
   286  		Provider:       "kubernetes",
   287  		Service:        "general",
   288  		Links:          []string{"https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits"},
   289  		Severity:       "LOW",
   290  		Terraform:      &scan.EngineMetadata{},
   291  		CloudFormation: &scan.EngineMetadata{},
   292  		CustomChecks:   scan.CustomChecks{Terraform: (*scan.TerraformCustomCheck)(nil)},
   293  		RegoPackage:    "data.builtin.kubernetes.KSV011",
   294  		Frameworks:     map[framework.Framework][]string{},
   295  	}, results.GetFailed()[0].Rule())
   296  
   297  	failure := results.GetFailed()[0]
   298  	actualCode, err := failure.GetCode()
   299  	require.NoError(t, err)
   300  	for i := range actualCode.Lines {
   301  		actualCode.Lines[i].Highlighted = ""
   302  	}
   303  	assert.Equal(t, []scan.Line{
   304  		{
   305  			Number:     6,
   306  			Content:    "spec: ",
   307  			IsCause:    true,
   308  			FirstCause: true,
   309  			Annotation: "",
   310  		},
   311  		{
   312  			Number:     7,
   313  			Content:    "  containers: ",
   314  			IsCause:    true,
   315  			Annotation: "",
   316  		},
   317  		{
   318  			Number:     8,
   319  			Content:    "  - command: [\"sh\", \"-c\", \"echo 'Hello' && sleep 1h\"]",
   320  			IsCause:    true,
   321  			Annotation: "",
   322  		},
   323  		{
   324  			Number:     9,
   325  			Content:    "    image: busybox",
   326  			IsCause:    true,
   327  			Annotation: "",
   328  		},
   329  		{
   330  			Number:     10,
   331  			Content:    "    name: hello",
   332  			IsCause:    true,
   333  			LastCause:  true,
   334  			Annotation: "",
   335  		},
   336  	}, actualCode.Lines)
   337  }
   338  
   339  func Test_FileScan(t *testing.T) {
   340  
   341  	results, err := NewScanner(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), options.ScannerWithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(`
   342  apiVersion: v1
   343  kind: Pod
   344  metadata: 
   345    name: hello-cpu-limit
   346  spec: 
   347    containers: 
   348    - command: ["sh", "-c", "echo 'Hello' && sleep 1h"]
   349      image: busybox
   350      name: hello
   351  `))
   352  	require.NoError(t, err)
   353  
   354  	assert.Greater(t, len(results.GetFailed()), 0)
   355  }
   356  
   357  func Test_FileScan_WithSeparator(t *testing.T) {
   358  
   359  	results, err := NewScanner(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(`
   360  ---
   361  ---
   362  apiVersion: v1
   363  kind: Pod
   364  metadata: 
   365    name: hello-cpu-limit
   366  spec: 
   367    containers: 
   368    - command: ["sh", "-c", "echo 'Hello' && sleep 1h"]
   369      image: busybox
   370      name: hello
   371  `))
   372  	require.NoError(t, err)
   373  
   374  	assert.Greater(t, len(results.GetFailed()), 0)
   375  }
   376  
   377  func Test_FileScan_MultiManifests(t *testing.T) {
   378  	file := `
   379  ---
   380  apiVersion: v1
   381  kind: Pod
   382  metadata: 
   383    name: hello1-cpu-limit
   384  spec: 
   385    containers: 
   386    - command: ["sh", "-c", "echo 'Hello1' && sleep 1h"]
   387      image: busybox
   388      name: hello1
   389  ---
   390  apiVersion: v1
   391  kind: Pod
   392  metadata: 
   393    name: hello2-cpu-limit
   394  spec: 
   395    containers: 
   396    - command: ["sh", "-c", "echo 'Hello2' && sleep 1h"]
   397      image: busybox
   398      name: hello2
   399  `
   400  
   401  	results, err := NewScanner(
   402  		options.ScannerWithEmbeddedPolicies(true),
   403  		options.ScannerWithEmbeddedLibraries(true),
   404  		options.ScannerWithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(file))
   405  	require.NoError(t, err)
   406  
   407  	assert.Greater(t, len(results.GetFailed()), 1)
   408  	fileLines := strings.Split(file, "\n")
   409  	for _, failure := range results.GetFailed() {
   410  		actualCode, err := failure.GetCode()
   411  		require.NoError(t, err)
   412  		assert.Greater(t, len(actualCode.Lines), 0)
   413  		for _, line := range actualCode.Lines {
   414  			assert.Greater(t, len(fileLines), line.Number)
   415  			assert.Equal(t, line.Content, fileLines[line.Number-1])
   416  		}
   417  	}
   418  }
   419  
   420  func Test_FileScanWithPolicyReader(t *testing.T) {
   421  
   422  	results, err := NewScanner(options.ScannerWithPolicyReader(strings.NewReader(`package defsec
   423  
   424  deny[msg] {
   425    msg = "fail"
   426  }
   427  `))).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(`
   428  apiVersion: v1
   429  kind: Pod
   430  metadata: 
   431    name: hello-cpu-limit
   432  spec: 
   433    containers: 
   434    - command: ["sh", "-c", "echo 'Hello' && sleep 1h"]
   435      image: busybox
   436      name: hello
   437  `))
   438  	require.NoError(t, err)
   439  
   440  	assert.Equal(t, 1, len(results.GetFailed()))
   441  }
   442  
   443  func Test_FileScanJSON(t *testing.T) {
   444  
   445  	results, err := NewScanner(options.ScannerWithPolicyReader(strings.NewReader(`package defsec
   446  
   447  deny[msg] {
   448    input.kind == "Pod"
   449    msg = "fail"
   450  }
   451  `))).ScanReader(context.TODO(), "k8s.json", strings.NewReader(`
   452  {
   453    "kind": "Pod",
   454    "apiVersion": "v1",
   455    "metadata": {
   456      "name": "mongo",
   457      "labels": {
   458        "name": "mongo",
   459        "role": "mongo"
   460      }
   461    },
   462    "spec": {
   463      "volumes": [
   464        {
   465          "name": "mongo-disk",
   466          "gcePersistentDisk": {
   467            "pdName": "mongo-disk",
   468            "fsType": "ext4"
   469          }
   470        }
   471      ],
   472      "containers": [
   473        {
   474          "name": "mongo",
   475          "image": "mongo:latest",
   476          "ports": [
   477            {
   478              "name": "mongo",
   479              "containerPort": 27017
   480            }
   481          ],
   482          "volumeMounts": [
   483            {
   484              "name": "mongo-disk",
   485              "mountPath": "/data/db"
   486            }
   487          ]
   488        }
   489      ]
   490    }
   491  }
   492  `))
   493  	require.NoError(t, err)
   494  
   495  	assert.Equal(t, 1, len(results.GetFailed()))
   496  }
   497  
   498  func Test_FileScanWithMetadata(t *testing.T) {
   499  
   500  	results, err := NewScanner(
   501  		options.ScannerWithDebug(os.Stdout),
   502  		options.ScannerWithTrace(os.Stdout),
   503  		options.ScannerWithPolicyReader(strings.NewReader(`package defsec
   504  
   505  deny[msg] {
   506    input.kind == "Pod"
   507    msg := {
   508            "msg": "fail",
   509            "startline": 2,
   510  		  "endline": 2,
   511            "filepath": "chartname/template/serviceAccount.yaml"
   512          }
   513  }
   514  `))).ScanReader(
   515  		context.TODO(),
   516  		"k8s.yaml",
   517  		strings.NewReader(`
   518  apiVersion: v1
   519  kind: Pod
   520  metadata: 
   521    name: hello-cpu-limit
   522  spec: 
   523    containers: 
   524    - command: ["sh", "-c", "echo 'Hello' && sleep 1h"]
   525      image: busybox
   526      name: hello
   527  `))
   528  	require.NoError(t, err)
   529  
   530  	assert.Greater(t, len(results.GetFailed()), 0)
   531  
   532  	firstResult := results.GetFailed()[0]
   533  	assert.Equal(t, 2, firstResult.Metadata().Range().GetStartLine())
   534  	assert.Equal(t, 2, firstResult.Metadata().Range().GetEndLine())
   535  	assert.Equal(t, "chartname/template/serviceAccount.yaml", firstResult.Metadata().Range().GetFilename())
   536  }
   537  
   538  func Test_FileScanExampleWithResultFunction(t *testing.T) {
   539  
   540  	results, err := NewScanner(
   541  		options.ScannerWithDebug(os.Stdout),
   542  		options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true),
   543  		options.ScannerWithPolicyReader(strings.NewReader(`package defsec
   544  
   545  import data.lib.kubernetes
   546  
   547  default checkCapsDropAll = false
   548  
   549  __rego_metadata__ := {
   550  "id": "KSV003",
   551  "avd_id": "AVD-KSV-0003",
   552  "title": "Default capabilities not dropped",
   553  "short_code": "drop-default-capabilities",
   554  "version": "v1.0.0",
   555  "severity": "LOW",
   556  "type": "Kubernetes Security Check",
   557  "description": "The container should drop all default capabilities and add only those that are needed for its execution.",
   558  "recommended_actions": "Add 'ALL' to containers[].securityContext.capabilities.drop.",
   559  "url": "https://kubesec.io/basics/containers-securitycontext-capabilities-drop-index-all/",
   560  }
   561  
   562  __rego_input__ := {
   563  "combine": false,
   564  "selector": [{"type": "kubernetes"}],
   565  }
   566  
   567  # Get all containers which include 'ALL' in security.capabilities.drop
   568  getCapsDropAllContainers[container] {
   569  allContainers := kubernetes.containers[_]
   570  lower(allContainers.securityContext.capabilities.drop[_]) == "all"
   571  container := allContainers.name
   572  }
   573  
   574  # Get all containers which don't include 'ALL' in security.capabilities.drop
   575  getCapsNoDropAllContainers[container] {
   576  container := kubernetes.containers[_]
   577  not getCapsDropAllContainers[container.name]
   578  }
   579  
   580  deny[res] {
   581  output := getCapsNoDropAllContainers[_]
   582  
   583  msg := kubernetes.format(sprintf("Container '%s' of %s '%s' should add 'ALL' to 'securityContext.capabilities.drop'", [output.name, kubernetes.kind, kubernetes.name]))
   584  
   585  res := result.new(msg, output)
   586  }
   587  
   588  `))).ScanReader(
   589  		context.TODO(),
   590  		"k8s.yaml",
   591  		strings.NewReader(`
   592  apiVersion: v1
   593  kind: Pod
   594  metadata: 
   595    name: hello-cpu-limit
   596  spec: 
   597    containers: 
   598    - command: ["sh", "-c", "echo 'Hello' && sleep 1h"]
   599      image: busybox
   600      name: hello
   601      securityContext:
   602        capabilities:
   603          drop:
   604          - nothing
   605  `))
   606  	require.NoError(t, err)
   607  
   608  	require.Greater(t, len(results.GetFailed()), 0)
   609  
   610  	firstResult := results.GetFailed()[0]
   611  	assert.Equal(t, 8, firstResult.Metadata().Range().GetStartLine())
   612  	assert.Equal(t, 14, firstResult.Metadata().Range().GetEndLine())
   613  	assert.Equal(t, "k8s.yaml", firstResult.Metadata().Range().GetFilename())
   614  }
   615  
   616  func Test_checkPolicyIsApplicable(t *testing.T) {
   617  	srcFS := testutil.CreateFS(t, map[string]string{
   618  		"policies/pod_policy.rego": `# METADATA
   619  # title: "Process can elevate its own privileges"
   620  # description: "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node."
   621  # scope: package
   622  # schemas:
   623  # - input: schema["kubernetes"]
   624  # related_resources:
   625  # - https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
   626  # custom:
   627  #   id: KSV001
   628  #   avd_id: AVD-KSV-0999
   629  #   severity: MEDIUM
   630  #   short_code: no-self-privesc
   631  #   recommended_action: "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'."
   632  #   input:
   633  #     selector:
   634  #     - type: kubernetes
   635  #       subtypes:
   636  #         - kind: Pod
   637  package builtin.kubernetes.KSV999
   638  
   639  import data.lib.kubernetes
   640  import data.lib.utils
   641  
   642  default checkAllowPrivilegeEscalation = false
   643  
   644  # getNoPrivilegeEscalationContainers returns the names of all containers which have
   645  # securityContext.allowPrivilegeEscalation set to false.
   646  getNoPrivilegeEscalationContainers[container] {
   647  	allContainers := kubernetes.containers[_]
   648  	allContainers.securityContext.allowPrivilegeEscalation == false
   649  	container := allContainers.name
   650  }
   651  
   652  # getPrivilegeEscalationContainers returns the names of all containers which have
   653  # securityContext.allowPrivilegeEscalation set to true or not set.
   654  getPrivilegeEscalationContainers[container] {
   655  	containerName := kubernetes.containers[_].name
   656  	not getNoPrivilegeEscalationContainers[containerName]
   657  	container := kubernetes.containers[_]
   658  }
   659  
   660  deny[res] {
   661  	output := getPrivilegeEscalationContainers[_]
   662  	msg := kubernetes.format(sprintf("Container '%s' of %s '%s' should set 'securityContext.allowPrivilegeEscalation' to false", [output.name, kubernetes.kind, kubernetes.name]))
   663  	res := result.new(msg, output)
   664  }
   665  
   666  `,
   667  		"policies/namespace_policy.rego": `# METADATA
   668  # title: "The default namespace should not be used"
   669  # description: "ensure that default namespace should not be used"
   670  # scope: package
   671  # schemas:
   672  # - input: schema["kubernetes"]
   673  # related_resources:
   674  # - https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
   675  # custom:
   676  #   id: KSV110
   677  #   avd_id: AVD-KSV-0888
   678  #   severity: LOW
   679  #   short_code: default-namespace-should-not-be-used
   680  #   recommended_action: "Ensure that namespaces are created to allow for appropriate segregation of Kubernetes resources and that all new resources are created in a specific namespace."
   681  #   input:
   682  #     selector:
   683  #     - type: kubernetes
   684  #       subtypes:
   685  #         - kind: Namespace
   686  package builtin.kubernetes.KSV888
   687  
   688  import data.lib.kubernetes
   689  
   690  default defaultNamespaceInUse = false
   691  
   692  defaultNamespaceInUse {
   693  	kubernetes.namespace == "default"
   694  }
   695  
   696  deny[res] {
   697  	defaultNamespaceInUse
   698  	msg := sprintf("%s '%s' should not be set with 'default' namespace", [kubernetes.kind, kubernetes.name])
   699  	res := result.new(msg, input.metadata.namespace)
   700  }
   701  
   702  `,
   703  		"test/KSV001/pod.yaml": `apiVersion: v1
   704  kind: Pod
   705  metadata:
   706    name: hello-cpu-limit
   707  spec:
   708    containers:
   709      - command: ["sh", "-c", "echo 'Hello' && sleep 1h"]
   710        image: busybox
   711        name: hello
   712        securityContext:
   713          capabilities:
   714            drop:
   715              - all
   716  `,
   717  	})
   718  
   719  	scanner := NewScanner(
   720  		// options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true),
   721  		options.ScannerWithEmbeddedLibraries(true),
   722  		options.ScannerWithPolicyDirs("policies/"),
   723  		options.ScannerWithPolicyFilesystem(srcFS),
   724  	)
   725  	results, err := scanner.ScanFS(context.TODO(), srcFS, "test/KSV001")
   726  	require.NoError(t, err)
   727  
   728  	require.NoError(t, err)
   729  	require.Len(t, results.GetFailed(), 1)
   730  
   731  	failure := results.GetFailed()[0].Rule()
   732  	assert.Equal(t, "Process can elevate its own privileges", failure.Summary)
   733  }