Skip to content

Enhance actors and adversaries sections in self-assessment - #2

Open
leecalcote wants to merge 4 commits into
angellk:meshery-ssafrom
leecalcote:patch-2
Open

Enhance actors and adversaries sections in self-assessment#2
leecalcote wants to merge 4 commits into
angellk:meshery-ssafrom
leecalcote:patch-2

Conversation

@leecalcote

Copy link
Copy Markdown

Expanded the section on actors to include details about system, internal, human, and external actors, along with their functions, security measures, and trust boundaries. Added a new section on assumed adversaries and their potential attempts against the system.

angellk and others added 2 commits August 4, 2026 19:54
Security self-assessment authored by Meshery maintainers following
the TAG Security template. Covers actors, actions, goals, non-goals,
security functions, development pipeline, vulnerability history, and
compliance posture. Required for incubation DD (cncf#1386).

Signed-off-by: Karena Angell <karena.angell@gmail.com>
Expanded the section on actors to include details about system, internal, human, and external actors, along with their functions, security measures, and trust boundaries. Added a new section on assumed adversaries and their potential attempts against the system.

Signed-off-by: Lee Calcote <leecalcote@gmail.com>
@kusari-inspector

Copy link
Copy Markdown

⚠️ Workspace Mapping Required

Hello! We noticed that your GitHub organization is not yet mapped to a Kusari workspace. Kusari Inspector now requires installations to be associated with a Kusari workspace.

⚠️ NOTE: Only the admin who installed the Kusari GitHub App can complete these steps. If the admin is unable to complete these steps, please contact support@kusari.dev

To complete the setup:

  1. Visit https://console.us.kusari.cloud/auth/github and log in via github
  2. If you have only one workspace, it will be automatically selected for you
  3. Once the mapping is complete, return here and create a new comment with: @kusari-inspector re-run

This will trigger the analysis to run again.

For more information, or if you need help, visit https://github.com/kusaridev/community/discussions

Removed the note about the permissive GraphQL WebSocket origin policy and emphasized the need for restricting allowed origins.
@kusari-inspector

Copy link
Copy Markdown

⚠️ Workspace Mapping Required

Hello! We noticed that your GitHub organization is not yet mapped to a Kusari workspace. Kusari Inspector now requires installations to be associated with a Kusari workspace.

⚠️ NOTE: Only the admin who installed the Kusari GitHub App can complete these steps. If the admin is unable to complete these steps, please contact support@kusari.dev

To complete the setup:

  1. Visit https://console.us.kusari.cloud/auth/github and log in via github
  2. If you have only one workspace, it will be automatically selected for you
  3. Once the mapping is complete, return here and create a new comment with: @kusari-inspector re-run

This will trigger the analysis to run again.

For more information, or if you need help, visit https://github.com/kusaridev/community/discussions

@evankanderson evankanderson left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This significantly helps me understand the attack surface to be secured, and generally looks good. My questions are mostly clarification, but not required.

|---|---|---|---|
| **Kubernetes cluster** | System | The managed system. Accepts API calls from Meshery Server, hosts the Operator, MeshSync and the Broker, and returns resource state. | Not a component Meshery ships, but squarely inside the trust boundary: the server holds cluster-admin-equivalent RBAC by default, so the cluster grants Meshery effectively unrestricted authority. |
| **Cluster administrator** | Human | Installs Meshery, grants its ServiceAccount RBAC, decides whether the Broker is exposed and whether broker auth and TLS are enabled, terminates TLS at the ingress. | Holds authority Meshery cannot constrain, and owns every control this document describes as operator-supplied. Most default-off protections are theirs to enable. |
| **Meshery operator (deployer)** | Human | Runs the server, sets `PROVIDER`, `ADAPTER_URLS` and provider base URLs; owns the host filesystem holding the SQLite datastore. | Fully trusted. Can read the datastore and any credential in it directly, since there is no application-layer encryption at rest. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the "fully trusted" clause also applies to the Kubernetes cluster and the cluster administrator, no?

I'd tend to prefer using common phrasing for these actors.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Common phrasing - I hear that. It makes much sense.

| **Cluster administrator** | Human | Installs Meshery, grants its ServiceAccount RBAC, decides whether the Broker is exposed and whether broker auth and TLS are enabled, terminates TLS at the ingress. | Holds authority Meshery cannot constrain, and owns every control this document describes as operator-supplied. Most default-off protections are theirs to enable. |
| **Meshery operator (deployer)** | Human | Runs the server, sets `PROVIDER`, `ADAPTER_URLS` and provider base URLs; owns the host filesystem holding the SQLite datastore. | Fully trusted. Can read the datastore and any credential in it directly, since there is no application-layer encryption at rest. |
| **End user (authenticated)** | Human | Registers clusters by supplying kubeconfigs, composes and deploys designs, runs benchmarks, invokes adapter operations. | With a remote provider, identity is provider-verified and capability keys apply. With the local provider there is exactly one anonymous, all-empowered user by design. |
| **Organization or provider admin** | Human | Assigns roles and keychains at the remote provider, determining what each principal may do. | Authority lives with the provider, not with Meshery. Meshery enforces the result; it does not decide it. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this another "fully trusted" actor?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spot on. Yes.


| Adversary | What they could attempt | What limits them today |
|---|---|---|
| **Unauthenticated network attacker** | Reach the server's HTTP port or the Broker's exposed service and read or inject cluster state. | Server API requires a verified JWT session under a remote provider. However, the server speaks plaintext HTTP in-process and relies on an ingress for TLS, and the Broker supports authentication and TLS with **both unset by default** while typically exposed via LoadBalancer or NodePort. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could NetworkPolicy also be used to protect the server? Is it used that way today? (I realize that NetworkPolicy can be a bit of a can of worms given the flexibility of the Kubernetes networking layer.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evankanderson - yes to the first, no to the second, and the second is the more useful answer.

Meshery ships no NetworkPolicy in either chart. I checked both install/kubernetes/helm/meshery and install/kubernetes/helm/meshery-operator; the only NetworkPolicy strings anywhere in the tree are model definitions for the resource type Meshery manages and the dashboard columns that render them. Nothing constrains who may reach Meshery itself.

That matters more than it might read, because the defaults lean open. The server Service is type: LoadBalancer on 9081 (values.yaml), and the Broker's client Service keeps the operator's historical LoadBalancer default when its type is unset. So out of the box both are reachable from outside the cluster, and inside the cluster any pod can reach any Meshery port.

Two policies would carry real weight and neither is exotic:

  • Server ingress admitted only from the ingress-controller namespace, since the server has no in-process TLS and expects to sit behind a proxy anyway.
  • Broker ingress admitted only from Meshery Server and MeshSync, which is the entire legitimate client set.

You are right that it is a can of worms - the policies are CNI-dependent and a wrong one silently breaks discovery rather than failing loudly, which is why we have not shipped defaults. Publishing them as reference configuration in the hardening guide, rather than as chart defaults, looks like the right shape. The document currently names NetworkPolicy only in Non-goals and in the "Security relevant" list; I have added it to the adversary row itself so the missing control sits next to the exposure it would address.

|---|---|---|
| **Unauthenticated network attacker** | Reach the server's HTTP port or the Broker's exposed service and read or inject cluster state. | Server API requires a verified JWT session under a remote provider. However, the server speaks plaintext HTTP in-process and relies on an ingress for TLS, and the Broker supports authentication and TLS with **both unset by default** while typically exposed via LoadBalancer or NodePort. |
| **Authenticated but unauthorized user** | Invoke a management action their role does not grant. | Capability keys mapped to roles, adjudicated by the provider and enforced by the server. See Security functions for the enforcement path and its coverage shape. |
| **Compromised or malicious adapter** | Impersonate an adapter, or intercept and modify operations dispatched to one. | Adapters are **disabled by default** (opt-in via `ADAPTER_URLS`). Once enabled, the server-adapter gRPC channel is **plaintext with no mutual TLS**, and adapters reuse the server's ServiceAccount. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are cluster-local DNS and k8s CNI integrity potential defenses for on-cluster adapters?

|---|---|---|
| **Unauthenticated network attacker** | Reach the server's HTTP port or the Broker's exposed service and read or inject cluster state. | Server API requires a verified JWT session under a remote provider. However, the server speaks plaintext HTTP in-process and relies on an ingress for TLS, and the Broker supports authentication and TLS with **both unset by default** while typically exposed via LoadBalancer or NodePort. |
| **Authenticated but unauthorized user** | Invoke a management action their role does not grant. | Capability keys mapped to roles, adjudicated by the provider and enforced by the server. See Security functions for the enforcement path and its coverage shape. |
| **Compromised or malicious adapter** | Impersonate an adapter, or intercept and modify operations dispatched to one. | Adapters are **disabled by default** (opt-in via `ADAPTER_URLS`). Once enabled, the server-adapter gRPC channel is **plaintext with no mutual TLS**, and adapters reuse the server's ServiceAccount. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mentions no mutual TLS -- could an attacker impersonate the server to call the adapter and modify cluster state? That seems like a more serious attack than attempting to impersonate the adapter to the server.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right.

| **Unauthenticated network attacker** | Reach the server's HTTP port or the Broker's exposed service and read or inject cluster state. | Server API requires a verified JWT session under a remote provider. However, the server speaks plaintext HTTP in-process and relies on an ingress for TLS, and the Broker supports authentication and TLS with **both unset by default** while typically exposed via LoadBalancer or NodePort. |
| **Authenticated but unauthorized user** | Invoke a management action their role does not grant. | Capability keys mapped to roles, adjudicated by the provider and enforced by the server. See Security functions for the enforcement path and its coverage shape. |
| **Compromised or malicious adapter** | Impersonate an adapter, or intercept and modify operations dispatched to one. | Adapters are **disabled by default** (opt-in via `ADAPTER_URLS`). Once enabled, the server-adapter gRPC channel is **plaintext with no mutual TLS**, and adapters reuse the server's ServiceAccount. |
| **Hostile in-cluster workload** | Reach the Broker from inside the cluster and publish forged resource events, poisoning the server's view of the cluster. | Bounded only by cluster network policy, which Meshery does not configure, plus broker authentication and TLS if the operator enabled them. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also attack the provider or adapters, correct? A quick network-level diagram would help call out which components are network-exposed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct on all three, and the row was too narrow in naming only the Broker.

A hostile workload on the pod network can reach the Broker, any enabled adapter's gRPC port (which, per the row above, authenticates no caller), and the server's own HTTP port. The remote provider is the one genuine exception in degree rather than in kind: it is reached outbound over HTTPS to an external endpoint, so it is not sitting on the pod network waiting to be called - but a workload able to intercept egress or subvert DNS resolution could still attempt it.

| **Authenticated but unauthorized user** | Invoke a management action their role does not grant. | Capability keys mapped to roles, adjudicated by the provider and enforced by the server. See Security functions for the enforcement path and its coverage shape. |
| **Compromised or malicious adapter** | Impersonate an adapter, or intercept and modify operations dispatched to one. | Adapters are **disabled by default** (opt-in via `ADAPTER_URLS`). Once enabled, the server-adapter gRPC channel is **plaintext with no mutual TLS**, and adapters reuse the server's ServiceAccount. |
| **Hostile in-cluster workload** | Reach the Broker from inside the cluster and publish forged resource events, poisoning the server's view of the cluster. | Bounded only by cluster network policy, which Meshery does not configure, plus broker authentication and TLS if the operator enabled them. |
| **Local filesystem attacker** | Read cluster credentials, sessions, or the CLI token from disk. | **Filesystem permissions only.** The SQLite datastore has no application-layer encryption at rest, and `mesheryctl`'s token is stored at `~/.meshery/auth.json` in plaintext. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "local filesystem attacker" equivalent to node-level access given that the SQLite datastore is stored in a PVC with read-write-once mount?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The premise does not hold for the shipped chart.

There is no PVC. install/kubernetes/helm/meshery/templates/deployment.yaml declares no volumes and no volumeMounts at all, so in-cluster the SQLite file lives in the container's writable layer. Two consequences follow. Reaching it requires node-level access to the container filesystem, pods/exec in the namespace, or a container escape - so for the in-cluster case your instinct is right even though the mechanism is different. And it does not survive a pod restart, which is consistent with the document's framing of the datastore as a cache of authoritative cluster state.

Under mesheryctl system start and the Docker Desktop extension, the datastore is an ordinary host path under ~/.meshery, next to ~/.meshery/auth.json, and an ordinary local user with read access is enough. These deployments are on individual desktops, so the local user is the only one who can reach it. The document's phrasing of "local filesystem attacker" is accurate for that case, but it is not accurate for the in-cluster case.

So the accurate answer is "it depends on the deployment mode," which is worth stating outright rather than leaving a reader to assume the stricter case. I have renamed the row and split the reachability by mode.

… model

Reviewer questions on cncf#2264 and on this PR surfaced three things the
assessment either understated or did not say at all.

Authorization model. The Critical security-function bullet now states that the
capability check is not part of the provider > auth > session-injector
middleware chain, and points at the per-operation enforcement in
server/models/remote_provider.go across 91 gated operations refusing with 403.
It adds the enforce-versus-adjudicate distinction, so the absence of an in-tree
negative test reads as a consequence of the provider boundary rather than as a
gap, and it introduces the access-check versus control-check vocabulary the
document did not carry.

Actors and adversaries. Every row in the human and external actors table now
opens with one common trust verdict. The adapter row is rebuilt around the
direction a reviewer correctly identified as the more serious one: the
server-adapter gRPC channel uses insecure.NewCredentials(), so it is plaintext
with no TLS in either direction, not merely without mutual TLS, and an adapter
acts on the cluster with a cluster-admin-equivalent ServiceAccount. The
unauthenticated-attacker row names the LoadBalancer defaults and the absent
NetworkPolicy. The hostile-workload row names all three reachable surfaces
rather than only the broker. The storage row replaces the assumption of a
persistent volume, which the chart does not declare, with the actual
reachability in the in-cluster and desktop deployment modes.

Broker authentication. The document said broker authentication is off by
default in three places. Meshery Operator has generated the meshery-nats-auth
Secret before the NATS pod starts since 2026-06-30, which post-dates this
assessment's stamp. Broker TLS is genuinely still absent, so only the
authentication half is corrected.

Signed-off-by: Lee Calcote <leecalcote@gmail.com>
@kusari-inspector

Copy link
Copy Markdown

⚠️ Workspace Mapping Required

Hello! We noticed that your GitHub organization is not yet mapped to a Kusari workspace. Kusari Inspector now requires installations to be associated with a Kusari workspace.

⚠️ NOTE: Only the admin who installed the Kusari GitHub App can complete these steps. If the admin is unable to complete these steps, please contact support@kusari.dev

To complete the setup:

  1. Visit https://console.us.kusari.cloud/auth/github and log in via github
  2. If you have only one workspace, it will be automatically selected for you
  3. Once the mapping is complete, return here and create a new comment with: @kusari-inspector re-run

This will trigger the analysis to run again.

For more information, or if you need help, visit https://github.com/kusaridev/community/discussions

1 similar comment
@kusari-inspector

Copy link
Copy Markdown

⚠️ Workspace Mapping Required

Hello! We noticed that your GitHub organization is not yet mapped to a Kusari workspace. Kusari Inspector now requires installations to be associated with a Kusari workspace.

⚠️ NOTE: Only the admin who installed the Kusari GitHub App can complete these steps. If the admin is unable to complete these steps, please contact support@kusari.dev

To complete the setup:

  1. Visit https://console.us.kusari.cloud/auth/github and log in via github
  2. If you have only one workspace, it will be automatically selected for you
  3. Once the mapping is complete, return here and create a new comment with: @kusari-inspector re-run

This will trigger the analysis to run again.

For more information, or if you need help, visit https://github.com/kusaridev/community/discussions

@github-actions

Copy link
Copy Markdown

⚠️ README Files Should Be Updated via tags.yaml

The following TAG or TOC subproject README files have been directly edited:

  • tags/tag-operational-resilience/README.md

These README files are auto-generated from the tags.yaml file using the generator script. Please update the tags.yaml file instead of editing the README files directly.

For more information on how to update these files, please see the generator README.

Once you update tags.yaml, the README files will be automatically regenerated by the existing automation workflow.

@kusari-inspector

Copy link
Copy Markdown

⚠️ Workspace Mapping Required

Hello! We noticed that your GitHub organization is not yet mapped to a Kusari workspace. Kusari Inspector now requires installations to be associated with a Kusari workspace.

⚠️ NOTE: Only the admin who installed the Kusari GitHub App can complete these steps. If the admin is unable to complete these steps, please contact support@kusari.dev

To complete the setup:

  1. Visit https://console.us.kusari.cloud/auth/github and log in via github
  2. If you have only one workspace, it will be automatically selected for you
  3. Once the mapping is complete, return here and create a new comment with: @kusari-inspector re-run

This will trigger the analysis to run again.

For more information, or if you need help, visit https://github.com/kusaridev/community/discussions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants