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

Add rego rule to check for windows securityContext compliance #318

Closed
wants to merge 6 commits into from

Conversation

0xquark
Copy link

@0xquark 0xquark commented Mar 7, 2023

Signed-off-by: Karanjot Singh [email protected]

Fixes: #317

Summary

In order to support Windows system, we should add rego rules to check for securityContext parameters also for this OS.
kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#windowssecuritycontextoptions-v1-core
So the following rules are being added:

  • set-gmsacredentialspec-value

    This rule will check if the GMSA credential spec is set in the security context
    The gmsaCredentialSpecSet function checks if the securityContext object exists and if the windowsOptions object exists within it. It then checks if the gmsaCredentialSpec field is set within windowsOptions. If all of these conditions are true, the rule will not fail. Otherwise, the rule will fail and return an alert message containing the name of the object that failed the check.

    Test Cases

image

  • set-gmsacredentialspecname-value

    This rule will check if the GMSA credential spec name is set in the security context

    Test Cases

image

  • set-hostprocess-true

    Check if any container in the pod is set to run as a 'Host Process' container or if WindowsHostProcessContainers feature flag is enabled in api-server when HostProcess is true

    Test Cases

image

  • runAsUserName

    checks if the runAsUserName is set in the security context

    Test Cases

image

@alegrey91
Copy link
Contributor

Hello @0xquark, thanks for the fast contribution you gave.
Sorry for the delay of reply but I was out for a conference. I'll review the PR today or at the latest tomorrow.

@alegrey91 alegrey91 self-requested a review March 9, 2023 11:39
@alegrey91
Copy link
Contributor

Hey @0xquark, is the PR still a Work In Progress?

@0xquark
Copy link
Author

0xquark commented Mar 9, 2023

Hi @alegrey91 !
The last two rules are in WIP. I've implemented them, just needed to commit it. I am currently at someplace else so Prolly would take me some time(Maybe today or Tomorrow for sure) to get back to my system and commit.

Edit : SSH'ed my way into commiting

@0xquark 0xquark marked this pull request as ready for review March 9, 2023 22:32
@0xquark
Copy link
Author

0xquark commented Mar 9, 2023

@alegrey91 This is ready for review now! 🚀

Signed-off-by: Karanjot Singh <[email protected]>

Added set-gmsacredentialspec-value rule as part of windowssecuritycontext rules

Signed-off-by: Karanjot Singh <[email protected]>

Added set-gmsacredentialspecname-value rule as part of windowssecuritycontext rules

Signed-off-by: Karanjot Singh <[email protected]>

Added set-hostprocess-true rule as part of windowssecuritycontext rules

Signed-off-by: Karanjot Singh <[email protected]>

Added runAsUserName rule as part of windowssecuritycontext rules
@0xquark 0xquark changed the title WIP:Add rego rule to check for windows securityContext compliance Add rego rule to check for windows securityContext compliance Mar 9, 2023
obj := input[_]
allowed_kinds := {"Pod", "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Job", "CronJob"}
allowed_kinds[obj.kind]

Copy link
Contributor

Choose a reason for hiding this comment

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

Please add OS check using the label kubernetes.io/os: "windows" located in Node object.

Copy link
Author

Choose a reason for hiding this comment

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

I've added a check for OS but still while testing it takes the input as other than windows and returning {} as result. Could you check where i am going wrong?

} else := false

