Merge pull request #103099 from liggitt/podsecurity

PodSecurity admission
This commit is contained in:
Kubernetes Prow Robot 2021-06-28 20:46:52 -07:00 committed by GitHub
commit d92f6c424d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2237 changed files with 46963 additions and 1 deletions

1
go.mod
View File

@ -129,6 +129,7 @@ require (
k8s.io/legacy-cloud-providers v0.0.0
k8s.io/metrics v0.0.0
k8s.io/mount-utils v0.0.0
k8s.io/pod-security-admission v0.0.0
k8s.io/sample-apiserver v0.0.0
k8s.io/system-validators v1.4.0
k8s.io/utils v0.0.0-20210521133846-da695404a2bc

View File

@ -721,6 +721,12 @@ const (
//
// Enables the use of `RuntimeDefault` as the default seccomp profile for all workloads.
SeccompDefault featuregate.Feature = "SeccompDefault"
// owner: @liggitt, @tallclair, sig-auth
// alpha: v1.22
//
// Enables the PodSecurity admission plugin
PodSecurity featuregate.Feature = "PodSecurity"
)
func init() {
@ -829,6 +835,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
StatefulSetMinReadySeconds: {Default: false, PreRelease: featuregate.Alpha},
ExpandedDNSConfig: {Default: false, PreRelease: featuregate.Alpha},
SeccompDefault: {Default: false, PreRelease: featuregate.Alpha},
PodSecurity: {Default: false, PreRelease: featuregate.Alpha},
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:

View File

@ -44,6 +44,7 @@ import (
"k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction"
podpriority "k8s.io/kubernetes/plugin/pkg/admission/priority"
"k8s.io/kubernetes/plugin/pkg/admission/runtimeclass"
"k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity"
"k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy"
"k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny"
"k8s.io/kubernetes/plugin/pkg/admission/serviceaccount"
@ -75,6 +76,7 @@ var AllOrderedPlugins = []string{
alwayspullimages.PluginName, // AlwaysPullImages
imagepolicy.PluginName, // ImagePolicyWebhook
podsecuritypolicy.PluginName, // PodSecurityPolicy
podsecurity.PluginName, // PodSecurity
podnodeselector.PluginName, // PodNodeSelector
podpriority.PluginName, // Priority
defaulttolerationseconds.PluginName, // DefaultTolerationSeconds
@ -126,6 +128,7 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
podtolerationrestriction.Register(plugins)
runtimeclass.Register(plugins)
resourcequota.Register(plugins)
podsecurity.Register(plugins) // before PodSecurityPolicy so audit/warn get exercised even if PodSecurityPolicy denies
podsecuritypolicy.Register(plugins)
podpriority.Register(plugins)
scdeny.Register(plugins)
@ -158,6 +161,7 @@ func DefaultOffAdmissionPlugins() sets.String {
certsigning.PluginName, // CertificateSigning
certsubjectrestriction.PluginName, // CertificateSubjectRestriction
defaultingressclass.PluginName, // DefaultIngressClass
podsecurity.PluginName, // PodSecurity
)
return sets.NewString(AllOrderedPlugins...).Difference(defaultOnPlugins)

View File

@ -0,0 +1,252 @@
/*
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 podsecurity
import (
"context"
"errors"
"fmt"
"io"
"sync"
// install conversions for types we need to convert
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/core/install"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission"
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/warning"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/component-base/featuregate"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
podsecurityadmission "k8s.io/pod-security-admission/admission"
podsecurityconfigloader "k8s.io/pod-security-admission/admission/api/load"
podsecurityadmissionapi "k8s.io/pod-security-admission/api"
"k8s.io/pod-security-admission/policy"
)
// PluginName is a string with the name of the plugin
const PluginName = "PodSecurity"
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(reader io.Reader) (admission.Interface, error) {
return newPlugin(reader)
})
}
// Plugin holds state for and implements the admission plugin.
type Plugin struct {
*admission.Handler
enabled bool
inspectedFeatureGates bool
client kubernetes.Interface
namespaceLister corev1listers.NamespaceLister
podLister corev1listers.PodLister
delegate *podsecurityadmission.Admission
}
var _ admission.ValidationInterface = &Plugin{}
var _ genericadmissioninit.WantsExternalKubeInformerFactory = &Plugin{}
var _ genericadmissioninit.WantsExternalKubeClientSet = &Plugin{}
// newPlugin creates a new admission plugin.
func newPlugin(reader io.Reader) (*Plugin, error) {
config, err := podsecurityconfigloader.LoadFromReader(reader)
if err != nil {
return nil, err
}
evaluator, err := policy.NewEvaluator(policy.DefaultChecks())
if err != nil {
return nil, fmt.Errorf("could not create PodSecurityRegistry: %w", err)
}
return &Plugin{
Handler: admission.NewHandler(admission.Create, admission.Update),
delegate: &podsecurityadmission.Admission{
Configuration: config,
Evaluator: evaluator,
Metrics: nil, // TODO: wire to default prometheus metrics
PodSpecExtractor: podsecurityadmission.DefaultPodSpecExtractor{},
},
}, nil
}
// SetExternalKubeInformerFactory registers an informer
func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
namespaceInformer := f.Core().V1().Namespaces()
p.namespaceLister = namespaceInformer.Lister()
p.podLister = f.Core().V1().Pods().Lister()
p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
p.updateDelegate()
}
// SetExternalKubeClientSet sets the plugin's client
func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
p.client = client
p.updateDelegate()
}
func (p *Plugin) updateDelegate() {
// return early if we don't have what we need to set up the admission delegate
if p.namespaceLister == nil {
return
}
if p.podLister == nil {
return
}
if p.client == nil {
return
}
p.delegate.PodLister = podsecurityadmission.PodListerFromInformer(p.podLister)
p.delegate.NamespaceGetter = podsecurityadmission.NamespaceGetterFromListerAndClient(p.namespaceLister, p.client)
}
func (c *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
c.enabled = featureGates.Enabled(features.PodSecurity)
c.inspectedFeatureGates = true
}
// ValidateInitialization ensures all required options are set
func (p *Plugin) ValidateInitialization() error {
if !p.inspectedFeatureGates {
return fmt.Errorf("%s did not see feature gates", PluginName)
}
if err := p.delegate.CompleteConfiguration(); err != nil {
return fmt.Errorf("%s configuration error: %w", PluginName, err)
}
if err := p.delegate.ValidateConfiguration(); err != nil {
return fmt.Errorf("%s invalid: %w", PluginName, err)
}
return nil
}
var (
applicableResources = map[schema.GroupResource]bool{
corev1.Resource("pods"): true,
corev1.Resource("namespaces"): true,
}
)
func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
if !p.enabled {
return nil
}
gr := a.GetResource().GroupResource()
if !applicableResources[gr] && !p.delegate.PodSpecExtractor.HasPodSpec(gr) {
return nil
}
a = &lazyConvertingAttributes{Attributes: a}
result := p.delegate.Validate(ctx, a)
for _, w := range result.Warnings {
warning.AddWarning(ctx, "", w)
}
for k, v := range result.AuditAnnotations {
audit.AddAuditAnnotation(ctx, podsecurityadmissionapi.AuditAnnotationPrefix+k, v)
}
if !result.Allowed {
if result.Result != nil && len(result.Result.Message) > 0 {
// TODO: use code/reason/etc from status
return admission.NewForbidden(a, errors.New(result.Result.Message))
}
return admission.NewForbidden(a, errors.New("Not allowed by PodSecurity"))
}
return nil
}
type lazyConvertingAttributes struct {
admission.Attributes
convertObjectOnce sync.Once
convertedObject runtime.Object
convertOldObjectOnce sync.Once
convertedOldObject runtime.Object
}
func (l *lazyConvertingAttributes) GetObject() runtime.Object {
l.convertObjectOnce.Do(func() {
obj, err := convert(l.Attributes.GetObject())
if err != nil {
utilruntime.HandleError(err)
}
l.convertedObject = obj
})
return l.convertedObject
}
func (l *lazyConvertingAttributes) GetOldObject() runtime.Object {
l.convertOldObjectOnce.Do(func() {
obj, err := convert(l.Attributes.GetOldObject())
if err != nil {
utilruntime.HandleError(err)
}
l.convertedOldObject = obj
})
return l.convertedOldObject
}
func convert(in runtime.Object) (runtime.Object, error) {
var out runtime.Object
switch in.(type) {
case *core.Namespace:
out = &corev1.Namespace{}
case *core.Pod:
out = &corev1.Pod{}
case *core.ReplicationController:
out = &corev1.ReplicationController{}
case *core.PodTemplate:
out = &corev1.PodTemplate{}
case *apps.ReplicaSet:
out = &appsv1.ReplicaSet{}
case *apps.Deployment:
out = &appsv1.Deployment{}
case *apps.StatefulSet:
out = &appsv1.StatefulSet{}
case *apps.DaemonSet:
out = &appsv1.DaemonSet{}
case *batch.Job:
out = &batchv1.Job{}
case *batch.CronJob:
out = &batchv1.CronJob{}
default:
return in, fmt.Errorf("unexpected type %T", in)
}
if err := legacyscheme.Scheme.Convert(in, out, nil); err != nil {
return in, err
}
return out, nil
}

View File

@ -0,0 +1,60 @@
/*
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 podsecurity
import (
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/core"
podsecurityadmission "k8s.io/pod-security-admission/admission"
)
func TestConvert(t *testing.T) {
extractor := podsecurityadmission.DefaultPodSpecExtractor{}
internalTypes := map[schema.GroupResource]runtime.Object{
core.Resource("pods"): &core.Pod{},
core.Resource("replicationcontrollers"): &core.ReplicationController{},
core.Resource("podtemplates"): &core.PodTemplate{},
apps.Resource("replicasets"): &apps.ReplicaSet{},
apps.Resource("deployments"): &apps.Deployment{},
apps.Resource("statefulsets"): &apps.StatefulSet{},
apps.Resource("daemonsets"): &apps.DaemonSet{},
batch.Resource("jobs"): &batch.Job{},
batch.Resource("cronjobs"): &batch.CronJob{},
}
for _, r := range extractor.PodSpecResources() {
internalType, ok := internalTypes[r]
if !ok {
t.Errorf("no internal type registered for %s", r.String())
continue
}
externalType, err := convert(internalType)
if err != nil {
t.Errorf("error converting %T: %v", internalType, err)
continue
}
_, _, err = extractor.ExtractPodSpec(externalType)
if err != nil {
t.Errorf("error extracting from %T: %v", externalType, err)
continue
}
}
}

View File

@ -267,6 +267,7 @@
allowedImports:
- k8s.io/api
- k8s.io/apimachinery
- k8s.io/apiserver/pkg/admission
- k8s.io/client-go
- k8s.io/klog
- k8s.io/pod-security-admission

View File

@ -1469,3 +1469,14 @@ rules:
branch: master
dir: staging/src/k8s.io/pod-security-admission
name: master
dependencies:
- repository: api
branch: master
- repository: apimachinery
branch: master
- repository: apiserver
branch: master
- repository: client-go
branch: master
- repository: component-base
branch: master

View 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
}

View File

@ -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)
}
})
}
}

View File

@ -0,0 +1,20 @@
/*
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.
*/
// +k8s:deepcopy-gen=package
// Package api contains PodSecurity admission configuration file types
package api

View File

@ -0,0 +1,87 @@
/*
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 api
import (
"fmt"
"k8s.io/apimachinery/pkg/util/errors"
policyapi "k8s.io/pod-security-admission/api"
)
var requiredErr = fmt.Errorf("required")
// TODO: deduplicate against PolicyToEvaluate
func ToPolicy(defaults PodSecurityDefaults) (policyapi.Policy, error) {
var (
err error
errs []error
p policyapi.Policy
)
if len(defaults.Enforce) == 0 {
errs = appendErr(errs, requiredErr, "Enforce.Level")
} else {
p.Enforce.Level, err = policyapi.ParseLevel(defaults.Enforce)
errs = appendErr(errs, err, "Enforce.Level")
}
if len(defaults.EnforceVersion) == 0 {
errs = appendErr(errs, requiredErr, "Enforce.Version")
} else {
p.Enforce.Version, err = policyapi.ParseVersion(defaults.EnforceVersion)
errs = appendErr(errs, err, "Enforce.Version")
}
if len(defaults.Audit) == 0 {
errs = appendErr(errs, requiredErr, "Audit.Level")
} else {
p.Audit.Level, err = policyapi.ParseLevel(defaults.Audit)
errs = appendErr(errs, err, "Audit.Level")
}
if len(defaults.AuditVersion) == 0 {
errs = appendErr(errs, requiredErr, "Audit.Version")
} else {
p.Audit.Version, err = policyapi.ParseVersion(defaults.AuditVersion)
errs = appendErr(errs, err, "Audit.Version")
}
if len(defaults.Warn) == 0 {
errs = appendErr(errs, requiredErr, "Warn.Level")
} else {
p.Warn.Level, err = policyapi.ParseLevel(defaults.Warn)
errs = appendErr(errs, err, "Warn.Level")
}
if len(defaults.WarnVersion) == 0 {
errs = appendErr(errs, requiredErr, "Warn.Version")
} else {
p.Warn.Version, err = policyapi.ParseVersion(defaults.WarnVersion)
errs = appendErr(errs, err, "Warn.Version")
}
return p, errors.NewAggregate(errs)
}
// appendErr is a helper function to collect field-specific errors.
func appendErr(errs []error, err error, field string) []error {
if err != nil {
return append(errs, fmt.Errorf("%s: %s", field, err.Error()))
}
return errs
}

View File

@ -0,0 +1,78 @@
/*
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 load
import (
"fmt"
"io"
"os"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/pod-security-admission/admission/api"
"k8s.io/pod-security-admission/admission/api/scheme"
apiv1alpha1 "k8s.io/pod-security-admission/admission/api/v1alpha1"
)
func LoadFromFile(file string) (*api.PodSecurityConfiguration, error) {
if len(file) == 0 {
// no file specified, use default config
return LoadFromData(nil)
}
// read from file
data, err := os.ReadFile(file)
if err != nil {
return nil, err
}
return LoadFromData(data)
}
func LoadFromReader(reader io.Reader) (*api.PodSecurityConfiguration, error) {
if reader == nil {
// no reader specified, use default config
return LoadFromData(nil)
}
data, err := io.ReadAll(reader)
if err != nil {
return nil, err
}
return LoadFromData(data)
}
func LoadFromData(data []byte) (*api.PodSecurityConfiguration, error) {
if len(data) == 0 {
// no config provided, return default
externalConfig := &apiv1alpha1.PodSecurityConfiguration{}
scheme.Scheme.Default(externalConfig)
internalConfig := &api.PodSecurityConfiguration{}
if err := scheme.Scheme.Convert(externalConfig, internalConfig, nil); err != nil {
return nil, err
}
return internalConfig, nil
}
decodedObj, err := runtime.Decode(scheme.Codecs.UniversalDecoder(), data)
if err != nil {
return nil, err
}
configuration, ok := decodedObj.(*api.PodSecurityConfiguration)
if !ok {
return nil, fmt.Errorf("expected PodSecurityConfiguration, got %T", decodedObj)
}
return configuration, nil
}

View File

@ -0,0 +1,305 @@
/*
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 load
import (
"bytes"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"k8s.io/pod-security-admission/admission/api"
)
var defaultConfig = &api.PodSecurityConfiguration{
Defaults: api.PodSecurityDefaults{
Enforce: "privileged", EnforceVersion: "latest",
Warn: "privileged", WarnVersion: "latest",
Audit: "privileged", AuditVersion: "latest",
},
}
func writeTempFile(t *testing.T, content string) string {
t.Helper()
file, err := ioutil.TempFile("", "podsecurityconfig")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
os.Remove(file.Name())
})
if err := ioutil.WriteFile(file.Name(), []byte(content), 0600); err != nil {
t.Fatal(err)
}
return file.Name()
}
func TestLoadFromFile(t *testing.T) {
// no file
{
config, err := LoadFromFile("")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !reflect.DeepEqual(config, defaultConfig) {
t.Fatalf("unexpected config:\n%s", cmp.Diff(defaultConfig, config))
}
}
// empty file
{
config, err := LoadFromFile(writeTempFile(t, ``))
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !reflect.DeepEqual(config, defaultConfig) {
t.Fatalf("unexpected config:\n%s", cmp.Diff(defaultConfig, config))
}
}
// valid file
{
input := `{
"apiVersion":"pod-security.admission.config.k8s.io/v1alpha1",
"kind":"PodSecurityConfiguration",
"defaults":{"enforce":"baseline"}}`
expect := &api.PodSecurityConfiguration{
Defaults: api.PodSecurityDefaults{
Enforce: "baseline", EnforceVersion: "latest",
Warn: "privileged", WarnVersion: "latest",
Audit: "privileged", AuditVersion: "latest",
},
}
config, err := LoadFromFile(writeTempFile(t, input))
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !reflect.DeepEqual(config, expect) {
t.Fatalf("unexpected config:\n%s", cmp.Diff(expect, config))
}
}
// missing file
{
_, err := LoadFromFile(`bogus-missing-pod-security-policy-config-file`)
if err == nil {
t.Fatalf("expected err, got none")
}
if !strings.Contains(err.Error(), "bogus-missing-pod-security-policy-config-file") {
t.Fatalf("expected missing file error, got %v", err)
}
}
// invalid content file
{
input := `{
"apiVersion":"pod-security.admission.config.k8s.io/v99",
"kind":"PodSecurityConfiguration",
"defaults":{"enforce":"baseline"}}`
_, err := LoadFromFile(writeTempFile(t, input))
if err == nil {
t.Fatalf("expected err, got none")
}
if !strings.Contains(err.Error(), "pod-security.admission.config.k8s.io/v99") {
t.Fatalf("expected apiVersion error, got %v", err)
}
}
}
func TestLoadFromReader(t *testing.T) {
// no reader
{
config, err := LoadFromReader(nil)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !reflect.DeepEqual(config, defaultConfig) {
t.Fatalf("unexpected config:\n%s", cmp.Diff(defaultConfig, config))
}
}
// empty reader
{
config, err := LoadFromReader(&bytes.Buffer{})
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !reflect.DeepEqual(config, defaultConfig) {
t.Fatalf("unexpected config:\n%s", cmp.Diff(defaultConfig, config))
}
}
// valid reader
{
input := `{
"apiVersion":"pod-security.admission.config.k8s.io/v1alpha1",
"kind":"PodSecurityConfiguration",
"defaults":{"enforce":"baseline"}}`
expect := &api.PodSecurityConfiguration{
Defaults: api.PodSecurityDefaults{
Enforce: "baseline", EnforceVersion: "latest",
Warn: "privileged", WarnVersion: "latest",
Audit: "privileged", AuditVersion: "latest",
},
}
config, err := LoadFromReader(bytes.NewBufferString(input))
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !reflect.DeepEqual(config, expect) {
t.Fatalf("unexpected config:\n%s", cmp.Diff(expect, config))
}
}
// invalid reader
{
input := `{
"apiVersion":"pod-security.admission.config.k8s.io/v99",
"kind":"PodSecurityConfiguration",
"defaults":{"enforce":"baseline"}}`
_, err := LoadFromReader(bytes.NewBufferString(input))
if err == nil {
t.Fatalf("expected err, got none")
}
if !strings.Contains(err.Error(), "pod-security.admission.config.k8s.io/v99") {
t.Fatalf("expected apiVersion error, got %v", err)
}
}
}
func TestLoadFromData(t *testing.T) {
testcases := []struct {
name string
data []byte
expectErr string
expectConfig *api.PodSecurityConfiguration
}{
{
name: "nil",
data: nil,
expectConfig: defaultConfig,
},
{
name: "nil",
data: []byte{},
expectConfig: defaultConfig,
},
{
name: "v1alpha1 - json",
data: []byte(`{
"apiVersion":"pod-security.admission.config.k8s.io/v1alpha1",
"kind":"PodSecurityConfiguration",
"defaults":{"enforce":"baseline"}}`),
expectConfig: &api.PodSecurityConfiguration{
Defaults: api.PodSecurityDefaults{
Enforce: "baseline", EnforceVersion: "latest",
Warn: "privileged", WarnVersion: "latest",
Audit: "privileged", AuditVersion: "latest",
},
},
},
{
name: "v1alpha1 - yaml",
data: []byte(`
apiVersion: pod-security.admission.config.k8s.io/v1alpha1
kind: PodSecurityConfiguration
defaults:
enforce: baseline
enforce-version: v1.7
exemptions:
usernames: ["alice","bob"]
namespaces: ["kube-system"]
runtimeClasses: ["special"]
`),
expectConfig: &api.PodSecurityConfiguration{
Defaults: api.PodSecurityDefaults{
Enforce: "baseline", EnforceVersion: "v1.7",
Warn: "privileged", WarnVersion: "latest",
Audit: "privileged", AuditVersion: "latest",
},
Exemptions: api.PodSecurityExemptions{
Usernames: []string{"alice", "bob"},
Namespaces: []string{"kube-system"},
RuntimeClasses: []string{"special"},
},
},
},
{
name: "missing apiVersion",
data: []byte(`{"kind":"PodSecurityConfiguration"}`),
expectErr: `'apiVersion' is missing`,
},
{
name: "missing kind",
data: []byte(`{"apiVersion":"pod-security.admission.config.k8s.io/v1alpha1"}`),
expectErr: `'Kind' is missing`,
},
{
name: "unknown group",
data: []byte(`{"apiVersion":"apps/v1alpha1","kind":"PodSecurityConfiguration"}`),
expectErr: `apps/v1alpha1`,
},
{
name: "unknown version",
data: []byte(`{"apiVersion":"pod-security.admission.config.k8s.io/v99","kind":"PodSecurityConfiguration"}`),
expectErr: `pod-security.admission.config.k8s.io/v99`,
},
{
name: "unknown kind",
data: []byte(`{"apiVersion":"pod-security.admission.config.k8s.io/v1alpha1","kind":"SomeConfiguration"}`),
expectErr: `SomeConfiguration`,
},
{
name: "unknown field",
data: []byte(`{
"apiVersion":"pod-security.admission.config.k8s.io/v1alpha1",
"kind":"PodSecurityConfiguration",
"deflaults":{"enforce":"baseline"}}`),
expectErr: `unknown field: deflaults`,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
config, err := LoadFromData(tc.data)
if err != nil {
if len(tc.expectErr) == 0 {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(err.Error(), tc.expectErr) {
t.Fatalf("unexpected error: %v", err)
}
return
}
if len(tc.expectErr) > 0 {
t.Fatalf("expected err, got none")
}
if !reflect.DeepEqual(config, tc.expectConfig) {
t.Fatalf("unexpected config:\n%s", cmp.Diff(tc.expectConfig, config))
}
})
}
}

View File

@ -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 api
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "pod-security.admission.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
var (
// SchemeBuilder is a pointer used to call AddToScheme
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
// AddToScheme is used to register the types to API encoding/decoding machinery
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes)
}
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&PodSecurityConfiguration{},
)
return nil
}

View File

@ -0,0 +1,17 @@
/*
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 api

View File

@ -0,0 +1,44 @@
/*
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 scheme
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
podsecurityapi "k8s.io/pod-security-admission/admission/api"
podsecurityv1alpha1 "k8s.io/pod-security-admission/admission/api/v1alpha1"
)
var (
// Scheme is the runtime.Scheme to which all podsecurity api types are registered.
Scheme = runtime.NewScheme()
// Codecs provides access to encoding and decoding for the scheme.
Codecs = serializer.NewCodecFactory(Scheme, serializer.EnableStrict)
)
func init() {
AddToScheme(Scheme)
}
// AddToScheme builds the podsecurity scheme using all known versions of the podsecurity api.
func AddToScheme(scheme *runtime.Scheme) {
utilruntime.Must(podsecurityapi.AddToScheme(scheme))
utilruntime.Must(podsecurityv1alpha1.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(podsecurityv1alpha1.SchemeGroupVersion))
}

View File

@ -0,0 +1,17 @@
/*
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 scheme

View File

@ -0,0 +1,44 @@
/*
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 api
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type PodSecurityConfiguration struct {
metav1.TypeMeta
Defaults PodSecurityDefaults
Exemptions PodSecurityExemptions
}
type PodSecurityDefaults struct {
Enforce string
EnforceVersion string
Audit string
AuditVersion string
Warn string
WarnVersion string
}
type PodSecurityExemptions struct {
Usernames []string
Namespaces []string
RuntimeClasses []string
}

View File

@ -0,0 +1,48 @@
/*
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 v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/pod-security-admission/api"
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}
func SetDefaults_PodSecurityDefaults(obj *PodSecurityDefaults) {
if len(obj.Enforce) == 0 {
obj.Enforce = string(api.LevelPrivileged)
}
if len(obj.Warn) == 0 {
obj.Warn = string(api.LevelPrivileged)
}
if len(obj.Audit) == 0 {
obj.Audit = string(api.LevelPrivileged)
}
if len(obj.EnforceVersion) == 0 {
obj.EnforceVersion = string(api.VersionLatest)
}
if len(obj.WarnVersion) == 0 {
obj.WarnVersion = string(api.VersionLatest)
}
if len(obj.AuditVersion) == 0 {
obj.AuditVersion = string(api.VersionLatest)
}
}

View File

@ -0,0 +1,17 @@
/*
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 v1alpha1

View File

@ -0,0 +1,23 @@
/*
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.
*/
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/pod-security-admission/admission/api
// +k8s:defaulter-gen=TypeMeta
// +groupName=pod-security.admission.config.k8s.io
// Package v1alpha1 contains PodSecurity admission configuration file types
package v1alpha1

View File

@ -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 v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "pod-security.admission.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
var (
// SchemeBuilder is a pointer used to call AddToScheme
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
// AddToScheme is used to register the types to API encoding/decoding machinery
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
}
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&PodSecurityConfiguration{},
)
return nil
}

View File

@ -0,0 +1,44 @@
/*
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 v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type PodSecurityConfiguration struct {
metav1.TypeMeta
Defaults PodSecurityDefaults `json:"defaults"`
Exemptions PodSecurityExemptions `json:"exemptions"`
}
type PodSecurityDefaults struct {
Enforce string `json:"enforce,omitempty"`
EnforceVersion string `json:"enforce-version,omitempty"`
Audit string `json:"audit,omitempty"`
AuditVersion string `json:"audit-version,omitempty"`
Warn string `json:"warn,omitempty"`
WarnVersion string `json:"warn-version,omitempty"`
}
type PodSecurityExemptions struct {
Usernames []string `json:"usernames,omitempty"`
Namespaces []string `json:"namespaces,omitempty"`
RuntimeClasses []string `json:"runtimeClasses,omitempty"`
}

View File

@ -0,0 +1,153 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
unsafe "unsafe"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
api "k8s.io/pod-security-admission/admission/api"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*PodSecurityConfiguration)(nil), (*api.PodSecurityConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_PodSecurityConfiguration_To_api_PodSecurityConfiguration(a.(*PodSecurityConfiguration), b.(*api.PodSecurityConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*api.PodSecurityConfiguration)(nil), (*PodSecurityConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_api_PodSecurityConfiguration_To_v1alpha1_PodSecurityConfiguration(a.(*api.PodSecurityConfiguration), b.(*PodSecurityConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*PodSecurityDefaults)(nil), (*api.PodSecurityDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_PodSecurityDefaults_To_api_PodSecurityDefaults(a.(*PodSecurityDefaults), b.(*api.PodSecurityDefaults), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*api.PodSecurityDefaults)(nil), (*PodSecurityDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_api_PodSecurityDefaults_To_v1alpha1_PodSecurityDefaults(a.(*api.PodSecurityDefaults), b.(*PodSecurityDefaults), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*PodSecurityExemptions)(nil), (*api.PodSecurityExemptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_PodSecurityExemptions_To_api_PodSecurityExemptions(a.(*PodSecurityExemptions), b.(*api.PodSecurityExemptions), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*api.PodSecurityExemptions)(nil), (*PodSecurityExemptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_api_PodSecurityExemptions_To_v1alpha1_PodSecurityExemptions(a.(*api.PodSecurityExemptions), b.(*PodSecurityExemptions), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_PodSecurityConfiguration_To_api_PodSecurityConfiguration(in *PodSecurityConfiguration, out *api.PodSecurityConfiguration, s conversion.Scope) error {
if err := Convert_v1alpha1_PodSecurityDefaults_To_api_PodSecurityDefaults(&in.Defaults, &out.Defaults, s); err != nil {
return err
}
if err := Convert_v1alpha1_PodSecurityExemptions_To_api_PodSecurityExemptions(&in.Exemptions, &out.Exemptions, s); err != nil {
return err
}
return nil
}
// Convert_v1alpha1_PodSecurityConfiguration_To_api_PodSecurityConfiguration is an autogenerated conversion function.
func Convert_v1alpha1_PodSecurityConfiguration_To_api_PodSecurityConfiguration(in *PodSecurityConfiguration, out *api.PodSecurityConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_PodSecurityConfiguration_To_api_PodSecurityConfiguration(in, out, s)
}
func autoConvert_api_PodSecurityConfiguration_To_v1alpha1_PodSecurityConfiguration(in *api.PodSecurityConfiguration, out *PodSecurityConfiguration, s conversion.Scope) error {
if err := Convert_api_PodSecurityDefaults_To_v1alpha1_PodSecurityDefaults(&in.Defaults, &out.Defaults, s); err != nil {
return err
}
if err := Convert_api_PodSecurityExemptions_To_v1alpha1_PodSecurityExemptions(&in.Exemptions, &out.Exemptions, s); err != nil {
return err
}
return nil
}
// Convert_api_PodSecurityConfiguration_To_v1alpha1_PodSecurityConfiguration is an autogenerated conversion function.
func Convert_api_PodSecurityConfiguration_To_v1alpha1_PodSecurityConfiguration(in *api.PodSecurityConfiguration, out *PodSecurityConfiguration, s conversion.Scope) error {
return autoConvert_api_PodSecurityConfiguration_To_v1alpha1_PodSecurityConfiguration(in, out, s)
}
func autoConvert_v1alpha1_PodSecurityDefaults_To_api_PodSecurityDefaults(in *PodSecurityDefaults, out *api.PodSecurityDefaults, s conversion.Scope) error {
out.Enforce = in.Enforce
out.EnforceVersion = in.EnforceVersion
out.Audit = in.Audit
out.AuditVersion = in.AuditVersion
out.Warn = in.Warn
out.WarnVersion = in.WarnVersion
return nil
}
// Convert_v1alpha1_PodSecurityDefaults_To_api_PodSecurityDefaults is an autogenerated conversion function.
func Convert_v1alpha1_PodSecurityDefaults_To_api_PodSecurityDefaults(in *PodSecurityDefaults, out *api.PodSecurityDefaults, s conversion.Scope) error {
return autoConvert_v1alpha1_PodSecurityDefaults_To_api_PodSecurityDefaults(in, out, s)
}
func autoConvert_api_PodSecurityDefaults_To_v1alpha1_PodSecurityDefaults(in *api.PodSecurityDefaults, out *PodSecurityDefaults, s conversion.Scope) error {
out.Enforce = in.Enforce
out.EnforceVersion = in.EnforceVersion
out.Audit = in.Audit
out.AuditVersion = in.AuditVersion
out.Warn = in.Warn
out.WarnVersion = in.WarnVersion
return nil
}
// Convert_api_PodSecurityDefaults_To_v1alpha1_PodSecurityDefaults is an autogenerated conversion function.
func Convert_api_PodSecurityDefaults_To_v1alpha1_PodSecurityDefaults(in *api.PodSecurityDefaults, out *PodSecurityDefaults, s conversion.Scope) error {
return autoConvert_api_PodSecurityDefaults_To_v1alpha1_PodSecurityDefaults(in, out, s)
}
func autoConvert_v1alpha1_PodSecurityExemptions_To_api_PodSecurityExemptions(in *PodSecurityExemptions, out *api.PodSecurityExemptions, s conversion.Scope) error {
out.Usernames = *(*[]string)(unsafe.Pointer(&in.Usernames))
out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces))
out.RuntimeClasses = *(*[]string)(unsafe.Pointer(&in.RuntimeClasses))
return nil
}
// Convert_v1alpha1_PodSecurityExemptions_To_api_PodSecurityExemptions is an autogenerated conversion function.
func Convert_v1alpha1_PodSecurityExemptions_To_api_PodSecurityExemptions(in *PodSecurityExemptions, out *api.PodSecurityExemptions, s conversion.Scope) error {
return autoConvert_v1alpha1_PodSecurityExemptions_To_api_PodSecurityExemptions(in, out, s)
}
func autoConvert_api_PodSecurityExemptions_To_v1alpha1_PodSecurityExemptions(in *api.PodSecurityExemptions, out *PodSecurityExemptions, s conversion.Scope) error {
out.Usernames = *(*[]string)(unsafe.Pointer(&in.Usernames))
out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces))
out.RuntimeClasses = *(*[]string)(unsafe.Pointer(&in.RuntimeClasses))
return nil
}
// Convert_api_PodSecurityExemptions_To_v1alpha1_PodSecurityExemptions is an autogenerated conversion function.
func Convert_api_PodSecurityExemptions_To_v1alpha1_PodSecurityExemptions(in *api.PodSecurityExemptions, out *PodSecurityExemptions, s conversion.Scope) error {
return autoConvert_api_PodSecurityExemptions_To_v1alpha1_PodSecurityExemptions(in, out, s)
}

View File

@ -0,0 +1,99 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurityConfiguration) DeepCopyInto(out *PodSecurityConfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
out.Defaults = in.Defaults
in.Exemptions.DeepCopyInto(&out.Exemptions)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityConfiguration.
func (in *PodSecurityConfiguration) DeepCopy() *PodSecurityConfiguration {
if in == nil {
return nil
}
out := new(PodSecurityConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PodSecurityConfiguration) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurityDefaults) DeepCopyInto(out *PodSecurityDefaults) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityDefaults.
func (in *PodSecurityDefaults) DeepCopy() *PodSecurityDefaults {
if in == nil {
return nil
}
out := new(PodSecurityDefaults)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurityExemptions) DeepCopyInto(out *PodSecurityExemptions) {
*out = *in
if in.Usernames != nil {
in, out := &in.Usernames, &out.Usernames
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RuntimeClasses != nil {
in, out := &in.RuntimeClasses, &out.RuntimeClasses
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityExemptions.
func (in *PodSecurityExemptions) DeepCopy() *PodSecurityExemptions {
if in == nil {
return nil
}
out := new(PodSecurityExemptions)
in.DeepCopyInto(out)
return out
}

View File

@ -0,0 +1,37 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
scheme.AddTypeDefaultingFunc(&PodSecurityConfiguration{}, func(obj interface{}) { SetObjectDefaults_PodSecurityConfiguration(obj.(*PodSecurityConfiguration)) })
return nil
}
func SetObjectDefaults_PodSecurityConfiguration(in *PodSecurityConfiguration) {
SetDefaults_PodSecurityDefaults(&in.Defaults)
}

View File

@ -0,0 +1,28 @@
/*
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 validation
import (
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/pod-security-admission/admission/api"
)
func ValidatePodSecurityConfiguration(configuration *api.PodSecurityConfiguration) field.ErrorList {
allErrs := field.ErrorList{}
// TODO: validate default levels and versions
return allErrs
}

View File

@ -0,0 +1,17 @@
/*
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 validation

View File

@ -0,0 +1,99 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package api
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurityConfiguration) DeepCopyInto(out *PodSecurityConfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
out.Defaults = in.Defaults
in.Exemptions.DeepCopyInto(&out.Exemptions)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityConfiguration.
func (in *PodSecurityConfiguration) DeepCopy() *PodSecurityConfiguration {
if in == nil {
return nil
}
out := new(PodSecurityConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PodSecurityConfiguration) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurityDefaults) DeepCopyInto(out *PodSecurityDefaults) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityDefaults.
func (in *PodSecurityDefaults) DeepCopy() *PodSecurityDefaults {
if in == nil {
return nil
}
out := new(PodSecurityDefaults)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurityExemptions) DeepCopyInto(out *PodSecurityExemptions) {
*out = *in
if in.Usernames != nil {
in, out := &in.Usernames, &out.Usernames
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RuntimeClasses != nil {
in, out := &in.RuntimeClasses, &out.RuntimeClasses
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityExemptions.
func (in *PodSecurityExemptions) DeepCopy() *PodSecurityExemptions {
if in == nil {
return nil
}
out := new(PodSecurityExemptions)
in.DeepCopyInto(out)
return out
}

View 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

View File

@ -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{})
}

View 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())
}

View File

@ -0,0 +1,46 @@
/*
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 api
type Level string
const (
LevelPrivileged Level = "privileged"
LevelBaseline Level = "baseline"
LevelRestricted Level = "restricted"
)
var validLevels = []string{
string(LevelPrivileged),
string(LevelBaseline),
string(LevelRestricted),
}
const VersionLatest = "latest"
const AuditAnnotationPrefix = labelPrefix
const (
labelPrefix = "pod-security.kubernetes.io/"
EnforceLevelLabel = labelPrefix + "enforce"
EnforceVersionLabel = labelPrefix + "enforce-version"
AuditLevelLabel = labelPrefix + "audit"
AuditVersionLabel = labelPrefix + "audit-version"
WarnLevelLabel = labelPrefix + "warn"
WarnVersionLabel = labelPrefix + "warn-version"
)

View 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 api contains constants and helpers for PodSecurity admission label keys and values
package api // import "k8s.io/pod-security-admission/api"

View File

@ -0,0 +1,202 @@
/*
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 api
import (
"fmt"
"regexp"
"strconv"
"strings"
"k8s.io/apimachinery/pkg/util/errors"
)
type Version struct {
major int
minor int
latest bool
}
func (v Version) String() string {
if v.latest {
return "latest"
}
return fmt.Sprintf("v%d.%d", v.major, v.minor)
}
// Older returns true if this version v is older than the other.
func (v *Version) Older(other Version) bool {
if v.latest { // Latest is always consider newer, even than future versions.
return false
}
if other.latest {
return true
}
if v.major != other.major {
return v.major < other.major
}
return v.minor < other.minor
}
func (v *Version) Major() int {
return v.major
}
func (v *Version) Minor() int {
return v.minor
}
func (v *Version) Latest() bool {
return v.latest
}
func MajorMinorVersion(major, minor int) Version {
return Version{major: major, minor: minor}
}
func LatestVersion() Version {
return Version{latest: true}
}
// ParseLevel returns the level that should be evaluated.
// level must be "privileged", "baseline", or "restricted".
// if level does not match one of those strings, "restricted" and an error is returned.
func ParseLevel(level string) (Level, error) {
switch Level(level) {
case LevelPrivileged, LevelBaseline, LevelRestricted:
return Level(level), nil
default:
return LevelRestricted, fmt.Errorf(`must be one of %s`, strings.Join(validLevels, ", "))
}
}
// Valid checks whether the level l is a valid level.
func (l *Level) Valid() bool {
switch *l {
case LevelPrivileged, LevelBaseline, LevelRestricted:
return true
default:
return false
}
}
var versionRegexp = regexp.MustCompile(`^v1\.([0-9]|[1-9][0-9]*)$`)
// ParseVersion returns the policy version that should be evaluated.
// version must be "latest" or "v1.x".
// If version does not match one of those patterns, the latest version and an error is returned.
func ParseVersion(version string) (Version, error) {
if version == "latest" {
return Version{latest: true}, nil
}
match := versionRegexp.FindStringSubmatch(version)
if len(match) != 2 {
return Version{latest: true}, fmt.Errorf(`must be "latest" or "v1.x"`)
}
versionNumber, err := strconv.Atoi(match[1])
if err != nil || versionNumber < 0 {
return Version{latest: true}, fmt.Errorf(`must be "latest" or "v1.x"`)
}
return Version{major: 1, minor: versionNumber}, nil
}
type LevelVersion struct {
Level
Version
}
func (lv LevelVersion) String() string {
return fmt.Sprintf("%s:%s", lv.Level, lv.Version)
}
type Policy struct {
Enforce LevelVersion
Audit LevelVersion
Warn LevelVersion
}
// PolicyToEvaluate resolves the PodSecurity namespace labels to the policy for that namespace,
// 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) {
var (
err error
errs []error
p = defaults
)
if level, ok := labels[EnforceLevelLabel]; ok {
p.Enforce.Level, err = ParseLevel(level)
errs = appendErr(errs, err, "Enforce.Level")
}
if version, ok := labels[EnforceVersionLabel]; ok {
p.Enforce.Version, err = ParseVersion(version)
errs = appendErr(errs, err, "Enforce.Version")
}
if level, ok := labels[AuditLevelLabel]; ok {
p.Audit.Level, err = ParseLevel(level)
errs = appendErr(errs, err, "Audit.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, "Audit.Version")
}
if level, ok := labels[WarnLevelLabel]; ok {
p.Warn.Level, err = ParseLevel(level)
errs = appendErr(errs, err, "Warn.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, "Warn.Version")
}
return p, errors.NewAggregate(errs)
}
// CompareLevels returns an integer comparing two levels by strictness. The result will be 0 if
// a==b, -1 if a is less strict than b, and +1 if a is more strict than b.
func CompareLevels(a, b Level) int {
if a == b {
return 0
}
switch a {
case LevelPrivileged:
return -1
case LevelRestricted:
return 1
default:
if b == LevelPrivileged {
return 1
} else if b == LevelRestricted {
return -1
}
}
// This should only happen if both a & b are invalid levels.
return 0
}
// appendErr is a helper function to collect field-specific errors.
func appendErr(errs []error, err error, field string) []error {
if err != nil {
return append(errs, fmt.Errorf("%s: %s", field, err.Error()))
}
return errs
}

View File

@ -0,0 +1,55 @@
/*
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 api
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseVersion(t *testing.T) {
successes := map[string]Version{
"latest": LatestVersion(),
"v1.0": MajorMinorVersion(1, 0),
"v1.1": MajorMinorVersion(1, 1),
"v1.20": MajorMinorVersion(1, 20),
"v1.10000": MajorMinorVersion(1, 10000),
}
for v, expected := range successes {
t.Run(v, func(t *testing.T) {
actual, err := ParseVersion(v)
require.NoError(t, err)
assert.Equal(t, expected, actual)
})
}
failures := []string{
"foo",
"",
"v2.0",
"v1",
"1.1",
}
for _, v := range failures {
t.Run(v, func(t *testing.T) {
_, err := ParseVersion(v)
assert.Error(t, err)
})
}
}

View File

@ -4,4 +4,23 @@ module k8s.io/pod-security-admission
go 1.16
replace k8s.io/pod-security-admission => ../pod-security-admission
require (
github.com/google/go-cmp v0.5.5
github.com/stretchr/testify v1.7.0
k8s.io/api v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/apiserver v0.0.0
k8s.io/client-go v0.0.0
k8s.io/klog/v2 v2.9.0
k8s.io/utils v0.0.0-20210521133846-da695404a2bc
sigs.k8s.io/yaml v1.2.0
)
replace (
k8s.io/api => ../api
k8s.io/apimachinery => ../apimachinery
k8s.io/apiserver => ../apiserver
k8s.io/client-go => ../client-go
k8s.io/component-base => ../component-base
k8s.io/pod-security-admission => ../pod-security-admission
)

View File

@ -0,0 +1,714 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM=
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/utils v0.0.0-20210521133846-da695404a2bc h1:dx6VGe+PnOW/kD/2UV4aUSsRfJGd7+lcqgJ6Xg0HwUs=
k8s.io/utils v0.0.0-20210521133846-da695404a2bc/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.21/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.1 h1:nYqY2A6oy37sKLYuSBXuQhbj4JVclzJK13BOIvJG5XU=
sigs.k8s.io/structured-merge-diff/v4 v4.1.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View 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 metrics contains metrics interfaces and implementations for PodSecurity admission
package metrics // import "k8s.io/pod-security-admission/metrics"

View File

@ -0,0 +1,24 @@
/*
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 metrics
type EvaluationRecorder interface {
// TODO: fill in args required to record https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/2579-psp-replacement#monitoring
RecordEvaluation()
}
// TODO: default prometheus-based implementation

View File

@ -0,0 +1,17 @@
/*
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 metrics

View File

@ -0,0 +1,92 @@
/*
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 policy
import (
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/pod-security-admission/api"
)
func init() {
addCheck(CheckAddCapabilities)
}
// CheckAddCapabilities returns a baseline level check
// that limits the capabilities that can be added in 1.0+
func CheckAddCapabilities() Check {
return Check{
ID: "addCapabilities",
Level: api.LevelBaseline,
Versions: []VersionedCheck{
{
MinimumVersion: api.MajorMinorVersion(1, 0),
CheckPod: addCapabilities_1_0,
},
},
}
}
var (
capabilities_allowed_1_0 = sets.NewString(
"AUDIT_WRITE",
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"FSETID",
"KILL",
"MKNOD",
"NET_BIND_SERVICE",
"SETFCAP",
"SETGID",
"SETPCAP",
"SETUID",
"SYS_CHROOT",
)
)
func addCapabilities_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
forbiddenContainers := sets.NewString()
forbiddenCapabilities := sets.NewString()
visitContainersWithPath(podSpec, field.NewPath("spec"), func(container *corev1.Container, path *field.Path) {
if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil {
for _, c := range container.SecurityContext.Capabilities.Add {
if !capabilities_allowed_1_0.Has(string(c)) {
forbiddenContainers.Insert(container.Name)
forbiddenCapabilities.Insert(string(c))
}
}
}
})
if len(forbiddenCapabilities) > 0 {
return CheckResult{
Allowed: false,
ForbiddenReason: "forbidden capabilities",
ForbiddenDetail: fmt.Sprintf(
"containers %q added %q",
forbiddenContainers.List(),
forbiddenCapabilities.List(),
),
}
}
return CheckResult{Allowed: true}
}

View File

@ -0,0 +1,76 @@
/*
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 policy
import (
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/pod-security-admission/api"
)
/*
Privilege escalation (such as via set-user-ID or set-group-ID file mode) should not be allowed.
**Restricted Fields:**
spec.containers[*].securityContext.allowPrivilegeEscalation
spec.initContainers[*].securityContext.allowPrivilegeEscalation
**Allowed Values:** false
*/
func init() {
addCheck(CheckAllowPrivilegeEscalation)
}
// CheckAllowPrivilegeEscalation returns a restricted level check
// that requires allowPrivilegeEscalation=false in 1.8+
func CheckAllowPrivilegeEscalation() Check {
return Check{
ID: "allowPrivilegeEscalation",
Level: api.LevelRestricted,
Versions: []VersionedCheck{
{
// Field added in 1.8:
// https://github.com/kubernetes/kubernetes/blob/v1.8.0/staging/src/k8s.io/api/core/v1/types.go#L4797-L4804
MinimumVersion: api.MajorMinorVersion(1, 8),
CheckPod: allowPrivilegeEscalation_1_8,
},
},
}
}
func allowPrivilegeEscalation_1_8(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
var forbiddenPaths []string
visitContainersWithPath(podSpec, field.NewPath("spec"), func(container *corev1.Container, path *field.Path) {
if container.SecurityContext == nil || container.SecurityContext.AllowPrivilegeEscalation == nil || *container.SecurityContext.AllowPrivilegeEscalation {
forbiddenPaths = append(forbiddenPaths, path.Child("securityContext", "allowPrivilegeEscalation").String())
}
})
if len(forbiddenPaths) > 0 {
return CheckResult{
Allowed: false,
ForbiddenReason: "allowPrivilegeEscalation != false",
ForbiddenDetail: strings.Join(forbiddenPaths, ", "),
}
}
return CheckResult{Allowed: true}
}

View File

@ -0,0 +1,105 @@
/*
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 policy
import (
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/pod-security-admission/api"
)
/*
Containers must be required to run as non-root users.
**Restricted Fields:**
spec.securityContext.runAsNonRoot
spec.containers[*].securityContext.runAsNonRoot
spec.initContainers[*].securityContext.runAsNonRoot
**Allowed Values:** true
*/
func init() {
addCheck(CheckRunAsNonRoot)
}
// CheckRunAsNonRoot returns a restricted level check
// that requires runAsNonRoot=true in 1.0+
func CheckRunAsNonRoot() Check {
return Check{
ID: "runAsNonRoot",
Level: api.LevelRestricted,
Versions: []VersionedCheck{
{
MinimumVersion: api.MajorMinorVersion(1, 0),
CheckPod: runAsNonRoot_1_0,
},
},
}
}
func runAsNonRoot_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
var forbiddenPaths []string
// TODO: how to check ephemeral containers
containerCount := 0
containerRunAsNonRootCount := 0
podRunAsNonRoot := false
visitContainersWithPath(podSpec, field.NewPath("spec"), func(container *corev1.Container, path *field.Path) {
containerCount++
if container.SecurityContext != nil && container.SecurityContext.RunAsNonRoot != nil {
if !*container.SecurityContext.RunAsNonRoot {
forbiddenPaths = append(forbiddenPaths, path.Child("securityContext", "runAsNonRoot").String())
} else {
containerRunAsNonRootCount++
}
}
})
if podSpec.SecurityContext != nil && podSpec.SecurityContext.RunAsNonRoot != nil {
if !*podSpec.SecurityContext.RunAsNonRoot {
forbiddenPaths = append(forbiddenPaths, field.NewPath("spec").Child("securityContext", "runAsNonRoot").String())
} else {
podRunAsNonRoot = true
}
}
// pod or containers explicitly set runAsNonRoot=false
if len(forbiddenPaths) > 0 {
return CheckResult{
Allowed: false,
ForbiddenReason: "runAsNonRoot != false",
ForbiddenDetail: strings.Join(forbiddenPaths, ", "),
}
}
// pod didn't set runAsNonRoot and not all containers opted into runAsNonRoot
if podRunAsNonRoot == false && containerCount > containerRunAsNonRootCount {
return CheckResult{
Allowed: false,
ForbiddenReason: "runAsNonRoot != false",
}
}
return CheckResult{Allowed: true}
}

View File

@ -0,0 +1,110 @@
/*
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 policy
import (
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/pod-security-admission/api"
)
/*
Setting the SELinux type is restricted, and setting a custom SELinux user or role option is forbidden.
**Restricted Fields:**
spec.securityContext.seLinuxOptions.type
spec.containers[*].securityContext.seLinuxOptions.type
spec.initContainers[*].securityContext.seLinuxOptions.type
**Allowed Values:**
undefined/empty
container_t
container_init_t
container_kvm_t
**Restricted Fields:**
spec.securityContext.seLinuxOptions.user
spec.containers[*].securityContext.seLinuxOptions.user
spec.initContainers[*].securityContext.seLinuxOptions.user
spec.securityContext.seLinuxOptions.role
spec.containers[*].securityContext.seLinuxOptions.role
spec.initContainers[*].securityContext.seLinuxOptions.role
**Allowed Values:** undefined/empty
*/
func init() {
addCheck(CheckSELinux)
}
// CheckSELinux returns a baseline level check
// that limits seLinuxOptions type, user, and role values in 1.0+
func CheckSELinux() Check {
return Check{
ID: "selinux",
Level: api.LevelBaseline,
Versions: []VersionedCheck{
{
MinimumVersion: api.MajorMinorVersion(1, 0),
CheckPod: checkSelinux_1_0,
},
},
}
}
var (
selinux_allowed_types_1_0 = sets.NewString("", "container_t", "container_init_t", "container_kvm_t")
)
func checkSelinux_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
var forbiddenDetails []string
checkSelinuxOptions := func(path *field.Path, opts *corev1.SELinuxOptions) {
if !selinux_allowed_types_1_0.Has(opts.Type) {
forbiddenDetails = append(forbiddenDetails, path.Child("securityContext", "seLinuxOptions", "type").String())
}
if len(opts.User) > 0 {
forbiddenDetails = append(forbiddenDetails, path.Child("securityContext", "seLinuxOptions", "user").String())
}
if len(opts.Role) > 0 {
forbiddenDetails = append(forbiddenDetails, path.Child("securityContext", "seLinuxOptions", "role").String())
}
}
if podSpec.SecurityContext != nil && podSpec.SecurityContext.SELinuxOptions != nil {
checkSelinuxOptions(field.NewPath("spec"), podSpec.SecurityContext.SELinuxOptions)
}
visitContainersWithPath(podSpec, field.NewPath("spec"), func(container *corev1.Container, path *field.Path) {
if container.SecurityContext != nil && container.SecurityContext.SELinuxOptions != nil {
checkSelinuxOptions(path, container.SecurityContext.SELinuxOptions)
}
})
if len(forbiddenDetails) > 0 {
return CheckResult{
Allowed: false,
ForbiddenReason: "forbidden seLinuxOptions",
ForbiddenDetail: strings.Join(forbiddenDetails, ", "),
}
}
return CheckResult{Allowed: true}
}

