Skip to content

feat: namespace-scoped application family - #77

Open
drey wants to merge 113 commits into
mainfrom
feat/ns-scoped-applications
Open

drey wants to merge 113 commits into
mainfrom
feat/ns-scoped-applications

Conversation

@drey

@drey drey commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a namespace-scoped resource family so a namespace owner can install a Helm chart without cluster-wide rights: five new CRDs (HelmApplication, HelmApplicationRepository, HelmApplicationChart, HelmClusterApplicationRepository, HelmClusterApplicationChart) and their controllers. The existing addon code is generalized rather than copied — both families now run through the same reconcilers.

Why

Installing a chart required cluster-admin, because every resource the module owns is cluster-scoped. A namespace owner needs a way to install charts in their own namespace without gaining access to the whole cluster.

Two requirements shaped the design. A release must be applied under a bounded identity, or a chart carrying a cluster-scoped object would escape the namespace boundary. And the new family must not become a second copy of the addon code, which lives on just as long and would have to be edited in both places.

Key changes

API (api/v1alpha1, api/naming, crds/). Five new CRDs with their Russian doc counterparts. RepositorySpec/RepositoryStatus and the chart catalog status are now shared types: all eight CRDs generate byte-identical to before, except one description line in the addon catalog. The catalog naming scheme moved to api/naming and became injective: the hash now covers the repository and chart joined by a byte no name can hold, so two pairs can no longer produce one object name.

Controller (images/operator-helm-controller/internal). Domain contracts in internal/source (repository, catalog, release, access) with a per-kind adapter in internal/adapter. The catalogs collapsed into one generic internal/catalog package, and the repository and release reconcilers are now shared. Adds controllers for the five new kinds and a HelmApplication webhook. A transitional step renames existing catalog objects to the current scheme on every repository reconcile, independent of the remote, carrying their status over before the old object is deleted.

Release identity (internal/services/access_service.go). Each application gets a token-less ServiceAccount in d8-operator-helm, a Role seeded in the application's namespace with full rights inside it, and a RoleBinding. The Role is created once and never read back, so a namespace owner may narrow it afterwards. The internal HelmRelease carries spec.serviceAccountName and spec.storageNamespace, and the release storage lives in the application's namespace.

Chart values service (images/chart-values-controller/internal). A repository kind is described by one family value instead of branching. The HTTP contract gained a namespace field, and authorization is decided per family: permission to create a HelmClusterAddon for addons, permission to create a HelmApplication in the requested namespace for applications. The namespace is part of a chart's identity only for a namespaced family; for a cluster-scoped one it is only the authorization context. Auxiliary object names now key on the repository namespace, and the cluster-scoped name shape is unchanged.

Templates (templates/). RBAC for both controllers, the webhook configuration and the ValidatingAdmissionPolicy cover the new kinds. The controller's rights over roles are narrowed to create, escalate and bind, since the Role is only ever seeded and never read back. Seven user-facing ClusterRoles are added for both Deckhouse role models, so a namespace owner can reach the new kinds at all: write access sits at admin level, the controller-written chart catalogs are read-only, and the cluster-scoped repository stays with cluster editors. The admission policy now also closes the application and application-repository statuses, which the seeded Role can otherwise reach.

