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

chore: adding psp-users CEL policy #537

Open
wants to merge 19 commits into
base: master
Choose a base branch
from

Conversation

JaydipGabani
Copy link
Contributor

@JaydipGabani JaydipGabani commented May 30, 2024

What this PR does / why we need it:

Which issue(s) does this PR fix (optional, using fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when the PR gets merged):
Fixes #541

Special notes for your reviewer:

Rego policy nehavior is -

  • filed in below list is reference to securityContext fields - [ runAsUser, runAsGroup, fsGroup, supplementalGroup ]

  • Input:
field:
      rule: MustRunAs
      ranges:
        - min: 100
          max: 200
  • Behavior: if field is missing from object then throw missing violation, else throw violation is field is not in required range

  • Input:
runAsUser:
      rule: MustRunAsNonRoot
  • Behavior: if runAsUser and runAsNonRoot both are missing from object then throw missing violation, else throw violation is runAsUser == 0

  • Input:
field:
      rule: mayRunAs
      ranges:
        - min: 100
          max: 200
  • Behavior: No missing field violation, but is field is present and violating the range then throw the violation

  • Input:
field:
  rule: RunAsAny

  • Behavior: No violations

@JaydipGabani JaydipGabani requested a review from a team as a code owner May 30, 2024 00:28
(variables.containers + variables.initContainers + variables.ephemeralContainers).filter(container,
container.image in variables.exemptImageExplicit ||
variables.exemptImagePrefixes.exists(exemption, string(container.image).startsWith(exemption)))
- name: badContainers
Copy link
Contributor

Choose a reason for hiding this comment

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

these are not bad containers yet, just containers subject to validation... candidateContainers?