View File

@ -0,0 +1,178 @@
/*
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 policy
import (
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/pod-security-admission/api"
)
type Check struct {
// ID is the unique ID of the check.
ID string
// Level is the policy level this check belongs to.
// Must be Baseline or Restricted.
// Baseline checks are evaluated for baseline and restricted namespaces.
// Restricted checks are only evaluated for restricted namespaces.
Level api.Level
// Versions contains one or more revisions of the check that apply to different versions.
// If the check is not yet assigned to a version, this must be a single-item list with a MinimumVersion of "".
// Otherwise, MinimumVersion of items must represent strictly increasing versions.
Versions []VersionedCheck
}
type VersionedCheck struct {
// MinimumVersion is the first policy version this check applies to.
// If unset, this check is not yet assigned to a policy version.
// If set, must not be "latest".
MinimumVersion api.Version
// CheckPod determines if the pod is allowed.
CheckPod CheckPodFn
}
type CheckPodFn func(*metav1.ObjectMeta, *corev1.PodSpec) CheckResult
// CheckResult contains the result of checking a pod and indicates whether the pod is allowed,
// and if not, why it was forbidden.
//
// Example output for (false, "host ports", "8080, 9090"):
// When checking all pods in a namespace:
// disallowed by policy "baseline": host ports, privileged containers, non-default capabilities
// When checking an individual pod:
// disallowed by policy "baseline": host ports (8080, 9090), privileged containers, non-default capabilities (CAP_NET_RAW)
type CheckResult struct {
// Allowed indicates if the check allowed the pod.
Allowed bool
// ForbiddenReason must be set if Allowed is false.
// ForbiddenReason should be as succinct as possible and is always output.
// Examples:
// - "host ports"
// - "privileged containers"
// - "non-default capabilities"
ForbiddenReason string
// ForbiddenDetail should only be set if Allowed is false, and is optional.
// ForbiddenDetail can include specific values that were disallowed and is used when checking an individual object.
// Examples:
// - list specific invalid host ports: "8080, 9090"
// - list specific invalid containers: "container1, container2"
// - list specific non-default capabilities: "CAP_NET_RAW"
ForbiddenDetail string
}
// AggergateCheckResult holds the aggregate result of running CheckPod across multiple checks.
type AggregateCheckResult struct {
// Allowed indicates if all checks allowed the pod.
Allowed bool
// ForbiddenReasons is a slice of the forbidden reasons from all the forbidden checks. It should not include empty strings.
// ForbiddenReasons and ForbiddenDetails must have the same number of elements, and the indexes are for the same check.
ForbiddenReasons []string
// ForbiddenDetails is a slice of the forbidden details from all the forbidden checks. It may include empty strings.
// ForbiddenReasons and ForbiddenDetails must have the same number of elements, and the indexes are for the same check.
ForbiddenDetails []string
}
// ForbiddenReason returns a comma-separated string of of the forbidden reasons.
// Example: host ports, privileged containers, non-default capabilities
func (a *AggregateCheckResult) ForbiddenReason() string {
return strings.Join(a.ForbiddenReasons, ", ")
}
// ForbiddenDetail returns a detailed forbidden message, with non-empty details formatted in
// parentheses with the associated reason.
// Example: host ports (8080, 9090), privileged containers, non-default capabilities (NET_RAW)
func (a *AggregateCheckResult) ForbiddenDetail() string {
var b strings.Builder
for i := 0; i < len(a.ForbiddenReasons); i++ {
b.WriteString(a.ForbiddenReasons[i])
if a.ForbiddenDetails[i] != "" {
b.WriteString(" (")
b.WriteString(a.ForbiddenDetails[i])
b.WriteString(")")
}
if i != len(a.ForbiddenReasons)-1 {
b.WriteString(", ")
}
}
return b.String()
}
// UnknownForbiddenReason is used as the placeholder forbidden reason for checks that incorrectly disallow without providing a reason.
const UnknownForbiddenReason = "unknown forbidden reason"
// AggregateCheckPod runs all the checks and aggregates the forbidden results into a single CheckResult.
// The aggregated reason is a comma-separated
func AggregateCheckResults(results []CheckResult) AggregateCheckResult {
var (
reasons []string
details []string
)
for _, result := range results {
if !result.Allowed {
if len(result.ForbiddenReason) == 0 {
reasons = append(reasons, UnknownForbiddenReason)
} else {
reasons = append(reasons, result.ForbiddenReason)
}
details = append(details, result.ForbiddenDetail)
}
}
return AggregateCheckResult{
Allowed: len(reasons) == 0,
ForbiddenReasons: reasons,
ForbiddenDetails: details,
}
}
var (
defaultChecks []func() Check
experimentalChecks []func() Check
)
func addCheck(f func() Check) {
// add to experimental or versioned list
c := f()
if len(c.Versions) == 1 && c.Versions[0].MinimumVersion == (api.Version{}) {
experimentalChecks = append(experimentalChecks, f)
} else {
defaultChecks = append(defaultChecks, f)
}
}
// DefaultChecks returns checks that are expected to be enabled by default.
// The results are mutually exclusive with ExperimentalChecks.
// It returns a new copy of checks on each invocation and is expected to be called once at setup time.
func DefaultChecks() []Check {
retval := make([]Check, 0, len(defaultChecks))
for _, f := range defaultChecks {
retval = append(retval, f())
}
return retval
}
// ExperimentalChecks returns checks that have not yet been assigned to policy versions.
// The results are mutually exclusive with DefaultChecks.
// It returns a new copy of checks on each invocation and is expected to be called once at setup time.
func ExperimentalChecks() []Check {
retval := make([]Check, 0, len(experimentalChecks))
for _, f := range experimentalChecks {
retval = append(retval, f())
}
return retval
}

View 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 policy contains implementations of Pod Security Standards checks
package policy // import "k8s.io/pod-security-admission/policy"

View File

@ -0,0 +1,146 @@
/*
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 policy
import (
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/pod-security-admission/api"
)
// Evaluator holds the Checks that are used to validate a policy.
type Evaluator interface {
// EvaluatePod evaluates the pod against the policy for the given level & version.
EvaluatePod(lv api.LevelVersion, podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) []CheckResult
}
// checkRegistry provides a default implementation of an Evaluator.
type checkRegistry struct {
// The checks are a map of check_ID -> sorted slice of versioned checks, newest first
baselineChecks, restrictedChecks map[api.Version][]CheckPodFn
// maxVersion is the maximum version that is cached, guaranteed to be at least
// the max MinimumVersion of all registered checks.
maxVersion api.Version
}
// NewEvaluator constructs a new Evaluator instance from the list of checks. If the provided checks are invalid,
// an error is returned. A valid list of checks must meet the following requirements:
// 1. Check.ID is unique in the list
// 2. Check.Level must be either Baseline or Restricted
// 3. Checks must have a non-empty set of versions, sorted in a strictly increasing order
// 4. Check.Versions cannot include 'latest'
func NewEvaluator(checks []Check) (Evaluator, error) {
if err := validateChecks(checks); err != nil {
return nil, err
}
r := &checkRegistry{
baselineChecks: map[api.Version][]CheckPodFn{},
restrictedChecks: map[api.Version][]CheckPodFn{},
}
populate(r, checks)
return r, nil
}
func (r *checkRegistry) EvaluatePod(lv api.LevelVersion, podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) []CheckResult {
if lv.Level == api.LevelPrivileged {
return nil
}
if r.maxVersion.Older(lv.Version) {
lv.Version = r.maxVersion
}
results := []CheckResult{}
for _, check := range r.baselineChecks[lv.Version] {
results = append(results, check(podMetadata, podSpec))
}
if lv.Level == api.LevelBaseline {
return results
}
for _, check := range r.restrictedChecks[lv.Version] {
results = append(results, check(podMetadata, podSpec))
}
return results
}
func validateChecks(checks []Check) error {
ids := map[string]bool{}
for _, check := range checks {
if ids[check.ID] {
return fmt.Errorf("multiple checks registered for ID %s", check.ID)
}
ids[check.ID] = true
if check.Level != api.LevelBaseline && check.Level != api.LevelRestricted {
return fmt.Errorf("check %s: invalid level %s", check.ID, check.Level)
}
if len(check.Versions) == 0 {
return fmt.Errorf("check %s: empty", check.ID)
}
maxVersion := api.Version{}
for _, c := range check.Versions {
if c.MinimumVersion == (api.Version{}) {
return fmt.Errorf("check %s: undefined version found", check.ID)
}
if c.MinimumVersion.Latest() {
return fmt.Errorf("check %s: version cannot be 'latest'", check.ID)
}
if maxVersion == c.MinimumVersion {
return fmt.Errorf("check %s: duplicate version %s", check.ID, c.MinimumVersion)
}
if !maxVersion.Older(c.MinimumVersion) {
return fmt.Errorf("check %s: versions must be strictly increasing", check.ID)
}
maxVersion = c.MinimumVersion
}
}
return nil
}
func populate(r *checkRegistry, validChecks []Check) {
// Find the max(MinimumVersion) across all checks.
for _, c := range validChecks {
lastVersion := c.Versions[len(c.Versions)-1].MinimumVersion
if r.maxVersion.Older(lastVersion) {
r.maxVersion = lastVersion
}
}
for _, c := range validChecks {
if c.Level == api.LevelRestricted {
inflateVersions(c, r.restrictedChecks, r.maxVersion)
} else {
inflateVersions(c, r.baselineChecks, r.maxVersion)
}
}
}
func inflateVersions(check Check, versions map[api.Version][]CheckPodFn, maxVersion api.Version) {
for i, c := range check.Versions {
var nextVersion api.Version
if i+1 < len(check.Versions) {
nextVersion = check.Versions[i+1].MinimumVersion
} else {
// Assumes only 1 Major version.
nextVersion = api.MajorMinorVersion(1, maxVersion.Minor()+1)
}
// Iterate over all versions from the minimum of the current check, to the minimum of the
// next check, or the maxVersion++.
for v := c.MinimumVersion; v.Older(nextVersion); v = api.MajorMinorVersion(1, v.Minor()+1) {
versions[v] = append(versions[v], check.Versions[i].CheckPod)
}
}
}

View File

@ -0,0 +1,103 @@
/*
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 policy
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/pod-security-admission/api"
)
func TestCheckRegistry(t *testing.T) {
checks := []Check{
generateCheck("a", api.LevelBaseline, []string{"v1.0"}),
generateCheck("b", api.LevelBaseline, []string{"v1.10"}),
generateCheck("c", api.LevelBaseline, []string{"v1.0", "v1.5", "v1.10"}),
generateCheck("d", api.LevelBaseline, []string{"v1.11", "v1.15", "v1.20"}),
generateCheck("e", api.LevelRestricted, []string{"v1.0"}),
generateCheck("f", api.LevelRestricted, []string{"v1.12", "v1.16", "v1.21"}),
}
reg, err := NewEvaluator(checks)
require.NoError(t, err)
levelCases := []struct {
level api.Level
version string
expectedReasons []string
}{
{api.LevelPrivileged, "v1.0", nil},
{api.LevelPrivileged, "latest", nil},
{api.LevelBaseline, "v1.0", []string{"a:v1.0", "c:v1.0"}},
{api.LevelBaseline, "v1.4", []string{"a:v1.0", "c:v1.0"}},
{api.LevelBaseline, "v1.5", []string{"a:v1.0", "c:v1.5"}},
{api.LevelBaseline, "v1.10", []string{"a:v1.0", "b:v1.10", "c:v1.10"}},
{api.LevelBaseline, "v1.11", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.11"}},
{api.LevelBaseline, "latest", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.20"}},
{api.LevelRestricted, "v1.0", []string{"a:v1.0", "c:v1.0", "e:v1.0"}},
{api.LevelRestricted, "v1.4", []string{"a:v1.0", "c:v1.0", "e:v1.0"}},
{api.LevelRestricted, "v1.5", []string{"a:v1.0", "c:v1.5", "e:v1.0"}},
{api.LevelRestricted, "v1.10", []string{"a:v1.0", "b:v1.10", "c:v1.10", "e:v1.0"}},
{api.LevelRestricted, "v1.11", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.11", "e:v1.0"}},
{api.LevelRestricted, "latest", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.20", "e:v1.0", "f:v1.21"}},
{api.LevelRestricted, "v1.10000", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.20", "e:v1.0", "f:v1.21"}},
}
for _, test := range levelCases {
t.Run(fmt.Sprintf("%s:%s", test.level, test.version), func(t *testing.T) {
results := reg.EvaluatePod(api.LevelVersion{test.level, versionOrPanic(test.version)}, nil, nil)
// Set extract the ForbiddenReasons from the results.
var actualReasons []string
for _, result := range results {
actualReasons = append(actualReasons, result.ForbiddenReason)
}
assert.ElementsMatch(t, test.expectedReasons, actualReasons)
})
}
}
func generateCheck(id string, level api.Level, versions []string) Check {
c := Check{
ID: id,
Level: level,
}
for _, ver := range versions {
v := versionOrPanic(ver) // Copy ver so it can be used in the CheckPod closure.
c.Versions = append(c.Versions, VersionedCheck{
MinimumVersion: v,
CheckPod: func(_ *metav1.ObjectMeta, _ *corev1.PodSpec) CheckResult {
return CheckResult{
ForbiddenReason: fmt.Sprintf("%s:%s", id, v),
}
},
})
}
return c
}
func versionOrPanic(v string) api.Version {
ver, err := api.ParseVersion(v)
if err != nil {
panic(err)
}
return ver
}

View File

@ -0,0 +1,42 @@
/*
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 policy
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ContainerVisitorWithPath is called with each container and the field.Path to that container
type ContainerVisitorWithPath func(container *corev1.Container, path *field.Path)
// visitContainersWithPath invokes the visitor function with a pointer to the spec
// of every container in the given pod spec and the field.Path to that container.
func visitContainersWithPath(podSpec *corev1.PodSpec, specPath *field.Path, visitor ContainerVisitorWithPath) {
fldPath := specPath.Child("initContainers")
for i := range podSpec.InitContainers {
visitor(&podSpec.InitContainers[i], fldPath.Index(i))
}
fldPath = specPath.Child("containers")
for i := range podSpec.Containers {
visitor(&podSpec.Containers[i], fldPath.Index(i))
}
fldPath = specPath.Child("ephemeralContainers")
for i := range podSpec.EphemeralContainers {
visitor((*corev1.Container)(&podSpec.EphemeralContainers[i].EphemeralContainerCommon), fldPath.Index(i))
}
}

View 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 test contains tests for PodSecurity admission
package test // import "k8s.io/pod-security-admission/test"

View File

@ -0,0 +1,193 @@
/*
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 test
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/pod-security-admission/api"
"k8s.io/pod-security-admission/policy"
"k8s.io/utils/pointer"
)
// minimalValidPods holds minimal valid pods per-level per-version.
// To get a valid pod for a particular level/version, use getMinimalValidPod().
var minimalValidPods = map[api.Level]map[api.Version]*corev1.Pod{}
func init() {
minimalValidPods[api.LevelBaseline] = map[api.Version]*corev1.Pod{}
minimalValidPods[api.LevelRestricted] = map[api.Version]*corev1.Pod{}
// Define minimal valid baseline pod.
// This must remain valid for all versions.
baseline_1_0 := &corev1.Pod{Spec: corev1.PodSpec{
InitContainers: []corev1.Container{{Name: "initcontainer1", Image: "k8s.gcr.io/pause"}},
Containers: []corev1.Container{{Name: "container1", Image: "k8s.gcr.io/pause"}}}}
minimalValidPods[api.LevelBaseline][api.MajorMinorVersion(1, 0)] = baseline_1_0
//
// Define minimal valid restricted pods.
//
// 1.0+: baseline + runAsNonRoot=true
restricted_1_0 := tweak(baseline_1_0, func(p *corev1.Pod) {
p.Spec.SecurityContext = &corev1.PodSecurityContext{RunAsNonRoot: pointer.BoolPtr(true)}
})
minimalValidPods[api.LevelRestricted][api.MajorMinorVersion(1, 0)] = restricted_1_0
// 1.8+: runAsNonRoot=true
restricted_1_8 := tweak(restricted_1_0, func(p *corev1.Pod) {
p.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{AllowPrivilegeEscalation: pointer.BoolPtr(false)}
p.Spec.InitContainers[0].SecurityContext = &corev1.SecurityContext{AllowPrivilegeEscalation: pointer.BoolPtr(false)}
})
minimalValidPods[api.LevelRestricted][api.MajorMinorVersion(1, 8)] = restricted_1_8
}
// getValidPod returns a minimal valid pod for the specified level and version.
func getMinimalValidPod(level api.Level, version api.Version) (*corev1.Pod, error) {
originalVersion := version
for {
pod, exists := minimalValidPods[level][version]
if exists {
return pod.DeepCopy(), nil
}
if version.Minor() <= 0 {
return nil, fmt.Errorf("no valid pod fixture found in specified or older versions for %s/%s", level, originalVersion.String())
}
version = api.MajorMinorVersion(version.Major(), version.Minor()-1)
}
}
// fixtureGenerators holds fixture generators per-level per-version.
// To add generators, use registerFixtureGenerator().
// To get fixtures for a particular level/version, use getFixtures().
var fixtureGenerators = map[fixtureKey]fixtureGenerator{}
// fixtureKey is a tuple of version/level/check name
type fixtureKey struct {
version api.Version
level api.Level
check string
}
// fixtureGenerator holds generators for valid and invalid fixtures.
type fixtureGenerator struct {
// expectErrorSubstring is a substring to expect in the error message for failed pods.
// if empty, the check ID is used.
expectErrorSubstring string
// generatePass transforms a minimum valid pod into one or more valid pods.
// pods do not need to populate metadata.name.
generatePass func(*corev1.Pod) []*corev1.Pod
// generateFail transforms a minimum valid pod into one or more invalid pods.
// pods do not need to populate metadata.name.
generateFail func(*corev1.Pod) []*corev1.Pod
}
// fixtureData holds valid and invalid pod fixtures.
type fixtureData struct {
expectErrorSubstring string
pass []*corev1.Pod
fail []*corev1.Pod
}
// registerFixtureGenerator adds a generator for the given level/version/check.
// A generator registered for v1.x is used for v1.x+ if no generator is registered for the higher version.
func registerFixtureGenerator(key fixtureKey, generator fixtureGenerator) {
if err := checkKey(key); err != nil {
panic(err)
}
if _, exists := fixtureGenerators[key]; exists {
panic(fmt.Errorf("fixture generator already registered for key %#v", key))
}
if generator.generatePass == nil || generator.generateFail == nil {
panic(fmt.Errorf("adding %#v: must specify generatePass/generateFail", key))
}
fixtureGenerators[key] = generator
if key.level == api.LevelBaseline {
// also register to restricted
restrictedKey := key
restrictedKey.level = api.LevelRestricted
if _, exists := fixtureGenerators[restrictedKey]; exists {
panic(fmt.Errorf("fixture generator already registered for restricted version of key %#v", key))
}
fixtureGenerators[restrictedKey] = generator
}
}
// getFixtures returns the fixture data for the specified level/version/check.
// Fixtures are generated by applying the registered generator to the minimal valid pod for that level/version.
// If no fixture generator exists for the given version, previous generators are checked back to 1.0.
func getFixtures(key fixtureKey) (fixtureData, error) {
if err := checkKey(key); err != nil {
return fixtureData{}, err
}
validPodForLevel, err := getMinimalValidPod(key.level, key.version)
if err != nil {
return fixtureData{}, err
}
for {
if generator, exists := fixtureGenerators[key]; exists {
data := fixtureData{
expectErrorSubstring: generator.expectErrorSubstring,
pass: generator.generatePass(validPodForLevel.DeepCopy()),
fail: generator.generateFail(validPodForLevel.DeepCopy()),
}
if len(data.expectErrorSubstring) == 0 {
data.expectErrorSubstring = key.check
}
if len(data.pass) == 0 || len(data.fail) == 0 {
return fixtureData{}, fmt.Errorf("generatePass/generateFail for %#v must return at least one pod each", key)
}
return data, nil
}
if key.version.Minor() == 0 {
return fixtureData{}, fmt.Errorf("no fixture generator found in specified or older versions for %#v", key)
}
// check the next older version
key.version = api.MajorMinorVersion(key.version.Major(), key.version.Minor()-1)
}
}
// checkKey ensures the fixture key has a valid level, version, and check.
func checkKey(key fixtureKey) error {
if key.level != api.LevelBaseline && key.level != api.LevelRestricted {
return fmt.Errorf("invalid key, level must be baseline or restricted: %#v", key)
}
if key.version.Latest() || key.version.Major() != 1 || key.version.Minor() < 0 {
return fmt.Errorf("invalid key, version must be 1.0+: %#v", key)
}
if key.check == "" {
return fmt.Errorf("invalid key, check must not be empty")
}
found := false
for _, check := range policy.DefaultChecks() {
if check.ID == key.check {
found = true
break
}
}
if !found {
return fmt.Errorf("invalid key %#v, check does not exist at version", key)
}
return nil
}

View File

@ -0,0 +1,106 @@
/*
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 test
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/pod-security-admission/api"
)
/*
TODO: include field paths in reflect-based unit test
containerFields: []string{
`securityContext.capabilities.add`,
},
*/
// ensureCapabilities ensures the pod and all initContainers and containers have a non-nil capabilities.
func ensureCapabilities(p *corev1.Pod) *corev1.Pod {
p = ensureSecurityContext(p)
for i := range p.Spec.Containers {
if p.Spec.Containers[i].SecurityContext.Capabilities == nil {
p.Spec.Containers[i].SecurityContext.Capabilities = &corev1.Capabilities{}
}
}
for i := range p.Spec.InitContainers {
if p.Spec.InitContainers[i].SecurityContext.Capabilities == nil {
p.Spec.InitContainers[i].SecurityContext.Capabilities = &corev1.Capabilities{}
}
}
return p
}
func init() {
fixtureData_1_0 := fixtureGenerator{
expectErrorSubstring: "forbidden capabilities",
generatePass: func(p *corev1.Pod) []*corev1.Pod {
p = ensureCapabilities(p)
return []*corev1.Pod{
// all allowed capabilities
tweak(p, func(p *corev1.Pod) {
p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{
"AUDIT_WRITE", "CHOWN", "DAC_OVERRIDE", "FOWNER", "FSETID", "KILL", "MKNOD", "NET_BIND_SERVICE", "SETFCAP", "SETGID", "SETPCAP", "SETUID", "SYS_CHROOT",
}
}),
tweak(p, func(p *corev1.Pod) {
p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{
"AUDIT_WRITE", "CHOWN", "DAC_OVERRIDE", "FOWNER", "FSETID", "KILL", "MKNOD", "NET_BIND_SERVICE", "SETFCAP", "SETGID", "SETPCAP", "SETUID", "SYS_CHROOT",
}
}),
}
},
generateFail: func(p *corev1.Pod) []*corev1.Pod {
p = ensureCapabilities(p)
return []*corev1.Pod{
// NET_RAW
tweak(p, func(p *corev1.Pod) {
p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"NET_RAW"}
}),
tweak(p, func(p *corev1.Pod) {
p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"NET_RAW"}
}),
// case-difference
tweak(p, func(p *corev1.Pod) {
p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"chown"}
}),
tweak(p, func(p *corev1.Pod) {
p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"chown"}
}),
// unknown capability
tweak(p, func(p *corev1.Pod) {
p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"bogus"}
}),
tweak(p, func(p *corev1.Pod) {
p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"bogus"}
}),
// CAP_ prefix
tweak(p, func(p *corev1.Pod) {
p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"CAP_CHOWN"}
}),
tweak(p, func(p *corev1.Pod) {
p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"CAP_CHOWN"}
}),
}
},
}
registerFixtureGenerator(
fixtureKey{level: api.LevelBaseline, version: api.MajorMinorVersion(1, 0), check: "addCapabilities"},
fixtureData_1_0,
)
}