# Function to get the security context of an object
getSecurityContext(obj) = obj.spec.securityContext {
Copy link
Contributor

Choose a reason for hiding this comment

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

Despite the function seems to work properly, we should keep the format used in other policies.
So, usually we split the policy in 3 rules (Cronjob, Pod and Workload, where Workload includes Deployment, ReplicaSet, DaemonSet, StatefulSet).
Here's an example: https://github.com/kubescape/regolibrary/blob/master/rules/set-seLinuxOptions/raw.rego

Copy link
Author

@0xquark 0xquark Mar 11, 2023

Choose a reason for hiding this comment

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

I couldn't understand why we have to split into 3 different rules, isn't having a single function to deal with cronjob,pod and workload a much efficient way ?

runAsUserNameSet(securityContext) := true {
securityContext != null
securityContext.windowsOptions != null
securityContext.windowsOptions.runAsUserName != null
Copy link
Contributor

Choose a reason for hiding this comment

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

You should check both for PodSecurityContext and SecurityContext. What I mean is that windowsOptions can be found both in these places, so you should check in both of them. You can find an example here: https://github.com/kubescape/regolibrary/blob/dev/rules/set-seLinuxOptions/raw.rego.
seLinuxOptions can be found in both the paths. So, the path variable that we are initializing, should have the right path according to which object has been checked.

Additionally, the check here, can be simplified using the following form:

runAsUserNameSet(securityContext) := true {
    securityContext.windowsOptions.runAsUserName != ""
} else := false

obj := input[_]
allowed_kinds := {"Pod", "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Job", "CronJob"}
allowed_kinds[obj.kind]

Copy link
Contributor

Choose a reason for hiding this comment

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

Please add OS check using the label kubernetes.io/os: "windows" located in Node object.

}

# Function to check if the GMSA credential spec is set in the security context
gmsaCredentialSpecSet(securityContext) := true if {
Copy link
Contributor

Choose a reason for hiding this comment

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

You should check both for PodSecurityContext and SecurityContext. What I mean is that windowsOptions can be found both in these places, so you should check in both of them. You can find an example here: https://github.com/kubescape/regolibrary/blob/dev/rules/set-seLinuxOptions/raw.rego.
seLinuxOptions can be found in both the paths. So, the path variable that we are initializing, should have the right path according to which object has been checked.

Additionally, the check here, can be simplified using the following form:

gmsaCredentialSpecSet(securityContext) := true {
    securityContext.windowsOptions.gmsaCredentialSpec != ""
} else := false

obj := input[_]
allowed_kinds := {"Pod", "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Job", "CronJob"}
allowed_kinds[obj.kind]

Copy link
Contributor

Choose a reason for hiding this comment

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

Please add OS check using the label kubernetes.io/os: "windows" located in Node object.

}

# Function to check if the GMSA credential spec name is set in the security context
gmsaCredentialSpecNameSet(securityContext) := true if {
Copy link
Contributor

Choose a reason for hiding this comment

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

You should check both for PodSecurityContext and SecurityContext. What I mean is that windowsOptions can be found both in these places, so you should check in both of them. You can find an example here: https://github.com/kubescape/regolibrary/blob/dev/rules/set-seLinuxOptions/raw.rego.
seLinuxOptions can be found in both the paths. So, the path variable that we are initializing, should have the right path according to which object has been checked.

Additionally, the check here, can be simplified using the following form:

gmsaCredentialSpecNameSet(securityContext) := true {
    securityContext.windowsOptions.gmsaCredentialSpecName != ""
} else := false

} else := false

# Function to get the security context of an object
getSecurityContext(obj) = obj.spec.securityContext {
Copy link
Contributor

Choose a reason for hiding this comment

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

Despite the function seems to work properly, we should keep the format used in other policies.
So, usually we split the policy in 3 rules (Cronjob, Pod and Workload, where Workload includes Deployment, ReplicaSet, DaemonSet, StatefulSet).
Here's an example: https://github.com/kubescape/regolibrary/blob/master/rules/set-seLinuxOptions/raw.rego

obj := input[_]
allowed_kinds := {"Pod", "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Job", "CronJob"}
allowed_kinds[obj.kind]

Copy link
Contributor

Choose a reason for hiding this comment

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

Please add OS check using the label kubernetes.io/os: "windows" located in Node object.


# Function to check if container is set as a 'Host Process' container
isHostProcessSet(obj) := true if {
obj.spec.hostProcess == true
Copy link
Contributor

Choose a reason for hiding this comment

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

You should check both for PodSecurityContext and SecurityContext. What I mean is that windowsOptions can be found both in these places, so you should check in both of them. You can find an example here: https://github.com/kubescape/regolibrary/blob/dev/rules/set-seLinuxOptions/raw.rego.
seLinuxOptions can be found in both the paths. So, the path variable that we are initializing, should have the right path according to which object has been checked.

@alegrey91
Copy link
Contributor

alegrey91 commented Mar 10, 2023

@0xquark first of all, I would say thanks for the effort that you gave in the contribution.
There are some points that I denoted in the review. If you have any doubt, feel free to ask.

@alegrey91
Copy link
Contributor

Additionally, there are some file out of scope this PR. Probably due to some mistake with branches. We can fix them one the PR is ready to be merged.

@0xquark
Copy link
Author

0xquark commented Mar 11, 2023

@alegrey91 Thank you for taking your time to review! I'll make the changes but i do have few questions regarding the implementation, i've replied to the review regarding this.

Signed-off-by: Karanjot Singh <[email protected]>
@alegrey91
Copy link
Contributor

hey @0xquark, are you still working on that?

@0xquark
Copy link
Author

0xquark commented Mar 20, 2023

@alegrey91 Left a couple of questions on the review above!

@alegrey91
Copy link
Contributor

@alegrey91 Left a couple of questions on the review above!

Do you mean this: #318 (comment)?

@0xquark
Copy link
Author

0xquark commented Mar 21, 2023

@alegrey91 Left a couple of questions on the review above!

Do you mean this: #318 (comment)?

Yess! #318 (comment) This too

@alegrey91
Copy link
Contributor

@alegrey91 Left a couple of questions on the review above!

Do you mean this: #318 (comment)?

Yess! #318 (comment) This too

Ok, so I already replied to these. Can you see my reply?

@0xquark
Copy link
Author

0xquark commented Mar 21, 2023

@alegrey91 I am not able to view it!
image

@alegrey91
Copy link
Contributor

I couldn't understand why we have to split into 3 different rules, isn't having a single function to deal with cronjob,pod and workload a much efficient way ?

This should be for compatibility reasons. It is hard to maintain the same code written in different ways. Keep in mind that you have also to check for PodSecurityContext, not only for SecurityContext.

@alegrey91
Copy link
Contributor

I've added a check for OS but still while testing it takes the input as other than windows and returning {} as result. Could you check where i am going wrong?

Try changing the check with this:

system_info := input[_]
system_info.kind == "Node"
system_info.metadata.labels.kubernetes.io/os == "windows"

And passing a Node resource as input.
You can use this as example:

apiVersion: v1
items:
- apiVersion: v1
  kind: Node
  metadata:
    annotations:
      kubeadm.alpha.kubernetes.io/cri-socket: unix:///run/containerd/containerd.sock
      node.alpha.kubernetes.io/ttl: "0"
      volumes.kubernetes.io/controller-managed-attach-detach: "true"
    creationTimestamp: "2023-03-13T20:37:33Z"
    labels:
      beta.kubernetes.io/arch: amd64
      beta.kubernetes.io/os: linux
      kubernetes.io/arch: amd64
      kubernetes.io/hostname: test-control-plane
      kubernetes.io/os: linux
      node-role.kubernetes.io/control-plane: ""
      node.kubernetes.io/exclude-from-external-load-balancers: ""
    name: test-control-plane
    resourceVersion: "404"
    uid: 34f43f52-de94-45ae-ba8d-a43e595cf2d6
  spec:
    podCIDR: 10.244.0.0/24
    podCIDRs:
    - 10.244.0.0/24
    providerID: kind://docker/test/test-control-plane
  status:
    addresses:
    - address: 172.18.0.2
      type: InternalIP
    - address: test-control-plane
      type: Hostname
    allocatable:
      cpu: "8"
      ephemeral-storage: 486745Mi
      hugepages-1Gi: "0"
      hugepages-2Mi: "0"
      memory: 16255180Ki
      pods: "110"
    capacity:
      cpu: "8"
      ephemeral-storage: 486745Mi
      hugepages-1Gi: "0"
      hugepages-2Mi: "0"
      memory: 16255180Ki
      pods: "110"
    conditions:
    - lastHeartbeatTime: "2023-03-13T20:37:57Z"
      lastTransitionTime: "2023-03-13T20:37:31Z"
      message: kubelet has sufficient memory available
      reason: KubeletHasSufficientMemory
      status: "False"
      type: MemoryPressure
    - lastHeartbeatTime: "2023-03-13T20:37:57Z"
      lastTransitionTime: "2023-03-13T20:37:31Z"
      message: kubelet has no disk pressure
      reason: KubeletHasNoDiskPressure
      status: "False"
      type: DiskPressure
    - lastHeartbeatTime: "2023-03-13T20:37:57Z"
      lastTransitionTime: "2023-03-13T20:37:31Z"
      message: kubelet has sufficient PID available
      reason: KubeletHasSufficientPID
      status: "False"
      type: PIDPressure
    - lastHeartbeatTime: "2023-03-13T20:37:57Z"
      lastTransitionTime: "2023-03-13T20:37:57Z"
      message: kubelet is posting ready status
      reason: KubeletReady
      status: "True"
      type: Ready
    daemonEndpoints:
      kubeletEndpoint:
        Port: 10250
    images:
    - names:
      - registry.k8s.io/etcd:3.5.4-0
      sizeBytes: 102157811
    - names:
      - docker.io/library/import-2022-10-25@sha256:4002c19dafb94b1995fc598fae590f70cac10135f61ca2551bd97aae37ed9c4a
      - registry.k8s.io/kube-apiserver:v1.25.3
      sizeBytes: 76530158
    - names:
      - docker.io/library/import-2022-10-25@sha256:1c35781a4b6011d5c27bedbba7ca130db72c4aaf74d108c60bc77ae49130e5e4
      - registry.k8s.io/kube-controller-manager:v1.25.3
      sizeBytes: 64499836
    - names:
      - docker.io/library/import-2022-10-25@sha256:0dae4b69c2aa90e6c24691ebbe2e860e2a1ae68463a622c627fb58110153d950
      - registry.k8s.io/kube-proxy:v1.25.3
      sizeBytes: 63275005
    - names:
      - docker.io/library/import-2022-10-25@sha256:409b0e81d9aecf59df96df445a3171f43e2ae834ef6c9e77b1492c4d19bfd78d
      - registry.k8s.io/kube-scheduler:v1.25.3
      sizeBytes: 51921020
    - names:
      - docker.io/kindest/kindnetd:v20221004-44d545d1
      sizeBytes: 25830582
    - names:
      - docker.io/kindest/local-path-provisioner:v0.0.22-kind.0
      sizeBytes: 17375346
    - names:
      - registry.k8s.io/coredns/coredns:v1.9.3
      sizeBytes: 14837849
    - names:
      - docker.io/kindest/local-path-helper:v20220607-9a4d8d2a
      sizeBytes: 2859509
    - names:
      - registry.k8s.io/pause:3.7
      sizeBytes: 311278
    nodeInfo:
      architecture: amd64
      bootID: bdef9f66-f199-4ef9-ba92-3a9902aaebe2
      containerRuntimeVersion: containerd://1.6.9
      kernelVersion: 6.1.11-200.fc37.x86_64
      kubeProxyVersion: v1.25.3
      kubeletVersion: v1.25.3
      machineID: 4a57c491734b40f4a2f006225217d80f
      operatingSystem: linux
      osImage: Ubuntu 22.04.1 LTS
      systemUUID: 294b1e5f-78d3-44a2-8161-c73979c56e25
kind: List
metadata:
  resourceVersion: ""

@alegrey91
Copy link
Contributor

@0xquark sorry, I can't understand why my comments where hidden.

@0xquark
Copy link
Author

0xquark commented Mar 23, 2023

I couldn't understand why we have to split into 3 different rules, isn't having a single function to deal with cronjob,pod and workload a much efficient way ?

This should be for compatibility reasons. It is hard to maintain the same code written in different ways. Keep in mind that you have also to check for PodSecurityContext, not only for SecurityContext.

Sure! I'll update the code

@alegrey91
Copy link
Contributor

Hi @0xquark, any news?

@0xquark
Copy link
Author

0xquark commented Mar 30, 2023

Sorry for the delay! @alegrey91. I've implemented the changes for the first two rules. I had been busy with working over the port scanner, I'll push the code with other two rules by this weekend.

@alegrey91
Copy link
Contributor

Great! Thanks for the update!

@0xquark
Copy link
Author

0xquark commented Apr 15, 2023

I've added a check for OS but still while testing it takes the input as other than windows and returning {} as result. Could you check where i am going wrong?

Try changing the check with this:

system_info := input[_]
system_info.kind == "Node"
system_info.metadata.labels.kubernetes.io/os == "windows"

And passing a Node resource as input. You can use this as example:

apiVersion: v1
items:
- apiVersion: v1
  kind: Node
  metadata:
    annotations:
      kubeadm.alpha.kubernetes.io/cri-socket: unix:///run/containerd/containerd.sock
      node.alpha.kubernetes.io/ttl: "0"
      volumes.kubernetes.io/controller-managed-attach-detach: "true"
    creationTimestamp: "2023-03-13T20:37:33Z"
    labels:
      beta.kubernetes.io/arch: amd64
      beta.kubernetes.io/os: linux
      kubernetes.io/arch: amd64
      kubernetes.io/hostname: test-control-plane
      kubernetes.io/os: linux
      node-role.kubernetes.io/control-plane: ""
      node.kubernetes.io/exclude-from-external-load-balancers: ""
    name: test-control-plane
    resourceVersion: "404"
    uid: 34f43f52-de94-45ae-ba8d-a43e595cf2d6
  spec:
    podCIDR: 10.244.0.0/24
    podCIDRs:
    - 10.244.0.0/24
    providerID: kind://docker/test/test-control-plane
  status:
    addresses:
    - address: 172.18.0.2
      type: InternalIP
    - address: test-control-plane
      type: Hostname
    allocatable:
      cpu: "8"
      ephemeral-storage: 486745Mi
      hugepages-1Gi: "0"
      hugepages-2Mi: "0"
      memory: 16255180Ki
      pods: "110"
    capacity:
      cpu: "8"
      ephemeral-storage: 486745Mi
      hugepages-1Gi: "0"
      hugepages-2Mi: "0"
      memory: 16255180Ki
      pods: "110"
    conditions:
    - lastHeartbeatTime: "2023-03-13T20:37:57Z"
      lastTransitionTime: "2023-03-13T20:37:31Z"
      message: kubelet has sufficient memory available
      reason: KubeletHasSufficientMemory
      status: "False"
      type: MemoryPressure
    - lastHeartbeatTime: "2023-03-13T20:37:57Z"
      lastTransitionTime: "2023-03-13T20:37:31Z"
      message: kubelet has no disk pressure
      reason: KubeletHasNoDiskPressure
      status: "False"
      type: DiskPressure
    - lastHeartbeatTime: "2023-03-13T20:37:57Z"
      lastTransitionTime: "2023-03-13T20:37:31Z"
      message: kubelet has sufficient PID available
      reason: KubeletHasSufficientPID
      status: "False"
      type: PIDPressure
    - lastHeartbeatTime: "2023-03-13T20:37:57Z"
      lastTransitionTime: "2023-03-13T20:37:57Z"
      message: kubelet is posting ready status
      reason: KubeletReady
      status: "True"
      type: Ready
    daemonEndpoints:
      kubeletEndpoint:
        Port: 10250
    images:
    - names:
      - registry.k8s.io/etcd:3.5.4-0
      sizeBytes: 102157811
    - names:
      - docker.io/library/import-2022-10-25@sha256:4002c19dafb94b1995fc598fae590f70cac10135f61ca2551bd97aae37ed9c4a
      - registry.k8s.io/kube-apiserver:v1.25.3
      sizeBytes: 76530158
    - names:
      - docker.io/library/import-2022-10-25@sha256:1c35781a4b6011d5c27bedbba7ca130db72c4aaf74d108c60bc77ae49130e5e4
      - registry.k8s.io/kube-controller-manager:v1.25.3
      sizeBytes: 64499836
    - names:
      - docker.io/library/import-2022-10-25@sha256:0dae4b69c2aa90e6c24691ebbe2e860e2a1ae68463a622c627fb58110153d950
      - registry.k8s.io/kube-proxy:v1.25.3
      sizeBytes: 63275005
    - names:
      - docker.io/library/import-2022-10-25@sha256:409b0e81d9aecf59df96df445a3171f43e2ae834ef6c9e77b1492c4d19bfd78d
      - registry.k8s.io/kube-scheduler:v1.25.3
      sizeBytes: 51921020
    - names:
      - docker.io/kindest/kindnetd:v20221004-44d545d1
      sizeBytes: 25830582
    - names:
      - docker.io/kindest/local-path-provisioner:v0.0.22-kind.0
      sizeBytes: 17375346
    - names:
      - registry.k8s.io/coredns/coredns:v1.9.3
      sizeBytes: 14837849
    - names:
      - docker.io/kindest/local-path-helper:v20220607-9a4d8d2a
      sizeBytes: 2859509
    - names:
      - registry.k8s.io/pause:3.7
      sizeBytes: 311278
    nodeInfo:
      architecture: amd64
      bootID: bdef9f66-f199-4ef9-ba92-3a9902aaebe2
      containerRuntimeVersion: containerd://1.6.9
      kernelVersion: 6.1.11-200.fc37.x86_64
      kubeProxyVersion: v1.25.3
      kubeletVersion: v1.25.3
      machineID: 4a57c491734b40f4a2f006225217d80f
      operatingSystem: linux
      osImage: Ubuntu 22.04.1 LTS
      systemUUID: 294b1e5f-78d3-44a2-8161-c73979c56e25
kind: List
metadata:
  resourceVersion: ""

While using this, i am running into this error "rego_unsafe_var_error: var os is unsafe"

Signed-off-by: Karanjot Singh <[email protected]>
@alegrey91
Copy link
Contributor

Try this way:

system_info.metadata.labels["kubernetes.io/os"] == "windows"

@yuleib yuleib deleted the branch kubescape:dev May 16, 2023 09:43
@yuleib yuleib closed this May 16, 2023
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.

3 participants