Skip to content

Commit 86ce93b

Browse files
gsalaz98Skybound1
andauthored
Move all Relationship references to single dataclass Relationship (#11)
* Move all Relationship references to single dataclass * Fixes bug in `attack_paths.py` where POD was used instead of PODS (due to internals pluralizing resources) * Address review: fix bug in var order for GRANT relationship creation * Format code using `poetry run black .` * CI fixes --------- Co-authored-by: Mohit Gupta <[email protected]>
1 parent b72665d commit 86ce93b

File tree

9 files changed

+158
-43
lines changed

9 files changed

+158
-43
lines changed

icekube/attack_paths.py

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from typing import List
44

5+
from icekube.relationships import Relationship
6+
57
WORKLOAD_TYPES = [
68
"ReplicationController",
79
"DaemonSet",
@@ -27,40 +29,40 @@ def workload_query(
2729

2830
attack_paths = {
2931
# Subject -> Role Bindings
30-
"BOUND_TO": "MATCH (src)-[:BOUND_TO]->(dest)",
32+
Relationship.BOUND_TO: "MATCH (src)-[:BOUND_TO]->(dest)",
3133
# Role Binding -> Role
32-
"GRANTS_PERMISSION": "MATCH (src)-[:GRANTS_PERMISSION]->(dest)",
34+
Relationship.GRANTS_PERMISSION: "MATCH (src)-[:GRANTS_PERMISSION]->(dest)",
3335
# Pod -> Service Account
34-
"USES_ACCOUNT": "MATCH (src:Pod)-[:USES_ACCOUNT]->(dest:ServiceAccount)",
36+
Relationship.USES_ACCOUNT: "MATCH (src:Pod)-[:USES_ACCOUNT]->(dest:ServiceAccount)",
3537
# Pod -> Secrett
36-
"MOUNTS_SECRET": "MATCH (src:Pod)-[:MOUNTS_SECRET]->(dest:Secret)",
38+
Relationship.MOUNTS_SECRET: "MATCH (src:Pod)-[:MOUNTS_SECRET]->(dest:Secret)",
3739
# Subject has permission to create pod within namespace with target
3840
# Service Account
39-
"CREATE_POD_WITH_SA": f"""
41+
Relationship.CREATE_POD_WITH_SA: f"""
4042
MATCH (src)-[:GRANTS_PODS_CREATE|{create_workload_query()}]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount)
4143
""",
4244
# Subject has permission to update workload within namespace with target
4345
# Service Account
44-
"UPDATE_WORKLOAD_WITH_SA": f"""
46+
Relationship.UPDATE_WORKLOAD_WITH_SA: f"""
4547
MATCH (src)-[:GRANTS_UPDATE|GRANTS_PATCH]->(workload)-[:WITHIN_NAMESPACE]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount)
4648
WHERE {workload_query()}
4749
""",
4850
# Subject -> Pod
49-
"EXEC_INTO": "MATCH (src)-[:GRANTS_EXEC_CREATE]->(dest:Pod)<-[:GRANTS_GET]-(src)",
51+
Relationship.EXEC_INTO: "MATCH (src)-[:GRANTS_EXEC_CREATE]->(dest:Pod)<-[:GRANTS_GET]-(src)",
5052
# Subject -> Pod
51-
"REPLACE_IMAGE": "MATCH (src)-[:GRANTS_PATCH]->(dest:Pod)",
53+
Relationship.REPLACE_IMAGE: "MATCH (src)-[:GRANTS_PATCH]->(dest:Pod)",
5254
# Subject -> Pod
53-
"DEBUG_POD": "MATCH (src)-[:GRANTS_EPHEMERAL_PATCH]->(dest:Pod)",
55+
Relationship.DEBUG_POD: "MATCH (src)-[:GRANTS_EPHEMERAL_PATCH]->(dest:Pod)",
5456
# Subject has permission to read authentication token for Service Account
55-
"GET_AUTHENTICATION_TOKEN_FOR": """
57+
Relationship.GET_AUTHENTICATION_TOKEN_FOR: """
5658
MATCH (src)-[:GRANTS_GET|GRANTS_LIST|GRANTS_WATCH]->(secret:Secret)-[:AUTHENTICATION_TOKEN_FOR]->(dest:ServiceAccount)
5759
""",
5860
# Subject -> Secret
59-
"ACCESS_SECRET": "MATCH (src)-[:GRANTS_GET|GRANTS_LIST|GRANTS_WATCH]->(dest:Secret)",
61+
Relationship.ACCESS_SECRET: "MATCH (src)-[:GRANTS_GET|GRANTS_LIST|GRANTS_WATCH]->(dest:Secret)",
6062
# Generate service account token
61-
"GENERATE_TOKEN": "MATCH (src)-[:GRANTS_TOKEN_CREATE]->(dest:ServiceAccount)",
63+
Relationship.GENERATE_TOKEN: "MATCH (src)-[:GRANTS_TOKEN_CREATE]->(dest:ServiceAccount)",
6264
# RBAC escalate verb to change a role to be more permissive
63-
"RBAC_ESCALATE_TO": [
65+
Relationship.RBAC_ESCALATE_TO: [
6466
# RoleBindings
6567
"""
6668
MATCH (src:RoleBinding)-[:GRANTS_ESCALATE]->(role)-[:WITHIN_NAMESPACE]->(:Namespace)<-[:WITHIN_NAMESPACE]-(dest)
@@ -73,39 +75,39 @@ def workload_query(
7375
""",
7476
],
7577
# Subject -> User / Group / ServiceAccount
76-
"GENERATE_CLIENT_CERTIFICATE": """
78+
Relationship.GENERATE_CLIENT_CERTIFICATE: """
7779
MATCH (src)-[:GRANTS_CERTIFICATESIGNINGREQUESTS_CREATE]->(cluster:Cluster), (dest)
7880
WHERE (src)-[:HAS_CSR_APPROVAL]->(cluster) AND (src)-[:GRANTS_APPROVE]->(:Signer {
7981
name: "kubernetes.io/kube-apiserver-client"
8082
}) AND (dest:User OR dest:Group OR dest:ServiceAccount)
8183
""",
8284
# Impersonate
83-
"CAN_IMPERSONATE": "MATCH (src)-[:GRANTS_IMPERSONATE]->(dest)",
85+
Relationship.CAN_IMPERSONATE: "MATCH (src)-[:GRANTS_IMPERSONATE]->(dest)",
8486
# Pod breakout
85-
"IS_PRIVILEGED": "MATCH (src:Pod {privileged: true})<-[:HOSTS_POD]-(dest:Node)",
86-
"CAN_CGROUP_BREAKOUT": 'MATCH (src:Pod)<-[:HOSTS_POD]-(dest:Node) WHERE "SYS_ADMIN" in src.capabilities',
87-
"CAN_LOAD_KERNEL_MODULES": 'MATCH (src:Pod)<-[:HOSTS_POD]-(dest:Node) WHERE "SYS_MODULE" in src.capabilities',
88-
"CAN_ACCESS_DANGEROUS_HOST_PATH": "MATCH (src:Pod {dangerous_host_path: true})<-[:HOSTS_POD]-(dest:Node)",
89-
"CAN_NSENTER_HOST": 'MATCH (src:Pod {hostPID: true})<-[:HOSTS_POD]-(dest:Node) WHERE all(x in ["SYS_ADMIN", "SYS_PTRACE"] WHERE x in src.capabilities)',
90-
"CAN_ACCESS_HOST_FD": 'MATCH (src:Pod)<-[:HOSTS_POD]-(dest:Node) WHERE "DAC_READ_SEARCH" in src.capabilities',
87+
Relationship.IS_PRIVILEGED: "MATCH (src:Pod {privileged: true})<-[:HOSTS_POD]-(dest:Node)",
88+
Relationship.CAN_CGROUP_BREAKOUT: 'MATCH (src:Pod)<-[:HOSTS_POD]-(dest:Node) WHERE "SYS_ADMIN" in src.capabilities',
89+
Relationship.CAN_LOAD_KERNEL_MODULES: 'MATCH (src:Pod)<-[:HOSTS_POD]-(dest:Node) WHERE "SYS_MODULE" in src.capabilities',
90+
Relationship.CAN_ACCESS_DANGEROUS_HOST_PATH: "MATCH (src:Pod {dangerous_host_path: true})<-[:HOSTS_POD]-(dest:Node)",
91+
Relationship.CAN_NSENTER_HOST: 'MATCH (src:Pod {hostPID: true})<-[:HOSTS_POD]-(dest:Node) WHERE all(x in ["SYS_ADMIN", "SYS_PTRACE"] WHERE x in src.capabilities)',
92+
Relationship.CAN_ACCESS_HOST_FD: 'MATCH (src:Pod)<-[:HOSTS_POD]-(dest:Node) WHERE "DAC_READ_SEARCH" in src.capabilities',
9193
# Can jump to pods running on node
92-
"ACCESS_POD": "MATCH (src:Node)-[:HOSTS_POD]->(dest:Pod)",
94+
Relationship.ACCESS_POD: "MATCH (src:Node)-[:HOSTS_POD]->(dest:Pod)",
9395
# Can exec into pods on a node
94-
"CAN_EXEC_THROUGH_KUBELET": "MATCH (src)-[:GRANTS_PROXY_CREATE]->(:Node)-[:HOSTS_POD]->(dest:Pod)",
96+
Relationship.CAN_EXEC_THROUGH_KUBELET: "MATCH (src)-[:GRANTS_PROXY_CREATE]->(:Node)-[:HOSTS_POD]->(dest:Pod)",
9597
# Can update aws-auth ConfigMap
96-
"UPDATE_AWS_AUTH": """
98+
Relationship.UPDATE_AWS_AUTH: """
9799
MATCH (src)-[:GRANTS_PATCH|GRANTS_UPDATE]->(:ConfigMap {
98100
name: 'aws-auth', namespace: 'kube-system'
99101
}), (dest:Group {
100102
name: 'system:masters'
101103
})
102104
""",
103-
"AZURE_POD_IDENTITY_EXCEPTION": [
105+
Relationship.AZURE_POD_IDENTITY_EXCEPTION: [
104106
# Create workload based of existing APIE
105107
f"""
106108
MATCH (src)-[:GRANTS_GET|GRANTS_LIST|GRANTS_WATCH]->(azexc:AzurePodIdentityException)-[:WITHIN_NAMESPACE]->(ns:Namespace), (dest:ClusterRoleBinding)
107109
WHERE (dest.name = 'aks-cluster-admin-binding' OR dest.name = 'aks-cluster-admin-binding-aad') AND (EXISTS {{
108-
MATCH (src)-[:{create_workload_query()}|GRANTS_POD_CREATE]->(ns)
110+
MATCH (src)-[:{create_workload_query()}|GRANTS_PODS_CREATE]->(ns)
109111
}} OR EXISTS {{
110112
MATCH (src)-[:GRANTS_PATCH|GRANTS_UPDATE]->(workload)-[:WITHIN_NAMESPACE]->(ns)
111113
WHERE {workload_query()}

icekube/models/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import traceback
66
from typing import Any, Dict, List, Optional, Tuple, Type, Union
77

8+
from icekube.relationships import Relationship
89
from icekube.utils import to_camel_case
910
from kubernetes import client
1011
from pydantic import BaseModel, Field, root_validator
@@ -214,7 +215,7 @@ def relationships(
214215
relationships += [
215216
(
216217
self,
217-
"WITHIN_NAMESPACE",
218+
Relationship.WITHIN_NAMESPACE,
218219
ns,
219220
),
220221
]

icekube/models/clusterrolebinding.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from icekube.models.serviceaccount import ServiceAccount
1111
from icekube.models.user import User
1212
from icekube.neo4j import find_or_mock, get_cluster_object, mock
13+
from icekube.relationships import Relationship
1314
from pydantic import root_validator
1415
from pydantic.fields import Field
1516

@@ -82,14 +83,16 @@ def relationships(
8283
initial: bool = True,
8384
) -> List[RELATIONSHIP]:
8485
relationships = super().relationships()
85-
relationships += [(self, "GRANTS_PERMISSION", self.role)]
86-
relationships += [(subject, "BOUND_TO", self) for subject in self.subjects]
86+
relationships += [(self, Relationship.GRANTS_PERMISSION, self.role)]
87+
relationships += [
88+
(subject, Relationship.BOUND_TO, self) for subject in self.subjects
89+
]
8790

8891
if not initial:
8992
for role_rule in self.role.rules:
9093
if role_rule.contains_csr_approval:
9194
relationships.append(
92-
(self, "HAS_CSR_APPROVAL", get_cluster_object()),
95+
(self, Relationship.HAS_CSR_APPROVAL, get_cluster_object()),
9396
)
9497
for relationship, resource in role_rule.affected_resource_query():
9598
relationships.append((self, relationship, resource))

icekube/models/pod.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from icekube.models.secret import Secret
1111
from icekube.models.serviceaccount import ServiceAccount
1212
from icekube.neo4j import mock
13+
from icekube.relationships import Relationship
1314
from pydantic import root_validator
1415

1516
CAPABILITIES = [
@@ -249,14 +250,14 @@ def relationships(
249250
relationships = super().relationships()
250251

251252
if self.service_account:
252-
relationships += [(self, "USES_ACCOUNT", self.service_account)]
253+
relationships += [(self, Relationship.USES_ACCOUNT, self.service_account)]
253254
if self.node:
254-
relationships += [(self.node, "HOSTS_POD", self)]
255+
relationships += [(self.node, Relationship.HOSTS_POD, self)]
255256
for secret in self.mounted_secrets:
256257
relationships += [
257258
(
258259
self,
259-
"MOUNTS_SECRET",
260+
Relationship.MOUNTS_SECRET,
260261
mock(Secret, namespace=cast(str, self.namespace), name=secret),
261262
),
262263
]

icekube/models/policyrule.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from fnmatch import fnmatch
44
from typing import Dict, Iterator, List, Optional, Tuple, Union
55

6+
from icekube.relationships import Relationship
67
from pydantic import BaseModel
78
from pydantic.fields import Field
89

@@ -91,23 +92,22 @@ def affected_resource_query(
9192
else:
9293
query_filter = {"kind": "Cluster"}
9394
yield (
94-
f"GRANTS_{resource}_CREATE".upper().replace("-", "_"),
95+
Relationship.generate_grant("CREATE", resource),
9596
generate_query(query_filter),
9697
)
9798
query_filter = {"kind": "Namespace"}
9899
yield (
99-
f"GRANTS_{resource}_CREATE".upper().replace("-", "_"),
100+
Relationship.generate_grant("CREATE", resource),
100101
generate_query(query_filter),
101102
)
102103
valid_verbs.remove("create")
103104

104105
if not valid_verbs:
105106
continue
106107

107-
if sub_resource is None:
108-
tags = [f"GRANTS_{verb}".upper() for verb in valid_verbs]
109-
else:
110-
tags = [f"GRANTS_{sub_resource}_{verb}".upper() for verb in valid_verbs]
108+
tags = [
109+
Relationship.generate_grant(verb, sub_resource) for verb in valid_verbs
110+
]
111111

112112
if not self.resourceNames:
113113
yield (tags, generate_query(find_filter))

icekube/models/rolebinding.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from icekube.models.role import Role
1111
from icekube.models.serviceaccount import ServiceAccount
1212
from icekube.models.user import User
13+
from icekube.relationships import Relationship
1314
from pydantic import root_validator
1415
from pydantic.fields import Field
1516

@@ -39,8 +40,10 @@ def relationships(
3940
initial: bool = True,
4041
) -> List[RELATIONSHIP]:
4142
relationships = super().relationships()
42-
relationships += [(self, "GRANTS_PERMISSION", self.role)]
43-
relationships += [(subject, "BOUND_TO", self) for subject in self.subjects]
43+
relationships += [(self, Relationship.GRANTS_PERMISSION, self.role)]
44+
relationships += [
45+
(subject, Relationship.BOUND_TO, self) for subject in self.subjects
46+
]
4447

4548
if not initial:
4649
for role_rule in self.role.rules:

icekube/models/secret.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from icekube.models.base import RELATIONSHIP, Resource
77
from icekube.neo4j import mock
8+
from icekube.relationships import Relationship
89
from pydantic import root_validator
910

1011

@@ -52,7 +53,7 @@ def relationships(self, initial: bool = True) -> List[RELATIONSHIP]:
5253
relationships.append(
5354
(
5455
self,
55-
"AUTHENTICATION_TOKEN_FOR",
56+
Relationship.AUTHENTICATION_TOKEN_FOR,
5657
account,
5758
),
5859
)

icekube/models/serviceaccount.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from icekube.models.base import RELATIONSHIP, Resource
77
from icekube.models.secret import Secret
88
from icekube.neo4j import mock
9+
from icekube.relationships import Relationship
910
from pydantic import root_validator
1011
from pydantic.fields import Field
1112

@@ -39,5 +40,7 @@ def relationships(
3940
initial: bool = True,
4041
) -> List[RELATIONSHIP]:
4142
relationships = super().relationships()
42-
relationships += [(x, "AUTHENTICATION_TOKEN_FOR", self) for x in self.secrets]
43+
relationships += [
44+
(x, Relationship.AUTHENTICATION_TOKEN_FOR, self) for x in self.secrets
45+
]
4346
return relationships

icekube/relationships.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from typing import ClassVar, Optional
2+
3+
4+
class Relationship:
5+
"""Consolidates the various relationship types into a single class.
6+
7+
This allows for better tracking of where we assign each relationship
8+
across the codebase.
9+
10+
Relationships in the order (ObjectOne, RELATIONSHIP, ObjectTwo) are
11+
in this direction in neo4j: (ObjectOne)-[:RELATIONSHIP]->(ObjectTwo)
12+
"""
13+
14+
HOSTS_POD: ClassVar[str] = "HOSTS_POD"
15+
16+
AUTHENTICATION_TOKEN_FOR: ClassVar[str] = "AUTHENTICATION_TOKEN_FOR"
17+
GET_AUTHENTICATION_TOKEN_FOR: ClassVar[str] = "GET_AUTHENTICATION_TOKEN_FOR"
18+
19+
WITHIN_NAMESPACE: ClassVar[str] = "WITHIN_NAMESPACE"
20+
21+
GRANTS_PODS_CREATE: ClassVar[str] = "GRANTS_PODS_CREATE"
22+
GRANTS_REPLICATIONCONTROLLERS_CREATE: ClassVar[
23+
str
24+
] = "GRANTS_REPLICATIONCONTROLLERS_CREATE"
25+
GRANTS_DAEMONSETS_CREATE: ClassVar[str] = "GRANTS_DAEMONSETS_CREATE"
26+
GRANTS_DEPLOYMENTS_CREATE: ClassVar[str] = "GRANTS_DEPLOYMENTS_CREATE"
27+
GRANTS_REPLICASETS_CREATE: ClassVar[str] = "GRANTS_REPLICASETS_CREATE"
28+
GRANTS_STATEFULSETS_CREATE: ClassVar[str] = "GRANTS_STATEFULSETS_CREATE"
29+
GRANTS_CRONJOBS_CREATE: ClassVar[str] = "GRANTS_CRONJOBS_CREATE"
30+
GRANTS_JOBS_CREATE: ClassVar[str] = "GRANTS_JOBS_CREATE"
31+
32+
GRANTS_AZUREPODIDENTITYEXCEPTIONS_CREATE: ClassVar[
33+
str
34+
] = "GRANTS_AZUREPODIDENTITYEXCEPTIONS_CREATE"
35+
GRANTS_CERTIFICATESIGNINGREQUESTS_CREATE: ClassVar[
36+
str
37+
] = "GRANTS_CERTIFICATESIGNINGREQUESTS_CREATE"
38+
GRANTS_PROXY_CREATE: ClassVar[str] = "GRANTS_PROXY_CREATE"
39+
40+
GRANTS_GET: ClassVar[str] = "GRANTS_GET"
41+
GRANTS_LIST: ClassVar[str] = "GRANTS_LIST"
42+
GRANTS_UPDATE: ClassVar[str] = "GRANTS_UPDATE"
43+
GRANTS_WATCH: ClassVar[str] = "GRANTS_WATCH"
44+
GRANTS_PATCH: ClassVar[str] = "GRANTS_PATCH"
45+
GRANTS_APPROVE: ClassVar[str] = "GRANTS_APPROVE"
46+
GRANTS_PERMISSION: ClassVar[str] = "GRANTS_PERMISSION"
47+
48+
GRANTS_ESCALATE: ClassVar[str] = "GRANTS_ESCALATE"
49+
GRANTS_IMPERSONATE: ClassVar[str] = "GRANTS_IMPERSONATE"
50+
GRANTS_TOKEN_CREATE: ClassVar[str] = "GRANTS_TOKEN_CREATE"
51+
GRANTS_EPHEMERAL_PATCH: ClassVar[str] = "GRANTS_EPHEMERAL_PATCH"
52+
53+
BOUND_TO: ClassVar[str] = "BOUND_TO"
54+
USES_ACCOUNT: ClassVar[str] = "USES_ACCOUNT"
55+
MOUNTS_SECRET: ClassVar[str] = "MOUNTS_SECRET"
56+
CREATE_POD_WITH_SA: ClassVar[str] = "CREATE_POD_WITH_SA"
57+
UPDATE_WORKLOAD_WITH_SA: ClassVar[str] = "UPDATE_WORKLOAD_WITH_SA"
58+
59+
EXEC_INTO: ClassVar[str] = "EXEC_INTO"
60+
REPLACE_IMAGE: ClassVar[str] = "REPLACE_IMAGE"
61+
DEBUG_POD: ClassVar[str] = "DEBUG_POD"
62+
63+
ACCESS_SECRET: ClassVar[str] = "ACCESS_SECRET"
64+
GENERATE_TOKEN: ClassVar[str] = "GENERATE_TOKEN"
65+
RBAC_ESCALATE_TO: ClassVar[str] = "RBAC_ESCALATE_TO"
66+
67+
GENERATE_CLIENT_CERTIFICATE: ClassVar[str] = "GENERATE_CLIENT_CERTIFICATE"
68+
HAS_CSR_APPROVAL: ClassVar[str] = "HAS_CSR_APPROVAL"
69+
70+
CAN_IMPERSONATE: ClassVar[str] = "CAN_IMPERSONATE"
71+
72+
IS_PRIVILEGED: ClassVar[str] = "IS_PRIVILEGED"
73+
CAN_CGROUP_BREAKOUT: ClassVar[str] = "CAN_CGROUP_BREAKOUT"
74+
CAN_LOAD_KERNEL_MODULES: ClassVar[str] = "CAN_LOAD_KERNEL_MODULES"
75+
CAN_ACCESS_DANGEROUS_HOST_PATH: ClassVar[str] = "CAN_ACCESS_DANGEROUS_HOST_PATH"
76+
CAN_NSENTER_HOST: ClassVar[str] = "CAN_NSENTER_HOST"
77+
CAN_ACCESS_HOST_FD: ClassVar[str] = "CAN_ACCESS_HOST_FD"
78+
CAN_EXEC_THROUGH_KUBELET: ClassVar[str] = "CAN_EXEC_THROUGH_KUBELET"
79+
80+
ACCESS_POD: ClassVar[str] = "ACCESS_POD"
81+
UPDATE_AWS_AUTH: ClassVar[str] = "UPDATE_AWS_AUTH"
82+
AZURE_POD_IDENTITY_EXCEPTION: ClassVar[str] = "AZURE_POD_IDENTITY_EXCEPTION"
83+
84+
# Current resource defines the spec/creation of the subresource
85+
DEFINES: ClassVar[str] = "DEFINES"
86+
# Defines a reference to another object (e.g. Pod -> ServiceAccount)
87+
REFERENCES: ClassVar[str] = "REFERENCES"
88+
# Directly consumes a resource (e.g. PersistentVolumeClaim -> PersistentVolume)
89+
CONSUMES: ClassVar[str] = "CONSUMES"
90+
# Indirectly consumes a resource, without an exclusive relationship to the refering
91+
# node (e.g. PersistentVolume -> StorageClass)
92+
USES: ClassVar[str] = "USES"
93+
# Defines ownership of a resource (e.g. Deployment-[:OWNS]->ReplicaSet)
94+
OWNS: ClassVar[str] = "OWNS"
95+
96+
@staticmethod
97+
def generate_grant(verb: str, sub_resource: Optional[str]) -> str:
98+
if sub_resource is None:
99+
return f"GRANTS_{verb.upper()}".replace("-", "_")
100+
101+
return f"GRANTS_{sub_resource}_{verb}".upper().replace("-", "_")

0 commit comments

Comments
 (0)