View File

@ -0,0 +1,65 @@
/*
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 test
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/pod-security-admission/api"
"k8s.io/utils/pointer"
)
/*
TODO: include field paths in reflect-based unit test
containerFields: []string{
`securityContext.allowPrivilegeEscalation`,
},
*/
func init() {
fixtureData_1_8 := fixtureGenerator{
generatePass: func(p *corev1.Pod) []*corev1.Pod {
return []*corev1.Pod{
// only valid pod is to explicitly set allowPrivilegeEscalation to false in all containers
p,
}
},
generateFail: func(p *corev1.Pod) []*corev1.Pod {
return []*corev1.Pod{
// explicit true
tweak(p, func(p *corev1.Pod) {
p.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = pointer.BoolPtr(true)
}),
tweak(p, func(p *corev1.Pod) {
p.Spec.InitContainers[0].SecurityContext.AllowPrivilegeEscalation = pointer.BoolPtr(true)
}),
// nil AllowPrivilegeEscalation
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = nil }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.AllowPrivilegeEscalation = nil }),
// nil security context
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext = nil }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext = nil }),
}
},
}
registerFixtureGenerator(
fixtureKey{level: api.LevelRestricted, version: api.MajorMinorVersion(1, 8), check: "allowPrivilegeEscalation"},
fixtureData_1_8,
)
}

