Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract jevt array misses as novalue #1601

Merged
merged 2 commits into from
Apr 9, 2021

Conversation

mstemm
Copy link
Contributor

@mstemm mstemm commented Apr 5, 2021

What type of PR is this?

Uncomment one (or more) /kind <> lines:

/kind bug

/kind cleanup

/kind design

/kind documentation

/kind failing-test

/kind feature

If contributing rules or changes to rules, please make sure to also uncomment one of the following line:

/kind rule-update

/kind rule-create

Any specific area of the project related to this PR?

Uncomment one (or more) /area <> lines:

/area build

/area engine

/area rules

/area tests

/area proposals

What this PR does / why we need it:

Which issue(s) this PR fixes:

Fixes #

Special notes for your reviewer:

Does this PR introduce a user-facing change?:

fix(userspace/engine): properly handle field extraction over lists of containers when not all containers match the specified sub-properties

While testing, I found a case when creating a pod where:
1) the first container had no securityContext value
2) the second container had a security context with privileged=true

and this did not match the default rule Create Privileged Pod, when it
should match.

The rule Create Privileged Pod uses the field
ka.req.pod.containers.privileged, which in turn uses
json_event_filter_check::def_extract(). def_extract() iterates
over a set of json_pointers, potentially expanding arrays as they are
returned. Many k8s audit fields use this extract function.

For ka.req.pod.containers.privileged, the first json_pointer is
/requestObject/spec/containers to find the list of containers, and the
second is /securityContext/privileged to extract the privileged property
out of the securityContext object. What's returned is an array of
true/false noting if each container is privileged.

The problem is that def_extract() aborts when iterating over arrays if
extracting a pointer from an array can't be done.

In this case, the first pointer extracts the array of containers, and
then when iterating over the array of containers, the security context
pointer doesn't extract, causing the whole filter field to abort and
return ::no_value.

The fix is to not abort when iterating over arrays, but use ::no_value
for that array item's value instead. This allows def_extract() to
extract the privileged value out of the second container.

Signed-off-by: Mark Stemm <[email protected]>
Add a test that verifies that a pod where one container has no security
context and the second container has a security context + privileged
properly matches the Create Privileged Pod falco rule.

There's a very similar test case already in
trace_files/k8s_audit/create_nginx_pod_privileged_2nd_container.json,
but in that case both containers have a securityContext property.

Signed-off-by: Mark Stemm <[email protected]>
@@ -128,6 +128,16 @@ trace_files: !mux
- Create Privileged Pod: 1
trace_file: trace_files/k8s_audit/create_nginx_pod_privileged.json

create_privileged_no_secctx_1st_container_2nd_container_pod:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how this test is related to the NA value in the code. Can you help me undertstand?

Copy link
Contributor Author

@mstemm mstemm Apr 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test ensures that for a k8s audit event where the first container doesn't have any security context and the 2nd container does have a security context and privileged is true, you still get a falco alert for the Create Privileged Pod rule.

Behind the scenes, what's changing is that older falco versions used to abort when trying to get the value for the ka.req.pod.containers.privileged field. Whenever a field can't be extracted from an event, the result is <NA>. With this fix, what's returned instead is an array with values for each container, where each container will have "true" if the security context is present and true, "false" if present and false, and <NA> if not present. Here's an example minimal rules file that demonstrates the difference:

- list: k8s_audit_stages
  items: ["ResponseComplete"]

- macro: kevt
  condition: (jevt.value[/stage] in (k8s_audit_stages))

- macro: kcreate
  condition: ka.verb=create

- macro: pod
  condition: ka.target.resource=pods and not ka.target.subresource exists

- rule: Create Pod
  desc: Detect any attempt to create a pod
  condition: kevt and pod and kcreate
  output: Pod created (user=%ka.user.name pod=%ka.resp.name ns=%ka.target.namespace images=%ka.req.pod.containers.image privileged=%ka.req.pod.containers.privileged)
  priority: INFO
  source: k8s_audit
  tags: [k8s]
$  sudo falco -r ~/create_pod.yaml -e create_nginx_pod_no_secctx_1st_container_privileged_2nd_container.json
Wed Apr  7 10:13:33 2021: Falco version 0.27.0-84+8c9d4f4 (driver version 5c0b863ddade7a45568c0ac97d037422c9efb750)
Wed Apr  7 10:13:33 2021: Falco initialized with configuration file /etc/falco/falco.yaml
Wed Apr  7 10:13:33 2021: Loading rules from file /home/mstemm/create_pod.yaml:
Wed Apr  7 10:13:33 2021: Reading k8s audit events from file: /mnt/sf_mstemm/work/src/falco/test/trace_files/k8s_audit/create_nginx_pod_no_secctx_1st_container_privileged_2nd_container.json
Events detected: 1
Rule counts by severity:
 INFO: 1
