Admission Controllers and Policy Enforcement
Admission Controllers and Policy Enforcement
Admission controllers provide policy enforcement at the orchestration layer. They can modify or reject resource creation based on security policies. ValidatingAdmissionWebhooks ensure resources meet security requirements. MutatingAdmissionWebhooks can add security defaults to resources. Policy engines like Open Policy Agent enable complex policy logic.
Implementing admission control requires careful policy design to avoid blocking legitimate operations. Policies should provide clear error messages guiding users toward compliance. Dry-run modes allow policy testing before enforcement. Exemption mechanisms enable emergency overrides with proper audit trails. Regular policy reviews ensure continued relevance.
// Example: Admission webhook for security enforcement
package main
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type SecurityAdmissionWebhook struct {
MinimumTLSVersion string
RequiredLabels []string
ForbiddenImages []string
}
func (s *SecurityAdmissionWebhook) Validate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
pod := corev1.Pod{}
if err := json.Unmarshal(ar.Request.Object.Raw, &pod); err != nil {
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
// Check security policies
violations := []string{}
// Verify required labels
for _, label := range s.RequiredLabels {
if _, exists := pod.Labels[label]; !exists {
violations = append(violations, fmt.Sprintf("Missing required label: %s", label))
}
}
// Check container configurations
for _, container := range pod.Spec.Containers {
// Verify image sources
for _, forbidden := range s.ForbiddenImages {
if strings.Contains(container.Image, forbidden) {
violations = append(violations,
fmt.Sprintf("Forbidden image registry: %s", container.Image))
}
}
// Ensure non-root user
if container.SecurityContext == nil ||
container.SecurityContext.RunAsNonRoot == nil ||
!*container.SecurityContext.RunAsNonRoot {
violations = append(violations,
fmt.Sprintf("Container %s must run as non-root", container.Name))
}
// Check for privileged mode
if container.SecurityContext != nil &&
container.SecurityContext.Privileged != nil &&
*container.SecurityContext.Privileged {
violations = append(violations,
fmt.Sprintf("Container %s cannot run in privileged mode", container.Name))
}
// Verify resource limits
if container.Resources.Limits == nil {
violations = append(violations,
fmt.Sprintf("Container %s must specify resource limits", container.Name))
}
}
// Return validation result
allowed := len(violations) == 0
result := &admissionv1.AdmissionResponse{
UID: ar.Request.UID,
Allowed: allowed,
}
if !allowed {
result.Result = &metav1.Status{
Message: fmt.Sprintf("Security policy violations: %s",
strings.Join(violations, "; ")),
}
}
return result
}
func (s *SecurityAdmissionWebhook) Mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
pod := corev1.Pod{}
if err := json.Unmarshal(ar.Request.Object.Raw, &pod); err != nil {
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
patches := []map[string]interface{}{}
// Add security defaults
if pod.Labels == nil {
patches = append(patches, map[string]interface{}{
"op": "add",
"path": "/metadata/labels",
"value": map[string]string{},
})
}
// Add security scanning label
patches = append(patches, map[string]interface{}{
"op": "add",
"path": "/metadata/labels/security-scan",
"value": "required",
})
// Set security context if missing
for i, container := range pod.Spec.Containers {
if container.SecurityContext == nil {
patches = append(patches, map[string]interface{}{
"op": "add",
"path": fmt.Sprintf("/spec/containers/%d/securityContext", i),
"value": map[string]interface{}{
"runAsNonRoot": true,
"readOnlyRootFilesystem": true,
"allowPrivilegeEscalation": false,
"capabilities": map[string]interface{}{
"drop": []string{"ALL"},
},
},
})
}
}
patchBytes, _ := json.Marshal(patches)
return &admissionv1.AdmissionResponse{
UID: ar.Request.UID,
Allowed: true,
Patch: patchBytes,
PatchType: func() *admissionv1.PatchType {
pt := admissionv1.PatchTypeJSONPatch
return &pt
}(),
}
}