Skip to content

CRD generation with #[serde(deny_unknown_fields)] applies both additionalProperties and properties #1828

@peasee

Description

@peasee

Current and expected behavior

Consider the following CRD:

#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
#[kube(
    kind = "MyCRD",
    group = "example.com",
    version = "v1",
    namespaced
)]
pub struct MyCRDSpec {
    pub subitem: SubItemDenyUnknown,
}

#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SubItemDenyUnknown {
    pub one: String,
    pub two: bool,
    pub three: i32,
}

When #[serde(deny_unknown_fields)] is specified, this generates a CRD with both a additionalProperties: false, and properties: of the SubItemDenyUnknown object itself:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: mycrds.example.com
spec:
  group: example.com
  names:
    categories: []
    kind: MyCRD
    plural: mycrds
    shortNames: []
    singular: mycrd
  scope: Namespaced
  versions:
  - additionalPrinterColumns: []
    name: v1
    schema:
      openAPIV3Schema:
        description: Auto-generated derived type for MyCRDSpec via `CustomResource`
        properties:
          spec:
            properties:
              subitem:
                additionalProperties: false
                properties:
                  one:
                    type: string
                  three:
                    format: int32
                    type: integer
                  two:
                    type: boolean
                required:
                - one
                - three
                - two
                type: object
            required:
            - subitem
            type: object
        required:
        - spec
        title: MyCRD
        type: object
    served: true
    storage: true
    subresources: {}

Because this object already contains properties, I would expect additionalProperties to not be specified here as it results in a Kubernetes CRD validation error:

Forbidden: additionalProperties and properties are mutual exclusive

Possible solution

I saw a similar issue in #844. I am not sure if a similar fix is applicable here. I think omitting additionalProperties: false entirely might be more applicable (see context).

Additional context

The CRD Validation page in the Kubernetes docs states:

The field additionalProperties cannot be set to false.

It doesn't seem like this returns an error though, as I tried applying this CRD which applied successfully but pruned the additionalProperties: false from the actual applied CRD. The CRD I applied:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: mycrds.example.com
spec:
  group: example.com
  names:
    categories: []
    kind: MyCRD
    plural: mycrds
    shortNames: []
    singular: mycrd
  scope: Namespaced
  versions:
  - additionalPrinterColumns: []
    name: v1
    schema:
      openAPIV3Schema:
        description: Auto-generated derived type for MyCRDSpec via `CustomResource`
        properties:
          spec:
            properties:
              one:
                type: string
              two:
                type: object
                additionalProperties: false
            required:
            - one
            type: object
        required:
        - spec
        title: MyCRD
        type: object
    served: true
    storage: true
    subresources: {}

And this is what ended up on my cluster:

spec:
  conversion:
    strategy: None
  group: example.com
  names:
    kind: MyCRD
    listKind: MyCRDList
    plural: mycrds
    singular: mycrd
  scope: Namespaced
  versions:
  - name: v1
    schema:
      openAPIV3Schema:
        description: Auto-generated derived type for MyCRDSpec via `CustomResource`
        properties:
          spec:
            properties:
              one:
                type: string
              two:
                type: object
            required:
            - one
            type: object
        required:
        - spec
        title: MyCRD
        type: object
    served: true
    storage: true
    subresources: {}
status:
  acceptedNames:
    kind: MyCRD
    listKind: MyCRDList
    plural: mycrds
    singular: mycrd
  conditions:
  - lastTransitionTime: "2025-09-25T06:25:43Z"
    message: no conflicts found
    reason: NoConflicts
    status: "True"
    type: NamesAccepted
  - lastTransitionTime: "2025-09-25T06:25:43Z"
    message: the initial names have been accepted
    reason: InitialNamesAccepted
    status: "True"
    type: Established
  storedVersions:
  - v1

Environment

I am running with kind, using a regular kind create cluster:

Client Version: v1.33.4
Kustomize Version: v5.6.0
Server Version: v1.33.1

Configuration and features

k8s-openapi = { version = "0.26.0", features = ["latest", "schemars"] }
kube = { version = "2.0.1", features = ["runtime", "client", "derive"] }

YAML

No response

Affected crates

No response

Would you like to work on fixing this bug?

yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions