Marketplace
Each namespace has its own marketplace catalogue, stored in a marketplace-sources ConfigMap and served to the dashboard through ark-api. Platform teams control three things, all with native Kubernetes mechanisms:
- What each namespace sees — seeded defaults via the dashboard Helm chart, then edited live through ark-api.
- Who can edit it — a namespace-scoped Role bound per namespace with a RoleBinding.
- Who can use a private source — a user’s
geton the source’s credential Secret, under impersonation. - Where catalogues live — one ConfigMap per namespace, governed independently.
For the ConfigMap schema, RBAC rules, and ark-api endpoints, see the Marketplace Sources reference. This guide covers operation.
Seed catalogues with Helm
The dashboard chart accepts a marketplaceSources values key. A post-install,post-upgrade Job writes each entry into the target namespace’s marketplace-sources ConfigMap:
marketplaceSources:
- name: agents-at-scale-marketplace
url: https://raw.githubusercontent.com/mckinsey/agents-at-scale-marketplace/main/marketplace.json
displayName: "Ark Marketplace"| Field | Required | Description |
|---|---|---|
name | yes | ConfigMap key for the source. Unique per namespace. |
url | yes | Absolute https URL of a marketplace manifest. The filename is not constrained. |
displayName | no | Label shown in the dashboard. Defaults to name. |
namespace | no | Target namespace. Defaults to the install namespace. |
auth | no | Authenticated fetch: { scheme: bearer|basic, secretRef } referencing a pre-existing credential Secret. See Authenticated sources. |
Set marketplaceSources: [] to seed nothing and skip the Job. The Job image is configurable via marketplaceSourcesSeed.image.
Apply the values
Pass the values when installing or upgrading the dashboard chart:
helm upgrade --install ark-dashboard <dashboard-chart> \
-n <install-namespace> -f values.yamlFor a one-off change without a file, use --set (one index per entry):
helm upgrade --install ark-dashboard <dashboard-chart> -n <install-namespace> \
--set 'marketplaceSources[0].name=internal' \
--set 'marketplaceSources[0].url=https://internal.example.com/marketplace.json'See Deploying Ark for the full install.
Seed multiple namespaces in one install
Give entries different namespace values. A single helm install/helm upgrade seeds every listed namespace, each with its own marketplace-sources ConfigMap:
marketplaceSources:
- name: agents-at-scale-marketplace
url: https://raw.githubusercontent.com/mckinsey/agents-at-scale-marketplace/main/marketplace.json
displayName: "Ark Marketplace"
namespace: team-a
- name: agents-at-scale-marketplace
url: https://raw.githubusercontent.com/mckinsey/agents-at-scale-marketplace/main/marketplace.json
displayName: "Ark Marketplace"
namespace: team-b
- name: internal-catalog
url: https://internal.example.com/marketplace.json
displayName: "Internal Catalog"
namespace: team-bThe same name may repeat across namespaces. To change which namespaces are seeded, edit marketplaceSources and run helm upgrade again — seeding happens at deploy time, not runtime.
Target namespaces must already exist. The seed Job applies into each listed namespace, and a missing namespace fails the Job — which fails the whole helm install/upgrade. Create them first:
kubectl create namespace team-bGrant edit access
The chart ships a marketplace-source-editor Role (namespace-scoped, never a ClusterRole) granting get/update/patch on the marketplace-sources ConfigMap and create/get/update/patch/delete on secrets (editors manage per-source credential Secrets too). It is created in the install namespace and bound to no one by default — the marketplace is read-only until a platform team binds it.
It is a Role, not a ClusterRole, because RBAC cannot scope create on secrets by name — a namespaced Role bounds the blast radius to the namespace. The trade-off is that editing other namespaces needs the Role replicated there.
Read access to the catalogue is already granted by the tenant role, so every dashboard user sees the sources regardless of edit permission.
Bind it in the install namespace, or replicate the Role and bind it in another namespace. The sample ships both objects:
# Edit namespace + subjects first, then apply (creates the Role + RoleBinding).
kubectl apply -f samples/marketplace/marketplace-source-editor-binding.yamlThe dashboard shows add/edit/delete controls only when ark-api’s permission probe reports canEdit: true; the probe runs a SelfSubjectAccessReview under the requesting user. The kube-apiserver is the real gate — a user without the binding gets HTTP 403 on writes even if the controls were forced.
Revoke to return the namespace to read-only:
kubectl delete rolebinding marketplace-source-editor -n team-aTo disable editing everywhere — catalogues managed only through Helm/GitOps, never the dashboard — set marketplaceSourceEditorRole.create: false. The Role is not created, so no RoleBinding can grant edit access.
Manage many namespaces
Edit access is per namespace, and the Role is namespace-scoped, so each namespace needs its own Role and RoleBinding. For N teams, replicate both into each namespace:
for ns in team-a team-b team-c; do
kubectl apply -n "$ns" -f samples/marketplace/marketplace-source-editor-binding.yaml
doneThis keeps each namespace’s catalogue and its editors independent: seeding decides what a namespace starts with, the RoleBinding decides who changes it afterward.
Authenticated sources
A source can fetch a manifest that requires authentication (private GitHub/GHES, Azure DevOps, authenticated artifact stores). The token lives in a per-source Kubernetes Secret; ark-api attaches the header server-side and reads the Secret under the requesting user’s identity, so a user’s get on that Secret is the “may use this private source” grant.
Seed one at deploy time
An authenticated source is a credential Secret plus a marketplaceSources entry with an auth block. Provision it declaratively — the token never goes in values.yaml:
-
Create the credential Secret out-of-band (External Secrets Operator, sealed-secrets, SOPS, Vault), named
marketplace-source-<name>-authwith a single keyvalue:kubectl create secret generic marketplace-source-internal-mirror-auth \ -n team-a --from-literal=value=<token> # prefer a managed Secret store over this -
Declare the source with its
authblock (schemeisbearerorbasic). The seed Job writes only the ConfigMap entry — it never creates the Secret:marketplaceSources: - name: internal-mirror url: https://internal.example.com/marketplace.json namespace: team-a auth: scheme: bearer # "basic" for Azure DevOps secretRef: marketplace-source-internal-mirror-auth -
Grant the viewer group
geton the credential Secret so the source resolves for them (sample:samples/marketplace/authenticated-source-rbac.yaml). A user outside that binding sees an “authentication failed” error and the credential is never borrowed.
Deploy-time writes skip the dashboard’s validate-before-save and re-auth-on-URL-change guards (UX guards, not security invariants); a wrong credential surfaces the per-source auth error at fetch time and does not fail the install.
See Marketplace Sources for the schema and RBAC details.
Persistence across upgrades
The seed Job uses server-side apply with the field manager helm-marketplace-seeder, which owns only the keys it writes. Ownership is tracked per ConfigMap key:
- A source a user adds through the dashboard is owned by ark-api —
helm upgradenever touches it. - A seeded source a user edits transfers ownership to ark-api — the upgrade leaves it intact (the Job logs the apply conflict and preserves the key).
- A seeded source no one touched stays owned by the seeder — the upgrade reconciles it to the chart value.
- A seeded source removed from
marketplaceSourcesis pruned on the nexthelm upgrade— but only if no user edited it. An edited source is owned by ark-api and survives.
So user changes survive upgrades; only untouched seeded defaults track the chart.
Operational notes
- Source availability. ark-api fetches each source concurrently with a 10s per-source timeout and a 30s aggregator deadline, and caches successful fetches for one hour. A failing source surfaces an inline error and does not block the others.
- Outbound restrictions. Source URLs must be
https. ark-api rejects non-routable hosts (loopback, link-local, multicast, reserved) and does not follow redirects — including for credentialed fetches, so a credential cannot be used to reach internal/metadata services or leaked to a redirect target. Private RFC-1918 hosts are allowed for internal mirrors. - Items not appearing. Check the source URL returns valid JSON with an
itemsarray, is reachable from the ark-api pod, and useshttps. For an authenticated source, anauth_errormeans the credential Secret is missing, the user lacksgeton it, or the upstream rejected it (401/403).
Uninstall and cleanup
helm uninstall of the dashboard chart does not remove catalogue data:
- The
marketplace-sourcesConfigMap in each namespace survives — the seed Job writes it via apply, so it is not owned by the Helm release. - Credential Secrets survive — they are provisioned out-of-band, never by the release.
- Roles and RoleBindings you applied with
kubectlsurvive — they are not part of the release. - The
marketplace-source-editorRole in the install namespace is removed — it is a release object.
To remove the catalogue and access grants, delete them explicitly per namespace:
kubectl delete configmap marketplace-sources -n team-a
kubectl delete role,rolebinding marketplace-source-editor -n team-aSee also
- Marketplace Sources — ConfigMap schema, RBAC rules, and ark-api endpoints.