View File

@ -0,0 +1,91 @@
/*
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 test
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/pod-security-admission/api"
"k8s.io/utils/pointer"
)
/*
TODO: include field paths in reflect-based unit test
podFields: []string{
`securityContext.runAsNonRoot`,
},
containerFields: []string{
`securityContext.runAsNonRoot`,
},
*/
func init() {
fixtureData_1_0 := fixtureGenerator{
generatePass: func(p *corev1.Pod) []*corev1.Pod {
p = ensureSecurityContext(p)
return []*corev1.Pod{
// set at pod level
tweak(p, func(p *corev1.Pod) {
p.Spec.SecurityContext.RunAsNonRoot = pointer.BoolPtr(true)
p.Spec.Containers[0].SecurityContext.RunAsNonRoot = nil
p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = nil
}),
// set on all containers
tweak(p, func(p *corev1.Pod) {
p.Spec.SecurityContext.RunAsNonRoot = nil
p.Spec.Containers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(true)
p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(true)
}),
// set at pod level and containers
tweak(p, func(p *corev1.Pod) {
p.Spec.SecurityContext.RunAsNonRoot = pointer.BoolPtr(true)
p.Spec.Containers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(true)
p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(true)
}),
}
},
generateFail: func(p *corev1.Pod) []*corev1.Pod {
p = ensureSecurityContext(p)
return []*corev1.Pod{
// unset everywhere
tweak(p, func(p *corev1.Pod) {
p.Spec.SecurityContext.RunAsNonRoot = nil
p.Spec.Containers[0].SecurityContext.RunAsNonRoot = nil
p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = nil
}),
// explicit false on pod
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.RunAsNonRoot = pointer.BoolPtr(false) }),
// explicit false on containers
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(false) }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(false) }),
// nil security contexts
tweak(p, func(p *corev1.Pod) {
p.Spec.SecurityContext = nil
p.Spec.Containers[0].SecurityContext = nil
p.Spec.InitContainers[0].SecurityContext = nil
}),
}
},
}
registerFixtureGenerator(
fixtureKey{level: api.LevelRestricted, version: api.MajorMinorVersion(1, 0), check: "runAsNonRoot"},
fixtureData_1_0,
)
}

