VYPR
Medium severity6.5NVD Advisory· Published Jan 30, 2025· Updated Apr 15, 2026

CVE-2025-24376

CVE-2025-24376

Description

kubewarden-controller is a Kubernetes controller that allows you to dynamically register Kubewarden admission policies. By design, AdmissionPolicy and AdmissionPolicyGroup can evaluate only namespaced resources. The resources to be evaluated are determined by the rules provided by the user when defining the policy. There might be Kubernetes namespaced resources that should not be validated by AdmissionPolicy and by the AdmissionPolicyGroup policies because of their sensitive nature. For example, PolicyReport are namespaced resources that contain the list of non compliant objects found inside of a namespace. An attacker can use either an AdmissionPolicy or an AdmissionPolicyGroup to prevent the creation and update of PolicyReport objects to hide non-compliant resources. Moreover, the same attacker might use a mutating AdmissionPolicy to alter the contents of the PolicyReport created inside of the namespace. Starting from the 1.21.0 release, the validation rules applied to AdmissionPolicy and AdmissionPolicyGroup have been tightened to prevent them from validating sensitive types of namespaced resources.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/kubewarden/kubewarden-controllerGo
>= 1.7.0, < 1.21.01.21.0

Patches

2
8124039b5f0c

fix: stricter checks on AdmissionPolicy and AdmissionPolicyGroup rules

