Skip to content

Commit

Permalink
add control C-0272
Browse files Browse the repository at this point in the history
Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>
  • Loading branch information
YiscahLevySilas1 committed Mar 3, 2024
1 parent d4a1f9f commit 8b49e80
Show file tree
Hide file tree
Showing 24 changed files with 588 additions and 0 deletions.
22 changes: 22 additions & 0 deletions controls/C-0272-workloadwithadministrativeroles.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "Workload with administrative roles",
"attributes": {},
"description": "This control identifies workloads where the associated service accounts have roles that grant administrative-level access across the cluster. Granting a workload such expansive permissions equates to providing it cluster admin roles. This level of access can pose a significant security risk, as it allows the workload to perform any action on any resource, potentially leading to unauthorized data access or cluster modifications.",
"remediation": "You should apply least privilege principle. Make sure cluster admin permissions are granted only when it is absolutely necessary. Don't use service accounts with such high permissions for daily operations.",
"rulesNames": [
"workload-with-administrative-roles"
],
"long_description": "In Kubernetes environments, workloads granted administrative-level privileges without restrictions represent a critical security vulnerability. When a service account associated with a workload is configured with permissions to perform any action on any resource, it essentially holds unrestricted access within the cluster, akin to cluster admin privileges. This configuration dramatically increases the risk of security breaches, including data theft, unauthorized modifications, and potentially full cluster takeovers. Such privileges allow attackers to exploit the workload for wide-ranging malicious activities, bypassing the principle of least privilege. Therefore, it's essential to follow the least privilege principle and make sure cluster admin permissions are granted only when it is absolutely necessary.",
"test": "Check if the service account used by a workload has cluster admin roles, either by being bound to the cluster-admin clusterrole, or by having equivalent high privileges.",
"controlID": "C-0272",
"baseScore": 6.0,
"category": {
"name" : "Workload"
},
"scanningScope": {
"matches": [
"cluster",
"file"
]
}
}
32 changes: 32 additions & 0 deletions rules/workload-with-administrative-roles/filter.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package armo_builtins

deny[msga] {
wl := input[_]
start_of_path := get_beginning_of_path(wl)

msga := {
"alertMessage": sprintf("%v: %v in the following namespace: %v mounts service account tokens by default", [wl.kind, wl.metadata.name, wl.metadata.namespace]),
"packagename": "armo_builtins",
"alertScore": 9,
"alertObject": {
"k8sApiObjects": [wl]
},
}
}


get_beginning_of_path(workload) = start_of_path {
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
spec_template_spec_patterns[workload.kind]
start_of_path := ["spec", "template", "spec"]
}

get_beginning_of_path(workload) = start_of_path {
workload.kind == "Pod"
start_of_path := ["spec"]
}

get_beginning_of_path(workload) = start_of_path {
workload.kind == "CronJob"
start_of_path := ["spec", "jobTemplate", "spec", "template", "spec"]
}
129 changes: 129 additions & 0 deletions rules/workload-with-administrative-roles/raw.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package armo_builtins

import future.keywords.in

deny[msga] {
wl := input[_]
start_of_path := get_start_of_path(wl)
wl_spec := object.get(wl, start_of_path, [])

# get service account wl is using
sa := input[_]
sa.kind == "ServiceAccount"
is_same_sa(wl_spec, sa.metadata, wl.metadata)

# check service account token is mounted
is_sa_auto_mounted(wl_spec, sa)

# check if sa has administrative roles
role := input[_]
role.kind in ["Role", "ClusterRole"]
is_administrative_role(role)

rolebinding := input[_]
rolebinding.kind in ["RoleBinding", "ClusterRoleBinding"]
rolebinding.roleRef.name == role.metadata.name
rolebinding.subjects[j].kind == "ServiceAccount"
rolebinding.subjects[j].name == sa.metadata.name
rolebinding.subjects[j].namespace == sa.metadata.namespace

reviewPath := "roleRef"
deletePath := sprintf("subjects[%d]", [j])

msga := {
"alertMessage": sprintf("%v: %v in the following namespace: %v has administrative roles", [wl.kind, wl.metadata.name, wl.metadata.namespace]),
"packagename": "armo_builtins",
"alertScore": 9,
"alertObject": {
"k8sApiObjects": [wl]
},
"relatedObjects": [{
"object": sa,
},
{
"object": rolebinding,
"reviewPaths": [reviewPath],
"deletePaths": [deletePath],
},
{
"object": role,
},]
}
}


get_start_of_path(workload) = start_of_path {
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
spec_template_spec_patterns[workload.kind]
start_of_path := ["spec", "template", "spec"]
}

get_start_of_path(workload) = start_of_path {
workload.kind == "Pod"
start_of_path := ["spec"]
}

get_start_of_path(workload) = start_of_path {
workload.kind == "CronJob"
start_of_path := ["spec", "jobTemplate", "spec", "template", "spec"]
}