View File

@ -0,0 +1,98 @@
/*
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 test
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/pod-security-admission/api"
)
/*
TODO: include field paths in reflect-based unit test
podFields: []string{
`spec.securityContext.seLinuxOptions.type`,
`spec.securityContext.seLinuxOptions.user`,
`spec.securityContext.seLinuxOptions.role`,
},
containerFields: []string{
`securityContext.seLinuxOptions.type`,
`securityContext.seLinuxOptions.user`,
`securityContext.seLinuxOptions.role`,
},
*/
func init() {
fixtureData_1_0 := fixtureGenerator{
expectErrorSubstring: "seLinuxOptions",
generatePass: func(p *corev1.Pod) []*corev1.Pod {
p = ensureSELinuxOptions(p)
return []*corev1.Pod{
// security context with no seLinuxOptions
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions = nil }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions = nil }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions = nil }),
// seLinuxOptions with type=""
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "" }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "" }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "" }),
// seLinuxOptions with type="container_t"
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "container_t" }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "container_t" }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "container_t" }),
// seLinuxOptions with type="container_init_t"
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "container_init_t" }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "container_init_t" }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "container_init_t" }),
// seLinuxOptions with type="container_kvm_t"
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "container_kvm_t" }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "container_kvm_t" }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "container_kvm_t" }),
// seLinuxOptions with level=""
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Level = "" }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Level = "" }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Level = "" }),
// seLinuxOptions with arbitrary level=""
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Level = "somevalue" }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Level = "somevalue" }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Level = "somevalue" }),
}
},
generateFail: func(p *corev1.Pod) []*corev1.Pod {
p = ensureSELinuxOptions(p)
return []*corev1.Pod{
// seLinuxOptions with out of bounds type
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "somevalue" }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "somevalue" }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "somevalue" }),
// seLinuxOptions with out of bounds user
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.User = "somevalue" }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.User = "somevalue" }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.User = "somevalue" }),
// seLinuxOptions with out of bounds role
tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Role = "somevalue" }),
tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Role = "somevalue" }),
tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Role = "somevalue" }),
}
},
}
registerFixtureGenerator(
fixtureKey{level: api.LevelBaseline, version: api.MajorMinorVersion(1, 0), check: "selinux"},
fixtureData_1_0,
)
}

