API Groups, CRUD, and Versioning
Your team is using a v1beta1 API that's about to be removed in the next K8s upgrade. Half your security policies reference this API version. How do you audit and migrate?
The Kubernetes API is not one API. It is dozens of APIs grouped logically and versioned independently, with explicit lifecycle stages and a documented deprecation policy. For day-to-day work this is mostly invisible; for security work, it determines which RBAC rules survive an upgrade, which admission policies still apply, and which fields are deprecated out from under your manifests.
This lesson is the model for API groups and versions, why the lifecycle matters for security, and the upgrade-safety questions to ask before any cluster version bump.
Attack path / threat explanation
The threat at this layer is not direct attack; it is silent breakage during cluster upgrades. Specifically:
- RBAC rules referencing removed API versions stop matching. A Role that allowed
apiGroups: ["extensions"],resources: ["ingresses"]no longer applies after theextensions/v1beta1Ingress was removed. The user keeps the Role but loses the access (or a different Role still grants it, with different scope). - Admission policies referencing removed APIs silently fail. A Gatekeeper constraint or Kyverno policy that targets a deprecated resource keeps existing but no longer evaluates anything. Your protection silently disappears.
- Workloads using deprecated APIs continue to work until they do not. A controller using
policy/v1beta1PodSecurityPolicy works fine until the upgrade that removes the API. Then the controller crashes or silently stops enforcing.
Each of these is invisible at the moment of failure. You see the consequence later, often during a security incident, when the protection you thought you had is missing.
The defense is awareness and auditing: knowing which API versions your cluster supports, which versions your manifests reference, and what is scheduled for removal in upcoming versions.
The API versioning lifecycle exists to give you advance warning before APIs are removed. Alpha can change or disappear in any release. Beta has a 9-month deprecation window. Stable APIs are guaranteed for at least 12 months after deprecation. Knowing which lifecycle stage every API you depend on is in lets you plan migrations before they become outages.
How it works under the hood
The structure of API groups and versions:
The Kubernetes API hierarchy, top to bottom
The actual object type. Identified by the (group, version, kind) triple. CRUD operations apply here.
The API contract version. Lifecycle: alpha (no guarantees) -> beta (9-month deprecation) -> stable (12+ month deprecation). Name pattern: vXalpha1 -> vXbeta1 -> vX.
Logical grouping of related resources. Two flavors: core (no group name, e.g. just v1) and named (group/version, e.g. apps/v1).
Routes requests based on the (group, version, resource) triple to the right storage and handlers. The same resource can exist at multiple versions simultaneously.
Hover to expand each layer
The two distinguishing facts most engineers misunderstand:
Core vs named API groups. The "core" group has no name; it is referenced as just v1 (e.g. Pods, Services, Nodes). All other API groups have explicit names (apps, networking.k8s.io, rbac.authorization.k8s.io, etc.). In RBAC, the core group is referenced with apiGroups: [""] (the empty string). Named groups use their actual name. This is a frequent source of "my RBAC rule does not work" bugs.
The same resource can have multiple version representations. A Deployment exists at apps/v1 (stable). Older versions (apps/v1beta1, apps/v1beta2, extensions/v1beta1) existed and were removed. The API server stored Deployments in one canonical version internally and converted on the fly for clients requesting other versions. When the API server upgrades and removes a version, requests for that version start returning 404. The data is still there; the API path to it is not.
The deprecation policy in detail:
| Stage | Lifetime guarantee | Allowed changes |
|---|---|---|
| Alpha (e.g. v1alpha1) | None; can disappear at any release | Anything |
| Beta (e.g. v1beta1) | At least 9 months or 3 minor releases after deprecation | Field additions, behavior changes |
| Stable (e.g. v1) | At least 12 months or 1 minor release after deprecation | Bug fixes only; backward compatible |
A practical implication: writing manifests against a beta API is acceptable for ephemeral or development workloads but risky for production. Stable APIs (v1) are the only safe choice for production manifests that must survive multiple cluster upgrades.
Defense architecture
The security-relevant audit checks for any production cluster:
1. Inventory which API versions are in use. Every manifest, RBAC rule, admission policy, and operator references API versions. Audit them.
2. Cross-reference against the deprecation schedule. The Kubernetes documentation publishes the schedule for each minor version. Anything scheduled for removal in the next 1-2 versions needs a migration plan now.
3. Use apiserver_requested_deprecated_apis metric. Modern API servers expose a Prometheus metric that counts requests against deprecated APIs. If it is non-zero, something in your cluster is using a deprecated API and will break on upgrade.
4. Block deprecated APIs in admission control. For new manifests, write a Kyverno or Gatekeeper policy that rejects manifests using deprecated API versions. Stops new debt from accumulating.
5. Validate webhook configurations. Mutating and validating admission webhooks register against specific (group, version, resource) triples. After a version removal, a webhook registered against the removed version simply does not fire. Re-validate webhook coverage after every upgrade.
Configuration examples
Inspecting your cluster's API surface:
# List all API groups and versions the cluster supports
kubectl api-versions
# Output (truncated):
# admissionregistration.k8s.io/v1
# apiextensions.k8s.io/v1
# apps/v1
# autoscaling/v1
# autoscaling/v2
# batch/v1
# discovery.k8s.io/v1
# events.k8s.io/v1
# networking.k8s.io/v1
# policy/v1
# rbac.authorization.k8s.io/v1
# storage.k8s.io/v1
# v1
# List all resources, with their group, version, and verbs
kubectl api-resources -o wide
# Find which API group a specific resource belongs to
kubectl api-resources | grep -i ingress
# Inspect the schema and version of a specific resource
kubectl explain ingress.spec --api-version=networking.k8s.io/v1
Querying the deprecated API metric:
# Requests against deprecated APIs (broken down by API)
sum by (group, version, resource, removed_release) (
apiserver_requested_deprecated_apis
)
# Alert if any deprecated API will be removed in the next 2 minor versions
apiserver_requested_deprecated_apis{removed_release=~"^1\\.(28|29|30)$"} > 0
A starter Kyverno policy that warns on deprecated API usage:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: warn-deprecated-apis
spec:
validationFailureAction: Audit # warn, do not block
rules:
- name: deprecated-policy-v1beta1
match:
any:
- resources:
kinds:
- PodSecurityPolicy
validate:
message: "PodSecurityPolicy (policy/v1beta1) is removed in K8s 1.25+. Migrate to Pod Security Admission."
deny: {}
Common misconfigurations
- RBAC rules with the wrong apiGroup string.
apiGroups: [""]for core resources (Pods, Services); the named group string for everything else. Mismatched values silently fail to grant access. - Pinning to alpha or beta APIs in production. Anything
vNalpha1orvNbeta1is a future migration job. Use stable when available. - Not auditing
apiserver_requested_deprecated_apisbefore upgrades. The metric exists for exactly this purpose; many teams discover too late that something was using a removed API. - Webhook configurations not updated after API removals. A webhook registered against a removed group/version is dead weight.
- Treating CRDs the same as built-in APIs. Custom Resource Definitions have their own lifecycle independent of K8s versions. Operators may pin to specific CRD versions; verify compatibility on operator upgrades.
- Skipping the API discovery check on cluster takeover. When inheriting an unfamiliar cluster,
kubectl api-resourcesandkubectl api-versionsare the first commands. Knowing what is installed bounds your security audit.
What's the difference between a core API group and a named API group? Why does the versioning lifecycle (alpha/beta/stable) matter for production security tooling?