mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
PodSecurity: admission: admission library
Co-authored-by: Jordan Liggitt <liggitt@google.com>
This commit is contained in:
parent
29f5ebf1fe
commit
02a6187757
549
staging/src/k8s.io/pod-security-admission/admission/admission.go
Normal file
549
staging/src/k8s.io/pod-security-admission/admission/admission.go
Normal file
@ -0,0 +1,549 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionapi "k8s.io/pod-security-admission/admission/api"
|
||||
"k8s.io/pod-security-admission/admission/api/validation"
|
||||
"k8s.io/pod-security-admission/api"
|
||||
"k8s.io/pod-security-admission/metrics"
|
||||
"k8s.io/pod-security-admission/policy"
|
||||
)
|
||||
|
||||
const (
|
||||
namespaceMaxPodsToCheck = 3000
|
||||
namespacePodCheckTimeout = 1 * time.Second
|
||||
)
|
||||
|
||||
// Admission implements the core admission logic for the Pod Security Admission controller.
|
||||
// The admission logic can be
|
||||
type Admission struct {
|
||||
Configuration *admissionapi.PodSecurityConfiguration
|
||||
|
||||
// Getting policy checks per level/version
|
||||
Evaluator policy.Evaluator
|
||||
|
||||
// Metrics
|
||||
Metrics metrics.EvaluationRecorder
|
||||
|
||||
// Arbitrary object --> PodSpec
|
||||
PodSpecExtractor PodSpecExtractor
|
||||
|
||||
// API connections
|
||||
NamespaceGetter NamespaceGetter
|
||||
PodLister PodLister
|
||||
|
||||
defaultPolicy api.Policy
|
||||
}
|
||||
|
||||
type NamespaceGetter interface {
|
||||
GetNamespace(ctx context.Context, name string) (*corev1.Namespace, error)
|
||||
}
|
||||
|
||||
type PodLister interface {
|
||||
ListPods(ctx context.Context, namespace string) ([]*corev1.Pod, error)
|
||||
}
|
||||
|
||||
// PodSpecExtractor extracts a PodSpec from pod-controller resources that embed a PodSpec.
|
||||
// This interface can be extended to enforce policy on CRDs for custom pod-controllers.
|
||||
type PodSpecExtractor interface {
|
||||
// HasPodSpec returns true if the given resource type MAY contain an extractable PodSpec.
|
||||
HasPodSpec(schema.GroupResource) bool
|
||||
// ExtractPodSpec returns a pod spec and metadata to evaluate from the object.
|
||||
// An error returned here does not block admission of the pod-spec-containing object and is not returned to the user.
|
||||
// If the object has no pod spec, return `nil, nil, nil`.
|
||||
ExtractPodSpec(runtime.Object) (*metav1.ObjectMeta, *corev1.PodSpec, error)
|
||||
}
|
||||
|
||||
var defaultPodSpecResources = map[schema.GroupResource]bool{
|
||||
corev1.Resource("pods"): true,
|
||||
corev1.Resource("replicationcontrollers"): true,
|
||||
corev1.Resource("podtemplates"): true,
|
||||
appsv1.Resource("replicasets"): true,
|
||||
appsv1.Resource("deployments"): true,
|
||||
appsv1.Resource("statefulsets"): true,
|
||||
appsv1.Resource("daemonsets"): true,
|
||||
batchv1.Resource("jobs"): true,
|
||||
batchv1.Resource("cronjobs"): true,
|
||||
}
|
||||
|
||||
type DefaultPodSpecExtractor struct{}
|
||||
|
||||
func (DefaultPodSpecExtractor) HasPodSpec(gr schema.GroupResource) bool {
|
||||
return defaultPodSpecResources[gr]
|
||||
}
|
||||
|
||||
func (DefaultPodSpecExtractor) ExtractPodSpec(obj runtime.Object) (*metav1.ObjectMeta, *corev1.PodSpec, error) {
|
||||
switch o := obj.(type) {
|
||||
case *corev1.Pod:
|
||||
return &o.ObjectMeta, &o.Spec, nil
|
||||
case *corev1.PodTemplate:
|
||||
return extractPodSpecFromTemplate(&o.Template)
|
||||
case *corev1.ReplicationController:
|
||||
return extractPodSpecFromTemplate(o.Spec.Template)
|
||||
case *appsv1.ReplicaSet:
|
||||
return extractPodSpecFromTemplate(&o.Spec.Template)
|
||||
case *appsv1.Deployment:
|
||||
return extractPodSpecFromTemplate(&o.Spec.Template)
|
||||
case *appsv1.DaemonSet:
|
||||
return extractPodSpecFromTemplate(&o.Spec.Template)
|
||||
case *appsv1.StatefulSet:
|
||||
return extractPodSpecFromTemplate(&o.Spec.Template)
|
||||
case *batchv1.Job:
|
||||
return extractPodSpecFromTemplate(&o.Spec.Template)
|
||||
case *batchv1.CronJob:
|
||||
return extractPodSpecFromTemplate(&o.Spec.JobTemplate.Spec.Template)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unexpected object type: %s", obj.GetObjectKind().GroupVersionKind().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (DefaultPodSpecExtractor) PodSpecResources() []schema.GroupResource {
|
||||
retval := make([]schema.GroupResource, 0, len(defaultPodSpecResources))
|
||||
for r := range defaultPodSpecResources {
|
||||
retval = append(retval, r)
|
||||
}
|
||||
return retval
|
||||
}
|
||||
|
||||
func extractPodSpecFromTemplate(template *corev1.PodTemplateSpec) (*metav1.ObjectMeta, *corev1.PodSpec, error) {
|
||||
if template == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return &template.ObjectMeta, &template.Spec, nil
|
||||
}
|
||||
|
||||
// CompleteConfiguration() sets up default or derived configuration.
|
||||
func (a *Admission) CompleteConfiguration() error {
|
||||
if a.Configuration != nil {
|
||||
if p, err := admissionapi.ToPolicy(a.Configuration.Defaults); err != nil {
|
||||
return err
|
||||
} else {
|
||||
a.defaultPolicy = p
|
||||
}
|
||||
}
|
||||
|
||||
if a.PodSpecExtractor == nil {
|
||||
a.PodSpecExtractor = &DefaultPodSpecExtractor{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateConfiguration() ensures all required fields are set with valid values.
|
||||
func (a *Admission) ValidateConfiguration() error {
|
||||
if a.Configuration == nil {
|
||||
return fmt.Errorf("configuration required")
|
||||
} else if errs := validation.ValidatePodSecurityConfiguration(a.Configuration); len(errs) > 0 {
|
||||
return errs.ToAggregate()
|
||||
} else {
|
||||
if p, err := admissionapi.ToPolicy(a.Configuration.Defaults); err != nil {
|
||||
return err
|
||||
} else if !reflect.DeepEqual(p, a.defaultPolicy) {
|
||||
return fmt.Errorf("default policy does not match; CompleteConfiguration() was not called before ValidateConfiguration()")
|
||||
}
|
||||
}
|
||||
// TODO: check metrics is non-nil?
|
||||
if a.PodSpecExtractor == nil {
|
||||
return fmt.Errorf("PodSpecExtractor required")
|
||||
}
|
||||
if a.Evaluator == nil {
|
||||
return fmt.Errorf("Evaluator required")
|
||||
}
|
||||
if a.NamespaceGetter == nil {
|
||||
return fmt.Errorf("NamespaceGetter required")
|
||||
}
|
||||
if a.PodLister == nil {
|
||||
return fmt.Errorf("PodLister required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate admits an API request.
|
||||
// The objects in admission attributes are expected to be external v1 objects that we care about.
|
||||
func (a *Admission) Validate(ctx context.Context, attrs admission.Attributes) admissionv1.AdmissionResponse {
|
||||
var response admissionv1.AdmissionResponse
|
||||
switch attrs.GetResource().GroupResource() {
|
||||
case corev1.Resource("namespaces"):
|
||||
response = a.ValidateNamespace(ctx, attrs)
|
||||
case corev1.Resource("pods"):
|
||||
response = a.ValidatePod(ctx, attrs)
|
||||
default:
|
||||
response = a.ValidatePodController(ctx, attrs)
|
||||
}
|
||||
|
||||
// TODO: record metrics.
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func (a *Admission) ValidateNamespace(ctx context.Context, attrs admission.Attributes) admissionv1.AdmissionResponse {
|
||||
// short-circuit on subresources
|
||||
if attrs.GetSubresource() != "" {
|
||||
return allowedResponse()
|
||||
}
|
||||
namespace, ok := attrs.GetObject().(*corev1.Namespace)
|
||||
if !ok {
|
||||
klog.InfoS("failed to assert namespace type", "type", reflect.TypeOf(attrs.GetObject()))
|
||||
return internalErrorResponse("failed to decode namespace")
|
||||
}
|
||||
|
||||
newPolicy, newErr := a.PolicyToEvaluate(namespace.Labels)
|
||||
|
||||
switch attrs.GetOperation() {
|
||||
case admission.Create:
|
||||
// require valid labels on create
|
||||
if newErr != nil {
|
||||
return invalidResponse(newErr.Error())
|
||||
}
|
||||
return allowedResponse()
|
||||
|
||||
case admission.Update:
|
||||
// if update, check if policy labels changed
|
||||
oldNamespace, ok := attrs.GetOldObject().(*corev1.Namespace)
|
||||
if !ok {
|
||||
klog.InfoS("failed to assert old namespace type", "type", reflect.TypeOf(attrs.GetOldObject()))
|
||||
return internalErrorResponse("failed to decode old namespace")
|
||||
}
|
||||
oldPolicy, oldErr := 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())
|
||||
}
|
||||
|
||||
// Skip dry-running pods:
|
||||
// * if the enforce policy is unchanged
|
||||
// * if the new enforce policy is privileged
|
||||
// * if the new enforce is the same version and level was relaxed
|
||||
// * for exempt namespaces
|
||||
if newPolicy.Enforce == oldPolicy.Enforce {
|
||||
return allowedResponse()
|
||||
}
|
||||
if newPolicy.Enforce.Level == api.LevelPrivileged {
|
||||
return allowedResponse()
|
||||
}
|
||||
if newPolicy.Enforce.Version == oldPolicy.Enforce.Version &&
|
||||
api.CompareLevels(newPolicy.Enforce.Level, oldPolicy.Enforce.Level) < 1 {
|
||||
return allowedResponse()
|
||||
}
|
||||
if a.exemptNamespace(attrs.GetNamespace()) {
|
||||
return allowedResponse()
|
||||
}
|
||||
response := allowedResponse()
|
||||
response.Warnings = a.EvaluatePodsInNamespace(ctx, namespace.Name, newPolicy.Enforce)
|
||||
return response
|
||||
|
||||
default:
|
||||
return allowedResponse()
|
||||
}
|
||||
}
|
||||
|
||||
// ignoredPodSubresources is a set of ignored Pod subresources.
|
||||
// Any other subresource is expected to be a *v1.Pod type and is evaluated.
|
||||
// This ensures a version skewed webhook fails safe and denies an unknown pod subresource that allows modifying the pod spec.
|
||||
var ignoredPodSubresources = map[string]bool{
|
||||
"exec": true,
|
||||
"attach": true,
|
||||
"binding": true,
|
||||
"eviction": true,
|
||||
"log": true,
|
||||
"portforward": true,
|
||||
"proxy": true,
|
||||
"status": true,
|
||||
}
|
||||
|
||||
func (a *Admission) ValidatePod(ctx context.Context, attrs admission.Attributes) admissionv1.AdmissionResponse {
|
||||
// short-circuit on ignored subresources
|
||||
if ignoredPodSubresources[attrs.GetSubresource()] {
|
||||
return allowedResponse()
|
||||
}
|
||||
// short-circuit on exempt namespaces and users
|
||||
if a.exemptNamespace(attrs.GetNamespace()) || a.exemptUser(attrs.GetUserInfo().GetName()) {
|
||||
return allowedResponse()
|
||||
}
|
||||
|
||||
pod, ok := attrs.GetObject().(*corev1.Pod)
|
||||
if !ok {
|
||||
klog.InfoS("failed to assert pod type", "type", reflect.TypeOf(attrs.GetObject()))
|
||||
return internalErrorResponse("failed to decode pod")
|
||||
}
|
||||
enforce := true
|
||||
if attrs.GetOperation() == admission.Update {
|
||||
oldPod, ok := attrs.GetOldObject().(*corev1.Pod)
|
||||
if !ok {
|
||||
klog.InfoS("failed to assert old pod type", "type", reflect.TypeOf(attrs.GetOldObject()))
|
||||
return internalErrorResponse("failed to decode old pod")
|
||||
}
|
||||
if !isSignificantPodUpdate(pod, oldPod) {
|
||||
// Nothing we care about changed, so always allow the update.
|
||||
return allowedResponse()
|
||||
}
|
||||
}
|
||||
return a.EvaluatePod(ctx, attrs.GetNamespace(), &pod.ObjectMeta, &pod.Spec, enforce)
|
||||
}
|
||||
|
||||
func (a *Admission) ValidatePodController(ctx context.Context, attrs admission.Attributes) admissionv1.AdmissionResponse {
|
||||
// short-circuit on subresources
|
||||
if attrs.GetSubresource() != "" {
|
||||
return allowedResponse()
|
||||
}
|
||||
// short-circuit on exempt namespaces and users
|
||||
if a.exemptNamespace(attrs.GetNamespace()) || a.exemptUser(attrs.GetUserInfo().GetName()) {
|
||||
return allowedResponse()
|
||||
}
|
||||
|
||||
podMetadata, podSpec, err := a.PodSpecExtractor.ExtractPodSpec(attrs.GetObject())
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to extract pod spec")
|
||||
return internalErrorResponse("failed to extract pod template")
|
||||
}
|
||||
if podMetadata == nil && podSpec == nil {
|
||||
// if a controller with an optional pod spec does not contain a pod spec, skip validation
|
||||
return allowedResponse()
|
||||
}
|
||||
return a.EvaluatePod(ctx, attrs.GetNamespace(), podMetadata, podSpec, false)
|
||||
}
|
||||
|
||||
// EvaluatePod looks up the policy for the pods namespace, and checks it against the given pod(-like) object.
|
||||
// The enforce policy is only checked if enforce=true.
|
||||
func (a *Admission) EvaluatePod(ctx context.Context, namespaceName string, podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec, enforce bool) admissionv1.AdmissionResponse {
|
||||
// short-circuit on exempt runtimeclass
|
||||
if a.exemptRuntimeClass(podSpec.RuntimeClassName) {
|
||||
return allowedResponse()
|
||||
}
|
||||
|
||||
namespace, err := a.NamespaceGetter.GetNamespace(ctx, namespaceName)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to fetch pod namespace", "namespace", namespaceName)
|
||||
return internalErrorResponse(fmt.Sprintf("failed to lookup namespace %s", namespaceName))
|
||||
}
|
||||
|
||||
auditAnnotations := map[string]string{}
|
||||
nsPolicy, err := a.PolicyToEvaluate(namespace.Labels)
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("failed to parse PodSecurity namespace labels", "err", err)
|
||||
auditAnnotations["error"] = fmt.Sprintf("Failed to parse policy: %v", err)
|
||||
}
|
||||
|
||||
response := allowedResponse()
|
||||
if enforce {
|
||||
if result := policy.AggregateCheckResults(a.Evaluator.EvaluatePod(nsPolicy.Enforce, podMetadata, podSpec)); !result.Allowed {
|
||||
response = forbiddenResponse(result.ForbiddenDetail())
|
||||
}
|
||||
}
|
||||
|
||||
// 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"] = result.ForbiddenDetail()
|
||||
}
|
||||
|
||||
// TODO: reuse previous evaluation if warn level+version is the same as audit or enforce level+version
|
||||
if result := policy.AggregateCheckResults(a.Evaluator.EvaluatePod(nsPolicy.Warn, podMetadata, podSpec)); !result.Allowed {
|
||||
// TODO: Craft a better user-facing warning message
|
||||
response.Warnings = append(response.Warnings, fmt.Sprintf("Pod violates PodSecurity profile %s: %s", nsPolicy.Warn.String(), result.ForbiddenDetail()))
|
||||
}
|
||||
|
||||
response.AuditAnnotations = auditAnnotations
|
||||
return response
|
||||
}
|
||||
|
||||
func (a *Admission) EvaluatePodsInNamespace(ctx context.Context, namespace string, enforce api.LevelVersion) []string {
|
||||
timeout := namespacePodCheckTimeout
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
timeRemaining := time.Duration(0.9 * float64(time.Until(deadline))) // Leave a little time to respond.
|
||||
if timeout > timeRemaining {
|
||||
timeout = timeRemaining
|
||||
}
|
||||
}
|
||||
deadline := time.Now().Add(timeout)
|
||||
ctx, cancel := context.WithDeadline(ctx, deadline)
|
||||
defer cancel()
|
||||
|
||||
pods, err := a.PodLister.ListPods(ctx, namespace)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Failed to list pods", "namespace", namespace)
|
||||
return []string{"Failed to list pods"}
|
||||
}
|
||||
|
||||
var warnings []string
|
||||
if len(pods) > namespaceMaxPodsToCheck {
|
||||
warnings = append(warnings, fmt.Sprintf("Large namespace: only checking the first %d of %d pods", namespaceMaxPodsToCheck, len(pods)))
|
||||
pods = pods[0:namespaceMaxPodsToCheck]
|
||||
}
|
||||
|
||||
for i, pod := range pods {
|
||||
// short-circuit on exempt runtimeclass
|
||||
if a.exemptRuntimeClass(pod.Spec.RuntimeClassName) {
|
||||
continue
|
||||
}
|
||||
r := policy.AggregateCheckResults(a.Evaluator.EvaluatePod(enforce, &pod.ObjectMeta, &pod.Spec))
|
||||
if !r.Allowed {
|
||||
// TODO: consider aggregating results (e.g. multiple pods failed for the same reasons)
|
||||
warnings = append(warnings, fmt.Sprintf("%s: %s", pod.Name, r.ForbiddenReason()))
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
return append(warnings, fmt.Sprintf("Timeout reached after checking %d pods", i+1))
|
||||
}
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
func (a *Admission) PolicyToEvaluate(labels map[string]string) (api.Policy, error) {
|
||||
return api.PolicyToEvaluate(labels, a.defaultPolicy)
|
||||
}
|
||||
|
||||
// allowResponse is the response used when the admission decision is allow.
|
||||
func allowedResponse() admissionv1.AdmissionResponse {
|
||||
return admissionv1.AdmissionResponse{Allowed: true}
|
||||
}
|
||||
|
||||
// forbiddenResponse is the response used when the admission decision is deny for policy violations.
|
||||
func forbiddenResponse(msg string) admissionv1.AdmissionResponse {
|
||||
return admissionv1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: metav1.StatusReasonForbidden,
|
||||
Message: msg,
|
||||
Code: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// invalidResponse is the response used for namespace requests when namespace labels are invalid.
|
||||
func invalidResponse(msg string) admissionv1.AdmissionResponse {
|
||||
return admissionv1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: metav1.StatusReasonInvalid,
|
||||
Message: msg,
|
||||
Code: 422,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// internalErrorResponse is the response used for unexpected errors
|
||||
func internalErrorResponse(msg string) admissionv1.AdmissionResponse {
|
||||
return admissionv1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: metav1.StatusReasonInternalError,
|
||||
Message: msg,
|
||||
Code: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// isSignificantPodUpdate determines whether a pod update should trigger a policy evaluation.
|
||||
// Relevant mutable pod fields as of 1.21 are image and seccomp annotations:
|
||||
// * https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/apis/core/validation/validation.go#L3947-L3949
|
||||
func isSignificantPodUpdate(pod, oldPod *corev1.Pod) bool {
|
||||
if pod.Annotations[corev1.SeccompPodAnnotationKey] != oldPod.Annotations[corev1.SeccompPodAnnotationKey] {
|
||||
return true
|
||||
}
|
||||
if len(pod.Spec.Containers) != len(oldPod.Spec.Containers) {
|
||||
return true
|
||||
}
|
||||
if len(pod.Spec.InitContainers) != len(oldPod.Spec.InitContainers) {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < len(pod.Spec.Containers); i++ {
|
||||
if isSignificantContainerUpdate(&pod.Spec.Containers[i], &oldPod.Spec.Containers[i], pod.Annotations, oldPod.Annotations) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(pod.Spec.InitContainers); i++ {
|
||||
if isSignificantContainerUpdate(&pod.Spec.InitContainers[i], &oldPod.Spec.InitContainers[i], pod.Annotations, oldPod.Annotations) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, c := range pod.Spec.EphemeralContainers {
|
||||
var oldC *corev1.Container
|
||||
for i, oc := range oldPod.Spec.EphemeralContainers {
|
||||
if oc.Name == c.Name {
|
||||
oldC = (*corev1.Container)(&oldPod.Spec.EphemeralContainers[i].EphemeralContainerCommon)
|
||||
break
|
||||
}
|
||||
}
|
||||
if oldC == nil {
|
||||
return true // EphemeralContainer added
|
||||
}
|
||||
if isSignificantContainerUpdate((*corev1.Container)(&c.EphemeralContainerCommon), oldC, pod.Annotations, oldPod.Annotations) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isSignificantContainerUpdate determines whether a container update should trigger a policy evaluation.
|
||||
func isSignificantContainerUpdate(container, oldContainer *corev1.Container, annotations, oldAnnotations map[string]string) bool {
|
||||
if container.Image != oldContainer.Image {
|
||||
return true
|
||||
}
|
||||
seccompKey := corev1.SeccompContainerAnnotationKeyPrefix + container.Name
|
||||
return annotations[seccompKey] != oldAnnotations[seccompKey]
|
||||
}
|
||||
|
||||
func (a *Admission) exemptNamespace(namespace string) bool {
|
||||
if len(namespace) == 0 {
|
||||
return false
|
||||
}
|
||||
// TODO: consider optimizing to O(1) lookup
|
||||
return containsString(namespace, a.Configuration.Exemptions.Namespaces)
|
||||
}
|
||||
func (a *Admission) exemptUser(username string) bool {
|
||||
if len(username) == 0 {
|
||||
return false
|
||||
}
|
||||
// TODO: consider optimizing to O(1) lookup
|
||||
return containsString(username, a.Configuration.Exemptions.Usernames)
|
||||
}
|
||||
func (a *Admission) exemptRuntimeClass(runtimeClass *string) bool {
|
||||
if runtimeClass == nil || len(*runtimeClass) == 0 {
|
||||
return false
|
||||
}
|
||||
// TODO: consider optimizing to O(1) lookup
|
||||
return containsString(*runtimeClass, a.Configuration.Exemptions.RuntimeClasses)
|
||||
}
|
||||
func containsString(needle string, haystack []string) bool {
|
||||
for _, s := range haystack {
|
||||
if s == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -0,0 +1,465 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionapi "k8s.io/pod-security-admission/admission/api"
|
||||
"k8s.io/pod-security-admission/api"
|
||||
"k8s.io/pod-security-admission/policy"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
func TestDefaultExtractPodSpec(t *testing.T) {
|
||||
metadata := metav1.ObjectMeta{
|
||||
Name: "foo-pod",
|
||||
}
|
||||
spec := corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "foo-container",
|
||||
}},
|
||||
}
|
||||
objects := []runtime.Object{
|
||||
&corev1.Pod{
|
||||
ObjectMeta: metadata,
|
||||
Spec: spec,
|
||||
},
|
||||
&corev1.PodTemplate{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-template"},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metadata,
|
||||
Spec: spec,
|
||||
},
|
||||
},
|
||||
&corev1.ReplicationController{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-rc"},
|
||||
Spec: corev1.ReplicationControllerSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
ObjectMeta: metadata,
|
||||
Spec: spec,
|
||||
},
|
||||
},
|
||||
},
|
||||
&appsv1.ReplicaSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-rs"},
|
||||
Spec: appsv1.ReplicaSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metadata,
|
||||
Spec: spec,
|
||||
},
|
||||
},
|
||||
},
|
||||
&appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-deployment"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metadata,
|
||||
Spec: spec,
|
||||
},
|
||||
},
|
||||
},
|
||||
&appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-ss"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metadata,
|
||||
Spec: spec,
|
||||
},
|
||||
},
|
||||
},
|
||||
&appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-ds"},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metadata,
|
||||
Spec: spec,
|
||||
},
|
||||
},
|
||||
},
|
||||
&batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-job"},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metadata,
|
||||
Spec: spec,
|
||||
},
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-cronjob"},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metadata,
|
||||
Spec: spec,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
extractor := &DefaultPodSpecExtractor{}
|
||||
for _, obj := range objects {
|
||||
name := obj.(metav1.Object).GetName()
|
||||
actualMetadata, actualSpec, err := extractor.ExtractPodSpec(obj)
|
||||
assert.NoError(t, err, name)
|
||||
assert.Equal(t, &metadata, actualMetadata, "%s: Metadata mismatch", name)
|
||||
assert.Equal(t, &spec, actualSpec, "%s: PodSpec mismatch", name)
|
||||
}
|
||||
|
||||
service := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-svc",
|
||||
},
|
||||
}
|
||||
_, _, err := extractor.ExtractPodSpec(service)
|
||||
assert.Error(t, err, "service should not have an extractable pod spec")
|
||||
}
|
||||
|
||||
func TestDefaultHasPodSpec(t *testing.T) {
|
||||
podLikeResources := []schema.GroupResource{
|
||||
corev1.Resource("pods"),
|
||||
corev1.Resource("replicationcontrollers"),
|
||||
corev1.Resource("podtemplates"),
|
||||
appsv1.Resource("replicasets"),
|
||||
appsv1.Resource("deployments"),
|
||||
appsv1.Resource("statefulsets"),
|
||||
appsv1.Resource("daemonsets"),
|
||||
batchv1.Resource("jobs"),
|
||||
batchv1.Resource("cronjobs"),
|
||||
}
|
||||
extractor := &DefaultPodSpecExtractor{}
|
||||
for _, gr := range podLikeResources {
|
||||
assert.True(t, extractor.HasPodSpec(gr), gr.String())
|
||||
}
|
||||
|
||||
nonPodResources := []schema.GroupResource{
|
||||
corev1.Resource("services"),
|
||||
admissionv1.Resource("admissionreviews"),
|
||||
appsv1.Resource("foobars"),
|
||||
}
|
||||
for _, gr := range nonPodResources {
|
||||
assert.False(t, extractor.HasPodSpec(gr), gr.String())
|
||||
}
|
||||
}
|
||||
|
||||
type testEvaluator struct {
|
||||
lv api.LevelVersion
|
||||
}
|
||||
|
||||
func (t *testEvaluator) EvaluatePod(lv api.LevelVersion, meta *metav1.ObjectMeta, spec *corev1.PodSpec) []policy.CheckResult {
|
||||
t.lv = lv
|
||||
if meta.Annotations["error"] != "" {
|
||||
return []policy.CheckResult{{Allowed: false, ForbiddenReason: meta.Annotations["error"]}}
|
||||
} else {
|
||||
return []policy.CheckResult{{Allowed: true}}
|
||||
}
|
||||
}
|
||||
|
||||
type testPodLister struct {
|
||||
called bool
|
||||
pods []*corev1.Pod
|
||||
}
|
||||
|
||||
func (t *testPodLister) ListPods(ctx context.Context, namespace string) ([]*corev1.Pod, error) {
|
||||
t.called = true
|
||||
return t.pods, nil
|
||||
}
|
||||
|
||||
func TestValidateNamespace(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
exemptNamespaces []string
|
||||
exemptRuntimeClasses []string
|
||||
// override default policy
|
||||
defaultPolicy *api.Policy
|
||||
// request subresource
|
||||
subresource string
|
||||
// labels for the new namespace
|
||||
newLabels map[string]string
|
||||
// labels for the old namespace (only used if update=true)
|
||||
oldLabels map[string]string
|
||||
// list of pods to return
|
||||
pods []*corev1.Pod
|
||||
|
||||
expectAllowed bool
|
||||
expectError string
|
||||
expectListPods bool
|
||||
expectEvaluate api.LevelVersion
|
||||
expectWarnings []string
|
||||
}{
|
||||
// creation tests, just validate labels
|
||||
{
|
||||
name: "create privileged",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelPrivileged), api.EnforceVersionLabel: "v1.0"},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "create baseline",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline)},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "create restricted",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline)},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "create malformed level",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: "unknown"},
|
||||
expectAllowed: false,
|
||||
expectError: `must be one of privileged, baseline, restricted`,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "create malformed version",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelPrivileged), api.EnforceVersionLabel: "unknown"},
|
||||
expectAllowed: false,
|
||||
expectError: `must be "latest" or "v1.x"`,
|
||||
expectListPods: false,
|
||||
},
|
||||
|
||||
// update tests that don't tighten effective policy, no pod list/evaluate
|
||||
{
|
||||
name: "update no-op",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "update no-op malformed level",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: "unknown"},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: "unknown"},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "update no-op malformed version",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "unknown"},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "unknown"},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "update relax level identical version",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "v1.0"},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "update relax level explicit latest",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "latest"},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "latest"},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "update relax level implicit latest",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline)},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted)},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "update to explicit privileged",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelPrivileged)},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "update to implicit privileged",
|
||||
newLabels: map[string]string{},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "update exempt to restricted",
|
||||
exemptNamespaces: []string{"test"},
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
|
||||
oldLabels: map[string]string{},
|
||||
expectAllowed: true,
|
||||
expectListPods: false,
|
||||
},
|
||||
|
||||
// update tests that introduce labels errors
|
||||
{
|
||||
name: "update malformed level",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: "unknown"},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
|
||||
expectAllowed: false,
|
||||
expectError: `must be one of privileged, baseline, restricted`,
|
||||
expectListPods: false,
|
||||
},
|
||||
{
|
||||
name: "update malformed version",
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelPrivileged), api.EnforceVersionLabel: "unknown"},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
|
||||
expectAllowed: false,
|
||||
expectError: `must be "latest" or "v1.x"`,
|
||||
expectListPods: false,
|
||||
},
|
||||
|
||||
// update tests that tighten effective policy
|
||||
{
|
||||
name: "update to implicit restricted",
|
||||
newLabels: map[string]string{},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "v1.0"},
|
||||
defaultPolicy: &api.Policy{Enforce: api.LevelVersion{Level: api.LevelRestricted, Version: api.LatestVersion()}},
|
||||
expectAllowed: true,
|
||||
expectListPods: true,
|
||||
expectEvaluate: api.LevelVersion{Level: api.LevelRestricted, Version: api.LatestVersion()},
|
||||
expectWarnings: []string{"noruntimeclasspod: message", "runtimeclass1pod: message", "runtimeclass2pod: message"},
|
||||
},
|
||||
{
|
||||
name: "update with runtimeclass exempt pods",
|
||||
exemptRuntimeClasses: []string{"runtimeclass1"},
|
||||
newLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted)},
|
||||
oldLabels: map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline)},
|
||||
expectAllowed: true,
|
||||
expectListPods: true,
|
||||
expectEvaluate: api.LevelVersion{Level: api.LevelRestricted, Version: api.LatestVersion()},
|
||||
expectWarnings: []string{"noruntimeclasspod: message", "runtimeclass2pod: message"},
|
||||
},
|
||||
|
||||
// TODO: test for aggregating pods with identical warnings
|
||||
// TODO: test for bounding evalution time with a warning
|
||||
// TODO: test for bounding pod count with a warning
|
||||
// TODO: test for prioritizing evaluating pods from unique controllers
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
newObject := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Labels: tc.newLabels,
|
||||
},
|
||||
}
|
||||
var operation = admission.Create
|
||||
var oldObject runtime.Object
|
||||
if tc.oldLabels != nil {
|
||||
operation = admission.Update
|
||||
oldObject = &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Labels: tc.oldLabels,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
attrs := admission.NewAttributesRecord(
|
||||
newObject,
|
||||
oldObject,
|
||||
schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"},
|
||||
newObject.Name,
|
||||
newObject.Name,
|
||||
schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"},
|
||||
tc.subresource,
|
||||
operation,
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
|
||||
defaultPolicy := api.Policy{
|
||||
Enforce: api.LevelVersion{
|
||||
Level: api.LevelPrivileged,
|
||||
Version: api.LatestVersion(),
|
||||
},
|
||||
}
|
||||
if tc.defaultPolicy != nil {
|
||||
defaultPolicy = *tc.defaultPolicy
|
||||
}
|
||||
|
||||
pods := tc.pods
|
||||
if pods == nil {
|
||||
pods = []*corev1.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "noruntimeclasspod", Annotations: map[string]string{"error": "message"}},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "runtimeclass1pod", Annotations: map[string]string{"error": "message"}},
|
||||
Spec: corev1.PodSpec{RuntimeClassName: pointer.String("runtimeclass1")},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "runtimeclass2pod", Annotations: map[string]string{"error": "message"}},
|
||||
Spec: corev1.PodSpec{RuntimeClassName: pointer.String("runtimeclass2")},
|
||||
},
|
||||
}
|
||||
}
|
||||
podLister := &testPodLister{pods: pods}
|
||||
evaluator := &testEvaluator{}
|
||||
a := &Admission{
|
||||
PodLister: podLister,
|
||||
Evaluator: evaluator,
|
||||
Configuration: &admissionapi.PodSecurityConfiguration{
|
||||
Exemptions: admissionapi.PodSecurityExemptions{
|
||||
Namespaces: tc.exemptNamespaces,
|
||||
RuntimeClasses: tc.exemptRuntimeClasses,
|
||||
},
|
||||
},
|
||||
defaultPolicy: defaultPolicy,
|
||||
}
|
||||
result := a.ValidateNamespace(context.TODO(), attrs)
|
||||
if result.Allowed != tc.expectAllowed {
|
||||
t.Errorf("expected allowed=%v, got %v", tc.expectAllowed, result.Allowed)
|
||||
}
|
||||
|
||||
resultError := ""
|
||||
if result.Result != nil {
|
||||
resultError = result.Result.Message
|
||||
}
|
||||
if (len(resultError) > 0) != (len(tc.expectError) > 0) {
|
||||
t.Errorf("expected error=%v, got %v", tc.expectError, resultError)
|
||||
}
|
||||
if len(tc.expectError) > 0 && !strings.Contains(resultError, tc.expectError) {
|
||||
t.Errorf("expected error containing '%s', got %s", tc.expectError, resultError)
|
||||
}
|
||||
if podLister.called != tc.expectListPods {
|
||||
t.Errorf("expected getPods=%v, got %v", tc.expectListPods, podLister.called)
|
||||
}
|
||||
if evaluator.lv != tc.expectEvaluate {
|
||||
t.Errorf("expected to evaluate %v, got %v", tc.expectEvaluate, evaluator.lv)
|
||||
}
|
||||
if !reflect.DeepEqual(result.Warnings, tc.expectWarnings) {
|
||||
t.Errorf("expected warnings:\n%v\ngot\n%v", tc.expectWarnings, result.Warnings)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
18
staging/src/k8s.io/pod-security-admission/admission/doc.go
Normal file
18
staging/src/k8s.io/pod-security-admission/admission/doc.go
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package admission contains PodSecurity admission logic
|
||||
package admission
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admission
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||
)
|
||||
|
||||
func NamespaceGetterFromClient(client kubernetes.Interface) NamespaceGetter {
|
||||
return &namespaceGetter{client: client}
|
||||
}
|
||||
|
||||
func NamespaceGetterFromListerAndClient(lister corev1listers.NamespaceLister, client kubernetes.Interface) NamespaceGetter {
|
||||
return &namespaceGetter{lister: lister, client: client}
|
||||
}
|
||||
|
||||
type namespaceGetter struct {
|
||||
lister corev1listers.NamespaceLister
|
||||
client kubernetes.Interface
|
||||
}
|
||||
|
||||
func (n *namespaceGetter) GetNamespace(ctx context.Context, name string) (namespace *corev1.Namespace, err error) {
|
||||
if n.lister != nil {
|
||||
namespace, err := n.lister.Get(name)
|
||||
if err == nil || !apierrors.IsNotFound(err) {
|
||||
return namespace, err
|
||||
}
|
||||
}
|
||||
return n.client.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{})
|
||||
}
|
61
staging/src/k8s.io/pod-security-admission/admission/pods.go
Normal file
61
staging/src/k8s.io/pod-security-admission/admission/pods.go
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admission
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||
)
|
||||
|
||||
// PodListerFromClient returns a PodLister that does live lists using the provided client.
|
||||
func PodListerFromClient(client kubernetes.Interface) PodLister {
|
||||
return &clientPodLister{client}
|
||||
}
|
||||
|
||||
type clientPodLister struct {
|
||||
client kubernetes.Interface
|
||||
}
|
||||
|
||||
func (p *clientPodLister) ListPods(ctx context.Context, namespace string) ([]*corev1.Pod, error) {
|
||||
list, err := p.client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pods := make([]*corev1.Pod, len(list.Items))
|
||||
for i := range list.Items {
|
||||
pods[i] = &list.Items[i]
|
||||
}
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
// PodListerFromInformer returns a PodLister that does cached lists using the provided lister.
|
||||
func PodListerFromInformer(lister corev1listers.PodLister) PodLister {
|
||||
return &informerPodLister{lister}
|
||||
}
|
||||
|
||||
type informerPodLister struct {
|
||||
lister corev1listers.PodLister
|
||||
}
|
||||
|
||||
func (p *informerPodLister) ListPods(ctx context.Context, namespace string) ([]*corev1.Pod, error) {
|
||||
return p.lister.Pods(namespace).List(labels.Everything())
|
||||
}
|
Loading…
Reference in New Issue
Block a user