Repository: FairwindsOps/polaris
Pull request #823 (2022-08-18 19:53:17)

FWI-2582: Add `clusterrolebindingClusterAdmin`, `rolebindingClusterAdminRole`, and `rolebindingClusterAdminClusterRole` checks + schema tests
This PR fixes #

## Checklist
* [ ] I have signed the CLA
* [ ] I have updated/added any relevant documentation

## Description
### What's the goal of this PR?
Add new checks that alert when:
* A ClusterRoleBinding references the default `cluster-admin` ClusterRole, or another role with wildcard permissions.
* A RoleBinding references the default `cluster-admin` ClusterRole, or another role with wildcard permissions.
* A RoleBinding references a Role with wildcard permissions.

Note that "wildcard permissions" also includes the literal specification of all 7 verbs.



fairwinds-insights[bot]: ## Fairwinds Insights CI Report
> [View the Full Report](https://insights.fairwinds.com/gh/FairwindsOps/polaris/FWI-2582-binding_clusteradmin)

:white_check_mark: No new Action Items detected!
ivanfetch-fw: Tests will fail for now until [this Polaris PR is merged](https://github.com/FairwindsOps/polaris/pull/822)

Patch:
diff --git a/checks/clusterrolebindingClusterAdmin.yaml b/checks/clusterrolebindingClusterAdmin.yaml
new file mode 100644
index 00000000..113b6c30
--- /dev/null
+++ b/checks/clusterrolebindingClusterAdmin.yaml
@@ -0,0 +1,88 @@
+successMessage: The ClusterRoleBinding does not reference the default cluster-admin ClusterRole or one with wildcard permissions
+failureMessage: The ClusterRoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions
+category: Security
+target: rbac.authorization.k8s.io/ClusterRoleBinding
+schemaString: |
+  '$schema': http://json-schema.org/draft-07/schema
+  type: object
+  anyOf:
+    # Do not alert on default ClusterRoleBindings.
+    - required: ["metadata"]
+      properties:
+        metadata:
+          type: object
+          required: ["name"]
+          properties:
+            name:
+              type: string
+              anyOf:
+                - const: "cluster-admin"
+                - const: "system:controller:generic-garbage-collector"
+                - const: "system:controller:namespace-controller"
+    - required: ["roleRef"]
+      properties:
+        roleRef:
+          required: ["apiGroup", "kind", "name"]
+          properties:
+            apiGroup:
+              type: string
+              const: "rbac.authorization.k8s.io"
+            kind:
+              type: string
+              const: "ClusterRole"
+            name:
+              type: string
+              minLength: 1
+              not:
+                const: "cluster-admin"
+additionalSchemaStrings:
+  rbac.authorization.k8s.io/ClusterRole: |
+    type: object
+    # Do not alert on default ClusterRoleBindings.
+    {{ if and (ne .metadata.name "cluster-admin") (ne .metadata.name "system:controller:generic-garbage-collector") (ne .metadata.name "system:controller:namespace-controller") }}
+    required: ["metadata", "rules"]
+    allOf:
+      - properties:
+          metadata:
+            required: ["name"]
+            properties:
+              name:
+                type: string
+                const: "{{ .roleRef.name }}"
+      - properties:
+          rules:
+            type: array
+            items:
+              type: object
+              not:
+                required: ["apiGroups", "resources", "verbs"]
+                properties:
+                  apiGroups:
+                    type: array
+                    contains:
+                      type: string
+                      const: "*"
+                  resources:
+                    type: array
+                    contains:
+                      type: string
+                      const: '*'
+                  verbs:
+                    type: array
+                    uniqueItems: true
+                    oneOf:
+                      - contains:
+                          type: string
+                          const: '*'
+                      - minItems: 7
+                        items:
+                          type: string
+                          enum:
+                            - "get"
+                            - "list"
+                            - "watch"
+                            - "create"
+                            - "update"
+                            - "patch"
+                            - "delete"
+    {{ end }}
diff --git a/checks/rolebindingClusterAdminClusterRole.yaml b/checks/rolebindingClusterAdminClusterRole.yaml
new file mode 100644
index 00000000..89d845b8
--- /dev/null
+++ b/checks/rolebindingClusterAdminClusterRole.yaml
@@ -0,0 +1,84 @@
+successMessage: The RoleBinding does not reference the default cluster-admin ClusterRole or one with wildcard permissions
+failureMessage: The RoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions
+category: Security
+target: rbac.authorization.k8s.io/RoleBinding
+schemaString: |
+  '$schema': http://json-schema.org/draft-07/schema
+  type: object
+  anyOf:
+    # Pass RoleBindings that point to a Role.
+    - required: ["roleRef"]
+      properties:
+        roleRef:
+          required: ["kind"]
+          properties:
+            kind:
+              type: string
+              const: "Role"
+    - required: ["roleRef"]
+      properties:
+        roleRef:
+          required: ["apiGroup", "kind", "name"]
+          properties:
+            apiGroup:
+              type: string
+              const: "rbac.authorization.k8s.io"
+            kind:
+              type: string
+              const: "ClusterRole"
+            name:
+              type: string
+              minLength: 1
+              not:
+                const: "cluster-admin"
+additionalSchemaStrings:
+  rbac.authorization.k8s.io/ClusterRole: |
+    type: object
+    # This schema is validated for all roleBindings, regardless of their roleRef.
+    {{ if eq .roleRef.kind "ClusterRole" }}
+    required: ["metadata", "rules"]
+    allOf:
+      - properties:
+          metadata:
+            required: ["name"]
+            properties:
+              name:
+                type: string
+                const: "{{ .roleRef.name }}"
+      - properties:
+          rules:
+            type: array
+            items:
+              type: object
+              not:
+                required: ["apiGroups", "resources", "verbs"]
+                properties:
+                  apiGroups:
+                    type: array
+                    contains:
+                      type: string
+                      const: "*"
+                  resources:
+                    type: array
+                    contains:
+                      type: string
+                      const: '*'
+                  verbs:
+                    type: array
+                    uniqueItems: true
+                    oneOf:
+                      - contains:
+                          type: string
+                          const: '*'
+                      - minItems: 7
+                        items:
+                          type: string
+                          enum:
+                            - "get"
+                            - "list"
+                            - "watch"
+                            - "create"
+                            - "update"
+                            - "patch"
+                            - "delete"
+    {{ end }}
diff --git a/checks/rolebindingClusterAdminRole.yaml b/checks/rolebindingClusterAdminRole.yaml
new file mode 100644
index 00000000..914497b4
--- /dev/null
+++ b/checks/rolebindingClusterAdminRole.yaml
@@ -0,0 +1,82 @@
+successMessage: The RoleBinding does not reference a Role with wildcard permissions
+failureMessage: The RoleBinding references a Role with wildcard permissions
+category: Security
+target: rbac.authorization.k8s.io/RoleBinding
+schemaString: |
+  '$schema': http://json-schema.org/draft-07/schema
+  type: object
+  anyOf:
+    # Pass RoleBindings that point to a ClusterRole.
+    - required: ["roleRef"]
+      properties:
+        roleRef:
+          required: ["kind"]
+          properties:
+            kind:
+              type: string
+              const: "ClusterRole"
+    - required: ["roleRef"]
+      properties:
+        roleRef:
+          required: ["apiGroup", "kind", "name"]
+          properties:
+            apiGroup:
+              type: string
+              const: "rbac.authorization.k8s.io"
+            kind:
+              type: string
+              const: "Role"
+            name:
+              type: string
+              minLength: 1
+additionalSchemaStrings:
+  rbac.authorization.k8s.io/Role: |
+    type: object
+    # This schema is validated for all roleBindings, regardless of their roleRef.
+    {{ if eq .roleRef.kind "Role" }}
+    required: ["metadata", "rules"]
+    allOf:
+      - properties:
+          metadata:
+            required: ["name"]
+            properties:
+              name:
+                type: string
+                const: "{{ .roleRef.name }}"
+      - properties:
+          rules:
+            type: array
+            items:
+              type: object
+              not:
+                required: ["apiGroups", "resources", "verbs"]
+                properties:
+                  apiGroups:
+                    type: array
+                    contains:
+                      type: string
+                      const: "*"
+                  resources:
+                    type: array
+                    contains:
+                      type: string
+                      const: '*'
+                  verbs:
+                    type: array
+                    uniqueItems: true
+                    oneOf:
+                      - contains:
+                          type: string
+                          const: '*'
+                      - minItems: 7
+                        items:
+                          type: string
+                          enum:
+                            - "get"
+                            - "list"
+                            - "watch"
+                            - "create"
+                            - "update"
+                            - "patch"
+                            - "delete"
+    {{ end }}
diff --git a/pkg/config/checks.go b/pkg/config/checks.go
index 85387081..8f26bca4 100644
--- a/pkg/config/checks.go
+++ b/pkg/config/checks.go
@@ -59,6 +59,9 @@ var (
 		"missingPodDisruptionBudget",
 		"missingNetworkPolicy",
 		"sensitiveConfigmapContent",
+		"clusterrolebindingClusterAdmin",
+		"rolebindingClusterAdminClusterRole",
+		"rolebindingClusterAdminRole",
 	}
 )
 


Test patch:
diff --git a/test/checks/clusterrolebindingClusterAdmin/failure.all_verbs.yaml b/test/checks/clusterrolebindingClusterAdmin/failure.all_verbs.yaml
new file mode 100644
index 00000000..70577ece
--- /dev/null
+++ b/test/checks/clusterrolebindingClusterAdmin/failure.all_verbs.yaml
@@ -0,0 +1,33 @@
+# This fails because the clusterRoleBinding references a ClusterRole that uses all wildcards and all verbs.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "get", "list", "watch", "create", "update", "patch", "delete" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/clusterrolebindingClusterAdmin/failure.all_verbs_different_order.yaml b/test/checks/clusterrolebindingClusterAdmin/failure.all_verbs_different_order.yaml
new file mode 100644
index 00000000..a7510c9e
--- /dev/null
+++ b/test/checks/clusterrolebindingClusterAdmin/failure.all_verbs_different_order.yaml
@@ -0,0 +1,33 @@
+# This fails because the clusterRoleBinding references a ClusterRole that uses all wildcards and all verbs (in different order from failure.all_verbs.yaml).
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "get", "list", "delete", "update", "create", "patch", "watch" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/clusterrolebindingClusterAdmin/failure.default_clusteradmin_clusterrole.yaml b/test/checks/clusterrolebindingClusterAdmin/failure.default_clusteradmin_clusterrole.yaml
new file mode 100644
index 00000000..3b22f510
--- /dev/null
+++ b/test/checks/clusterrolebindingClusterAdmin/failure.default_clusteradmin_clusterrole.yaml
@@ -0,0 +1,26 @@
+# This fails because the ClusterRoleBinding references the default cluster-admin ClusterRole.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cluster-admin
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This ClusterRole emulates the default cluster-admin one, only by name.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: cluster-admin
+rules:
+- apiGroups:
+  - ''
+  resources:
+  - 'deployments'
+  verbs:
+  - 'get'
diff --git a/test/checks/clusterrolebindingClusterAdmin/failure.wildcards.yaml b/test/checks/clusterrolebindingClusterAdmin/failure.wildcards.yaml
new file mode 100644
index 00000000..d1c7594e
--- /dev/null
+++ b/test/checks/clusterrolebindingClusterAdmin/failure.wildcards.yaml
@@ -0,0 +1,33 @@
+# This fails because the clusterRoleBinding references a ClusterRole that uses all wildcards.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "*" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/clusterrolebindingClusterAdmin/success.allow_unrelated_perms.yaml b/test/checks/clusterrolebindingClusterAdmin/success.allow_unrelated_perms.yaml
new file mode 100644
index 00000000..326979d5
--- /dev/null
+++ b/test/checks/clusterrolebindingClusterAdmin/success.allow_unrelated_perms.yaml
@@ -0,0 +1,22 @@
+# This succeeds because the ClusterRoleBinding references a ClusterRole with safe permissions.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "deployments" ]
+    verbs: [ "get", "list" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
diff --git a/test/checks/clusterrolebindingClusterAdmin/success.explicit_apigroups.yaml b/test/checks/clusterrolebindingClusterAdmin/success.explicit_apigroups.yaml
new file mode 100644
index 00000000..d1e535fe
--- /dev/null
+++ b/test/checks/clusterrolebindingClusterAdmin/success.explicit_apigroups.yaml
@@ -0,0 +1,33 @@
+# This succeeds because the clusterRoleBinding references a ClusterRole with a safer subset of apiGroups.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "*" ]
+    verbs: [ "*" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/clusterrolebindingClusterAdmin/success.explicit_resources.yaml b/test/checks/clusterrolebindingClusterAdmin/success.explicit_resources.yaml
new file mode 100644
index 00000000..a5e9cabe
--- /dev/null
+++ b/test/checks/clusterrolebindingClusterAdmin/success.explicit_resources.yaml
@@ -0,0 +1,34 @@
+# This succeeds because the clusterRoleBinding references a ClusterRole with a safer subset of resources.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "pods" ]
+    verbs: [ "*" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
+
diff --git a/test/checks/clusterrolebindingClusterAdmin/success.explicit_verbs.yaml b/test/checks/clusterrolebindingClusterAdmin/success.explicit_verbs.yaml
new file mode 100644
index 00000000..2000a702
--- /dev/null
+++ b/test/checks/clusterrolebindingClusterAdmin/success.explicit_verbs.yaml
@@ -0,0 +1,33 @@
+# This succeeds because the clusterRoleBinding references a ClusterRole with a safer subset of verbs.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "get", "list" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+  # This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminClusterRole/failure.all_verbs.yaml b/test/checks/rolebindingClusterAdminClusterRole/failure.all_verbs.yaml
new file mode 100644
index 00000000..6e548295
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminClusterRole/failure.all_verbs.yaml
@@ -0,0 +1,34 @@
+# This fails because the roleBinding references a ClusterRole that uses all wildcards and all verbs.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "get", "list", "watch", "create", "update", "patch", "delete" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminClusterRole/failure.all_verbs_different_order.yaml b/test/checks/rolebindingClusterAdminClusterRole/failure.all_verbs_different_order.yaml
new file mode 100644
index 00000000..248f19aa
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminClusterRole/failure.all_verbs_different_order.yaml
@@ -0,0 +1,34 @@
+# This fails because the roleBinding references a ClusterRole that uses all wildcards and all verbs (in different order from failure.all_verbs.yaml).
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "get", "list", "delete", "update", "create", "patch", "watch" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminClusterRole/failure.default_clusteradmin_clusterrole.yaml b/test/checks/rolebindingClusterAdminClusterRole/failure.default_clusteradmin_clusterrole.yaml
new file mode 100644
index 00000000..4113d2d5
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminClusterRole/failure.default_clusteradmin_clusterrole.yaml
@@ -0,0 +1,31 @@
+# This fails because the roleBinding references the default cluster-admin ClusterRole.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cluster-admin
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This ClusterRole emulates the default cluster-admin one.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: cluster-admin
+rules:
+- apiGroups:
+  - '*'
+  resources:
+  - '*'
+  verbs:
+  - 'get'
+- nonResourceURLs:
+  - '*'
+  verbs:
+  - '*'
diff --git a/test/checks/rolebindingClusterAdminClusterRole/failure.wildcards.yaml b/test/checks/rolebindingClusterAdminClusterRole/failure.wildcards.yaml
new file mode 100644
index 00000000..9d057eae
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminClusterRole/failure.wildcards.yaml
@@ -0,0 +1,34 @@
+# This fails because the roleBinding references a ClusterRole that uses all wildcards.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "*" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminClusterRole/success.allow_unrelated_perms.yaml b/test/checks/rolebindingClusterAdminClusterRole/success.allow_unrelated_perms.yaml
new file mode 100644
index 00000000..c676730c
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminClusterRole/success.allow_unrelated_perms.yaml
@@ -0,0 +1,34 @@
+# This succeeds because the RoleBinding references a ClusterRole with safe permissions.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "deployments" ]
+    verbs: [ "get", "list" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+  # This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminClusterRole/success.binding_references_role.yaml b/test/checks/rolebindingClusterAdminClusterRole/success.binding_references_role.yaml
new file mode 100644
index 00000000..99d141b6
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminClusterRole/success.binding_references_role.yaml
@@ -0,0 +1,35 @@
+# This succeeds because the roleBinding references a Role instead of a ClusterRole.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: test
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ get, create ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This ClusterRole exists so there is at least one ClusterRole for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminClusterRole/success.explicit_apigroups.yaml b/test/checks/rolebindingClusterAdminClusterRole/success.explicit_apigroups.yaml
new file mode 100644
index 00000000..7d2dd351
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminClusterRole/success.explicit_apigroups.yaml
@@ -0,0 +1,34 @@
+# This succeeds because the roleBinding references a ClusterRole with a safer subset of apiGroups.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "*" ]
+    verbs: [ "*" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminClusterRole/success.explicit_resources.yaml b/test/checks/rolebindingClusterAdminClusterRole/success.explicit_resources.yaml
new file mode 100644
index 00000000..52845344
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminClusterRole/success.explicit_resources.yaml
@@ -0,0 +1,35 @@
+# This succeeds because the roleBinding references a ClusterRole with a safer subset of resources.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "pods" ]
+    verbs: [ "*" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
+
diff --git a/test/checks/rolebindingClusterAdminClusterRole/success.explicit_verbs.yaml b/test/checks/rolebindingClusterAdminClusterRole/success.explicit_verbs.yaml
new file mode 100644
index 00000000..7bf63f04
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminClusterRole/success.explicit_verbs.yaml
@@ -0,0 +1,34 @@
+# This succeeds because the roleBinding references a ClusterRole with a safer subset of verbs.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "get", "list" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+  # This Role exists so there is at least one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminRole/failure.all_verbs.yaml b/test/checks/rolebindingClusterAdminRole/failure.all_verbs.yaml
new file mode 100644
index 00000000..7ccb272d
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminRole/failure.all_verbs.yaml
@@ -0,0 +1,34 @@
+# This fails because the roleBinding references a Role that uses all wildcards and all verbs.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: test
+  namespace: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "get", "list", "watch", "create", "update", "patch", "delete" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This ClusterRole exists so there is at leat one ClusterRole for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: not-used
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminRole/failure.all_verbs_different_order.yaml b/test/checks/rolebindingClusterAdminRole/failure.all_verbs_different_order.yaml
new file mode 100644
index 00000000..a009648c
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminRole/failure.all_verbs_different_order.yaml
@@ -0,0 +1,34 @@
+# This fails because the roleBinding references a Role that uses all wildcards and all verbs (in different order from failure.all_verbs.yaml).
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: test
+  namespace: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "get", "list", "delete", "update", "create", "patch", "watch" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This ClusterRole exists so there is at leat one ClusterRole for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: not-used
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminRole/failure.wildcards.yaml b/test/checks/rolebindingClusterAdminRole/failure.wildcards.yaml
new file mode 100644
index 00000000..41b46919
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminRole/failure.wildcards.yaml
@@ -0,0 +1,34 @@
+# This fails because the roleBinding references a Role that uses all wildcards.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: test
+  namespace: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "*" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This ClusterRole exists so there is at leat one ClusterRole for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: not-used
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminRole/success.allow_unrelated_perms.yaml b/test/checks/rolebindingClusterAdminRole/success.allow_unrelated_perms.yaml
new file mode 100644
index 00000000..d3838e2e
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminRole/success.allow_unrelated_perms.yaml
@@ -0,0 +1,34 @@
+# This succeeds because the roleBinding references a Role with safe permissions.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: test
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "deployments" ]
+    verbs: [ "get", "list" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+  # This ClusterRole exists so there is at least one ClusterRole for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: not-used
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminRole/success.binding_references_clusterrole.yaml b/test/checks/rolebindingClusterAdminRole/success.binding_references_clusterrole.yaml
new file mode 100644
index 00000000..b748d885
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminRole/success.binding_references_clusterrole.yaml
@@ -0,0 +1,34 @@
+# This succeeds because the roleBinding references a ClusterRole instead of a Role.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ get, create ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This Role exists so there is at leat one Role for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: not-used
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminRole/success.explicit_apigroups.yaml b/test/checks/rolebindingClusterAdminRole/success.explicit_apigroups.yaml
new file mode 100644
index 00000000..3df246ab
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminRole/success.explicit_apigroups.yaml
@@ -0,0 +1,34 @@
+# This succeeds because the roleBinding references a Role with a safer subset of apiGroups.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: test
+  namespace: test
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "*" ]
+    verbs: [ "*" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This ClusterRole exists so there is at leat one ClusterRole for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: not-used
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/checks/rolebindingClusterAdminRole/success.explicit_resources.yaml b/test/checks/rolebindingClusterAdminRole/success.explicit_resources.yaml
new file mode 100644
index 00000000..b041f468
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminRole/success.explicit_resources.yaml
@@ -0,0 +1,35 @@
+# This succeeds because the roleBinding references a Role with a safer subset of resources.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: test
+  namespace: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "pods" ]
+    verbs: [ "*" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+# This ClusterRole exists so there is at leat one ClusterRole for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: not-used
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
+
diff --git a/test/checks/rolebindingClusterAdminRole/success.explicit_verbs.yaml b/test/checks/rolebindingClusterAdminRole/success.explicit_verbs.yaml
new file mode 100644
index 00000000..a0397180
--- /dev/null
+++ b/test/checks/rolebindingClusterAdminRole/success.explicit_verbs.yaml
@@ -0,0 +1,34 @@
+# This succeeds because the roleBinding references a Role with a safer subset of verbs.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: test
+  namespace: test
+rules:
+  - apiGroups: [ "*" ]
+    resources: [ "*" ]
+    verbs: [ "get", "list" ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: test
+  namespace: test
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: test
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: User
+  name: testuser
+---
+  # This ClusterRole exists so there is at leat one ClusterRole for the additionalSchema to find.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: not-used
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ list ]
diff --git a/test/fixtures.go b/test/fixtures.go
index ba7d6c7b..102d8277 100644
--- a/test/fixtures.go
+++ b/test/fixtures.go
@@ -256,6 +256,15 @@ func SetupTestAPI(objects ...runtime.Object) (kubernetes.Interface, dynamic.Inte
 				{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
 			},
 		},
+		{
+			GroupVersion: "rbac.authorization.k8s.io/v1",
+			APIResources: []metav1.APIResource{
+				{Name: "clusterroles", Namespaced: false, Kind: "ClusterRole"},
+				{Name: "roles", Namespaced: true, Kind: "Role"},
+				{Name: "clusterrolebindings", Namespaced: false, Kind: "ClusterRoleBinding"},
+				{Name: "rolebindings", Namespaced: true, Kind: "RoleBinding"},
+			},
+		},
 	}
 	return k, dynamicClient
 }