Assert documented security controls re-checks the claims on every change, so
removing a control from the chart fails the build.
Three companion pages carry the detail:
Pod security
The measured Pod Security Admission level for each namespace.
Network policy
Every rule the shipped policies allow, and what has no rule.
Supply chain
Signature and bill-of-materials verification commands.
Boundaries and defense in depth
The most useful thing to know about a control is whether crossing it requires defeating something other than the component being constrained. Controls in the first list are enforced by the Kubernetes API server, the kubelet or the network fabric, so a compromised Admin Server cannot step around them. Controls in the second list raise the cost of an attack but are enforced by the component itself. Security boundaries
Defense in depth
Identity and RBAC
The chart creates threeServiceAccount objects, one per workload that needs to
reach the Kubernetes API. The Admin UI gets none, and sets
automountServiceAccountToken: false.
What the Admin Server may do cluster-wide
Defined intemplates/admin-server.yaml under adminServer.rbac.create, bound
by a ClusterRoleBinding of the same name. No rule uses a wildcard.
Three of these need a justification.
namespaces: get and create
namespaces: get and create
A
Namespace is cluster-scoped, so neither verb can be confined by a
RoleBinding. create is what lets the console provision a database into a
new namespace without an administrator preparing one first.list is deliberately absent. The console derives the namespaces it shows
from the namespaces that hold a FalkorDB cluster, so the server never
enumerates the cluster’s namespaces and cannot use this grant to discover
what else the cluster runs.rolebindings: create and delete
rolebindings: create and delete
create is how the Admin Server extends its own Secret access into a
namespace as it adds that namespace. delete cleans up the RoleBinding
objects KubeBlocks leaves behind when a ComponentDefinition changes.On its own this pair would be a privilege-escalation path: anything that can
create a RoleBinding can grant itself any role in the cluster. The next
grant is what closes it.clusterroles: bind, restricted by name
clusterroles: bind, restricted by name
Kubernetes requires the
bind verb before a subject may reference a role it
does not already hold. The chart grants bind on exactly one name, the
namespace-scoped ClusterRole generated by
falkordb-enterprise.adminServerNamespacedRoleName.The API server rejects any RoleBinding the Admin Server creates that
references a different role. The create grant above is therefore bounded
to a single, known set of permissions.What the Admin Server may do per namespace
Secret and ConfigMap access is not in theClusterRole above. It lives in a
second ClusterRole, generated by
falkordb-enterprise.adminServerNamespacedRoleName, which is never bound
cluster-wide:
The chart binds it into the release namespace with a
RoleBinding, where the
Admin Server keeps its settings, identity provider credentials and the pull
secrets it replicates outward. The Admin Server binds it into each database
namespace as it adds that namespace, using the create and bind grants above.
The consequence is the one worth writing down: the Admin Server has no access
to Secret or ConfigMap objects in any namespace it has not been bound
into. That is enforced by the API server, not by the product.
Job roles
Both jobs are scoped down from the Admin Server’s reach.
The preflight role is cluster-scoped only because the resources it removes are
cluster-scoped. The browser encryption key job is a namespace-scoped
Role,
because everything it touches is in the release namespace.
The KubeBlocks operator
KubeBlocks generates aClusterRole per cluster component and binds it to that
component’s ServiceAccount, so its operator needs ClusterRole write access.
This grant cannot be narrowed with resourceNames: that field has no effect on
create, and the generated names are not known in advance.
Kubernetes’ own escalation guard bounds the risk, in that the operator can only
create a ClusterRole whose rules it already holds. But the operator is broadly
privileged in its own right, so when you review the trust boundary of a cluster
running FalkorDB Enterprise, treat the KubeBlocks operator ServiceAccount as a
cluster-admin-equivalent identity.
Workload hardening
All four workloads the chart owns satisfy therestricted Pod Security Standard.
Each sets the same properties:
emptyDir:
Pod Security Admission
The installer labels namespaces (apply_pod_security_labels in
scripts/install.sh); the chart does not. The strictest level each namespace
can run at was measured, not assumed:
The four workloads the chart owns would each pass
restricted. The release
namespace is held at baseline by a single subchart. See
Pod security for the reported violations per
workload.
Network isolation
Network policies are off by default, because a cluster whose CNI plugin does not implementNetworkPolicy would accept the objects and silently enforce
nothing. Enable them with networkPolicy.enabled=true once you have confirmed
your CNI plugin enforces them.
Two policies are created, one for the Admin Server and one for the Admin UI. Both
set policyTypes: [Ingress, Egress], so anything not listed is denied:
Narrowing
networkPolicy.externalEgress.cidrs to your control plane and identity
provider ranges is the single most valuable change you can make to the shipped
policies. See Network policy for the flows that
deliberately have no rule.
Secrets
Set the values that hold credentials through
--set or a values file you keep
out of version control, or point the corresponding existingSecret key at a
Secret you manage yourself.
Registry credentials
global.imageCredentials.create makes the chart create a pull secret in the
release namespace. The Admin Server copies the secrets named in
falkordb-enterprise.imagePullSecretNames into each database namespace, because
a pull secret is only usable in the namespace it lives in.
This replication is the reason the Admin Server holds create on secrets in
the namespace-scoped role rather than read-only access.
Supply chain
Container images and the Helm chart are signed withcosign using a key pair
held by the release pipeline, and images carry a CycloneDX bill of materials as
an attestation. Signing does not use the public transparency log, so
verification needs --insecure-ignore-tlog=true:
adminServer.image.digest and adminUi.image.digest
to remove the registry’s ability to change what a tag points at. See
Supply chain for chart verification, bill-of-materials
verification and how mirroring preserves signatures.
Verification is not enforced at admission time by the chart. To make a signature
a boundary rather than a claim, run an admission controller that verifies it,
such as the Sigstore policy controller or Kyverno.
What CI enforces
scripts/assert-security-controls.py runs against the rendered chart on every
change to it. It fails the build if any of the following stops being true:
- No role the chart owns uses a wildcard in
apiGroups,resourcesorverbs. - The
namespacesgrant is exactlygetandcreate. - The Admin Server
ClusterRolegrants no cluster-widesecretsorconfigmaps. - The
clusterrolesgrant isbindonly, and carries aresourceNamesrestriction. - The namespace-scoped
ClusterRoleis never referenced by aClusterRoleBinding. - Every workload the chart owns sets
runAsNonRoot,seccompProfile: RuntimeDefault,allowPrivilegeEscalation: false,readOnlyRootFilesystem: trueand drops all capabilities. - The Admin UI does not mount a
ServiceAccounttoken. - Both network policies restrict egress as well as ingress.