- name: missingRequiredRunAsNonRootContainers
expression: |
variables.badContainers.filter(container,
has(variables.params.runAsUser) && has(variables.params.runAsUser.rule) && (variables.params.runAsUser.rule == "MustRunAsNonRoot") && ((has(container.securityContext) && !(has(container.securityContext.runAsUser) || has(container.securityContext.runAsNonRoot))) ?
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to check that runAsNonRoot is both defined and equal to true (this is implied in the Rego check, since not will invert both "missing" and "false" to true.

Also, has(container.securityContext.runAsUser) || has(container.securityContext.runAsNonRoot) should be has(container.securityContext.runAsUser) && has(container.securityContext.runAsNonRoot) , since both conditions need to be satisfied.

- name: invalidRunAsUserContainers
expression: |
variables.badContainers.filter(container,
!(container.name in variables.processedRunAsUserContainers) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

we should not assume that container names are globally unique, since tools like gator may not perform that kind of validation.

(
variables.params.runAsUser.rule == "MustRunAs" ?
(
has(container.securityContext) && has(container.securityContext.runAsUser) ? !variables.params.runAsUser.ranges.all(range, container.securityContext.runAsUser >= range.min && container.securityContext.runAsUser <= range.max) :
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be exists() instead of all(), since the constraint is satisfied as long as the user ID lies within at least one of the specified ranges.

(variables.params.runAsGroup.rule == "MustRunAs" || variables.params.runAsGroup.rule == "MayRunAs") &&
(
has(container.securityContext) && has(container.securityContext.runAsGroup) ?
!variables.params.runAsGroup.ranges.all(range, container.securityContext.runAsGroup >= range.min && container.securityContext.runAsGroup <= range.max) :
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment about all() vs. exists()

- name: invalidRunAsFsGroupContainers
expression: |
variables.badContainers.filter(container,
!(variables.missingRequiredFsGroupContainers.exists(c, c.name == container.name)) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

as above, let's not rely on names being globally unique

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Processing each container type separately.

(variables.params.fsGroup.rule == "MustRunAs" || variables.params.fsGroup.rule == "MayRunAs") &&
(
has(container.securityContext) && has(container.securityContext.fsGroup) ?
!variables.params.fsGroup.ranges.all(range, container.securityContext.fsGroup >= range.min && container.securityContext.fsGroup <= range.max) :
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment about all() vs. exists()

(
has(container.securityContext) && has(container.securityContext.fsGroup) ?
!variables.params.fsGroup.ranges.all(range, container.securityContext.fsGroup >= range.min && container.securityContext.fsGroup <= range.max) :
variables.podRunAsFsGroup == null || !variables.params.fsGroup.ranges.all(range, variables.podRunAsFsGroup >= range.min && variables.podRunAsFsGroup <= range.max)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment about all() vs. exists()

(variables.params.supplementalGroups.rule == "MustRunAs" || variables.params.supplementalGroups.rule == "MayRunAs") &&
(
has(container.securityContext) && has(container.securityContext.supplementalGroups) ?
!variables.params.supplementalGroups.ranges.all(range, container.securityContext.supplementalGroups.all(gp, gp>= range.min && gp <= range.max)) :
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment about all() vs. exists()

(
has(container.securityContext) && has(container.securityContext.supplementalGroups) ?
!variables.params.supplementalGroups.ranges.all(range, container.securityContext.supplementalGroups.all(gp, gp>= range.min && gp <= range.max)) :
variables.podRunAsSupplementalGroups == null || !variables.params.supplementalGroups.ranges.all(range, variables.podRunAsSupplementalGroups.all(gp, gp >= range.min && gp <= range.max))
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment about all() vs. exists()

Signed-off-by: Jaydip Gabani <[email protected]>
Signed-off-by: Jaydip Gabani <[email protected]>
Signed-off-by: Jaydip Gabani <[email protected]>
Signed-off-by: Jaydip Gabani <[email protected]>
@JaydipGabani
Copy link
Contributor Author

@maxsmythe @ritazh @sozercan PTAL

expression: |
!has(variables.params.exemptImages) ? [] :
variables.params.exemptImages.filter(image, !image.endsWith("*"))
- name: exemptImages
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need map(container, container.image) ?

Copy link
Contributor

Choose a reason for hiding this comment

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

this still needs fixing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

expression: |
variables.containers.filter(container,
!(container.image in variables.exemptImages) &&
has(variables.params.runAsUser) && has(variables.params.runAsUser.rule) && (variables.params.runAsUser.rule == "MustRunAsNonRoot") ?
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 think you need a trinary operator (?) here

(
(!has(container.securityContext) || (
(!has(container.securityContext.runAsNonRoot) || !container.securityContext.runAsNonRoot) && (!has(container.securityContext.runAsUser) || container.securityContext.runAsUser == 0)
)) || variables.missingRunAsNonRootGlobal
Copy link
Contributor

Choose a reason for hiding this comment

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

This reads like it will be true if either there is no global definition for running as non root OR if there is no local definition for running as non root.

This is incorrect. If local is defined but global is not, then there is no violation, as local definition supersedes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the code to correct this.

(
variables.params.runAsUser.rule == "RunAsAny" ? false :
(
variables.params.runAsUser.rule == "MustRunAsNonRoot" ?
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the code for this will be much simpler if we:

  • have a variable for each rule type (MustRunAs, MayRunAs, etc.) that returns the containers violating the rule.
  • if the rule type is not used, this variable will return an empty list.

This will allow you to:

  • remove all code duplication (can go back to concatenating all containers again, since you are no longer required to filter based off container name)
  • Reduce the nesting/branching for each variable.

Because this will significantly change/shorten the code, I'll stop reviewing here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. Made an update to CEL code, it should now be much more compact. PTAL.

variables.anyObject.kind == "Pod" && has(variables.anyObject.spec.securityContext) && has(variables.anyObject.spec.securityContext.supplementalGroups) ? variables.anyObject.spec.securityContext.supplementalGroups : null
- name: podRunAsGroup
expression: |
variables.anyObject.kind == "Pod" && has(variables.anyObject.spec.securityContext) && has(variables.anyObject.spec.securityContext.runAsGroup) ? variables.anyObject.spec.securityContext.runAsGroup : null
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just put the test for kind == Pod into a matchCondition?

I don't think this constraint makes sense for anything other than Pods... we can add the same test to the Rego if necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

wdym by "put test for kind == Pod into a matchCondition?

I removed the condition kind == Pod from CEL and rego.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated CEL to add matchCondition.

!has(variables.anyObject.securityContext) || ((!has(variables.anyObject.securityContext.runAsNonRoot) || !variables.anyObject.securityContext.runAsNonRoot) && (!has(variables.anyObject.securityContext.runAsUser) || variables.anyObject.securityContext.runAsUser == 0))
- name: violatingMustOrMayRunAsUser
expression: |
has(variables.params.runAsUser) && has(variables.params.runAsUser.rule) && variables.params.runAsUser.rule == "MustRunAs" ? variables.nonExemptContainers.filter(container, (!has(container.securityContext) || !has(container.securityContext.runAsUser)) && variables.podRunAsUser == null).map(container, "Container " + container.name + " is attempting to run without a required securityContext/runAsUser") + variables.nonExemptContainers.filter(container, (has(container.securityContext) && has(container.securityContext.runAsUser) ? !variables.params.runAsUser.ranges.exists(range, container.securityContext.runAsUser >= range.min && container.securityContext.runAsUser <= range.max) : variables.podRunAsUser != null && !variables.params.runAsUser.ranges.exists(range, variables.podRunAsUser >= range.min && variables.podRunAsUser <= range.max))).map(container, "Container " + container.name + " is attempting to run as disallowed user. Allowed runAsUser: {ranges: [" + variables.params.runAsUser.ranges.map(range, "{max: " + string(range.max) + ", min: " + string(range.min) + "}").join(", ") + ", rule: " + variables.params.runAsUser.rule + "}") : []
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use some string/block formatting to make the various declarations more legible? It's hard to parse the assertions without formatting. This is a general comment for all of these larger code blocks.

TBH I can't review without formatting improvements... the statements bleed together.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Appologies for putting the large code block in one line. Formatted all code large code blocks. Should be easier to parse and read.

Copy link
Contributor

Choose a reason for hiding this comment

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

thanks!

@JaydipGabani
Copy link
Contributor Author

@maxsmythe PTAL.

Choose a reason for hiding this comment

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

Copilot reviewed 6 out of 18 changed files in this pull request and generated no suggestions.

Files not reviewed (12)
  • artifacthub/library/pod-security-policy/users/1.1.0/samples/psp-pods-allowed-user-ranges/constraint.yaml: Language not supported
  • artifacthub/library/pod-security-policy/users/1.1.0/samples/psp-pods-allowed-user-ranges/example_allowed_exempt_image.yaml: Language not supported
  • artifacthub/library/pod-security-policy/users/1.1.0/samples/psp-pods-allowed-user-ranges/example_disallowed.yaml: Language not supported
  • artifacthub/library/pod-security-policy/users/1.1.0/suite.yaml: Language not supported
  • src/pod-security-policy/users/constraint.tmpl: Language not supported
  • src/pod-security-policy/users/src.cel: Language not supported
  • library/pod-security-policy/users/suite.yaml: Evaluated as low risk
  • library/pod-security-policy/users/samples/psp-pods-allowed-user-ranges/constraint.yaml: Evaluated as low risk
  • library/pod-security-policy/users/samples/psp-pods-allowed-user-ranges/example_disallowed.yaml: Evaluated as low risk
  • artifacthub/library/pod-security-policy/users/1.1.0/kustomization.yaml: Evaluated as low risk
  • artifacthub/library/pod-security-policy/users/1.1.0/samples/psp-pods-allowed-user-ranges/example_allowed.yaml: Evaluated as low risk
  • library/pod-security-policy/users/samples/psp-pods-allowed-user-ranges/example_allowed_exempt_image.yaml: Evaluated as low risk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add CEL code for PSP Policies in library
2 participants