Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial version for creating secrets #24

Merged
merged 1 commit into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/CREATE_SECRET_WITH_TOKEN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# CREATE_SECRET_WITH_TOKEN

### Overview

This attack aims to locate subjects which can gain access to a service account token by creating a secret which would be populated by the control plane with the relevant secret. The subject would also need a method to read the newly created secret.

### Description

Secrets can be configured with the type `kubernetes.io/service-account-token` and annotated with `kubernetes.io/service-account.name` which will result in the secret being automatically populated with a token for said service account.

An attacker could leverage this by creating a secret for a service account and use it to request a token for a service account they wish to gain access to. Once created, an attacker would need to read the secret. This could be done in a few different ways such as reading the secret directly or mounting it into a pod and using that to exfiltrate the secret.

### Defense

RBAC permissions regarding creating secrets should be reviewed. Access should be restricted where not needed.

### Cypher Deep-Dive

#### Listing secrets

```cypher
MATCH (src)-[:GRANTS_SECRETS_CREATE]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount) WHERE (src)-[:GRANTS_SECRETS_LIST]->(ns)

```

This query identifies subjects (`src`) that can create secrets within a namespace. It also checks whether the same subject can list secrets within the same namespace. This would allow them to read the secret upon creation.

#### Workload creation

```cypher
MATCH (src)-[:GRANTS_SECRETS_CREATE]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount) WHERE (src)-[:GRANTS_PODS_CREATE|GRANTS_REPLICATIONCONTROLLERS_CREATE|GRANTS_DAEMONSETS_CREATE|GRANTS_DEPLOYMENTS_CREATE|GRANTS_REPLICASETS_CREATE|GRANTS_STATEFULSETS_CREATE|GRANTS_CRONJOBS_CREATE|GRANTS_JOBS_CREATE]->(ns)
```

This query identifies subjects (`src`) that can create secrets within a namespace. It also checks the subject can create a workload within the same namespace which could be configured to mount the newly created secret for exfiltration purposes.

Workload creation includes the following:
- `pods`
- `replicationcontrollers`
- `daemonsets`
- `deployments`
- `replicasets`
- `statefulsets`
- `cornjobs`
- `jobs`
13 changes: 13 additions & 0 deletions icekube/attack_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ def workload_query(
Relationship.ACCESS_SECRET: "MATCH (src)-[:GRANTS_GET|GRANTS_LIST|GRANTS_WATCH]->(dest:Secret)",
# Generate service account token
Relationship.GENERATE_TOKEN: "MATCH (src)-[:GRANTS_TOKEN_CREATE]->(dest:ServiceAccount)",
# Create a long-lived secret for a service account
Relationship.CREATE_SECRET_WITH_TOKEN: [
# Uses a workload to read the generated secret
f"""
MATCH (src)-[:GRANTS_SECRETS_CREATE]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount)
WHERE (src)-[:GRANTS_PODS_CREATE|{create_workload_query()}]->(ns)
""",
# Uses secret list to read the generated secret
"""
MATCH (src)-[:GRANTS_SECRETS_CREATE]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount)
WHERE (src)-[:GRANTS_SECRETS_LIST]->(ns)
""",
],
# RBAC escalate verb to change a role to be more permissive
Relationship.RBAC_ESCALATE_TO: [
# RoleBindings
Expand Down
21 changes: 13 additions & 8 deletions icekube/models/policyrule.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,24 +83,29 @@ def affected_resource_query(
for verb in self.verbs:
valid_verbs.update(fnfilter(api_resource.verbs, verb.lower()))

if "create" in valid_verbs and sub_resource is None:
verbs_for_namespace = set("create list".split()).intersection(valid_verbs)

if verbs_for_namespace and sub_resource is None:
if namespace:
query_filter: Dict[str, Union[str, List[str]]] = {
"kind": "Namespace",
"name": namespace,
}
else:
query_filter = {"apiVersion": "N/A", "kind": "Cluster"}
for verb in verbs_for_namespace:
yield (
Relationship.generate_grant(verb.upper(), resource),
generate_query(query_filter),
)
query_filter = {"kind": "Namespace"}
for verb in verbs_for_namespace:
yield (
Relationship.generate_grant("CREATE", resource),
Relationship.generate_grant(verb.upper(), resource),
generate_query(query_filter),
)
query_filter = {"kind": "Namespace"}
yield (
Relationship.generate_grant("CREATE", resource),
generate_query(query_filter),
)
valid_verbs.remove("create")
if "create" in verbs_for_namespace:
valid_verbs.remove("create")

if not valid_verbs:
continue
Expand Down
1 change: 1 addition & 0 deletions icekube/relationships.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Relationship:

AUTHENTICATION_TOKEN_FOR: ClassVar[str] = "AUTHENTICATION_TOKEN_FOR"
GET_AUTHENTICATION_TOKEN_FOR: ClassVar[str] = "GET_AUTHENTICATION_TOKEN_FOR"
CREATE_SECRET_WITH_TOKEN: ClassVar[str] = "CREATE_SECRET_WITH_TOKEN"

WITHIN_NAMESPACE: ClassVar[str] = "WITHIN_NAMESPACE"

Expand Down
Loading