kubernetes implemented read profiles/v1alpha1
Rendered from adapters/kubernetes/contracts/reads/v1alpha1/semantics.md by connectors-docs; the source owns every rule on this page.
This adapter owns the native behavior of its existing local implementation. Shared wire envelopes and admission remain in service v1alpha1. This relocation changes no runtime or selected codec.
Kubernetes: paginated allowed resource lists, EndpointSlice-derived endpoint observations, and explicitly enabled node/host discovery. Namespace and kind allowlists precede HTTP dispatch. ResourceVersion and continue tokens remain observable; expired snapshots return a stale-cursor failure. Endpoint observations carry source UID, namespace, address/port/protocol, service association, readiness and observation revision. Classifications are candidates, not connection grants. Discovery never dials, authenticates to, or materializes the observed endpoint. Source facts: https://kubernetes.io/docs/reference/using-api/api-concepts/ and https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/. Source access: 2026-09-08.
Single objects, namespaces, events and rollout history
The typed selections are in the adapter's ESS model
(reads domain). Every read below keeps the
rules above: the configured namespaces bound it, the configured resource_kinds
admit each kind it reads, it writes nothing, and items are full provider objects.
Each scope refusal is forbidden and precedes every provider request.
- Kinds.
resource_kindsalso admitsreplicasets(apps/v1) andevents(core/v1 Event, the collectionkubectl get eventsreads).resources.listreads them like any other kind: one bounded page, no watch. resources.get(kubernetes-get) reads one object of an admitted kind by name:GET <collection>/<name>. The name must be a DNS-1123 subdomain and is refused asinvalid_inputotherwise, so it never adds a path segment. A 404 answersnot_found, distinct from an emptyresources.listpage. The result is a page of exactly one item withcomplete: true, no cursor and the object's ownresourceVersionas its source revision; an object reporting another name or namespace isupstream_protocol. A separate operation, rather than anameinput onresources.list, keeps a list's empty page and a missing object distinct.namespaces.list(kubernetes-configured-namespaces) reads each configured namespace by exact name (GET /api/v1/namespaces/<name>) in configuration order and lists the present ones. A 404 is an absent namespace and is omitted; any other refusal refuses the page, because a denied namespace is not evidence of absence. The cluster-scoped namespace collection is never listed, so a namespace outside the configured set can neither be read nor observed: namespace authority is the configured set it was before.limitbounds the configured names read per page; the continuation is an adapter-issued position bound to the configured list and page size, not a provider token. The page carries no collection revision.deployments.history(kubernetes-rollout-history) needs bothdeploymentsandreplicasetsadmitted. It reads the Deployment (a 404 isnot_found), lists ReplicaSets with the Deployment's ownspec.selectortranslated to a label selector, and keeps a ReplicaSet only when anownerReferencesentry hascontroller: true, kindDeploymentand the Deployment's uid; a label match alone is not ownership. A selector this binding cannot carry exactly (empty, or a key or value outside the label grammar) refuses before the list. Each item's revision is itsdeployment.kubernetes.io/revisionannotation. Items keep provider order; the provider's continuation and collection revision are carried as forresources.list, and the continuation is bound to the Deployment's uid. The controller prunes beyondspec.revisionHistoryLimit, so a missing revision is pruned or never existed, and the page cannot say which.
Required RBAC, per configured namespace: get on namespaces for
namespaces.list (a namespace object's authorization namespace is its own name),
get on the admitted kind for resources.get, list on replicasets and
events for those kinds, and get on deployments with list on replicasets
for deployments.history.
Statefulsets, nodes and custom resources
statefulsetsis an admissibleresource_kindsentry (apps/v1,/apis/apps/v1/namespaces/<ns>/statefulsets).resources.listandresources.getread it like any other kind. Required RBAC:list(andgetforresources.get) onstatefulsetsin each configured namespace.nodes.list(kubernetes-nodes-list) listsGET /api/v1/nodesone bounded page at a time (limit1–100, provider continuation and collection revision as forresources.list), each item the full Node object. Nodes are cluster-scoped, so the read takes no namespace and needs cluster-widelistonnodes; it is a separate admission,node_reads: true, never implied by the configured namespaces. Absent, the operation is not advertised and an invocation isforbiddenwith zero requests. It is distinct fromhosts.discover, which reads the same collection underdiscover_hostsand returns a fixed projection; enabling either does not enable the other, and a continuation issued by one is not readable by the other.custom_resources.list(kubernetes-custom-resources) lists one namespaced custom resource collection,GET /apis/<group>/<version>/namespaces/<ns>/<plural>, one bounded page at a time, items the full objects. Its input isnamespace,group,version,plural,limit(1–100) andcursor. The configuration admits each collection explicitly incustom_resources(at most 32 distinct{group, version, plural}entries); the operation is advertised only when it admits at least one. A selection that is not a configured entry, or a namespace outside the configured set, isforbiddenbefore any request. A configuredgroupmust be a DNS-1123 subdomain containing a dot and must not bek8s.io,kubernetes.ioor a subdomain of either, which is Kubernetes' own rule for a custom resource's group and keeps every built-in collection, Secrets included, out of this allowlist;versionandpluralare DNS-1123 labels. A configuration breaking any of these is refused at load. A configured collection the cluster does not serve answers the provider's 404 asnot_found, never as an empty page. The continuation is bound to the exact collection, so a cursor from one custom resource cannot page another. Cluster-scoped custom resources are not admitted. Required RBAC:liston<plural>in API group<group>in each configured namespace it is read in.
Exec-plugin credentials
A kubeconfig user that authenticates through an exec credential plugin (for
example aws eks get-token) is supported in one pinned form, decided on
2026-10-15 (decision-blocker:kubernetes-exec-credential-plugin, option b) inside
the bounded pinned-binary process family of 2026-10-03. Running whatever the
kubeconfig names is refused. The typed binding, selection, lifetime and refusals
are the exec_credentials ESS domain.
- Only by explicit configuration. The local native configuration names an
exec_pluginbinding:path, the plugin's absolute path;sha256, the hex digest of its bytes;user, the kubeconfigusersentry whoseexecblock supplies the arguments;env, the allow-list of environment variable names (eachExecPluginEnvName: neverHOMEorKUBERNETES_EXEC_INFO, and never one of these names, which load code or change how a loader or shell starts: the dynamic loader'sLD_*; the interpreter and runtime start-up namesPYTHON*,PERL*,RUBY*,NODE_*,DOTNET_*(DOTNET_STARTUP_HOOKSamong them),COREHOST_*,LUA_INIT,LUA_INIT_*,JAVA_TOOL_OPTIONS,_JAVA_OPTIONS,JDK_JAVA_OPTIONS,CLASSPATHandTCLLIBPATH; the shell start-up and parsing namesBASH_ENV,ENV,SHELLOPTS,BASHOPTS,PS4,IFS,CDPATH,GLOBIGNORE,PROMPT_COMMAND,ZDOTDIRandFPATH; the library module and configuration namesOPENSSL_CONF,OPENSSL_MODULES,OPENSSL_ENGINES,GIO_MODULE_DIRandGTK_MODULES; and the names glibc drops for a set-ID program other thanLD_*,TMPDIRandTZDIR:GCONV_PATH,GETCONF_DIR,GLIBC_TUNABLES,HOSTALIASES,LOCALDOMAIN,LOCPATH,MALLOC_TRACE,NIS_PATH,NLSPATH,RESOLV_HOST_CONFandRES_OPTIONS.TMPDIRandTZDIRload no code and are admitted. The contract refuses exactly the listed names and prefixes; it does not claim that no other variable can influence a plugin, and a name not listed that a particular plugin or library reads is the operator's to allow-list or not. A binding naming a listed one is refused when the configuration loads); optionallyhome, an absolute directory; and optionallytimeout_ms(1 to 60000, 10000 when omitted). It requireskubeconfig. No default configuration and no federated service configuration enables it. Absent, no plugin ever runs, thekubernetes.exec_pluginprofile is not offered and validating it isunsupported. The binding enters the effective configuration asexec_plugin_digest, so any change to it changes the configuration revision. - The profile. With a binding, the bootstrap offers
kubernetes.exec_plugininstead ofkubernetes.token, and every operation requires it. Its scheme ishttp_bearer: the plugin's token is sent as a bearer token. Its one protected entry field,user, is a confirmation, not a secret: it must equal the binding'suser, so connecting is an explicit act that names the kubeconfig user the plugin runs for. The SelfSubjectReview identity probe and every rule of the token profile apply to the plugin's token unchanged. - The executable. Before every run the adapter opens
pathonce, without following a symbolic link as its final component, refuses a file that is not regular, is writable by group or others, carries the set-user-ID or set-group-ID bit or file capabilities (asecurity.capabilityextended attribute), or is owned by a user other than root and the adapter's own, and hashes its bytes through that open descriptor; a digest that is notsha256refuses before any process starts. The process executes that same descriptor (fexecve), neverpathagain, so a file swapped in atpathafter the check never runs.pathmust therefore name the file itself, not a symbolic link to it. A script plugin (#!) keeps the descriptor open across the exec, because its interpreter reads the script through/dev/fd; a native executable inherits no descriptor. Besides that one descriptor and its standard input, output and error, the plugin inherits no descriptor of the adapter: every other one is closed on the exec, whether or not the adapter opened it close-on-exec. Limitation: on a kernel before Linux 5.11, which cannot mark a range close-on-exec in one call, the adapter marks descriptors one by one up to the open-files hard limit, capped at 1048576 (2^20, the defaultfs.nr_open); wherefs.nr_openwas raised and the adapter holds a descriptor numbered above that cap, that descriptor is not marked. On macOS, which has nofexecveand no file capabilities, the guarantee is weaker: the adapter runs the pinned path, not a verified image, so root or a process of the adapter's own user can replace or rewrite the file between the check and the exec. Other users cannot: the directories ofpathare resolved once (realpathof its parent), and every directory of the resolved path, from/down, must be owned by root or the adapter's user and not writable by group or others (the sticky bit does not help), or the run isplugin_unavailable. The same open, checks and hash then apply there, without the capability check, and right before the exec the resolved path must still name the file that was hashed (same device and inode). The adapter executes that path with the same argument vector and environment; a script keeps no descriptor across the exec, and descriptors are always marked one by one, with the same cap.pathmust name the plugin's real file, not a symbolic link to it such as the one a package manager puts onPATH. Running a plugin needs Linux or macOS; elsewhere it isplugin_unavailable. The kubeconfig'scommandmust equalpathbyte for byte (a trailing or repeated slash is another spelling and refuses) or be a bare file name equal byte for byte to the path's file name. - Arguments and environment. The kubeconfig is read when the plugin runs,
and only then: a use that a held token serves does not read it. Its
argsare the argument vector as data; no shell runs. The plugin starts from an empty environment and receives exactly:HOMEwhenhomeis configured (it is then also the working directory, otherwise/),KUBERNETES_EXEC_INFO(apiVersion,kind: ExecCredential,spec.interactive: false), and each allow-listed name, valued from the kubeconfig'sexec.enventry or, failing that, from the adapter's own environment (which the local owner clears). A kubeconfigexec.envname outside the allow-list refuses rather than being dropped.interactiveMode: Alwaysrefuses;provideClusterInfois not honoured. stdin is closed and stderr is discarded. File reads beyondHOMEare not sandboxed: the allow-list bounds what the plugin is told, not what the operating system lets it open. - When it runs. Only inside an admitted credential validation or operation:
never while loading the configuration or answering a description. One run is
bounded by
timeout_ms; at the deadline the plugin's whole process group and the plugin's own process are killed, and the caller is released then, whatever group the plugin's process moved itself to, and even when a descendant that left the group still holds stdout open (its read end is closed). stdout is bounded to 65536 bytes; more kills the process group and the plugin. The run ends when stdout is closed and the plugin's process has exited; whatever the outcome, the group and the plugin's own process are then each killed once, while that process is still unreaped so its id cannot name another process or group, and neither is signalled after it is reaped. Limitation: a descendant that left the plugin's process group is not followed; it is not killed and only loses its stdout. - The token. The answer must be an
ExecCredentialof the requestedapiVersion(client.authentication.k8s.io/v1orv1beta1) whosestatus.tokenis 1 to 8192 printable ASCII bytes; a client certificate answer is not admitted. The token is held in adapter memory, zeroized on drop, and reused until itsstatus.expirationTimestamp; the next admitted use at or after it runs the plugin again. With no expirationTimestamp the token serves only the use that ran the plugin. Any valid RFC 3339 date-time is an expiry, one before 1970 included (Go's zero time0001-01-01T00:00:00Zamong them); one at or before now serves only the use that ran the plugin and is notmalformed_credential. A numeric offset is+HH:MMor-HH:MMwithHH00 to 23 andMM00 to 59. The adapter's own buffers holding the token, the output it came in, the environment strings built for the exec and the kubeconfig bytes read for a run (read into one buffer allocated to the file bound, which never reallocates, so no earlier copy is released) are zeroized before their memory is released (the kubeconfig parser's internal copies ofexec.envvalues are not). It is never written to disk, a configuration, a description, a log or an error. - Refusals.
digest_mismatch,plugin_unavailableandkubeconfig_mismatchareinvalid_configuration;timeoutistimeout;output_too_largeandmalformed_credentialareprotocol; a non-zero exit (plugin_failed) isunavailable.
The shared exec_plugin acquisition flow stays reserved
(acquisition):
this binding is Kubernetes-native, and the host sees an ordinary http_bearer
protected-entry profile.
Source facts:
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
and https://kubernetes.io/docs/reference/config-api/client-authentication.v1/.
Source access: 2026-10-15.
Descriptor visibility
The implemented adapter filters disabled hosts.discover from its public descriptor. With a current descriptor revision, shared public lookup returns not_found before the separate adapter-internal forbidden guard. An old revision fails stale_description first. This is a native implementation fact, not a universal shared disabled-operation policy.
Kubeconfig contexts
contexts.list (kubernetes-kubeconfig-contexts) lists the contexts of the
kubeconfig a local connection was configured with. Its typed projection is the
contexts ESS domain.
- Which file. The local native configuration names it as
kubeconfig, an absolute path to an owner-only file. The composition reads and parses it once at load (a file that is not a readable kubeconfig refuses the configuration) and records the path's digest in the effective configuration, so the path is part of the configuration revision the host admitted. The operation's only input islimit(1–256): no input can name a file, and a connection reads the file it was configured with and no other. The service configuration cannot enable it. - Why an adapter read and not a setup listing. Both would read the same file; only this one is invocable on a saved connection, and fixing the path in the admitted configuration is what keeps an operation from reaching any other kubeconfig. The composition, not the business library, reads the file, so the business adapter still holds no file authority.
- When. The file is read at each invocation, so a context added later or a
changed
current-contextshows on the next read; there is no provider request. - What. Each context in file order:
name,cluster(the cluster entry's name),namespace(null when the context sets none) andcurrent(true for the contextcurrent-contextnames; all false when it names none). Users, cluster servers, certificate authorities, client certificates and keys, tokens, exec plugins and auth providers are never read into the result. - Bounds and refusals. At most 1 MiB and 256 contexts; a repeated context
name, a name over 256 bytes or a namespace over 63 refuses the whole file
rather than listing part of it. A smaller
limitreturns the first contexts withcomplete: false; there is no cursor. A file missing or no longer owner-only at invocation isunavailable.