Enhance actors and adversaries sections in self-assessment - #2
Enhance actors and adversaries sections in self-assessment#2leecalcote wants to merge 4 commits into
Conversation
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>
|
Removed the note about the permissive GraphQL WebSocket origin policy and emphasized the need for restricting allowed origins.
|
evankanderson
left a comment
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
Is this another "fully trusted" actor?
|
|
||
| | 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. | |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
@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. | |
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
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.
| | **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. | |
There was a problem hiding this comment.
This could also attack the provider or adapters, correct? A quick network-level diagram would help call out which components are network-exposed.
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
Is "local filesystem attacker" equivalent to node-level access given that the SQLite datastore is stored in a PVC with read-write-once mount?
There was a problem hiding this comment.
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>
|
1 similar comment
|
|
|
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.