Kubernetes Security for DevOps Engineers

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 the extensions/v1beta1 Ingress 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/v1beta1 PodSecurityPolicy 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.

KEY CONCEPT

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

Resource (e.g. Pod, Deployment, NetworkPolicy)

The actual object type. Identified by the (group, version, kind) triple. CRUD operations apply here.

Version (e.g. v1, v1beta1, v1alpha1)

The API contract version. Lifecycle: alpha (no guarantees) -> beta (9-month deprecation) -> stable (12+ month deprecation). Name pattern: vXalpha1 -> vXbeta1 -> vX.

API group (e.g. apps, networking.k8s.io, rbac.authorization.k8s.io)

Logical grouping of related resources. Two flavors: core (no group name, e.g. just v1) and named (group/version, e.g. apps/v1).

API server (the entry point)

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:

StageLifetime guaranteeAllowed changes
Alpha (e.g. v1alpha1)None; can disappear at any releaseAnything
Beta (e.g. v1beta1)At least 9 months or 3 minor releases after deprecationField additions, behavior changes
Stable (e.g. v1)At least 12 months or 1 minor release after deprecationBug 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 vNalpha1 or vNbeta1 is a future migration job. Use stable when available.
  • Not auditing apiserver_requested_deprecated_apis before 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-resources and kubectl api-versions are the first commands. Knowing what is installed bounds your security audit.
INTERVIEW QUESTION

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?