Triggered rules by rule name:
 Create Pod: 1
Syscall event drop monitoring:
 - event drop detected: 0 occurrences
 - num times actions taken: 0
10:53:07.006844928: Notice Pod created (user=system:serviceaccount:kube-system:replicaset-controller pod=nginx-deployment-544b59f8b8-ffkxm ns=default images=(nginx,mysql:latest) privileged=<NA>)

$ sudo ./userspace/falco/falco -r ~/create_pod.yaml -e create_nginx_pod_no_secctx_1st_container_privileged_2nd_container.json
Wed Apr  7 10:14:09 2021: Falco version 0.27.0-84+8c9d4f4 (driver version 5c0b863ddade7a45568c0ac97d037422c9efb750)
Wed Apr  7 10:14:09 2021: Falco initialized with configuration file /etc/falco/falco.yaml
Wed Apr  7 10:14:09 2021: Loading rules from file /home/mstemm/create_pod.yaml:
Wed Apr  7 10:14:09 2021: Reading k8s audit events from file: /mnt/sf_mstemm/work/src/falco/test/trace_files/k8s_audit/create_nginx_pod_no_secctx_1st_container_privileged_2nd_container.json
Events detected: 1
Rule counts by severity:
 INFO: 1
Triggered rules by rule name:
 Create Pod: 1
Syscall event drop monitoring:
 - event drop detected: 0 occurrences
 - num times actions taken: 0
10:53:07.006844928: Notice Pod created (user=system:serviceaccount:kube-system:replicaset-controller pod=nginx-deployment-544b59f8b8-ffkxm ns=default images=(nginx,mysql:latest) privileged=(<NA>,true))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the exhaustive explanation Mark!

Copy link
Member

@leodido leodido left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good (and also necessary) to me. Thanks Mark

@poiana
Copy link
Contributor

poiana commented Apr 9, 2021

LGTM label has been added.

Git tree hash: 4cee3e32a8b9449b756c20be6114da7bb74b21d3

@poiana poiana added the approved label Apr 9, 2021
@leodido
Copy link
Member

leodido commented Apr 9, 2021

/milestone 0.28.0

@poiana poiana added this to the 0.28.0 milestone Apr 9, 2021
@poiana
Copy link
Contributor

poiana commented Apr 9, 2021

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: fntlnz, leodido, leogr

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:
  • OWNERS [fntlnz,leodido,leogr]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@poiana poiana merged commit f14b379 into master Apr 9, 2021
@poiana poiana deleted the extract-jevt-array-misses-as-novalue branch April 9, 2021 08:34
jasondellaluce added a commit to falcosecurity/plugins that referenced this pull request Apr 21, 2022
In the legacy implementation, list values used to include the value <NA> whenever a given field could
not be extracted (see: falcosecurity/falco#1601). However, this makes the semantics
of missing values ambigue and not coherent. Instead, here we just skip missing values. If a value is not found
when extracted, the plugin framework is able to signal the "field is not present" info. For list values, missing
values are simply skipped now. This works as expected with operators such as `in` and `intersects`, for which
the <NA> string was ambiguous anyways. If a list field is accessed with an index argument, then the extracted
value can either be a list with a single value (the one actually extracted at the positional index of the arg),
or an empty string (which is interpreted by the framework as <NA> internally).

Signed-off-by: Jason Dellaluce <[email protected]>
poiana pushed a commit to falcosecurity/plugins that referenced this pull request Apr 22, 2022
In the legacy implementation, list values used to include the value <NA> whenever a given field could
not be extracted (see: falcosecurity/falco#1601). However, this makes the semantics
of missing values ambigue and not coherent. Instead, here we just skip missing values. If a value is not found
when extracted, the plugin framework is able to signal the "field is not present" info. For list values, missing
values are simply skipped now. This works as expected with operators such as `in` and `intersects`, for which
the <NA> string was ambiguous anyways. If a list field is accessed with an index argument, then the extracted
value can either be a list with a single value (the one actually extracted at the positional index of the arg),
or an empty string (which is interpreted by the framework as <NA> internally).

Signed-off-by: Jason Dellaluce <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants