-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>
- Loading branch information
1 parent
d4a1f9f
commit 8b49e80
Showing
24 changed files
with
588 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
63
rules/workload-with-administrative-roles/rule.metadata.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
110 changes: 110 additions & 0 deletions
110
rules/workload-with-administrative-roles/test/fail-wl-creates-pod/expected.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
] | ||
} | ||
] |
8 changes: 8 additions & 0 deletions
8
rules/workload-with-administrative-roles/test/fail-wl-creates-pod/input/clusterrole.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", "*"] |
15 changes: 15 additions & 0 deletions
15
...workload-with-administrative-roles/test/fail-wl-creates-pod/input/clusterrolebinding.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.