From dab452f518ba8097949025daab9082fc7487cb3b Mon Sep 17 00:00:00 2001 From: Daniel-GrunbergerCA Date: Wed, 1 Sep 2021 08:54:29 +0300 Subject: [PATCH] First commit --- .github/workflows/export.yaml | 55 +++ .gitignore | 16 + .idea/.gitignore | 3 + README.md | 18 + ...ationscredentialsinconfigurationfiles.json | 11 + controls/ListKubernetessecrets.json | 11 + controls/SSHserverrunninginsidecontainer.json | 11 + controls/accesscontainerserviceaccount.json | 11 + controls/accessk8sdashboard.json | 11 + controls/accesskubeletAPI.json | 11 + controls/accesstillerendpoint.json | 10 + controls/allowedhostpath.json | 10 + controls/allowprivilegeescalation.json | 10 + controls/anonymousrequests.json | 11 + controls/applicationexploitRCE.json | 11 + controls/automaticmappingserviceaccount.json | 10 + controls/backdoorcontainer.json | 11 + controls/bash-cmdinsidecontainer.json | 11 + controls/clearcontainerlogs.json | 11 + controls/cluster-adminbinding.json | 11 + controls/clusterInternalnetworking.json | 11 + controls/compromisedimagesinregistry.json | 11 + controls/configuredlivenessprobe.json | 10 + controls/configuredreadinessprobe.json | 10 + controls/containerhostport.json | 10 + controls/controlplanehardening.json | 10 + controls/coreDNSpoisoning.json | 11 + controls/dangerouscapabilities.json | 10 + controls/datadestruction.json | 11 + controls/deleteKubernetesevents.json | 11 + controls/execintocontainer.json | 11 + controls/exposeddashboard.json | 11 + controls/exposedsensitiveinterfaces.json | 10 + controls/hostPathmount.json | 11 + controls/hostnetworkaccess.json | 10 + controls/hostpidipcprivileges.json | 10 + controls/immutablecontainerfilesystem.json | 10 + controls/ingressandegressblocked.json | 10 + controls/insecurecapabilities.json | 10 + controls/instancemetadataAPI..json | 11 + controls/kubernetescronJob.json | 11 + controls/linuxhardening.json | 10 + ...maliciousadmissioncontroller-mutating.json | 11 + ...liciousadmissioncontroller-validating.json | 11 + controls/morethanonereplicas.json | 10 + controls/mountserviceprincipal.json | 11 + controls/namesimilarity.json | 11 + controls/networkmapping.json | 11 + controls/networkpolicies.json | 11 + controls/newcontainer.json | 11 + controls/nonrootcontainers.json | 10 + controls/podspecificversiontag.json | 10 + controls/privilegedcontainer.json | 11 + controls/resourcehijacking.json | 11 + controls/resourcepolicies.json | 10 + controls/resourcescpulimit.json | 10 + controls/resourcesmemorylimit.json | 10 + controls/sidecarinjection.json | 11 + controls/useridlessthanthousand.json | 10 + controls/vulnerableapplication.json | 11 + controls/writablehostPathmount.json | 11 + export.py | 90 +++++ frameworks/MITRE.json | 37 ++ frameworks/NSAframework.json | 29 ++ frameworks/developer_framework.json | 25 ++ rules/RBAC-enabled/raw.rego | 74 ++++ .../access-container-service-account/raw.rego | 304 +++++++++++++++++ .../rule.metadata.json | 38 +++ rules/access-tiller-endpoint/raw.rego | 19 ++ .../access-tiller-endpoint/rule.metadata.json | 25 ++ rules/alert-any-hostpath/raw.rego | 60 ++++ rules/alert-any-hostpath/rule.metadata.json | 31 ++ rules/alert-rw-hostpath/raw.rego | 86 +++++ rules/alert-rw-hostpath/rule.metadata.json | 38 +++ rules/anonymous-requests/raw.rego | 16 + rules/anonymous-requests/rule.metadata.json | 30 ++ rules/automount-service-account/raw.rego | 21 ++ .../rule.metadata.json | 25 ++ rules/configured-liveness-probe/raw.rego | 51 +++ .../rule.metadata.json | 31 ++ rules/configured-readiness-probe/raw.rego | 51 +++ .../rule.metadata.json | 31 ++ rules/container-hostPort/raw.rego | 58 ++++ rules/container-hostPort/rule.metadata.json | 31 ++ rules/container-image-repository/raw.rego | 67 ++++ .../rule.metadata.json | 31 ++ rules/dangerous-capabilities/raw.rego | 54 +++ .../dangerous-capabilities/rule.metadata.json | 31 ++ rules/deny-RCE-vuln-image-pods/raw.rego | 319 +++++++++++++++++ .../rule.metadata.json | 37 ++ rules/deny-vuln-image-pods/raw.rego | 131 +++++++ rules/deny-vuln-image-pods/rule.metadata.json | 39 +++ rules/exec-into-container/raw.rego | 110 ++++++ rules/exec-into-container/rule.metadata.json | 32 ++ rules/exposed-sensitive-interfaces/raw.rego | 107 ++++++ .../rule.metadata.json | 36 ++ rules/host-network-access/raw.rego | 52 +++ rules/host-network-access/rule.metadata.json | 31 ++ rules/host-pid-ipc-privileges/raw.rego | 106 ++++++ .../rule.metadata.json | 31 ++ rules/image-pull-secrets/raw.rego | 19 ++ rules/image-pull-secrets/rule.metadata.json | 25 ++ rules/immutable-container-filesystem/raw.rego | 57 ++++ .../rule.metadata.json | 31 ++ rules/ingress-and-egress-blocked/raw.rego | 167 +++++++++ .../rule.metadata.json | 32 ++ rules/insecure-capabilities/raw.rego | 54 +++ .../insecure-capabilities/rule.metadata.json | 31 ++ rules/insecure-port-flag/raw.rego | 41 +++ rules/insecure-port-flag/rule.metadata.json | 31 ++ rules/instance-metadata-api-access/raw.rego | 80 +++++ .../rule.metadata.json | 25 ++ rules/internal-networking/raw.rego | 26 ++ rules/internal-networking/rule.metadata.json | 26 ++ rules/linux-hardening/raw.rego | 85 +++++ rules/linux-hardening/rule.metadata.json | 31 ++ rules/list-all-mutating-webhooks/raw.rego | 17 + .../rule.metada.json | 25 ++ rules/list-all-validating-webhooks/raw.rego | 19 ++ .../rule.metadata.json | 25 ++ rules/more-than-one-replicas/raw.rego | 28 ++ .../more-than-one-replicas/rule.metadata.json | 31 ++ rules/non-root-containers/raw.rego | 139 ++++++++ rules/non-root-containers/rule.metadata.json | 31 ++ rules/pod-specific-version-tag/raw.rego | 54 +++ .../rule.metadata.json | 31 ++ rules/resource-policies/raw.rego | 111 ++++++ rules/resource-policies/rule.metadata.json | 34 ++ rules/resources-cpu-limit/raw.rego | 51 +++ rules/resources-cpu-limit/rule.metadata.json | 31 ++ rules/resources-memory-limit/raw.rego | 51 +++ .../resources-memory-limit/rule.metadata.json | 31 ++ rules/rule-access-dashboard/raw.rego | 105 ++++++ .../rule-access-dashboard/rule.metadata.json | 26 ++ rules/rule-access-kubelet-API/raw.rego | 19 ++ .../rule.metadata.json | 25 ++ .../raw.rego | 58 ++++ .../rule.metadata.json | 31 ++ rules/rule-bash-cmd-inside-container/raw.rego | 102 ++++++ .../rule.metadata.json | 36 ++ .../raw.rego | 321 ++++++++++++++++++ .../rule.metadata.json | 32 ++ rules/rule-can-create-modify-pod/raw.rego | 132 +++++++ .../rule.metadata.json | 32 ++ .../rule-can-create-pod-kube-system/raw.rego | 115 +++++++ .../rule.metadata.json | 32 ++ rules/rule-can-delete-create-service/raw.rego | 113 ++++++ .../rule.metadata.json | 32 ++ rules/rule-can-delete-k8s-events/raw.rego | 112 ++++++ .../rule.metadata.json | 32 ++ rules/rule-can-delete-logs/raw.rego | 121 +++++++ rules/rule-can-delete-logs/rule.metadata.json | 32 ++ .../raw.rego | 109 ++++++ .../rule.metadata.json | 32 ++ rules/rule-can-list-get-secrets/raw.rego | 118 +++++++ .../rule.metadata.json | 32 ++ rules/rule-can-ssh-to-pod/raw.rego | 97 ++++++ rules/rule-can-ssh-to-pod/rule.metadata.json | 32 ++ rules/rule-can-update-configmap/raw.rego | 141 ++++++++ .../rule.metadata.json | 33 ++ rules/rule-credentials-configmap/raw.rego | 63 ++++ .../rule.metadata.json | 25 ++ rules/rule-credentials-in-env-var/raw.rego | 74 ++++ .../rule.metadata.json | 31 ++ rules/rule-deny-cronjobs/raw.rego | 18 + rules/rule-deny-cronjobs/rule.metadata.json | 25 ++ rules/rule-excessive-delete-rights/raw.rego | 136 ++++++++ .../rule.metadata.json | 28 ++ rules/rule-exposed-dashboard/raw.rego | 37 ++ .../rule-exposed-dashboard/rule.metadata.json | 26 ++ .../raw.rego | 128 +++++++ .../rule.metadata.json | 31 ++ rules/rule-list-all-cluster-admins/raw.rego | 96 ++++++ .../rule.metadata.json | 32 ++ rules/rule-name-similarity/raw.rego | 29 ++ rules/rule-name-similarity/rule.metadata.json | 27 ++ rules/rule-privilege-escalation/raw.rego | 57 ++++ .../rule.metadata.json | 33 ++ rules/security-context-in-pod/raw.rego | 63 ++++ .../rule.metadata.json | 31 ++ rules/sidecar-injection/raw.rego | 107 ++++++ rules/sidecar-injection/rule.metadata.json | 31 ++ rules/user-id-less-than-thousands/raw.rego | 124 +++++++ .../rule.metadata.json | 31 ++ 184 files changed, 7966 insertions(+) create mode 100644 .github/workflows/export.yaml create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 README.md create mode 100644 controls/Applicationscredentialsinconfigurationfiles.json create mode 100644 controls/ListKubernetessecrets.json create mode 100644 controls/SSHserverrunninginsidecontainer.json create mode 100644 controls/accesscontainerserviceaccount.json create mode 100644 controls/accessk8sdashboard.json create mode 100644 controls/accesskubeletAPI.json create mode 100644 controls/accesstillerendpoint.json create mode 100644 controls/allowedhostpath.json create mode 100644 controls/allowprivilegeescalation.json create mode 100644 controls/anonymousrequests.json create mode 100644 controls/applicationexploitRCE.json create mode 100644 controls/automaticmappingserviceaccount.json create mode 100644 controls/backdoorcontainer.json create mode 100644 controls/bash-cmdinsidecontainer.json create mode 100644 controls/clearcontainerlogs.json create mode 100644 controls/cluster-adminbinding.json create mode 100644 controls/clusterInternalnetworking.json create mode 100644 controls/compromisedimagesinregistry.json create mode 100644 controls/configuredlivenessprobe.json create mode 100644 controls/configuredreadinessprobe.json create mode 100644 controls/containerhostport.json create mode 100644 controls/controlplanehardening.json create mode 100644 controls/coreDNSpoisoning.json create mode 100644 controls/dangerouscapabilities.json create mode 100644 controls/datadestruction.json create mode 100644 controls/deleteKubernetesevents.json create mode 100644 controls/execintocontainer.json create mode 100644 controls/exposeddashboard.json create mode 100644 controls/exposedsensitiveinterfaces.json create mode 100644 controls/hostPathmount.json create mode 100644 controls/hostnetworkaccess.json create mode 100644 controls/hostpidipcprivileges.json create mode 100644 controls/immutablecontainerfilesystem.json create mode 100644 controls/ingressandegressblocked.json create mode 100644 controls/insecurecapabilities.json create mode 100644 controls/instancemetadataAPI..json create mode 100644 controls/kubernetescronJob.json create mode 100644 controls/linuxhardening.json create mode 100644 controls/maliciousadmissioncontroller-mutating.json create mode 100644 controls/maliciousadmissioncontroller-validating.json create mode 100644 controls/morethanonereplicas.json create mode 100644 controls/mountserviceprincipal.json create mode 100644 controls/namesimilarity.json create mode 100644 controls/networkmapping.json create mode 100644 controls/networkpolicies.json create mode 100644 controls/newcontainer.json create mode 100644 controls/nonrootcontainers.json create mode 100644 controls/podspecificversiontag.json create mode 100644 controls/privilegedcontainer.json create mode 100644 controls/resourcehijacking.json create mode 100644 controls/resourcepolicies.json create mode 100644 controls/resourcescpulimit.json create mode 100644 controls/resourcesmemorylimit.json create mode 100644 controls/sidecarinjection.json create mode 100644 controls/useridlessthanthousand.json create mode 100644 controls/vulnerableapplication.json create mode 100644 controls/writablehostPathmount.json create mode 100644 export.py create mode 100644 frameworks/MITRE.json create mode 100644 frameworks/NSAframework.json create mode 100644 frameworks/developer_framework.json create mode 100644 rules/RBAC-enabled/raw.rego create mode 100644 rules/access-container-service-account/raw.rego create mode 100644 rules/access-container-service-account/rule.metadata.json create mode 100644 rules/access-tiller-endpoint/raw.rego create mode 100644 rules/access-tiller-endpoint/rule.metadata.json create mode 100644 rules/alert-any-hostpath/raw.rego create mode 100644 rules/alert-any-hostpath/rule.metadata.json create mode 100644 rules/alert-rw-hostpath/raw.rego create mode 100644 rules/alert-rw-hostpath/rule.metadata.json create mode 100644 rules/anonymous-requests/raw.rego create mode 100644 rules/anonymous-requests/rule.metadata.json create mode 100644 rules/automount-service-account/raw.rego create mode 100644 rules/automount-service-account/rule.metadata.json create mode 100644 rules/configured-liveness-probe/raw.rego create mode 100644 rules/configured-liveness-probe/rule.metadata.json create mode 100644 rules/configured-readiness-probe/raw.rego create mode 100644 rules/configured-readiness-probe/rule.metadata.json create mode 100644 rules/container-hostPort/raw.rego create mode 100644 rules/container-hostPort/rule.metadata.json create mode 100644 rules/container-image-repository/raw.rego create mode 100644 rules/container-image-repository/rule.metadata.json create mode 100644 rules/dangerous-capabilities/raw.rego create mode 100644 rules/dangerous-capabilities/rule.metadata.json create mode 100644 rules/deny-RCE-vuln-image-pods/raw.rego create mode 100644 rules/deny-RCE-vuln-image-pods/rule.metadata.json create mode 100644 rules/deny-vuln-image-pods/raw.rego create mode 100644 rules/deny-vuln-image-pods/rule.metadata.json create mode 100644 rules/exec-into-container/raw.rego create mode 100644 rules/exec-into-container/rule.metadata.json create mode 100644 rules/exposed-sensitive-interfaces/raw.rego create mode 100644 rules/exposed-sensitive-interfaces/rule.metadata.json create mode 100644 rules/host-network-access/raw.rego create mode 100644 rules/host-network-access/rule.metadata.json create mode 100644 rules/host-pid-ipc-privileges/raw.rego create mode 100644 rules/host-pid-ipc-privileges/rule.metadata.json create mode 100644 rules/image-pull-secrets/raw.rego create mode 100644 rules/image-pull-secrets/rule.metadata.json create mode 100644 rules/immutable-container-filesystem/raw.rego create mode 100644 rules/immutable-container-filesystem/rule.metadata.json create mode 100644 rules/ingress-and-egress-blocked/raw.rego create mode 100644 rules/ingress-and-egress-blocked/rule.metadata.json create mode 100644 rules/insecure-capabilities/raw.rego create mode 100644 rules/insecure-capabilities/rule.metadata.json create mode 100644 rules/insecure-port-flag/raw.rego create mode 100644 rules/insecure-port-flag/rule.metadata.json create mode 100644 rules/instance-metadata-api-access/raw.rego create mode 100644 rules/instance-metadata-api-access/rule.metadata.json create mode 100644 rules/internal-networking/raw.rego create mode 100644 rules/internal-networking/rule.metadata.json create mode 100644 rules/linux-hardening/raw.rego create mode 100644 rules/linux-hardening/rule.metadata.json create mode 100644 rules/list-all-mutating-webhooks/raw.rego create mode 100644 rules/list-all-mutating-webhooks/rule.metada.json create mode 100644 rules/list-all-validating-webhooks/raw.rego create mode 100644 rules/list-all-validating-webhooks/rule.metadata.json create mode 100644 rules/more-than-one-replicas/raw.rego create mode 100644 rules/more-than-one-replicas/rule.metadata.json create mode 100644 rules/non-root-containers/raw.rego create mode 100644 rules/non-root-containers/rule.metadata.json create mode 100644 rules/pod-specific-version-tag/raw.rego create mode 100644 rules/pod-specific-version-tag/rule.metadata.json create mode 100644 rules/resource-policies/raw.rego create mode 100644 rules/resource-policies/rule.metadata.json create mode 100644 rules/resources-cpu-limit/raw.rego create mode 100644 rules/resources-cpu-limit/rule.metadata.json create mode 100644 rules/resources-memory-limit/raw.rego create mode 100644 rules/resources-memory-limit/rule.metadata.json create mode 100644 rules/rule-access-dashboard/raw.rego create mode 100644 rules/rule-access-dashboard/rule.metadata.json create mode 100644 rules/rule-access-kubelet-API/raw.rego create mode 100644 rules/rule-access-kubelet-API/rule.metadata.json create mode 100644 rules/rule-allow-privilegege-escalation/raw.rego create mode 100644 rules/rule-allow-privilegege-escalation/rule.metadata.json create mode 100644 rules/rule-bash-cmd-inside-container/raw.rego create mode 100644 rules/rule-bash-cmd-inside-container/rule.metadata.json create mode 100644 rules/rule-can-create-bind-escalate-role/raw.rego create mode 100644 rules/rule-can-create-bind-escalate-role/rule.metadata.json create mode 100644 rules/rule-can-create-modify-pod/raw.rego create mode 100644 rules/rule-can-create-modify-pod/rule.metadata.json create mode 100644 rules/rule-can-create-pod-kube-system/raw.rego create mode 100644 rules/rule-can-create-pod-kube-system/rule.metadata.json create mode 100644 rules/rule-can-delete-create-service/raw.rego create mode 100644 rules/rule-can-delete-create-service/rule.metadata.json create mode 100644 rules/rule-can-delete-k8s-events/raw.rego create mode 100644 rules/rule-can-delete-k8s-events/rule.metadata.json create mode 100644 rules/rule-can-delete-logs/raw.rego create mode 100644 rules/rule-can-delete-logs/rule.metadata.json create mode 100644 rules/rule-can-impersonate-users-groups/raw.rego create mode 100644 rules/rule-can-impersonate-users-groups/rule.metadata.json create mode 100644 rules/rule-can-list-get-secrets/raw.rego create mode 100644 rules/rule-can-list-get-secrets/rule.metadata.json create mode 100644 rules/rule-can-ssh-to-pod/raw.rego create mode 100644 rules/rule-can-ssh-to-pod/rule.metadata.json create mode 100644 rules/rule-can-update-configmap/raw.rego create mode 100644 rules/rule-can-update-configmap/rule.metadata.json create mode 100644 rules/rule-credentials-configmap/raw.rego create mode 100644 rules/rule-credentials-configmap/rule.metadata.json create mode 100644 rules/rule-credentials-in-env-var/raw.rego create mode 100644 rules/rule-credentials-in-env-var/rule.metadata.json create mode 100644 rules/rule-deny-cronjobs/raw.rego create mode 100644 rules/rule-deny-cronjobs/rule.metadata.json create mode 100644 rules/rule-excessive-delete-rights/raw.rego create mode 100644 rules/rule-excessive-delete-rights/rule.metadata.json create mode 100644 rules/rule-exposed-dashboard/raw.rego create mode 100644 rules/rule-exposed-dashboard/rule.metadata.json create mode 100644 rules/rule-identify-blacklisted-image-registries/raw.rego create mode 100644 rules/rule-identify-blacklisted-image-registries/rule.metadata.json create mode 100644 rules/rule-list-all-cluster-admins/raw.rego create mode 100644 rules/rule-list-all-cluster-admins/rule.metadata.json create mode 100644 rules/rule-name-similarity/raw.rego create mode 100644 rules/rule-name-similarity/rule.metadata.json create mode 100644 rules/rule-privilege-escalation/raw.rego create mode 100644 rules/rule-privilege-escalation/rule.metadata.json create mode 100644 rules/security-context-in-pod/raw.rego create mode 100644 rules/security-context-in-pod/rule.metadata.json create mode 100644 rules/sidecar-injection/raw.rego create mode 100644 rules/sidecar-injection/rule.metadata.json create mode 100644 rules/user-id-less-than-thousands/raw.rego create mode 100644 rules/user-id-less-than-thousands/rule.metadata.json diff --git a/.github/workflows/export.yaml b/.github/workflows/export.yaml new file mode 100644 index 000000000..f5fbed39f --- /dev/null +++ b/.github/workflows/export.yaml @@ -0,0 +1,55 @@ +name: export + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + types: [ closed ] + +jobs: + once: + name: Create release + runs-on: ubuntu-latest + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + steps: + - name: Create a release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v1.0.${{ github.run_number }} + release_name: Release v1.0.${{ github.run_number }} + draft: false + prerelease: false + build: + name: Create cross-platform release build, tag and upload binaries + needs: once + runs-on: ubuntu-latest + strategy: + matrix: + framework: [ mitre, nsa, developer_framework ] + steps: + - uses: actions/checkout@v2 + name: checkout repo content + + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: 3.8 #install the python needed + - name: execute py script # run the run.py to get the latest data + run: | + python export.py + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.once.outputs.upload_url }} + asset_path: release/${{ matrix.framework }}.json + asset_name: release-${{ matrix.framework }} + asset_content_type: application/octet-stream diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..35e523ebd --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ +.vscode/* diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/README.md b/README.md new file mode 100644 index 000000000..de20ac03a --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Regostore +Here we store regos + +### [MITRE Framework](https://www.microsoft.com/security/blog/wp-content/uploads/2021/03/Matrix-1536x926.png) + +| Initial Access | Execution | Persistence | Privilege Escalation | Defense Evasion | Credential Access| Discovery | Lateral Movement | Collection | Impact | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +|Using Cloud credentials|[Exec into container](/controls/execintocontainer.json)|[Backdoor container](/controls/backdoorcontainer.json)|[Privileged container](/controls/privilegedcontainer.json)|[Clear container logs](/controls/clearcontainerlogs.json)|[List k8s secrets](/controls/ListKubernetessecrets.json)|[Access the K8S API server](/controls/accessthek8sAPIserver.json)|Access cloud resources|[Image from private registery](/controls/imagefromPrivateRegistry.json)|[Data Destruction](/controls/datadestruction.json) || +|[Compromised Image in registery](/controls/compromisedimagesinregistry.json)| [bash/cmd inside container](/controls/bash-cmdinsidecontainer.json)|[Writable hostPath mount](/controls/writablehostPathmount.json)|[Cluster-admin binding](/controls/cluster-adminbinding.json)|[Delete K8S events](/controls/deleteKubernetesevents.json)|[Mount service principal](/controls/mountserviceprincipal.json)|[Access Kubelet API](/controls/accesskubeletAPI.json)|[Container service account](/controls/accesscontainerserviceaccount.json)||[Resources Hijacking](/controls/resourcehijacking.json)|| +|kubeconfig file|[New container](/controls/newcontainer.json)|[kubernetes CronJob](/controls/kubernetescronJob.json)|[hostPath mount](/controls/hostPathmount.json)|[Pod/Container name similarity](/controls/namesimilarity.json)|[Access container service account](/controls/accesscontainerserviceaccount.json)|[Network mapping](/controls/networkmapping.json)|[Cluster internal networking](/controls/clusterInternalnetworking.json)||Denial of service|| +|[Application vulnerability](/controls/vulnerableapplication.json)|[Application Exploit (RCE)](/controls/applicationexploitRCE.json)|[Malicious admission controller](/controls/maliciousadmissioncontroller-mutating.json)|Access cloud resources| Connect from Proxy server| [Application credentials in configuration files](/controls/Applicationscredentialsinconfigurationfiles.json)|[Access kubernetes dashboard](/controls/accessk8sdashboard.json)|[Application credentials in configuration](/controls/Applicationscredentialsinconfigurationfiles.json)||||| +|[Exposed Dashboard](/controls/exposeddashboard.json)|[SSH server running insider container](/controls/SSHserverrunninginsidecontainer.json)||||Access managed identity credentials|[instance Metadata API](/controls/instancemetadataAPI..json)|[Writable volume mounts on the host](/controls/writablehostPathmount.json)|||| +|[Exposed sensitive interface](/controls/exposedsensitiveinterfaces.json)|[Sidecar injection](/controls/sidecarinjection.json)||||[Malicious admission controller](/controls/maliciousadmissioncontroller-validating.json)||[Access kubernetes dashboard](/controls/accessk8sdashboard.json)|||| +||||||||[access tiller endpoint](/controls/accesstillerendpoint.json)||||| +||||||||[CoreDNS poisoning](/controls/coreDNSpoisoning.json)||||| +||||||||ARP and IP spoofing||||| + +### [NSA Framework](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) diff --git a/controls/Applicationscredentialsinconfigurationfiles.json b/controls/Applicationscredentialsinconfigurationfiles.json new file mode 100644 index 000000000..61ec20f9d --- /dev/null +++ b/controls/Applicationscredentialsinconfigurationfiles.json @@ -0,0 +1,11 @@ +{ + "name": "Applications credentials in configuration files", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Credential access","Lateral Movement"] + }, + "description": "Attackers who have access to configuration files can steal the stored secrets and use them. Checks if ConfigMaps or pods have sensitive information in configuration.", + "remediation": "Use Kubernetes secrets to store credentials. Use ARMO secret protection solution to improve your security even more.", + "rulesNames": ["rule-credentials-in-env-var", "rule-credentials-configmap" + ] + } diff --git a/controls/ListKubernetessecrets.json b/controls/ListKubernetessecrets.json new file mode 100644 index 000000000..8e10edf3d --- /dev/null +++ b/controls/ListKubernetessecrets.json @@ -0,0 +1,11 @@ +{ + "name": "List Kubernetes secrets", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Credential access"] + }, + "description": "Attackers who have permissions to access secrets can access sensitive information that might include credentials to various services. Determines which subjects can list/get secrets.", + "remediation": "Monitor and approve users and service accounts that can access secrets. You can also protect these secrets using ARMO runtime protection.", + "rulesNames": [ "rule-can-list-get-secrets" + ] +} diff --git a/controls/SSHserverrunninginsidecontainer.json b/controls/SSHserverrunninginsidecontainer.json new file mode 100644 index 000000000..1a15ff276 --- /dev/null +++ b/controls/SSHserverrunninginsidecontainer.json @@ -0,0 +1,11 @@ +{ + "name": "SSH server running inside container", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Execution"] + }, + "description": "An SSH server that is running inside a container may be used by attackers to get remote access to the container. Checks if pods have an open SSH port (22/2222).", + "remediation": "Remove SSH from the container image or limit the access to the SSH server using network policy (Native or ARMO runtime protection).", + "rulesNames": [ "rule-can-ssh-to-pod" + ] +} diff --git a/controls/accesscontainerserviceaccount.json b/controls/accesscontainerserviceaccount.json new file mode 100644 index 000000000..23d3a5171 --- /dev/null +++ b/controls/accesscontainerserviceaccount.json @@ -0,0 +1,11 @@ +{ + "name": "Access container service account", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Credential access"] + }, + "description": "Attackers who get access to a pod can access the SA and perform actions in the cluster, according to the SA permissions. Determines which service accounts can be used to access other resources in the cluster.", + "remediation": "If RBAC is not enabled, you should enable RBAC (refer to the API server documentation). If RBAC is enabled, make sure that you apply least privilege. Monitor and approve privileges of workloads which use kube-api.", + "rulesNames": [ "access-container-service-account" + ] +} diff --git a/controls/accessk8sdashboard.json b/controls/accessk8sdashboard.json new file mode 100644 index 000000000..fbbcedb23 --- /dev/null +++ b/controls/accessk8sdashboard.json @@ -0,0 +1,11 @@ +{ + "name": "Access Kubernetes dashboard", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Discovery","Lateral Movement"] + }, + "description": "Attackers who gain access to the dashboard service account or have its RBAC permissions can use its network access to retrieve information about resources in the cluster or change them. Checks if subject that is not dashboard service account is bound to dashboard role/clusterrole, or - if anyone that is not dashboard pod is associated with its service account.", + "remediation": "Make sure that the “Kubernetes Dashboard” service account is only bound to the Kubernetes dashboard following the least privilege principle.", + "rulesNames": ["rule-access-dashboard" + ] +} diff --git a/controls/accesskubeletAPI.json b/controls/accesskubeletAPI.json new file mode 100644 index 000000000..d7dc36dd8 --- /dev/null +++ b/controls/accesskubeletAPI.json @@ -0,0 +1,11 @@ +{ + "name": "Access Kubelet API", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Discovery"] + }, + "description": "Kubelet is the Kubernetes agent that is installed on each node. Kubelet is responsible for the proper execution of pods that are assigned to the node. Kubelet exposes a read-only API service that does not require authentication (TCP port 10255). Attackers with network access to the host (for example, via running code on a compromised container) can send API requests to the Kubelet API. Specifically querying https://[NODE IP]:10255/pods/ retrieves the running pods on the node. https://[NODE IP]:10255/spec/ retrieves information about the node itself, such as CPU and memory consumption.", + "remediation": "Define network policy (native kubernetes or using ARMO runtime protection). Use ARMO runtime protection capabilities to monitor network traffic.", + "rulesNames": [ + ] + } \ No newline at end of file diff --git a/controls/accesstillerendpoint.json b/controls/accesstillerendpoint.json new file mode 100644 index 000000000..07f635734 --- /dev/null +++ b/controls/accesstillerendpoint.json @@ -0,0 +1,10 @@ +{ + "name": "Access tiller endpoint", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Lateral movement"] + }, + "description":" Attackers may run code on any container that is accessible to the tiller’s service and perform actions in the cluster, using the tiller’s service account, which often has high privileges. Checks if Tiller exists in cluster.", + "remediation": "Use version higher than 2 of Helm which doesn’t use Tiller", + "rulesNames": ["access-tiller-endpoint"] +} diff --git a/controls/allowedhostpath.json b/controls/allowedhostpath.json new file mode 100644 index 000000000..5a8c59de3 --- /dev/null +++ b/controls/allowedhostpath.json @@ -0,0 +1,10 @@ +{ + "name": "Allowed hostPath", + "attributes": { + "armoBuiltin": true + }, + "description": "Mounting host directory to the container can be abused to get access to sensitive data and gain persistence on the host machine.", + "remediation": "Refrain from using host path mount.", + "rulesNames": [ "alert-rw-hostpath" + ] + } diff --git a/controls/allowprivilegeescalation.json b/controls/allowprivilegeescalation.json new file mode 100644 index 000000000..e9f5e6858 --- /dev/null +++ b/controls/allowprivilegeescalation.json @@ -0,0 +1,10 @@ +{ + "name": "Allow privilege escalation", + "attributes": { + "armoBuiltin": true + }, + "description": "Attackers may gain access to a container and uplift its privilege to enable excessive capabilities.", + "remediation": "If your application does not need it, make sure the allowPrivilegeEscalation field of the securityContext is set to false.", + "rulesNames": [ "rule-allow-privilege-escalation" + ] + } diff --git a/controls/anonymousrequests.json b/controls/anonymousrequests.json new file mode 100644 index 000000000..4112dd941 --- /dev/null +++ b/controls/anonymousrequests.json @@ -0,0 +1,11 @@ + +{ + "name": "Anonymous requests", + "attributes": { + "armoBuiltin": true + }, + "description": "In Kubernetes 1.6 and newer, anonymous requests are allowed by default. If there is no RBAC enabled, this type of requests will have authorization to do everything.", + "remediation": "Anonymous requests should be disabled by passing the --anonymous-auth=false option to the API server. Leaving anonymous requests enabled could allow a cyber actor to access cluster resources without authentication.", + "rulesNames": [ "anonymous-requests" + ] + } diff --git a/controls/applicationexploitRCE.json b/controls/applicationexploitRCE.json new file mode 100644 index 000000000..7178398c1 --- /dev/null +++ b/controls/applicationexploitRCE.json @@ -0,0 +1,11 @@ +{ + "name": "Application exploit (RCE)", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Execution"] + }, + "description": "Applications that are vulnerable to a remote code execution vulnerability, enables attackers to run malicious code in the cluster. Determines if pods have vulnerable image with remote code execution using ARMO vulnerability scan (must run vulnerability scan before running posture scan).", + "remediation": "Patch your container with a version that does not have this vulnerability or use ARMO runtime protection (sign the workload).", + "rulesNames": [ "deny-RCE-vuln-image-pods" + ] +} diff --git a/controls/automaticmappingserviceaccount.json b/controls/automaticmappingserviceaccount.json new file mode 100644 index 000000000..9ef13c181 --- /dev/null +++ b/controls/automaticmappingserviceaccount.json @@ -0,0 +1,10 @@ +{ + "name": "Automatic mapping of service account", + "attributes": { + "armoBuiltin": true + }, + "description": "Potential attacker may gain access to a POD and steal its service account token. Therefore, it is recommended to disable automatic mapping of the service account tokens in service account configuration and enable it only for PODs that need to use them.", + "remediation": "Only map token to PODs that are really using them. We suggest disabling the automatic mounting of service account tokens to PODs at the service account level, by specifying the securityContext.readOnlyRootFilesystem field to true, and explicitly enabling the map for the PODs which are using it at the POD spec level.", + "rulesNames": [ "automount-service-account" + ] + } diff --git a/controls/backdoorcontainer.json b/controls/backdoorcontainer.json new file mode 100644 index 000000000..3716fe928 --- /dev/null +++ b/controls/backdoorcontainer.json @@ -0,0 +1,11 @@ +{ + "name": "Backdoor container", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Persistence"] + }, + "description": "Attackers run their malicious code in a container in the cluster. By using the Kubernetes controllers such as DaemonSets or Deployments, attackers can ensure that a constant number of containers run in one, or all, the nodes in the cluster." , + "remediation": "You should apply least privilege principle (we can point to our audit/least privilege screen). Approve the users who can create new containers.", + "rulesNames": [ "rule-can-create-modify-pod" + ] + } \ No newline at end of file diff --git a/controls/bash-cmdinsidecontainer.json b/controls/bash-cmdinsidecontainer.json new file mode 100644 index 000000000..b6ed4b5fd --- /dev/null +++ b/controls/bash-cmdinsidecontainer.json @@ -0,0 +1,11 @@ +{ + "name": "Bash/cmd inside container", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Execution"] + }, + "description": "Attackers who can run new processes inside a container might use cmd/bash script inside a container can use it to execute malicious code. Determines which containers have bash/cmd inside it." , + "remediation": "Remove cmd/bash from the containers you are using.", + "rulesNames": [ "rule-can-bash-cmd-inside-container" + ] + } diff --git a/controls/clearcontainerlogs.json b/controls/clearcontainerlogs.json new file mode 100644 index 000000000..78900909f --- /dev/null +++ b/controls/clearcontainerlogs.json @@ -0,0 +1,11 @@ +{ + "name": "Clear container logs", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Defense Evasion"] + }, + "description": "Attackers may delete the application or OS logs on a compromised container in an attempt to prevent detection of their activity." , + "remediation": "You should apply least privilege principle. Approve the users who can delete logs inside containers.", + "rulesNames": [ "rule-can-delete-logs" + ] + } \ No newline at end of file diff --git a/controls/cluster-adminbinding.json b/controls/cluster-adminbinding.json new file mode 100644 index 000000000..b8c374548 --- /dev/null +++ b/controls/cluster-adminbinding.json @@ -0,0 +1,11 @@ +{ + "name": "Cluster-admin binding", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Privilege escalation"] + }, + "description": "Attackers who have Cluster-admin permissions (can perform any action on any resource), can take advantage of their high privileges for malicious intentions. Determines which subjects have cluster admin permissions.", + "remediation": "You should apply least privilege principle. Monitor and approve cluster admins and make sure users that do not require cluster-admin are not assigned with this role.", + "rulesNames": [ "rule-list-all-cluster-admins" + ] +} diff --git a/controls/clusterInternalnetworking.json b/controls/clusterInternalnetworking.json new file mode 100644 index 000000000..94f0f4f1b --- /dev/null +++ b/controls/clusterInternalnetworking.json @@ -0,0 +1,11 @@ +{ + "name": "Cluster internal networking", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Lateral movement"] + }, + "description": "If no network policy is defined, attackers who gain access to a single container may use it to probe the network. Lists namespaces in which no network policies are defined.", + "remediation": "Define network policy (native K8s or using ARMO runtime protection).", + "rulesNames": [ "internal-networking" + ] +} diff --git a/controls/compromisedimagesinregistry.json b/controls/compromisedimagesinregistry.json new file mode 100644 index 000000000..4d06fabd5 --- /dev/null +++ b/controls/compromisedimagesinregistry.json @@ -0,0 +1,11 @@ +{ + "name": "Compromised images in registry", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Initial Access"] + }, + "description": "In cases where the Kubernetes cluster is deployed in a public cloud (e.g., AKS in Azure, GKE in GCP, or EKS in AWS), compromised cloud credential can lead to cluster takeover. Attackers who have access to the cloud account credentials can get access to the cluster’s management layer.", + "remediation": "Limit the registries from which you pull container images. ", + "rulesNames": ["rule-identify-blacklisted-image-registries" + ] +} \ No newline at end of file diff --git a/controls/configuredlivenessprobe.json b/controls/configuredlivenessprobe.json new file mode 100644 index 000000000..59e2a8422 --- /dev/null +++ b/controls/configuredlivenessprobe.json @@ -0,0 +1,10 @@ +{ + "name": "Configured liveness probe", + "attributes": { + "armoBuiltin": true + }, + "description": "Liveness probe is not configured.", + "remediation": "Ensure Liveness probe is configured", + "rulesNames": [ "configured-liveness-probe" + ] + } diff --git a/controls/configuredreadinessprobe.json b/controls/configuredreadinessprobe.json new file mode 100644 index 000000000..c5cade855 --- /dev/null +++ b/controls/configuredreadinessprobe.json @@ -0,0 +1,10 @@ +{ + "name": "Configured readiness probe", + "attributes": { + "armoBuiltin": true + }, + "description": "Readiness probe is not configured.", + "remediation": "Ensure Readiness probe is configured.", + "rulesNames": [ "configured-readiness-probe" + ] + } diff --git a/controls/containerhostport.json b/controls/containerhostport.json new file mode 100644 index 000000000..1fa5c11f0 --- /dev/null +++ b/controls/containerhostport.json @@ -0,0 +1,10 @@ +{ + "name": "Container hostPort", + "attributes": { + "armoBuiltin": true + }, + "description": "Configuring hostPort limits you to a particular port, and if any two workloads that specify the same HostPort cannot be deployed to the same node. And if the scale of your workload is larger than the number of nodes in your Kubernetes cluster, the deployment fails.", + "remediation": "Make sure you do not configure hostPort for the container, if necessary use NodePort / ClusterIP", + "rulesNames": [ "container-hostPort" + ] + } diff --git a/controls/controlplanehardening.json b/controls/controlplanehardening.json new file mode 100644 index 000000000..5934b961e --- /dev/null +++ b/controls/controlplanehardening.json @@ -0,0 +1,10 @@ +{ + "name": "Control plane hardening", + "attributes": { + "armoBuiltin": true + }, + "description": "Kubernetes control plane API is running with non-secure port enabled which allows attackers to gain unprotected access to the cluster.", + "remediation": "Set the insecure-port flag of the API server to zero.", + "rulesNames": ["insecure-port-flag" + ] + } diff --git a/controls/coreDNSpoisoning.json b/controls/coreDNSpoisoning.json new file mode 100644 index 000000000..62acf32a3 --- /dev/null +++ b/controls/coreDNSpoisoning.json @@ -0,0 +1,11 @@ +{ + "name": "CoreDNS poisoning", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Lateral Movement"] + }, + "description": "If attackers have permissions to modify the coredns ConfigMap, they can change the behavior of the cluster’s DNS, poison it, and take the network identity of other services. Determines which users can update/patch the 'coredns' configmap.", + "remediation": "You should apply least privilege principle. Monitor and approve the users who can modify the 'coredns' configmap.", + "rulesNames": [ "rule-can-update-configmap" + ] + } diff --git a/controls/dangerouscapabilities.json b/controls/dangerouscapabilities.json new file mode 100644 index 000000000..ac898c480 --- /dev/null +++ b/controls/dangerouscapabilities.json @@ -0,0 +1,10 @@ +{ + "name": "Dangerous capabilities", + "attributes": { + "armoBuiltin": true + }, + "description": "Giving dangerous and unnecessary capabilities for a container can increase the impact of a container compromise.", + "remediation": "Remove all dangerous capabilities which aren’t necessary for the container.", + "rulesNames": [ "dangerous-capabilities" + ] + } diff --git a/controls/datadestruction.json b/controls/datadestruction.json new file mode 100644 index 000000000..91590a43e --- /dev/null +++ b/controls/datadestruction.json @@ -0,0 +1,11 @@ +{ + "name": "Data Destruction", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Impact"] + }, + "description": "Attackers may attempt to destroy data and resources in the cluster. This includes deleting deployments, configurations, storage, and compute resources. Determines which subjects can delete resources.", + "remediation": "You should apply least privilege principle. Monitor and approve the users who can delete resources.", + "rulesNames": [ "rule-excessive-delete-rights" + ] + } diff --git a/controls/deleteKubernetesevents.json b/controls/deleteKubernetesevents.json new file mode 100644 index 000000000..c885f8cf6 --- /dev/null +++ b/controls/deleteKubernetesevents.json @@ -0,0 +1,11 @@ +{ + "name": "Delete Kubernetes events", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Defense evasion"] + }, + "description": "Attackers may delete these events to avoid detection of their activity in the cluster. Determines which subjects can delete k8s events.", + "remediation": "You should apply least privilege principle. Monitor and approve the users who can delete events.", + "rulesNames": [ "rule-can-delete-k8s-events" + ] + } diff --git a/controls/execintocontainer.json b/controls/execintocontainer.json new file mode 100644 index 000000000..3f1da28f4 --- /dev/null +++ b/controls/execintocontainer.json @@ -0,0 +1,11 @@ +{ + "name": "Exec into container", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Execution"] + }, + "description": "Attackers who have permissions, can run malicious commands in containers in the cluster using exec command (“kubectl exec”). Determines which subjects have permissions to exec into containers." , + "remediation": "You should apply least privilege principal (we can point to our audit/least privilege screen). You should monitor and approve users who can exec into containers.", + "rulesNames": [ "exec-into-container" + ] +} diff --git a/controls/exposeddashboard.json b/controls/exposeddashboard.json new file mode 100644 index 000000000..22f9ba30b --- /dev/null +++ b/controls/exposeddashboard.json @@ -0,0 +1,11 @@ +{ + "name": "Exposed dashboard", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Initial Access"] + }, + "description": "If Kubernetes dashboard is exposed externally in Dashboard versions before 2.01, it will allow unauthenticated remote management of the cluster.", + "remediation": "Update dashboard version to v2.0.1 or above.", + "rulesNames": ["rule-exposed-dashboard" + ] +} diff --git a/controls/exposedsensitiveinterfaces.json b/controls/exposedsensitiveinterfaces.json new file mode 100644 index 000000000..6043877e2 --- /dev/null +++ b/controls/exposedsensitiveinterfaces.json @@ -0,0 +1,10 @@ +{ + "name": "Exposed sensitive interfaces", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Initial access"] + }, + "description": "Exposing a sensitive interface to the internet poses a security risk. It might enable attacker to run malicious code or deploy containers in the cluster. Checks if known interfaces have externally exposed services.", + "remediation": "Consider not exposing such interfaces.", + "rulesNames": ["exposed-sensitive-interfaces"] +} diff --git a/controls/hostPathmount.json b/controls/hostPathmount.json new file mode 100644 index 000000000..53f078cc0 --- /dev/null +++ b/controls/hostPathmount.json @@ -0,0 +1,11 @@ +{ + "name": "hostPath mount", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Privilege escalation"] + }, + "description": "Mounting host directory to the container can be used by attackers to get access to the underlying host.", + "remediation": "Try to refrain from using host path mount. You can use ARMO runtime protection (encryption capability) to encrypt these files.", + "rulesNames": [ "alert-any-hostpath" + ] +} diff --git a/controls/hostnetworkaccess.json b/controls/hostnetworkaccess.json new file mode 100644 index 000000000..ae41c79bd --- /dev/null +++ b/controls/hostnetworkaccess.json @@ -0,0 +1,10 @@ +{ + "name": "hostNetwork access", + "attributes": { + "armoBuiltin": true + }, + "description": "Potential attackers may gain access to a POD and inherit access to the entire host network. For example, in AWS case, they will have access to the entire VPC.", + "remediation": "Only connect PODs to host network when it is necessary. If not, set the hostNetwork field of the pod spec to false, or erase it (false is the default). Whitelist those PODs that need access to host network by design.", + "rulesNames": [ "host-network-access" + ] + } diff --git a/controls/hostpidipcprivileges.json b/controls/hostpidipcprivileges.json new file mode 100644 index 000000000..43b4bdf24 --- /dev/null +++ b/controls/hostpidipcprivileges.json @@ -0,0 +1,10 @@ +{ + "name": "Host PID/IPC privileges", + "attributes": { + "armoBuiltin": true + }, + "description": "Containers should be as isolated as possible from the host machine. The hostPID and hostIPC fields in Kubernetes may excessively expose the host for potentially malicious actions.", + "remediation": "Apply least privilege principle and disable the hostPID and hostIPC fields unless strictly needed.", + "rulesNames": ["host-pid-ipc-privileges" + ] + } diff --git a/controls/immutablecontainerfilesystem.json b/controls/immutablecontainerfilesystem.json new file mode 100644 index 000000000..637010d7c --- /dev/null +++ b/controls/immutablecontainerfilesystem.json @@ -0,0 +1,10 @@ +{ + "name": "Immutable container filesystem", + "attributes": { + "armoBuiltin": true + }, + "description": "Mutable container filesystem can be abused to gain malicious code and data injection into containers. Use immutable (read-only) filesystem to limit potential attacks.", + "remediation": "Set the filesystem of the container to read-only when possible. If the containers application needs to write into the filesystem, it is possible to mount secondary filesystems for specific directories where application require write access. ", + "rulesNames": [ "immutable-container-filesystem" + ] + } diff --git a/controls/ingressandegressblocked.json b/controls/ingressandegressblocked.json new file mode 100644 index 000000000..206afbec3 --- /dev/null +++ b/controls/ingressandegressblocked.json @@ -0,0 +1,10 @@ +{ + "name": "Ingress and Egress blocked", + "attributes": { + "armoBuiltin": true + }, + "description": "By default, you should disable Ingress and Egress traffic on all pods.", + "remediation": "Define a network policy that restricts ingress and egress connections. ", + "rulesNames": [ "ingress-and-egress-blocked" + ] + } diff --git a/controls/insecurecapabilities.json b/controls/insecurecapabilities.json new file mode 100644 index 000000000..6e19b1451 --- /dev/null +++ b/controls/insecurecapabilities.json @@ -0,0 +1,10 @@ +{ + "name": "Insecure capabilities", + "attributes": { + "armoBuiltin": true + }, + "description": "Giving insecure and unnecessary capabilities for a container can increase the impact of a container compromise.", + "remediation": "Remove all insecure capabilities which aren’t necessary for the container.", + "rulesNames": [ "insecure-capabilities" + ] + } diff --git a/controls/instancemetadataAPI..json b/controls/instancemetadataAPI..json new file mode 100644 index 000000000..8d5469b06 --- /dev/null +++ b/controls/instancemetadataAPI..json @@ -0,0 +1,11 @@ +{ + "name": "Instance Metadata API", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Discovery"] + }, + "description": "Attackers who gain access to a container, may query the metadata API service for getting information about the underlying node. Checks if there is access from the nodes to cloud providers instance metadata services.", + "remediation": "Disable metadata services for pods in cloud provider settings.", + "rulesNames": [ "instance-metadata-api-access" + ] +} diff --git a/controls/kubernetescronJob.json b/controls/kubernetescronJob.json new file mode 100644 index 000000000..1f05f7056 --- /dev/null +++ b/controls/kubernetescronJob.json @@ -0,0 +1,11 @@ +{ + "name": "Kubernetes CronJob", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Persistence"] + }, + "description": "Attackers may use Kubernetes CronJob for scheduling execution of malicious code that would run as a container in the cluster. Lists all CronJobs that exist in the cluster for the user to approve.", + "remediation": "Watch Kubernetes CronJobs and make sure there is a reason for creating these CronJobs.", + "rulesNames": [ "rule-deny-cronjobs" + ] +} diff --git a/controls/linuxhardening.json b/controls/linuxhardening.json new file mode 100644 index 000000000..c590d5ab0 --- /dev/null +++ b/controls/linuxhardening.json @@ -0,0 +1,10 @@ +{ + "name": "Linux hardening", + "attributes": { + "armoBuiltin": true + }, + "description": "Often, containers are given more privileges than actually needed. This behavior can increase the impact of a container compromise.", + "remediation": "Make sure you define at least one linux security hardening property out of AppArmor, Seccomp, SELinux or Capabilities.", + "rulesNames": [ "linux-hardening" + ] + } diff --git a/controls/maliciousadmissioncontroller-mutating.json b/controls/maliciousadmissioncontroller-mutating.json new file mode 100644 index 000000000..fea4aa1b3 --- /dev/null +++ b/controls/maliciousadmissioncontroller-mutating.json @@ -0,0 +1,11 @@ +{ + "name": "Malicious admission controller (mutating)", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Persistence"] + }, + "description": "Attackers can use mutating webhooks to intercept and modify resources in the cluster. Returns mutating webhook configurations to be verified.", + "remediation": "Analyze webhook for malicious behavior", + "rulesNames": [ "list-all-mutating-webhooks" + ] + } diff --git a/controls/maliciousadmissioncontroller-validating.json b/controls/maliciousadmissioncontroller-validating.json new file mode 100644 index 000000000..bc4f642b2 --- /dev/null +++ b/controls/maliciousadmissioncontroller-validating.json @@ -0,0 +1,11 @@ +{ + "name": "Malicious admission controller (validating)", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Credential access"] + }, + "description": "Attackers can use mutating webhooks to intercept and modify resources in the cluster. Returns mutating webhook configurations to be verified.", + "remediation": "Analyze webhook for malicious behavior.", + "rulesNames": [ "list-all-validating-webhooks" + ] + } diff --git a/controls/morethanonereplicas.json b/controls/morethanonereplicas.json new file mode 100644 index 000000000..1a9a789fc --- /dev/null +++ b/controls/morethanonereplicas.json @@ -0,0 +1,10 @@ +{ + "name": "More than one replicas", + "attributes": { + "armoBuiltin": true + }, + "description": "Replicas are set to one.", + "remediation": "Ensure replicas field is set and value is bigger than one.", + "rulesNames": [ "more-than-one-replicas" + ] + } diff --git a/controls/mountserviceprincipal.json b/controls/mountserviceprincipal.json new file mode 100644 index 000000000..a394492da --- /dev/null +++ b/controls/mountserviceprincipal.json @@ -0,0 +1,11 @@ +{ + "name": "Mount service principal", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Credential Access"] + }, + "description": "Determines if any workload contains a hostPath volume.", + "remediation": "Try to refrain from using host path mount. You can use ARMO runtime protection (encryption capability) to encrypt these files.", + "rulesNames": [ "alert-any-hostpath" + ] +} diff --git a/controls/namesimilarity.json b/controls/namesimilarity.json new file mode 100644 index 000000000..ace7c4ba3 --- /dev/null +++ b/controls/namesimilarity.json @@ -0,0 +1,11 @@ +{ + "name": "Pod / container name similarity", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Defense evasion"] + }, + "description": "Pods that are created by controllers such as Deployment or DaemonSet have random suffix in their names. Attackers can use this fact and name their backdoor pods as they were created by the existing controllers. For example, an attacker could create a malicious pod named coredns-{random suffix} which would look related to the CoreDNS Deployment.", + "remediation": "You should look at the reported Pods and make sure they were created and developed by your team. It would be wise to change the Pod names.", + "rulesNames": [ "rule-name-similarity" + ] + } \ No newline at end of file diff --git a/controls/networkmapping.json b/controls/networkmapping.json new file mode 100644 index 000000000..1a0ca9b15 --- /dev/null +++ b/controls/networkmapping.json @@ -0,0 +1,11 @@ +{ + "name": "Network mapping", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Discovery"] + }, + "description": "If no network policy is defined, attackers who gain access to a single container may use it to probe the network. Lists namespaces in which no network policies are defined.", + "remediation": "Define network policy (native Kubernetes or using ARMO runtime protection). Use ARMO runtime protection capabilities to monitor network traffic.", + "rulesNames": [ "internal-networking" + ] +} diff --git a/controls/networkpolicies.json b/controls/networkpolicies.json new file mode 100644 index 000000000..b7140a81e --- /dev/null +++ b/controls/networkpolicies.json @@ -0,0 +1,11 @@ +{ + "name": "Network policies", + "attributes": { + "armoBuiltin": true + }, + "description": "If no network policy is defined, attackers who gain access to a single container may use it to probe the network. Lists namespaces in which no network policies are defined.", + "remediation": "Define network policy.", + "rulesNames": [ "internal-networking" + ] + } + \ No newline at end of file diff --git a/controls/newcontainer.json b/controls/newcontainer.json new file mode 100644 index 000000000..b9bd52a9b --- /dev/null +++ b/controls/newcontainer.json @@ -0,0 +1,11 @@ +{ + "name": "New container", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Execution"] + }, + "description": "Attackers may attempt to run their code in the cluster by deploying a container. Attackers who have permissions to deploy a pod or a controller in the cluster (such as DaemonSet / ReplicaSet / Deployment) can create a new resource for running their code." , + "remediation": "You should apply least privilege principle (we can point to our audit/least privilege screen). Approve the users who can create new containers.", + "rulesNames": [ "rule-can-create-modify-pod" + ] +} \ No newline at end of file diff --git a/controls/nonrootcontainers.json b/controls/nonrootcontainers.json new file mode 100644 index 000000000..fb97db8f6 --- /dev/null +++ b/controls/nonrootcontainers.json @@ -0,0 +1,10 @@ +{ + "name": "Non-root containers", + "attributes": { + "armoBuiltin": true + }, + "description": "Potential attackers may gain access to a container and leverage its privileges to conduct an attack. Hence it is not recommended to deploy containers with root privileges unless it is absolutely necessary.", + "remediation": "If your application does not need root privileges, make sure to define the runAsUser and runAsGroup under the PodSecurityContext to use user ID 1000 or higher, do not turn on allowPrivlegeEscalation bit and runAsNonRoot is true.", + "rulesNames": [ "non-root-containers" + ] + } diff --git a/controls/podspecificversiontag.json b/controls/podspecificversiontag.json new file mode 100644 index 000000000..5ab5bab2e --- /dev/null +++ b/controls/podspecificversiontag.json @@ -0,0 +1,10 @@ +{ + "name": "Specific version tag", + "attributes": { + "armoBuiltin": true + }, + "description": "", + "remediation": "", + "rulesNames": [ "pod-specific-version-tag" + ] + } diff --git a/controls/privilegedcontainer.json b/controls/privilegedcontainer.json new file mode 100644 index 000000000..064a32f91 --- /dev/null +++ b/controls/privilegedcontainer.json @@ -0,0 +1,11 @@ +{ + "name": "Privileged container", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Privilege escalation"] + }, + "description": "Potential attackers may gain access to privileged containers and inherit access to the host resources. Therefore, it is not recommended to deploy privileged containers unless it is absolutely necessary.", + "remediation": "Change the deployment and/or pod definition to unprivileged. The securityContext.privileged should be false.", + "rulesNames": [ "rule-privilege-escalation" + ] +} diff --git a/controls/resourcehijacking.json b/controls/resourcehijacking.json new file mode 100644 index 000000000..73665c859 --- /dev/null +++ b/controls/resourcehijacking.json @@ -0,0 +1,11 @@ +{ + "name": "Resource Hijacking", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Impact"] + }, + "description": "Attackers who have access to a container in the cluster or have permissions to create new containers may abuse them to run compromising tasks, such as running digital currency mining. Determines which subjects have permissions to create/modify pods.", + "remediation": "You should apply least privilege principle. Approve the users who can create/delete pods.", + "rulesNames": [ "rule-can-create-modify-pod" + ] + } diff --git a/controls/resourcepolicies.json b/controls/resourcepolicies.json new file mode 100644 index 000000000..548a7f769 --- /dev/null +++ b/controls/resourcepolicies.json @@ -0,0 +1,10 @@ +{ + "name": "Resource policies", + "attributes": { + "armoBuiltin": true + }, + "description": "CPU and memory resources should have a limit set for every container to prevent resource exhaustion.", + "remediation": "Define LimitRange and ResourceQuota policies to limit resource usage for namespaces or nodes.", + "rulesNames": ["resource-policies" + ] + } diff --git a/controls/resourcescpulimit.json b/controls/resourcescpulimit.json new file mode 100644 index 000000000..1c08845c9 --- /dev/null +++ b/controls/resourcescpulimit.json @@ -0,0 +1,10 @@ +{ + "name": "Resources CPU limit", + "attributes": { + "armoBuiltin": true + }, + "description": "CPU limits are not set.", + "remediation": "Ensure CPU limits are set.", + "rulesNames": [ "resources-cpu-limit" + ] + } diff --git a/controls/resourcesmemorylimit.json b/controls/resourcesmemorylimit.json new file mode 100644 index 000000000..4efa5c1f1 --- /dev/null +++ b/controls/resourcesmemorylimit.json @@ -0,0 +1,10 @@ +{ + "name": "Resources memory limit", + "attributes": { + "armoBuiltin": true + }, + "description": "memory limits are not set.", + "remediation": "Ensure memory limits are set.", + "rulesNames": [ "resources-memory-limit" + ] + } diff --git a/controls/sidecarinjection.json b/controls/sidecarinjection.json new file mode 100644 index 000000000..716b2d721 --- /dev/null +++ b/controls/sidecarinjection.json @@ -0,0 +1,11 @@ +{ + "name": "Sidecar injection", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Execution"] + }, + "description": "A Kubernetes Pod is a group of one or more containers with shared storage and network resources. Sidecar container is a term that is used to describe an additional container that resides alongside the main container. For example, service-mesh proxies are operating as sidecars in the applications’ pods. Attackers can run their code and hide their activity by injecting a sidecar container to a legitimate pod in the cluster instead of running their own separated pod in the cluster.", + "remediation": "", + "rulesNames": [ "sidecar-injection" + ] + } \ No newline at end of file diff --git a/controls/useridlessthanthousand.json b/controls/useridlessthanthousand.json new file mode 100644 index 000000000..9b9cb1e9d --- /dev/null +++ b/controls/useridlessthanthousand.json @@ -0,0 +1,10 @@ +{ + "name": "User-id-less-than-thousand", + "attributes": { + "armoBuiltin": true + }, + "description": "Potential attackers may gain access to a container and leverage its privileges to conduct an attack. Hence it is not recommended to deploy containers with user/group id less than 1000, unless it is absolutely necessary.", + "remediation": "If your application does not need root privileges, make sure to define the runAsUser and runAsGroup under the PodSecurityContext to use user ID 1000 or higher, do not turn on allowPrivlegeEscalation bit and runAsNonRoot is true.", + "rulesNames": [ "user-id-less-than-thousands" + ] + } diff --git a/controls/vulnerableapplication.json b/controls/vulnerableapplication.json new file mode 100644 index 000000000..875cb7ee5 --- /dev/null +++ b/controls/vulnerableapplication.json @@ -0,0 +1,11 @@ +{ + "name": "Vulnerable application", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Initial Access"] + }, + "description": "Running a vulnerable application in a cluster can enable an attacker initial access to the cluster. Determines if pods/deployments have vulnerable image using ARMO vulnerability scan (must run vulnerability scan before running posture scan). ", + "remediation": "Patch your container with a version that does not have this vulnerability or use ARMO runtime protection (sign the workload).", + "rulesNames": [ "deny-vuln-image-pods" + ] +} diff --git a/controls/writablehostPathmount.json b/controls/writablehostPathmount.json new file mode 100644 index 000000000..b94f73b48 --- /dev/null +++ b/controls/writablehostPathmount.json @@ -0,0 +1,11 @@ +{ + "name": "Writable hostPath mount", + "attributes": { + "armoBuiltin": true, + "microsoftMitreColumns": ["Persistence", "Lateral Movement"] + }, + "description": "Mounting host directory to the container can be used by attackers to get access to the underlying host.", + "remediation": "Try to refrain from using host path mount. You can use ARMO runtime protection (encryption capability) to encrypt these files.", + "rulesNames": [ "alert-rw-hostpath" + ] +} diff --git a/export.py b/export.py new file mode 100644 index 000000000..b8e454c60 --- /dev/null +++ b/export.py @@ -0,0 +1,90 @@ +import json +import os +import subprocess as s +from pathlib import Path + +""" +Export rules controls and frameworks to files in json format +""" +currDir = os.path.abspath(os.getcwd()) + + +def load_rules(): + p1 = currDir + '/rules' + regofile = 'raw.rego' + rules_path = Path(p1).glob('**/*.json') + loaded_rules = {} # rules loaded from file system + + for path in rules_path: + path_in_str = str(path) + with open(path_in_str, "r") as f: + new_rule = json.load(f) + + pos = path_in_str.rfind('/') + with open(path_in_str[:pos + 1] + regofile, 'r') as f: + rule = f.read() + if new_rule: + new_rule["rule"] = rule + + loaded_rules[new_rule['name']] = new_rule + + return loaded_rules + + +def load_controls(loaded_rules: dict): + p2 = currDir + '/controls' + controls_path = Path(p2).glob('**/*.json') + loaded_controls = {} + + for path in controls_path: + path_in_str = str(path) + + with open(path_in_str, "r") as f: + new_control = json.load(f) + new_control["rules"] = [] + + for rule_name in new_control["rulesNames"]: + if rule_name in loaded_rules: + new_control["rules"].append(loaded_rules[rule_name]) + + del new_control["rulesNames"] # remove rule names list from dict + loaded_controls[new_control['name']] = new_control + + return loaded_controls + + +def load_frameworks(loaded_controls: dict): + p3 = currDir + '/frameworks' + frameworks_path = Path(p3).glob('**/*.json') + loaded_frameworks = {} + + for path in frameworks_path: + path_in_str = str(path) + with open(path_in_str, "r") as f: + new_framework = json.load(f) + new_framework["controls"] = [] + + for control_name in new_framework["controlsNames"]: + if control_name in loaded_controls: + new_framework["controls"].append(loaded_controls[control_name]) + + del new_framework["controlsNames"] + loaded_frameworks[new_framework['name']] = new_framework + + return loaded_frameworks + + +def export_json(d: dict, output_path: str): + os.makedirs(output_path, exist_ok=True) + + for k, v in d.items(): + with open(os.path.join(output_path, f"{k.lower()}.json"), "w") as f: + f.write(json.dumps(v, indent=4)) + + +if __name__ == '__main__': + rules = load_rules() + controls = load_controls(loaded_rules=rules) + frameworks = load_frameworks(loaded_controls=controls) + + export_json(d=frameworks, output_path="release") diff --git a/frameworks/MITRE.json b/frameworks/MITRE.json new file mode 100644 index 000000000..78e583950 --- /dev/null +++ b/frameworks/MITRE.json @@ -0,0 +1,37 @@ +{ + "name": "MITRE", + "description": "Testing MITRE for k8s as suggested by microsoft in https://www.microsoft.com/security/blog/wp-content/uploads/2020/04/k8s-matrix.png", + "attributes": { + "armoBuiltin": true + }, + "controlsNames": [ + "Access container service account", + "Access Kubernetes dashboard", + "Access tiller endpoint", + "Application exploit (RCE)", + "Applications credentials in configuration files", + "Bash/cmd inside container", + "Cluster-admin binding", + "Cluster internal networking", + "Exec into container", + "Exposed dashboard", + "Exposed sensitive interfaces", + "hostPath mount", + "Instance Metadata API", + "Kubernetes CronJob", + "List Kubernetes secrets", + "Mount service principal", + "Network mapping", + "New container", + "Privileged container", + "SSH server running inside container", + "Vulnerable application", + "Writable hostPath mount", + "Malicious admission controller (mutating)", + "Malicious admission controller (validating)", + "Delete Kubernetes events", + "CoreDNS poisoning", + "Data Destruction", + "Resource Hijacking" + ] +} diff --git a/frameworks/NSAframework.json b/frameworks/NSAframework.json new file mode 100644 index 000000000..850d707aa --- /dev/null +++ b/frameworks/NSAframework.json @@ -0,0 +1,29 @@ +{ + "name": "NSA", + "description": "Implement NSA security advices for K8s ", + "attributes": { + "armoBuiltin": true + }, + "controlsNames": [ + "Control plane hardening", + "Host PID/IPC privileges", + "Immutable container filesystem", + "Non-root containers", + "Privileged container", + "Allowed hostPath", + "Automatic mapping of service account", + "hostNetwork access", + "Resource policies", + "Exposed dashboard", + "Allow privilege escalation", + "Applications credentials in configuration files", + "Cluster-admin binding", + "Exec into container", + "Dangerous capabilities", + "Insecure capabilities", + "Linux hardening", + "Ingress and Egress blocked", + "Container hostPort", + "Network policies" + ] + } diff --git a/frameworks/developer_framework.json b/frameworks/developer_framework.json new file mode 100644 index 000000000..81af1d1c2 --- /dev/null +++ b/frameworks/developer_framework.json @@ -0,0 +1,25 @@ +{ + "name": "developer_framework", + "description": "Testing MITRE for k8s as suggested by microsoft in https://www.microsoft.com/security/blog/wp-content/uploads/2020/04/k8s-matrix.png", + "attributes": { + "armoBuiltin": true + }, + "controlsNames": [ + "Access container service account", + "Access Kubernetes dashboard", + "Access Kubelet API", + "Applications credentials in configuration files", + "Cluster-admin binding", + "Cluster internal networking", + "Compromised images in registry", + "Exposed dashboard", + "hostPath mount", + "Instance Metadata API", + "Kubernetes CronJob", + "Pod / container name similarity", + "Network mapping", + "Privileged container", + "SSH server running inside container", + "Writable hostPath mount" + ] + } \ No newline at end of file diff --git a/rules/RBAC-enabled/raw.rego b/rules/RBAC-enabled/raw.rego new file mode 100644 index 000000000..f44a8d676 --- /dev/null +++ b/rules/RBAC-enabled/raw.rego @@ -0,0 +1,74 @@ +package armo_builtins + + +#Checks if RBAC is enabled +deny[msga] { + apigouplist := input[_] + groupVersions := [groupVersion | groupVersion = apigouplist.groups[_].versions[_].groupVersion] + + not list_contains(groupVersions, "rbac.authorization.k8s.io/v1") + + msga := { + "alertMessage": sprintf("%s", ["RBAC is not enabled for this cluster"]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [] + } + + } +} + + + +# Fails is rolebinding gives permsisiosn to anonymous user +deny[msga] { + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + rolebinding := rolebindings[_] + + isAnonymous(rolebinding) + + msga := { + "alertMessage": sprintf("the following rolebinding: %v gives permissions to anonymous users", [rolebinding.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [rolebinding] + } + + } +} + + +# Fails is clusterrolebinding gives permsisiosn to anonymous user +deny[msga] { + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + rolebinding := rolebindings[_] + + isAnonymous(rolebinding) + + msga := { + "alertMessage": sprintf("the following rolebinding: %v gives permissions to anonymous users", [rolebinding.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [rolebinding] + } + + } +} + +isAnonymous(binding) { + subject := binding.subjects[_] + subject.name == "system:anonymous" +} + +isAnonymous(binding) { + subject := binding.subjects[_] + subject.name == "system:unauthenticated" +} + +list_contains(list, element) { + some i + list[i] == element +} \ No newline at end of file diff --git a/rules/access-container-service-account/raw.rego b/rules/access-container-service-account/raw.rego new file mode 100644 index 000000000..28757c2df --- /dev/null +++ b/rules/access-container-service-account/raw.rego @@ -0,0 +1,304 @@ +package armo_builtins + + +# Returns for each Pod, what are the permission of its service account + +deny[msga] { + serviceAccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceAccounts[_] + serviceAccountName := serviceaccount.metadata.name + + pods := [pod | pod=input[_]; pod.kind =="Pod"] + pod := pods[_] + pod.spec.serviceAccountName == serviceAccountName + + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + rolebinding := rolebindings[_] + rolesubject := rolebinding.subjects[_] + rolesubject.name == serviceAccountName + + roles := [role | role = input[_]; role.kind == "Role"] + role := roles[_] + role.metadata.name == rolebinding.roleRef.name + + msga := { + "alertMessage": sprintf("Pod: %v has the following permissions in the cluster: %v", [pod.metadata.name, rolebinding.roleRef.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [rolebinding, role, pod] + } + } +} + +# Returns for each Pod, what are the permission of its service account + deny[msga] { + serviceAccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceAccounts[_] + serviceAccountName := serviceaccount.metadata.name + + pods := [pod | pod=input[_]; pod.kind =="Pod"] + pod := pods[_] + pod.spec.serviceAccountName == serviceAccountName + + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + rolebinding := rolebindings[_] + rolesubject := rolebinding.subjects[_] + rolesubject.name == serviceAccountName + + roles := [role | role = input[_]; role.kind == "ClusterRole"] + role := roles[_] + role.metadata.name == rolebinding.roleRef.name + + msga := { + "alertMessage": sprintf("Pod: %v has the following permissions in the cluster: %v", [pod.metadata.name, rolebinding.roleRef.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [rolebinding, role, pod] + } + } +} + +# Returns for each Pod, what are the permission of its service account + + deny[msga] { + serviceAccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceAccounts[_] + serviceAccountName := serviceaccount.metadata.name + + pods := [pod | pod=input[_]; pod.kind =="Pod"] + pod := pods[_] + pod.spec.serviceAccountName == serviceAccountName + + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + rolebinding := rolebindings[_] + rolesubject := rolebinding.subjects[_] + rolesubject.name == serviceAccountName + + roles := [role | role = input[_]; role.kind == "ClusterRole"] + role := roles[_] + role.metadata.name == rolebinding.roleRef.name + + msga := { + "alertMessage": sprintf("Pod: %v has the following permissions in the cluster: %v", [pod.metadata.name, rolebinding.roleRef.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [rolebinding, role, pod] + } + } +} + + + + +### ---------------- ##### + + + +# Returns for each Workloads, what are the permission of its service account +deny[msga] { + serviceAccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceAccounts[_] + serviceAccountName := serviceaccount.metadata.name + + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + + wl.spec.template.spec.serviceAccountName == serviceAccountName + + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + rolebinding := rolebindings[_] + rolesubject := rolebinding.subjects[_] + rolesubject.name == serviceAccountName + + roles := [role | role = input[_]; role.kind == "Role"] + role := roles[_] + role.metadata.name == rolebinding.roleRef.name + + msga := { + "alertMessage": sprintf("%v: %v has the following permissions in the cluster: %v", [wl.kind, wl.metadata.name, rolebinding.roleRef.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [rolebinding, role, wl] + } + } + + + +} + + +# Returns for each Workloads, what are the permission of its service account +deny[msga] { + serviceAccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceAccounts[_] + serviceAccountName := serviceaccount.metadata.name + + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + + wl.spec.template.spec.serviceAccountName == serviceAccountName + + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + rolebinding := rolebindings[_] + rolesubject := rolebinding.subjects[_] + rolesubject.name == serviceAccountName + + roles := [role | role = input[_]; role.kind == "ClusterRole"] + role := roles[_] + role.metadata.name == rolebinding.roleRef.name + + msga := { + "alertMessage": sprintf("%v: %v has the following permissions in the cluster: %v", [wl.kind, wl.metadata.name, rolebinding.roleRef.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [rolebinding, role, wl] + } + } + + +} + + + +# Returns for each Workloads, what are the permission of its service account +deny[msga] { + serviceAccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceAccounts[_] + serviceAccountName := serviceaccount.metadata.name + + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + + wl.spec.template.spec.serviceAccountName == serviceAccountName + + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + rolebinding := rolebindings[_] + rolesubject := rolebinding.subjects[_] + rolesubject.name == serviceAccountName + + roles := [role | role = input[_]; role.kind == "ClusterRole"] + role := roles[_] + role.metadata.name == rolebinding.roleRef.name + + + msga := { + "alertMessage": sprintf("%v: %v has the following permissions in the cluster: %v", [wl.kind, wl.metadata.name, rolebinding.roleRef.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [rolebinding, role, wl] + } + } + +} + + + + +### ---------------- ##### + + +# Returns for each Cronjob, what are the permission of its service account + +deny[msga] { + serviceAccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceAccounts[_] + serviceAccountName := serviceaccount.metadata.name + + wl := input[_] + wl.kind == "CronJob" + wl.spec.jobTemplate.spec.template.spec.serviceAccountName == serviceAccountName + + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + rolebinding := rolebindings[_] + rolesubject := rolebinding.subjects[_] + rolesubject.name == serviceAccountName + + roles := [role | role = input[_]; role.kind == "Role"] + role := roles[_] + role.metadata.name == rolebinding.roleRef.name + + msga := { + "alertMessage": sprintf("Cronjob: %v has the following permissions in the cluster: %v", [wl.metadata.name, rolebinding.roleRef.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [rolebinding, role, wl] + } + } +} + + + +# Returns for each Cronjob, what are the permission of its service account +deny[msga] { + serviceAccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceAccounts[_] + serviceAccountName := serviceaccount.metadata.name + + + wl := input[_] + wl.kind == "CronJob" + wl.spec.jobTemplate.spec.template.spec.serviceAccountName == serviceAccountName + + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + rolebinding := rolebindings[_] + rolesubject := rolebinding.subjects[_] + rolesubject.name == serviceAccountName + + roles := [role | role = input[_]; role.kind == "ClusterRole"] + role := roles[_] + role.metadata.name == rolebinding.roleRef.name + + msga := { + "alertMessage": sprintf("Cronjob: %v has the following permissions in the cluster: %v", [wl.metadata.name, rolebinding.roleRef.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [rolebinding, role, wl] + } + } + +} + + +# Returns for each Cronjob, what are the permission of its service account +deny[msga] { + serviceAccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceAccounts[_] + serviceAccountName := serviceaccount.metadata.name + + + wl := input[_] + wl.kind == "CronJob" + wl.spec.jobTemplate.spec.template.spec.serviceAccountName == serviceAccountName + + + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + rolebinding := rolebindings[_] + rolesubject := rolebinding.subjects[_] + rolesubject.name == serviceAccountName + + roles := [role | role = input[_]; role.kind == "ClusterRole"] + role := roles[_] + role.metadata.name == rolebinding.roleRef.name + + + msga := { + "alertMessage": sprintf("Cronjob: %v has the following permissions in the cluster: %v", [wl.metadata.name, rolebinding.roleRef.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [rolebinding, role, wl] + } + } + +} \ No newline at end of file diff --git a/rules/access-container-service-account/rule.metadata.json b/rules/access-container-service-account/rule.metadata.json new file mode 100644 index 000000000..6331ad0af --- /dev/null +++ b/rules/access-container-service-account/rule.metadata.json @@ -0,0 +1,38 @@ +{ + "name": "access-container-service-account", + "attributes": { + "m$K8sThreatMatrix": "Credential Access::Access container service account, Lateral Movement::Container service account", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob", + "ServiceAccounts", + "RoleBinding", + "ClusterRoleBinding", + "Role", + "ClusterRole" + ] + } + ], + "ruleDependencies": [ + + ], + "description": "determines which service accounts can be used to access other resources in the cluster", + "remediation": "", + "ruleQuery": "armo_builtins" +} diff --git a/rules/access-tiller-endpoint/raw.rego b/rules/access-tiller-endpoint/raw.rego new file mode 100644 index 000000000..aaac9d7a9 --- /dev/null +++ b/rules/access-tiller-endpoint/raw.rego @@ -0,0 +1,19 @@ +package armo_builtins + +# input: deployment +# fails if tiller exists in cluster + +deny[msga] { + deployment := input[_] + deployment.kind == "Deployment" + deployment.metadata.name == "tiller-deploy" + + msga := { + "alertMessage": sprintf("tiller exists in namespace: %v", [deployment.metadata.namespace]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [deployment] + } + } +} \ No newline at end of file diff --git a/rules/access-tiller-endpoint/rule.metadata.json b/rules/access-tiller-endpoint/rule.metadata.json new file mode 100644 index 000000000..7b533ecc2 --- /dev/null +++ b/rules/access-tiller-endpoint/rule.metadata.json @@ -0,0 +1,25 @@ +{ + "name": "access-tiller-endpoint", + "attributes": { + "microsoftK8sThreatMatrix": "Lateral movement::Access tiller endpoint", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment" + ] + } + ], + "ruleDependencies": [], + "description": "fails if tiller exists in cluster", + "remediation": "", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/alert-any-hostpath/raw.rego b/rules/alert-any-hostpath/raw.rego new file mode 100644 index 000000000..dcda42732 --- /dev/null +++ b/rules/alert-any-hostpath/raw.rego @@ -0,0 +1,60 @@ +package armo_builtins + + +deny[msga] { + pod := input[_] + pod.kind == "Pod" + volumes := pod.spec.volumes + volume := volumes[_] + volume.hostPath + podname := pod.metadata.name + + + msga := { + "alertMessage": sprintf("pod: %v has: %v as hostPath volume", [podname, volume.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +#handles majority of workload resources +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + volumes := wl.spec.template.spec.volumes + volume := volumes[_] + volume.hostPath + + + msga := { + "alertMessage": sprintf("%v: %v has: %v as hostPath volume", [wl.kind, wl.metadata.name, volume.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +#handles CronJobs +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + volumes := wl.spec.jobTemplate.spec.template.spec.volumes + volume := volumes[_] + volume.hostPath + + + msga := { + "alertMessage": sprintf("%v: %v has: %v as hostPath volume", [wl.kind, wl.metadata.name, volume.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} \ No newline at end of file diff --git a/rules/alert-any-hostpath/rule.metadata.json b/rules/alert-any-hostpath/rule.metadata.json new file mode 100644 index 000000000..0ddc39db9 --- /dev/null +++ b/rules/alert-any-hostpath/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "alert-any-hostpath", + "attributes": { + "m$K8sThreatMatrix": "Privilege Escalation::hostPath mount", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob", + "Pod" + ] + } + ], + "ruleDependencies": [], + "description": "determines if any workload contains a hostPath volume", + "remediation": "Try to refrain from using hostPath mounts", + "ruleQuery": "" +} \ No newline at end of file diff --git a/rules/alert-rw-hostpath/raw.rego b/rules/alert-rw-hostpath/raw.rego new file mode 100644 index 000000000..7b3ea807d --- /dev/null +++ b/rules/alert-rw-hostpath/raw.rego @@ -0,0 +1,86 @@ +package armo_builtins + +# input: pod +# apiversion: v1 +# does: returns hostPath volumes + +deny[msga] { + pod := input[_] + pod.kind == "Pod" + volumes := pod.spec.volumes + volume := volumes[_] + volume.hostPath + container := pod.spec.containers[_] + volumeMount := container.volumeMounts[_] + volumeMount.name == volume.name + isRWMount(volumeMount) + + podname := pod.metadata.name + + + msga := { + "alertMessage": sprintf("pod: %v has: %v as hostPath volume", [podname, volume.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +#handles majority of workload resources +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + volumes := wl.spec.template.spec.volumes + volume := volumes[_] + volume.hostPath + container := wl.spec.template.spec.containers[_] + volumeMount := container.volumeMounts[_] + volumeMount.name == volume.name + isRWMount(volumeMount) + + + + msga := { + "alertMessage": sprintf("%v: %v has: %v as hostPath volume", [wl.kind, wl.metadata.name, volume.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + + } +} + +#handles CronJobs +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + volumes := wl.spec.jobTemplate.spec.template.spec.volumes + volume := volumes[_] + volume.hostPath + + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + volumeMount := container.volumeMounts[_] + volumeMount.name == volume.name + isRWMount(volumeMount) + + + msga := { + "alertMessage": sprintf("%v: %v has: %v as hostPath volume", [wl.kind, wl.metadata.name, volume.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +isRWMount(mount) { + not mount.readOnly +} +isRWMount(mount) { + mount.readOnly == false +} \ No newline at end of file diff --git a/rules/alert-rw-hostpath/rule.metadata.json b/rules/alert-rw-hostpath/rule.metadata.json new file mode 100644 index 000000000..3208e6a85 --- /dev/null +++ b/rules/alert-rw-hostpath/rule.metadata.json @@ -0,0 +1,38 @@ +{ + "name": "alert-rw-hostpath", + "attributes": { + "m$K8sThreatMatrix": "Persistance::Writable hostPath mount, Lateral Movement::Writable volume mounts on the host", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob", + "Pod" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + }, + { + "packageName": "kubernetes.api.client" + } + ], + "description": "determines if any workload contains a hostPath volume with rw permissions", + "remediation": "Set the readOnly field of the mount to true", + "ruleQuery": "" +} \ No newline at end of file diff --git a/rules/anonymous-requests/raw.rego b/rules/anonymous-requests/raw.rego new file mode 100644 index 000000000..8a3fe528b --- /dev/null +++ b/rules/anonymous-requests/raw.rego @@ -0,0 +1,16 @@ +package armo_builtins +import data.kubernetes.api.client as client + + deny[msga] { + pods_resource := client.query_all_no_auth("pods") + pod := pods_resource.status + pod == "200 OK" + output :="Anonymous requests are allowed" + msga := { + "alertMessage": sprintf("%v", [output]), + "alertScore": 2, + "packagename": "armo_builtins", + "alertObject": { + }, + } + } \ No newline at end of file diff --git a/rules/anonymous-requests/rule.metadata.json b/rules/anonymous-requests/rule.metadata.json new file mode 100644 index 000000000..41bef84ee --- /dev/null +++ b/rules/anonymous-requests/rule.metadata.json @@ -0,0 +1,30 @@ +{ + "name": "anonymous-requests", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + }, + { + "packageName": "kubernetes.api.client" + } + ], + "description": "Determines if anonymosu requests are allowed.", + "remediation": "Disable anonymous requests by setting the anonymous-auth flag to false.", + "ruleQuery": "" + } \ No newline at end of file diff --git a/rules/automount-service-account/raw.rego b/rules/automount-service-account/raw.rego new file mode 100644 index 000000000..aa1b476b5 --- /dev/null +++ b/rules/automount-service-account/raw.rego @@ -0,0 +1,21 @@ +package armo_builtins + +# Fails if user account mount tokens in pod by default. +deny [msga]{ + serviceaccounts := [serviceaccount | serviceaccount= input[_]; serviceaccount.kind == "ServiceAccount"] + serviceaccount := serviceaccounts[_] + isAutoMount(serviceaccount) + + msga := { + "alertMessage": sprintf("the following service account: %v in the following namespace: %v mounts service account tokens in pods by default", [serviceaccount.metadata.name, serviceaccount.metadata.namespace]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [serviceaccount] + } + } +} + +isAutoMount(serviceaccount) { + not serviceaccount.automountServiceAccountToken == false +} \ No newline at end of file diff --git a/rules/automount-service-account/rule.metadata.json b/rules/automount-service-account/rule.metadata.json new file mode 100644 index 000000000..061ba3533 --- /dev/null +++ b/rules/automount-service-account/rule.metadata.json @@ -0,0 +1,25 @@ +{ + "name": "automount-service-account", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Serviceaccount" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if service account automountServiceAccountToken enabled", + "remediation": "Make sure that the automountServiceAccountToken field on the service account spec if set to false", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/configured-liveness-probe/raw.rego b/rules/configured-liveness-probe/raw.rego new file mode 100644 index 000000000..0774e0c03 --- /dev/null +++ b/rules/configured-liveness-probe/raw.rego @@ -0,0 +1,51 @@ +package armo_builtins + + +# Fails if pod doas not have container with livenessProbe +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + not container.livenessProbe + msga := { + "alertMessage": sprintf("Container: %v does not have livenessProbe", [ container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if workload doas not have container with livenessProbe +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + not container.livenessProbe + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not have livenessProbe", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Fails if cronjob doas not have container with livenessProbe +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + not container.livenessProbe + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not have livenessProbe", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} diff --git a/rules/configured-liveness-probe/rule.metadata.json b/rules/configured-liveness-probe/rule.metadata.json new file mode 100644 index 000000000..90d518aa2 --- /dev/null +++ b/rules/configured-liveness-probe/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "configured-liveness-probe", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "Liveness probe is not configured", + "remediation": "Ensure Liveness probe is configured", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/configured-readiness-probe/raw.rego b/rules/configured-readiness-probe/raw.rego new file mode 100644 index 000000000..38956ec22 --- /dev/null +++ b/rules/configured-readiness-probe/raw.rego @@ -0,0 +1,51 @@ +package armo_builtins + + +# Fails if pod doas not have container with readinessProbe +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + not container.readinessProbe + msga := { + "alertMessage": sprintf("Container: %v does not have readinessProbe", [ container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if workload doas not have container with readinessProbe +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + not container.readinessProbe + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not have readinessProbe", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Fails if cronjob doas not have container with readinessProbe +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + not container.readinessProbe + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not have readinessProbe", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} diff --git a/rules/configured-readiness-probe/rule.metadata.json b/rules/configured-readiness-probe/rule.metadata.json new file mode 100644 index 000000000..51c02cc2c --- /dev/null +++ b/rules/configured-readiness-probe/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "configured-readiness-probe", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "Readiness probe is not configured", + "remediation": "Ensure Readiness probe is configured", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/container-hostPort/raw.rego b/rules/container-hostPort/raw.rego new file mode 100644 index 000000000..fa8b1cad7 --- /dev/null +++ b/rules/container-hostPort/raw.rego @@ -0,0 +1,58 @@ +package armo_builtins + + +# Fails if pod has container with hostPort +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isHostPort(container) + msga := { + "alertMessage": sprintf("Container: %v has Host-port", [ container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if workload has container with hostPort +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isHostPort(container) + msga := { + "alertMessage": sprintf("Container: %v in %v: %v has Host-port", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Fails if cronjob has container with hostPort +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isHostPort(container) + msga := { + "alertMessage": sprintf("Container: %v in %v: %v has Host-port", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + + +isHostPort(container){ + ports := container.ports[_] + ports.hostPort +} \ No newline at end of file diff --git a/rules/container-hostPort/rule.metadata.json b/rules/container-hostPort/rule.metadata.json new file mode 100644 index 000000000..bcb1286b8 --- /dev/null +++ b/rules/container-hostPort/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "container-hostPort", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if container has hostPort", + "remediation": "Make sure you do not configure hostPort for the container, if necessary use NodePort / ClusterIP", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/container-image-repository/raw.rego b/rules/container-image-repository/raw.rego new file mode 100644 index 000000000..d5177c511 --- /dev/null +++ b/rules/container-image-repository/raw.rego @@ -0,0 +1,67 @@ +package armo_builtins +# import data.kubernetes.api.client as client + +allowlist(z) = x { + x := ["mcr.microsoft.com/", "gcr.io/", "azurecr.io/"] +} + +untrustedImageRepo[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + image := container.image + registry := allowlist(image)[_] + not contains(image, registry) + + not pod.spec["imagePullSecrets"] + + msga := { + "alertMessage": sprintf("image '%v' in container '%s' comes from untrusted registry", [image, container.name]), + "alertScore": 2, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +untrustedImageRepo[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + image := container.image + registry := allowlist(image)[_] + not contains(image, registry) + + not wl.spec.template.spec["imagePullSecrets"] + + msga := { + "alertMessage": sprintf("image '%v' in container '%s' comes from untrusted registry", [image, container.name]), + "alertScore": 2, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +untrustedImageRepo[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + image := container.image + registry := allowlist(image)[_] + not contains(image, registry) + + not wl.spec.jobTemplate.spec.template.spec["imagePullSecrets"] + + msga := { + "alertMessage": sprintf("image '%v' in container '%s' comes from untrusted registry", [image, container.name]), + "alertScore": 2, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} \ No newline at end of file diff --git a/rules/container-image-repository/rule.metadata.json b/rules/container-image-repository/rule.metadata.json new file mode 100644 index 000000000..dd4f02b49 --- /dev/null +++ b/rules/container-image-repository/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "container-image-repository", + "attributes": { + "m$K8sThreatMatrix": "Collection::Images from private registry", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Pods", + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob" + ] + } + ], + "ruleDependencies": [], + "description": "fails if image is not from allowlisted registry (microsoft, google, azure) and also has no imagePullSecrets", + "remediation": "", + "ruleQuery": "" + } \ No newline at end of file diff --git a/rules/dangerous-capabilities/raw.rego b/rules/dangerous-capabilities/raw.rego new file mode 100644 index 000000000..0a74cc0fc --- /dev/null +++ b/rules/dangerous-capabilities/raw.rego @@ -0,0 +1,54 @@ +package armo_builtins + + +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isDangerousCapabilities(container) + msga := { + "alertMessage": sprintf("container: %v in pod: %v have dangerous capabilities", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isDangerousCapabilities(container) + msga := { + "alertMessage": sprintf("container: %v in workload: %v have dangerous capabilities", [container.name, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + isDangerousCapabilities(container) + msga := { + "alertMessage": sprintf("container: %v in cronjob: %v have dangerous capabilities", [container.name, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +isDangerousCapabilities(container){ + dangerousCapabilities := ["ALL", "SYS_ADMIN", "NET_ADMIN"] + dangerousCapabilitie := dangerousCapabilities[_] + contains(container.securityContext.capabilities.add[_], dangerousCapabilitie) +} \ No newline at end of file diff --git a/rules/dangerous-capabilities/rule.metadata.json b/rules/dangerous-capabilities/rule.metadata.json new file mode 100644 index 000000000..aecc3e53e --- /dev/null +++ b/rules/dangerous-capabilities/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "dangerous-capabilities", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if container has dangrous capabilities", + "remediation": "Remove all dangerous capabilities which aren’t necessary for the container.", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/deny-RCE-vuln-image-pods/raw.rego b/rules/deny-RCE-vuln-image-pods/raw.rego new file mode 100644 index 000000000..20f7d4e57 --- /dev/null +++ b/rules/deny-RCE-vuln-image-pods/raw.rego @@ -0,0 +1,319 @@ +package armo_builtins +import data.cautils as cautils + +# ========= RCE : no service score 5 ================ +deny[msga] { + pod := input[_] + container := pod.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + t := { "containersScanID": scan.containersScanID,"count":count(vulnerabilities),"vulnerabilities":vulnerabilities} + + msga := { + "alertMessage": sprintf("image %v has %v RCE vulnerabilities", [container.image,count(vulnerabilities)]), + "alertScore": 5, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [pod] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +# workloads +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + t := { "containersScanID": scan.containersScanID,"count":count(vulnerabilities),"vulnerabilities":vulnerabilities} + + msga := { + "alertMessage": sprintf("image %v has %v RCE vulnerabilities", [container.image,count(vulnerabilities)]), + "alertScore": 5, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +# cronjobs +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + t := { "containersScanID": scan.containersScanID,"count":count(vulnerabilities),"vulnerabilities":vulnerabilities} + + msga := { + "alertMessage": sprintf("image %v has %v RCE vulnerabilities", [container.image,count(vulnerabilities)]), + "alertScore": 5, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +# ======== RCE + service (not nodeport and not loadbalancer) 7 ===================== +deny[msga] { + pod := input[_] + container := pod.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + t := { "containersScanID": scan.containersScanID,"count":count(vulnerabilities),"vulnerabilities":vulnerabilities} + + service := input[_] + service.kind == "Service" + service.metadata.namespace == pod.metadata.namespace + labels := pod.metadata.labels + filtered_labels := json.remove(labels, ["pod-template-hash"]) + np_or_lb := {"NodePort", "LoadBalancer"} + not np_or_lb[service.spec.type] + cautils.is_subobject(service.spec.selector, filtered_labels) + + msga := { + "alertMessage": sprintf("image %v has %v RCE vulnerabilities", [container.image,count(vulnerabilities)]), + "alertScore": 7, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [pod] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +# workloads +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + t := { "containersScanID": scan.containersScanID,"count":count(vulnerabilities),"vulnerabilities":vulnerabilities} + + service := input[_] + service.kind == "Service" + service.metadata.namespace == wl.metadata.namespace + labels := wl.spec.template.metadata.labels + np_or_lb := {"NodePort", "LoadBalancer"} + not np_or_lb[service.spec.type] + cautils.is_subobject(service.spec.selector,labels) + + msga := { + "alertMessage": sprintf("image %v has %v RCE vulnerabilities", [container.image,count(vulnerabilities)]), + "alertScore": 7, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + + } +} +# cronjobs +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + t := { "containersScanID": scan.containersScanID,"count":count(vulnerabilities),"vulnerabilities":vulnerabilities} + + service := input[_] + service.kind == "Service" + service.metadata.namespace == wl.metadata.namespace + labels := wl.spec.jobTemplate.spec.template.metadata.labels + np_or_lb := {"NodePort", "LoadBalancer"} + not np_or_lb[service.spec.type] + cautils.is_subobject(service.spec.selector,labels) + + msga := { + "alertMessage": sprintf("image %v has %v RCE vulnerabilities", [container.image,count(vulnerabilities)]), + "alertScore": 7, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +# ======= RCE + service nodeport/loadbalancer 10 =========================== +deny[msga] { + pod := input[_] + container := pod.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + t := { "containersScanID": scan.containersScanID,"count":count(vulnerabilities),"vulnerabilities":vulnerabilities} + + service := input[_] + service.kind == "Service" + service.metadata.namespace == pod.metadata.namespace + labels := pod.metadata.labels + filtered_labels := json.remove(labels, ["pod-template-hash"]) + np_or_lb := {"NodePort", "LoadBalancer"} + np_or_lb[service.spec.type] + cautils.is_subobject(service.spec.selector, filtered_labels) + + msga := { + "alertMessage": sprintf("image %v has %v RCE vulnerabilities", [container.image,count(vulnerabilities)]), + "alertScore": 10, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [pod] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +# workloads +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + t := { "containersScanID": scan.containersScanID,"count":count(vulnerabilities),"vulnerabilities":vulnerabilities} + + service := input[_] + service.kind == "Service" + service.metadata.namespace == wl.metadata.namespace + labels := wl.spec.template.metadata.labels + np_or_lb := {"NodePort", "LoadBalancer"} + np_or_lb[service.spec.type] + cautils.is_subobject(service.spec.selector,labels) + + msga := { + "alertMessage": sprintf("image %v has %v RCE vulnerabilities", [container.image,count(vulnerabilities)]), + "alertScore": 10, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +# cronjobs +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + t := { "containersScanID": scan.containersScanID,"count":count(vulnerabilities),"vulnerabilities":vulnerabilities} + + service := input[_] + service.kind == "Service" + service.metadata.namespace == wl.metadata.namespace + labels := wl.spec.jobTemplate.spec.template.metadata.labels + np_or_lb := {"NodePort", "LoadBalancer"} + np_or_lb[service.spec.type] + cautils.is_subobject(service.spec.selector,labels) + + msga := { + "alertMessage": sprintf("image %v has %v RCE vulnerabilities", [container.image,count(vulnerabilities)]), + "alertScore": 10, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +#treat as potentially critical +is_unsafe_image(scanresult) { + scanresult.numOfUnknownSeverity > 0 +} +is_unsafe_image(scanresult) { + scanresult.numOfNegligibleSeverity > 0 +} + +is_unsafe_image(scanresult) { + scanresult.numOfLowSeverity > 0 +} + +is_unsafe_image(scanresult) { + scanresult.numOfMeduiumSeverity > 0 +} + +is_unsafe_image(scanresult) { + scanresult.numOfHighSeverity > 0 +} + +is_unsafe_image(scanresult) { + scanresult.numOfCriticalSeverity > 0 +} \ No newline at end of file diff --git a/rules/deny-RCE-vuln-image-pods/rule.metadata.json b/rules/deny-RCE-vuln-image-pods/rule.metadata.json new file mode 100644 index 000000000..0fef10331 --- /dev/null +++ b/rules/deny-RCE-vuln-image-pods/rule.metadata.json @@ -0,0 +1,37 @@ +{ + "name": "deny-RCE-vuln-image-pods", + "attributes": { + "m$K8sThreatMatrix": "Execution::Application Exploit (RCE)", + "armoBuiltin": true, + "armoOpa": "true" + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob", + "Pod", + "services" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines if pods has vulnerable image with remote code execution", + "remediation": "", + "ruleQuery": "package armo_builtins" +} \ No newline at end of file diff --git a/rules/deny-vuln-image-pods/raw.rego b/rules/deny-vuln-image-pods/raw.rego new file mode 100644 index 000000000..90db4ece6 --- /dev/null +++ b/rules/deny-vuln-image-pods/raw.rego @@ -0,0 +1,131 @@ +package armo_builtins +import data.cautils as cautils + +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + + labels := pod.metadata.labels + filtered_labels := json.remove(labels, ["pod-template-hash"]) + service := input[_] + service.kind == "Service" + service.metadata.namespace == pod.metadata.namespace + np_or_lb := {"NodePort", "LoadBalancer"} + np_or_lb[service.spec.type] + cautils.is_subobject(service.spec.selector,filtered_labels) + + msga := { + "alertMessage": sprintf("pod %v/%v has vulnerabilities", [pod.metadata.namespace,pod.metadata.name]), + "alertScore": 2, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [pod] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +# covers most workloads +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + + labels := wl.spec.template.metadata.labels + service := input[_] + service.kind == "Service" + service.metadata.namespace == wl.metadata.namespace + np_or_lb := {"NodePort", "LoadBalancer"} + np_or_lb[service.spec.type] + cautils.is_subobject(service.spec.selector,labels) + + msga := { + "alertMessage": sprintf("%v: %v/%v has vulnerabilities", [wl.kind, wl.metadata.namespace, wl.metadata.name]), + "alertScore": 2, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + +# covers cronjobs +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + + is_unsafe_image(scan) + scan.containersScanID + vulnerabilities := armo.get_image_scan_details({"containersScanID":scan.containersScanID, "fieldCreteria":{"description":"RCE|like,Remote Code Execution|like,remote code execution|like,remote command execution|like,Remote Command Execution|like,arbitrary code|like,code execution|like,Arbitrary Code|like,Code Execution|like,code injection|like,Code Injection|like,execute code|like,Execute Code|like,arbitrary command|like,Arbitrary Command|like,arbitrary commands|like,Arbitrary Commands|like,command injection|like,Command Injection|like,command execution|like,Command Execution|like,inject arbitrary commands|like,Inject Arbitrary Commands|like"} }) + count(vulnerabilities) > 0 + + labels := wl.spec.jobTemplate.spec.template.metadata.labels + service := input[_] + service.kind == "Service" + service.metadata.namespace == wl.metadata.namespace + np_or_lb := {"NodePort", "LoadBalancer"} + np_or_lb[service.spec.type] + cautils.is_subobject(service.spec.selector,labels) + + + msga := { + "alertMessage": sprintf("%v: %v/%v has vulnerabilities", [wl.kind, wl.metadata.namespace, wl.metadata.name]), + "alertScore": 2, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "vulnerabilities" : [vulnerabilities] + } + } +} + + +#treat as potentially critical +is_unsafe_image(scanresult) { + scanresult.numOfUnknownSeverity > 0 +} +is_unsafe_image(scanresult) { + scanresult.numOfNegligibleSeverity > 0 +} + +is_unsafe_image(scanresult) { + scanresult.numOfLowSeverity > 0 +} + +is_unsafe_image(scanresult) { + scanresult.numOfMeduiumSeverity > 0 +} + +is_unsafe_image(scanresult) { + scanresult.numOfHighSeverity > 0 +} + +is_unsafe_image(scanresult) { + scanresult.numOfCriticalSeverity > 0 +} diff --git a/rules/deny-vuln-image-pods/rule.metadata.json b/rules/deny-vuln-image-pods/rule.metadata.json new file mode 100644 index 000000000..2885b486c --- /dev/null +++ b/rules/deny-vuln-image-pods/rule.metadata.json @@ -0,0 +1,39 @@ +{ + "name": "deny-vuln-image-pods", + "attributes": { + "m$K8sThreatMatrix": "Initial Access::Application Vulnerability", + "mitre": "Exploit Public-Facing Application", + "mitreCode": "T1190", + "armoBuiltin": true, + "armoOpa": "true" + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob", + "Pod", + "services" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines if pods/deployments has vulnerable image", + "remediation": "Isolate such deployments in sandboxes if possible. Otherwise, keep scanning frequently for in case a patch will be available - MAKE SURE it has least privileges as necessary!", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/exec-into-container/raw.rego b/rules/exec-into-container/raw.rego new file mode 100644 index 000000000..3b98761a7 --- /dev/null +++ b/rules/exec-into-container/raw.rego @@ -0,0 +1,110 @@ + +package armo_builtins +import data.cautils as cautils + + +# input: clusterrolebindings + rolebindings +# apiversion: rbac.authorization.k8s.io/v1 +# returns subjects that can exec into container + +deny[msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canExecToPodResource(rule) + canExecToPodVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v, can exec into containers", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role, rolebinding] + } + + } +} + + +# input: clusterrolebindings + rolebindings +# apiversion: rbac.authorization.k8s.io/v1 +# returns subjects that can exec into container + +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canExecToPodResource(rule) + canExecToPodVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v, can exec into containers", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role, rolebinding] + } + } +} + +# input: clusterrolebindings + rolebindings +# apiversion: rbac.authorization.k8s.io/v1 +# returns subjects that can exec into container + +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canExecToPodResource(rule) + canExecToPodVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v, can exec into containers", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role, rolebinding] + } + } +} + +canExecToPodVerb(rule) { + cautils.list_contains(rule.verbs, "create") +} +canExecToPodVerb(rule) { + cautils.list_contains(rule.verbs, "*") +} + +canExecToPodResource(rule) { + cautils.list_contains(rule.resources,"pods/exec") +} +canExecToPodResource(rule) { + cautils.list_contains(rule.resources,"pods/*") +} +canExecToPodResource(rule) { + cautils.list_contains(rule.resources,"*") +} diff --git a/rules/exec-into-container/rule.metadata.json b/rules/exec-into-container/rule.metadata.json new file mode 100644 index 000000000..88f5e8de6 --- /dev/null +++ b/rules/exec-into-container/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "exec-into-container", + "attributes": { + "m$K8sThreatMatrix": "Privilege Escalation::Exec into container", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users have permissions to exec into pods", + "remediation": "", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/exposed-sensitive-interfaces/raw.rego b/rules/exposed-sensitive-interfaces/raw.rego new file mode 100644 index 000000000..c136c51a3 --- /dev/null +++ b/rules/exposed-sensitive-interfaces/raw.rego @@ -0,0 +1,107 @@ +package armo_builtins +import data.kubernetes.api.client as client + +# loadbalancer +deny[msga] { + service := input[_] + service.kind == "Service" + service.spec.type == "LoadBalancer" + + wl := input[_] + workload_types = {"Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Job", "Pod", "CronJob"} + workload_types[wl.kind] + wl_connectedto_service(wl, service) + + # "Apache NiFi", Kubeflow, "Argo Workflows", "Weave Scope", "Kubernetes dashboard". + services_names := {"nifi-service", "argo-server", "minio", "postgres", "workflow-controller-metrics", + "weave-scope-app", "kubernetes-dashboard"} + services_names[service.metadata.name] + # externalIP := service.spec.externalIPs[_] + externalIP := service.status.loadBalancer.ingress[0].ip + + + msga := { + "alertMessage": sprintf("service: %v is exposed", [service.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl, service] + } + } +} + + +# nodePort +# get a pod connected to that service, get nodeIP (hostIP?) +# use ip + nodeport +deny[msga] { + service := input[_] + service.kind == "Service" + service.spec.type == "NodePort" + + # "Apache NiFi", Kubeflow, "Argo Workflows", "Weave Scope", "Kubernetes dashboard". + services_names := {"nifi-service", "argo-server", "minio", "postgres", "workflow-controller-metrics", + "weave-scope-app", "kubernetes-dashboard"} + services_names[service.metadata.name] + + pod := input[_] + pod.kind == "Pod" + + wl_connectedto_service(pod, service) + + + + msga := { + "alertMessage": sprintf("service: %v is exposed", [service.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod, service] + } + } +} + +# nodePort +# get a workload connected to that service, get nodeIP (hostIP?) +# use ip + nodeport +deny[msga] { + service := input[_] + service.kind == "Service" + service.spec.type == "NodePort" + + # "Apache NiFi", Kubeflow, "Argo Workflows", "Weave Scope", "Kubernetes dashboard". + services_names := {"nifi-service", "argo-server", "minio", "postgres", "workflow-controller-metrics", + "weave-scope-app", "kubernetes-dashboard"} + services_names[service.metadata.name] + + wl := input[_] + spec_template_spec_patterns := {"Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Job", "CronJob"} + spec_template_spec_patterns[wl.kind] + + wl_connectedto_service(wl, service) + + pods_resource := client.query_all("pods") + pod := pods_resource.body.items[_] + my_pods := [pod | startswith(pod.metadata.name, wl.metadata.name)] + + + + msga := { + "alertMessage": sprintf("service: %v is exposed", [service.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl, service] + } + } +} + +# ==================================================================================== + +wl_connectedto_service(wl, service){ + count({x | service.spec.selector[x] == wl.metadata.labels[x]}) == count(service.spec.selector) +} + +wl_connectedto_service(wl, service){ + wl.spec.selector.matchLabels == service.spec.selector +} diff --git a/rules/exposed-sensitive-interfaces/rule.metadata.json b/rules/exposed-sensitive-interfaces/rule.metadata.json new file mode 100644 index 000000000..d9621e652 --- /dev/null +++ b/rules/exposed-sensitive-interfaces/rule.metadata.json @@ -0,0 +1,36 @@ +{ + "name": "exposed-sensitive-interfaces", + "attributes": { + "microsoftK8sThreatMatrix": "Initial access::Exposed sensitive interfaces", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Service", + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "kubernetes.api.client" + } + ], + "description": "fails if known interfaces have exposed services", + "remediation": "", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/host-network-access/raw.rego b/rules/host-network-access/raw.rego new file mode 100644 index 000000000..9d692e35e --- /dev/null +++ b/rules/host-network-access/raw.rego @@ -0,0 +1,52 @@ +package armo_builtins + +# Fails if pod has hostNetwork enabled +deny[msga] { + pods := [ pod | pod = input[_] ; pod.kind == "Pod"] + pod := pods[_] + + isHostNetwork(pod.spec) + + msga := { + "alertMessage": sprintf("Pod: %v is connected to the host network", [pod.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if workload has hostNetwork enabled +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + isHostNetwork(wl.spec.template.spec) + msga := { + "alertMessage": sprintf("%v: %v has a pod connected to the host network", [wl.kind, wl.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Fails if cronjob has hostNetwork enabled +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + isHostNetwork(wl.spec.jobTemplate.spec.template.spec) + msga := { + "alertMessage": sprintf("CronJob: %v has a pod connected to the host network", [wl.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +isHostNetwork(podspec) { + podspec.hostNetwork == true +} \ No newline at end of file diff --git a/rules/host-network-access/rule.metadata.json b/rules/host-network-access/rule.metadata.json new file mode 100644 index 000000000..1df9d6963 --- /dev/null +++ b/rules/host-network-access/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "host-network-access", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if pod has hostNetwork enabled", + "remediation": "Make sure that the hostNetwork field of the pod spec is not set to true (set to false or not present)", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/host-pid-ipc-privileges/raw.rego b/rules/host-pid-ipc-privileges/raw.rego new file mode 100644 index 000000000..f8c6bc055 --- /dev/null +++ b/rules/host-pid-ipc-privileges/raw.rego @@ -0,0 +1,106 @@ +package armo_builtins + + +# Fails if pod has hostPID enabled +deny[msga] { + pod := input[_] + pod.kind == "Pod" + isHostPID(pod.spec) + msga := { + "alertMessage": sprintf("Pod: %v has hostPID enabled", [pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if pod has hostIPC enabled +deny[msga] { + pod := input[_] + pod.kind == "Pod" + isHostIPC(pod.spec) + msga := { + "alertMessage": sprintf("Pod: %v has hostIPC enabled", [pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + + +# Fails if workload has hostPID enabled +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + isHostPID(wl.spec.template.spec) + msga := { + "alertMessage": sprintf("%v: %v has a pod with hostPID enabled", [wl.kind, wl.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# Fails if workload has hostIPC enabled +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + isHostIPC(wl.spec.template.spec) + msga := { + "alertMessage": sprintf("%v: %v has a pod with hostIPC enabled", [wl.kind, wl.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Fails if cronjob has hostPID enabled +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + isHostPID(wl.spec.jobTemplate.spec.template.spec) + msga := { + "alertMessage": sprintf("CronJob: %v has a pod with hostPID enabled", [wl.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# Fails if cronjob has hostIPC enabled +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + isHostIPC(wl.spec.jobTemplate.spec.template.spec) + msga := { + "alertMessage": sprintf("CronJob: %v has a pod with hostIPC enabled", [wl.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Check that hostPID and hostIPC are set to false. Default is false. Only in pod spec + + +isHostPID(podspec){ + podspec.hostPID == true +} + +isHostIPC(podspec){ + podspec.hostIPC == true +} \ No newline at end of file diff --git a/rules/host-pid-ipc-privileges/rule.metadata.json b/rules/host-pid-ipc-privileges/rule.metadata.json new file mode 100644 index 000000000..5704c44b9 --- /dev/null +++ b/rules/host-pid-ipc-privileges/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "host-pid-ipc-privileges", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if pod has hostIPC/hostPID enabled", + "remediation": "Make sure that the fields hostIPC and hostPID in the pod spec are not set to true (set to false or not present)", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/image-pull-secrets/raw.rego b/rules/image-pull-secrets/raw.rego new file mode 100644 index 000000000..81e6d3314 --- /dev/null +++ b/rules/image-pull-secrets/raw.rego @@ -0,0 +1,19 @@ +package armo_builtins + +# input: service accounts +# apiversion: v1 +# returns ImagePullSecrets that more than one service account have access to + +deny[msga] { + + image = input[i].imagePullSecrets[_] == input[j].imagePullSecrets[_] + i > j + + msga := { + "alertMessage": sprintf("the following ImagePullSecret: %v, is exposed to more than one serviceaccount", [image]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + } + } +} \ No newline at end of file diff --git a/rules/image-pull-secrets/rule.metadata.json b/rules/image-pull-secrets/rule.metadata.json new file mode 100644 index 000000000..b5933953e --- /dev/null +++ b/rules/image-pull-secrets/rule.metadata.json @@ -0,0 +1,25 @@ +{ + "name": "image-pull-secrets", + "attributes": { + "m$K8sThreatMatrix": "Collection::Images from private registry", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "ServiceAccounts" + ] + } + ], + "ruleDependencies": [], + "description": "Checks if more than on service account have access to an ImagePullSecrets", + "remediation": "", + "ruleQuery": "armo_builtins" +} diff --git a/rules/immutable-container-filesystem/raw.rego b/rules/immutable-container-filesystem/raw.rego new file mode 100644 index 000000000..b2317e4cf --- /dev/null +++ b/rules/immutable-container-filesystem/raw.rego @@ -0,0 +1,57 @@ +package armo_builtins + + +# Fails if pods has container with mutable filesystem +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isMutableFilesystem(container) + msga := { + "alertMessage": sprintf("container: %v in pod: %v has mutable filesystem", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if workload has container with mutable filesystem +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isMutableFilesystem(container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v has mutable filesystem", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# Fails if cronjob has container with mutable filesystem +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isMutableFilesystem(container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v has mutable filesystem", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Default of readOnlyRootFilesystem is false. This field is only in container spec and not pod spec +isMutableFilesystem(container){ + not container.securityContext.readOnlyRootFilesystem + } \ No newline at end of file diff --git a/rules/immutable-container-filesystem/rule.metadata.json b/rules/immutable-container-filesystem/rule.metadata.json new file mode 100644 index 000000000..39f1a0552 --- /dev/null +++ b/rules/immutable-container-filesystem/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "immutable-container-filesystem", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if container has mutable filesystem", + "remediation": "Make sure that the securityContext.readOnlyRootFilesystem field in the container/pod spec is set to true", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/ingress-and-egress-blocked/raw.rego b/rules/ingress-and-egress-blocked/raw.rego new file mode 100644 index 000000000..711e4533b --- /dev/null +++ b/rules/ingress-and-egress-blocked/raw.rego @@ -0,0 +1,167 @@ +package armo_builtins + + +# For pods +deny[msga] { + pods := [pod | pod= input[_]; pod.kind == "Pod"] + networkpolicies := [networkpolicie | networkpolicie= input[_]; networkpolicie.kind == "NetworkPolicy"] + pod := pods[_] + networkpoliciesConnectedToPod := [networkpolicie | networkpolicie= networkpolicies[_]; podConnectedToNetworkPolicy(pod, networkpolicie)] + count(networkpoliciesConnectedToPod) > 0 + goodPolicies := [goodpolicie | goodpolicie= networkpoliciesConnectedToPod[_]; isIngerssEgressPolicy(goodpolicie)] + count(goodPolicies) < 1 + + msga := { + "alertMessage": sprintf("Pod: %v does not have ingress/egress defined", [pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } + +} + +# For pods +deny[msga] { + pods := [pod | pod= input[_]; pod.kind == "Pod"] + networkpolicies := [networkpolicie | networkpolicie= input[_]; networkpolicie.kind == "NetworkPolicy"] + pod := pods[_] + networkpoliciesConnectedToPod := [networkpolicie | networkpolicie= networkpolicies[_]; podConnectedToNetworkPolicy(pod, networkpolicie)] + count(networkpoliciesConnectedToPod) < 1 + + msga := { + "alertMessage": sprintf("Pod: %v does not have ingress/egress defined", [pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } + +} + +# For workloads +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + networkpolicies := [networkpolicie | networkpolicie= input[_]; networkpolicie.kind == "NetworkPolicy"] + networkpoliciesConnectedToPod := [networkpolicie | networkpolicie= networkpolicies[_]; wlConnectedToNetworkPolicy(wl, networkpolicie)] + count(networkpoliciesConnectedToPod) > 0 + goodPolicies := [goodpolicie | goodpolicie= networkpoliciesConnectedToPod[_]; isIngerssEgressPolicy(goodpolicie)] + count(goodPolicies) < 1 + + msga := { + "alertMessage": sprintf("%v: %v has Pods which don't have ingress/egress defined", [wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# For workloads +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + networkpolicies := [networkpolicie | networkpolicie= input[_]; networkpolicie.kind == "NetworkPolicy"] + networkpoliciesConnectedToPod := [networkpolicie | networkpolicie= networkpolicies[_]; wlConnectedToNetworkPolicy(wl, networkpolicie)] + count(networkpoliciesConnectedToPod) < 1 + + msga := { + "alertMessage": sprintf("%v: %v has Pods which don't have ingress/egress defined", [wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# For Cronjobs +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + networkpolicies := [networkpolicie | networkpolicie= input[_]; networkpolicie.kind == "NetworkPolicy"] + networkpoliciesConnectedToPod := [networkpolicie | networkpolicie= networkpolicies[_]; cronjobConnectedToNetworkPolicy(wl, networkpolicie)] + count(networkpoliciesConnectedToPod) > 0 + goodPolicies := [goodpolicie | goodpolicie= networkpoliciesConnectedToPod[_]; isIngerssEgressPolicy(goodpolicie)] + count(goodPolicies) < 1 + + msga := { + "alertMessage": sprintf("%v: %v has Pods which don't have ingress/egress defined", [wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# For Cronjobs +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + networkpolicies := [networkpolicie | networkpolicie= input[_]; networkpolicie.kind == "NetworkPolicy"] + networkpoliciesConnectedToPod := [networkpolicie | networkpolicie= networkpolicies[_]; cronjobConnectedToNetworkPolicy(wl, networkpolicie)] + count(networkpoliciesConnectedToPod) < 1 + + msga := { + "alertMessage": sprintf("%v: %v has Pods which don't have ingress/egress defined", [wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +podConnectedToNetworkPolicy(pod, networkpolicie){ + networkpolicie.metadata.namespace == pod.metadata.namespace + count(networkpolicie.spec.podSelector) > 0 + count({x | networkpolicie.spec.podSelector.matchLabels[x] == pod.metadata.labels[x]}) == count(networkpolicie.spec.podSelector.matchLabels) +} + +podConnectedToNetworkPolicy(pod, networkpolicie){ + networkpolicie.metadata.namespace == pod.metadata.namespace + count(networkpolicie.spec.podSelector) == 0 +} + +wlConnectedToNetworkPolicy(wl, networkpolicie){ + wl.metadata.namespace == networkpolicie.metadata.namespace + count(networkpolicie.spec.podSelector) == 0 +} + + +wlConnectedToNetworkPolicy(wl, networkpolicie){ + wl.metadata.namespace == wl.metadata.namespace + count(networkpolicie.spec.podSelector) > 0 + count({x | networkpolicie.spec.podSelector.matchLabels[x] == wl.spec.template.metadata.labels[x]}) == count(networkpolicie.spec.podSelector.matchLabels) +} + + +cronjobConnectedToNetworkPolicy(cj, networkpolicie){ + cj.metadata.namespace == networkpolicie.metadata.namespace + count(networkpolicie.spec.podSelector) == 0 +} + +cronjobConnectedToNetworkPolicy(cj, networkpolicie){ + cj.metadata.namespace == networkpolicie.metadata.namespace + count(networkpolicie.spec.podSelector) > 0 + count({x | networkpolicie.spec.podSelector.matchLabels[x] == cj.spec.jobTemplate.spec.template.metadata.labels[x]}) == count(networkpolicie.spec.podSelector.matchLabels) +} + +isIngerssEgressPolicy(networkpolicie) { + list_contains(networkpolicie.spec.policyTypes, "Ingress") + list_contains(networkpolicie.spec.policyTypes, "Egress") + } + +list_contains(list, element) { + some i + list[i] == element +} \ No newline at end of file diff --git a/rules/ingress-and-egress-blocked/rule.metadata.json b/rules/ingress-and-egress-blocked/rule.metadata.json new file mode 100644 index 000000000..9096f950f --- /dev/null +++ b/rules/ingress-and-egress-blocked/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "ingress-and-egress-blocked", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob", + "NetworkPolicy" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if there are no ingress and egress defined for pod", + "remediation": "Make sure you define ingress and egress policies for all your Pods", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/insecure-capabilities/raw.rego b/rules/insecure-capabilities/raw.rego new file mode 100644 index 000000000..80c5d53d9 --- /dev/null +++ b/rules/insecure-capabilities/raw.rego @@ -0,0 +1,54 @@ +package armo_builtins + + +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isDangerousCapabilities(container) + msga := { + "alertMessage": sprintf("container: %v in pod: %v have dangerous capabilities", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isDangerousCapabilities(container) + msga := { + "alertMessage": sprintf("container: %v in workload: %v have dangerous capabilities", [container.name, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + isDangerousCapabilities(container) + msga := { + "alertMessage": sprintf("container: %v in cronjob: %v have dangerous capabilities", [container.name, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +isDangerousCapabilities(container){ + insecureCapabilities := ["CHOWN", "DAC_OVERRIDE", "FSETID", "FOWNER", "MKNOD", "NET_RAW", "SETGID", "SETUID", "SETFCAP", "NET_BIND_SERVICE","SYS_CHROOT","KILL","AUDIT_WRITE"] + insecureCapabilitie := insecureCapabilities[_] + contains(container.securityContext.capabilities.add[_], insecureCapabilitie) +} \ No newline at end of file diff --git a/rules/insecure-capabilities/rule.metadata.json b/rules/insecure-capabilities/rule.metadata.json new file mode 100644 index 000000000..a06b9b471 --- /dev/null +++ b/rules/insecure-capabilities/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "insecure-capabilities", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if container has insecure capabilities", + "remediation": "Remove all insecure capabilities which aren’t necessary for the container.", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/insecure-port-flag/raw.rego b/rules/insecure-port-flag/raw.rego new file mode 100644 index 000000000..d80c9849b --- /dev/null +++ b/rules/insecure-port-flag/raw.rego @@ -0,0 +1,41 @@ +package armo_builtins +import data.cautils as cautils + +# Fails if pod has insecure-port flag enabled +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isInsecurePortFlag(container) + msga := { + "alertMessage": sprintf("The API server container: %v has insecure-port flag enabled", [ container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if workload has insecure-port flag enabled +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isInsecurePortFlag(container) + msga := { + "alertMessage": sprintf("The API server container: %v has insecure-port flag enabled", [ container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + + +isInsecurePortFlag(container){ + cautils.list_contains(container.command, "--insecure-port=1") +} \ No newline at end of file diff --git a/rules/insecure-port-flag/rule.metadata.json b/rules/insecure-port-flag/rule.metadata.json new file mode 100644 index 000000000..ac6831d56 --- /dev/null +++ b/rules/insecure-port-flag/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "insecure-port-flag", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if the api server has insecure-port enabled", + "remediation": "Make sure that the insecure-port flag of the api server is set to 0", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/instance-metadata-api-access/raw.rego b/rules/instance-metadata-api-access/raw.rego new file mode 100644 index 000000000..7cad98c39 --- /dev/null +++ b/rules/instance-metadata-api-access/raw.rego @@ -0,0 +1,80 @@ +package armo_builtins +# Check for images from blacklisted repos + +metadata_azure(z) = http.send({ + "url": "http://169.254.169.254/metadata/instance?api-version=2020-09-01", + "method": "get", + "headers": {"Metadata": "true"}, + "raise_error": true, +}) + +metadata_gcp(z) = http.send({ + "url": "http://169.254.169.254/computeMetadata/v1/?alt=json&recursive=true", + "method": "get", + "headers": {"Metadata-Flavor": "Google"}, + "raise_error": true, +}) + +metadata_aws(z) = metadata_object { + hostname := http.send({ + "url": "http://169.254.169.254/latest/meta-data/local-hostname", + "method": "get", + "raise_error": true, + }) + metadata_object := { + "raw_body": hostname.raw_body, + "hostname" : hostname.raw_body, + "status_code" : hostname.status_code + } +} + +azure_metadata[msga] { + metadata_object := metadata_azure("aaa") + metadata_object.status_code == 200 + node_name := metadata_object.body.compute.name + msga := { + "alertMessage": sprintf("Node '%s' has access to Instance Metadata Services of Azure.", [node_name]), + "alert": true, + "prevent": false, + "alertScore": 1, + "alertObject": { + "externalObjects": { + "azureMetadata" : [metadata_object.body] + } + } + } +} + +gcp_metadata[msga] { + metadata_object := metadata_gcp("aaa") + metadata_object.status_code == 200 + node_name := metadata_object.body.instance.hostname + msga := { + "alertMessage": sprintf("Node '%s' has access to Instance Metadata Services of GCP.", [node_name]), + "alert": true, + "prevent": false, + "alertScore": 1, + "alertObject": { + "externalObjects": { + "gcpMetadata" : [metadata_object.raw_body] + } + } + } +} + +aws_metadata[msga] { + metadata_object := metadata_aws("aaa") + metadata_object.status_code == 200 + node_name := metadata_object.hostname + msga := { + "alertMessage": sprintf("Node '%s' has access to Instance Metadata Services of AWS.", [node_name]), + "alert": true, + "prevent": false, + "alertScore": 1, + "alertObject": { + "externalObjects": { + "awsMetadata" : [metadata_object.raw_body] + } + } + } +} \ No newline at end of file diff --git a/rules/instance-metadata-api-access/rule.metadata.json b/rules/instance-metadata-api-access/rule.metadata.json new file mode 100644 index 000000000..d9ab6e1d2 --- /dev/null +++ b/rules/instance-metadata-api-access/rule.metadata.json @@ -0,0 +1,25 @@ +{ + "name": "instance-metadata-api-access", + "attributes": { + "m$K8sThreatMatrix": "Credential Access::Instance Metadata API", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "nodes" + ] + } + ], + "ruleDependencies": [], + "description": "Checks if there is access from the nodes to cloud prividers instance metadata services", + "remediation": "", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/internal-networking/raw.rego b/rules/internal-networking/raw.rego new file mode 100644 index 000000000..bb4ba80e6 --- /dev/null +++ b/rules/internal-networking/raw.rego @@ -0,0 +1,26 @@ +package armo_builtins + +# input: network policies +# apiversion: networking.k8s.io/v1 +# fails if no network policies are defined in a certain namespace + +deny[msga] { + namespaces := [namespace | namespace = input[_]; namespace.kind == "Namespace"] + namespace := namespaces[_] + policy_names := [policy.metadata.namespace | policy = input[_]; policy.kind == "NetworkPolicy"] + not list_contains(policy_names, namespace.metadata.name) + + msga := { + "alertMessage": sprintf("no policy is defined for namespace %v", [namespace.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [namespace] + } + } +} + +list_contains(list, element) { + some i + list[i] == element +} \ No newline at end of file diff --git a/rules/internal-networking/rule.metadata.json b/rules/internal-networking/rule.metadata.json new file mode 100644 index 000000000..75177a1c1 --- /dev/null +++ b/rules/internal-networking/rule.metadata.json @@ -0,0 +1,26 @@ +{ + "name": "internal-networking", + "attributes": { + "m$K8sThreatMatrix": "Lateral Movement::Container internal networking, Discovery::Network mapping", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "networkpolicies", + "namespaces" + ] + } + ], + "ruleDependencies": [], + "description": "lists namespaces in which no network policies are defined", + "remediation": "", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/linux-hardening/raw.rego b/rules/linux-hardening/raw.rego new file mode 100644 index 000000000..1c96984b9 --- /dev/null +++ b/rules/linux-hardening/raw.rego @@ -0,0 +1,85 @@ +package armo_builtins + + +# Fails if pod does not define linux security hardening +deny[msga] { + pod := input[_] + pod.kind == "Pod" + isUnsafePod(pod) + container := pod.spec.containers[_] + isUnsafeContainer(container) + + msga := { + "alertMessage": sprintf("Pod: %v does not define any linux security hardening", [pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if workload does not define linux security hardening +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + isUnsafeWorkload(wl) + container := wl.spec.template.spec.containers[_] + isUnsafeContainer(container) + msga := { + "alertMessage": sprintf("Workload: %v does not define any linux security hardening", [wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# Fails if pod does not define linux security hardening +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + isUnsafeCronJob(wl) + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isUnsafeContainer(container) + + msga := { + "alertMessage": sprintf("Cronjob: %v does not define any linux security hardening", [wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +isUnsafePod(pod){ + not pod.spec.securityContext.seccompProfile + not pod.spec.securityContext.seLinuxOptions + annotations := [pod.metadata.annotations[i] | annotaion = i; startswith(i, "container.apparmor.security.beta.kubernetes.io")] + not count(annotations) > 0 +} + +isUnsafeContainer(container){ + not container.securityContext.seccompProfile + not container.securityContext.seLinuxOptions + not container.securityContext.capabilities.drop +} + +isUnsafeWorkload(wl) { + not wl.spec.template.spec.securityContext.seccompProfile + not wl.spec.template.spec.securityContext.seLinuxOptions + annotations := [wl.spec.template.metadata.annotations[i] | annotaion = i; startswith(i, "container.apparmor.security.beta.kubernetes.io")] + not count(annotations) > 0 +} + +isUnsafeCronJob(cronjob) { + not cronjob.spec.jobTemplate.spec.template.spec.securityContext.seccompProfile + not cronjob.spec.jobTemplate.spec.template.spec.securityContext.seLinuxOptions + annotations := [cronjob.spec.jobTemplate.spec.template.metadata.annotations[i] | annotaion = i; startswith(i, "container.apparmor.security.beta.kubernetes.io")] + not count(annotations) > 0 +} + diff --git a/rules/linux-hardening/rule.metadata.json b/rules/linux-hardening/rule.metadata.json new file mode 100644 index 000000000..d430a3775 --- /dev/null +++ b/rules/linux-hardening/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "linux-hardening", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if container does not defien any linux security hardening", + "remediation": "Make sure you define at least one linux security hardening property out of Seccomp, SELinux or Capabilities.", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/list-all-mutating-webhooks/raw.rego b/rules/list-all-mutating-webhooks/raw.rego new file mode 100644 index 000000000..621f39eec --- /dev/null +++ b/rules/list-all-mutating-webhooks/raw.rego @@ -0,0 +1,17 @@ +package armo_builtins +import data.cautils as cautils + + +deny [msga] { + mutatingwebhooks := [mutatingwebhook | mutatingwebhook = input[_]; mutatingwebhook.kind == "MutatingWebhookConfiguration"] + mutatingwebhook := mutatingwebhooks[_] + + msga := { + "alertMessage": sprintf("the following mutating webhook configuration should be checked %v.", [mutatingwebhook]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [mutatingwebhook] + } + } +} \ No newline at end of file diff --git a/rules/list-all-mutating-webhooks/rule.metada.json b/rules/list-all-mutating-webhooks/rule.metada.json new file mode 100644 index 000000000..36de88a7e --- /dev/null +++ b/rules/list-all-mutating-webhooks/rule.metada.json @@ -0,0 +1,25 @@ +{ + "name": "list-all-mutating-webhooks", + "attributes": { + "m$K8sThreatMatrix": "Persistence::Malicious admission controller", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "MutatingWebhookConfiguration" + ] + } + ], + "ruleDependencies": [], + "description": "Returns mutating webhook configurations to be verified", + "remediation": "Analyze webhook for malicious behavior", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/list-all-validating-webhooks/raw.rego b/rules/list-all-validating-webhooks/raw.rego new file mode 100644 index 000000000..a3f51404e --- /dev/null +++ b/rules/list-all-validating-webhooks/raw.rego @@ -0,0 +1,19 @@ +package armo_builtins +import data.cautils as cautils + + +deny [msga] { + admissionwebhooks := [admissionwebhook | admissionwebhook = input[_]; admissionwebhook.kind == "ValidatingWebhookConfiguration"] + admissionwebhook := admissionwebhooks[_] + + msga := { + "alertMessage": sprintf("the following validating webhook configuration should be checked %v.", [admissionwebhook]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [admissionwebhook] + } + } + + +} \ No newline at end of file diff --git a/rules/list-all-validating-webhooks/rule.metadata.json b/rules/list-all-validating-webhooks/rule.metadata.json new file mode 100644 index 000000000..0e57a80cc --- /dev/null +++ b/rules/list-all-validating-webhooks/rule.metadata.json @@ -0,0 +1,25 @@ +{ + "name": "list-all-validating-webhooks", + "attributes": { + "m$K8sThreatMatrix": "Credential Access::Malicious admission controller", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "ValidatingWebhookConfiguration" + ] + } + ], + "ruleDependencies": [], + "description": "Returns validating webhook configurations to be verified", + "remediation": "Analyze webhook for malicious behavior", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/more-than-one-replicas/raw.rego b/rules/more-than-one-replicas/raw.rego new file mode 100644 index 000000000..94437f0f8 --- /dev/null +++ b/rules/more-than-one-replicas/raw.rego @@ -0,0 +1,28 @@ +package armo_builtins + + +# Fails if workload doas not have replicas more than one +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","StatefulSet"} + spec_template_spec_patterns[wl.kind] + spec := wl.spec + replicasOneOrLess(spec) + msga := { + "alertMessage": sprintf("Workload: %v: %v doas not have replicas more than one", [ wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +replicasOneOrLess(spec){ + not spec.replicas +} + +replicasOneOrLess(spec){ + spec.replicas == 1 +} diff --git a/rules/more-than-one-replicas/rule.metadata.json b/rules/more-than-one-replicas/rule.metadata.json new file mode 100644 index 000000000..05ee76114 --- /dev/null +++ b/rules/more-than-one-replicas/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "more-than-one-replicas", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "Replicas are set to one.", + "remediation": "Ensure replicas field is set and value is bigger than one.", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/non-root-containers/raw.rego b/rules/non-root-containers/raw.rego new file mode 100644 index 000000000..2ae7e5d56 --- /dev/null +++ b/rules/non-root-containers/raw.rego @@ -0,0 +1,139 @@ +package armo_builtins + + +# Fails if pod has container configured to run as root +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isRootContainer(container) + msga := { + "alertMessage": sprintf("container: %v in pod: %v may run as root", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if pod has container configured to run as root +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isRootPod(pod, container) + msga := { + "alertMessage": sprintf("container: %v in pod: %v may run as root", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + + + +# Fails if workload has container configured to run as root +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isRootContainer(container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v may run as root", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Fails if workload has container configured to run as root +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isRootPod(wl.spec.template, container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v may run as root", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# Fails if cronjob has a container configured to run as root +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isRootContainer(container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v may run as root", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + + +# Fails if workload has container configured to run as root +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isRootPod(wl.spec.jobTemplate.spec.template, container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v may run as root", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +isRootPod(pod, container) { + not container.securityContext.runAsUser + pod.spec.securityContext.runAsUser == 0 +} + +isRootPod(pod, container) { + not container.securityContext.runAsUser + not container.securityContext.runAsGroup + not container.securityContext.runAsNonRoot + not pod.spec.securityContext.runAsUser + not pod.spec.securityContext.runAsGroup + pod.spec.securityContext.runAsNonRoot == false +} + +isRootPod(pod, container) { + not container.securityContext.runAsGroup + pod.spec.securityContext.runAsGroup == 0 +} + +isRootPod(pod, container) { + not pod.spec.securityContext.runAsGroup + not pod.spec.securityContext.runAsUser + container.securityContext.runAsNonRoot == false +} + +isRootContainer(container) { + container.securityContext.runAsUser == 0 +} + +isRootContainer(container) { + container.securityContext.runAsGroup == 0 +} \ No newline at end of file diff --git a/rules/non-root-containers/rule.metadata.json b/rules/non-root-containers/rule.metadata.json new file mode 100644 index 000000000..0f94d2f8d --- /dev/null +++ b/rules/non-root-containers/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "non-root-containers", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if container can run as root", + "remediation": "Make sure that the user/group in the securityContext of pod/container is set to an id less than 1000, or the runAsNonRoot flag is set to true. Also make sure that the allowPrivilegeEscalation field is set to false", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/pod-specific-version-tag/raw.rego b/rules/pod-specific-version-tag/raw.rego new file mode 100644 index 000000000..5fa8af182 --- /dev/null +++ b/rules/pod-specific-version-tag/raw.rego @@ -0,0 +1,54 @@ +package armo_builtins + + +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isLatestImageTag(container) + msga := { + "alertMessage": sprintf("Container: %v in pod: %v has latest image tag.", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isLatestImageTag(container) + msga := { + "alertMessage": sprintf("Container: %v in %v: %v has latest image tag.", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isLatestImageTag(container) + msga := { + "alertMessage": sprintf("Container: %v in %v: %v has latest image tag.", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +isLatestImageTag(container) { + endswith(container.image, ":latest") +} \ No newline at end of file diff --git a/rules/pod-specific-version-tag/rule.metadata.json b/rules/pod-specific-version-tag/rule.metadata.json new file mode 100644 index 000000000..840e6c263 --- /dev/null +++ b/rules/pod-specific-version-tag/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "pod-specific-version-tag", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob", + "Pod" + ] + } + ], + "ruleDependencies": [ + ], + "description": "Fails if container has image tag set to latest", + "remediation": "Make sure you define a specific image tag for container and not 'latest'.", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/resource-policies/raw.rego b/rules/resource-policies/raw.rego new file mode 100644 index 000000000..f267be132 --- /dev/null +++ b/rules/resource-policies/raw.rego @@ -0,0 +1,111 @@ +package armo_builtins + + +# Check if container has limits +deny[msga] { + pods := [pod | pod = input[_]; pod.kind == "Pod"] + pod := pods[_] + container := pod.spec.containers[_] + not container.resources.limits + + msga := { + "alertMessage": sprintf("there are no resource limits defined for container : %v", [container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + + +# Check if container has limits - for workloads +# If there is no limits specified in the workload, we check the namespace, since if limits are only specified for namespace +# and not in workload, it won't be on the yaml +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + not container.resources.limits + isNamespaceWithLimits(wl.metadata.namespace) + + msga := { + "alertMessage": sprintf("there are no resource limits defined for container : %v", [container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } + +} + +# Check if container has limits - for cronjobs +# If there is no limits specified in the cronjob, we check the namespace, since if limits are only specified for namespace +# and not in cronjob, it won't be on the yaml +deny [msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + not container.resources.limits + isNamespaceWithLimits(wl.metadata.namespace) + msga := { + "alertMessage": sprintf("there are no resource limits defined for container : %v", [container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# Fails if LimitRange exists but it does not define maximum usage of resources +deny[msga] { + + limitRanges := [limitRange | limitRange = input[_]; limitRange.kind == "LimitRange"] + limitRange := limitRanges[_] + + limits := limitRange.spec.limits[_] + not limits.max + + msga := { + "alertMessage": sprintf("the following LimitRange: %v does not define a maximum field for resources", [limitRange.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [limitRange] + } + } +} + +# Fails if ResourQuota exists but it does not define maximum usage of resources +deny[msga] { + resourceQuotas := [resourceQuota | resourceQuota = input[_]; resourceQuota.kind == "ResourceQuota"] + resourceQuota := resourceQuotas[_] + + not resourceQuota.spec.hard + + msga := { + "alertMessage": sprintf("the following ResourQuota: %v does not define a hard field", [resourceQuota.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [resourceQuota] + } + } +} + + +list_contains(list, element) { + some i + list[i] == element +} + + +# Check only LimitRange. For ResourceQuota limits need to be specified. +isNamespaceWithLimits(namespace) { + limitRanges := [policy.metadata.namespace | policy = input[_]; policy.kind == "LimitRange"] + not list_contains(limitRanges, namespace) +} diff --git a/rules/resource-policies/rule.metadata.json b/rules/resource-policies/rule.metadata.json new file mode 100644 index 000000000..51245b72e --- /dev/null +++ b/rules/resource-policies/rule.metadata.json @@ -0,0 +1,34 @@ +{ + "name": "resource-policies", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob", + "Pod", + "LimitRange", + "ResourceQuota" + + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if namespace has no resource policies defined", + "remediation": "Make sure that you definy resource policies (LimitRange or ResourceQuota) which limit the usage of resources for all the namespaces", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/resources-cpu-limit/raw.rego b/rules/resources-cpu-limit/raw.rego new file mode 100644 index 000000000..e1a465d6c --- /dev/null +++ b/rules/resources-cpu-limit/raw.rego @@ -0,0 +1,51 @@ +package armo_builtins + + +# Fails if pod doas not have container with CPU-limit +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + not container.resources.limits.cpu + msga := { + "alertMessage": sprintf("Container: %v does not have CPU-limit", [ container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if workload doas not have container with CPU-limit +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + not container.resources.limits.cpu + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not have CPU-limit", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Fails if cronjob doas not have container with CPU-limit +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + not container.resources.limits.cpu + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not have CPU-limit", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} diff --git a/rules/resources-cpu-limit/rule.metadata.json b/rules/resources-cpu-limit/rule.metadata.json new file mode 100644 index 000000000..5f39cccce --- /dev/null +++ b/rules/resources-cpu-limit/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "resources-cpu-limit", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "CPU limits are not set.", + "remediation": "Ensure CPU limits are set.", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/resources-memory-limit/raw.rego b/rules/resources-memory-limit/raw.rego new file mode 100644 index 000000000..fa2ed2b95 --- /dev/null +++ b/rules/resources-memory-limit/raw.rego @@ -0,0 +1,51 @@ +package armo_builtins + + +# Fails if pod doas not have container with memory-limit +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + not container.resources.limits.memory + msga := { + "alertMessage": sprintf("Container: %v does not have memory-limit", [ container.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if workload doas not have container with memory-limit +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + not container.resources.limits.memory + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not have memory-limit", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Fails if cronjob doas not have container with memory-limit +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + not container.resources.limits.memory + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not have memory-limit", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} diff --git a/rules/resources-memory-limit/rule.metadata.json b/rules/resources-memory-limit/rule.metadata.json new file mode 100644 index 000000000..b095297de --- /dev/null +++ b/rules/resources-memory-limit/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "resources-memory-limit", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "memory limits are not set.", + "remediation": "Ensure memory limits are set.", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/rule-access-dashboard/raw.rego b/rules/rule-access-dashboard/raw.rego new file mode 100644 index 000000000..86d8a3c09 --- /dev/null +++ b/rules/rule-access-dashboard/raw.rego @@ -0,0 +1,105 @@ +package armo_builtins + +# input: roleBinding +# apiversion: v1 +# fails if a subject that is not dashboard service account is bound to dashboard role + +deny[msga] { + roleBinding := input[_] + roleBinding.kind == "RoleBinding" + roleBinding.roleRef.name == "kubernetes-dashboard" + subject := roleBinding.subjects[_] + subject.name != "kubernetes-dashboard" + subject.kind != "ServiceAccount" + + msga := { + "alertMessage": sprintf("the following subjects: %s are bound to dashboard role/clusterrole", [subject.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [roleBinding] + } + } +} + +# input: clusterRoleBinding +# apiversion: v1 +# fails if a subject that is not dashboard service account is bound to dashboard role + +deny[msga] { + roleBinding := input[_] + roleBinding.kind == "ClusterRoleBinding" + roleBinding.roleRef.name == "kubernetes-dashboard" + subject := roleBinding.subjects[_] + subject.name != "kubernetes-dashboard" + subject.kind != "ServiceAccount" + + msga := { + "alertMessage": sprintf("the following subjects: %s are bound to dashboard role/clusterrole", [subject.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [roleBinding] + } + } +} + +# input: +# apiversion: +# fails if pod that is not dashboard is associated to dashboard service account + +deny[msga] { + pod := input[_] + pod.spec.serviceaccountname == "kubernetes-dashboard" + not startswith(pod.metadata.name, "kubernetes-dashboard") + + msga := { + "alertMessage": sprintf("the following pods: %s are associated with dashboard service account", [pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# input: +# apiversion: +# fails if workload that is not dashboard is associated to dashboard service account + +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + wl.spec.template.spec.serviceaccountname == "kubernetes-dashboard" + not startswith(wl.metadata.name, "kubernetes-dashboard") + + msga := { + "alertMessage": sprintf("%v: %v is associated with dashboard service account", [wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# input: +# apiversion: +# fails if CronJob that is not dashboard is associated to dashboard service account + +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + wl.spec.jobTemplate.spec.template.spec.serviceaccountname == "kubernetes-dashboard" + not startswith(wl.metadata.name, "kubernetes-dashboard") + + msga := { + "alertMessage": sprintf("the following cronjob: %s is associated with dashboard service account", [wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} \ No newline at end of file diff --git a/rules/rule-access-dashboard/rule.metadata.json b/rules/rule-access-dashboard/rule.metadata.json new file mode 100644 index 000000000..88128f14e --- /dev/null +++ b/rules/rule-access-dashboard/rule.metadata.json @@ -0,0 +1,26 @@ +{ + "name": "rule-access-dashboard", + "attributes": { + "m$K8sThreatMatrix": "Lateral Movement::Access Kubernetes dashboard, Discovery::Access Kubernetes dashboard", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "RoleBinding", + "ClusterRoleBinding" + ] + } + ], + "ruleDependencies": [], + "description": "fails if subject that is not dashboard service account is bound to dashboard role/clusterrole, or- if anyone that is not dashboard pod is associated with its service account.", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-access-kubelet-API/raw.rego b/rules/rule-access-kubelet-API/raw.rego new file mode 100644 index 000000000..07f56ca0f --- /dev/null +++ b/rules/rule-access-kubelet-API/raw.rego @@ -0,0 +1,19 @@ +package armo_builtins + +# input: network policies +# apiversion: networking.k8s.io/v1 +# fails if no network policies are defined + +deny[msga] { + networkpolicies := input + count(networkpolicies) == 0 + + msga := { + "alertMessage": "no network policy is defined", + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + + } + } +} \ No newline at end of file diff --git a/rules/rule-access-kubelet-API/rule.metadata.json b/rules/rule-access-kubelet-API/rule.metadata.json new file mode 100644 index 000000000..d25b2a31f --- /dev/null +++ b/rules/rule-access-kubelet-API/rule.metadata.json @@ -0,0 +1,25 @@ +{ + "name": "rule-access-kubelet-API", + "attributes": { + "m$K8sThreatMatrix": "Discovery::Access Kubelet API", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "networkpolicies" + ] + } + ], + "ruleDependencies": [], + "description": "fails if no network policy exists", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-allow-privilegege-escalation/raw.rego b/rules/rule-allow-privilegege-escalation/raw.rego new file mode 100644 index 000000000..65237947f --- /dev/null +++ b/rules/rule-allow-privilegege-escalation/raw.rego @@ -0,0 +1,58 @@ +package armo_builtins + + +# Fails if pod has container that allow privilege escalation +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isAllowPrivilegeEscalationContainer(container) + msga := { + "alertMessage": sprintf("container: %v in pod: %v allow privilege escalation", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + + +# Fails if workload has a container that allow privilege escalation +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isAllowPrivilegeEscalationContainer(container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v allow privilege escalation", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# Fails if cronjob has a container that allow privilege escalation +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isAllowPrivilegeEscalationContainer(container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v allow privilege escalation", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +isAllowPrivilegeEscalationContainer(container) { + container.securityContext.allowPrivilegeEscalation == true +} \ No newline at end of file diff --git a/rules/rule-allow-privilegege-escalation/rule.metadata.json b/rules/rule-allow-privilegege-escalation/rule.metadata.json new file mode 100644 index 000000000..12b7704b0 --- /dev/null +++ b/rules/rule-allow-privilegege-escalation/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "rule-allow-privilege-escalation", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if container allows privilege escalation", + "remediation": "Make sure that the allowPrivilegeEscalation field in the securityContext of pod/container is set to false", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/rule-bash-cmd-inside-container/raw.rego b/rules/rule-bash-cmd-inside-container/raw.rego new file mode 100644 index 000000000..4201f5593 --- /dev/null +++ b/rules/rule-bash-cmd-inside-container/raw.rego @@ -0,0 +1,102 @@ +package armo_builtins +import data.cautils as cautils + + +# Fails if container has bash/cmd inside it +# Pods +deny [msga] { + pod := input[_] + container := pod.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + isBashContainer(scan) + + + msga := { + "alertMessage": sprintf("the following container: %v has bash/cmd inside it.", [container.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [pod] + }, + "externalObjects": { + "container" : [{container.name}] + } + } +} + + +# Workloads +deny [msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + isBashContainer(scan) + + + msga := { + "alertMessage": sprintf("the following container: %v has bash/cmd inside it.", [container.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "container" : [{container.name}] + } + } +} + +# Cronjobs +deny [msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + res := armo.get_image_scan_summary({"type":"imageTag","value":container.image,"size":1}) + scan := res[_] + isBashContainer(scan) + + msga := { + "alertMessage": sprintf("the following container: %v has bash/cmd inside it.", [container.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + }, + "externalObjects": { + "container" : [{container.name}] + } + } +} + + +isBashContainer(scan) { + cautils.list_contains(scan.listOfDangerousArtifcats, "bin/bash") +} +isBashContainer(scan) { + cautils.list_contains(scan.listOfDangerousArtifcats, "sbin/sh") +} +isBashContainer(scan) { + cautils.list_contains(scan.listOfDangerousArtifcats, "bin/ksh") +} +isBashContainer(scan) { + cautils.list_contains(scan.listOfDangerousArtifcats, "bin/tcsh") +} +isBashContainer(scan) { + cautils.list_contains(scan.listOfDangerousArtifcats, "bin/zsh") +} +isBashContainer(scan) { + cautils.list_contains(scan.listOfDangerousArtifcats, "usr/bin/scsh") +} +isBashContainer(scan) { + cautils.list_contains(scan.listOfDangerousArtifcats, "bin/csh") +} +isBashContainer(scan) { + cautils.list_contains(scan.listOfDangerousArtifcats, "bin/busybox") +} +isBashContainer(scan) { + cautils.list_contains(scan.listOfDangerousArtifcats, "usr/bin/busybox") +} diff --git a/rules/rule-bash-cmd-inside-container/rule.metadata.json b/rules/rule-bash-cmd-inside-container/rule.metadata.json new file mode 100644 index 000000000..64a5a095b --- /dev/null +++ b/rules/rule-bash-cmd-inside-container/rule.metadata.json @@ -0,0 +1,36 @@ +{ + "name": "rule-can-bash-cmd-inside-container", + "attributes": { + "microsoftK8sThreatMatrix": "Execution::Bash/cmd inside container", + "armoBuiltin": true, + "armoOpa": "true" + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob", + "Pod" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which containers have bash/cmd inside it", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-can-create-bind-escalate-role/raw.rego b/rules/rule-can-create-bind-escalate-role/raw.rego new file mode 100644 index 000000000..291c48820 --- /dev/null +++ b/rules/rule-can-create-bind-escalate-role/raw.rego @@ -0,0 +1,321 @@ + +package armo_builtins +import data.cautils as cautils + +# ================= create/update =============================== + +# fails if user has access to create/update rolebindings/clusterrolebindings +# RoleBinding to Role +deny[msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreateUpdateToRoleResource(rule) + canCreateUpdateToRoleVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + + msga := { + "alertMessage": sprintf("the following users: %v , can create/update rolebinding/clusterrolebinding", [rolebinding.subjects]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user has access to create/update rolebindings/clusterrolebindings +# RoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreateUpdateToRoleResource(rule) + canCreateUpdateToRoleVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + msga := { + "alertMessage": sprintf("the following users: %v , can create/update rolebinding/clusterrolebinding", [rolebinding.subjects]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user has access to create/update rolebindings/clusterrolebindings +# ClusterRoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canCreateUpdateToRoleResource(rule) + canCreateUpdateToRoleVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + msga := { + "alertMessage": sprintf("the following users: %v , can create/update rolebinding/clusterrolebinding", [clusterrolebinding.subjects]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } +} + +# ================= bind =============================== + +# fails if user has access to bind clusterroles/roles +# RoleBinding to Role +deny [msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + + canBindToRoleResource(rule) + canBindToRoleVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + + msga := { + "alertMessage": sprintf("the following users: %v , can bind roles/clusterroles", [rolebinding.subjects]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + + +# fails if user has access to bind clusterroles/roles +# RoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + + canBindToRoleResource(rule) + canBindToRoleVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + msga := { + "alertMessage": sprintf("the following users: %v , can bind roles/clusterroles", [rolebinding.subjects]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + + +# fails if user has access to bind clusterroles/roles +# ClusterRoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canBindToRoleResource(rule) + canBindToRoleVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + msga := { + "alertMessage": sprintf("the following users: %v , can bind roles/clusterroles", [clusterrolebinding.subjects]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } +} + + +# ================= escalate =============================== + + +# fails if user has access to escalate rolebindings/clusterrolebindings +# RoleBinding to Role +deny[msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canEscalateToRoleResource(rule) + canEscalateToRoleVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + + msga := { + "alertMessage": sprintf("the following users: %v , can escalate rolebinding/clusterrolebinding", [rolebinding.subjects]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user has access to escalate rolebindings/clusterrolebindings +# RoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canEscalateToRoleResource(rule) + canEscalateToRoleVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + msga := { + "alertMessage": sprintf("the following users: %v , can escalaterolebinding/clusterrolebinding", [rolebinding.subjects]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user has access to escalate rolebindings/clusterrolebindings +# ClusterRoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canEscalateToRoleResource(rule) + canEscalateToRoleVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + msga := { + "alertMessage": sprintf("the following users: %v , can escalate rolebinding/clusterrolebinding", [clusterrolebinding.subjects]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } +} + + +# ============== escalate ===================== + +canEscalateToRoleResource(rule){ + cautils.list_contains(rule.resources,"clusterroles") +} + +canEscalateToRoleResource(rule){ + cautils.list_contains(rule.resources,"roles") +} + +canEscalateToRoleResource(rule){ + cautils.list_contains(rule.resources,"*") +} + +canEscalateToRoleVerb(rule) { + cautils.list_contains(rule.verbs, "escalate") +} + +canEscalateToRoleVerb(rule) { + cautils.list_contains(rule.verbs, "*") +} + + +# ============== bind ===================== + +canBindToRoleResource(rule){ + cautils.list_contains(rule.resources,"clusterroles") +} + +canBindToRoleResource(rule){ + cautils.list_contains(rule.resources,"roles") +} + +canBindToRoleResource(rule){ + cautils.list_contains(rule.resources,"*") +} + + +canBindToRoleVerb(rule) { + cautils.list_contains(rule.verbs, "bind") +} + +canBindToRoleVerb(rule) { + cautils.list_contains(rule.verbs, "*") +} + +# ============== create/update ===================== + +canCreateUpdateToRoleResource(rule) { + cautils.list_contains(rule.resources,"rolebindings") +} + +canCreateUpdateToRoleResource(rule) { + cautils.list_contains(rule.resources,"clusterrolebindings") +} + +canCreateUpdateToRoleResource(rule) { + cautils.list_contains(rule.resources,"*") +} + + +canCreateUpdateToRoleVerb(rule) { + cautils.list_contains(rule.verbs, "create") +} + +canCreateUpdateToRoleVerb(rule) { + cautils.list_contains(rule.verbs, "update") +} + +canCreateUpdateToRoleVerb(rule) { + cautils.list_contains(rule.verbs, "patch") +} + +canCreateUpdateToRoleVerb(rule) { + cautils.list_contains(rule.verbs, "*") +} diff --git a/rules/rule-can-create-bind-escalate-role/rule.metadata.json b/rules/rule-can-create-bind-escalate-role/rule.metadata.json new file mode 100644 index 000000000..5187e3072 --- /dev/null +++ b/rules/rule-can-create-bind-escalate-role/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-can-create-bind-escalate-role", + "attributes": { + "microsoftK8sThreatMatrix": "Discovery::Access the K8s API server", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users can create/update rolebindings/clusterrolebindings or bind roles/clusterroles", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-can-create-modify-pod/raw.rego b/rules/rule-can-create-modify-pod/raw.rego new file mode 100644 index 000000000..339b608d6 --- /dev/null +++ b/rules/rule-can-create-modify-pod/raw.rego @@ -0,0 +1,132 @@ + +package armo_builtins +import data.cautils as cautils + + + +# fails if user has create/modify access to pods +# RoleBinding to Role +deny [msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreateModifyToPodResource(rule) + canCreateModifyToPodVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can create/modify workloads", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user has create/modify access to pods +# RoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreateModifyToPodResource(rule) + canCreateModifyToPodVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can create/modify workloads", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user has create/modify access to pods +# ClusterRoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canCreateModifyToPodResource(rule) + canCreateModifyToPodVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + subjects := clusterrolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can create/modify workloads", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } +} + + + + +canCreateModifyToPodResource(rule){ + cautils.list_contains(rule.resources,"pods") +} + +canCreateModifyToPodResource(rule){ + cautils.list_contains(rule.resources,"deployments") +} + +canCreateModifyToPodResource(rule){ + cautils.list_contains(rule.resources,"daemonsets") +} + +canCreateModifyToPodResource(rule){ + cautils.list_contains(rule.resources,"replicasets") +} +canCreateModifyToPodResource(rule){ + cautils.list_contains(rule.resources,"statefulsets") +} +canCreateModifyToPodResource(rule){ + cautils.list_contains(rule.resources,"jobs") +} +canCreateModifyToPodResource(rule){ + cautils.list_contains(rule.resources,"cronjobs") +} +canCreateModifyToPodResource(rule){ + cautils.list_contains(rule.resources,"*") +} + +canCreateModifyToPodVerb(rule) { + cautils.list_contains(rule.verbs, "create") +} + +canCreateModifyToPodVerb(rule) { + cautils.list_contains(rule.verbs, "patch") +} + +canCreateModifyToPodVerb(rule) { + cautils.list_contains(rule.verbs, "update") +} + +canCreateModifyToPodVerb(rule) { + cautils.list_contains(rule.verbs, "*") +} diff --git a/rules/rule-can-create-modify-pod/rule.metadata.json b/rules/rule-can-create-modify-pod/rule.metadata.json new file mode 100644 index 000000000..0a959463b --- /dev/null +++ b/rules/rule-can-create-modify-pod/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-can-create-modify-pod", + "attributes": { + "m$K8sThreatMatrix": "Execution::New container, Persistence::Backdoor container", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users have create/modify permissions on pods", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-can-create-pod-kube-system/raw.rego b/rules/rule-can-create-pod-kube-system/raw.rego new file mode 100644 index 000000000..9ac3ad7f7 --- /dev/null +++ b/rules/rule-can-create-pod-kube-system/raw.rego @@ -0,0 +1,115 @@ + +package armo_builtins +import data.cautils as cautils + +# fails if user has create access to pods within kube-system namespace +# RoleBinding to Role +deny[msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreateToPodNamespace(role) + canCreateToPodResource(rule) + canCreateToPodVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can create pods in kube-system", [subjects.kind, subjects.name]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + + + + +# fails if user has create access to pods within kube-system namespace +# RoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreateToPodNamespace(rolebinding) + canCreateToPodResource(rule) + canCreateToPodVerb(rule) + + + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can create pods in kube-system", [subjects.kind, subjects.name]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + + + + +# fails if user has create access to pods within kube-system namespace +# ClusterRoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canCreateToPodResource(rule) + canCreateToPodVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + subjects := clusterrolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can create pods in kube-system", [subjects.kind, subjects.name]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } +} + + +canCreateToPodResource(rule){ + cautils.list_contains(rule.resources,"pods") +} + +canCreateToPodResource(rule){ + cautils.list_contains(rule.resources,"*") +} + +canCreateToPodVerb(rule) { + cautils.list_contains(rule.verbs, "create") +} + + +canCreateToPodVerb(rule) { + cautils.list_contains(rule.verbs, "*") +} + +canCreateToPodNamespace(role) { + role.metadata.namespace == "kube-system" +} diff --git a/rules/rule-can-create-pod-kube-system/rule.metadata.json b/rules/rule-can-create-pod-kube-system/rule.metadata.json new file mode 100644 index 000000000..246a092dd --- /dev/null +++ b/rules/rule-can-create-pod-kube-system/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-can-create-pod-kube-system", + "attributes": { + "microsoftK8sThreatMatrix": "Discovery::Access the K8s API server", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users can create pods in kube-system namespace", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-can-delete-create-service/raw.rego b/rules/rule-can-delete-create-service/raw.rego new file mode 100644 index 000000000..b27d55ae7 --- /dev/null +++ b/rules/rule-can-delete-create-service/raw.rego @@ -0,0 +1,113 @@ + +package armo_builtins +import data.cautils as cautils + + + +# fails if user has create/delete access to services +# RoleBinding to Role +deny[msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreateDeleteToServiceResource(rule) + canCreateDeleteToServiceVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can create/delete services", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user has create/delete access to services +# RoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + + canCreateDeleteToServiceResource(rule) + canCreateDeleteToServiceVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can create/delete services", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } + +} + +# fails if user has create/delete access to services +# ClusterRoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + + canCreateDeleteToServiceResource(rule) + canCreateDeleteToServiceVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + subjects := clusterrolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can create/delete services", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } +} + + +canCreateDeleteToServiceResource(rule) { + cautils.list_contains(rule.resources, "services") +} + +canCreateDeleteToServiceResource(rule) { + cautils.list_contains(rule.resources, "*") +} + +canCreateDeleteToServiceVerb(rule) { + cautils.list_contains(rule.verbs, "create") +} + +canCreateDeleteToServiceVerb(rule) { + cautils.list_contains(rule.verbs, "delete") +} + +canCreateDeleteToServiceVerb(rule) { + cautils.list_contains(rule.verbs, "deletecollection") +} + +canCreateDeleteToServiceVerb(rule) { + cautils.list_contains(rule.verbs, "*") +} \ No newline at end of file diff --git a/rules/rule-can-delete-create-service/rule.metadata.json b/rules/rule-can-delete-create-service/rule.metadata.json new file mode 100644 index 000000000..9539bbccb --- /dev/null +++ b/rules/rule-can-delete-create-service/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-can-delete-create-service", + "attributes": { + "m$K8sThreatMatrix": "Discovery::Access the K8s API server", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users have create/delete permissions on services", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-can-delete-k8s-events/raw.rego b/rules/rule-can-delete-k8s-events/raw.rego new file mode 100644 index 000000000..8c099d8d4 --- /dev/null +++ b/rules/rule-can-delete-k8s-events/raw.rego @@ -0,0 +1,112 @@ +package armo_builtins +import data.cautils as cautils + +# fails if user can delete events +#RoleBinding to Role +deny [msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canDeleteEventsResource(rule) + canDeleteEventsVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can delete events", [subjects.kind, subjects.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } + +} + + +# fails if user can delete events +#RoleBinding to ClusterRole +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canDeleteEventsResource(rule) + canDeleteEventsVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can delete events", [subjects.kind, subjects.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + + +# fails if user can delete events +# ClusterRoleBinding to ClusterRole +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canDeleteEventsResource(rule) + canDeleteEventsVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + + subjects := clusterrolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can delete events", [subjects.kind, subjects.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } +} + + + + + +canDeleteEventsResource(rule) { + cautils.list_contains(rule.resources,"events") +} +canDeleteEventsResource(rule) { + cautils.list_contains(rule.resources,"*") +} + + +canDeleteEventsVerb(rule) { + cautils.list_contains(rule.verbs,"delete") +} + +canDeleteEventsVerb(rule) { + cautils.list_contains(rule.verbs,"deletecollection") +} + +canDeleteEventsVerb(rule) { + cautils.list_contains(rule.verbs,"*") +} \ No newline at end of file diff --git a/rules/rule-can-delete-k8s-events/rule.metadata.json b/rules/rule-can-delete-k8s-events/rule.metadata.json new file mode 100644 index 000000000..9db016671 --- /dev/null +++ b/rules/rule-can-delete-k8s-events/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-can-delete-k8s-events", + "attributes": { + "microsoftK8sThreatMatrix": "Defense Evasion::Delete K8S events", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users can delete events", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-can-delete-logs/raw.rego b/rules/rule-can-delete-logs/raw.rego new file mode 100644 index 000000000..05ab26a8b --- /dev/null +++ b/rules/rule-can-delete-logs/raw.rego @@ -0,0 +1,121 @@ +package armo_builtins +import data.cautils as cautils + + + +# fails if user can delete logs of pod +#RoleBinding to Role +deny [msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canDeleteLogs(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can delete logs", [subjects.kind, subjects.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } + +} + + +# fails if user can delete logs of pod +# RoleBinding to ClusterRole +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canDeleteLogs(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can delete logs", [subjects.kind, subjects.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user can delete logs of pod +# ClusterRoleBinding to ClusterRole +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canDeleteLogs(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + + subjects := clusterrolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can delete logs", [subjects.kind, subjects.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } +} + + + + +canDeleteLogs(rule) { + cautils.list_contains(rule.resources,"*") + cautils.list_contains(rule.verbs,"*") +} + +canDeleteLogs(rule) { + cautils.list_contains(rule.resources,"pods/log") + cautils.list_contains(rule.verbs,"delete") +} +canDeleteLogs(rule) { + cautils.list_contains(rule.resources,"pods/log") + cautils.list_contains(rule.verbs,"*") +} + +canDeleteLogs(rule) { + cautils.list_contains(rule.resources,"*") + cautils.list_contains(rule.verbs,"delete") +} + +canDeleteLogs(rule) { + cautils.list_contains(rule.resources,"pods/*") + cautils.list_contains(rule.verbs,"delete") +} +canDeleteLogs(rule) { + cautils.list_contains(rule.resources,"pods/*") + cautils.list_contains(rule.verbs,"*") +} + +canDeleteLogs(rule) { + cautils.list_contains(rule.resources,"*") + cautils.list_contains(rule.verbs,"deletecollection") +} diff --git a/rules/rule-can-delete-logs/rule.metadata.json b/rules/rule-can-delete-logs/rule.metadata.json new file mode 100644 index 000000000..5bbbf5128 --- /dev/null +++ b/rules/rule-can-delete-logs/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-can-delete-logs", + "attributes": { + "microsoftK8sThreatMatrix": "Defense Evasion::Clear container logs", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users can delete logs inside a container", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-can-impersonate-users-groups/raw.rego b/rules/rule-can-impersonate-users-groups/raw.rego new file mode 100644 index 000000000..d44a9658a --- /dev/null +++ b/rules/rule-can-impersonate-users-groups/raw.rego @@ -0,0 +1,109 @@ + +package armo_builtins +import data.cautils as cautils + +# fails if user has impersonate access to users/groups +# RoleBinding to Role +deny[msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canImpersonateToUsersGroupsResource(rule) + canImpersonateToUsersGroupsVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can impersonate users/groups", [subjects.kind, subjects.name]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user has impersonate access to users/groups +# RoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canImpersonateToUsersGroupsResource(rule) + canImpersonateToUsersGroupsVerb(rule) + + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can impersonate users/groups", [subjects.kind, subjects.name]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + + +# fails if user has impersonate access to users/groups +# ClusterRoleBinding to ClusterRole +deny [msga]{ + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canImpersonateToUsersGroupsResource(rule) + canImpersonateToUsersGroupsVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + subjects := clusterrolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can impersonate users/groups", [subjects.kind, subjects.name]), + "alertScore": 3, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } +} + + + +canImpersonateToUsersGroupsResource(rule) { + cautils.list_contains(rule.resources,"users") +} + +canImpersonateToUsersGroupsResource(rule) { + cautils.list_contains(rule.resources,"groups") +} + +canImpersonateToUsersGroupsResource(rule) { + cautils.list_contains(rule.resources,"*") +} + +canImpersonateToUsersGroupsVerb(rule) { + cautils.list_contains(rule.verbs, "impersonate") +} + +canImpersonateToUsersGroupsVerb(rule) { + cautils.list_contains(rule.verbs, "*") +} + + diff --git a/rules/rule-can-impersonate-users-groups/rule.metadata.json b/rules/rule-can-impersonate-users-groups/rule.metadata.json new file mode 100644 index 000000000..c4e3a0568 --- /dev/null +++ b/rules/rule-can-impersonate-users-groups/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-can-impersonate-users-groups", + "attributes": { + "microsoftK8sThreatMatrix": "Discovery::Access the K8s API server", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users can impersonate users/groups", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-can-list-get-secrets/raw.rego b/rules/rule-can-list-get-secrets/raw.rego new file mode 100644 index 000000000..f191645e7 --- /dev/null +++ b/rules/rule-can-list-get-secrets/raw.rego @@ -0,0 +1,118 @@ +package armo_builtins +import data.cautils as cautils + + +# fails if user can list/get secrets +#RoleBinding to Role +deny[msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canViewSecretsResource(rule) + canViewSecretsVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can read secrets", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } + + +} + + +# fails if user can list/get secrets +#RoleBinding to ClusterRole +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canViewSecretsResource(rule) + canViewSecretsVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can read secrets", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user can list/get secrets +# ClusterRoleBinding to ClusterRole +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canViewSecretsResource(rule) + canViewSecretsVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + subjects := clusterrolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can read secrets", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } + +} + + + + +canViewSecretsVerb(rule) { + cautils.list_contains(rule.verbs,"get") +} + +canViewSecretsVerb(rule) { + cautils.list_contains(rule.verbs,"list") +} + +canViewSecretsVerb(rule) { + cautils.list_contains(rule.verbs,"watch") +} + + +canViewSecretsVerb(rule) { + cautils.list_contains(rule.verbs,"*") +} + + +canViewSecretsResource(rule) { + cautils.list_contains(rule.resources,"secrets") +} + +canViewSecretsResource(rule) { + cautils.list_contains(rule.resources,"*") +} \ No newline at end of file diff --git a/rules/rule-can-list-get-secrets/rule.metadata.json b/rules/rule-can-list-get-secrets/rule.metadata.json new file mode 100644 index 000000000..94e601ae1 --- /dev/null +++ b/rules/rule-can-list-get-secrets/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-can-list-get-secrets", + "attributes": { + "microsoftK8sThreatMatrix": "Discovery::Access the K8s API server", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users can list/get secrets", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-can-ssh-to-pod/raw.rego b/rules/rule-can-ssh-to-pod/raw.rego new file mode 100644 index 000000000..f1614fa60 --- /dev/null +++ b/rules/rule-can-ssh-to-pod/raw.rego @@ -0,0 +1,97 @@ +package armo_builtins + +# input: pod +# apiversion: v1 +# does: returns the external facing services of that pod + +deny[msga] { + pod := input[_] + pod.kind == "Pod" + podns := pod.metadata.namespace + podname := pod.metadata.name + labels := pod.metadata.labels + filtered_labels := json.remove(labels, ["pod-template-hash"]) + + service := input[_] + service.kind == "Service" + service.metadata.namespace == podns + service.spec.selector == filtered_labels + + hasSSHPorts(service) + + msga := { + "alertMessage": sprintf("pod %v/%v exposed by SSH services: %v", [podns, podname, service]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod,service] + } + } +} + +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + labels := wl.spec.template.metadata.labels + + service := input[_] + service.kind == "Service" + service.metadata.namespace == wl.metadata.namespace + service.spec.selector == labels + + hasSSHPorts(service) + + msga := { + "alertMessage": sprintf("%v: %v is exposed by SSH services: %v", [wl.kind, wl.metadata.name, service]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl,service] + } + } +} + +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + labels := wl.spec.jobTemplate.spec.template.metadata.labels + + service := input[_] + service.kind == "Service" + service.metadata.namespace == wl.metadata.namespace + service.spec.selector == labels + + hasSSHPorts(service) + + msga := { + "alertMessage": sprintf("%v: %v is exposed by SSH services: %v", [wl.kind, wl.metadata.name, service]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl,service] + } + } +} + +hasSSHPorts(service) { + port := service.spec.ports[_] + port.port == 22 +} + + +hasSSHPorts(service) { + port := service.spec.ports[_] + port.port == 2222 +} + +hasSSHPorts(service) { + port := service.spec.ports[_] + port.targetPort == 22 +} + + +hasSSHPorts(service) { + port := service.spec.ports[_] + port.targetPort == 2222 +} diff --git a/rules/rule-can-ssh-to-pod/rule.metadata.json b/rules/rule-can-ssh-to-pod/rule.metadata.json new file mode 100644 index 000000000..035a632e0 --- /dev/null +++ b/rules/rule-can-ssh-to-pod/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-can-ssh-to-pod", + "attributes": { + "microsoftK8sThreatMatrix": "Execution::SSH server running inside container", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob", + "Pod", + "Service" + ] + } + ], + "ruleDependencies": [], + "description": "denies pods with SSH ports opened(22/222)", + "remediation": "", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/rule-can-update-configmap/raw.rego b/rules/rule-can-update-configmap/raw.rego new file mode 100644 index 000000000..013d0d109 --- /dev/null +++ b/rules/rule-can-update-configmap/raw.rego @@ -0,0 +1,141 @@ +package armo_builtins +import data.cautils as cautils + + +# Fails if user can modify all configmaps, or if he can modify the 'coredns' configmap (default for coredns) +#RoleBinding to Role +deny [msga] { + configmaps := [configmap | configmap = input[_]; configmap.kind == "ConfigMap"] + configmap := configmaps[_] + configmap.metadata.name == "coredns" + + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + + canModifyConfigMapResource(rule) + canModifyConfigMapVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + rolebinding.metadata.namespace == "kube-system" + + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can modify 'coredns' configmap", [subjects.kind, subjects.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + + +# Fails if user can modify all configmaps, or if he can modify the 'coredns' configmap (default for coredns) +# RoleBinding to ClusterRole +deny[msga] { + configmaps := [configmap | configmap = input[_]; configmap.kind == "ConfigMap"] + configmap := configmaps[_] + configmap.metadata.name == "coredns" + + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canModifyConfigMapResource(rule) + canModifyConfigMapVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + rolebinding.metadata.namespace == "kube-system" + + + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can modify 'coredns' configmap", [subjects.kind, subjects.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } + +} + + +# Fails if user can modify all configmaps, or if he can modify the 'coredns' configmap (default for coredns) +# ClusterRoleBinding to ClusterRole +deny[msga] { + configmaps := [configmap | configmap = input[_]; configmap.kind == "ConfigMap"] + configmap := configmaps[_] + configmap.metadata.name == "coredns" + + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canModifyConfigMapResource(rule) + canModifyConfigMapVerb(rule) + + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + + + subjects := clusterrolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can modify 'coredns' configmap", [subjects.kind, subjects.name]), + "alertScore": 6, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } + +} + + + + + + canModifyConfigMapResource(rule) { + not rule.resourceNames + cautils.list_contains(rule.resources,"configmaps") + } + + canModifyConfigMapResource(rule) { + not rule.resourceNames + cautils.list_contains(rule.resources,"*") + } + + canModifyConfigMapResource(rule) { + cautils.list_contains(rule.resources,"configmaps") + cautils.list_contains(rule.resourceNames,"coredns") + } + + canModifyConfigMapVerb(rule) { + cautils.list_contains(rule.verbs,"update") + } + + + canModifyConfigMapVerb(rule) { + cautils.list_contains(rule.verbs,"patch") + } + + canModifyConfigMapVerb(rule) { + cautils.list_contains(rule.verbs,"*") + } diff --git a/rules/rule-can-update-configmap/rule.metadata.json b/rules/rule-can-update-configmap/rule.metadata.json new file mode 100644 index 000000000..be865a367 --- /dev/null +++ b/rules/rule-can-update-configmap/rule.metadata.json @@ -0,0 +1,33 @@ +{ + "name": "rule-can-update-configmap", + "attributes": { + "microsoftK8sThreatMatrix": "Lateral Movement::CoreDNS poisoning", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding", + "ConfigMap" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users can update/patch the 'coredns' configmap", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-credentials-configmap/raw.rego b/rules/rule-credentials-configmap/raw.rego new file mode 100644 index 000000000..c72653f68 --- /dev/null +++ b/rules/rule-credentials-configmap/raw.rego @@ -0,0 +1,63 @@ +package armo_builtins +# import data.cautils as cautils +# import data.kubernetes.api.client as client + +# fails if config map has keys with suspicious name +deny[msga] { + configmap := input[_] + configmap.kind == "ConfigMap" + sensitive_key_names := {"aws_access_key_id", "aws_secret_access_key", "azure_batchai_storage_account", "azure_batchai_storage_key", + "azure_batch_account", "azure_batch_key", "pass", "username", "pwd", "cred", "token", "key", "cert"} + key_name := sensitive_key_names[_] + map_secret := configmap.data[map_key] + contains(lower(map_key), key_name) + msga := { + "alertMessage": sprintf("this configmap has sensitive information: %v", [configmap.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [configmap] + } + } +} + +# fails if config map has values with suspicious content - not base 64 +deny[msga] { + sensitive_values := {"BEGIN", "PRIVATE KEY", "eyJhbGciO"} + value := sensitive_values[_] + + configmap := input[_] + configmap.kind == "ConfigMap" + map_secret := configmap.data[map_key] + contains(map_secret, value) + + msga := { + "alertMessage": sprintf("this configmap has sensitive information: %v", [configmap.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [configmap] + } + } +} + +# fails if config map has values with suspicious content - base 64 +deny[msga] { + sensitive_values := {"BEGIN", "PRIVATE KEY", "eyJhbGciO"} + value := sensitive_values[_] + + configmap := input[_] + configmap.kind == "ConfigMap" + map_secret := configmap.data[map_key] + decoded_secret := base64.decode(map_secret) + contains(decoded_secret, value) + + msga := { + "alertMessage": sprintf("this configmap has sensitive information: %v", [configmap.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [configmap] + } + } +} \ No newline at end of file diff --git a/rules/rule-credentials-configmap/rule.metadata.json b/rules/rule-credentials-configmap/rule.metadata.json new file mode 100644 index 000000000..2228979b7 --- /dev/null +++ b/rules/rule-credentials-configmap/rule.metadata.json @@ -0,0 +1,25 @@ +{ + "name": "rule-credentials-configmap", + "attributes": { + "m$K8sThreatMatrix": "Credential access::Applications credentials in configuration files, Lateral Movement::Applications credentials in configuration files", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "ConfigMap" + ] + } + ], + "ruleDependencies": [], + "description": "fails if ConfigMaps have sensitive information in configuration", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-credentials-in-env-var/raw.rego b/rules/rule-credentials-in-env-var/raw.rego new file mode 100644 index 000000000..7bebd8f70 --- /dev/null +++ b/rules/rule-credentials-in-env-var/raw.rego @@ -0,0 +1,74 @@ +package armo_builtins +# import data.cautils as cautils +# import data.kubernetes.api.client as client + +deny[msga] { + pod := input[_] + pod.kind == "Pod" + sensitive_key_names := {"aws_access_key_id", "aws_secret_access_key", "azure_batchai_storage_account", "azure_batchai_storage_key", + "azure_batch_account", "azure_batch_key", "passwd","password", "username", "pwd", "cred", "token", "key", "cert"} + key_name := sensitive_key_names[_] + container := pod.spec.containers[_] + env := container.env[_] + contains(lower(env.name), key_name) + isNotReference(env) + msga := { + "alertMessage": sprintf("Pod: %v has sensitive information in environment variables", [pod.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + + sensitive_key_names := {"aws_access_key_id", "aws_secret_access_key", "azure_batchai_storage_account", "azure_batchai_storage_key", + "azure_batch_account", "azure_batch_key", "passwd","password", "username", "pwd", "cred", "token", "key", "cert"} + key_name := sensitive_key_names[_] + container := wl.spec.template.spec.containers[_] + env := container.env[_] + contains(lower(env.name), key_name) + isNotReference(env) + msga := { + "alertMessage": sprintf("%v: %v has sensitive information in environment variables", [wl.kind, wl.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + sensitive_key_names := {"aws_access_key_id", "aws_secret_access_key", "azure_batchai_storage_account", "azure_batchai_storage_key", + "azure_batch_account", "azure_batch_key", "passwd","password", "username", "pwd", "cred", "token", "key", "cert"} + key_name := sensitive_key_names[_] + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + env := container.env[_] + contains(lower(env.name), key_name) + isNotReference(env) + msga := { + "alertMessage": sprintf("Cronjob: %v has sensitive information in environment variables", [wl.metadata.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + + +isNotReference(env) +{ + not env.valueFrom.secretKeyRef + not env.valueFrom.configMapKeyRef +} + diff --git a/rules/rule-credentials-in-env-var/rule.metadata.json b/rules/rule-credentials-in-env-var/rule.metadata.json new file mode 100644 index 000000000..5bc26f7a3 --- /dev/null +++ b/rules/rule-credentials-in-env-var/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "rule-credentials-in-env-var", + "attributes": { + "m$K8sThreatMatrix": "Credential access::Applications credentials in configuration files, Lateral Movement::Applications credentials in configuration files", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [], + "description": "fails if Pods have sensitive information in configuration", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-deny-cronjobs/raw.rego b/rules/rule-deny-cronjobs/raw.rego new file mode 100644 index 000000000..98d46adc9 --- /dev/null +++ b/rules/rule-deny-cronjobs/raw.rego @@ -0,0 +1,18 @@ +package armo_builtins + +# alert cronjobs + +#handles cronjob +deny[msga] { + + wl := input[_] + wl.kind == "CronJob" + msga := { + "alertMessage": sprintf("the following cronjobs are defined: %v/%v", [wl.metadata.namespace,wl.metadata.name]), + "alertScore": 2, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [wl] + } + } +} diff --git a/rules/rule-deny-cronjobs/rule.metadata.json b/rules/rule-deny-cronjobs/rule.metadata.json new file mode 100644 index 000000000..0b8250ab2 --- /dev/null +++ b/rules/rule-deny-cronjobs/rule.metadata.json @@ -0,0 +1,25 @@ +{ + "name": "rule-deny-cronjobs", + "attributes": { + "m$K8sThreatMatrix": "Persistence::Kubernetes Cronjob", + "armoBuiltin": true + }, + "ruleLanguage": "rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "CronJob" + ] + } + ], + "ruleDependencies": [], + "description": "determines if it's cronjob", + "remediation": "", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/rule-excessive-delete-rights/raw.rego b/rules/rule-excessive-delete-rights/raw.rego new file mode 100644 index 000000000..7b562820d --- /dev/null +++ b/rules/rule-excessive-delete-rights/raw.rego @@ -0,0 +1,136 @@ +package armo_builtins +import data.cautils as cautils + + +# fails if user can can delete important resources +#RoleBinding to Role +deny[msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canDeleteResource(rule) + canDeleteVerb(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + + + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can delete important resources", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } + + +} + + +# fails if user can can delete important resources +#RoleBinding to ClusterRole +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canDeleteResource(rule) + canDeleteVerb(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can delete important resources", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# fails if user can can delete important resources +# ClusterRoleBinding to ClusterRole +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + clusterrolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + clusterrolebinding := clusterrolebindings[_] + + rule:= role.rules[_] + canDeleteResource(rule) + canDeleteVerb(rule) + + clusterrolebinding.roleRef.kind == "ClusterRole" + clusterrolebinding.roleRef.name == role.metadata.name + + + subjects := clusterrolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v can delete important resources", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,clusterrolebinding] + } + } + +} + + +canDeleteVerb(rule) { + cautils.list_contains(rule.verbs,"delete") +} + +canDeleteVerb(rule) { + cautils.list_contains(rule.verbs,"deletecollection") +} + +canDeleteVerb(rule) { + cautils.list_contains(rule.verbs,"*") +} + +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"secrets") +} +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"pods") +} +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"services") +} +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"deployments") +} +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"replicasets") +} +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"daemonsets") +} +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"statefulsets") +} +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"jobs") +} +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"cronjobs") +} +canDeleteResource(rule) { + cautils.list_contains(rule.resources,"*") +} \ No newline at end of file diff --git a/rules/rule-excessive-delete-rights/rule.metadata.json b/rules/rule-excessive-delete-rights/rule.metadata.json new file mode 100644 index 000000000..4f734adb5 --- /dev/null +++ b/rules/rule-excessive-delete-rights/rule.metadata.json @@ -0,0 +1,28 @@ +{ + "name": "rule-excessive-delete-rights", + "attributes": { + "m$K8sThreatMatrix": "Impact::Data Destruction", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [], + "description": "fails if user can delete important resources", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-exposed-dashboard/raw.rego b/rules/rule-exposed-dashboard/raw.rego new file mode 100644 index 000000000..4bc7ca207 --- /dev/null +++ b/rules/rule-exposed-dashboard/raw.rego @@ -0,0 +1,37 @@ +package armo_builtins + +# input: pods +# apiversion: v1 +# fails if dashboard exists and is exposed + +deny[msga] { + deployment := input[_] + startswith(deployment.metadata.name, "kubernetes-dashboard") + container := deployment.spec.template.spec.containers[_] + version := trim_prefix(container.image, "kubernetesui/dashboard:v") + to_number(replace(version, ".", "")) < 201 + + service := input[_] + service.kind == "Service" + isNodePortLbService(service) + count({x | service.spec.selector[x]; deployment.metadata.labels[x]}) == count(service.spec.selector) + + msga := { + "alertMessage": sprintf("dashboard exists and is exposed %s", [container.image]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [deployment] + } + } +} + + + +isNodePortLbService(service) { + service.spec.type == "NodePort" +} + +isNodePortLbService(service) { + service.spec.type == "LoadBalancer" +} \ No newline at end of file diff --git a/rules/rule-exposed-dashboard/rule.metadata.json b/rules/rule-exposed-dashboard/rule.metadata.json new file mode 100644 index 000000000..168c57489 --- /dev/null +++ b/rules/rule-exposed-dashboard/rule.metadata.json @@ -0,0 +1,26 @@ +{ + "name": "rule-exposed-dashboard", + "attributes": { + "m$K8sThreatMatrix": "Initial Access::Exposed Dashboard", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "Service" + ] + } + ], + "ruleDependencies": [], + "description": "fails if dashboard exists and is exposed", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-identify-blacklisted-image-registries/raw.rego b/rules/rule-identify-blacklisted-image-registries/raw.rego new file mode 100644 index 000000000..0a4ede609 --- /dev/null +++ b/rules/rule-identify-blacklisted-image-registries/raw.rego @@ -0,0 +1,128 @@ +package armo_builtins +# Check for images from blacklisted repos + +untrusted_registries(z) = x { + x := ["015253967648.dkr.ecr.eu-central-1.amazonaws.com/"] +} + +public_registries(z) = y{ + y := ["quay.io/kiali/","quay.io/datawire/","quay.io/keycloak/","quay.io/bitnami/"] +} + +untrustedImageRepo[msga] { + pod := input[_] + k := pod.kind + k == "Pod" + container := pod.spec.containers[_] + image := container.image + repo_prefix := untrusted_registries(image)[_] + startswith(image, repo_prefix) + containerName := container.name + + msga := { + "alertMessage": sprintf("image '%v' in container '%s' comes from untrusted registry", [image, containerName]), + "packagename": "armo_builtins", + "alertScore": 2, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +untrustedImageRepo[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + image := container.image + repo_prefix := untrusted_registries(image)[_] + startswith(image, repo_prefix) + containerName := container.name + + msga := { + "alertMessage": sprintf("image '%v' in container '%s' comes from untrusted registry", [image, containerName]), + "packagename": "armo_builtins", + "alertScore": 2, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +untrustedImageRepo[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + image := container.image + repo_prefix := untrusted_registries(image)[_] + startswith(image, repo_prefix) + containerName := container.name + + msga := { + "alertMessage": sprintf("image '%v' in container '%s' comes from untrusted registry", [image, containerName]), + "packagename": "armo_builtins", + "alertScore": 2, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +untrustedImageRepo[msga] { + pod := input[_] + k := pod.kind + k == "Pod" + container := pod.spec.containers[_] + image := container.image + repo_prefix := public_registries(image)[_] + startswith(image, repo_prefix) + containerName := container.name + + msga := { + "alertMessage": sprintf("image '%v' in container '%s' comes from public registry", [image, containerName]), + "packagename": "armo_builtins", + "alertScore": 1, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +untrustedImageRepo[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + image := container.image + repo_prefix := public_registries(image)[_] + startswith(image, repo_prefix) + containerName := container.name + + msga := { + "alertMessage": sprintf("image '%v' in container '%s' comes from public registry", [image, containerName]), + "packagename": "armo_builtins", + "alertScore": 1, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +untrustedImageRepo[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + image := container.image + repo_prefix := public_registries(image)[_] + startswith(image, repo_prefix) + containerName := container.name + + msga := { + "alertMessage": sprintf("image '%v' in container '%s' comes from public registry", [image, containerName]), + "packagename": "armo_builtins", + "alertScore": 1, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} \ No newline at end of file diff --git a/rules/rule-identify-blacklisted-image-registries/rule.metadata.json b/rules/rule-identify-blacklisted-image-registries/rule.metadata.json new file mode 100644 index 000000000..49ec24ee6 --- /dev/null +++ b/rules/rule-identify-blacklisted-image-registries/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "rule-identify-blacklisted-image-registries", + "attributes": { + "m$K8sThreatMatrix": "Initial Access::Compromised images in registry", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Pods", + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob" + ] + } + ], + "ruleDependencies": [], + "description": "Identifying if pod container images are from unallowed registries", + "remediation": "Use images from safe registry", + "ruleQuery": "" +} \ No newline at end of file diff --git a/rules/rule-list-all-cluster-admins/raw.rego b/rules/rule-list-all-cluster-admins/raw.rego new file mode 100644 index 000000000..785d8bb9f --- /dev/null +++ b/rules/rule-list-all-cluster-admins/raw.rego @@ -0,0 +1,96 @@ +package armo_builtins +import data.cautils as cautils + +# input: roles +# apiversion: v1 +# does: returns roles+ related subjects in rolebinding + +deny[msga] { + roles := [role | role= input[_]; role.kind == "Role"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreate(rule) + canCreateResources(rule) + + rolebinding.roleRef.kind == "Role" + rolebinding.roleRef.name == role.metadata.name + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v have high privileges, such as cluster-admin", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# input: ClusterRole +# apiversion: v1 +# does: returns clusterroles+ related subjects in rolebinding + +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "RoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreate(rule) + canCreateResources(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v have high privileges, such as cluster-admin", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + +# input: ClusterRole +# apiversion: v1 +# does: returns clusterroles+ related subjects in clusterrolebinding + +deny[msga] { + roles := [role | role= input[_]; role.kind == "ClusterRole"] + rolebindings := [rolebinding | rolebinding = input[_]; rolebinding.kind == "ClusterRoleBinding"] + role:= roles[_] + rolebinding := rolebindings[_] + + rule:= role.rules[_] + canCreate(rule) + canCreateResources(rule) + + rolebinding.roleRef.kind == "ClusterRole" + rolebinding.roleRef.name == role.metadata.name + + subjects := rolebinding.subjects[_] + + msga := { + "alertMessage": sprintf("the following %v: %v have high privileges, such as cluster-admin", [subjects.kind, subjects.name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [role,rolebinding] + } + } +} + + +canCreate(rule) { + cautils.list_contains(rule.verbs,"*") +} +canCreateResources(rule){ + cautils.list_contains(rule.resources,"*") +} diff --git a/rules/rule-list-all-cluster-admins/rule.metadata.json b/rules/rule-list-all-cluster-admins/rule.metadata.json new file mode 100644 index 000000000..e6670d58d --- /dev/null +++ b/rules/rule-list-all-cluster-admins/rule.metadata.json @@ -0,0 +1,32 @@ +{ + "name": "rule-list-all-cluster-admins", + "attributes": { + "m$K8sThreatMatrix": "Privilege Escalation::Cluster-admin binding", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Role", + "ClusterRole", + "ClusterRoleBinding", + "RoleBinding" + ] + } + ], + "ruleDependencies": [ + { + "packageName": "cautils" + } + ], + "description": "determines which users have cluster admin permissions", + "remediation": "", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/rule-name-similarity/raw.rego b/rules/rule-name-similarity/raw.rego new file mode 100644 index 000000000..fe30e2f97 --- /dev/null +++ b/rules/rule-name-similarity/raw.rego @@ -0,0 +1,29 @@ +package armo_builtins +# import data.cautils as cautils +# import data.kubernetes.api.client as client + +# input: pods +# apiversion: v1 +# fails if object has similar name to known workload (but is not from that workload) + +deny[msga] { + object := input[_] + wanted_kinds := {"Pod", "ReplicaSet", "Job"} + wanted_kinds[object.kind] + + wl_known_names := {"coredns", "kube-proxy", + "event-exporter-gke", "kube-dns", "17-default-backend", "metrics-server", + "ca-audit", "ca-dashboard-aggregator","ca-notification-server", "ca-ocimage","ca-oracle", + "ca-posture", "ca-rbac", "ca-vuln-scan", "ca-webhook", "ca-websocket", "clair-clair"} + wl_name := wl_known_names[_] + contains(object.metadata.name, wl_name) + + msga := { + "alertMessage": sprintf("this %v has a similar name to %v", [object.kind, wl_name]), + "alertScore": 9, + "packagename": "armo_builtins", + "alertObject": { + "k8sApiObjects": [object] + } + } +} \ No newline at end of file diff --git a/rules/rule-name-similarity/rule.metadata.json b/rules/rule-name-similarity/rule.metadata.json new file mode 100644 index 000000000..1876c2839 --- /dev/null +++ b/rules/rule-name-similarity/rule.metadata.json @@ -0,0 +1,27 @@ +{ + "name": "rule-name-similarity", + "attributes": { + "m$K8sThreatMatrix": "Defense evasion::Pod / container name similarity", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Pod", + "ReplicaSet", + "Job" + ] + } + ], + "ruleDependencies": [], + "description": "fails if there are objects with names similar to system pods, or other known deployments", + "remediation": "", + "ruleQuery": "armo_builtins" + } \ No newline at end of file diff --git a/rules/rule-privilege-escalation/raw.rego b/rules/rule-privilege-escalation/raw.rego new file mode 100644 index 000000000..ce5c8f5bd --- /dev/null +++ b/rules/rule-privilege-escalation/raw.rego @@ -0,0 +1,57 @@ +package armo_builtins +# Deny mutating action unless user is in group owning the resource + + +#privileged pods +deny[msga] { + + pod := input[_] + pod.kind == "Pod" + containers := pod.spec.containers[_] + containers.securityContext.privileged == true + msga := { + "alertMessage": sprintf("the following pods are defined as privileged: %v", [pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 3, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + + +#handles majority of workload resources +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + containers := wl.spec.template.spec.containers[_] + containers.securityContext.privileged == true + + msga := { + "alertMessage": sprintf("%v: %v is defined as privileged:", [wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 3, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +#handles cronjob +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + containers := wl.spec.jobTemplate.spec.template.spec.containers[_] + containers.securityContext.privileged == true + + msga := { + "alertMessage": sprintf("the following cronjobs are defined as privileged: %v", [wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 3, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + diff --git a/rules/rule-privilege-escalation/rule.metadata.json b/rules/rule-privilege-escalation/rule.metadata.json new file mode 100644 index 000000000..09d2eb66f --- /dev/null +++ b/rules/rule-privilege-escalation/rule.metadata.json @@ -0,0 +1,33 @@ +{ + "name": "rule-privilege-escalation", + "attributes": { + "m$K8sThreatMatrix": "Privilege Escalation::privileged container", + "mitre": "Privilege Escalation", + "mitreCode": "TA0004", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [], + "description": "determines if pods/deployments defined as privileged true", + "remediation": "avoid defining pods as privilleged", + "ruleQuery": "" +} \ No newline at end of file diff --git a/rules/security-context-in-pod/raw.rego b/rules/security-context-in-pod/raw.rego new file mode 100644 index 000000000..2e4ababea --- /dev/null +++ b/rules/security-context-in-pod/raw.rego @@ -0,0 +1,63 @@ +package armo_builtins + + +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isNotSecurityContext(pod, container) + msga := { + "alertMessage": sprintf("Container: %v in pod: %v does not define a securityContext.", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + + + +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isNotSecurityContext(wl.spec.template, container) + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not define a securityContext.", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isNotSecurityContext(wl.spec.jobTemplate.spec.template, container) + msga := { + "alertMessage": sprintf("Container: %v in %v: %v does not define a securityContext.", [ container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + + +isNotSecurityContext(pod, container) { + count(pod.spec.securityContext) == 0 + not container.securityContext +} + +isNotSecurityContext(pod, container) { + count(pod.spec.securityContext) == 0 + count(container.securityContext) == 0 +} \ No newline at end of file diff --git a/rules/security-context-in-pod/rule.metadata.json b/rules/security-context-in-pod/rule.metadata.json new file mode 100644 index 000000000..f26327e0f --- /dev/null +++ b/rules/security-context-in-pod/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "security-context-in-pod", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if pod/container does not define a security context.", + "remediation": "Make sure that the securityContext field is defined for pod/container.", + "ruleQuery": "armo_builtins" +} \ No newline at end of file diff --git a/rules/sidecar-injection/raw.rego b/rules/sidecar-injection/raw.rego new file mode 100644 index 000000000..f94082e56 --- /dev/null +++ b/rules/sidecar-injection/raw.rego @@ -0,0 +1,107 @@ +package armo_builtins + +# =========== looks for containers with lifecycle.type "Sidecar" =========== +#pods +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + container.lifecycle.type == "Sidecar" + msga := { + "alertMessage": sprintf("The pod: %v has a sidecar: %v", [pod.metadata.name, container.name]), + "packagename": "armo_builtins", + "alertScore": 3, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +#handles majority of workload resources +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + container.lifecycle.type == "Sidecar" + + msga := { + "alertMessage": sprintf("%v: %v has a sidecar: %v", [wl.kind, wl.metadata.name, container.name]), + "packagename": "armo_builtins", + "alertScore": 3, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +#handles cronjob +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + container.lifecycle.type == "Sidecar" + + msga := { + "alertMessage": sprintf("Cronjob: %v has a sidecar: %v", [wl.metadata.name, container.name]), + "packagename": "armo_builtins", + "alertScore": 3, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# =========== looks for containers "sidecar" in name =========== +#pods +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + contains(lower(container.name), "sidecar") + + msga := { + "alertMessage": sprintf("The pod: %v has a sidecar: %v", [pod.metadata.name, container.name]), + "packagename": "armo_builtins", + "alertScore": 3, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +#handles majority of workload resources +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + contains(lower(container.name), "sidecar") + + msga := { + "alertMessage": sprintf("%v: %v has a sidecar: %v", [wl.kind, wl.metadata.name, container.name]), + "packagename": "armo_builtins", + "alertScore": 3, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +#handles cronjob +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container := wl.spec.jobTemplate.spec.template.spec.containers[_] + contains(lower(container.name), "sidecar") + + msga := { + "alertMessage": sprintf("Cronjob: %v has a sidecar: %v", [wl.metadata.name, container.name]), + "packagename": "armo_builtins", + "alertScore": 3, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} \ No newline at end of file diff --git a/rules/sidecar-injection/rule.metadata.json b/rules/sidecar-injection/rule.metadata.json new file mode 100644 index 000000000..98ab30cf6 --- /dev/null +++ b/rules/sidecar-injection/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "sidecar-injection", + "attributes": { + "m$K8sThreatMatrix": "Execution::Sidecar injection", + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Pods", + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "CronJob" + ] + } + ], + "ruleDependencies": [], + "description": "fails if container lifecycle field is set to sidecar, or if container name includes 'sidecar'.", + "remediation": "", + "ruleQuery": "" + } \ No newline at end of file diff --git a/rules/user-id-less-than-thousands/raw.rego b/rules/user-id-less-than-thousands/raw.rego new file mode 100644 index 000000000..935dc6fa2 --- /dev/null +++ b/rules/user-id-less-than-thousands/raw.rego @@ -0,0 +1,124 @@ +package armo_builtins + + +# Fails if pod has container configured to run with id less than 1000 +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isRootContainer(container) + msga := { + "alertMessage": sprintf("container: %v in pod: %v runs with id less than 1000", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + +# Fails if pod has container configured to run with id less than 1000 +deny[msga] { + pod := input[_] + pod.kind == "Pod" + container := pod.spec.containers[_] + isRootPod(pod, container) + msga := { + "alertMessage": sprintf("container: %v in pod: %v runs with id less than 1000", [container.name, pod.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [pod] + } + } +} + + + +# Fails if workload has container configured to run with id less than 1000 +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isRootContainer(container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v runs with id less than 1000", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + +# Fails if workload has container configured to run with id less than 1000 +deny[msga] { + wl := input[_] + spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"} + spec_template_spec_patterns[wl.kind] + container := wl.spec.template.spec.containers[_] + isRootPod(wl.spec.template, container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v runs with id less than 1000", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +# Fails if cronjob has a container configured to run with id less than 1000 +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isRootContainer(container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v runs with id less than 1000", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + + +# Fails if workload has container configured to run with id less than 1000 +deny[msga] { + wl := input[_] + wl.kind == "CronJob" + container = wl.spec.jobTemplate.spec.template.spec.containers[_] + isRootPod(wl.spec.jobTemplate.spec.template, container) + msga := { + "alertMessage": sprintf("container :%v in %v: %v runs with id less than 1000", [container.name, wl.kind, wl.metadata.name]), + "packagename": "armo_builtins", + "alertScore": 7, + "alertObject": { + "k8sApiObjects": [wl] + } + } +} + + +isRootPod(pod, container) { + not container.securityContext.runAsUser + pod.spec.securityContext.runAsUser < 1000 +} + +isRootPod(pod, container) { + not container.securityContext.runAsGroup + pod.spec.securityContext.runAsGroup < 1000 +} + +isRootContainer(container) { + container.securityContext.runAsUser < 1000 +} + +isRootContainer(container) { + container.securityContext.runAsGroup < 1000 +} \ No newline at end of file diff --git a/rules/user-id-less-than-thousands/rule.metadata.json b/rules/user-id-less-than-thousands/rule.metadata.json new file mode 100644 index 000000000..43974da90 --- /dev/null +++ b/rules/user-id-less-than-thousands/rule.metadata.json @@ -0,0 +1,31 @@ +{ + "name": "user-id-less-than-thousands", + "attributes": { + "armoBuiltin": true + }, + "ruleLanguage": "Rego", + "match": [ + { + "apiGroups": [ + "*" + ], + "apiVersions": [ + "*" + ], + "resources": [ + "Deployment", + "ReplicaSet", + "DaemonSet", + "StatefulSet", + "Job", + "Pod", + "CronJob" + ] + } + ], + "ruleDependencies": [ + ], + "description": "fails if container can run as high user (id less than 1000)", + "remediation": "Make sure that the user/group in the securityContext of pod/container is set to an id less than 1000.", + "ruleQuery": "armo_builtins" +} \ No newline at end of file