Tests and docs. New e2e suites helmapplication and helmapplicationrepository. The derived-name scheme is mirrored in tests/e2e/internal/naming (the operator's internal package cannot be imported) and tied to the operator's own implementation by twin tests. README and EXAMPLE in both languages describe the new family and correct the claim that cluster-admin is required. CI gained a unit-test workflow and a generated-files check, and the Go lint job is enabled again now that the linter is built from source rather than downloaded.

Review focus / risks

  • Every chart catalog object is renamed, the released addon ones included. The name a repository and chart pair produces was not injective, so two repositories could fight over one catalog object; the hash now covers the pair joined by a byte no name can hold. Existing objects therefore move. On the first reconcile after the upgrade each repository recreates its catalog objects under the new name, carries the old status over — a version a consumer still holds keeps its RemovedFromRepository marker and media type — and deletes the old object. The rename runs before anything touches the remote, so a repository not yet due for a sync, or one whose registry is gone for good, still migrates. Everything about it is marked TRANSITIONAL and comes out once every cluster has reconciled once: MigrateNames in internal/catalog/catalog.go, its interface entry, and Inputs.MigrateErr.
  • Impersonation is never actually exercised. The tests confirm serviceAccountName and storageNamespace are set, but the werf/3p-helm-controller fork holds full rights itself: an implementation honouring the storage namespace while ignoring the service account would pass every check in this branch. Proving it needs a chart containing a cluster-scoped object and an assertion of Ready=False with forbidden. This is the one load-bearing assumption no test closes.
  • Creating a HelmApplication is effectively namespace-admin. The seeded Role grants */*/* within the namespace. It is a deliberate decision, documented, but worth confirming the boundary is acceptable.
  • The user-facing roles rest on review alone. Seven ClusterRoles cover both Deckhouse role models. Nothing checks them automatically: the module linter skips templates/rbacv2/** entirely, its wildcard rule only reads files named rbac-for-us.yaml, and its classic-model rule verifies the name and the access-level annotation but not the rules. No e2e exercises the aggregation chain either. Worth reading the labels against the upstream selectors by hand, in particular that write access lands at admin level and never at an editor one.
  • The released addon family. No expectation changed in the thirteen addon test files touched; conditions, reasons, requeues, finalizers and the HTTP contract match. Three deliberate differences: catalog object names move as above, an addon release name longer than 53 characters is now truncated instead of being rejected by Helm, and three catalog error messages were reworded. Every other derived name is frozen and pinned by a test.
  • The namespace divergence for helmclusterapplicationrepository. The request must carry a namespace and is authorized against it, but the namespace is dropped when the chart is resolved. Worth confirming the divergence is genuinely not exploitable.
  • Repository isolation is only partly proven. The e2e creates same-named repositories in two namespaces but with the same URL, so their chart sets coincide: a controller pruning objects by label without a namespace restriction would still pass that test.

drey added 30 commits September 10, 2026 11:53
Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Move LabelRepositoryName, LabelChartName and the UnavailableReason*
constants out of helm_cluster_addon_chart.go into constants.go, since
they are now shared by three chart-catalog families instead of one.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…elpers

Bound spec.metadata.name to 63 characters via a CEL rule: the controller
writes the object's name into a source label value on every internal
resource it creates (chart_service.go, release_service.go,
oci_repo_service.go, chart_claim_service.go), and Kubernetes caps label
values at 63 characters. The kind is unreleased, so tightening it now is
free; closing it after release would be a breaking change. No lower bound
is added, since nothing references a HelmApplication by name.

Added RepositoryName() and RepositoryKind() on
*HelmApplicationLastAppliedChartRef, mirroring the existing helpers on
HelmApplication's spec, so callers that need "which repository did we
last deploy from" (uninstall, and the namespaced-to-cluster migration
IsChartStatusInfoOutdated detects) don't have to re-implement the
repository/clusterRepository XOR by hand.

Also covers ConfigurationApplyInProgress/UpdateInstallInProgress with
tests that reach their Unknown+ReasonReconciling branch, and clarifies
a subtest name that overstated its assertion.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…tion

The description asserted that the resource "requires no cluster-wide
permissions". Whether that holds is decided by the RBAC that ships with
the controllers, not by the schema, so the CRD is the wrong place to
promise it. The namespace statement it followed already carries the part
that is true of the API itself.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The three repository kinds differ only in scope; their spec and status
were already identical field for field, description for description, so
the generated CRDs are unchanged by this commit. One declaration lets the
controller reconcile all of them through a single code path.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The three catalog kinds are projections of a repository index and were
already identical in shape. Sharing the status type changes one word in
the released HelmClusterAddonChart description ("addon chart state" ->
"chart state") and its translation; nothing else in the schema moves.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Internal objects of every family share d8-operator-helm, so a name derived
from the source name alone lets two same-named namespaced sources overwrite
each other's HelmRepository and auth secret. DerivedName always includes a
hash over kind, namespace and name. The addon naming functions are left
untouched and their exact output is now pinned by a test: those names are
live objects.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
source.Repository is how the services will see any repository kind; the
three adapters hold everything that differs between kinds — labels, derived
names, owner GVK. The addon adapter reproduces the released names and
labels exactly. Nothing consumes the contract yet.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…catalog

The catalog writer is the one place generic over API types: catalog objects
must be created, listed and status-patched with their concrete kind. A
namespaced catalog lists by the repository namespace, which is what keeps two
same-named repositories in different namespaces apart. The merge and sort
rules move here unchanged with their tests; the application catalogs have no
consumers until HelmApplication is reconciled.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…h the contract

EnsureSecrets, EnsureInternalHelmRepository and the cleanup paths take a
source.Repository instead of the addon type; names and labels come from the
adapter. The addon reconciler wraps its object at the boundary, so nothing
observable changes for the addon family.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
RepoSyncService fetches through source.Repository and writes through
source.Catalog; its own catalog code is gone, the generic catalog owns it.
OCIRepoService cleans up by InternalNames and implements ConsumerForcer for
the addon family. Behaviour is unchanged: the synchronization tests pass with
their expectations untouched.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…iler

The repository reconciler reads the API object through a source.Repository
adapter handed to it as a factory, so the same code serves any repository
kind. The package moves from reconcile/helmclusteraddonrepository to
reconcile/repository; Evaluate and its tests move with it unchanged. The
addon controller is the first, and so far the only, user.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
A namespaced source cannot be identified by its name alone once its internal
objects share d8-operator-helm with everything else; the new mapper reads the
namespace from the second source label. The existing mapper is untouched.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…ApplicationRepository

Both kinds are served by the shared repository reconciler through their
adapters. Their internal objects carry namespace-aware derived names and, for
the namespaced kind, a source-namespace label the watches map back through.
Neither has consumers yet: HelmApplication is the next plan, and until then
force requests stop at the repository and no chart version is protected from
pruning.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…neralization

Mapper log lines for other families' internal objects drop to debug
verbosity; a reconciler test now covers the namespaced kind end to end;
catalog errors name the repository namespace; RBAC gains /finalizers on the
new resources; stale comments corrected and the stage-5 stand-ins marked.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
source.Release is how the services will see any release kind; the
companion contracts (RepositoryResolver, ChartClaim, TargetNamespaceEnsurer,
AccessManager, ReleaseLister) name the places where the addon and the
application families differ, each with a no-op for the family that does
not need it. HelmReleaseName bounds a release name to Helm's 53-character
limit without touching any name that already fits; Catalog.Lookup lets a
release find the version it asks for. Nothing consumes the contract yet.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The addon release adapter reproduces the released names and labels exactly
and bounds the release name to Helm's limit. The repository resolver and
the release lister give the repository branch a way to reach the addon's
consumers without knowing their kind.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…es through the release contract

ChartService, ReleaseService and MaintenanceService take a source.Release
(and, for the chart, the source.Repository whose internal HelmRepository
it points at). ResolveChartSource takes the repository url so every kind
can use it. The HelmRelease gains serviceAccountName and storageNamespace
when the family impersonates — no family does yet. The addon reconciler
wraps its objects at the boundary; behaviour is unchanged.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…e lister

The release side of OCIRepoService takes the contract. Forcing the
consumers' internal OCIRepositories moves into ForceService, which is
handed the family's ReleaseLister instead of listing addons itself; the
catalog's in-use lookup is built from the same lister. The addon family is
the only one wired so far.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…release contract

ClaimService takes a source.Release and implements source.ChartClaim; it
stays an addon service in substance — the holder is read as a
HelmClusterAddon. Creating the target namespace moves out of the reconciler
into NamespaceService, the addon's TargetNamespaceEnsurer.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The release reconciler reads the API object through a source.Release
adapter and takes its kind-specific collaborators as Deps: repository
resolver, chart claim, target-namespace ensurer and access manager. Each
step is the addon's step of today; the package moves from
reconcile/helmclusteraddon to reconcile/release. AccessSetupFailed is the
reason a release reports when its identity cannot be set up — no family
sets one up yet.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The application adapter is the one place the two mutually exclusive
repository fields are read; downstream sees a kind, a namespace and a
name. Its internal objects carry namespace-aware derived names, the Helm
release name is prefixed so a hand-installed release cannot be taken over,
and lastAppliedChart is replaced wholesale. The HelmApplication indexes
carry the repository kind and namespace, which is what lets the
application catalogs see real consumers scoped to their namespace.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…ied with

AccessService creates a ServiceAccount in the operator namespace without a
token, seeds a Role with full rights inside the application namespace and
binds the two. The Role is created once and never reconciled: it is where a
namespace owner cuts the rights down, and a RoleBinding cannot grant
anything beyond the namespace regardless of its content. Cleanup removes
the account and the binding and keeps the Role.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…sing them

Two mappers enqueue the HelmApplication objects referencing a repository
or one of its charts, through indexes that carry the repository kind and
namespace. The application repository controllers now force their real
consumers instead of nobody; the last stage-5 stand-ins of the repository
plan are gone.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
HelmApplication is served by the shared release reconciler with no chart
claim, no target-namespace creation and an identity of its own: the chart
is applied as a ServiceAccount bound to a namespace Role, so the release
cannot reach beyond its namespace. The webhook rejects system namespaces
and deletion under maintenance. The controller gains rights on roles and
role bindings (with escalate and bind) and on service accounts in its own
namespace; helm-controller already holds every verb.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The chart of an application is applied as a ServiceAccount bound to a Role
with every permission inside the namespace, so the right to create the
resource is the right to administer the namespace. The CRD description —
the source of the module's resource documentation — now says so, and that
the Role may be narrowed by the namespace owner.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
drey added 28 commits September 11, 2026 23:32
The verdict of a failed rename was written only when the pass also
attempted a synchronization, but the rename runs on every pass by design.
A repository already past its schedule therefore kept the Synced=True its
last successful sync had left, reporting health while its consumers could
not resolve their chart, and the failure reached neither the work queue
nor the logs.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
HelmApplicationRepository and HelmClusterApplicationRepository carry their
registry credentials in plaintext (spec.auth.username/password), so any
right to list them is a right to read that password. The viewer capability
and the classic User role granted get/list/watch on both kinds; move that
read to the level where Deckhouse first permits reading Secrets instead
(the new user capability, and the PrivilegedUser role), leaving
HelmApplication and the chart catalogs at viewer/User. Write-level roles
keep read access transitively through the existing aggregation chain.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
A version the repository no longer offers survives only in the status of
the object under the old name. Writing the fetched versions into the new,
still-empty object made the next pass consider the carry-over done and
delete that last copy, so a consumer pinned to that version lost the media
type it needs and could not recover: the version is gone from the remote
too.

The gate before the synchronization now checks the rename alongside the
two failures it already checked.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
EnsureAccess failing returned only the status-patch error, which is nil
on a successful write, so a failed identity setup was reported once and
never retried: nothing watches the ServiceAccount/RoleBinding it manages,
and a status-only update is filtered out by the controller's predicates.
Return the service error alongside the (best-effort) status write so the
work queue's rate limiter picks the release back up.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
CleanupAccess failing during deletion already returned an error, so the
finalizer correctly stays and the queue retries, but the status was left
untouched. Every earlier step of the delete path marks the status through
the shared status manager; this one did not, so the object gave no reason
for why deletion was stuck. Call MarkDeletionFailed, mirroring the sibling
repository reconciler's use of the same method.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
chartObjectName truncated and trimmed the repository and chart name but never
lower-cased or replaced characters a Kubernetes object name cannot hold, so an
upper-case or space-containing chart from a repository index (both legal
there) produced a name the API server rejects, failing CreateOrPatch for that
one entry and taking Synced=False for the whole repository with it.

Sanitize both readable parts (lower-case, replace anything outside
[a-z0-9.-] with a dash) before the existing truncate/trim logic runs, and
widen the final trim to both ends so an empty or all-separator readable part
no longer leaves a leading dash. The hash still hashes the raw inputs, so
uniqueness is unaffected.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…uest

The handler decoded an unbounded body and validated only presence of the
request fields before authorizing, so an unauthenticated caller could send an
arbitrarily large body, hold the handler open past the header-only timeout,
or send a whitespace-only version that would never match a catalog entry and
poll as pending forever; an invalid repositoryName reached the resolver and
read back as repository_not_found instead of a bad request.

Bound the body with http.MaxBytesReader (answering 413 when exceeded), add
ReadTimeout and WriteTimeout alongside the existing ReadHeaderTimeout, and
move the cheap bearer-token presence check ahead of body parsing; the actual
token/access review still needs the kind and namespace the body carries, so
it stays after. Validate repositoryName as an object name and against the
length bounds its own repository CRD enforces, and reject a whitespace-only
chart or version, bounding both lengths without imposing a naming grammar
neither field actually has.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…take

The release name hashed only what exceeded Helm's limit, so a short name
spelled exactly like the cut and hashed form of a long one produced the
same release. Both names are valid, both applications live in one
namespace, and two releases under one name share one storage: each
overwrites the other's history, and deleting one uninstalls the other.

The application family now always carries the hash, which removes the
second branch the two names met in. The addon family keeps its scheme —
moving it would move every release installed today — and the limit is
recorded where the scheme lives.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…espace

The rule named three kube- namespaces and the d8- prefix, so an
application could be installed into default or into any other kube- one.
Installing it there seeds a Role granting everything inside that
namespace to whatever the chart contains. The rule mattered less when
only a cluster administrator could create a namespaced resource here.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Mirror the CEL rule already on HelmApplicationRepository and
HelmClusterApplicationRepository: this repository's name is likewise
copied into the "repository" label on its chart catalog objects, and a
label value cannot exceed 63 characters.

Because this rule also applies to updates of existing objects, any live
HelmClusterAddonRepository whose name is shorter than 3 characters becomes
un-updatable by this change, including status writes from the controller.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The condition type and the reason were still spelled as literals here,
while the application twin next to them uses the constants.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The policy closed the status of every kind an application's account can
reach but stopped short of the two cluster-scoped repositories. No role
this module seeds reaches them, so nothing depends on it — but an entry
missing from a list like this reads as an oversight rather than a
decision.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The delete helper promised to wait for the internal helm release and
polled only the application, a weaker signal than the addon family's own
helper gives. And the namespace each suite creates was never registered
for deletion, so a run against a live cluster left it behind along with
whatever the module had seeded in it.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The resource list repeated the same sentence for all three catalog kinds
and the limitations named one of them among the resources a namespace
owner manages, which the policy and the roles both contradict.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
escalate and bind are checked against the role a RoleBinding or Role update
would name, so both can be restricted to resourceNames: [operator-helm-application]
— the only Role this module ever seeds. create is left unrestricted because the
object does not exist yet when the request is authorized. rolebindings also
dropped list and watch: the RoleBinding cache is disabled and the code only
ever does Get, Create, Patch and Delete on it.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The HelmApplication webhook and NamespaceService read a Namespace through the
manager's cached client, whose reflector needs watch — a verb the ClusterRole
does not grant. Without it the reflector relists in a loop and the cache is
stale between relists, which matters for the webhook's delete path deciding
whether the namespace is terminating. Both now read through mgr.GetAPIReader(),
the same pattern ClaimService already uses for a decision that must not be made
against stale data.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Transport built a bare &http.Transport{} per probe, carrying none of the
default's dial/idle timeouts and never releasing its connection pool, and
ChartLayerMediaType had no deadline of its own, so a caller with none could
be held open indefinitely by a hung registry. Transport now clones
http.DefaultTransport before setting TLS, and the probe both bounds itself
with a probeTimeout floor (kept below the caller's own deadline when it has
one) and closes the single-use transport's idle connections once it returns.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
…pace

main.go disabled caching for ServiceAccount and RoleBinding but left Secret
alone, and the repository controllers watch Secrets, so the operator held
every Secret in the cluster in memory. Every Secret the operator reads or
writes lives in the module namespace (the repository services all take
helmv1alpha1.TargetNamespace), so cache.Options.ByObject restricts the Secret
informer to it. Adding Secret to Client.Cache.DisableFor instead would not
help: an explicit Watches(&corev1.Secret{}, ...) starts an informer regardless.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The message is produced on update as well as on create, where "cannot be
created" describes neither the request nor the rule.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Scoping escalate by resourceNames denied every seeded role: the check
authorizes against the name in the request path, and a create carries
none, so the rule matched nothing and each application failed to set up
its identity. bind keeps the scope — it is authorized against the name in
the binding's roleRef, which is always present.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
It belongs to whoever works there, and that is a user without a namespace
of their own — exactly the person this family exists for. Refusing an
application in default kept them out for no reason the cluster shares.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Every repository kind now records the size of the catalog it published at
its last successful read, refreshed on each synchronization. The field is
a pointer: a repository nobody has managed to read yet answers nothing,
which is not the same answer as a repository offering no charts.

A failed read leaves the previous count standing rather than replacing it
with zero, so the field always describes a real reading.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The experimental role model is dropped until it is added deliberately,
and the classic one is reduced to the two levels that matter: Editor may
do anything with an application and list its repositories and charts,
Admin adds the repositories themselves and a read of the chart catalog.
Levels accumulate upwards, so Admin carries only the difference.

The cluster-scoped repository and its catalog lose their user-facing
level with the experimental model and are reachable only cluster-wide.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Reading a repository is reading its password, since the credentials live
in the object; PrivilegedUser is the level from which Deckhouse permits
reading Secrets, so that is where both kinds become readable. Admin keeps
only what it adds: creating and modifying a repository.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
The addon family and the cluster-wide application repository had no role
of their own, so they were reachable only by a binding written by hand.
ClusterAdmin now carries them, and reads both cluster-scoped catalogs —
writing a catalog belongs to the controller and to no one else.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Installing an application carries namespace-admin rights, so the level
below it had nothing to offer that was safe to offer: reading a
repository is reading its password, and everything else it granted led
straight to that install. Admin now holds the whole namespaced family.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
An application may take its chart from a HelmClusterApplicationRepository
that only ClusterAdmin manages, and until now the person writing that
application could not see what the repository offers. The catalog moves
down to Admin; ClusterAdmin keeps the addon one, which is all that is
left to add there.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
A client that lists a catalog usually wants to follow it, and refusing
the watch only pushes it into polling.

Signed-off-by: Ilya Drey <ilya.drey@flant.com>
Signed-off-by: Ilya Drey <ilya.drey@flant.com>
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.

1 participant