PodSecurity: return field errors for invalid namespace labels

This commit is contained in:
Jordan Liggitt
2021-10-28 00:23:44 -04:00
parent c0f33ddf08
commit 3aa656b63f
2 changed files with 35 additions and 28 deletions

View File

@@ -30,9 +30,11 @@ import (
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
admissionapi "k8s.io/pod-security-admission/admission/api"
"k8s.io/pod-security-admission/admission/api/validation"
"k8s.io/pod-security-admission/api"
@@ -239,13 +241,13 @@ func (a *Admission) ValidateNamespace(ctx context.Context, attrs api.Attributes)
return badRequestResponse("failed to decode namespace")
}
newPolicy, newErr := a.PolicyToEvaluate(namespace.Labels)
newPolicy, newErrs := a.PolicyToEvaluate(namespace.Labels)
switch attrs.GetOperation() {
case admissionv1.Create:
// require valid labels on create
if newErr != nil {
return invalidResponse(newErr.Error())
if len(newErrs) > 0 {
return invalidResponse(attrs, newErrs)
}
return sharedAllowedResponse()
@@ -261,11 +263,11 @@ func (a *Admission) ValidateNamespace(ctx context.Context, attrs api.Attributes)
klog.InfoS("failed to assert old namespace type", "type", reflect.TypeOf(oldObj))
return badRequestResponse("failed to decode old namespace")
}
oldPolicy, oldErr := a.PolicyToEvaluate(oldNamespace.Labels)
oldPolicy, oldErrs := a.PolicyToEvaluate(oldNamespace.Labels)
// require valid labels on update if they have changed
if newErr != nil && (oldErr == nil || newErr.Error() != oldErr.Error()) {
return invalidResponse(newErr.Error())
if len(newErrs) > 0 && (len(oldErrs) == 0 || !reflect.DeepEqual(newErrs, oldErrs)) {
return invalidResponse(attrs, newErrs)
}
// Skip dry-running pods:
@@ -327,8 +329,8 @@ func (a *Admission) ValidatePod(ctx context.Context, attrs api.Attributes) *admi
klog.ErrorS(err, "failed to fetch pod namespace", "namespace", attrs.GetNamespace())
return internalErrorResponse(fmt.Sprintf("failed to lookup namespace %s", attrs.GetNamespace()))
}
nsPolicy, nsPolicyErr := a.PolicyToEvaluate(namespace.Labels)
if nsPolicyErr == nil && nsPolicy.Enforce.Level == api.LevelPrivileged && nsPolicy.Warn.Level == api.LevelPrivileged && nsPolicy.Audit.Level == api.LevelPrivileged {
nsPolicy, nsPolicyErrs := a.PolicyToEvaluate(namespace.Labels)
if len(nsPolicyErrs) == 0 && nsPolicy.Enforce.Level == api.LevelPrivileged && nsPolicy.Warn.Level == api.LevelPrivileged && nsPolicy.Audit.Level == api.LevelPrivileged {
return sharedAllowedResponse()
}
@@ -358,7 +360,7 @@ func (a *Admission) ValidatePod(ctx context.Context, attrs api.Attributes) *admi
return sharedAllowedResponse()
}
}
return a.EvaluatePod(ctx, nsPolicy, nsPolicyErr, &pod.ObjectMeta, &pod.Spec, attrs, true)
return a.EvaluatePod(ctx, nsPolicy, nsPolicyErrs.ToAggregate(), &pod.ObjectMeta, &pod.Spec, attrs, true)
}
// ValidatePodController evaluates a pod controller create or update request against the effective policy for the namespace.
@@ -379,8 +381,8 @@ func (a *Admission) ValidatePodController(ctx context.Context, attrs api.Attribu
klog.ErrorS(err, "failed to fetch pod namespace", "namespace", attrs.GetNamespace())
return internalErrorResponse(fmt.Sprintf("failed to lookup namespace %s", attrs.GetNamespace()))
}
nsPolicy, nsPolicyErr := a.PolicyToEvaluate(namespace.Labels)
if nsPolicyErr == nil && nsPolicy.Warn.Level == api.LevelPrivileged && nsPolicy.Audit.Level == api.LevelPrivileged {
nsPolicy, nsPolicyErrs := a.PolicyToEvaluate(namespace.Labels)
if len(nsPolicyErrs) == 0 && nsPolicy.Warn.Level == api.LevelPrivileged && nsPolicy.Audit.Level == api.LevelPrivileged {
return sharedAllowedResponse()
}
@@ -398,7 +400,7 @@ func (a *Admission) ValidatePodController(ctx context.Context, attrs api.Attribu
// if a controller with an optional pod spec does not contain a pod spec, skip validation
return sharedAllowedResponse()
}
return a.EvaluatePod(ctx, nsPolicy, nsPolicyErr, podMetadata, podSpec, attrs, false)
return a.EvaluatePod(ctx, nsPolicy, nsPolicyErrs.ToAggregate(), podMetadata, podSpec, attrs, false)
}
// EvaluatePod evaluates the given policy against the given pod(-like) object.
@@ -559,7 +561,7 @@ func decoratePodWarnings(podWarningsToCount map[string]podCount, warnings []stri
}
}
func (a *Admission) PolicyToEvaluate(labels map[string]string) (api.Policy, error) {
func (a *Admission) PolicyToEvaluate(labels map[string]string) (api.Policy, field.ErrorList) {
return api.PolicyToEvaluate(labels, a.defaultPolicy)
}
@@ -592,8 +594,11 @@ func forbiddenResponse(msg string) *admissionv1.AdmissionResponse {
}
// invalidResponse is the response used for namespace requests when namespace labels are invalid.
func invalidResponse(msg string) *admissionv1.AdmissionResponse {
return failureResponse(msg, metav1.StatusReasonInvalid, 422)
func invalidResponse(attrs api.Attributes, fieldErrors field.ErrorList) *admissionv1.AdmissionResponse {
return &admissionv1.AdmissionResponse{
Allowed: false,
Result: &apierrors.NewInvalid(attrs.GetKind().GroupKind(), attrs.GetName(), fieldErrors).ErrStatus,
}
}
// badRequestResponse is the response used when a request cannot be processed.

View File

@@ -22,7 +22,7 @@ import (
"strconv"
"strings"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/component-base/version"
)
@@ -149,44 +149,44 @@ type Policy struct {
// falling back to the provided defaults when a label is unspecified. A valid policy is always
// returned, even when an error is returned. If labels cannot be parsed correctly, the values of
// "restricted" and "latest" are used for level and version respectively.
func PolicyToEvaluate(labels map[string]string, defaults Policy) (Policy, error) {
func PolicyToEvaluate(labels map[string]string, defaults Policy) (Policy, field.ErrorList) {
var (
err error
errs []error
errs field.ErrorList
p = defaults
)
if level, ok := labels[EnforceLevelLabel]; ok {
p.Enforce.Level, err = ParseLevel(level)
errs = appendErr(errs, err, EnforceLevelLabel)
errs = appendErr(errs, err, EnforceLevelLabel, level)
}
if version, ok := labels[EnforceVersionLabel]; ok {
p.Enforce.Version, err = ParseVersion(version)
errs = appendErr(errs, err, EnforceVersionLabel)
errs = appendErr(errs, err, EnforceVersionLabel, version)
}
if level, ok := labels[AuditLevelLabel]; ok {
p.Audit.Level, err = ParseLevel(level)
errs = appendErr(errs, err, AuditLevelLabel)
errs = appendErr(errs, err, AuditLevelLabel, level)
if err != nil {
p.Audit.Level = LevelPrivileged // Fail open for audit.
}
}
if version, ok := labels[AuditVersionLabel]; ok {
p.Audit.Version, err = ParseVersion(version)
errs = appendErr(errs, err, AuditVersionLabel)
errs = appendErr(errs, err, AuditVersionLabel, version)
}
if level, ok := labels[WarnLevelLabel]; ok {
p.Warn.Level, err = ParseLevel(level)
errs = appendErr(errs, err, WarnLevelLabel)
errs = appendErr(errs, err, WarnLevelLabel, level)
if err != nil {
p.Warn.Level = LevelPrivileged // Fail open for warn.
}
}
if version, ok := labels[WarnVersionLabel]; ok {
p.Warn.Version, err = ParseVersion(version)
errs = appendErr(errs, err, WarnVersionLabel)
errs = appendErr(errs, err, WarnVersionLabel, version)
}
return p, errors.NewAggregate(errs)
return p, errs
}
// CompareLevels returns an integer comparing two levels by strictness. The result will be 0 if
@@ -211,10 +211,12 @@ func CompareLevels(a, b Level) int {
return 0
}
// appendErr is a helper function to collect field-specific errors.
func appendErr(errs []error, err error, field string) []error {
var labelsPath = field.NewPath("metadata", "labels")
// appendErr is a helper function to collect label-specific errors.
func appendErr(errs field.ErrorList, err error, label, value string) field.ErrorList {
if err != nil {
return append(errs, fmt.Errorf("%s: %s", field, err.Error()))
return append(errs, field.Invalid(labelsPath.Key(label), value, err.Error()))
}
return errs
}