is_sa_auto_mounted(wl_spec, sa) {
# automountServiceAccountToken not in pod spec
not wl_spec.automountServiceAccountToken == false
not wl_spec.automountServiceAccountToken == true

not sa.automountServiceAccountToken == false
}

is_sa_auto_mounted(wl_spec, sa) {
# automountServiceAccountToken set to true in pod spec
wl_spec.automountServiceAccountToken == true
}


is_same_sa(wl_spec, sa_metadata, wl_metadata) {
wl_spec.serviceAccountName == sa_metadata.name
is_same_namespace(sa_metadata , wl_metadata)
}

is_same_sa(wl_spec, sa_metadata, wl_metadata) {
not wl_spec.serviceAccountName
sa_metadata.name == "default"
is_same_namespace(sa_metadata , wl_metadata)
}

# is_same_namespace supports cases where ns is not configured in the metadata
# for yaml scans
is_same_namespace(metadata1, metadata2) {
metadata1.namespace == metadata2.namespace
}

is_same_namespace(metadata1, metadata2) {
not metadata1.namespace
not metadata2.namespace
}

is_same_namespace(metadata1, metadata2) {
not metadata2.namespace
metadata1.namespace == "default"
}

is_same_namespace(metadata1, metadata2) {
not metadata1.namespace
metadata2.namespace == "default"
}


is_administrative_role(role){
administrative_resources := ["*"]
administrative_verbs := ["*"]
administrative_api_groups := ["", "*"]

administrative_rule := [rule | rule = role.rules[i] ;
rule.resources[a] in administrative_resources ;
rule.verbs[b] in administrative_verbs ;
rule.apiGroups[c] in administrative_api_groups]
count(administrative_rule) > 0
}
63 changes: 63 additions & 0 deletions rules/workload-with-administrative-roles/rule.metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"name": "workload-with-administrative-roles",
"attributes": {},
"ruleLanguage": "Rego",
"match": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"resources": [
"Pod",
"ServiceAccount"
]
},
{
"apiGroups": [
"apps"
],
"apiVersions": [
"v1"
],
"resources": [
"Deployment",
"ReplicaSet",
"DaemonSet",
"StatefulSet"
]
},
{
"apiGroups": [
"batch"
],
"apiVersions": [
"*"
],
"resources": [
"Job",
"CronJob"
]
},
{
"apiGroups": [
"rbac.authorization.k8s.io"
],
"apiVersions": [
"v1"
],
"resources": [
"RoleBinding",
"ClusterRoleBinding",
"Role",
"ClusterRole"
]
}
],
"ruleDependencies": [],
"description": "",
"remediation": "",
"ruleQuery": "armo_builtins"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
[
{
"alertMessage": "Pod: test-pd in the following namespace: default has administrative roles",
"failedPaths": null,
"reviewPaths": null,
"deletePaths": null,
"fixPaths": null,
"ruleStatus": "",
"packagename": "armo_builtins",
"alertScore": 9,
"alertObject": {
"k8sApiObjects": [
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test-pd"
}
}
]
},
"relatedObjects": [
{
"object": {
"apiVersion": "v1",
"automountServiceAccountToken": true,
"kind": "ServiceAccount",
"metadata": {
"creationTimestamp": "2022-02-07T11:21:55Z",
"name": "default",
"namespace": "default",
"resourceVersion": "410",
"uid": "5195ed3a-fa3c-46ce-8c66-32d1a83ea41f"
},
"secrets": [
{
"name": "default-token-sn9f8"
}
]
},
"failedPaths": null,
"reviewPaths": null,
"deletePaths": null,
"fixPaths": null
},
{
"object": {
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "ClusterRoleBinding",
"metadata": {
"name": "read-secrets-global"
},
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "ClusterRole",
"name": "test"
},
"subjects": [
{
"apiGroup": "rbac.authorization.k8s.io",
"kind": "Group",
"name": "manager"
},
{
"kind": "ServiceAccount",
"name": "default",
"namespace": "default"
}
]
},
"failedPaths": null,
"reviewPaths": [
"roleRef"
],
"deletePaths": [
"subjects[1]"
],
"fixPaths": null
},
{
"object": {
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "ClusterRole",
"metadata": {
"name": "test"
},
"rules": [
{
"apiGroups": [
""
],
"resources": [
"pods",
"*"
],
"verbs": [
"create",
"*"
]
}
]
},
"failedPaths": null,
"reviewPaths": null,
"deletePaths": null,
"fixPaths": null
}
]
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: test
rules:
- apiGroups: [""]
resources: ["pods", "*"]
verbs: ["create", "*"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: ClusterRole
name: test
apiGroup: rbac.authorization.k8s.io
Loading

0 comments on commit 8b49e80

Please sign in to comment.