diff --git a/staging/src/k8s.io/pod-security-admission/admission/admission.go b/staging/src/k8s.io/pod-security-admission/admission/admission.go index 0a75435103d..722aee1955d 100644 --- a/staging/src/k8s.io/pod-security-admission/admission/admission.go +++ b/staging/src/k8s.io/pod-security-admission/admission/admission.go @@ -286,7 +286,7 @@ func (a *Admission) ValidateNamespace(ctx context.Context, attrs api.Attributes) return sharedAllowedResponse() } if a.exemptNamespace(attrs.GetNamespace()) { - return sharedAllowedResponse() + return sharedAllowedByNamespaceExemptionResponse() } response := allowedResponse() response.Warnings = a.EvaluatePodsInNamespace(ctx, namespace.Name, newPolicy.Enforce) @@ -319,8 +319,12 @@ func (a *Admission) ValidatePod(ctx context.Context, attrs api.Attributes) *admi return sharedAllowedResponse() } // short-circuit on exempt namespaces and users - if a.exemptNamespace(attrs.GetNamespace()) || a.exemptUser(attrs.GetUserName()) { - return sharedAllowedResponse() + if a.exemptNamespace(attrs.GetNamespace()) { + return sharedAllowedByNamespaceExemptionResponse() + } + + if a.exemptUser(attrs.GetUserName()) { + return sharedAllowedByUserExemptionResponse() } // short-circuit on privileged enforce+audit+warn namespaces @@ -371,8 +375,12 @@ func (a *Admission) ValidatePodController(ctx context.Context, attrs api.Attribu return sharedAllowedResponse() } // short-circuit on exempt namespaces and users - if a.exemptNamespace(attrs.GetNamespace()) || a.exemptUser(attrs.GetUserName()) { - return sharedAllowedResponse() + if a.exemptNamespace(attrs.GetNamespace()) { + return sharedAllowedByNamespaceExemptionResponse() + } + + if a.exemptUser(attrs.GetUserName()) { + return sharedAllowedByUserExemptionResponse() } // short-circuit on privileged audit+warn namespaces @@ -409,7 +417,7 @@ func (a *Admission) ValidatePodController(ctx context.Context, attrs api.Attribu func (a *Admission) EvaluatePod(ctx context.Context, nsPolicy api.Policy, nsPolicyErr error, podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec, attrs api.Attributes, enforce bool) *admissionv1.AdmissionResponse { // short-circuit on exempt runtimeclass if a.exemptRuntimeClass(podSpec.RuntimeClassName) { - return sharedAllowedResponse() + return sharedAllowedByRuntimeClassExemptionResponse() } auditAnnotations := map[string]string{} @@ -424,6 +432,8 @@ func (a *Admission) EvaluatePod(ctx context.Context, nsPolicy api.Policy, nsPoli response := allowedResponse() if enforce { + auditAnnotations[api.EnforcedPolicyAnnotationKey] = nsPolicy.Enforce.String() + if result := policy.AggregateCheckResults(a.Evaluator.EvaluatePod(nsPolicy.Enforce, podMetadata, podSpec)); !result.Allowed { response = forbiddenResponse(fmt.Sprintf( "pod violates PodSecurity %q: %s", @@ -438,7 +448,7 @@ func (a *Admission) EvaluatePod(ctx context.Context, nsPolicy api.Policy, nsPoli // TODO: reuse previous evaluation if audit level+version is the same as enforce level+version if result := policy.AggregateCheckResults(a.Evaluator.EvaluatePod(nsPolicy.Audit, podMetadata, podSpec)); !result.Allowed { - auditAnnotations["audit"] = fmt.Sprintf( + auditAnnotations[api.AuditViolationsAnnotationKey] = fmt.Sprintf( "would violate PodSecurity %q: %s", nsPolicy.Audit.String(), result.ForbiddenDetail(), @@ -565,17 +575,41 @@ func (a *Admission) PolicyToEvaluate(labels map[string]string) (api.Policy, fiel return api.PolicyToEvaluate(labels, a.defaultPolicy) } -var _sharedAllowedResponse = allowedResponse() +var ( + _sharedAllowedResponse = allowedResponse() + _sharedAllowedByUserExemptionResponse = allowedByExemptResponse("user") + _sharedAllowedByNamespaceExemptionResponse = allowedByExemptResponse("namespace") + _sharedAllowedByRuntimeClassExemptionResponse = allowedByExemptResponse("runtimeClass") +) func sharedAllowedResponse() *admissionv1.AdmissionResponse { return _sharedAllowedResponse } +func sharedAllowedByUserExemptionResponse() *admissionv1.AdmissionResponse { + return _sharedAllowedByUserExemptionResponse +} + +func sharedAllowedByNamespaceExemptionResponse() *admissionv1.AdmissionResponse { + return _sharedAllowedByNamespaceExemptionResponse +} + +func sharedAllowedByRuntimeClassExemptionResponse() *admissionv1.AdmissionResponse { + return _sharedAllowedByRuntimeClassExemptionResponse +} + // allowedResponse is the response used when the admission decision is allow. func allowedResponse() *admissionv1.AdmissionResponse { return &admissionv1.AdmissionResponse{Allowed: true} } +func allowedByExemptResponse(exemptionReason string) *admissionv1.AdmissionResponse { + return &admissionv1.AdmissionResponse{ + Allowed: true, + AuditAnnotations: map[string]string{api.ExemptionReasonAnnotationKey: exemptionReason}, + } +} + func failureResponse(msg string, reason metav1.StatusReason, code int32) *admissionv1.AdmissionResponse { return &admissionv1.AdmissionResponse{ Allowed: false, diff --git a/staging/src/k8s.io/pod-security-admission/admission/admission_test.go b/staging/src/k8s.io/pod-security-admission/admission/admission_test.go index 49fd3b7f8fa..e3f652fcb63 100644 --- a/staging/src/k8s.io/pod-security-admission/admission/admission_test.go +++ b/staging/src/k8s.io/pod-security-admission/admission/admission_test.go @@ -609,25 +609,28 @@ func TestValidatePodController(t *testing.T) { gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, }, { - desc: "namespace in exemptNamespaces will be exempted", - newObject: &badDeploy, - gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, - gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, - exemptNamespaces: []string{testNamespace}, + desc: "namespace in exemptNamespaces will be exempted", + newObject: &badDeploy, + gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, + exemptNamespaces: []string{testNamespace}, + expectAuditAnnotations: map[string]string{"exempt": "namespace"}, }, { - desc: "runtimeClass in exemptRuntimeClasses will be exempted", - newObject: &badDeploy, - gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, - gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, - exemptRuntimeClasses: []string{"containerd"}, + desc: "runtimeClass in exemptRuntimeClasses will be exempted", + newObject: &badDeploy, + gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, + exemptRuntimeClasses: []string{"containerd"}, + expectAuditAnnotations: map[string]string{"exempt": "runtimeClass"}, }, { - desc: "user in exemptUsers will be exempted", - newObject: &badDeploy, - gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, - gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, - exemptUsers: []string{"testuser"}, + desc: "user in exemptUsers will be exempted", + newObject: &badDeploy, + gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, + exemptUsers: []string{"testuser"}, + expectAuditAnnotations: map[string]string{"exempt": "user"}, }, { desc: "podMetadata == nil && podSpec == nil will skip verification", @@ -647,7 +650,7 @@ func TestValidatePodController(t *testing.T) { newObject: &badDeploy, gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, - expectAuditAnnotations: map[string]string{"audit": "would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"}, + expectAuditAnnotations: map[string]string{"audit-violations": "would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"}, expectWarnings: []string{"would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"}, }, { @@ -656,7 +659,7 @@ func TestValidatePodController(t *testing.T) { oldObject: &goodDeploy, gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, - expectAuditAnnotations: map[string]string{"audit": "would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"}, + expectAuditAnnotations: map[string]string{"audit-violations": "would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"}, expectWarnings: []string{"would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"}, }, } diff --git a/staging/src/k8s.io/pod-security-admission/api/constants.go b/staging/src/k8s.io/pod-security-admission/api/constants.go index 394f7b7499e..9d87ad59b17 100644 --- a/staging/src/k8s.io/pod-security-admission/api/constants.go +++ b/staging/src/k8s.io/pod-security-admission/api/constants.go @@ -43,4 +43,8 @@ const ( AuditVersionLabel = labelPrefix + "audit-version" WarnLevelLabel = labelPrefix + "warn" WarnVersionLabel = labelPrefix + "warn-version" + + ExemptionReasonAnnotationKey = "exempt" + AuditViolationsAnnotationKey = "audit-violations" + EnforcedPolicyAnnotationKey = "enforce-policy" )