Mesh 3 policy migration - #7211
Open
justindavies wants to merge 42 commits into
Open
justindavies wants to merge 42 commits into
justindavies wants to merge 42 commits into
Conversation
MeshTrafficPermission was documented as two pages, meshtrafficpermission
and meshtrafficpermission_experimental, both declaring the same policy
name. They described two mutually exclusive spec formats: the 2.x `from`
array with an `action`, and the SPIFFE ID matcher form under `rules`.
Kong Mesh 3 accepts only the second. Verified against a live 3.0 control
plane: applying the `action: Allow` form is rejected with "at least one of
'allow', 'allowWithShadowDeny', 'deny' has to be defined", and `from` was
removed as a policy targetRef concept, with /_resources now reporting
hasFromTargetRef: false for every policy type. So the page labelled
experimental documented the only supported syntax, and the unlabelled one
documented a form the control plane refuses.
There is now a single meshtrafficpermission page covering the SPIFFE ID
form, built from the experimental page's content, plus:
- the evaluation order, and why a deny cannot be overridden by an allow
- the spiffeID and sni matchers, with Exact and Prefix, and how a
prefix addresses a whole namespace
- allowWithShadowDeny as the way to validate a rule before enforcing it
- targetRef.sectionName for narrowing to one port
- zone proxies, carried over from the 2.x page
- a section on upgrading from 2.x, mapping from/action onto rules
Field names follow the CRD: `spiffeID`, capital ID, as in
api/common/v1alpha1/match.go. A live control plane also accepts
`spiffeId`, since Go's JSON decoder matches field names case
insensitively, but that spelling is not canonical.
Four examples replace the three carried over, adding a shadow-deny one.
The v2 snapshot keeps both 2.x pages, so 2.x readers still have the
`from` documentation. Their canonical_url entries now point at the single
latest page.
The top-level experimental stub is removed, with redirects for its index
and reference URLs. Three example redirects that Fabian added pointing at
the v2 snapshot now point at the migrated examples instead, since those
exist at latest. The legacy service-allow-requests example redirect still
points at v2, because that form is not carried forward.
MeshTrust and MeshIdentity linked to the experimental URL and now link to
the consolidated page.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Reach for it when you need to..." addresses the reader as a friend offering advice. The reader is looking for information about the policy, not counsel. State what the policy is for instead: Use it to trace a failing request across services, ... Same sentence, same content, no change in meaning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The matcher table claimed both spiffeID and sni take a type of Exact or Prefix. That holds for spiffeID; sni accepts Exact only. SNIMatchType is a single-value enum in api/common/v1alpha1/match.go (+kubebuilder:validation:Enum=Exact), and ValidateMatch rejects anything else with "unrecognized type, supported values are: Exact". It also requires the value to be a DNS-1123 subdomain, where spiffeID requires a parseable SPIFFE ID. The table now carries a type column per matcher rather than one sentence covering both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The evaluation order was stated correctly but its consequence was not. In 2.x the last matching rule won, so the common pattern was a mesh-wide deny-all followed by narrower allow rules that opened specific paths. That inverts in 3.0. RBACConfigurer.createMatcher emits every deny matcher from every merged policy before any allow matcher, and Envoy stops at the first match, so a blanket deny matches all clients and no later or narrower allow can re-open it. Carrying the 2.x pattern forward therefore denies everything. The pattern is also unnecessary: the matcher's OnNoMatch action is RBAC_DENY, so a mesh with no MeshTrafficPermission already allows nothing. Only allow rules need writing. Says so in both places a reader meets the question: under the evaluation order, and in the section on upgrading from 2.x. This also confirms the default-deny statement in the page opening, which had been carried over from the previous page rather than verified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The zone proxy section presented 10001 and 10002 as the sectionName values for zone ingress and egress, carried over from the 2.x page. They are neither constants nor listener names. sectionName matches Dataplane.networking.listeners[].name. Per api/mesh/v1alpha1/dataplane.proto, that field is optional and "if unset, the port value is used as the name (as a string)". So the value depends on the listener's port, or on an explicit name where one is set. Neither number appears anywhere in the Kong Mesh 3 source: the only 10001 is FirstOutboundPort in a test builder. 10001 and 10002 are the chart defaults (deployments/charts/kuma/values.yaml, ingress.service.port and egress.service.port), so they are usually right on a Helm install and wrong anywhere the ports were changed. Now explains what sectionName matches, gives the defaults as defaults, and shows how to read the listener names off the Dataplane. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the latest MeshRetry page, with four examples, and points the v2
snapshot's canonical_url entries at it.
Checked against the v3 source rather than carried over from the 2.x page,
which turned up one thing worth a warning on the page:
retryOn accepts `5xx`, lower case. `5XX` is rejected by the control
plane as "unknown item '5XX'", because validateHTTPRetryOn switches on
the constants exactly and All5xx is "5xx". `5XX` nevertheless appears
as the example value in both the CRD
(pkg/plugins/policies/meshretry/k8s/crd/kuma.io_meshretries.yaml) and
the OpenAPI schema, and in the prose of the 2.x page. There is no
kubebuilder enum on HTTPRetryOn, only an example, so a Kubernetes apply
passes schema validation and the control plane rejects it afterwards.
Every other named condition is upper camel case, which makes the
exception easy to trip over.
Other facts taken from source:
- targetRef accepts Mesh or Dataplane; to[].targetRef accepts Mesh,
MeshService, MeshExternalService, MeshMultiZoneService or
MeshHTTPRoute, per the TopLevelTargetRefKind and
OutboundTargetRefKind enums.
- gRPC retryOn is a closed kubebuilder enum, unlike the HTTP list.
- TCP takes only maxConnectAttempt.
- Defaults: numRetries 1, backOff.baseInterval 25ms, backOff.maxInterval
ten times baseInterval, rateLimitedBackOff.maxInterval 300s,
Predicate.updateFrequency 2, hostSelectionMaxAttempts one reattempt.
- At least one of tcp, http or grpc is required in a `default`.
The upgrade section records the two 3.0 changes that affect an existing
policy: MeshSubset and MeshServiceSubset are gone as selectors, and
header matches no longer materialize `type: Exact` into the stored
resource, per UPGRADE.md.
Three redirects that pointed latest example URLs at the v2 snapshot now
point at the migrated examples.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every MeshRetry example was rejected by a Kong Mesh 3 control plane. Two
faults, both from copying the shape of the 2.x examples:
spec.to[0].targetRef.labels (): must be set when kind is MeshService
spec.to[0].default.http.rateLimitedBackOff.resetHeaders[0].name ():
in body should match '^[a-z0-9!#$%&'*+\-.^_\x60|~]+$'
OutboundTargetRef carries only kind, labels and sectionName. It has no
name field, so `name: backend` selected nothing and labels is required. A
MeshService is selected the way UPGRADE.md selects one, by
kuma.io/display-name. The 2.x examples used name, namespace, sectionName
and a _port field, none of which apply.
HeaderName is constrained to lower case by a kubebuilder pattern, so
Retry-After and X-RateLimit-Reset had to become retry-after and
x-ratelimit-reset.
Reading the struct was not enough to catch either: the OutboundTargetRef
fields were in front of me and I wrote `name` anyway, and the pattern
lives on the HeaderName type rather than at the point of use. Both
surfaced only on apply.
All four examples/ files and all three policy_yaml blocks in the page now
apply cleanly to a 3.0 control plane. The MeshTrafficPermission page's
four blocks and four examples were re-checked the same way and needed no
change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Two pieces do the work:" and "Three pieces do the work:" personify configuration fields and tell the reader nothing about what follows. Replaced with "What each field does:", which names the list. Three pages: the phrasing originated in the MeshAccessLog overview and was carried into MeshTrafficPermission and MeshRetry when those were written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the latest MeshCircuitBreaker page, with four examples, and points
the v2 snapshot's canonical_url entries at it.
Checked against the v3 source, which surfaced two silent-data-loss traps
that both get a warning on the page:
spec.from is removed, and dropped silently. A policy using from
alongside to or rules is accepted and the from configuration has no
effect on inbound traffic; a policy where from was the only field set is
rejected, because the spec then has neither to nor rules. Per
UPGRADE.md, from maps onto rules, and a from entry targeting kind: Mesh
becomes a single catch-all rule.
healthyPanicThreshold moved here from MeshHealthCheck, and un-migrated
settings are dropped silently. It now lives at
to[].default.outlierDetection.healthyPanicThreshold. On Kubernetes the
old field is pruned by CRD validation and on Universal it is discarded
during deserialization, so nothing reports it and the cluster reverts to
Envoy's 50% default.
Other facts taken from source:
- This policy has both to and rules, unlike MeshRetry (to only) and
MeshTrafficPermission (rules only). At least one is required, and each
default needs at least one of connectionLimits or outlierDetection.
- to[].targetRef accepts Mesh, MeshService, MeshExternalService or
MeshMultiZoneService. It does not accept MeshHTTPRoute, which MeshRetry
does: circuit breaking applies to a destination, not to a route.
- rules applies to all inbound traffic, with no L7 matching, so a single
catch-all entry is the only available shape.
- detectors is required whenever outlierDetection is set and needs at
least one of its five detectors.
- maxEjectionPercent defaults to 10%, and at least one endpoint is
always ejectable regardless of it.
- healthyPanicThreshold defaults to 50%; 0 disables panic mode.
All four examples and all three policy_yaml blocks were applied to a live
Kong Mesh 3 control plane and accepted, then deleted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A reader coming from MeshTrafficPermission to MeshCircuitBreaker asks why
one matches clients by SPIFFE ID and the other names a MeshService. The
pages carried both facts and explained neither.
Each page now opens its "Where a policy applies" section with the three
selectors and what each answers:
spec.targetRef which proxies the policy is installed on
spec.to[].targetRef which destination, for traffic the proxy sends
spec.rules[] which client, for traffic the proxy receives
Followed by the reason they differ: a destination is something the proxy
chooses to call, so it is named directly; a client is remote and asserts
its own identity, so a name or label would be the caller's own claim about
itself, and it is matched on the SPIFFE ID MeshIdentity issued instead.
That is also why v3 dropped `from` with MeshServiceSubset from
MeshTrafficPermission.
The four shapes differ more than a reader would guess, so each page states
its own rather than implying a common one:
MeshTrafficPermission rules only, matching clients
MeshRetry to only
MeshAccessLog to and rules, rules can match clients
MeshCircuitBreaker to and rules, but rules cannot match at all —
L7 matching for inbound circuit breaking is not
implemented, so it is catch-all only
Kinds per field follow the cross-policy table in UPGRADE.md: only
MeshAccessLog, MeshLoadBalancingStrategy, MeshRetry and MeshTimeout accept
MeshHTTPRoute in to[], which MeshCircuitBreaker rejects.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit explained the three selectors on each policy page,
which duplicated a shared concept four times and would have repeated it
across every remaining policy.
Adds /mesh/policy-targeting/, covering:
- the three selectors and what each answers
- why outbound names a destination and inbound matches an identity: a
destination is something the proxy chooses to call, while a client
describes itself, and a label would be the caller's own claim. This is
why 3.0 removed `from` and MeshServiceSubset from
MeshTrafficPermission.
- what each field accepts, including the cross-policy rules from
UPGRADE.md: MeshHTTPRoute is accepted in to[] only by MeshAccessLog,
MeshLoadBalancingStrategy, MeshRetry and MeshTimeout; MeshRateLimit
and MeshFaultInjection accept Mesh only, and only when the top-level
targetRef selects a gateway; MeshSubset, MeshServiceSubset and
MeshGateway are rejected in to[] everywhere.
- the spiffeID and sni matchers, with sni accepting Exact only.
Each policy page now states only what is true of itself and links to the
reference. The per-policy facts that differ are kept where a reader needs
them: MeshTrafficPermission has no `to`, MeshRetry has no `rules`,
MeshCircuitBreaker allows both but its `rules` cannot match a client.
Placed in app/mesh/ alongside the other standalone mesh references, since
/mesh/policies/ is a generated list page.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every policy page now carries "How policies select traffic" as its first related_resources entry, so the shared concept is the first thing offered alongside a policy. MeshAccessLog had no related_resources block and now has one. Also fixes the tags on the new page: `policies` and `targetref` are not in the frontmatter schema enum, which failed make validate-frontmatters. Replaced with `policy`, `service-mesh` and `traffic-control`, which are. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"This is the part that looks inconsistent and is not." performs rather than states. The heading above it already named the subject, so the sentence carried no information and existed only for effect. Deleted. Two aphorisms replaced with the mechanism they were standing in for: "access control decided on a self-asserted tag was not access control" becomes "those selectors matched an inbound client on tags the client set itself". "sends traffic to all endpoints regardless of health, on the grounds that a degraded endpoint beats no endpoint" becomes "resumes sending traffic to all endpoints, ejected ones included, rather than failing the requests". This also makes healthyPanicThreshold's meaning explicit: the percentage of healthy endpoints below which panic mode starts. "Two differences from other policies are worth knowing" editorialised about the content and is now "This policy differs from others in two ways". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the latest MeshTimeout page, with four examples, and points the v2 snapshot's eight canonical_url entries at it. The 2.x page had seven examples covering variations of the same two cases; the latest page has four, and the redirects map each old URL onto the one that covers it. Two restrictions came out of the source and are both confirmed against a live 3.0 control plane: `to` and `rules` are mutually exclusive, unlike MeshCircuitBreaker where both may be set together. A policy defining both is rejected with "fields 'to' must be empty when 'rules' is defined", so inbound and outbound timeouts need separate policies. A rule matching on spiffeID may set only http.requestTimeout and http.streamIdleTimeout. The other five fields are rejected with "can't be specified when matches contain spiffeID because this field cannot be conditioned on source identity". The page explains why: those two act on a request or stream, which belongs to one client, while connectionTimeout, idleTimeout, requestHeadersTimeout, maxStreamDuration and maxConnectionDuration act on a connection or listener, which is shared. The defaults are documented, since a mesh already has timeouts and a policy changes them rather than introducing them. Kuma creates two MeshTimeout resources per mesh, from pkg/defaults/mesh/meshtimeout.go and pkg/plugins/policies/core/defaults/consts.go: connectionTimeout outbound 5s inbound 10s idleTimeout outbound 1h inbound 2h http.requestTimeout outbound 15s inbound 0, disabled http.streamIdleTimeout outbound 30m inbound 1h http.maxStreamDuration outbound unset inbound 0, disabled Inbound is twice outbound, so a receiving proxy does not time out a request before the caller does, and the inbound request timeout is disabled for the same reason. A mesh created with skipCreatingInitialPolicies covering MeshTimeout has neither default. The upgrade section covers `from` becoming `rules`, the removed targetRef kinds, and the Universal inbound that loses HTTP timeouts when its protocol is declared only through the kuma.io/protocol tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the latest MeshHealthCheck page, with four examples, and points the
v2 snapshot's canonical_url entries at it.
The healthyPanicThreshold removal is verified live, including the part
that makes it dangerous. A policy setting the field is accepted rather
than rejected, and reading it back shows the field gone:
$ kongctl create mesh -f hc-with-panic-threshold.yaml
created
$ kongctl get mesh meshhealthchecks hc-panic-probe -o json --jq '.spec.to[0].default | keys'
["http", "interval", "timeout"]
So nothing reports the loss, and the cluster reverts to Envoy's 50%
default. The page says so, and points at
MeshCircuitBreaker.to[].default.outlierDetection.healthyPanicThreshold.
The page opens by distinguishing this policy from MeshCircuitBreaker,
since they reach the same outcome differently: probes here are scheduled
and independent of real requests, which is active checking, while
outlierDetection judges the traffic already flowing. A destination with
little traffic is the case for active checks, because there may not be
enough requests to judge it by.
Facts taken from source:
- to only, no rules: a health check is something a caller performs.
to[].targetRef accepts Mesh, MeshService, MeshExternalService or
MeshMultiZoneService, and not MeshHTTPRoute, since a probe addresses
an endpoint rather than a route.
- At least one of http, tcp or grpc is required.
- Defaults: interval 1m, timeout 15s, unhealthyThreshold 5,
healthyThreshold 1, noTrafficInterval 60s, reuseConnection true, HTTP
path /, expectedStatuses 200 only, and statuses limited to 100-599.
- Protocol selection takes the most specific configured protocol, and
disabled: true falls back to the more general one.
- TCP receive blocks match fuzzily but in order; an empty receive makes
the probe connect-only.
The page also notes what the default interval and threshold mean together:
an endpoint failing every probe is removed about five minutes after it
starts failing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrite the policy page against the v3 source. The `from` array is gone; clients are matched with `rules[].matches` on `spiffeID` or `sni`, and `targetRef.kind` is limited to Mesh and Dataplane. Document the `to[]` restriction, which is verified in the validator and against a live v3 control plane: `to` is rejected outright unless `targetRef.kind` is Mesh, and then only accepts `kind: Mesh`. The restriction exists because a fault is produced by the proxy answering the request, so it is configured inbound; the `to` form covers gateways. Also cover the three fault types with their validated ranges, and the compounding of percentages down the fault list. Four examples replace the three v2 ones, all applied to a v3 control plane and accepted. v2.yml canonical URLs and the latest -> v2 example redirects now point at the migrated pages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrite the policy page against the v3 source, and cover the behavioural changes that matter more than the schema ones. Verified in source and against a live v3 control plane: - `to[].targetRef` accepts only MeshService, MeshExternalService and MeshMultiZoneService. `Mesh` is rejected with `value 'Mesh' is not supported`, so a route always names a destination. - `to[].hostnames` is in the schema but unimplemented, and rejected with `must not be defined`. Same for `urlRewrite.hostToBackendHostname`. - `replacePrefixMatch` is rejected unless every entry in the rule's matches uses a PathPrefix path. - `backendRefs[].kind` no longer accepts MeshServiceSubset, and `tags` is gone from the schema. - A MeshMultiZoneService backendRef requires `port`. - A PathPrefix other than `/` is rejected if it ends in a slash. Documented from source, not exercised at runtime: the 404 for a request matching no rule, the 500 for a rule whose backendRefs all fail to resolve or name a missing port, the 503 for an all-zero-weight list, and the Gateway API precedence order from CompareMatch. `targetRef.kind` is Mesh or Dataplane only; real resources are selected by labels. Four examples replace the three v2 ones, all applied to a v3 control plane and accepted, along with the page's inline block. v2.yml canonical URLs and the latest -> v2 example redirects now point at the migrated pages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The section was six warning callouts with no order or reasoning between them. Replace it with an ordered walkthrough: six steps, each saying what changed, what breaks if it is skipped, and before/after YAML where the rewrite is not obvious. The steps are ordered by what the change costs to miss, and the opening paragraph draws the distinction that decides that: the selector rewrites are rejected on apply, while the routing changes pass validation and change where traffic goes. Two facts verified live while writing it. A targetRef carrying name and namespace instead of labels is rejected with `labels (): must be set when kind is MeshService`, and a legacy top-level kind with `in body should be one of [Mesh Dataplane]` -- so both are loud, not silent. The 3.x before/after selector, including `k8s.kuma.io/namespace`, was applied to a v3 control plane and accepted. Also dropped UPGRADE.md's claim that MeshServiceSubset survives as a backendRefs kind. The validator restricts backendRefs to MeshService, MeshExternalService and MeshMultiZoneService, and a live control plane rejects MeshServiceSubset there, which agrees with the same document's other section on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same treatment as MeshHTTPRoute: ordered steps with before/after YAML, the exact validation error where a change is rejected, and an explicit note where it is not. The order in each is what the change costs to miss, so the silent ones come first. Checking the source rather than the existing text turned up several things the callout lists had wrong or missing. A top-level `kind: Dataplane` does not require `labels`. ValidateTargetRef requires them for MeshService, MeshHTTPRoute, MeshExternalService and MeshMultiZoneService, and for Dataplane requires nothing, so a 2.x `kind: Dataplane` with `name` is accepted, `name` is dropped, and the policy widens from one proxy to every proxy in the mesh. Verified live: a MeshTrafficPermission written that way was created and read back as a bare `kind: Dataplane`. That is a security change on MTP and a silent one everywhere, so it is now a warning on all seven pages. MeshRetry's note about `type: Exact` no longer being a schema default was wrong: HeaderMatch.Type still carries `+kubebuilder:default=Exact`. Replaced with the constraint that actually bites, the lower-case-only HeaderName pattern, which rejects a 2.x `Retry-After`. Exact errors and behaviours confirmed against a v3 control plane: - MTP with only `from`: `spec (): policy must define rules`. - MeshTimeout, MeshCircuitBreaker, MeshAccessLog with `from` alongside `to`/`rules`: created, and `from` absent when read back. With `from` alone: `spec (): at least one of 'to' or 'rules' has to be defined`. - MeshHealthCheck with `healthyPanicThreshold`: created, field absent when read back. - MeshAccessLog with `openTelemetry.endpoint`: `openTelemetry.backendRef (): must be defined`. The backendRef form was applied and accepted. - MeshRetry `resetHeaders[].name: Retry-After`: rejected by the HeaderName pattern. - Legacy top-level kinds: `in body should be one of [Mesh Dataplane]`. Added from source, not previously covered: which MeshTimeout fields sit under `http` and are lost when a Universal inbound has no protocol (connectionTimeout and idleTimeout are not); that MeshCircuitBreaker's to[] does not accept MeshHTTPRoute; the MeshProxyPatch circuitBreakers merge change; MeshTrafficPermission's MeshIdentity prerequisite, the legacy TrafficPermission becoming inert, and the three removed control plane settings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrite the page against the v3 source, with a migration guide in the same shape as the other policies. Verified in source and against a live v3 control plane: - `to[].targetRef` accepts only MeshService, MeshExternalService and MeshMultiZoneService. `Mesh` is rejected, so the 2.x gateway form (targetRef: MeshGateway with to[].targetRef.kind: Mesh) has no equivalent -- name the destination instead. - `backendRefs` is required: an empty or missing list is rejected with `backendRefs (): must be defined`. This differs from MeshHTTPRoute, where omitting it falls back to the destination. - `to` needs at least one entry, and a `to` entry accepts at most one rule, since a TCP rule has no matches to distinguish a second one by. - backendRefs no longer accept MeshServiceSubset. - Real resources are selected by labels only. From the xDS path, not exercised at runtime: an unresolved backendRef is dropped from the split rather than answered with an error, because a TCP proxy has no status code to answer with, and where none resolve the client gets no outbound listener for that destination at all (generateFromService returns nothing for an empty ref list). That is a different failure from MeshHTTPRoute's 500. Also from source: MeshHTTPRoute wins whenever the destination is HTTP based and any MeshHTTPRoute targets it, whatever either route configures (getBackendRefs returns nil in that case). Four examples replace the two v2 ones, all applied to a v3 control plane and accepted, along with the page's inline block. v2.yml canonical URLs and the latest -> v2 example redirects now point at the migrated pages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrite the page against the v3 source, with a migration guide in the
same shape as the other policies.
Verified in source and against a live v3 control plane:
- `to[].targetRef.kind: MeshHTTPRoute` accepts only `hashPolicies`.
`loadBalancer` and `localityAwareness` are rejected with `field is not
allowed when targetRef.kind is MeshHTTPRoute, only hashPolicies is
supported`.
- `localityAwareness.crossZone` is rejected on any kind but
MeshMultiZoneService, with `crossZone is only supported when
targetRef.kind is MeshMultiZoneService`.
- `affinityTags` weights are all-or-nothing: `all or none affinity tags
should have weight`.
- A failover `to.type` of Any or None must have no `to.zones`; Only and
AnyExcept must have some.
- `loadBalancer.ringHash.hashPolicies` is accepted and silently dropped,
not rejected -- UPGRADE.md left this open ("may be rejected by
validation or have those fields pruned"). Reading the policy back shows
an empty `ringHash: {}`, so the algorithm stays and has nothing left to
hash. That is now the first step of the guide, with a warning, since
requests that were pinned to an endpoint quietly stop being pinned.
The v2 page documents `hashFunction` as `XX_HASH` / `MURMUR_HASH_2`,
which the control plane rejects: `in body should be one of [XXHash
MurmurHash2]`. The enum has been those two values since at least 2.14, so
this is a docs error rather than an upgrade change, and the new page does
not present it as one.
From source, not exercised at runtime: locality awareness is on unless
disabled (isLocalityAware is true when localityAwareness is unset), and
`localZone`/`crossZone` apply regardless of `disabled`, because
atLeastOneLocalityGroup is evaluated independently of it. Unset
affinityTag weights are generated as 9*10^(n-i-1), which is the 90/9/1
split.
The 2.x Zone Egress caveats are dropped rather than carried over:
ZoneEgress and ZoneIngress are removed in v3, so the restriction they
imposed on the top-level targetRef is gone with them.
Five examples replace the five v2 ones, all applied to a v3 control plane
and accepted, along with the page's inline block.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The ten policy pages each carried their own "Upgrading from 2.x" section, which put the migration in front of readers who were there to configure a policy, and repeated the shared changes ten times. Move the content to app/mesh/migrate-policies-to-3.md and link to it from each policy's related resources, second after the targeting page. Consolidating removed real duplication rather than just relocating it. Stated once on the new page instead of per policy: - Legacy top-level targetRef kinds, with the warning that a `kind: Dataplane` carrying `name` is accepted and widens the policy to the whole mesh. That was repeated on eight pages. - Real resources selected by labels, and the contrast that the four real-resource kinds require labels while Dataplane does not. - The `from` to `rules` move, now a table of which policies reject a stored `from` and which accept and drop it, since that differs. - The MeshHTTPRoute 404, which reached four other policy pages because they can attach to a route. - The Universal inbound protocol change, which listed a different subset of lost L7 filters on each page it appeared on. - The legacy policies going inert, previously split between the MeshTrafficPermission and MeshTCPRoute sections. - MeshServiceSubset backendRefs and "a route names a destination", shared by the two route policies and previously written out twice. Policy pages drop from 3080 to 2187 lines; the migration page is 585. No content is lost: every verified error string and behaviour from the per-policy sections is carried over. Front matter validates, the page builds, all internal anchors and outbound links resolve, and vale is clean. The latest mesh tree has no navigation index yet (only app/_indices/mesh/v2.yaml exists, and policy-targeting.md is not registered anywhere either), so the page is reachable by link and will need adding to the sidebar when that index lands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrite the page against the v3 source. Upgrade notes go to /mesh/migrate-policies-to-3/, not onto the policy page. The `to` and `rules` split is stricter than any other policy, and is verified live in all four combinations: - `targetRef: Dataplane` accepts `rules`; `to` is rejected with `spec.to (): must not be defined`. - `targetRef: Mesh` accepts `to`; `rules` is rejected with `spec.rules (): must not be defined`. - Both together: `field 'to' must be empty when 'rules' is defined`. - Neither: `at least one of 'to' or 'rules' has to be defined`. That leaves no way to write a mesh-wide inbound limit as `kind: Mesh`. A bare `kind: Dataplane` with no labels does it instead, which was also applied and accepted. Both facts are on the page and in the migration guide, since a 2.x policy carrying both fields has to become two. Also verified live: a rule whose matches contain a spiffeID cannot carry `local.tcp` (`can't be specified when matches contain spiffeID because this field cannot be conditioned on source identity`), because the identity comes out of the TLS handshake that a connection-level limit is counting rather than inspecting; and an interval of 10ms is rejected with `must be greater than: 50ms`. From source: the limit is a local token bucket enforced per proxy, so a destination's effective ceiling is the limit times its replica count -- worth stating, since MeshGlobalRateLimit does not exist on v3. The default rejection status is 429, confirmed by the generated listener (`status: code: TooManyRequests` when `onRateLimit.status` is unset). Five examples replace the two v2 ones, all applied to a v3 control plane and accepted, along with the page's four inline blocks. MeshRateLimit is added to the shared `from` table on the migration page as one of the policies that accepts and silently drops a stored `from`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nges Rewrite the page against the v3 source, with its upgrade notes on /mesh/migrate-policies-to-3/. Re-fetching kuma master first turned up sixteen UPGRADE.md sections added since I extracted it earlier in this work, two of which land on pages already written: - `MeshHTTPRoute`/`MeshRetry` header matches and `MeshHTTPRoute`/`MeshTCPRoute` backend refs no longer declare schema defaults for `type` and `weight`. Both kubebuilder annotations are gone from api/common as of today. An earlier commit removed a MeshRetry note saying exactly this, on the grounds that the annotation was still present -- which was true when I checked and is no longer. Restored as a shared note, now covering weight too. Behaviour is unchanged; only what the API echoes back differs. - OpenTelemetry `backendRef` backends always export through kuma-dp, and the two settings that made Envoy export directly are removed. Added to the MeshAccessLog section, and it applies to MeshTrace and MeshMetric when those are written. MeshPassthrough itself has two changes from commits dated today: - A `Domain` match is resolved by the sidecar and pinned to a port instead of building an ORIGINAL_DST cluster. The old behaviour let a workload dial any address, present an allowed domain, and reach that address through the policy. A non-wildcard `Domain` now requires `port`; a stored match without one stops applying, and where it was the policy's only match nothing is left to allow. - Two matches resolving to the same Envoy filter chain are rejected on apply, where such a policy used to be accepted and Envoy then rejected the whole listener. Verified live: domain with `tcp`, partial wildcard, wildcard with an L7 protocol and no port, `mysql` without a port, a duplicate value on one port and protocol, and an invalid CIDR are all rejected with the strings quoted on the page. Distinct chains are accepted -- `tcp` and `tls` on one address, two `http` domains on one port -- as is one domain repeated across two ports. The domain-requires-port check could not be exercised: the Konnect control plane was created 2026-09-07 and the commit landed 2026-09-11, so it still accepts a portless domain. Documented from source and UPGRADE.md rather than from the live response. Also from source, replacing the v2 page's weaker wording: without transparent proxying the plugin is skipped entirely and the proxy is given the warning `policy doesn't support proxy running without transparent-proxy`; and `passthroughMode` defaults to Matched, where the v2 page says None. Four examples replace the three v2 ones, all applied to a v3 control plane and accepted, along with the page's two inline blocks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two changes. Each policy page's "Migrate policies to Kong Mesh 3" link now points at that policy's own section rather than the page root. A deep link that skipped the shared changes would have been worse than no deep link, because for some policies the shared changes are all of them, so every policy section now opens with a line naming the shared changes that apply to it and linking to each. MeshTimeout had no section of its own for that reason -- every change affecting it is shared -- so it has one now that says so and lists them. All twelve deep links resolve against the built page. MeshGlobalRateLimit is dropped rather than migrated: it does not exist on v3. The latest stub is deleted, so /mesh/policies/meshglobalratelimit/ and its generated reference page are gone. The release map loader takes either `status: pending` or a non-nil canonical_url, with no state for "removed", so the three v2 entries now point at the migration page -- which is the successor to those pages, in that it explains where the policy went. The dead latest URLs redirect to the removal note. That note comes from the vendored kong-mesh UPGRADE.md, which already documents the removal: the policy and its control plane support are gone along with the rate-limit service the Helm chart deployed, so the `ratelimit.*` / `global.ratelimit.*` values, the `KMESH_GLOBAL_RATE_LIMIT_*` env vars and `kmesh.globalRateLimit` all need removing before upgrading. A leftover resource becomes inert rather than rejected, and the CRD outlives the upgrade because Helm does not delete CRDs. The note also says plainly that there is no replacement for a fleet-wide limit, since MeshRateLimit is local to each proxy. Re-fetched all three repos first: kuma is at 89cc0192f6 and added no new UPGRADE.md sections since the last catch-up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrite the page against the v3 source, with upgrade notes on /mesh/migrate-policies-to-3/. The `value` validation claim I first wrote was wrong, and the live control plane caught it. An unrecognized Envoy field name is accepted, stored in the policy verbatim, and then dropped when the config is generated -- util_proto.FromJSON sets AllowUnknownFields: true. A misspelled key therefore passes validation and silently does nothing, while the policy reads back looking correct. That is now a warning on the page, with the advice to check a new modification against the proxy's /config_dump rather than the stored policy. What *is* rejected is a wrong type or enum, verified live: `bad Duration: time: invalid duration "not-a-duration"` and `unknown value "NOT_A_REAL_TYPE" for enum envoy.config.cluster.v3.Cluster.DiscoveryType`. Other rejections confirmed against the live control plane: two modifications in one entry, `AddBefore` without `match.name`, `Add` with a `match`, `Patch` with both `value` and `jsonPatches`, an empty `appendModifications`, and an operation outside a resource type's set. The v2 page's origin list is wrong for v3 in both directions. It lists `gateway`, which no longer exists now the built-in gateway is removed, and omits `admin` and `proxy-template-modifications`. The new list comes from the metadata constants, plus the five policy-introduced origins (`meshaccesslog`, `mesh-trace`, `open-telemetry`, `meshpassthrough`, `mesh-proxy-patch`) read from their own packages. I expected `ingress` and `egress` to be dead too, since standalone ZoneIngress and ZoneEgress proxies are removed, and checked rather than assuming: ZoneProxyListenerGenerator is still in NewDefaultProxyProfile, so cross-zone listeners and clusters are still generated with those origins on ordinary proxies. Both are documented as live, and the migration note says so explicitly. One source inconsistency documented as-is: an added cluster is marked `proxy-template-modifications` (cluster_mod.go) while an added listener is marked `mesh-proxy-patch` (listener_mod.go). A user matching on origin would hit that, so the page names both rather than picking one. Four examples replace the two v2 ones, all applied to a v3 control plane and accepted, including the hand-written static cluster and virtual host, which exercises the Envoy proto validation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrite the page against the v3 source, with upgrade notes on /mesh/migrate-policies-to-3/. Verified in source and against a live v3 control plane: - `tlsVersion` and `tlsCiphers` are accepted only with a top-level `targetRef.kind: Mesh`, each rejected on a Dataplane with `<field> can only be defined with top level targetRef kind: Mesh`. `mode` is accepted on either. The page gives the reason: both peers have to agree on a version and cipher, so setting them per workload would let one end permit what the other refuses. - `min` above `max` is rejected with `min version must be lower than max`, and `TLSAuto` at either end is not compared. - A cipher outside the six is rejected, with the error listing them. From source: an inbound is Strict unless a MeshTLS policy sets otherwise -- getMeshTLSMode returns ModeStrict for a nil mode, with the comment that only a MeshTLS policy can select Permissive. Permissive adds a raw-buffer filter chain beside the TLS one, so it changes the inbound side only and outbound stays encrypted. The plugin skips a proxy with no workload identity entirely, logging `skip applying MeshTLS, the proxy has no workload identity`, so the page says a MeshIdentity is needed first. `tlsCiphers` sets TlsParameters.CipherSuites, which Envoy applies to TLS 1.2 and below, so the page notes that restricting ciphers while permitting TLS 1.3 constrains only the older versions. Two upgrade items, both silent: - A mesh that relied on `mtls.backends[].mode: PERMISSIVE` gets Strict inbounds, because the mode now resolves from the policy alone and `Mesh.mtls` is removed from the API. Needs a policy authored before upgrading. - `from` is removed. Unlike the other policies that lost it, MeshTLS has no check that the remaining spec configures anything, so a from-only policy is *accepted* rather than rejected. Verified live: it applies and reads back as nothing but a targetRef, and since an inbound with no policy is Strict, a `from` that allowed plaintext silently stops allowing it. Also documented: a permissive inbound cannot identify a plaintext client, since the identity comes out of the handshake, so a MeshTrafficPermission matching on spiffeID has nothing to match for that traffic. Four examples replace the two v2 ones, all applied to a v3 control plane and accepted, along with the page's two inline blocks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrite the page against the v3 source, with upgrade notes on /mesh/migrate-policies-to-3/. The one breaking change is the OpenTelemetry backend: the inline `endpoint` is gone and `backendRef` naming a MeshOpenTelemetryBackend is the only form. Verified live -- `endpoint` is rejected with `openTelemetry.backendRef (): must be defined` -- and the backendRef form was applied and accepted. Also verified against the live control plane: a Datadog url must use the http scheme and carry a port and nothing else (`scheme must be http`, `port must be defined`, `path must not be defined`); a tag setting both `header` and `literal` is rejected; and a sampling value outside 0-100 is rejected with `must be between 0 and 100`. `backends` behaves asymmetrically and the page says so: omitting the field is rejected with `must be defined`, while an empty list is accepted. Both confirmed live. Reading the plugin, an empty list makes Configure return before writing any tracing configuration, so `backends: []` is how a subset of proxies is exempted from a mesh-wide policy -- and `sampling` and `tags` alongside it are inert, which the page states rather than leaving to be discovered. Dropped a claim I could not support. I had written that the proxy propagates trace headers whether or not the policy is configured; nothing in the source says that. Replaced with what the v2 page documents and the architecture requires: the application has to carry the headers across, with the six b3 headers named. Five examples replace the five v2 ones, all applied to a v3 control plane and accepted, along with the page's four inline blocks. The two v2 "target east/west" examples have no example-level successor -- they demonstrated MeshSubset targeting, which is gone -- so they point at the policy page rather than at a misleading match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page opened with "an inbound is Strict unless a MeshTLS policy says otherwise, so plaintext is rejected by default", and only mentioned the identity requirement two paragraphs later. Read in order, that tells a reader with no MeshIdentity the opposite of what happens: per the kong-mesh upgrade notes, a proxy no MeshIdentity matches gets no mTLS transport socket at all and serves plaintext, and MeshTLS does not apply to it. Reordered so the precondition comes first, and the Strict default is stated as following from having an identity. Also states plainly that a mesh wanting mTLS everywhere on Envoy's default version and ciphers needs no MeshTLS at all, and that MeshIdentity carries no TLS version, cipher or mode fields, so the two do not overlap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrite the page against the v3 source, with upgrade notes on /mesh/migrate-policies-to-3/. Found a silent security regression that neither UPGRADE.md mentions. `prometheus.tls.mode: ActiveMTLSBackend` is still in the CRD enum but has no Go constant and no handling: prometheus_configurer.go applies TLS only when the mode is ProvidedTLS and the certificate paths are set. Verified live -- a policy setting ActiveMTLSBackend is accepted -- so a 2.x mesh that used it keeps applying and serves its metrics endpoint in plaintext, with nothing reporting the change. It took its certificates from the mesh CA backend, which v3 removes. Documented as a warning on both the policy page and the migration guide, with the switch to ProvidedTLS. That enum value looks like it should have been dropped with the CA backend, and is worth raising with the Mesh team alongside the other source inconsistencies collected so far. Also verified live: the OpenTelemetry inline `endpoint` is rejected with `openTelemetry.backendRef (): must be defined`; an uncompilable Regex selector with `invalid regex`; an unknown profile name with `in body should be one of [All Basic None]`; an out-of-range application port with `port must be a valid (1-65535)`; a `Contains` selector is accepted; and more than one backend is accepted, unlike MeshTrace which caps at one. The validator's selector error says only Regex, Prefix and Exact are supported while the enum and the switch both accept Contains, so the page documents the four accepted values rather than quoting that message. Also on the migration page: MADS no longer starts on Kubernetes control planes whatever the setting says, so deployments discovering proxies that way need Prometheus Kubernetes service discovery instead. Five examples replace the one v2 example, all applied to a v3 control plane and accepted, along with the page's two inline blocks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MeshOPA is enterprise, so the source is in kong-mesh and the upgrade
notes are in the vendored kong-mesh UPGRADE.md rather than Kuma's. That
guide carries four MeshOPA sections the Kuma one does not, which is why
reading both now matters:
- `agentConfig` and `appendPolicies[].rego` move from the removed flat
DataSource type to SecureDataSource, which needs a `type` discriminator
and nests the value under a matching field. Verified live: the old flat
shape is rejected with `rego.type (): in body is required`, and the new
shape is accepted. `inline` was base64 and `insecureInline.value` is
plain text, so the note says to decode when rewriting -- otherwise the
agent gets base64 where it expects rego.
- `targetRef.kind: MeshService` is rejected (verified live).
- `targetRef.name`, `namespace` and `mesh` are pruned rather than
rejected, so a policy scoped to one service silently widens to every
proxy matching `kind` and its rego starts evaluating traffic it never
saw. Now a warning.
- The legacy OPAPolicy resource and CRD are removed.
Verified live besides: an inline rego policy is compiled at write time,
so a syntax error is rejected with the OPA parse error attached; a
statusOnError of 700 is rejected; and a Secret-backed rego is accepted
without being compiled, since the control plane never reads it.
Found a reachable nil dereference in the kong-mesh validator, and did not
trigger it. validator.go:65 dereferences RequestBody.SendRawBody directly
while guarding MaxSize with pointer.Deref, so a `requestBody` with no
`sendRawBody` -- including `requestBody: {}` -- panics inside validation.
There is no panic recovery in pkg/api-server, so I documented it from
source and told the page to set `sendRawBody` explicitly rather than
demonstrating it against a shared control plane.
Also fixes two icons I had invented: meshloadbalancingstrategy.png and
meshopa.png do not exist in app/assets/icons/mesh_policies/. Both v2
pages use policy.svg, which is what these now use. Audited every latest
policy page's icon against the files on disk; the rest resolve.
Five examples replace the one v2 example, all applied to a v3 control
plane and accepted, along with the page's three inline blocks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rces to the render cap These two are resources rather than policies -- no targetRef, to or rules, and accepted only in the system namespace on Kubernetes -- so the pages describe a label selector and issuance rather than traffic selection, and the migration guide says the shared changes do not apply to them. Verified against the live control plane, with names and selectors neutralised: applying these examples as written would have overwritten the working `default-identity` and, in the case of `corporate-ca`, won selection over it (BestMatched ties on match-label count break lexicographically) and changed the trust domain of every proxy in the mesh. So each was applied under a zzprobe- name with a selector matching no workload, which exercises the validator without touching live mTLS. Validation is what applying verifies; selection is not checked at write time. Rejections confirmed live: a Bundled provider with neither autogenerate nor ca (`ca (): must be defined`); ca alongside autogenerate (`shouldn't be defined once using autogenerated`); an unparseable trustDomain template; an unknown provider type; a negative certificate expiry; a MeshTrust with no caBundles, with a garbage PEM, and with a PRIVATE KEY block where the certificate belongs -- isPEMCertificate rejects key blocks by type, so that fails on apply rather than at the proxy. Two behaviours found by applying rather than reading: - A MeshIdentity using the default trust domain template produces one MeshTrust per zone, with a different trust domain each (default.zone1.mesh.local and default.zone2.mesh.local on this two-zone control plane). - Deleting a MeshIdentity does not delete the MeshTrust it generated. The generated resource carries kuma.io/origin: zone, so the Konnect API refuses to delete it (`the origin label must be set to 'global'`), with or without --force. Now a warning in the migration guide. Also fixes a silent truncation I had introduced. The layout renders only four related resources, and adding the migration link pushed ten pages to five -- MeshTrafficPermission to seven, which was already over the cap before this work. Each is now trimmed to four. Where plain truncation would have dropped the more important link, the choice is explicit: MeshTrafficPermission keeps MeshIdentity and the bundled-provider how-to rather than losing the prerequisite, and MeshIdentity keeps MeshTrafficPermission. Four MeshIdentity examples replace the three v2 ones and two MeshTrust examples replace the one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…licies Closes the gap the policy migration created: MeshAccessLog, MeshTrace and MeshMetric all tell the reader to create one of these, and there was no page to send them to. All three now link to it, as does the migration guide. The v2 page is recent and its content holds up against the v3 source -- the env policy, the signal states and the blocked reasons all match constants in pkg/core/xds/otel_pipe.go and pkg/xds/otel/status. The one thing that changed is the reference shape: BackendResourceRef in v3 carries `kind` and `labels` only, with labels required, so the v2 page's guidance about choosing between `backendRef.name` and `backendRef.labels` (and the hash suffix on synced names) no longer applies. Labels are the only option, and the page says the oldest matching backend wins, which makes over-broad labels a creation-order dependency rather than an error. Verified against a live v3 control plane: an empty spec is accepted, and is the node-local default where kuma-dp resolves the address from HOST_IP; `endpoint.path` is rejected with `must not be set when protocol is grpc`, `must start with /` and `must not contain query or fragment`; a non-hostname address, an out-of-range port and an unknown `env.mode` are each rejected with the strings quoted on the page. Also trimmed: the v2 page ran to 619 lines with five worked examples and a troubleshooting walkthrough per failure state. This one keeps the field reference, the environment-variable merge order, the one-backend-per- signal constraint and the status fields, and drops the repeated examples. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eService The three destination kinds the route and load-balancing pages name throughout. None is a policy -- each *is* a target, so there is no targetRef -- and the pages are shaped around that. Verified against a live v3 control plane: - MeshService is read only there: a write is refused with `Mesh Service is read only on this control plane and cannot be created or updated`, because on Kubernetes the control plane generates one per Service. The page leads with that, since it changes what a reader does about it. No examples were applied for it as a result. - MeshExternalService: `match.protocol` accepts tcp, grpc, http and http2 and rejects `tls`, unlike MeshPassthrough; a clientCert without a clientKey is rejected with `must be defined when clientCert is defined`; a serverName that is not a DNS name is rejected. A full TLS origination spec with priorities and SAN matches was accepted. - MeshMultiZoneService: an empty ports list is rejected with `in body should have at least 1 items`, and a label-selected aggregate was accepted. Two things from source worth stating plainly: - `MeshService.spec.selector.dataplaneTags` was removed in 3.0, and a resource carrying only that selector is read back with an empty selector matching no proxies. The control plane reports it as a deprecation rather than an error, so it is a warning on the page and belongs in the migration guide. - `MeshService.spec.identities[].type: ServiceTag` is gone from the v3 API, yet the live control plane still emits ServiceTag entries on generated MeshServices -- it predates the change. Documented as no longer accepted, per the upgrade notes. MeshService's `tls.status: Pending` gets its own explanation: it exists because a proxy is certified independently of its destination, so flipping clients to mTLS in the same pass would drop requests sent before the destination's inbound chain arrived. A status stuck there is a different problem from the handover being in progress, which the page says. Also resolved the four legacy ExternalService entries in the release map. They were pending against `/mesh/meshexternalservice/`, which now exists; the two zone-egress examples point there too, ZoneEgress being removed. That takes _mesh_policies to zero pending entries. Linked the new pages from MeshLoadBalancingStrategy and MeshPassthrough in prose, both being at the four-entry related_resources cap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for kongdeveloper ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Replaces the v2 kumactl page. Written against the binary rather than the implementation: every command and flag shown was run, and a check confirms all seventeen command forms on the page resolve. The framing difference from kumactl leads: kongctl reads the resource types from the control plane instead of carrying a compiled-in list, so `get mesh resource-types` is authoritative for whatever control plane is in front of you and a newer policy needs no newer CLI. Includes a kumactl-to-kongctl mapping table and the behaviours that differ beyond command names. Two of those are worth calling out because they are gaps rather than changes: - There is no `inspect` equivalent. I checked before writing that this was a v3 removal and it is not -- the control plane still serves `_policies` and `_resources/dataplanes`, and `kumactl inspect` still reaches them. So the page says plainly that this is the one case where kumactl still does something kongctl cannot, rather than implying the capability is gone. - Installation has no counterpart under `kongctl mesh`. `kongctl install` exists but installs kongctl's own features, which the page disambiguates so nobody goes looking for a control plane installer. Documented a flag collision I introduced in the implementation: on `dump mesh`, `--profile` means the export profile and the global `-p, --profile` that selects a kongctl configuration profile is unavailable. Verified both directions -- `dump mesh --profile default` is rejected as an invalid export profile, and `-p` is not accepted at all. It is a warning on the page for now; renaming the export flag would be the better fix and would make that warning deletable. Verified live: type names resolve as plural, singular or short alias, and matching is case insensitive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The CLI page said inspect was the one case where kumactl still did something kongctl could not. That is now implemented, so the claim is removed and the command documented. Notes the part that differs from kumactl: inspect dataplane reports per port rather than per proxy, because the control plane only answers with policy data on the scoped endpoints, and because a policy applying to a proxy's inbound but not its outbounds is the case that is hard to see any other way. Also says plainly what each --type needs. Only `policies` works without the proxy being reachable; the rest relay Envoy admin output and need the zone connected and the proxy up, with the failure at either hop reported as it arrives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Universal is where the responsibility split actually matters, so the set leads with that rather than with installation: - /mesh/universal/ — what Universal is for, a concern-by-concern table of Kubernetes against Universal, and the changes that only bite here. - /mesh/universal-control-plane/ — modes, stores, joining a zone to a global, and what the control plane generates. - /mesh/universal-data-plane/ — the Dataplane resource, labels, inbounds, outbounds, transparent proxying, readiness, zone proxies and tokens. Reading the code corrected the premise I started from. The control plane does *not* leave MeshServices to the user on Universal: a zone control plane on a non-Kubernetes store generates one per distinct `kuma.io/workload` label value, with a port per inbound, reconciled every 2s by default and removed an hour after the last proxy goes. Same for Workload resources. The generator is skipped for Global mode, for a Kubernetes store, and when the interval is set to 0 -- at which point it logs `MeshServices must be managed manually`. So the rule the pages give is: set `kuma.io/workload` and the MeshService follows; omit it and nothing is generated. Two findings reshape how much Universal matters in 3.0: - A Global control plane can no longer run on Kubernetes at all. `mode: global` is rejected with `environment: kubernetes` and again with `store.type: kubernetes`. Every multi-zone deployment now has a Universal control plane in it, whatever the zones run. - Standalone zone proxies are gone. Cross-zone traffic runs on ordinary Dataplanes with `networking.listeners` of type ZoneIngress/ZoneEgress, authenticating with dataplane tokens rather than zone tokens. Three v3 changes are silent and land only on Universal, so each is a warning: inbound `tags` removed and dropped on read; outbound `tags` replaced by a required `backendRef`, where a stored resource keeps being served but generates no listener; and `networking.inbound[].protocol` no longer falling back to the `kuma.io/protocol` tag, which turns a port TCP and strips its L7 policies. The Dataplane YAML could not be applied to the live control plane -- `Dataplane is read only on this control plane`, since it is a Kubernetes zone and Pods generate them, which is itself the point these pages make. It is instead checked field by field against the proto and against test/framework/dataplane_template.go, the canonical Universal fixture, whose own comment confirms the framing: identity is "sourced solely from Dataplane labels, not inbound tags". Six superseded v2 pages now point at these as their canonical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New pages for v3 alongside the v2 series, not replacing it: the v2 pages stay at /mesh/v2/get-started/universal/ untouched, and these live at /mesh/get-started/universal/. Given its own series id, `mesh-get-started-universal-3`, because the series generator groups purely by `series.id` with no version awareness. Sharing the v2 id would have put ten pages with duplicate positions 1-5 into one series, and next/prev use `.find` on position, so a v3 page would have linked to a v2 page. That is not hypothetical. `mesh-scoped-zone-proxy` already shares an id across both versions, and the built page proves the consequence: the latest /mesh/zone-proxies/ page's "Next" link points at /mesh/v2/zone-proxy-policies/, sending a v3 reader into v2 docs. Left alone here as pre-existing, but it wants the same fix. Page 1 covers the install and the two files the rest of the series reuses, and uses the Dataplane template to introduce the three things Universal actually asks of you: the kuma.io/workload label that drives MeshService generation, ordinary labels now that inbounds carry no tags, and the inbound protocol that silently costs you every L7 policy when omitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five pages at /mesh/get-started/universal/, additive to the v2 series which stays untouched at /mesh/v2/get-started/universal/. Page 5 is a rewrite rather than an adaptation, because most of what the v2 version teaches no longer exists: `Mesh.mtls` and `meshServices.mode` are removed from the Mesh schema, MeshTrafficPermission's `from` array, `action` field and MeshSubset targeting are removed, and the entire built-in gateway is gone -- MeshGateway, MeshGatewayRoute, MeshGatewayInstance and MeshGatewayConfig, their CRDs, and MeshGateway as a targetRef kind. So it teaches MeshIdentity, spiffeID allow rules, and names the delegated gateway as the replacement rather than walking through a gateway that cannot be built. It also states the 2.x deny-all-then-allow idiom is now wrong: every deny is evaluated before every allow, so a mesh-wide deny can never be re-opened. The series carries its own id, mesh-get-started-universal-3, and the v2 entry in series.yml is repointed at the v2 install page -- it was aimed at the v3 URL while all five of its pages are v2, which sent readers across versions. Verified: zero links into /mesh/v2/ from any of the five pages, and the series navigation resolves among themselves. One invented command caught before it shipped: page 2 had `kongctl config set`, which does not exist. kongctl takes configuration from a profile-keyed YAML file at $XDG_CONFIG_HOME/kongctl/config.yaml, so the page now shows that file and uses --control-plane-url explicitly throughout. The container steps are not executed end to end -- no Universal control plane was available -- so they are written from the flags and config the source defines, and every kongctl command shown exists in the binary. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Initial commit of Mesh v3 policies
Description
Fixes #7146
Preview Links
Checklist
descriptionentry in frontmatter.