https://github.com/kubewarden/kubewarden-controllerFlavio CastelliJan 24, 2025via ghsa
3 files changed · +300 18
  • api/policies/v1/policy_validation.go+94 0 modified
    @@ -40,6 +40,41 @@ var (
     
     const maxMatchConditionsCount = 64
     
    +type sensitiveResource struct {
    +	APIGroup string
    +	Resource string
    +}
    +
    +func (sr sensitiveResource) String() string {
    +	return fmt.Sprintf("APIGroup: %s, Resource: %s", sr.APIGroup, sr.Resource)
    +}
    +
    +func (sr sensitiveResource) MatchesRules(apiGroups []string, resource []string) bool {
    +	apiGroupMatches := false
    +	for _, apiGroup := range apiGroups {
    +		if apiGroup == sr.APIGroup || apiGroup == "*" {
    +			apiGroupMatches = true
    +			break
    +		}
    +	}
    +
    +	resourceMatches := false
    +	for _, res := range resource {
    +		if res == sr.Resource || res == "*" || res == "*/*" || strings.HasPrefix(res, sr.Resource+"/") {
    +			resourceMatches = true
    +			break
    +		}
    +	}
    +
    +	return apiGroupMatches && resourceMatches
    +}
    +
    +func defaultSensitiveResources() []sensitiveResource {
    +	return []sensitiveResource{
    +		{APIGroup: "wgpolicyk8s.io", Resource: "policyreports"},
    +	}
    +}
    +
     func validatePolicyCreate(policy Policy) field.ErrorList {
     	var allErrors field.ErrorList
     
    @@ -74,6 +109,9 @@ func validateRulesField(policy Policy) field.ErrorList {
     		return allErrors
     	}
     
    +	_, isAdmissionPolicy := policy.(*AdmissionPolicy)
    +	_, isAdmissionPolicyGroup := policy.(*AdmissionPolicyGroup)
    +
     	for _, rule := range policy.GetRules() {
     		switch {
     		case len(rule.Operations) == 0:
    @@ -85,6 +123,11 @@ func validateRulesField(policy Policy) field.ErrorList {
     			allErrors = append(allErrors, checkOperationsArrayForEmptyString(rule.Operations, rulesField)...)
     			allErrors = append(allErrors, checkRulesArrayForEmptyString(rule.Rule.APIVersions, rulesField.Child("rule.apiVersions"))...)
     			allErrors = append(allErrors, checkRulesArrayForEmptyString(rule.Rule.Resources, rulesField.Child("rule.resources"))...)
    +
    +			if isAdmissionPolicy || isAdmissionPolicyGroup {
    +				allErrors = append(allErrors, checkRulesArrayForWildcardUsage(rule.Rule.APIVersions, rule.Rule.Resources, rulesField)...)
    +				allErrors = append(allErrors, checkRulesArrayForSensitiveResourcesBeingTargeted(rule.Rule.APIVersions, rule.Rule.Resources, rulesField)...)
    +			}
     		}
     	}
     
    @@ -119,6 +162,57 @@ func checkRulesArrayForEmptyString(rulesArray []string, rulesField *field.Path)
     	return allErrors
     }
     
    +// checkRulesArrayForWildcardUsage checks if the rules array contains a wildcard and returns an error if both the apiGroups
    +// and resources contain wildcards.
    +func checkRulesArrayForWildcardUsage(rulesAPIGroups []string, rulesResources []string, rulesField *field.Path) field.ErrorList {
    +	var allErrors field.ErrorList
    +
    +	apiGroupHasWildcard := false
    +	apiGroupWildcardIndex := -1
    +
    +	resourceHasWildcard := false
    +	resourceWildcardIndex := -1
    +
    +	for i, apiGroup := range rulesAPIGroups {
    +		if apiGroup == "*" {
    +			apiGroupHasWildcard = true
    +			apiGroupWildcardIndex = i
    +			break
    +		}
    +	}
    +
    +	for i, resource := range rulesResources {
    +		if resource == "*" || resource == "*/*" {
    +			resourceHasWildcard = true
    +			resourceWildcardIndex = i
    +			break
    +		}
    +	}
    +
    +	if apiGroupHasWildcard && resourceHasWildcard {
    +		allErrors = append(allErrors, field.Forbidden(rulesField.Child("apiGroups").Index(apiGroupWildcardIndex), "apiGroups cannot use wildcards when using AdmissionPolicy or AdmissionPolicyGroup"))
    +		allErrors = append(allErrors, field.Forbidden(rulesField.Child("resources").Index(resourceWildcardIndex), "resources cannot use wildcards when using AdmissionPolicy or AdmissionPolicyGroup"))
    +	}
    +
    +	return allErrors
    +}
    +
    +// checkRulesArrayForSensitiveResourcesBeingTargeted checks if any of the sensitive resources are being targeted by the
    +// rule.
    +func checkRulesArrayForSensitiveResourcesBeingTargeted(rulesAPIGroups []string, rulesResources []string, rulesField *field.Path) field.ErrorList {
    +	var allErrors field.ErrorList
    +
    +	sensitiveResources := defaultSensitiveResources()
    +
    +	for _, sensitiveResource := range sensitiveResources {
    +		if sensitiveResource.MatchesRules(rulesAPIGroups, rulesResources) {
    +			allErrors = append(allErrors, field.Forbidden(rulesField, fmt.Sprintf("{%s} resources cannot be targeted by AdmissionPolicy or AdmissionPolicyGroup", sensitiveResource)))
    +		}
    +	}
    +
    +	return allErrors
    +}
    +
     func validatePolicyServerField(oldPolicy, newPolicy Policy) *field.Error {
     	if oldPolicy.GetPolicyServer() != newPolicy.GetPolicyServer() {
     		return field.Forbidden(field.NewPath("spec").Child("policyServer"), "the field is immutable")
    
  • api/policies/v1/policy_validation_test.go+180 18 modified
    @@ -23,11 +23,86 @@ import (
     	"k8s.io/apimachinery/pkg/util/validation/field"
     )
     
    +func TestSensitiveResourceMatchRule(t *testing.T) {
    +	sr := sensitiveResource{
    +		APIGroup: "apps",
    +		Resource: "deployments",
    +	}
    +
    +	tests := []struct {
    +		name      string
    +		apiGroups []string
    +		resources []string
    +		matches   bool
    +	}{
    +		{
    +			"with matching APIGroups and Resources",
    +			[]string{"apps"},
    +			[]string{"statefulsets", "deployments"},
    +			true,
    +		},
    +		{
    +			"with APIGroups using wildcard and matching Resources",
    +			[]string{"*"},
    +			[]string{"deployments"},
    +			true,
    +		},
    +		{
    +			"with Resources using wildcards and APIGroups matching",
    +			[]string{"apps"},
    +			[]string{"*"},
    +			true,
    +		},
    +		{
    +			"with Resources using double wildcards and APIGroups matching",
    +			[]string{"apps"},
    +			[]string{"*/*"},
    +			true,
    +		},
    +		{
    +			"with sub-Resources using wildcards and APIGroups matching",
    +			[]string{"apps"},
    +			[]string{"deployments/*"},
    +			true,
    +		},
    +		{
    +			"with sub-Resources and APIGroups matching",
    +			[]string{"apps"},
    +			[]string{"deployments/status"},
    +			true,
    +		},
    +		{
    +			"with only APIGroups matching",
    +			[]string{"apps"},
    +			[]string{"statefulsets"},
    +			false,
    +		},
    +		{
    +			"with APIGroups not matching and a Resopurce using wildcard",
    +			[]string{""},
    +			[]string{"*"},
    +			false,
    +		},
    +		{
    +			"with APIGroups not matching and a Resopurce matching",
    +			[]string{"argoproj.io"},
    +			[]string{"deployments"},
    +			false,
    +		},
    +	}
    +
    +	for _, test := range tests {
    +		t.Run(test.name, func(t *testing.T) {
    +			require.Equal(t, test.matches, sr.MatchesRules(test.apiGroups, test.resources))
    +		})
    +	}
    +}
    +
     func TestValidateRulesField(t *testing.T) {
     	tests := []struct {
    -		name                 string
    -		policy               Policy
    -		expectedErrorMessage string // use empty string when no error is expected
    +		name                  string
    +		policy                Policy
    +		expectedErrorMessages []string // use nil when no error is expected
     	}{
     		{
     			"with valid APIVersion and resources. But with empty APIGroup",
    @@ -43,7 +118,7 @@ func TestValidateRulesField(t *testing.T) {
     					},
     				}).
     				WithPolicyServer("default").Build(),
    -			"",
    +			nil,
     		},
     		{
     			"with valid APIVersion, Resources and APIGroup",
    @@ -59,21 +134,21 @@ func TestValidateRulesField(t *testing.T) {
     					},
     				}).
     				WithPolicyServer("default").Build(),
    -			"",
    +			nil,
     		},
     		{
     			"with no operations and API groups and resources",
     			NewClusterAdmissionPolicyFactory().
     				WithRules([]admissionregistrationv1.RuleWithOperations{}).
     				WithPolicyServer("default").Build(),
    -			"spec.rules: Required value: a value must be specified",
    +			[]string{"spec.rules: Required value: a value must be specified"},
     		},
     		{
     			"with empty objects",
     			NewClusterAdmissionPolicyFactory().
     				WithRules([]admissionregistrationv1.RuleWithOperations{{}}).
     				WithPolicyServer("default").Build(),
    -			"spec.rules.operations: Required value: a value must be specified",
    +			[]string{"spec.rules.operations: Required value: a value must be specified"},
     		},
     		{
     			"with no operations",
    @@ -89,7 +164,7 @@ func TestValidateRulesField(t *testing.T) {
     					},
     				}).
     				WithPolicyServer("default").Build(),
    -			"spec.rules.operations: Required value: a value must be specified",
    +			[]string{"spec.rules.operations: Required value: a value must be specified"},
     		},
     		{
     			"with null operations",
    @@ -103,7 +178,7 @@ func TestValidateRulesField(t *testing.T) {
     					},
     				}}).
     				WithPolicyServer("default").Build(),
    -			"spec.rules.operations: Required value: a value must be specified",
    +			[]string{"spec.rules.operations: Required value: a value must be specified"},
     		},
     		{
     			"with empty operations string",
    @@ -117,7 +192,7 @@ func TestValidateRulesField(t *testing.T) {
     					},
     				}}).
     				WithPolicyServer("default").Build(),
    -			"spec.rules.operations[0]: Required value: must be non-empty",
    +			[]string{"spec.rules.operations[0]: Required value: must be non-empty"},
     		},
     		{
     			"with no apiVersion",
    @@ -131,7 +206,7 @@ func TestValidateRulesField(t *testing.T) {
     					},
     				}}).
     				WithPolicyServer("default").Build(),
    -			"spec.rules: Required value: apiVersions and resources must have specified values",
    +			[]string{"spec.rules: Required value: apiVersions and resources must have specified values"},
     		},
     		{
     			"with no resources",
    @@ -144,7 +219,7 @@ func TestValidateRulesField(t *testing.T) {
     						Resources:   []string{},
     					},
     				}}).WithPolicyServer("default").Build(),
    -			"spec.rules: Required value: apiVersions and resources must have specified values",
    +			[]string{"spec.rules: Required value: apiVersions and resources must have specified values"},
     		},
     		{
     			"with empty apiVersion string",
    @@ -157,7 +232,7 @@ func TestValidateRulesField(t *testing.T) {
     						Resources:   []string{"*/*"},
     					},
     				}}).WithPolicyServer("defaule").Build(),
    -			"spec.rules.rule.apiVersions[0]: Required value: must be non-empty",
    +			[]string{"spec.rules.rule.apiVersions[0]: Required value: must be non-empty"},
     		},
     		{
     			"with empty resources string",
    @@ -170,7 +245,7 @@ func TestValidateRulesField(t *testing.T) {
     						Resources:   []string{""},
     					},
     				}}).WithPolicyServer("default").Build(),
    -			"spec.rules.rule.resources[0]: Required value: must be non-empty",
    +			[]string{"spec.rules.rule.resources[0]: Required value: must be non-empty"},
     		},
     		{
     			"with some of the resources are empty strings",
    @@ -183,7 +258,7 @@ func TestValidateRulesField(t *testing.T) {
     						Resources:   []string{"", "pods"},
     					},
     				}}).WithPolicyServer("default").Build(),
    -			"spec.rules.rule.resources[0]: Required value: must be non-empty",
    +			[]string{"spec.rules.rule.resources[0]: Required value: must be non-empty"},
     		},
     		{
     			"with all operations and API groups and resources",
    @@ -198,17 +273,104 @@ func TestValidateRulesField(t *testing.T) {
     						},
     					},
     				}).Build(),
    -			"",
    +			nil,
    +		},
    +		{
    +			"with wildcard usage. But an AdmissionPolicy",
    +			NewAdmissionPolicyFactory().
    +				WithRules([]admissionregistrationv1.RuleWithOperations{
    +					{
    +						Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
    +						Rule: admissionregistrationv1.Rule{
    +							APIGroups:   []string{"*"},
    +							APIVersions: []string{"*"},
    +							Resources:   []string{"*/*"},
    +						},
    +					},
    +				}).Build(),
    +			[]string{
    +				"spec.rules.apiGroups[0]: Forbidden: apiGroups cannot use wildcards when using AdmissionPolicy or AdmissionPolicyGroup",
    +				"spec.rules.resources[0]: Forbidden: resources cannot use wildcards when using AdmissionPolicy or AdmissionPolicyGroup",
    +			},
    +		},
    +		{
    +			"with wildcard usage. But an AdmissionPolicyGroup",
    +			NewAdmissionPolicyGroupFactory().
    +				WithRules([]admissionregistrationv1.RuleWithOperations{
    +					{
    +						Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
    +						Rule: admissionregistrationv1.Rule{
    +							APIGroups:   []string{"*"},
    +							APIVersions: []string{"*"},
    +							Resources:   []string{"*"},
    +						},
    +					},
    +				}).Build(),
    +			[]string{
    +				"spec.rules.apiGroups[0]: Forbidden: apiGroups cannot use wildcards when using AdmissionPolicy or AdmissionPolicyGroup",
    +				"spec.rules.resources[0]: Forbidden: resources cannot use wildcards when using AdmissionPolicy or AdmissionPolicyGroup",
    +			},
    +		},
    +		{
    +			"targeting a PolicyReport. But a ClusterAdmissionPolicy",
    +			NewClusterAdmissionPolicyFactory().
    +				WithRules([]admissionregistrationv1.RuleWithOperations{
    +					{
    +						Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
    +						Rule: admissionregistrationv1.Rule{
    +							APIGroups:   []string{"wgpolicyk8s.io"},
    +							APIVersions: []string{"*"},
    +							Resources:   []string{"policyreports"},
    +						},
    +					},
    +				}).Build(),
    +			nil,
    +		},
    +		{
    +			"targeting a PolicyReport. But an AdmissionPolicy",
    +			NewAdmissionPolicyFactory().
    +				WithRules([]admissionregistrationv1.RuleWithOperations{
    +					{
    +						Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
    +						Rule: admissionregistrationv1.Rule{
    +							APIGroups:   []string{"wgpolicyk8s.io"},
    +							APIVersions: []string{"*"},
    +							Resources:   []string{"policyreports"},
    +						},
    +					},
    +				}).Build(),
    +			[]string{
    +				"spec.rules: Forbidden: {APIGroup: wgpolicyk8s.io, Resource: policyreports} resources cannot be targeted by AdmissionPolicy or AdmissionPolicyGroup",
    +			},
    +		},
    +		{
    +			"targeting a wgpolicyk8s.io resources. But an AdmissionPolicyGroup",
    +			NewAdmissionPolicyGroupFactory().
    +				WithRules([]admissionregistrationv1.RuleWithOperations{
    +					{
    +						Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
    +						Rule: admissionregistrationv1.Rule{
    +							APIGroups:   []string{"wgpolicyk8s.io"},
    +							APIVersions: []string{"*"},
    +							Resources:   []string{"*"},
    +						},
    +					},
    +				}).Build(),
    +			[]string{
    +				"spec.rules: Forbidden: {APIGroup: wgpolicyk8s.io, Resource: policyreports} resources cannot be targeted by AdmissionPolicy or AdmissionPolicyGroup",
    +			},
     		},
     	}
     
     	for _, test := range tests {
     		t.Run(test.name, func(t *testing.T) {
     			allErrors := validateRulesField(test.policy)
     
    -			if test.expectedErrorMessage != "" {
    +			if len(test.expectedErrorMessages) != 0 {
     				err := prepareInvalidAPIError(test.policy, allErrors)
    -				require.ErrorContains(t, err, test.expectedErrorMessage)
    +				for _, expectedErrorMessage := range test.expectedErrorMessages {
    +					require.ErrorContains(t, err, expectedErrorMessage)
    +				}
     			} else {
     				require.Empty(t, allErrors)
     			}
    
  • config/crd/bases/policies.kubewarden.io_policyservers.yaml+26 0 modified
    @@ -1438,6 +1438,32 @@ spec:
                               Note that this field cannot be set when spec.os.name is windows.
                             format: int64
                             type: integer
    +                      seLinuxChangePolicy:
    +                        description: |-
    +                          seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod.
    +                          It has no effect on nodes that do not support SELinux or to volumes does not support SELinux.
    +                          Valid values are "MountOption" and "Recursive".
    +
    +                          "Recursive" means relabeling of all files on all Pod volumes by the container runtime.
    +                          This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node.
    +
    +                          "MountOption" mounts all eligible Pod volumes with `-o context` mount option.
    +                          This requires all Pods that share the same volume to use the same SELinux label.
    +                          It is not possible to share the same volume among privileged and unprivileged Pods.
    +                          Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes
    +                          whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their
    +                          CSIDriver instance. Other volumes are always re-labelled recursively.
    +                          "MountOption" value is allowed only when SELinuxMount feature gate is enabled.
    +
    +                          If not specified and SELinuxMount feature gate is enabled, "MountOption" is used.
    +                          If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes
    +                          and "Recursive" for all other volumes.
    +
    +                          This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers.
    +
    +                          All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state.
    +                          Note that this field cannot be set when spec.os.name is windows.
    +                        type: string
                           seLinuxOptions:
                             description: |-
                               The SELinux context to be applied to all containers.
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.