View File

@ -0,0 +1,155 @@
/*
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 test
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/pod-security-admission/api"
"k8s.io/pod-security-admission/policy"
"sigs.k8s.io/yaml"
)
const updateEnvVar = "UPDATE_POD_SECURITY_FIXTURE_DATA"
// TestFixtures ensures fixtures are registered for every check,
// and that in-memory fixtures match serialized fixtures in testdata.
// When adding new versions or checks, serialized fixtures can be updated by running:
//
// UPDATE_POD_SECURITY_FIXTURE_DATA=true go test k8s.io/pod-security-admission/test
func TestFixtures(t *testing.T) {
expectedFiles := sets.NewString("testdata/README.md")
defaultChecks := policy.DefaultChecks()
for _, level := range []api.Level{api.LevelBaseline, api.LevelRestricted} {
// TODO: derive from registered levels
for version := 0; version <= 22; version++ {
passDir := filepath.Join("testdata", string(level), fmt.Sprintf("v1.%d", version), "pass")
failDir := filepath.Join("testdata", string(level), fmt.Sprintf("v1.%d", version), "fail")
// render the minimal valid pod fixture
validPod, err := getMinimalValidPod(level, api.MajorMinorVersion(1, version))
if err != nil {
t.Fatal(err)
}
expectedFiles.Insert(testFixtureFile(t, passDir, "base", validPod))
// render check-specific fixtures
checkIDs, err := checksForLevelAndVersion(defaultChecks, level, api.MajorMinorVersion(1, version))
if err != nil {
t.Fatal(err)
}
if len(checkIDs) == 0 {
t.Fatal(fmt.Errorf("no checks registered for %s/1.%d", level, version))
}
for _, checkID := range checkIDs {
checkData, err := getFixtures(fixtureKey{level: level, version: api.MajorMinorVersion(1, version), check: checkID})
if err != nil {
t.Fatal(err)
}
for i, pod := range checkData.pass {
expectedFiles.Insert(testFixtureFile(t, passDir, fmt.Sprintf("%s%d", strings.ToLower(checkID), i), pod))
}
for i, pod := range checkData.fail {
expectedFiles.Insert(testFixtureFile(t, failDir, fmt.Sprintf("%s%d", strings.ToLower(checkID), i), pod))
}
}
}
}
actualFileList := []string{}
err := filepath.Walk("testdata", func(path string, f os.FileInfo, err error) error {
if !f.IsDir() {
actualFileList = append(actualFileList, path)
}
return nil
})
if err != nil {
t.Fatal(err)
}
actualFiles := sets.NewString(actualFileList...)
if missingFiles := expectedFiles.Difference(actualFiles); len(missingFiles) > 0 {
t.Errorf("unexpected missing fixtures:\n%s", strings.Join(missingFiles.List(), "\n"))
}
if extraFiles := actualFiles.Difference(expectedFiles); len(extraFiles) > 0 {
t.Errorf("unexpected extra fixtures:\n%s", strings.Join(extraFiles.List(), "\n"))
if os.Getenv(updateEnvVar) == "true" {
for extra := range extraFiles {
os.Remove(extra)
}
t.Logf("Removed extra fixture files")
t.Logf("Verify the diff, commit changes, and rerun the tests")
} else {
t.Logf("If the files are expected to be removed, re-run with %s=true to drop extra fixture files", updateEnvVar)
}
}
}
func testFixtureFile(t *testing.T, dir, name string, pod *corev1.Pod) string {
filename := filepath.Join(dir, name+".yaml")
pod = pod.DeepCopy()
pod.Name = name
expectedYAML, _ := ioutil.ReadFile(filename)
jsonData, err := runtime.Encode(scheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion), pod)
if err != nil {
t.Fatal(err)
}
yamlData, err := yaml.JSONToYAML(jsonData)
if err != nil {
t.Fatal(err)
}
// clean up noise in fixtures
yamlData = []byte(strings.ReplaceAll(string(yamlData), " creationTimestamp: null\n", ""))
yamlData = []byte(strings.ReplaceAll(string(yamlData), " resources: {}\n", ""))
yamlData = []byte(strings.ReplaceAll(string(yamlData), "status: {}\n", ""))
if string(yamlData) != string(expectedYAML) {
t.Errorf("fixture data does not match the test fixture in %s", filename)
if os.Getenv(updateEnvVar) == "true" {
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filename, []byte(yamlData), os.FileMode(0755)); err == nil {
t.Logf("Updated data in %s", filename)
t.Logf("Verify the diff, commit changes, and rerun the tests")
} else {
t.Logf("Could not update data in %s: %v", filename, err)
}
} else {
t.Logf("Diff between generated data and fixture data in %s:\n-------------\n%s", filename, diff.StringDiff(string(yamlData), string(expectedYAML)))
t.Logf("If the change is expected, re-run with %s=true to update the fixtures", updateEnvVar)
}
}
return filename
}

View File

@ -0,0 +1,67 @@
/*
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 test
import (
corev1 "k8s.io/api/core/v1"
)
// tweak makes a copy of in, passes it to f(), and returns the result.
// the input is not modified.
func tweak(in *corev1.Pod, f func(copy *corev1.Pod)) *corev1.Pod {
out := in.DeepCopy()
f(out)
return out
}
// ensureSecurityContext ensures the pod and all initContainers and containers have a non-nil security context.
func ensureSecurityContext(p *corev1.Pod) *corev1.Pod {
p = p.DeepCopy()
if p.Spec.SecurityContext == nil {
p.Spec.SecurityContext = &corev1.PodSecurityContext{}
}
for i := range p.Spec.Containers {
if p.Spec.Containers[i].SecurityContext == nil {
p.Spec.Containers[i].SecurityContext = &corev1.SecurityContext{}
}
}
for i := range p.Spec.InitContainers {
if p.Spec.InitContainers[i].SecurityContext == nil {
p.Spec.InitContainers[i].SecurityContext = &corev1.SecurityContext{}
}
}
return p
}
// ensureSELinuxOptions ensures the pod and all initContainers and containers have a non-nil seLinuxOptions.
func ensureSELinuxOptions(p *corev1.Pod) *corev1.Pod {
p = ensureSecurityContext(p)
if p.Spec.SecurityContext.SELinuxOptions == nil {
p.Spec.SecurityContext.SELinuxOptions = &corev1.SELinuxOptions{}
}
for i := range p.Spec.Containers {
if p.Spec.Containers[i].SecurityContext.SELinuxOptions == nil {
p.Spec.Containers[i].SecurityContext.SELinuxOptions = &corev1.SELinuxOptions{}
}
}
for i := range p.Spec.InitContainers {
if p.Spec.InitContainers[i].SecurityContext.SELinuxOptions == nil {
p.Spec.InitContainers[i].SecurityContext.SELinuxOptions = &corev1.SELinuxOptions{}
}
}
return p
}

View File

@ -0,0 +1,300 @@
/*
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 test
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"testing"
appsv1 "k8s.io/api/apps/v1"
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"
"k8s.io/client-go/rest"
"k8s.io/pod-security-admission/api"
"k8s.io/pod-security-admission/policy"
)
// Options hold configuration for running integration tests against an existing server.
type Options struct {
// ClientConfig is a client configuration with sufficient permission to create, update, and delete
// namespaces, pods, and pod-template-containing objects.
// Required.
ClientConfig *rest.Config
// CreateNamespace is an optional stub for creating a namespace with the given name and labels.
// Returning an error fails the test.
// If nil, DefaultCreateNamespace is used.
CreateNamespace func(client kubernetes.Interface, name string, labels map[string]string) (*corev1.Namespace, error)
// These are the check ids/starting versions to exercise.
// If unset, policy.DefaultChecks() are used.
Checks []policy.Check
// ExemptClient is an optional client interface to exercise behavior of an exempt client.
ExemptClient kubernetes.Interface
// ExemptNamespaces are optional namespaces not expected to have PodSecurity controls enforced.
ExemptNamespaces []string
// ExemptRuntimeClasses are optional runtimeclasses not expected to have PodSecurity controls enforced.
ExemptRuntimeClasses []string
}
func toJSON(pod *corev1.Pod) string {
data, _ := json.Marshal(pod)
return string(data)
}
// checksForLevelAndVersion returns the set of check IDs that apply when evaluating the given level and version.
// checks are assumed to be well-formed and valid to pass to policy.NewEvaluator().
// level must be api.LevelRestricted or api.LevelBaseline
func checksForLevelAndVersion(checks []policy.Check, level api.Level, version api.Version) ([]string, error) {
retval := []string{}
for _, check := range checks {
if !version.Older(check.Versions[0].MinimumVersion) && (level == check.Level || level == api.LevelRestricted) {
retval = append(retval, check.ID)
}
}
return retval, nil
}
// maxMinorVersionToTest returns the maximum minor version to exercise for a given set of checks.
// checks are assumed to be well-formed and valid to pass to policy.NewEvaluator().
func maxMinorVersionToTest(checks []policy.Check) (int, error) {
// start with the release under development (1.22 at time of writing).
// this can be incremented to the current version whenever is convenient.
maxTestMinor := 22
for _, check := range checks {
lastCheckVersion := check.Versions[len(check.Versions)-1].MinimumVersion
if lastCheckVersion.Major() != 1 {
return 0, fmt.Errorf("expected major version 1, got %d", lastCheckVersion.Major())
}
if lastCheckVersion.Minor() > maxTestMinor {
maxTestMinor = lastCheckVersion.Minor()
}
}
return maxTestMinor, nil
}
type testWarningHandler struct {
lock sync.Mutex
warnings []string
}
func (t *testWarningHandler) HandleWarningHeader(code int, agent string, warning string) {
t.lock.Lock()
defer t.lock.Unlock()
t.warnings = append(t.warnings, warning)
}
func (t *testWarningHandler) FlushWarnings() []string {
t.lock.Lock()
defer t.lock.Unlock()
warnings := t.warnings
t.warnings = nil
return warnings
}
// and ensures pod fixtures expected to pass and fail against that level/version work as expected.
func Run(t *testing.T, opts Options) {
warningHandler := &testWarningHandler{}
configCopy := rest.CopyConfig(opts.ClientConfig)
configCopy.WarningHandler = warningHandler
client, err := kubernetes.NewForConfig(configCopy)
if err != nil {
t.Fatalf("error creating client: %v", err)
}
if opts.CreateNamespace == nil {
opts.CreateNamespace = DefaultCreateNamespace
}
if len(opts.Checks) == 0 {
opts.Checks = policy.DefaultChecks()
}
_, err = policy.NewEvaluator(opts.Checks)
if err != nil {
t.Fatalf("invalid checks: %v", err)
}
maxMinor, err := maxMinorVersionToTest(opts.Checks)
if err != nil {
t.Fatalf("invalid checks: %v", err)
}
for _, level := range []api.Level{api.LevelBaseline, api.LevelRestricted} {
for minor := 0; minor <= maxMinor; minor++ {
version := api.MajorMinorVersion(1, minor)
// create test name
ns := fmt.Sprintf("podsecurity-%s-1-%d", level, minor)
// create namespace
_, err := opts.CreateNamespace(client, ns, map[string]string{
api.EnforceLevelLabel: string(level),
api.EnforceVersionLabel: fmt.Sprintf("v1.%d", minor),
api.WarnLevelLabel: string(level),
api.WarnVersionLabel: fmt.Sprintf("v1.%d", minor),
})
if err != nil {
t.Errorf("failed creating namespace %s: %v", ns, err)
continue
}
t.Cleanup(func() {
client.CoreV1().Namespaces().Delete(context.Background(), ns, metav1.DeleteOptions{})
})
// create service account (to allow pod to pass serviceaccount admission)
sa, err := client.CoreV1().ServiceAccounts(ns).Create(
context.Background(),
&corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "default"}},
metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
t.Errorf("failed creating serviceaccount %s: %v", ns, err)
continue
}
t.Cleanup(func() {
client.CoreV1().ServiceAccounts(ns).Delete(context.Background(), sa.Name, metav1.DeleteOptions{})
})
// create pod
createPod := func(t *testing.T, i int, pod *corev1.Pod, expectSuccess bool, expectErrorSubstring string) {
t.Helper()
// avoid mutating original pod fixture
pod = pod.DeepCopy()
// assign pod name and serviceaccount
pod.Name = "test"
pod.Spec.ServiceAccountName = "default"
// dry-run create
_, err := client.CoreV1().Pods(ns).Create(context.Background(), pod, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}})
if !expectSuccess {
if err == nil {
t.Errorf("%d: expected error creating %s, got none", i, toJSON(pod))
return
}
if strings.Contains(err.Error(), policy.UnknownForbiddenReason) {
t.Errorf("%d: unexpected unknown forbidden reason creating %s: %v", i, toJSON(pod), err)
return
}
if !strings.Contains(err.Error(), expectErrorSubstring) {
t.Errorf("%d: expected error with substring %q, got %v", i, expectErrorSubstring, err)
return
}
}
if expectSuccess && err != nil {
t.Errorf("%d: unexpected error creating %s: %v", i, toJSON(pod), err)
}
}
// create controller
createController := func(t *testing.T, i int, pod *corev1.Pod, expectSuccess bool, expectErrorSubstring string) {
t.Helper()
// avoid mutating original pod fixture
pod = pod.DeepCopy()
if pod.Labels == nil {
pod.Labels = map[string]string{}
}
pod.Labels["test"] = "true"
warningHandler.FlushWarnings()
// dry-run create
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"test": "true"}},
Template: corev1.PodTemplateSpec{
ObjectMeta: pod.ObjectMeta,
Spec: pod.Spec,
},
},
}
_, err := client.AppsV1().Deployments(ns).Create(context.Background(), deployment, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}})
if err != nil {
t.Errorf("%d: unexpected error creating controller with %s: %v", i, toJSON(pod), err)
return
}
warningText := strings.Join(warningHandler.FlushWarnings(), "; ")
if !expectSuccess {
if len(warningText) == 0 {
t.Errorf("%d: expected warnings creating %s, got none", i, toJSON(pod))
return
}
if strings.Contains(warningText, policy.UnknownForbiddenReason) {
t.Errorf("%d: unexpected unknown forbidden reason creating %s: %v", i, toJSON(pod), warningText)
return
}
if !strings.Contains(warningText, expectErrorSubstring) {
t.Errorf("%d: expected warning with substring %q, got %v", i, expectErrorSubstring, warningText)
return
}
}
if expectSuccess && len(warningText) > 0 {
t.Errorf("%d: unexpected warning creating %s: %v", i, toJSON(pod), warningText)
}
}
minimalValidPod, err := getMinimalValidPod(level, version)
if err != nil {
t.Fatal(err)
}
t.Run(ns+"_pass_base", func(t *testing.T) {
createPod(t, 0, minimalValidPod.DeepCopy(), true, "")
createController(t, 0, minimalValidPod.DeepCopy(), true, "")
})
checkIDs, err := checksForLevelAndVersion(opts.Checks, level, version)
if err != nil {
t.Fatal(err)
}
if len(checkIDs) == 0 {
t.Fatal(fmt.Errorf("no checks registered for %s/1.%d", level, minor))
}
for _, checkID := range checkIDs {
checkData, err := getFixtures(fixtureKey{level: level, version: version, check: checkID})
if err != nil {
t.Fatal(err)
}
t.Run(ns+"_pass_"+checkID, func(t *testing.T) {
for i, pod := range checkData.pass {
createPod(t, i, pod, true, "")
createController(t, i, pod, true, "")
}
})
t.Run(ns+"_fail_"+checkID, func(t *testing.T) {
for i, pod := range checkData.fail {
createPod(t, i, pod, false, checkData.expectErrorSubstring)
createController(t, i, pod, false, checkData.expectErrorSubstring)
}
})
}
}
}
}
func DefaultCreateNamespace(client kubernetes.Interface, name string, labels map[string]string) (*corev1.Namespace, error) {
return client.CoreV1().Namespaces().Create(
context.Background(),
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: name, Labels: labels},
},
metav1.CreateOptions{},
)
}

View File

@ -0,0 +1 @@
The fixtures in this folder are generated by TestFixtures.

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities0
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities:
add:
- NET_RAW
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities: {}
securityContext: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities1
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities:
add:
- NET_RAW
securityContext: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities2
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities:
add:
- chown
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities: {}
securityContext: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities3
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities:
add:
- chown
securityContext: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities4
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities:
add:
- bogus
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities: {}
securityContext: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities5
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities:
add:
- bogus
securityContext: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities6
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities:
add:
- CAP_CHOWN
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities: {}
securityContext: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities7
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities:
add:
- CAP_CHOWN
securityContext: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux0
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions:
type: somevalue

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux1
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions:
type: somevalue
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux2
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions:
type: somevalue
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux3
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions:
user: somevalue

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux4
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions:
user: somevalue
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux5
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions:
user: somevalue
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux6
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions:
role: somevalue

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux7
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions:
role: somevalue
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux8
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions:
role: somevalue
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,30 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities0
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities:
add:
- AUDIT_WRITE
- CHOWN
- DAC_OVERRIDE
- FOWNER
- FSETID
- KILL
- MKNOD
- NET_BIND_SERVICE
- SETFCAP
- SETGID
- SETPCAP
- SETUID
- SYS_CHROOT
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities: {}
securityContext: {}

View File

@ -0,0 +1,30 @@
apiVersion: v1
kind: Pod
metadata:
name: addcapabilities1
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
capabilities: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
capabilities:
add:
- AUDIT_WRITE
- CHOWN
- DAC_OVERRIDE
- FOWNER
- FSETID
- KILL
- MKNOD
- NET_BIND_SERVICE
- SETFCAP
- SETGID
- SETPCAP
- SETUID
- SYS_CHROOT
securityContext: {}

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: base
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1

View File

@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux0
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext: {}

View File

@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux1
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux10
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions:
type: container_init_t
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux11
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions:
type: container_init_t
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux12
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions:
type: container_kvm_t

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux13
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions:
type: container_kvm_t
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux14
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions:
type: container_kvm_t
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux15
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux16
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux17
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux18
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions:
level: somevalue

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux19
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions:
level: somevalue
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux2
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux20
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions:
level: somevalue
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux3
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux4
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux5
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux6
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions:
type: container_t

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux7
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions:
type: container_t
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions: {}
securityContext:
seLinuxOptions: {}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: selinux8
spec:
containers:
- image: k8s.gcr.io/pause
name: container1
securityContext:
seLinuxOptions: {}
initContainers:
- image: k8s.gcr.io/pause
name: initcontainer1
securityContext:
seLinuxOptions:
type: container_t
securityContext:
seLinuxOptions: {}

Some files were not shown because too many files have changed in this diff Show More