Security
How convoy authenticates and authorizes requests, which identities it uses, and how to limit access.
convoy is an aggregated API server. The host cluster authenticates and authorizes each request to
the convoy API. The convoy controllers act with two kinds of identity. On the host cluster, they
use their own service account. On a target cluster, they use the identity of the Cluster. This page tells how to
control the access to each part.
Authentication and authorization#
A client sends a convoy request to the host kube-apiserver. The kube-aggregator sends it to the convoy apiserver with the identity of the user. The convoy apiserver then asks the host cluster:
TokenReviewand theextension-apiserver-authenticationConfigMap, to authenticate.SubjectAccessReview, to authorize.
Thus you control the access to convoy objects with RBAC on the host cluster. The API groups are
core.convoy.kumodo.io, source.convoy.kumodo.io and extensions.convoy.kumodo.io. The
apiserver service account needs the system:auth-delegator ClusterRole and the
extension-apiserver-authentication-reader Role in kube-system.
RBAC for Release subresources#
RBAC controls each subresource of a Release separately:
| Resource | Verbs | Gives |
|---|---|---|
releases/sync | create | Request a reconcile, and an apply in manual sync mode. |
releases/pin | get, update, patch | Read, set or clear the pin. |
releases/mutations | get, update, patch | Read or replace the mutations. |
releases/lifecycle | get, update, patch | Read or replace the lifecycle hooks. |
releases/preview | get | Read the desired render. |
releases/diff | get | Read the desired and the live objects. |
releases/health | get | Read the health of each resource. |
releases/history | get | Read the applied renders. |
releases/status | update, patch | Write the status. Give it only to convoy components. |
Treat update and patch on releases as full control of the Release. Give these verbs only to
the owners of the Release. For a user who needs only one part, for example the pin, give only the
subresource.
preview and diff can show the values of rendered Secrets. Give get on these subresources
only to users who can read the Secrets in the target namespaces.
Example: a Role that lets a user pin and sync, but not edit the Release.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: release-operator
namespace: team-a
rules:
- apiGroups: ["core.convoy.kumodo.io"]
resources: ["releases"]
verbs: ["get", "list", "watch"]
- apiGroups: ["core.convoy.kumodo.io"]
resources: ["releases/pin"]
verbs: ["get", "update", "patch"]
- apiGroups: ["core.convoy.kumodo.io"]
resources: ["releases/sync"]
verbs: ["create"]See Subresources.
Same-namespace references#
A Release can refer only to objects in its own namespace. The apiserver rejects a Release with a namespace in one of these references, when the namespace is not the namespace of the Release:
spec.clusterRefspec.source.gitRepository,spec.source.helmRegistryandspec.source.ociRegistryspec.content.helm.values[].gitRepositorymutations[].from
The reason is credential borrowing. A Cluster or a source can hold credentials. The controllers
read these objects with their own identity, not with the identity of the user. Without the rule,
a user could deploy with a Cluster in a namespace that the user cannot read.
convoy reads each referenced Secret and ConfigMap in the namespace of the object that refers to it:
- A
Clusterkubeconfig Secret, in the namespace of theCluster. - Source credentials, in the namespace of the
GitRepository,HelmRegistryorOCIRegistry. - Helm values and mutation sources from a Secret or a ConfigMap, in the namespace of the Release.
Identities of the controllers#
On the host cluster, the controller-manager and the observer use their own service account. They need these rights:
- Read the convoy objects through the aggregated API.
- Write the status subresources and the
ReleaseInventoryobjects. geton Secrets and ConfigMaps in each namespace that has convoy objects.- The lease rights. See Scaling and high availability.
The dev ClusterRoles for the controllers are not a complete least-privilege set. The
cluster-admin bindings hide the missing rules.
On a target cluster, the identity depends on spec.connectionMode of the Cluster:
| Mode | Identity on the target cluster |
|---|---|
inCluster | The service account of the convoy process: the controller-manager for apply and prune, the observer for drift and health. |
agentless | The credentials in the kubeconfig Secret of the Cluster. |
Give write access to the kubeconfig Secrets of your Clusters only to cluster administrators. A kubeconfig can contain credential plugins, and the convoy components use the kubeconfig as it is.
Dry run needs write rights#
The pre-apply dry run and the drift check of the observer use Server-Side Apply with dry run.
The target cluster authorizes a dry run as a real write. Thus the observer needs the same write
rights on the target cluster as the controller-manager. For agentless, the two components use
the same kubeconfig. For inCluster, give the observer service account the same rights as the
controller-manager service account. See Apply and prune.
The observer endpoint#
The apiserver authorizes preview, diff, health and history requests, and then sends them
to the observer at --observer-url over HTTP. Let only the apiserver pods connect to the
observer port. Use a NetworkPolicy for this.
Render store#
The render store contains each applied render, also rendered Secrets. Give access to the bucket or the volume only to the controller-manager and the observer. See Render store backends.
Planned#
- Cross-namespace references with a
ReferenceGrantobject in the target namespace. See ReferenceGrant. Not built - A least-privilege identity for each target cluster, used for the apply and the dry run. Not built
In the code
convoy v0.9.0
internal/registry/core/release.(*Storage).Mapinternal/registry/core/release/store.go:60internal/registry/core/release.Strategy.GetResetFieldsinternal/registry/core/release/strategy.go:124api/core.validateSameNamespaceRefsapi/core/validation.go:168internal/auth.ResolveSecretKeyinternal/auth/keyref.go:28internal/clusterconn.(*Connector).Connectinternal/clusterconn/connector.go:64internal/registry/core/release.(*observerProxyREST).Connectinternal/registry/core/release/store.go:402internal/observer.(*Controller).Serveinternal/observer/server.go:40