Report privately via GitHub security advisories, or by email to security@alethic.solutions. Please do not open a public issue for a vulnerability.
Include what you did, what happened, and what you expected. A cluster-role dump
and the Weave that triggered it are usually enough to reproduce.
Weft exists to narrow a privilege boundary, so it is worth being explicit about where that boundary is and what it does not cover.
A composition author gaining privileges they do not have. Every read and
every write is performed as spec.serviceAccountName, impersonated. The
controller holds no write access of its own to anything a composition might
generate. A Weave cannot create a resource its ServiceAccount could not have
created by hand, and cannot read a resource its ServiceAccount could not have
read. This holds even when the controller itself runs as cluster-admin, and is
verified by pointing a Weave at a ServiceAccount with no permissions and
observing that nothing is created.
Reading a value the author cannot see. Creation authority is not read
authority, and the whole data path is "read a value out of one object and write
it into a sibling". An unimpersonated read on that path would leak the contents
of an object the author cannot open, so there is no unimpersonated read: the
kube package offers no way to make one, and the informer caches are
metadata-only so there is structurally no cached field value to read by mistake.
An untrusted program affecting the controller. Programs run in a bounded
Starlark interpreter: no load(), no recursion, no clock, no randomness, no
I/O, an execution-step budget, and caps on result size and nesting depth. Every
limit fails as a condition on the Weave rather than as a wedged controller.
Escaping the namespace. Outputs must live in the Weave's own namespace,
cannot be cluster-scoped, and cannot set their own owner references. These are
rejected rather than corrected.
Taking over a resource by naming it. Server-side apply is create-or-update,
so a program naming an object that already exists would otherwise adopt it
silently — and deleting the Weave would then delete something it never made.
Weft refuses instead. Handing an object over requires an annotation on that
object naming the Weave, so consent comes from whoever holds the resource
rather than from whoever wrote the program. See
docs/ownership.md.
A ServiceAccount that is over-granted. Weft faithfully performs whatever its
ServiceAccount may do. Binding cluster-admin to a composer ServiceAccount hands
that to anybody who can write a Weave in that namespace. The security of a
Weave is exactly the security of the ServiceAccount it names, which is the
point, and means the grant is where the review should happen.
Resource exhaustion by volume. Limits bound one evaluation, not the number
of Weave objects a user creates. A namespace user who can create Weave
objects can create many. Use quotas.
Anything the author of a resource being read writes into it. What a program
reads is data, and it may write that data into an output. A user who can edit a
ConfigMap a Weave reads can influence what that Weave produces, within the
bounds of what the ServiceAccount may create.
Reading Secret contents into a less-protected object. If the ServiceAccount can read a Secret and create a ConfigMap, a program can copy one into the other. That is a property of the grant, not of Weft, and it is another reason to scope the ServiceAccount rather than reuse a broad one.
The full grant is in charts/weft/templates/rbac.yaml and is deliberately
short. It is worth reading for what is absent: no write access to any resource
type a composition might generate.
Two rules deserve attention.
impersonate on serviceaccounts. This is the mechanism. Because the API
server authorises a username of the form system:serviceaccount:<ns>:<name>
against the serviceaccounts resource in that namespace, this grant can be
confined per namespace — see rbac.impersonation.scope in the chart.
impersonate on groups, pinned by name. Weft impersonates
system:serviceaccounts and system:authenticated so the impersonated identity
matches what a real ServiceAccount token carries. The grant is restricted with
resourceNames because an unrestricted one would let anyone who compromises the
controller impersonate system:masters, which is a worse position than the one
this project set out to avoid.
The chart refuses to render an unrestricted group grant unless
rbac.allowUnrestrictedGroupImpersonation is set explicitly. If you set it,
understand that compromising the controller then means cluster-admin.
An impersonated request records both identities, so the audit log shows who a change was really made for:
"user": {"username": "system:serviceaccount:weft-system:weft-controller"},
"impersonatedUser": {"username": "system:serviceaccount:sweep-labs:composer"}Filtering on impersonatedUser gives every change a given composition made.
- Pin
image.digestrather than a tag. - Set
rbac.impersonation.scope: namespacedif Weft only needs some namespaces. - Leave
rbac.allowUnrestrictedGroupImpersonationoff. - Scope composer ServiceAccounts narrowly. Weft tells users exactly which verbs
and resources they need, so a minimal Role is not much more work than
edit. - Leave
crds.keepon, so an uninstall cannot cascade into deleting everyWeaveand the infrastructure they own. - Run
weft reapbefore removing the controller if any program usesread(..., hold=True).
Pre-1.0. Fixes land on main and in the next tagged release.