mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 02:33:34 +00:00
rebase: bump k8s.io/kubernetes from 1.26.2 to 1.27.2
Bumps [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) from 1.26.2 to 1.27.2. - [Release notes](https://github.com/kubernetes/kubernetes/releases) - [Commits](https://github.com/kubernetes/kubernetes/compare/v1.26.2...v1.27.2) --- updated-dependencies: - dependency-name: k8s.io/kubernetes dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
mergify[bot]
parent
0e79135419
commit
07b05616a0
111
vendor/k8s.io/apiserver/pkg/admission/cel/metrics.go
generated
vendored
Normal file
111
vendor/k8s.io/apiserver/pkg/admission/cel/metrics.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
const (
|
||||
metricsNamespace = "apiserver"
|
||||
metricsSubsystem = "validating_admission_policy"
|
||||
)
|
||||
|
||||
var (
|
||||
// Metrics provides access to validation admission metrics.
|
||||
Metrics = newValidationAdmissionMetrics()
|
||||
)
|
||||
|
||||
// ValidatingAdmissionPolicyMetrics aggregates Prometheus metrics related to validation admission control.
|
||||
type ValidatingAdmissionPolicyMetrics struct {
|
||||
policyCheck *metrics.CounterVec
|
||||
policyDefinition *metrics.CounterVec
|
||||
policyLatency *metrics.HistogramVec
|
||||
}
|
||||
|
||||
func newValidationAdmissionMetrics() *ValidatingAdmissionPolicyMetrics {
|
||||
check := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: metricsSubsystem,
|
||||
Name: "check_total",
|
||||
Help: "Validation admission policy check total, labeled by policy and further identified by binding, enforcement action taken, and state.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"policy", "policy_binding", "enforcement_action", "state"},
|
||||
)
|
||||
definition := metrics.NewCounterVec(&metrics.CounterOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: metricsSubsystem,
|
||||
Name: "definition_total",
|
||||
Help: "Validation admission policy count total, labeled by state and enforcement action.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"state", "enforcement_action"},
|
||||
)
|
||||
latency := metrics.NewHistogramVec(&metrics.HistogramOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: metricsSubsystem,
|
||||
Name: "check_duration_seconds",
|
||||
Help: "Validation admission latency for individual validation expressions in seconds, labeled by policy and further including binding, state and enforcement action taken.",
|
||||
// the bucket distribution here is based oo the benchmark suite at
|
||||
// github.com/DangerOnTheRanger/cel-benchmark performed on 16-core Intel Xeon
|
||||
// the lowest bucket was based around the 180ns/op figure for BenchmarkAccess,
|
||||
// plus some additional leeway to account for the apiserver doing other things
|
||||
// the largest bucket was chosen based on the fact that benchmarks indicate the
|
||||
// same Xeon running a CEL expression close to the estimated cost limit takes
|
||||
// around 760ms, so that bucket should only ever have the slowest CEL expressions
|
||||
// in it
|
||||
Buckets: []float64{0.0000005, 0.001, 0.01, 0.1, 1.0},
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"policy", "policy_binding", "enforcement_action", "state"},
|
||||
)
|
||||
|
||||
legacyregistry.MustRegister(check)
|
||||
legacyregistry.MustRegister(definition)
|
||||
legacyregistry.MustRegister(latency)
|
||||
return &ValidatingAdmissionPolicyMetrics{policyCheck: check, policyDefinition: definition, policyLatency: latency}
|
||||
}
|
||||
|
||||
// Reset resets all validation admission-related Prometheus metrics.
|
||||
func (m *ValidatingAdmissionPolicyMetrics) Reset() {
|
||||
m.policyCheck.Reset()
|
||||
m.policyDefinition.Reset()
|
||||
m.policyLatency.Reset()
|
||||
}
|
||||
|
||||
// ObserveDefinition observes a policy definition.
|
||||
func (m *ValidatingAdmissionPolicyMetrics) ObserveDefinition(ctx context.Context, state, enforcementAction string) {
|
||||
m.policyDefinition.WithContext(ctx).WithLabelValues(state, enforcementAction).Inc()
|
||||
}
|
||||
|
||||
// ObserveAdmissionWithError observes a policy validation error that was ignored due to failure policy.
|
||||
func (m *ValidatingAdmissionPolicyMetrics) ObserveAdmissionWithError(ctx context.Context, elapsed time.Duration, policy, binding, state string) {
|
||||
m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, "allow", state).Inc()
|
||||
m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, "allow", state).Observe(elapsed.Seconds())
|
||||
}
|
||||
|
||||
// ObserveRejection observes a policy validation error that was at least one of the reasons for a deny.
|
||||
func (m *ValidatingAdmissionPolicyMetrics) ObserveRejection(ctx context.Context, elapsed time.Duration, policy, binding, state string) {
|
||||
m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, "deny", state).Inc()
|
||||
m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, "deny", state).Observe(elapsed.Seconds())
|
||||
}
|
233
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
Normal file
233
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
Copyright 2015 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 lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "NamespaceLifecycle"
|
||||
// how long a namespace stays in the force live lookup cache before expiration.
|
||||
forceLiveLookupTTL = 30 * time.Second
|
||||
// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
|
||||
// this accomplishes two things:
|
||||
// 1. It allows a watch-fed cache time to observe a namespace creation event
|
||||
// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
|
||||
// so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
|
||||
missingNamespaceWait = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic))
|
||||
})
|
||||
}
|
||||
|
||||
// Lifecycle is an implementation of admission.Interface.
|
||||
// It enforces life-cycle constraints around a Namespace depending on its Phase
|
||||
type Lifecycle struct {
|
||||
*admission.Handler
|
||||
client kubernetes.Interface
|
||||
immortalNamespaces sets.String
|
||||
namespaceLister corelisters.NamespaceLister
|
||||
// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
|
||||
// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
|
||||
forceLiveLookupCache *utilcache.LRUExpireCache
|
||||
}
|
||||
|
||||
var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
|
||||
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (l *Lifecycle) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
// prevent deletion of immortal namespaces
|
||||
if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
|
||||
}
|
||||
|
||||
// always allow non-namespaced resources
|
||||
if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
|
||||
// if a namespace is deleted, we want to prevent all further creates into it
|
||||
// while it is undergoing termination. to reduce incidences where the cache
|
||||
// is slow to update, we add the namespace into a force live lookup list to ensure
|
||||
// we are not looking at stale state.
|
||||
if a.GetOperation() == admission.Delete {
|
||||
l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
|
||||
}
|
||||
// allow all operations to namespaces
|
||||
return nil
|
||||
}
|
||||
|
||||
// always allow deletion of other resources
|
||||
if a.GetOperation() == admission.Delete {
|
||||
return nil
|
||||
}
|
||||
|
||||
// always allow access review checks. Returning status about the namespace would be leaking information
|
||||
if isAccessReview(a) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we need to wait for our caches to warm
|
||||
if !l.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
var (
|
||||
exists bool
|
||||
err error
|
||||
)
|
||||
|
||||
namespace, err := l.namespaceLister.Get(a.GetNamespace())
|
||||
if err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
} else {
|
||||
exists = true
|
||||
}
|
||||
|
||||
if !exists && a.GetOperation() == admission.Create {
|
||||
// give the cache time to observe the namespace before rejecting a create.
|
||||
// this helps when creating a namespace and immediately creating objects within it.
|
||||
time.Sleep(missingNamespaceWait)
|
||||
namespace, err = l.namespaceLister.Get(a.GetNamespace())
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
// no-op
|
||||
case err != nil:
|
||||
return errors.NewInternalError(err)
|
||||
default:
|
||||
exists = true
|
||||
}
|
||||
if exists {
|
||||
klog.V(4).InfoS("Namespace existed in cache after waiting", "namespace", klog.KRef("", a.GetNamespace()))
|
||||
}
|
||||
}
|
||||
|
||||
// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
|
||||
forceLiveLookup := false
|
||||
if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
|
||||
// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
|
||||
forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
|
||||
}
|
||||
|
||||
// refuse to operate on non-existent namespaces
|
||||
if !exists || forceLiveLookup {
|
||||
// as a last resort, make a call directly to storage
|
||||
namespace, err = l.client.CoreV1().Namespaces().Get(context.TODO(), a.GetNamespace(), metav1.GetOptions{})
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
return err
|
||||
case err != nil:
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
klog.V(4).InfoS("Found namespace via storage lookup", "namespace", klog.KRef("", a.GetNamespace()))
|
||||
}
|
||||
|
||||
// ensure that we're not trying to create objects in terminating namespaces
|
||||
if a.GetOperation() == admission.Create {
|
||||
if namespace.Status.Phase != v1.NamespaceTerminating {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
|
||||
if apierr, ok := err.(*errors.StatusError); ok {
|
||||
apierr.ErrStatus.Details.Causes = append(apierr.ErrStatus.Details.Causes, metav1.StatusCause{
|
||||
Type: v1.NamespaceTerminatingCause,
|
||||
Message: fmt.Sprintf("namespace %s is being terminated", a.GetNamespace()),
|
||||
Field: "metadata.namespace",
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewLifecycle creates a new namespace Lifecycle admission control handler
|
||||
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
|
||||
return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
|
||||
}
|
||||
|
||||
func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
|
||||
forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
|
||||
return &Lifecycle{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
immortalNamespaces: immortalNamespaces,
|
||||
forceLiveLookupCache: forceLiveLookupCache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
|
||||
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().V1().Namespaces()
|
||||
l.namespaceLister = namespaceInformer.Lister()
|
||||
l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
|
||||
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
|
||||
l.client = client
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (l *Lifecycle) ValidateInitialization() error {
|
||||
if l.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if l.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// accessReviewResources are resources which give a view into permissions in a namespace. Users must be allowed to create these
|
||||
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
|
||||
var accessReviewResources = map[schema.GroupResource]bool{
|
||||
{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: true,
|
||||
}
|
||||
|
||||
func isAccessReview(a admission.Attributes) bool {
|
||||
return accessReviewResources[a.GetResource().GroupResource()]
|
||||
}
|
10
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/OWNERS
generated
vendored
Normal file
10
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/OWNERS
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- jpbetz
|
||||
- cici37
|
||||
- alexzielenski
|
||||
reviewers:
|
||||
- jpbetz
|
||||
- cici37
|
||||
- alexzielenski
|
189
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission.go
generated
vendored
Normal file
189
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission.go
generated
vendored
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
Copyright 2022 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/component-base/featuregate"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Plugin Definition
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Definition for CEL admission plugin. This is the entry point into the
|
||||
// CEL admission control system.
|
||||
//
|
||||
// Each plugin is asked to validate every object update.
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "ValidatingAdmissionPolicy"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin()
|
||||
})
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Plugin Initialization & Dependency Injection
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type celAdmissionPlugin struct {
|
||||
*admission.Handler
|
||||
evaluator CELPolicyEvaluator
|
||||
|
||||
inspectedFeatureGates bool
|
||||
enabled bool
|
||||
|
||||
// Injected Dependencies
|
||||
informerFactory informers.SharedInformerFactory
|
||||
client kubernetes.Interface
|
||||
restMapper meta.RESTMapper
|
||||
dynamicClient dynamic.Interface
|
||||
stopCh <-chan struct{}
|
||||
}
|
||||
|
||||
var _ initializer.WantsExternalKubeInformerFactory = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsExternalKubeClientSet = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsRESTMapper = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsDynamicClient = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsDrainedNotification = &celAdmissionPlugin{}
|
||||
|
||||
var _ admission.InitializationValidator = &celAdmissionPlugin{}
|
||||
var _ admission.ValidationInterface = &celAdmissionPlugin{}
|
||||
|
||||
func NewPlugin() (admission.Interface, error) {
|
||||
return &celAdmissionPlugin{
|
||||
Handler: admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
c.informerFactory = f
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) SetExternalKubeClientSet(client kubernetes.Interface) {
|
||||
c.client = client
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) SetRESTMapper(mapper meta.RESTMapper) {
|
||||
c.restMapper = mapper
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) SetDynamicClient(client dynamic.Interface) {
|
||||
c.dynamicClient = client
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) SetDrainedNotification(stopCh <-chan struct{}) {
|
||||
c.stopCh = stopCh
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
||||
if featureGates.Enabled(features.ValidatingAdmissionPolicy) {
|
||||
c.enabled = true
|
||||
}
|
||||
c.inspectedFeatureGates = true
|
||||
}
|
||||
|
||||
// ValidateInitialization - once clientset and informer factory are provided, creates and starts the admission controller
|
||||
func (c *celAdmissionPlugin) ValidateInitialization() error {
|
||||
if !c.inspectedFeatureGates {
|
||||
return fmt.Errorf("%s did not see feature gates", PluginName)
|
||||
}
|
||||
if !c.enabled {
|
||||
return nil
|
||||
}
|
||||
if c.informerFactory == nil {
|
||||
return errors.New("missing informer factory")
|
||||
}
|
||||
if c.client == nil {
|
||||
return errors.New("missing kubernetes client")
|
||||
}
|
||||
if c.restMapper == nil {
|
||||
return errors.New("missing rest mapper")
|
||||
}
|
||||
if c.dynamicClient == nil {
|
||||
return errors.New("missing dynamic client")
|
||||
}
|
||||
if c.stopCh == nil {
|
||||
return errors.New("missing stop channel")
|
||||
}
|
||||
c.evaluator = NewAdmissionController(c.informerFactory, c.client, c.restMapper, c.dynamicClient)
|
||||
if err := c.evaluator.ValidateInitialization(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.SetReadyFunc(c.evaluator.HasSynced)
|
||||
go c.evaluator.Run(c.stopCh)
|
||||
return nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// admission.ValidationInterface
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (c *celAdmissionPlugin) Handles(operation admission.Operation) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) Validate(
|
||||
ctx context.Context,
|
||||
a admission.Attributes,
|
||||
o admission.ObjectInterfaces,
|
||||
) (err error) {
|
||||
if !c.enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// isPolicyResource determines if an admission.Attributes object is describing
|
||||
// the admission of a ValidatingAdmissionPolicy or a ValidatingAdmissionPolicyBinding
|
||||
if isPolicyResource(a) {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
return c.evaluator.Validate(ctx, a, o)
|
||||
}
|
||||
|
||||
func isPolicyResource(attr admission.Attributes) bool {
|
||||
gvk := attr.GetResource()
|
||||
if gvk.Group == "admissionregistration.k8s.io" {
|
||||
if gvk.Resource == "validatingadmissionpolicies" || gvk.Resource == "validatingadmissionpolicybindings" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
231
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/compiler.go
generated
vendored
Normal file
231
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/compiler.go
generated
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
/*
|
||||
Copyright 2022 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
)
|
||||
|
||||
const (
|
||||
ObjectVarName = "object"
|
||||
OldObjectVarName = "oldObject"
|
||||
ParamsVarName = "params"
|
||||
RequestVarName = "request"
|
||||
|
||||
checkFrequency = 100
|
||||
)
|
||||
|
||||
type envs struct {
|
||||
noParams *cel.Env
|
||||
withParams *cel.Env
|
||||
}
|
||||
|
||||
var (
|
||||
initEnvsOnce sync.Once
|
||||
initEnvs *envs
|
||||
initEnvsErr error
|
||||
)
|
||||
|
||||
func getEnvs() (*envs, error) {
|
||||
initEnvsOnce.Do(func() {
|
||||
base, err := buildBaseEnv()
|
||||
if err != nil {
|
||||
initEnvsErr = err
|
||||
return
|
||||
}
|
||||
noParams, err := buildNoParamsEnv(base)
|
||||
if err != nil {
|
||||
initEnvsErr = err
|
||||
return
|
||||
}
|
||||
withParams, err := buildWithParamsEnv(noParams)
|
||||
if err != nil {
|
||||
initEnvsErr = err
|
||||
return
|
||||
}
|
||||
initEnvs = &envs{noParams: noParams, withParams: withParams}
|
||||
})
|
||||
return initEnvs, initEnvsErr
|
||||
}
|
||||
|
||||
// This is a similar code as in k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go
|
||||
// If any changes are made here, consider to make the same changes there as well.
|
||||
func buildBaseEnv() (*cel.Env, error) {
|
||||
var opts []cel.EnvOption
|
||||
opts = append(opts, cel.HomogeneousAggregateLiterals())
|
||||
// Validate function declarations once during base env initialization,
|
||||
// so they don't need to be evaluated each time a CEL rule is compiled.
|
||||
// This is a relatively expensive operation.
|
||||
opts = append(opts, cel.EagerlyValidateDeclarations(true), cel.DefaultUTCTimeZone(true))
|
||||
opts = append(opts, library.ExtensionLibs...)
|
||||
|
||||
return cel.NewEnv(opts...)
|
||||
}
|
||||
|
||||
func buildNoParamsEnv(baseEnv *cel.Env) (*cel.Env, error) {
|
||||
var propDecls []cel.EnvOption
|
||||
reg := apiservercel.NewRegistry(baseEnv)
|
||||
|
||||
requestType := buildRequestType()
|
||||
rt, err := apiservercel.NewRuleTypes(requestType.TypeName(), requestType, reg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rt == nil {
|
||||
return nil, nil
|
||||
}
|
||||
opts, err := rt.EnvOptions(baseEnv.TypeProvider())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
propDecls = append(propDecls, cel.Variable(ObjectVarName, cel.DynType))
|
||||
propDecls = append(propDecls, cel.Variable(OldObjectVarName, cel.DynType))
|
||||
propDecls = append(propDecls, cel.Variable(RequestVarName, requestType.CelType()))
|
||||
|
||||
opts = append(opts, propDecls...)
|
||||
env, err := baseEnv.Extend(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func buildWithParamsEnv(noParams *cel.Env) (*cel.Env, error) {
|
||||
return noParams.Extend(cel.Variable(ParamsVarName, cel.DynType))
|
||||
}
|
||||
|
||||
// buildRequestType generates a DeclType for AdmissionRequest. This may be replaced with a utility that
|
||||
// converts the native type definition to apiservercel.DeclType once such a utility becomes available.
|
||||
// The 'uid' field is omitted since it is not needed for in-process admission review.
|
||||
// The 'object' and 'oldObject' fields are omitted since they are exposed as root level CEL variables.
|
||||
func buildRequestType() *apiservercel.DeclType {
|
||||
field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
||||
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
||||
}
|
||||
fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
|
||||
result := make(map[string]*apiservercel.DeclField, len(fields))
|
||||
for _, f := range fields {
|
||||
result[f.Name] = f
|
||||
}
|
||||
return result
|
||||
}
|
||||
gvkType := apiservercel.NewObjectType("kubernetes.GroupVersionKind", fields(
|
||||
field("group", apiservercel.StringType, true),
|
||||
field("version", apiservercel.StringType, true),
|
||||
field("kind", apiservercel.StringType, true),
|
||||
))
|
||||
gvrType := apiservercel.NewObjectType("kubernetes.GroupVersionResource", fields(
|
||||
field("group", apiservercel.StringType, true),
|
||||
field("version", apiservercel.StringType, true),
|
||||
field("resource", apiservercel.StringType, true),
|
||||
))
|
||||
userInfoType := apiservercel.NewObjectType("kubernetes.UserInfo", fields(
|
||||
field("username", apiservercel.StringType, false),
|
||||
field("uid", apiservercel.StringType, false),
|
||||
field("groups", apiservercel.NewListType(apiservercel.StringType, -1), false),
|
||||
field("extra", apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1), false),
|
||||
))
|
||||
return apiservercel.NewObjectType("kubernetes.AdmissionRequest", fields(
|
||||
field("kind", gvkType, true),
|
||||
field("resource", gvrType, true),
|
||||
field("subResource", apiservercel.StringType, false),
|
||||
field("requestKind", gvkType, true),
|
||||
field("requestResource", gvrType, true),
|
||||
field("requestSubResource", apiservercel.StringType, false),
|
||||
field("name", apiservercel.StringType, true),
|
||||
field("namespace", apiservercel.StringType, false),
|
||||
field("operation", apiservercel.StringType, true),
|
||||
field("userInfo", userInfoType, true),
|
||||
field("dryRun", apiservercel.BoolType, false),
|
||||
field("options", apiservercel.DynType, false),
|
||||
))
|
||||
}
|
||||
|
||||
// CompilationResult represents a compiled ValidatingAdmissionPolicy validation expression.
|
||||
type CompilationResult struct {
|
||||
Program cel.Program
|
||||
Error *apiservercel.Error
|
||||
}
|
||||
|
||||
// CompileValidatingPolicyExpression returns a compiled vaalidating policy CEL expression.
|
||||
func CompileValidatingPolicyExpression(validationExpression string, hasParams bool) CompilationResult {
|
||||
var env *cel.Env
|
||||
envs, err := getEnvs()
|
||||
if err != nil {
|
||||
return CompilationResult{
|
||||
Error: &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInternal,
|
||||
Detail: "compiler initialization failed: " + err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
if hasParams {
|
||||
env = envs.withParams
|
||||
} else {
|
||||
env = envs.noParams
|
||||
}
|
||||
|
||||
ast, issues := env.Compile(validationExpression)
|
||||
if issues != nil {
|
||||
return CompilationResult{
|
||||
Error: &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInvalid,
|
||||
Detail: "compilation failed: " + issues.String(),
|
||||
},
|
||||
}
|
||||
}
|
||||
if ast.OutputType() != cel.BoolType {
|
||||
return CompilationResult{
|
||||
Error: &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInvalid,
|
||||
Detail: "cel expression must evaluate to a bool",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
_, err = cel.AstToCheckedExpr(ast)
|
||||
if err != nil {
|
||||
// should be impossible since env.Compile returned no issues
|
||||
return CompilationResult{
|
||||
Error: &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInternal,
|
||||
Detail: "unexpected compilation error: " + err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
prog, err := env.Program(ast,
|
||||
cel.EvalOptions(cel.OptOptimize),
|
||||
cel.OptimizeRegex(library.ExtensionLibRegexOptimizations...),
|
||||
cel.InterruptCheckFrequency(checkFrequency),
|
||||
)
|
||||
if err != nil {
|
||||
return CompilationResult{
|
||||
Error: &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInvalid,
|
||||
Detail: "program instantiation failed: " + err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return CompilationResult{
|
||||
Program: prog,
|
||||
}
|
||||
}
|
406
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go
generated
vendored
Normal file
406
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go
generated
vendored
Normal file
@ -0,0 +1,406 @@
|
||||
/*
|
||||
Copyright 2022 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
var _ CELPolicyEvaluator = &celAdmissionController{}
|
||||
|
||||
// celAdmissionController is the top-level controller for admission control using CEL
|
||||
// it is responsible for watching policy definitions, bindings, and config param CRDs
|
||||
type celAdmissionController struct {
|
||||
// Context under which the controller runs
|
||||
runningContext context.Context
|
||||
|
||||
policyDefinitionsController generic.Controller[*v1alpha1.ValidatingAdmissionPolicy]
|
||||
policyBindingController generic.Controller[*v1alpha1.ValidatingAdmissionPolicyBinding]
|
||||
|
||||
// dynamicclient used to create informers to watch the param crd types
|
||||
dynamicClient dynamic.Interface
|
||||
restMapper meta.RESTMapper
|
||||
|
||||
// Provided to the policy's Compile function as an injected dependency to
|
||||
// assist with compiling its expressions to CEL
|
||||
validatorCompiler ValidatorCompiler
|
||||
|
||||
// Lock which protects:
|
||||
// - definitionInfo
|
||||
// - bindingInfos
|
||||
// - paramCRDControllers
|
||||
// - definitionsToBindings
|
||||
// All other fields should be assumed constant
|
||||
mutex sync.RWMutex
|
||||
|
||||
// controller and metadata
|
||||
paramsCRDControllers map[v1alpha1.ParamKind]*paramInfo
|
||||
|
||||
// Index for each definition namespace/name, contains all binding
|
||||
// namespace/names known to exist for that definition
|
||||
definitionInfo map[namespacedName]*definitionInfo
|
||||
|
||||
// Index for each bindings namespace/name. Contains compiled templates
|
||||
// for the binding depending on the policy/param combination.
|
||||
bindingInfos map[namespacedName]*bindingInfo
|
||||
|
||||
// Map from namespace/name of a definition to a set of namespace/name
|
||||
// of bindings which depend on it.
|
||||
// All keys must have at least one dependent binding
|
||||
// All binding names MUST exist as a key bindingInfos
|
||||
definitionsToBindings map[namespacedName]sets.Set[namespacedName]
|
||||
}
|
||||
|
||||
// namespaceName is used as a key in definitionInfo and bindingInfos
|
||||
type namespacedName struct {
|
||||
namespace, name string
|
||||
}
|
||||
|
||||
type definitionInfo struct {
|
||||
// Error about the state of the definition's configuration and the cluster
|
||||
// preventing its enforcement or compilation.
|
||||
// Reset every reconciliation
|
||||
configurationError error
|
||||
|
||||
// Last value seen by this controller to be used in policy enforcement
|
||||
// May not be nil
|
||||
lastReconciledValue *v1alpha1.ValidatingAdmissionPolicy
|
||||
}
|
||||
|
||||
type bindingInfo struct {
|
||||
// Compiled CEL expression turned into an validator
|
||||
validator atomic.Pointer[Validator]
|
||||
|
||||
// Last value seen by this controller to be used in policy enforcement
|
||||
// May not be nil
|
||||
lastReconciledValue *v1alpha1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
||||
type paramInfo struct {
|
||||
// Controller which is watching this param CRD
|
||||
controller generic.Controller[*unstructured.Unstructured]
|
||||
|
||||
// Function to call to stop the informer and clean up the controller
|
||||
stop func()
|
||||
|
||||
// Policy Definitions which refer to this param CRD
|
||||
dependentDefinitions sets.Set[namespacedName]
|
||||
}
|
||||
|
||||
func NewAdmissionController(
|
||||
// Injected Dependencies
|
||||
informerFactory informers.SharedInformerFactory,
|
||||
client kubernetes.Interface,
|
||||
restMapper meta.RESTMapper,
|
||||
dynamicClient dynamic.Interface,
|
||||
) CELPolicyEvaluator {
|
||||
matcher := matching.NewMatcher(informerFactory.Core().V1().Namespaces().Lister(), client)
|
||||
validatorCompiler := &CELValidatorCompiler{
|
||||
Matcher: matcher,
|
||||
}
|
||||
c := &celAdmissionController{
|
||||
definitionInfo: make(map[namespacedName]*definitionInfo),
|
||||
bindingInfos: make(map[namespacedName]*bindingInfo),
|
||||
paramsCRDControllers: make(map[v1alpha1.ParamKind]*paramInfo),
|
||||
definitionsToBindings: make(map[namespacedName]sets.Set[namespacedName]),
|
||||
dynamicClient: dynamicClient,
|
||||
validatorCompiler: validatorCompiler,
|
||||
restMapper: restMapper,
|
||||
}
|
||||
|
||||
c.policyDefinitionsController = generic.NewController(
|
||||
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicy](
|
||||
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies().Informer()),
|
||||
c.reconcilePolicyDefinition,
|
||||
generic.ControllerOptions{
|
||||
Workers: 1,
|
||||
Name: "cel-policy-definitions",
|
||||
},
|
||||
)
|
||||
c.policyBindingController = generic.NewController(
|
||||
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicyBinding](
|
||||
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicyBindings().Informer()),
|
||||
c.reconcilePolicyBinding,
|
||||
generic.ControllerOptions{
|
||||
Workers: 1,
|
||||
Name: "cel-policy-bindings",
|
||||
},
|
||||
)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *celAdmissionController) Run(stopCh <-chan struct{}) {
|
||||
if c.runningContext != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
c.runningContext = ctx
|
||||
defer func() {
|
||||
c.runningContext = nil
|
||||
}()
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.policyDefinitionsController.Run(ctx)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.policyBindingController.Run(ctx)
|
||||
}()
|
||||
|
||||
<-stopCh
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (c *celAdmissionController) Validate(
|
||||
ctx context.Context,
|
||||
a admission.Attributes,
|
||||
o admission.ObjectInterfaces,
|
||||
) (err error) {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
|
||||
var deniedDecisions []policyDecisionWithMetadata
|
||||
|
||||
addConfigError := func(err error, definition *v1alpha1.ValidatingAdmissionPolicy, binding *v1alpha1.ValidatingAdmissionPolicyBinding) {
|
||||
// we always default the FailurePolicy if it is unset and validate it in API level
|
||||
var policy v1alpha1.FailurePolicyType
|
||||
if definition.Spec.FailurePolicy == nil {
|
||||
policy = v1alpha1.Fail
|
||||
} else {
|
||||
policy = *definition.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
// apply FailurePolicy specified in ValidatingAdmissionPolicy, the default would be Fail
|
||||
switch policy {
|
||||
case v1alpha1.Ignore:
|
||||
// TODO: add metrics for ignored error here
|
||||
return
|
||||
case v1alpha1.Fail:
|
||||
var message string
|
||||
if binding == nil {
|
||||
message = fmt.Errorf("failed to configure policy: %w", err).Error()
|
||||
} else {
|
||||
message = fmt.Errorf("failed to configure binding: %w", err).Error()
|
||||
}
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
policyDecision: policyDecision{
|
||||
action: actionDeny,
|
||||
message: message,
|
||||
},
|
||||
definition: definition,
|
||||
binding: binding,
|
||||
})
|
||||
default:
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
policyDecision: policyDecision{
|
||||
action: actionDeny,
|
||||
message: fmt.Errorf("unrecognized failure policy: '%v'", policy).Error(),
|
||||
},
|
||||
definition: definition,
|
||||
binding: binding,
|
||||
})
|
||||
}
|
||||
}
|
||||
for definitionNamespacedName, definitionInfo := range c.definitionInfo {
|
||||
definition := definitionInfo.lastReconciledValue
|
||||
matches, matchKind, err := c.validatorCompiler.DefinitionMatches(a, o, definition)
|
||||
if err != nil {
|
||||
// Configuration error.
|
||||
addConfigError(err, definition, nil)
|
||||
continue
|
||||
}
|
||||
if !matches {
|
||||
// Policy definition does not match request
|
||||
continue
|
||||
} else if definitionInfo.configurationError != nil {
|
||||
// Configuration error.
|
||||
addConfigError(definitionInfo.configurationError, definition, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
dependentBindings := c.definitionsToBindings[definitionNamespacedName]
|
||||
if len(dependentBindings) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for namespacedBindingName := range dependentBindings {
|
||||
// If the key is inside dependentBindings, there is guaranteed to
|
||||
// be a bindingInfo for it
|
||||
bindingInfo := c.bindingInfos[namespacedBindingName]
|
||||
binding := bindingInfo.lastReconciledValue
|
||||
matches, err := c.validatorCompiler.BindingMatches(a, o, binding)
|
||||
if err != nil {
|
||||
// Configuration error.
|
||||
addConfigError(err, definition, binding)
|
||||
continue
|
||||
}
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
var param *unstructured.Unstructured
|
||||
|
||||
// If definition has paramKind, paramRef is required in binding.
|
||||
// If definition has no paramKind, paramRef set in binding will be ignored.
|
||||
paramKind := definition.Spec.ParamKind
|
||||
paramRef := binding.Spec.ParamRef
|
||||
if paramKind != nil && paramRef != nil {
|
||||
|
||||
// Find the params referred by the binding by looking its name up
|
||||
// in our informer for its CRD
|
||||
paramInfo, ok := c.paramsCRDControllers[*paramKind]
|
||||
if !ok {
|
||||
addConfigError(fmt.Errorf("paramKind kind `%v` not known",
|
||||
paramKind.String()), definition, binding)
|
||||
continue
|
||||
}
|
||||
|
||||
// If the param informer for this admission policy has not yet
|
||||
// had time to perform an initial listing, don't attempt to use
|
||||
// it.
|
||||
//!TOOD(alexzielenski): add a wait for a very short amount of
|
||||
// time for the cache to sync
|
||||
if !paramInfo.controller.HasSynced() {
|
||||
addConfigError(fmt.Errorf("paramKind kind `%v` not yet synced to use for admission",
|
||||
paramKind.String()), definition, binding)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(paramRef.Namespace) == 0 {
|
||||
param, err = paramInfo.controller.Informer().Get(paramRef.Name)
|
||||
} else {
|
||||
param, err = paramInfo.controller.Informer().Namespaced(paramRef.Namespace).Get(paramRef.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Apply failure policy
|
||||
addConfigError(err, definition, binding)
|
||||
|
||||
if k8serrors.IsInvalid(err) {
|
||||
// Param mis-configured
|
||||
// require to set paramRef.namespace for namespaced resource and unset paramRef.namespace for cluster scoped resource
|
||||
continue
|
||||
} else if k8serrors.IsNotFound(err) {
|
||||
// Param not yet available. User may need to wait a bit
|
||||
// before being able to use it for validation.
|
||||
continue
|
||||
}
|
||||
|
||||
// There was a bad internal error
|
||||
utilruntime.HandleError(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
validator := bindingInfo.validator.Load()
|
||||
if validator == nil {
|
||||
// Compile policy definition using binding
|
||||
newValidator := c.validatorCompiler.Compile(definition)
|
||||
validator = &newValidator
|
||||
|
||||
bindingInfo.validator.Store(validator)
|
||||
}
|
||||
|
||||
decisions, err := (*validator).Validate(a, o, param, matchKind)
|
||||
if err != nil {
|
||||
// runtime error. Apply failure policy
|
||||
wrappedError := fmt.Errorf("failed to evaluate CEL expression: %w", err)
|
||||
addConfigError(wrappedError, definition, binding)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, decision := range decisions {
|
||||
switch decision.action {
|
||||
case actionAdmit:
|
||||
if decision.evaluation == evalError {
|
||||
celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.elapsed, definition.Name, binding.Name, "active")
|
||||
}
|
||||
case actionDeny:
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
definition: definition,
|
||||
binding: binding,
|
||||
policyDecision: decision,
|
||||
})
|
||||
celmetrics.Metrics.ObserveRejection(ctx, decision.elapsed, definition.Name, binding.Name, "active")
|
||||
default:
|
||||
return fmt.Errorf("unrecognized evaluation decision '%s' for ValidatingAdmissionPolicyBinding '%s' with ValidatingAdmissionPolicy '%s'",
|
||||
decision.action, binding.Name, definition.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(deniedDecisions) > 0 {
|
||||
// TODO: refactor admission.NewForbidden so the name extraction is reusable but the code/reason is customizable
|
||||
var message string
|
||||
deniedDecision := deniedDecisions[0]
|
||||
if deniedDecision.binding != nil {
|
||||
message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' with binding '%s' denied request: %s", deniedDecision.definition.Name, deniedDecision.binding.Name, deniedDecision.message)
|
||||
} else {
|
||||
message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' denied request: %s", deniedDecision.definition.Name, deniedDecision.message)
|
||||
}
|
||||
err := admission.NewForbidden(a, errors.New(message)).(*k8serrors.StatusError)
|
||||
reason := deniedDecision.reason
|
||||
if len(reason) == 0 {
|
||||
reason = metav1.StatusReasonInvalid
|
||||
}
|
||||
err.ErrStatus.Reason = reason
|
||||
err.ErrStatus.Code = reasonToCode(reason)
|
||||
err.ErrStatus.Details.Causes = append(err.ErrStatus.Details.Causes, metav1.StatusCause{Message: message})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *celAdmissionController) HasSynced() bool {
|
||||
return c.policyBindingController.HasSynced() &&
|
||||
c.policyDefinitionsController.HasSynced()
|
||||
}
|
||||
|
||||
func (c *celAdmissionController) ValidateInitialization() error {
|
||||
return c.validatorCompiler.ValidateInitialization()
|
||||
}
|
233
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller_reconcile.go
generated
vendored
Normal file
233
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller_reconcile.go
generated
vendored
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
Copyright 2022 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
func (c *celAdmissionController) reconcilePolicyDefinition(namespace, name string, definition *v1alpha1.ValidatingAdmissionPolicy) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
// Namespace for policydefinition is empty.
|
||||
nn := getNamespaceName(namespace, name)
|
||||
info, ok := c.definitionInfo[nn]
|
||||
if !ok {
|
||||
info = &definitionInfo{}
|
||||
c.definitionInfo[nn] = info
|
||||
// TODO(DangerOnTheRanger): add support for "warn" being a valid enforcementAction
|
||||
celmetrics.Metrics.ObserveDefinition(context.TODO(), "active", "deny")
|
||||
}
|
||||
|
||||
var paramSource *v1alpha1.ParamKind
|
||||
if definition != nil {
|
||||
paramSource = definition.Spec.ParamKind
|
||||
}
|
||||
|
||||
// If param source has changed, remove definition as dependent of old params
|
||||
// If there are no more dependents of old param, stop and clean up controller
|
||||
if info.lastReconciledValue != nil && info.lastReconciledValue.Spec.ParamKind != nil {
|
||||
oldParamSource := *info.lastReconciledValue.Spec.ParamKind
|
||||
|
||||
// If we are:
|
||||
// - switching from having a param to not having a param (includes deletion)
|
||||
// - or from having a param to a different one
|
||||
// we remove dependency on the controller.
|
||||
if paramSource == nil || *paramSource != oldParamSource {
|
||||
if oldParamInfo, ok := c.paramsCRDControllers[oldParamSource]; ok {
|
||||
oldParamInfo.dependentDefinitions.Delete(nn)
|
||||
if len(oldParamInfo.dependentDefinitions) == 0 {
|
||||
oldParamInfo.stop()
|
||||
delete(c.paramsCRDControllers, oldParamSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset all previously compiled evaluators in case something relevant in
|
||||
// definition has changed.
|
||||
for key := range c.definitionsToBindings[nn] {
|
||||
bindingInfo := c.bindingInfos[key]
|
||||
bindingInfo.validator.Store(nil)
|
||||
c.bindingInfos[key] = bindingInfo
|
||||
}
|
||||
|
||||
if definition == nil {
|
||||
delete(c.definitionInfo, nn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update definition info
|
||||
info.lastReconciledValue = definition
|
||||
info.configurationError = nil
|
||||
|
||||
if paramSource == nil {
|
||||
// Skip setting up controller for empty param type
|
||||
return nil
|
||||
}
|
||||
|
||||
// find GVR for params
|
||||
// Parse param source into a GVK
|
||||
|
||||
paramSourceGV, err := schema.ParseGroupVersion(paramSource.APIVersion)
|
||||
if err != nil {
|
||||
// Failed to resolve. Return error so we retry again (rate limited)
|
||||
// Save a record of this definition with an evaluator that unconditionally
|
||||
info.configurationError = fmt.Errorf("failed to parse apiVersion of paramKind '%v' with error: %w", paramSource.String(), err)
|
||||
|
||||
// Return nil, since this error cannot be resolved by waiting more time
|
||||
return nil
|
||||
}
|
||||
|
||||
paramsGVR, err := c.restMapper.RESTMapping(schema.GroupKind{
|
||||
Group: paramSourceGV.Group,
|
||||
Kind: paramSource.Kind,
|
||||
}, paramSourceGV.Version)
|
||||
|
||||
if err != nil {
|
||||
// Failed to resolve. Return error so we retry again (rate limited)
|
||||
// Save a record of this definition with an evaluator that unconditionally
|
||||
//
|
||||
info.configurationError = fmt.Errorf("failed to find resource referenced by paramKind: '%v'", paramSourceGV.WithKind(paramSource.Kind))
|
||||
return info.configurationError
|
||||
}
|
||||
|
||||
if info, ok := c.paramsCRDControllers[*paramSource]; ok {
|
||||
// If a param controller is already active for this paramsource, make
|
||||
// sure it is tracking this policy's dependency upon it
|
||||
info.dependentDefinitions.Insert(nn)
|
||||
|
||||
} else {
|
||||
instanceContext, instanceCancel := context.WithCancel(c.runningContext)
|
||||
|
||||
// Watch for new instances of this policy
|
||||
informer := dynamicinformer.NewFilteredDynamicInformer(
|
||||
c.dynamicClient,
|
||||
paramsGVR.Resource,
|
||||
corev1.NamespaceAll,
|
||||
30*time.Second,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
nil,
|
||||
)
|
||||
|
||||
controller := generic.NewController(
|
||||
generic.NewInformer[*unstructured.Unstructured](informer.Informer()),
|
||||
c.reconcileParams,
|
||||
generic.ControllerOptions{
|
||||
Workers: 1,
|
||||
Name: paramSource.String() + "-controller",
|
||||
},
|
||||
)
|
||||
|
||||
c.paramsCRDControllers[*paramSource] = ¶mInfo{
|
||||
controller: controller,
|
||||
stop: instanceCancel,
|
||||
dependentDefinitions: sets.New(nn),
|
||||
}
|
||||
|
||||
go informer.Informer().Run(instanceContext.Done())
|
||||
go controller.Run(instanceContext)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *celAdmissionController) reconcilePolicyBinding(namespace, name string, binding *v1alpha1.ValidatingAdmissionPolicyBinding) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
// Namespace for PolicyBinding is empty. In the future a namespaced binding
|
||||
// may be added
|
||||
// https://github.com/kubernetes/enhancements/blob/bf5c3c81ea2081d60c1dc7c832faa98479e06209/keps/sig-api-machinery/3488-cel-admission-control/README.md?plain=1#L1042
|
||||
nn := getNamespaceName(namespace, name)
|
||||
info, ok := c.bindingInfos[nn]
|
||||
if !ok {
|
||||
info = &bindingInfo{}
|
||||
c.bindingInfos[nn] = info
|
||||
}
|
||||
|
||||
var oldNamespacedDefinitionName namespacedName
|
||||
if info.lastReconciledValue != nil {
|
||||
// All validating policies are cluster-scoped so have empty namespace
|
||||
oldNamespacedDefinitionName = getNamespaceName("", info.lastReconciledValue.Spec.PolicyName)
|
||||
}
|
||||
|
||||
var namespacedDefinitionName namespacedName
|
||||
if binding != nil {
|
||||
// All validating policies are cluster-scoped so have empty namespace
|
||||
namespacedDefinitionName = getNamespaceName("", binding.Spec.PolicyName)
|
||||
}
|
||||
|
||||
// Remove record of binding from old definition if the referred policy
|
||||
// has changed
|
||||
if oldNamespacedDefinitionName != namespacedDefinitionName {
|
||||
if dependentBindings, ok := c.definitionsToBindings[oldNamespacedDefinitionName]; ok {
|
||||
dependentBindings.Delete(nn)
|
||||
|
||||
// if there are no more dependent bindings, remove knowledge of the
|
||||
// definition altogether
|
||||
if len(dependentBindings) == 0 {
|
||||
delete(c.definitionsToBindings, oldNamespacedDefinitionName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if binding == nil {
|
||||
delete(c.bindingInfos, nn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add record of binding to new definition
|
||||
if dependentBindings, ok := c.definitionsToBindings[namespacedDefinitionName]; ok {
|
||||
dependentBindings.Insert(nn)
|
||||
} else {
|
||||
c.definitionsToBindings[namespacedDefinitionName] = sets.New(nn)
|
||||
}
|
||||
|
||||
// Remove compiled template for old binding
|
||||
info.validator.Store(nil)
|
||||
info.lastReconciledValue = binding
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *celAdmissionController) reconcileParams(namespace, name string, params *unstructured.Unstructured) error {
|
||||
// Do nothing.
|
||||
// When we add informational type checking we will need to compile in the
|
||||
// reconcile loops instead of lazily so we can add compiler errors / type
|
||||
// checker errors to the status of the resources.
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNamespaceName(namespace, name string) namespacedName {
|
||||
return namespacedName{
|
||||
namespace: namespace,
|
||||
name: name,
|
||||
}
|
||||
}
|
30
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/initializer.go
generated
vendored
Normal file
30
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/initializer.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright 2022 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
type CELPolicyEvaluator interface {
|
||||
admission.InitializationValidator
|
||||
|
||||
Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error
|
||||
HasSynced() bool
|
||||
Run(stopCh <-chan struct{})
|
||||
}
|
50
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go
generated
vendored
Normal file
50
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2022 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// Validator defines the func used to validate the cel expressions
|
||||
// matchKind provides the GroupVersionKind that the object should be
|
||||
// validated by CEL expressions as.
|
||||
type Validator interface {
|
||||
Validate(a admission.Attributes, o admission.ObjectInterfaces, versionedParams runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error)
|
||||
}
|
||||
|
||||
// ValidatorCompiler is Dependency Injected into the PolicyDefinition's `Compile`
|
||||
// function to assist with converting types and values to/from CEL-typed values.
|
||||
type ValidatorCompiler interface {
|
||||
admission.InitializationValidator
|
||||
|
||||
// Matches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1alpha1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionKind, error)
|
||||
|
||||
// Matches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1alpha1.ValidatingAdmissionPolicyBinding) (bool, error)
|
||||
|
||||
// Compile is used for the cel expression compilation
|
||||
Compile(
|
||||
policy *v1alpha1.ValidatingAdmissionPolicy,
|
||||
) Validator
|
||||
}
|
272
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/controller.go
generated
vendored
Normal file
272
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/controller.go
generated
vendored
Normal file
@ -0,0 +1,272 @@
|
||||
/*
|
||||
Copyright 2022 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 generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ Controller[runtime.Object] = &controller[runtime.Object]{}
|
||||
|
||||
type controller[T runtime.Object] struct {
|
||||
informer Informer[T]
|
||||
queue workqueue.RateLimitingInterface
|
||||
|
||||
// Returns an error if there was a transient error during reconciliation
|
||||
// and the object should be tried again later.
|
||||
reconciler func(namespace, name string, newObj T) error
|
||||
|
||||
options ControllerOptions
|
||||
}
|
||||
|
||||
type ControllerOptions struct {
|
||||
Name string
|
||||
Workers uint
|
||||
}
|
||||
|
||||
func (c *controller[T]) Informer() Informer[T] {
|
||||
return c.informer
|
||||
}
|
||||
|
||||
func NewController[T runtime.Object](
|
||||
informer Informer[T],
|
||||
reconciler func(namepace, name string, newObj T) error,
|
||||
options ControllerOptions,
|
||||
) Controller[T] {
|
||||
if options.Workers == 0 {
|
||||
options.Workers = 2
|
||||
}
|
||||
|
||||
if len(options.Name) == 0 {
|
||||
options.Name = fmt.Sprintf("%T-controller", *new(T))
|
||||
}
|
||||
|
||||
return &controller[T]{
|
||||
options: options,
|
||||
informer: informer,
|
||||
reconciler: reconciler,
|
||||
queue: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Runs the controller and returns an error explaining why running was stopped.
|
||||
// Reconciliation ends as soon as the context completes. If there are events
|
||||
// waiting to be processed at that itme, they will be dropped.
|
||||
func (c *controller[T]) Run(ctx context.Context) error {
|
||||
klog.Infof("starting %s", c.options.Name)
|
||||
defer klog.Infof("stopping %s", c.options.Name)
|
||||
|
||||
c.queue = workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), c.options.Name)
|
||||
|
||||
// Forcefully shutdown workqueue. Drop any enqueued items.
|
||||
// Important to do this in a `defer` at the start of `Run`.
|
||||
// Otherwise, if there are any early returns without calling this, we
|
||||
// would never shut down the workqueue
|
||||
defer c.queue.ShutDown()
|
||||
|
||||
enqueue := func(obj interface{}) {
|
||||
var key string
|
||||
var err error
|
||||
if key, err = cache.DeletionHandlingMetaNamespaceKeyFunc(obj); err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return
|
||||
}
|
||||
c.queue.Add(key)
|
||||
}
|
||||
|
||||
registration, err := c.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
enqueue(obj)
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
oldMeta, err1 := meta.Accessor(oldObj)
|
||||
newMeta, err2 := meta.Accessor(newObj)
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
if err1 != nil {
|
||||
utilruntime.HandleError(err1)
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
utilruntime.HandleError(err2)
|
||||
}
|
||||
return
|
||||
} else if oldMeta.GetResourceVersion() == newMeta.GetResourceVersion() {
|
||||
if len(oldMeta.GetResourceVersion()) == 0 {
|
||||
klog.Warningf("%v throwing out update with empty RV. this is likely to happen if a test did not supply a resource version on an updated object", c.options.Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
enqueue(newObj)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
// Enqueue
|
||||
enqueue(obj)
|
||||
},
|
||||
})
|
||||
|
||||
// Error might be raised if informer was started and stopped already
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure event handler is removed from informer in case return early from
|
||||
// an error
|
||||
defer func() {
|
||||
// Remove event handler and Handle Error here. Error should only be raised
|
||||
// for improper usage of event handler API.
|
||||
if err := c.informer.RemoveEventHandler(registration); err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for initial cache list to complete before beginning to reconcile
|
||||
// objects.
|
||||
if !cache.WaitForNamedCacheSync(c.options.Name, ctx.Done(), c.informer.HasSynced) {
|
||||
// ctx cancelled during cache sync. return early
|
||||
err := ctx.Err()
|
||||
if err == nil {
|
||||
// if context wasnt cancelled then the sync failed for another reason
|
||||
err = errors.New("cache sync failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
waitGroup := sync.WaitGroup{}
|
||||
|
||||
for i := uint(0); i < c.options.Workers; i++ {
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
wait.Until(c.runWorker, time.Second, ctx.Done())
|
||||
waitGroup.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
klog.Infof("Started %v workers for %v", c.options.Workers, c.options.Name)
|
||||
|
||||
// Wait for context cancel.
|
||||
<-ctx.Done()
|
||||
|
||||
// Forcefully shutdown workqueue. Drop any enqueued items.
|
||||
c.queue.ShutDown()
|
||||
|
||||
// Workqueue shutdown signals for workers to stop. Wait for all workers to
|
||||
// clean up
|
||||
waitGroup.Wait()
|
||||
|
||||
// Only way for workers to ever stop is for caller to cancel the context
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func (c *controller[T]) HasSynced() bool {
|
||||
return c.informer.HasSynced()
|
||||
}
|
||||
|
||||
func (c *controller[T]) runWorker() {
|
||||
for {
|
||||
key, shutdown := c.queue.Get()
|
||||
if shutdown {
|
||||
return
|
||||
}
|
||||
|
||||
// We wrap this block in a func so we can defer c.workqueue.Done.
|
||||
err := func(obj interface{}) error {
|
||||
// We call Done here so the workqueue knows we have finished
|
||||
// processing this item. We also must remember to call Forget if we
|
||||
// do not want this work item being re-queued. For example, we do
|
||||
// not call Forget if a transient error occurs, instead the item is
|
||||
// put back on the workqueue and attempted again after a back-off
|
||||
// period.
|
||||
defer c.queue.Done(obj)
|
||||
var key string
|
||||
var ok bool
|
||||
// We expect strings to come off the workqueue. These are of the
|
||||
// form namespace/name. We do this as the delayed nature of the
|
||||
// workqueue means the items in the informer cache may actually be
|
||||
// more up to date that when the item was initially put onto the
|
||||
// workqueue.
|
||||
if key, ok = obj.(string); !ok {
|
||||
// How did an incorrectly formatted key get in the workqueue?
|
||||
// Done is sufficient. (Forget resets rate limiter for the key,
|
||||
// but the key is invalid so there is no point in doing that)
|
||||
return fmt.Errorf("expected string in workqueue but got %#v", obj)
|
||||
}
|
||||
|
||||
if err := c.reconcile(key); err != nil {
|
||||
// Put the item back on the workqueue to handle any transient errors.
|
||||
c.queue.AddRateLimited(key)
|
||||
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
|
||||
}
|
||||
// Finally, if no error occurs we Forget this item so it is allowed
|
||||
// to be re-enqueued without a long rate limit
|
||||
c.queue.Forget(obj)
|
||||
klog.V(4).Infof("syncAdmissionPolicy(%q)", key)
|
||||
return nil
|
||||
}(key)
|
||||
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *controller[T]) reconcile(key string) error {
|
||||
var newObj T
|
||||
var err error
|
||||
var namespace string
|
||||
var name string
|
||||
var lister NamespacedLister[T]
|
||||
|
||||
// Convert the namespace/name string into a distinct namespace and name
|
||||
namespace, name, err = cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(namespace) > 0 {
|
||||
lister = c.informer.Namespaced(namespace)
|
||||
} else {
|
||||
lister = c.informer
|
||||
}
|
||||
|
||||
newObj, err = lister.Get(name)
|
||||
if err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deleted object. Inform reconciler with empty
|
||||
}
|
||||
|
||||
return c.reconciler(namespace, name, newObj)
|
||||
}
|
29
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/doc.go
generated
vendored
Normal file
29
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/doc.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2022 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 generic contains a typed wrapper over cache SharedIndexInformer
|
||||
// and Lister (maybe eventually should have a home there?)
|
||||
//
|
||||
// This interface is being experimented with as an easier way to write controllers
|
||||
// with a bit less boilerplate.
|
||||
//
|
||||
// Informer/Lister classes are thin wrappers providing a type-safe interface
|
||||
// over regular interface{}-based Informers/Listers
|
||||
//
|
||||
// Controller[T] provides a reusable way to reconcile objects out of an informer
|
||||
// using the tried and true controller design pattern found all over k8s
|
||||
// codebase based upon syncFunc/reconcile
|
||||
package generic
|
36
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/informer.go
generated
vendored
Normal file
36
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/informer.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2022 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 generic
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
var _ Informer[runtime.Object] = informer[runtime.Object]{}
|
||||
|
||||
type informer[T runtime.Object] struct {
|
||||
cache.SharedIndexInformer
|
||||
lister[T]
|
||||
}
|
||||
|
||||
func NewInformer[T runtime.Object](informe cache.SharedIndexInformer) Informer[T] {
|
||||
return informer[T]{
|
||||
SharedIndexInformer: informe,
|
||||
lister: NewLister[T](informe.GetIndexer()),
|
||||
}
|
||||
}
|
62
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/interface.go
generated
vendored
Normal file
62
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/interface.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2022 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 generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type Controller[T runtime.Object] interface {
|
||||
// Meant to be run inside a goroutine
|
||||
// Waits for and reacts to changes in whatever type the controller
|
||||
// is concerned with.
|
||||
//
|
||||
// Returns an error always non-nil explaining why the worker stopped
|
||||
Run(ctx context.Context) error
|
||||
|
||||
// Retrieves the informer used to back this controller
|
||||
Informer() Informer[T]
|
||||
|
||||
// Returns true if the informer cache has synced, and all the objects from
|
||||
// the initial list have been reconciled at least once.
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
type NamespacedLister[T any] interface {
|
||||
// List lists all ValidationRuleSets in the indexer for a given namespace.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []T, err error)
|
||||
// Get retrieves the ValidationRuleSet from the indexer for a given namespace and name.
|
||||
// Objects returned here must be treated as read-only.
|
||||
Get(name string) (T, error)
|
||||
}
|
||||
|
||||
type Informer[T any] interface {
|
||||
cache.SharedIndexInformer
|
||||
Lister[T]
|
||||
}
|
||||
|
||||
// Lister[T] helps list Ts.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type Lister[T any] interface {
|
||||
NamespacedLister[T]
|
||||
Namespaced(namespace string) NamespacedLister[T]
|
||||
}
|
100
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/lister.go
generated
vendored
Normal file
100
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic/lister.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright 2022 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 generic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
var _ Lister[runtime.Object] = lister[runtime.Object]{}
|
||||
|
||||
type namespacedLister[T runtime.Object] struct {
|
||||
indexer cache.Indexer
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (w namespacedLister[T]) List(selector labels.Selector) (ret []T, err error) {
|
||||
err = cache.ListAllByNamespace(w.indexer, w.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(T))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (w namespacedLister[T]) Get(name string) (T, error) {
|
||||
var result T
|
||||
|
||||
obj, exists, err := w.indexer.GetByKey(w.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if !exists {
|
||||
return result, &kerrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Reason: metav1.StatusReasonNotFound,
|
||||
Message: fmt.Sprintf("%s not found", name),
|
||||
}}
|
||||
}
|
||||
result = obj.(T)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type lister[T runtime.Object] struct {
|
||||
indexer cache.Indexer
|
||||
}
|
||||
|
||||
func (w lister[T]) List(selector labels.Selector) (ret []T, err error) {
|
||||
err = cache.ListAll(w.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(T))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (w lister[T]) Get(name string) (T, error) {
|
||||
var result T
|
||||
|
||||
obj, exists, err := w.indexer.GetByKey(name)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if !exists {
|
||||
// kerrors.StatusNotFound requires a GroupResource we cannot provide
|
||||
return result, &kerrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Reason: metav1.StatusReasonNotFound,
|
||||
Message: fmt.Sprintf("%s not found", name),
|
||||
}}
|
||||
}
|
||||
result = obj.(T)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (w lister[T]) Namespaced(namespace string) NamespacedLister[T] {
|
||||
return namespacedLister[T]{namespace: namespace, indexer: w.indexer}
|
||||
}
|
||||
|
||||
func NewLister[T runtime.Object](indexer cache.Indexer) lister[T] {
|
||||
return lister[T]{indexer: indexer}
|
||||
}
|
192
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching/matching.go
generated
vendored
Normal file
192
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching/matching.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
Copyright 2022 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 matching
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
listersv1 "k8s.io/client-go/listers/core/v1"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules"
|
||||
)
|
||||
|
||||
type MatchCriteria interface {
|
||||
namespace.NamespaceSelectorProvider
|
||||
object.ObjectSelectorProvider
|
||||
|
||||
GetMatchResources() v1alpha1.MatchResources
|
||||
}
|
||||
|
||||
// Matcher decides if a request matches against matchCriteria
|
||||
type Matcher struct {
|
||||
namespaceMatcher *namespace.Matcher
|
||||
objectMatcher *object.Matcher
|
||||
}
|
||||
|
||||
// NewMatcher initialize the matcher with dependencies requires
|
||||
func NewMatcher(
|
||||
namespaceLister listersv1.NamespaceLister,
|
||||
client kubernetes.Interface,
|
||||
) *Matcher {
|
||||
return &Matcher{
|
||||
namespaceMatcher: &namespace.Matcher{
|
||||
NamespaceLister: namespaceLister,
|
||||
Client: client,
|
||||
},
|
||||
objectMatcher: &object.Matcher{},
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateInitialization verify if the matcher is ready before use
|
||||
func (m *Matcher) ValidateInitialization() error {
|
||||
if err := m.namespaceMatcher.Validate(); err != nil {
|
||||
return fmt.Errorf("namespaceMatcher is not properly setup: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Matcher) Matches(attr admission.Attributes, o admission.ObjectInterfaces, criteria MatchCriteria) (bool, schema.GroupVersionKind, error) {
|
||||
matches, matchNsErr := m.namespaceMatcher.MatchNamespaceSelector(criteria, attr)
|
||||
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchNsErr == nil {
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
matches, matchObjErr := m.objectMatcher.MatchObjectSelector(criteria, attr)
|
||||
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchObjErr == nil {
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
matchResources := criteria.GetMatchResources()
|
||||
matchPolicy := matchResources.MatchPolicy
|
||||
if isExcluded, _, err := matchesResourceRules(matchResources.ExcludeResourceRules, matchPolicy, attr, o); isExcluded || err != nil {
|
||||
return false, schema.GroupVersionKind{}, err
|
||||
}
|
||||
|
||||
var (
|
||||
isMatch bool
|
||||
matchKind schema.GroupVersionKind
|
||||
matchErr error
|
||||
)
|
||||
if len(matchResources.ResourceRules) == 0 {
|
||||
isMatch = true
|
||||
matchKind = attr.GetKind()
|
||||
} else {
|
||||
isMatch, matchKind, matchErr = matchesResourceRules(matchResources.ResourceRules, matchPolicy, attr, o)
|
||||
}
|
||||
if matchErr != nil {
|
||||
return false, schema.GroupVersionKind{}, matchErr
|
||||
}
|
||||
if !isMatch {
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
// now that we know this applies to this request otherwise, if there were selector errors, return them
|
||||
if matchNsErr != nil {
|
||||
return false, schema.GroupVersionKind{}, matchNsErr
|
||||
}
|
||||
if matchObjErr != nil {
|
||||
return false, schema.GroupVersionKind{}, matchObjErr
|
||||
}
|
||||
|
||||
return true, matchKind, nil
|
||||
}
|
||||
|
||||
func matchesResourceRules(namedRules []v1alpha1.NamedRuleWithOperations, matchPolicy *v1alpha1.MatchPolicyType, attr admission.Attributes, o admission.ObjectInterfaces) (bool, schema.GroupVersionKind, error) {
|
||||
matchKind := attr.GetKind()
|
||||
for _, namedRule := range namedRules {
|
||||
rule := v1.RuleWithOperations(namedRule.RuleWithOperations)
|
||||
ruleMatcher := rules.Matcher{
|
||||
Rule: rule,
|
||||
Attr: attr,
|
||||
}
|
||||
if !ruleMatcher.Matches() {
|
||||
continue
|
||||
}
|
||||
// an empty name list always matches
|
||||
if len(namedRule.ResourceNames) == 0 {
|
||||
return true, matchKind, nil
|
||||
}
|
||||
// TODO: GetName() can return an empty string if the user is relying on
|
||||
// the API server to generate the name... figure out what to do for this edge case
|
||||
name := attr.GetName()
|
||||
for _, matchedName := range namedRule.ResourceNames {
|
||||
if name == matchedName {
|
||||
return true, matchKind, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if match policy is undefined or exact, don't perform fuzzy matching
|
||||
// note that defaulting to fuzzy matching is set by the API
|
||||
if matchPolicy == nil || *matchPolicy == v1alpha1.Exact {
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
attrWithOverride := &attrWithResourceOverride{Attributes: attr}
|
||||
equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource())
|
||||
for _, namedRule := range namedRules {
|
||||
for _, equivalent := range equivalents {
|
||||
if equivalent == attr.GetResource() {
|
||||
// we have already checked the original resource
|
||||
continue
|
||||
}
|
||||
attrWithOverride.resource = equivalent
|
||||
rule := v1.RuleWithOperations(namedRule.RuleWithOperations)
|
||||
m := rules.Matcher{
|
||||
Rule: rule,
|
||||
Attr: attrWithOverride,
|
||||
}
|
||||
if !m.Matches() {
|
||||
continue
|
||||
}
|
||||
matchKind = o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource())
|
||||
if matchKind.Empty() {
|
||||
return false, schema.GroupVersionKind{}, fmt.Errorf("unable to convert to %v: unknown kind", equivalent)
|
||||
}
|
||||
// an empty name list always matches
|
||||
if len(namedRule.ResourceNames) == 0 {
|
||||
return true, matchKind, nil
|
||||
}
|
||||
|
||||
// TODO: GetName() can return an empty string if the user is relying on
|
||||
// the API server to generate the name... figure out what to do for this edge case
|
||||
name := attr.GetName()
|
||||
for _, matchedName := range namedRule.ResourceNames {
|
||||
if name == matchedName {
|
||||
return true, matchKind, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
type attrWithResourceOverride struct {
|
||||
admission.Attributes
|
||||
resource schema.GroupVersionResource
|
||||
}
|
||||
|
||||
func (a *attrWithResourceOverride) GetResource() schema.GroupVersionResource { return a.resource }
|
70
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/policy_decision.go
generated
vendored
Normal file
70
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/policy_decision.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2022 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type policyDecisionAction string
|
||||
|
||||
const (
|
||||
actionAdmit policyDecisionAction = "admit"
|
||||
actionDeny policyDecisionAction = "deny"
|
||||
)
|
||||
|
||||
type policyDecisionEvaluation string
|
||||
|
||||
const (
|
||||
evalAdmit policyDecisionEvaluation = "admit"
|
||||
evalError policyDecisionEvaluation = "error"
|
||||
evalDeny policyDecisionEvaluation = "deny"
|
||||
)
|
||||
|
||||
type policyDecision struct {
|
||||
action policyDecisionAction
|
||||
evaluation policyDecisionEvaluation
|
||||
message string
|
||||
reason metav1.StatusReason
|
||||
elapsed time.Duration
|
||||
}
|
||||
|
||||
type policyDecisionWithMetadata struct {
|
||||
policyDecision
|
||||
definition *v1alpha1.ValidatingAdmissionPolicy
|
||||
binding *v1alpha1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
||||
func reasonToCode(r metav1.StatusReason) int32 {
|
||||
switch r {
|
||||
case metav1.StatusReasonForbidden:
|
||||
return http.StatusForbidden
|
||||
case metav1.StatusReasonUnauthorized:
|
||||
return http.StatusUnauthorized
|
||||
case metav1.StatusReasonRequestEntityTooLarge:
|
||||
return http.StatusRequestEntityTooLarge
|
||||
case metav1.StatusReasonInvalid:
|
||||
return http.StatusUnprocessableEntity
|
||||
default:
|
||||
// It should not reach here since we only allow above reason to be set from API level
|
||||
return http.StatusUnprocessableEntity
|
||||
}
|
||||
}
|
318
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go
generated
vendored
Normal file
318
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go
generated
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
/*
|
||||
Copyright 2022 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
)
|
||||
|
||||
var _ ValidatorCompiler = &CELValidatorCompiler{}
|
||||
var _ matching.MatchCriteria = &matchCriteria{}
|
||||
|
||||
type matchCriteria struct {
|
||||
constraints *v1alpha1.MatchResources
|
||||
}
|
||||
|
||||
// GetParsedNamespaceSelector returns the converted LabelSelector which implements labels.Selector
|
||||
func (m *matchCriteria) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
return metav1.LabelSelectorAsSelector(m.constraints.NamespaceSelector)
|
||||
}
|
||||
|
||||
// GetParsedObjectSelector returns the converted LabelSelector which implements labels.Selector
|
||||
func (m *matchCriteria) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
return metav1.LabelSelectorAsSelector(m.constraints.ObjectSelector)
|
||||
}
|
||||
|
||||
// GetMatchResources returns the matchConstraints
|
||||
func (m *matchCriteria) GetMatchResources() v1alpha1.MatchResources {
|
||||
return *m.constraints
|
||||
}
|
||||
|
||||
// CELValidatorCompiler implement the interface ValidatorCompiler.
|
||||
type CELValidatorCompiler struct {
|
||||
Matcher *matching.Matcher
|
||||
}
|
||||
|
||||
// DefinitionMatches returns whether this ValidatingAdmissionPolicy matches the provided admission resource request
|
||||
func (c *CELValidatorCompiler) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1alpha1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionKind, error) {
|
||||
criteria := matchCriteria{constraints: definition.Spec.MatchConstraints}
|
||||
return c.Matcher.Matches(a, o, &criteria)
|
||||
}
|
||||
|
||||
// BindingMatches returns whether this ValidatingAdmissionPolicyBinding matches the provided admission resource request
|
||||
func (c *CELValidatorCompiler) BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, binding *v1alpha1.ValidatingAdmissionPolicyBinding) (bool, error) {
|
||||
if binding.Spec.MatchResources == nil {
|
||||
return true, nil
|
||||
}
|
||||
criteria := matchCriteria{constraints: binding.Spec.MatchResources}
|
||||
isMatch, _, err := c.Matcher.Matches(a, o, &criteria)
|
||||
return isMatch, err
|
||||
}
|
||||
|
||||
// ValidateInitialization checks if Matcher is initialized.
|
||||
func (c *CELValidatorCompiler) ValidateInitialization() error {
|
||||
return c.Matcher.ValidateInitialization()
|
||||
}
|
||||
|
||||
type validationActivation struct {
|
||||
object, oldObject, params, request interface{}
|
||||
}
|
||||
|
||||
// ResolveName returns a value from the activation by qualified name, or false if the name
|
||||
// could not be found.
|
||||
func (a *validationActivation) ResolveName(name string) (interface{}, bool) {
|
||||
switch name {
|
||||
case ObjectVarName:
|
||||
return a.object, true
|
||||
case OldObjectVarName:
|
||||
return a.oldObject, true
|
||||
case ParamsVarName:
|
||||
return a.params, true
|
||||
case RequestVarName:
|
||||
return a.request, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// Parent returns the parent of the current activation, may be nil.
|
||||
// If non-nil, the parent will be searched during resolve calls.
|
||||
func (a *validationActivation) Parent() interpreter.Activation {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compile compiles the cel expression defined in ValidatingAdmissionPolicy
|
||||
func (c *CELValidatorCompiler) Compile(p *v1alpha1.ValidatingAdmissionPolicy) Validator {
|
||||
if len(p.Spec.Validations) == 0 {
|
||||
return nil
|
||||
}
|
||||
hasParam := false
|
||||
if p.Spec.ParamKind != nil {
|
||||
hasParam = true
|
||||
}
|
||||
compilationResults := make([]CompilationResult, len(p.Spec.Validations))
|
||||
for i, validation := range p.Spec.Validations {
|
||||
compilationResults[i] = CompileValidatingPolicyExpression(validation.Expression, hasParam)
|
||||
}
|
||||
return &CELValidator{policy: p, compilationResults: compilationResults}
|
||||
}
|
||||
|
||||
// CELValidator implements the Validator interface
|
||||
type CELValidator struct {
|
||||
policy *v1alpha1.ValidatingAdmissionPolicy
|
||||
compilationResults []CompilationResult
|
||||
}
|
||||
|
||||
func convertObjectToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
|
||||
if obj == nil || reflect.ValueOf(obj).IsNil() {
|
||||
return &unstructured.Unstructured{Object: nil}, nil
|
||||
}
|
||||
ret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &unstructured.Unstructured{Object: ret}, nil
|
||||
}
|
||||
|
||||
func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
||||
if r == nil || reflect.ValueOf(r).IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
v, err := convertObjectToUnstructured(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.Object, nil
|
||||
}
|
||||
|
||||
func policyDecisionActionForError(f v1alpha1.FailurePolicyType) policyDecisionAction {
|
||||
if f == v1alpha1.Ignore {
|
||||
return actionAdmit
|
||||
}
|
||||
return actionDeny
|
||||
}
|
||||
|
||||
// Validate validates all cel expressions in Validator and returns a PolicyDecision for each CEL expression or returns an error.
|
||||
// An error will be returned if failed to convert the object/oldObject/params/request to unstructured.
|
||||
// Each PolicyDecision will have a decision and a message.
|
||||
// policyDecision.message will be empty if the decision is allowed and no error met.
|
||||
func (v *CELValidator) Validate(a admission.Attributes, o admission.ObjectInterfaces, versionedParams runtime.Object, matchKind schema.GroupVersionKind) ([]policyDecision, error) {
|
||||
// TODO: replace unstructured with ref.Val for CEL variables when native type support is available
|
||||
|
||||
decisions := make([]policyDecision, len(v.compilationResults))
|
||||
var err error
|
||||
versionedAttr, err := generic.NewVersionedAttributes(a, matchKind, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldObjectVal, err := objectToResolveVal(versionedAttr.VersionedOldObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objectVal, err := objectToResolveVal(versionedAttr.VersionedObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paramsVal, err := objectToResolveVal(versionedParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request := createAdmissionRequest(versionedAttr.Attributes)
|
||||
requestVal, err := convertObjectToUnstructured(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
va := &validationActivation{
|
||||
object: objectVal,
|
||||
oldObject: oldObjectVal,
|
||||
params: paramsVal,
|
||||
request: requestVal.Object,
|
||||
}
|
||||
|
||||
var f v1alpha1.FailurePolicyType
|
||||
if v.policy.Spec.FailurePolicy == nil {
|
||||
f = v1alpha1.Fail
|
||||
} else {
|
||||
f = *v.policy.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
for i, compilationResult := range v.compilationResults {
|
||||
validation := v.policy.Spec.Validations[i]
|
||||
|
||||
var policyDecision = &decisions[i]
|
||||
|
||||
if compilationResult.Error != nil {
|
||||
policyDecision.action = policyDecisionActionForError(f)
|
||||
policyDecision.evaluation = evalError
|
||||
policyDecision.message = fmt.Sprintf("compilation error: %v", compilationResult.Error)
|
||||
continue
|
||||
}
|
||||
if compilationResult.Program == nil {
|
||||
policyDecision.action = policyDecisionActionForError(f)
|
||||
policyDecision.evaluation = evalError
|
||||
policyDecision.message = "unexpected internal error compiling expression"
|
||||
continue
|
||||
}
|
||||
t1 := time.Now()
|
||||
evalResult, _, err := compilationResult.Program.Eval(va)
|
||||
elapsed := time.Since(t1)
|
||||
policyDecision.elapsed = elapsed
|
||||
if err != nil {
|
||||
policyDecision.action = policyDecisionActionForError(f)
|
||||
policyDecision.evaluation = evalError
|
||||
policyDecision.message = fmt.Sprintf("expression '%v' resulted in error: %v", v.policy.Spec.Validations[i].Expression, err)
|
||||
} else if evalResult != celtypes.True {
|
||||
policyDecision.action = actionDeny
|
||||
if validation.Reason == nil {
|
||||
policyDecision.reason = metav1.StatusReasonInvalid
|
||||
} else {
|
||||
policyDecision.reason = *validation.Reason
|
||||
}
|
||||
if len(validation.Message) > 0 {
|
||||
policyDecision.message = strings.TrimSpace(validation.Message)
|
||||
} else {
|
||||
policyDecision.message = fmt.Sprintf("failed expression: %v", strings.TrimSpace(validation.Expression))
|
||||
}
|
||||
|
||||
} else {
|
||||
policyDecision.action = actionAdmit
|
||||
policyDecision.evaluation = evalAdmit
|
||||
}
|
||||
}
|
||||
|
||||
return decisions, nil
|
||||
}
|
||||
|
||||
func createAdmissionRequest(attr admission.Attributes) *admissionv1.AdmissionRequest {
|
||||
// FIXME: how to get resource GVK, GVR and subresource?
|
||||
gvk := attr.GetKind()
|
||||
gvr := attr.GetResource()
|
||||
subresource := attr.GetSubresource()
|
||||
|
||||
requestGVK := attr.GetKind()
|
||||
requestGVR := attr.GetResource()
|
||||
requestSubResource := attr.GetSubresource()
|
||||
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
var userInfo authenticationv1.UserInfo
|
||||
if aUserInfo != nil {
|
||||
userInfo = authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
Groups: aUserInfo.GetGroups(),
|
||||
UID: aUserInfo.GetUID(),
|
||||
Username: aUserInfo.GetName(),
|
||||
}
|
||||
// Convert the extra information in the user object
|
||||
for key, val := range aUserInfo.GetExtra() {
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
}
|
||||
|
||||
dryRun := attr.IsDryRun()
|
||||
|
||||
return &admissionv1.AdmissionRequest{
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
Version: gvk.Version,
|
||||
},
|
||||
Resource: metav1.GroupVersionResource{
|
||||
Group: gvr.Group,
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: subresource,
|
||||
RequestKind: &metav1.GroupVersionKind{
|
||||
Group: requestGVK.Group,
|
||||
Kind: requestGVK.Kind,
|
||||
Version: requestGVK.Version,
|
||||
},
|
||||
RequestResource: &metav1.GroupVersionResource{
|
||||
Group: requestGVR.Group,
|
||||
Resource: requestGVR.Resource,
|
||||
Version: requestGVR.Version,
|
||||
},
|
||||
RequestSubResource: requestSubResource,
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
// Leave Object and OldObject unset since we don't provide access to them via request
|
||||
DryRun: &dryRun,
|
||||
Options: runtime.RawExtension{
|
||||
Object: attr.GetOperationOptions(),
|
||||
},
|
||||
}
|
||||
}
|
303
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
Normal file
303
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
Normal file
@ -0,0 +1,303 @@
|
||||
/*
|
||||
Copyright 2018 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 validating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
endpointsrequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// ValidatingAuditAnnotationPrefix is a prefix for keeping noteworthy
|
||||
// validating audit annotations.
|
||||
ValidatingAuditAnnotationPrefix = "validating.webhook.admission.k8s.io/"
|
||||
// ValidatingAuditAnnotationFailedOpenKeyPrefix in an annotation indicates
|
||||
// the validating webhook failed open when the webhook backend connection
|
||||
// failed or returned an internal server error.
|
||||
ValidatingAuditAnnotationFailedOpenKeyPrefix = "failed-open." + ValidatingAuditAnnotationPrefix
|
||||
)
|
||||
|
||||
type validatingDispatcher struct {
|
||||
cm *webhookutil.ClientManager
|
||||
plugin *Plugin
|
||||
}
|
||||
|
||||
func newValidatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return &validatingDispatcher{cm, p}
|
||||
}
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher = &validatingDispatcher{}
|
||||
|
||||
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
|
||||
var relevantHooks []*generic.WebhookInvocation
|
||||
// Construct all the versions we need to call our webhooks
|
||||
versionedAttrs := map[schema.GroupVersionKind]*generic.VersionedAttributes{}
|
||||
for _, hook := range hooks {
|
||||
invocation, statusError := d.plugin.ShouldCallHook(hook, attr, o)
|
||||
if statusError != nil {
|
||||
return statusError
|
||||
}
|
||||
if invocation == nil {
|
||||
continue
|
||||
}
|
||||
relevantHooks = append(relevantHooks, invocation)
|
||||
// If we already have this version, continue
|
||||
if _, ok := versionedAttrs[invocation.Kind]; ok {
|
||||
continue
|
||||
}
|
||||
versionedAttr, err := generic.NewVersionedAttributes(attr, invocation.Kind, o)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttrs[invocation.Kind] = versionedAttr
|
||||
}
|
||||
|
||||
if len(relevantHooks) == 0 {
|
||||
// no matching hooks
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the request has already timed out before spawning remote calls
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// parent context is canceled or timed out, no point in continuing
|
||||
return apierrors.NewTimeoutError("request did not complete within requested timeout", 0)
|
||||
default:
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
errCh := make(chan error, 2*len(relevantHooks)) // double the length to handle extra errors for panics in the gofunc
|
||||
wg.Add(len(relevantHooks))
|
||||
for i := range relevantHooks {
|
||||
go func(invocation *generic.WebhookInvocation, idx int) {
|
||||
ignoreClientCallFailures := false
|
||||
hookName := "unknown"
|
||||
versionedAttr := versionedAttrs[invocation.Kind]
|
||||
// The ordering of these two defers is critical. The wg.Done will release the parent go func to close the errCh
|
||||
// that is used by the second defer to report errors. The recovery and error reporting must be done first.
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
// HandleCrash has already called the crash handlers and it has been configured to utilruntime.ReallyCrash
|
||||
// This block prevents the second panic from failing our process.
|
||||
// This failure mode for the handler functions properly using the channel below.
|
||||
recover()
|
||||
}()
|
||||
defer utilruntime.HandleCrash(
|
||||
func(r interface{}) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
if ignoreClientCallFailures {
|
||||
// if failures are supposed to ignored, ignore it
|
||||
klog.Warningf("Panic calling webhook, failing open %v: %v", hookName, r)
|
||||
admissionmetrics.Metrics.ObserveWebhookFailOpen(ctx, hookName, "validating")
|
||||
key := fmt.Sprintf("%sround_0_index_%d", ValidatingAuditAnnotationFailedOpenKeyPrefix, idx)
|
||||
value := hookName
|
||||
if err := versionedAttr.Attributes.AddAnnotation(key, value); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, value, hookName, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
// this ensures that the admission request fails and a message is provided.
|
||||
errCh <- apierrors.NewInternalError(fmt.Errorf("ValidatingAdmissionWebhook/%v has panicked: %v", hookName, r))
|
||||
},
|
||||
)
|
||||
|
||||
hook, ok := invocation.Webhook.GetValidatingWebhook()
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("validating webhook dispatch requires v1.ValidatingWebhook, but got %T", hook))
|
||||
return
|
||||
}
|
||||
hookName = hook.Name
|
||||
ignoreClientCallFailures = hook.FailurePolicy != nil && *hook.FailurePolicy == v1.Ignore
|
||||
t := time.Now()
|
||||
err := d.callHook(ctx, hook, invocation, versionedAttr)
|
||||
rejected := false
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *webhookutil.ErrCallingWebhook:
|
||||
if !ignoreClientCallFailures {
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(ctx, hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionCallingWebhookError, int(err.Status.ErrStatus.Code))
|
||||
}
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "validating", int(err.Status.ErrStatus.Code))
|
||||
case *webhookutil.ErrWebhookRejection:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(ctx, hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionNoError, int(err.Status.ErrStatus.Code))
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "validating", int(err.Status.ErrStatus.Code))
|
||||
default:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(ctx, hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionAPIServerInternalError, 0)
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "validating", 0)
|
||||
}
|
||||
} else {
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "validating", 200)
|
||||
return
|
||||
}
|
||||
|
||||
if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
|
||||
if ignoreClientCallFailures {
|
||||
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||
admissionmetrics.Metrics.ObserveWebhookFailOpen(ctx, hook.Name, "validating")
|
||||
key := fmt.Sprintf("%sround_0_index_%d", ValidatingAuditAnnotationFailedOpenKeyPrefix, idx)
|
||||
value := hook.Name
|
||||
if err := versionedAttr.Attributes.AddAnnotation(key, value); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, value, hook.Name, err)
|
||||
}
|
||||
utilruntime.HandleError(callErr)
|
||||
return
|
||||
}
|
||||
|
||||
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
||||
errCh <- apierrors.NewInternalError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if rejectionErr, ok := err.(*webhookutil.ErrWebhookRejection); ok {
|
||||
err = rejectionErr.Status
|
||||
}
|
||||
klog.Warningf("rejected by webhook %q: %#v", hook.Name, err)
|
||||
errCh <- err
|
||||
}(relevantHooks[i], i)
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
|
||||
var errs []error
|
||||
for e := range errCh {
|
||||
errs = append(errs, e)
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(errs) > 1 {
|
||||
for i := 1; i < len(errs); i++ {
|
||||
// TODO: merge status errors; until then, just return the first one.
|
||||
utilruntime.HandleError(errs[i])
|
||||
}
|
||||
}
|
||||
return errs[0]
|
||||
}
|
||||
|
||||
func (d *validatingDispatcher) callHook(ctx context.Context, h *v1.ValidatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes) error {
|
||||
if attr.Attributes.IsDryRun() {
|
||||
if h.SideEffects == nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil"), Status: apierrors.NewBadRequest("Webhook SideEffects is nil")}
|
||||
}
|
||||
if !(*h.SideEffects == v1.SideEffectClassNone || *h.SideEffects == v1.SideEffectClassNoneOnDryRun) {
|
||||
return webhookerrors.NewDryRunUnsupportedErr(h.Name)
|
||||
}
|
||||
}
|
||||
|
||||
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("could not create admission objects: %w", err), Status: apierrors.NewBadRequest("error creating admission objects")}
|
||||
}
|
||||
// Make the webhook request
|
||||
client, err := invocation.Webhook.GetRESTClient(d.cm)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("could not get REST client: %w", err), Status: apierrors.NewBadRequest("error getting REST client")}
|
||||
}
|
||||
ctx, span := tracing.Start(ctx, "Call validating webhook",
|
||||
attribute.String("configuration", invocation.Webhook.GetConfigurationName()),
|
||||
attribute.String("webhook", h.Name),
|
||||
attribute.Stringer("resource", attr.GetResource()),
|
||||
attribute.String("subresource", attr.GetSubresource()),
|
||||
attribute.String("operation", string(attr.GetOperation())),
|
||||
attribute.String("UID", string(uid)))
|
||||
defer span.End(500 * time.Millisecond)
|
||||
|
||||
// if the webhook has a specific timeout, wrap the context to apply it
|
||||
if h.TimeoutSeconds != nil {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(*h.TimeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
r := client.Post().Body(request)
|
||||
|
||||
// if the context has a deadline, set it as a parameter to inform the backend
|
||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
||||
// compute the timeout
|
||||
if timeout := time.Until(deadline); timeout > 0 {
|
||||
// if it's not an even number of seconds, round up to the nearest second
|
||||
if truncated := timeout.Truncate(time.Second); truncated != timeout {
|
||||
timeout = truncated + time.Second
|
||||
}
|
||||
// set the timeout
|
||||
r.Timeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
do := func() { err = r.Do(ctx).Into(response) }
|
||||
if wd, ok := endpointsrequest.LatencyTrackersFrom(ctx); ok {
|
||||
tmp := do
|
||||
do = func() { wd.ValidatingWebhookTracker.Track(tmp) }
|
||||
}
|
||||
do()
|
||||
if err != nil {
|
||||
var status *apierrors.StatusError
|
||||
if se, ok := err.(*apierrors.StatusError); ok {
|
||||
status = se
|
||||
} else {
|
||||
status = apierrors.NewBadRequest("error calling webhook")
|
||||
}
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("failed to call webhook: %w", err), Status: status}
|
||||
}
|
||||
span.AddEvent("Request completed")
|
||||
|
||||
result, err := webhookrequest.VerifyAdmissionResponse(uid, false, response)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("received invalid webhook response: %w", err), Status: apierrors.NewServiceUnavailable("error validating webhook response")}
|
||||
}
|
||||
|
||||
for k, v := range result.AuditAnnotations {
|
||||
key := h.Name + "/" + k
|
||||
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, v, h.Name, err)
|
||||
}
|
||||
}
|
||||
for _, w := range result.Warnings {
|
||||
warning.AddWarning(ctx, "", w)
|
||||
}
|
||||
if result.Allowed {
|
||||
return nil
|
||||
}
|
||||
return &webhookutil.ErrWebhookRejection{Status: webhookerrors.ToStatusErr(h.Name, result.Result)}
|
||||
}
|
19
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2017 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 validating makes calls to validating (i.e., non-mutating) webhooks
|
||||
// during the admission process.
|
||||
package validating // import "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
|
67
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
Normal file
67
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2017 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 validating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/configuration"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "ValidatingAdmissionWebhook"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
|
||||
plugin, err := NewValidatingAdmissionWebhook(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*generic.Webhook
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
|
||||
// NewValidatingAdmissionWebhook returns a generic admission webhook plugin.
|
||||
func NewValidatingAdmissionWebhook(configFile io.Reader) (*Plugin, error) {
|
||||
handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
|
||||
p := &Plugin{}
|
||||
var err error
|
||||
p.Webhook, err = generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher(p))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Webhook.Dispatch(ctx, attr, o)
|
||||
}
|
33
vendor/k8s.io/apiserver/pkg/apis/audit/install/install.go
generated
vendored
Normal file
33
vendor/k8s.io/apiserver/pkg/apis/audit/install/install.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2017 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 install installs the experimental API group, making it available as
|
||||
// an option to all of the API encoding/decoding machinery.
|
||||
package install
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/apis/audit/v1"
|
||||
)
|
||||
|
||||
// Install registers the API group and adds types to a scheme
|
||||
func Install(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(audit.AddToScheme(scheme))
|
||||
utilruntime.Must(v1.AddToScheme(scheme))
|
||||
utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion))
|
||||
}
|
133
vendor/k8s.io/apiserver/pkg/apis/audit/validation/validation.go
generated
vendored
Normal file
133
vendor/k8s.io/apiserver/pkg/apis/audit/validation/validation.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2017 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 (
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
)
|
||||
|
||||
// ValidatePolicy validates the audit policy
|
||||
func ValidatePolicy(policy *audit.Policy) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
allErrs = append(allErrs, validateOmitStages(policy.OmitStages, field.NewPath("omitStages"))...)
|
||||
rulePath := field.NewPath("rules")
|
||||
for i, rule := range policy.Rules {
|
||||
allErrs = append(allErrs, validatePolicyRule(rule, rulePath.Index(i))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validatePolicyRule(rule audit.PolicyRule, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
allErrs = append(allErrs, validateLevel(rule.Level, fldPath.Child("level"))...)
|
||||
allErrs = append(allErrs, validateNonResourceURLs(rule.NonResourceURLs, fldPath.Child("nonResourceURLs"))...)
|
||||
allErrs = append(allErrs, validateResources(rule.Resources, fldPath.Child("resources"))...)
|
||||
allErrs = append(allErrs, validateOmitStages(rule.OmitStages, fldPath.Child("omitStages"))...)
|
||||
|
||||
if len(rule.NonResourceURLs) > 0 {
|
||||
if len(rule.Resources) > 0 || len(rule.Namespaces) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "rules cannot apply to both regular resources and non-resource URLs"))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var validLevels = []string{
|
||||
string(audit.LevelNone),
|
||||
string(audit.LevelMetadata),
|
||||
string(audit.LevelRequest),
|
||||
string(audit.LevelRequestResponse),
|
||||
}
|
||||
|
||||
var validOmitStages = []string{
|
||||
string(audit.StageRequestReceived),
|
||||
string(audit.StageResponseStarted),
|
||||
string(audit.StageResponseComplete),
|
||||
string(audit.StagePanic),
|
||||
}
|
||||
|
||||
func validateLevel(level audit.Level, fldPath *field.Path) field.ErrorList {
|
||||
switch level {
|
||||
case audit.LevelNone, audit.LevelMetadata, audit.LevelRequest, audit.LevelRequestResponse:
|
||||
return nil
|
||||
case "":
|
||||
return field.ErrorList{field.Required(fldPath, "")}
|
||||
default:
|
||||
return field.ErrorList{field.NotSupported(fldPath, level, validLevels)}
|
||||
}
|
||||
}
|
||||
|
||||
func validateNonResourceURLs(urls []string, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
for i, url := range urls {
|
||||
if url == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(url, "/") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), url, "non-resource URL rules must begin with a '/' character"))
|
||||
}
|
||||
|
||||
if url != "" && strings.ContainsRune(url[:len(url)-1], '*') {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), url, "non-resource URL wildcards '*' must be the final character of the rule"))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateResources(groupResources []audit.GroupResources, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
for _, groupResource := range groupResources {
|
||||
// The empty string represents the core API group.
|
||||
if len(groupResource.Group) != 0 {
|
||||
// Group names must be lower case and be valid DNS subdomains.
|
||||
// reference: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md
|
||||
// an error is returned for group name like rbac.authorization.k8s.io/v1beta1
|
||||
// rbac.authorization.k8s.io is the valid one
|
||||
if msgs := validation.NameIsDNSSubdomain(groupResource.Group, false); len(msgs) != 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), groupResource.Group, strings.Join(msgs, ",")))
|
||||
}
|
||||
}
|
||||
|
||||
if len(groupResource.ResourceNames) > 0 && len(groupResource.Resources) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceNames"), groupResource.ResourceNames, "using resourceNames requires at least one resource"))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateOmitStages(omitStages []audit.Stage, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
for i, stage := range omitStages {
|
||||
valid := false
|
||||
for _, validOmitStage := range validOmitStages {
|
||||
if string(stage) == validOmitStage {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), string(stage), "allowed stages are "+strings.Join(validOmitStages, ",")))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
19
vendor/k8s.io/apiserver/pkg/apis/config/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/apiserver/pkg/apis/config/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2018 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 config // import "k8s.io/apiserver/pkg/apis/config"
|
53
vendor/k8s.io/apiserver/pkg/apis/config/register.go
generated
vendored
Normal file
53
vendor/k8s.io/apiserver/pkg/apis/config/register.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2018 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 config
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
// SchemeBuilder points to a list of functions added to Scheme.
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
// AddToScheme adds this group to a scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package.
|
||||
const GroupName = "apiserver.config.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects.
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns a Group qualified GroupKind.
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource.
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
// TODO this will get cleaned up with the scheme types are fixed
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&EncryptionConfiguration{},
|
||||
)
|
||||
return nil
|
||||
}
|
103
vendor/k8s.io/apiserver/pkg/apis/config/types.go
generated
vendored
Normal file
103
vendor/k8s.io/apiserver/pkg/apis/config/types.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2018 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 config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// EncryptionConfiguration stores the complete configuration for encryption providers.
|
||||
type EncryptionConfiguration struct {
|
||||
metav1.TypeMeta
|
||||
// resources is a list containing resources, and their corresponding encryption providers.
|
||||
Resources []ResourceConfiguration
|
||||
}
|
||||
|
||||
// ResourceConfiguration stores per resource configuration.
|
||||
type ResourceConfiguration struct {
|
||||
// resources is a list of kubernetes resources which have to be encrypted.
|
||||
Resources []string
|
||||
// providers is a list of transformers to be used for reading and writing the resources to disk.
|
||||
// eg: aesgcm, aescbc, secretbox, identity.
|
||||
Providers []ProviderConfiguration
|
||||
}
|
||||
|
||||
// ProviderConfiguration stores the provided configuration for an encryption provider.
|
||||
type ProviderConfiguration struct {
|
||||
// aesgcm is the configuration for the AES-GCM transformer.
|
||||
AESGCM *AESConfiguration
|
||||
// aescbc is the configuration for the AES-CBC transformer.
|
||||
AESCBC *AESConfiguration
|
||||
// secretbox is the configuration for the Secretbox based transformer.
|
||||
Secretbox *SecretboxConfiguration
|
||||
// identity is the (empty) configuration for the identity transformer.
|
||||
Identity *IdentityConfiguration
|
||||
// kms contains the name, cache size and path to configuration file for a KMS based envelope transformer.
|
||||
KMS *KMSConfiguration
|
||||
}
|
||||
|
||||
// AESConfiguration contains the API configuration for an AES transformer.
|
||||
type AESConfiguration struct {
|
||||
// keys is a list of keys to be used for creating the AES transformer.
|
||||
// Each key has to be 32 bytes long for AES-CBC and 16, 24 or 32 bytes for AES-GCM.
|
||||
Keys []Key
|
||||
}
|
||||
|
||||
// SecretboxConfiguration contains the API configuration for an Secretbox transformer.
|
||||
type SecretboxConfiguration struct {
|
||||
// keys is a list of keys to be used for creating the Secretbox transformer.
|
||||
// Each key has to be 32 bytes long.
|
||||
Keys []Key
|
||||
}
|
||||
|
||||
// Key contains name and secret of the provided key for a transformer.
|
||||
type Key struct {
|
||||
// name is the name of the key to be used while storing data to disk.
|
||||
Name string
|
||||
// secret is the actual key, encoded in base64.
|
||||
Secret string
|
||||
}
|
||||
|
||||
// String implements Stringer interface in a log safe way.
|
||||
func (k Key) String() string {
|
||||
return fmt.Sprintf("Name: %s, Secret: [REDACTED]", k.Name)
|
||||
}
|
||||
|
||||
// IdentityConfiguration is an empty struct to allow identity transformer in provider configuration.
|
||||
type IdentityConfiguration struct{}
|
||||
|
||||
// KMSConfiguration contains the name, cache size and path to configuration file for a KMS based envelope transformer.
|
||||
type KMSConfiguration struct {
|
||||
// apiVersion of KeyManagementService
|
||||
// +optional
|
||||
APIVersion string
|
||||
// name is the name of the KMS plugin to be used.
|
||||
Name string
|
||||
// cachesize is the maximum number of secrets which are cached in memory. The default value is 1000.
|
||||
// Set to a negative value to disable caching.
|
||||
// +optional
|
||||
CacheSize *int32
|
||||
// endpoint is the gRPC server listening address, for example "unix:///var/run/kms-provider.sock".
|
||||
Endpoint string
|
||||
// timeout for gRPC calls to kms-plugin (ex. 5s). The default is 3 seconds.
|
||||
// +optional
|
||||
Timeout *metav1.Duration
|
||||
}
|
49
vendor/k8s.io/apiserver/pkg/apis/config/v1/defaults.go
generated
vendored
Normal file
49
vendor/k8s.io/apiserver/pkg/apis/config/v1/defaults.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2019 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 v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultTimeout = &metav1.Duration{Duration: 3 * time.Second}
|
||||
defaultCacheSize int32 = 1000
|
||||
defaultAPIVersion = "v1"
|
||||
)
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
}
|
||||
|
||||
// SetDefaults_KMSConfiguration applies defaults to KMSConfiguration.
|
||||
func SetDefaults_KMSConfiguration(obj *KMSConfiguration) {
|
||||
if obj.Timeout == nil {
|
||||
obj.Timeout = defaultTimeout
|
||||
}
|
||||
|
||||
if obj.CacheSize == nil {
|
||||
obj.CacheSize = &defaultCacheSize
|
||||
}
|
||||
|
||||
if obj.APIVersion == "" {
|
||||
obj.APIVersion = defaultAPIVersion
|
||||
}
|
||||
}
|
23
vendor/k8s.io/apiserver/pkg/apis/config/v1/doc.go
generated
vendored
Normal file
23
vendor/k8s.io/apiserver/pkg/apis/config/v1/doc.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2018 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:conversion-gen=k8s.io/apiserver/pkg/apis/config
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=apiserver.config.k8s.io
|
||||
|
||||
// Package v1 is the v1 version of the API.
|
||||
package v1
|
53
vendor/k8s.io/apiserver/pkg/apis/config/v1/register.go
generated
vendored
Normal file
53
vendor/k8s.io/apiserver/pkg/apis/config/v1/register.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2018 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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package.
|
||||
const GroupName = "apiserver.config.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects.
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
|
||||
|
||||
var (
|
||||
// SchemeBuilder points to a list of functions added to Scheme.
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
// AddToScheme adds this group to a scheme.
|
||||
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)
|
||||
localSchemeBuilder.Register(addDefaultingFuncs)
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&EncryptionConfiguration{},
|
||||
)
|
||||
// also register into the v1 group as EncryptionConfig (due to a docs bug)
|
||||
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "EncryptionConfig"}, &EncryptionConfiguration{})
|
||||
return nil
|
||||
}
|
103
vendor/k8s.io/apiserver/pkg/apis/config/v1/types.go
generated
vendored
Normal file
103
vendor/k8s.io/apiserver/pkg/apis/config/v1/types.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2017 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 v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// EncryptionConfiguration stores the complete configuration for encryption providers.
|
||||
type EncryptionConfiguration struct {
|
||||
metav1.TypeMeta
|
||||
// resources is a list containing resources, and their corresponding encryption providers.
|
||||
Resources []ResourceConfiguration `json:"resources"`
|
||||
}
|
||||
|
||||
// ResourceConfiguration stores per resource configuration.
|
||||
type ResourceConfiguration struct {
|
||||
// resources is a list of kubernetes resources which have to be encrypted.
|
||||
Resources []string `json:"resources"`
|
||||
// providers is a list of transformers to be used for reading and writing the resources to disk.
|
||||
// eg: aesgcm, aescbc, secretbox, identity.
|
||||
Providers []ProviderConfiguration `json:"providers"`
|
||||
}
|
||||
|
||||
// ProviderConfiguration stores the provided configuration for an encryption provider.
|
||||
type ProviderConfiguration struct {
|
||||
// aesgcm is the configuration for the AES-GCM transformer.
|
||||
AESGCM *AESConfiguration `json:"aesgcm,omitempty"`
|
||||
// aescbc is the configuration for the AES-CBC transformer.
|
||||
AESCBC *AESConfiguration `json:"aescbc,omitempty"`
|
||||
// secretbox is the configuration for the Secretbox based transformer.
|
||||
Secretbox *SecretboxConfiguration `json:"secretbox,omitempty"`
|
||||
// identity is the (empty) configuration for the identity transformer.
|
||||
Identity *IdentityConfiguration `json:"identity,omitempty"`
|
||||
// kms contains the name, cache size and path to configuration file for a KMS based envelope transformer.
|
||||
KMS *KMSConfiguration `json:"kms,omitempty"`
|
||||
}
|
||||
|
||||
// AESConfiguration contains the API configuration for an AES transformer.
|
||||
type AESConfiguration struct {
|
||||
// keys is a list of keys to be used for creating the AES transformer.
|
||||
// Each key has to be 32 bytes long for AES-CBC and 16, 24 or 32 bytes for AES-GCM.
|
||||
Keys []Key `json:"keys"`
|
||||
}
|
||||
|
||||
// SecretboxConfiguration contains the API configuration for an Secretbox transformer.
|
||||
type SecretboxConfiguration struct {
|
||||
// keys is a list of keys to be used for creating the Secretbox transformer.
|
||||
// Each key has to be 32 bytes long.
|
||||
Keys []Key `json:"keys"`
|
||||
}
|
||||
|
||||
// Key contains name and secret of the provided key for a transformer.
|
||||
type Key struct {
|
||||
// name is the name of the key to be used while storing data to disk.
|
||||
Name string `json:"name"`
|
||||
// secret is the actual key, encoded in base64.
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
// String implements Stringer interface in a log safe way.
|
||||
func (k Key) String() string {
|
||||
return fmt.Sprintf("Name: %s, Secret: [REDACTED]", k.Name)
|
||||
}
|
||||
|
||||
// IdentityConfiguration is an empty struct to allow identity transformer in provider configuration.
|
||||
type IdentityConfiguration struct{}
|
||||
|
||||
// KMSConfiguration contains the name, cache size and path to configuration file for a KMS based envelope transformer.
|
||||
type KMSConfiguration struct {
|
||||
// apiVersion of KeyManagementService
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion"`
|
||||
// name is the name of the KMS plugin to be used.
|
||||
Name string `json:"name"`
|
||||
// cachesize is the maximum number of secrets which are cached in memory. The default value is 1000.
|
||||
// Set to a negative value to disable caching.
|
||||
// +optional
|
||||
CacheSize *int32 `json:"cachesize,omitempty"`
|
||||
// endpoint is the gRPC server listening address, for example "unix:///var/run/kms-provider.sock".
|
||||
Endpoint string `json:"endpoint"`
|
||||
// timeout for gRPC calls to kms-plugin (ex. 5s). The default is 3 seconds.
|
||||
// +optional
|
||||
Timeout *metav1.Duration `json:"timeout,omitempty"`
|
||||
}
|
299
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.conversion.go
generated
vendored
Normal file
299
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.conversion.go
generated
vendored
Normal file
@ -0,0 +1,299 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +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 v1
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
config "k8s.io/apiserver/pkg/apis/config"
|
||||
)
|
||||
|
||||
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((*AESConfiguration)(nil), (*config.AESConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_AESConfiguration_To_config_AESConfiguration(a.(*AESConfiguration), b.(*config.AESConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.AESConfiguration)(nil), (*AESConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_AESConfiguration_To_v1_AESConfiguration(a.(*config.AESConfiguration), b.(*AESConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*EncryptionConfiguration)(nil), (*config.EncryptionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration(a.(*EncryptionConfiguration), b.(*config.EncryptionConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.EncryptionConfiguration)(nil), (*EncryptionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration(a.(*config.EncryptionConfiguration), b.(*EncryptionConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*IdentityConfiguration)(nil), (*config.IdentityConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_IdentityConfiguration_To_config_IdentityConfiguration(a.(*IdentityConfiguration), b.(*config.IdentityConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.IdentityConfiguration)(nil), (*IdentityConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_IdentityConfiguration_To_v1_IdentityConfiguration(a.(*config.IdentityConfiguration), b.(*IdentityConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*KMSConfiguration)(nil), (*config.KMSConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_KMSConfiguration_To_config_KMSConfiguration(a.(*KMSConfiguration), b.(*config.KMSConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.KMSConfiguration)(nil), (*KMSConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_KMSConfiguration_To_v1_KMSConfiguration(a.(*config.KMSConfiguration), b.(*KMSConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*Key)(nil), (*config.Key)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_Key_To_config_Key(a.(*Key), b.(*config.Key), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.Key)(nil), (*Key)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_Key_To_v1_Key(a.(*config.Key), b.(*Key), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ProviderConfiguration)(nil), (*config.ProviderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ProviderConfiguration_To_config_ProviderConfiguration(a.(*ProviderConfiguration), b.(*config.ProviderConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.ProviderConfiguration)(nil), (*ProviderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_ProviderConfiguration_To_v1_ProviderConfiguration(a.(*config.ProviderConfiguration), b.(*ProviderConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ResourceConfiguration)(nil), (*config.ResourceConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ResourceConfiguration_To_config_ResourceConfiguration(a.(*ResourceConfiguration), b.(*config.ResourceConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.ResourceConfiguration)(nil), (*ResourceConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_ResourceConfiguration_To_v1_ResourceConfiguration(a.(*config.ResourceConfiguration), b.(*ResourceConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*SecretboxConfiguration)(nil), (*config.SecretboxConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration(a.(*SecretboxConfiguration), b.(*config.SecretboxConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.SecretboxConfiguration)(nil), (*SecretboxConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration(a.(*config.SecretboxConfiguration), b.(*SecretboxConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1_AESConfiguration_To_config_AESConfiguration(in *AESConfiguration, out *config.AESConfiguration, s conversion.Scope) error {
|
||||
out.Keys = *(*[]config.Key)(unsafe.Pointer(&in.Keys))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_AESConfiguration_To_config_AESConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_AESConfiguration_To_config_AESConfiguration(in *AESConfiguration, out *config.AESConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_AESConfiguration_To_config_AESConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_AESConfiguration_To_v1_AESConfiguration(in *config.AESConfiguration, out *AESConfiguration, s conversion.Scope) error {
|
||||
out.Keys = *(*[]Key)(unsafe.Pointer(&in.Keys))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_AESConfiguration_To_v1_AESConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_AESConfiguration_To_v1_AESConfiguration(in *config.AESConfiguration, out *AESConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_AESConfiguration_To_v1_AESConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration(in *EncryptionConfiguration, out *config.EncryptionConfiguration, s conversion.Scope) error {
|
||||
out.Resources = *(*[]config.ResourceConfiguration)(unsafe.Pointer(&in.Resources))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration(in *EncryptionConfiguration, out *config.EncryptionConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration(in *config.EncryptionConfiguration, out *EncryptionConfiguration, s conversion.Scope) error {
|
||||
out.Resources = *(*[]ResourceConfiguration)(unsafe.Pointer(&in.Resources))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration(in *config.EncryptionConfiguration, out *EncryptionConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_IdentityConfiguration_To_config_IdentityConfiguration(in *IdentityConfiguration, out *config.IdentityConfiguration, s conversion.Scope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_IdentityConfiguration_To_config_IdentityConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_IdentityConfiguration_To_config_IdentityConfiguration(in *IdentityConfiguration, out *config.IdentityConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_IdentityConfiguration_To_config_IdentityConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_IdentityConfiguration_To_v1_IdentityConfiguration(in *config.IdentityConfiguration, out *IdentityConfiguration, s conversion.Scope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_IdentityConfiguration_To_v1_IdentityConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_IdentityConfiguration_To_v1_IdentityConfiguration(in *config.IdentityConfiguration, out *IdentityConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_IdentityConfiguration_To_v1_IdentityConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_KMSConfiguration_To_config_KMSConfiguration(in *KMSConfiguration, out *config.KMSConfiguration, s conversion.Scope) error {
|
||||
out.APIVersion = in.APIVersion
|
||||
out.Name = in.Name
|
||||
out.CacheSize = (*int32)(unsafe.Pointer(in.CacheSize))
|
||||
out.Endpoint = in.Endpoint
|
||||
out.Timeout = (*metav1.Duration)(unsafe.Pointer(in.Timeout))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_KMSConfiguration_To_config_KMSConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_KMSConfiguration_To_config_KMSConfiguration(in *KMSConfiguration, out *config.KMSConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_KMSConfiguration_To_config_KMSConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_KMSConfiguration_To_v1_KMSConfiguration(in *config.KMSConfiguration, out *KMSConfiguration, s conversion.Scope) error {
|
||||
out.APIVersion = in.APIVersion
|
||||
out.Name = in.Name
|
||||
out.CacheSize = (*int32)(unsafe.Pointer(in.CacheSize))
|
||||
out.Endpoint = in.Endpoint
|
||||
out.Timeout = (*metav1.Duration)(unsafe.Pointer(in.Timeout))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_KMSConfiguration_To_v1_KMSConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_KMSConfiguration_To_v1_KMSConfiguration(in *config.KMSConfiguration, out *KMSConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_KMSConfiguration_To_v1_KMSConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_Key_To_config_Key(in *Key, out *config.Key, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Secret = in.Secret
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_Key_To_config_Key is an autogenerated conversion function.
|
||||
func Convert_v1_Key_To_config_Key(in *Key, out *config.Key, s conversion.Scope) error {
|
||||
return autoConvert_v1_Key_To_config_Key(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_Key_To_v1_Key(in *config.Key, out *Key, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Secret = in.Secret
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_Key_To_v1_Key is an autogenerated conversion function.
|
||||
func Convert_config_Key_To_v1_Key(in *config.Key, out *Key, s conversion.Scope) error {
|
||||
return autoConvert_config_Key_To_v1_Key(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ProviderConfiguration_To_config_ProviderConfiguration(in *ProviderConfiguration, out *config.ProviderConfiguration, s conversion.Scope) error {
|
||||
out.AESGCM = (*config.AESConfiguration)(unsafe.Pointer(in.AESGCM))
|
||||
out.AESCBC = (*config.AESConfiguration)(unsafe.Pointer(in.AESCBC))
|
||||
out.Secretbox = (*config.SecretboxConfiguration)(unsafe.Pointer(in.Secretbox))
|
||||
out.Identity = (*config.IdentityConfiguration)(unsafe.Pointer(in.Identity))
|
||||
out.KMS = (*config.KMSConfiguration)(unsafe.Pointer(in.KMS))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ProviderConfiguration_To_config_ProviderConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_ProviderConfiguration_To_config_ProviderConfiguration(in *ProviderConfiguration, out *config.ProviderConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_ProviderConfiguration_To_config_ProviderConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_ProviderConfiguration_To_v1_ProviderConfiguration(in *config.ProviderConfiguration, out *ProviderConfiguration, s conversion.Scope) error {
|
||||
out.AESGCM = (*AESConfiguration)(unsafe.Pointer(in.AESGCM))
|
||||
out.AESCBC = (*AESConfiguration)(unsafe.Pointer(in.AESCBC))
|
||||
out.Secretbox = (*SecretboxConfiguration)(unsafe.Pointer(in.Secretbox))
|
||||
out.Identity = (*IdentityConfiguration)(unsafe.Pointer(in.Identity))
|
||||
out.KMS = (*KMSConfiguration)(unsafe.Pointer(in.KMS))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_ProviderConfiguration_To_v1_ProviderConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_ProviderConfiguration_To_v1_ProviderConfiguration(in *config.ProviderConfiguration, out *ProviderConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_ProviderConfiguration_To_v1_ProviderConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ResourceConfiguration_To_config_ResourceConfiguration(in *ResourceConfiguration, out *config.ResourceConfiguration, s conversion.Scope) error {
|
||||
out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources))
|
||||
out.Providers = *(*[]config.ProviderConfiguration)(unsafe.Pointer(&in.Providers))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ResourceConfiguration_To_config_ResourceConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_ResourceConfiguration_To_config_ResourceConfiguration(in *ResourceConfiguration, out *config.ResourceConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_ResourceConfiguration_To_config_ResourceConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_ResourceConfiguration_To_v1_ResourceConfiguration(in *config.ResourceConfiguration, out *ResourceConfiguration, s conversion.Scope) error {
|
||||
out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources))
|
||||
out.Providers = *(*[]ProviderConfiguration)(unsafe.Pointer(&in.Providers))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_ResourceConfiguration_To_v1_ResourceConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_ResourceConfiguration_To_v1_ResourceConfiguration(in *config.ResourceConfiguration, out *ResourceConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_ResourceConfiguration_To_v1_ResourceConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration(in *SecretboxConfiguration, out *config.SecretboxConfiguration, s conversion.Scope) error {
|
||||
out.Keys = *(*[]config.Key)(unsafe.Pointer(&in.Keys))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration(in *SecretboxConfiguration, out *config.SecretboxConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration(in *config.SecretboxConfiguration, out *SecretboxConfiguration, s conversion.Scope) error {
|
||||
out.Keys = *(*[]Key)(unsafe.Pointer(&in.Keys))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration(in *config.SecretboxConfiguration, out *SecretboxConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration(in, out, s)
|
||||
}
|
228
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
228
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +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 v1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
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 *AESConfiguration) DeepCopyInto(out *AESConfiguration) {
|
||||
*out = *in
|
||||
if in.Keys != nil {
|
||||
in, out := &in.Keys, &out.Keys
|
||||
*out = make([]Key, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AESConfiguration.
|
||||
func (in *AESConfiguration) DeepCopy() *AESConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AESConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EncryptionConfiguration) DeepCopyInto(out *EncryptionConfiguration) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Resources != nil {
|
||||
in, out := &in.Resources, &out.Resources
|
||||
*out = make([]ResourceConfiguration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EncryptionConfiguration.
|
||||
func (in *EncryptionConfiguration) DeepCopy() *EncryptionConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EncryptionConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *EncryptionConfiguration) 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 *IdentityConfiguration) DeepCopyInto(out *IdentityConfiguration) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityConfiguration.
|
||||
func (in *IdentityConfiguration) DeepCopy() *IdentityConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IdentityConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KMSConfiguration) DeepCopyInto(out *KMSConfiguration) {
|
||||
*out = *in
|
||||
if in.CacheSize != nil {
|
||||
in, out := &in.CacheSize, &out.CacheSize
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.Timeout != nil {
|
||||
in, out := &in.Timeout, &out.Timeout
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KMSConfiguration.
|
||||
func (in *KMSConfiguration) DeepCopy() *KMSConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KMSConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Key) DeepCopyInto(out *Key) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Key.
|
||||
func (in *Key) DeepCopy() *Key {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Key)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProviderConfiguration) DeepCopyInto(out *ProviderConfiguration) {
|
||||
*out = *in
|
||||
if in.AESGCM != nil {
|
||||
in, out := &in.AESGCM, &out.AESGCM
|
||||
*out = new(AESConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AESCBC != nil {
|
||||
in, out := &in.AESCBC, &out.AESCBC
|
||||
*out = new(AESConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Secretbox != nil {
|
||||
in, out := &in.Secretbox, &out.Secretbox
|
||||
*out = new(SecretboxConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Identity != nil {
|
||||
in, out := &in.Identity, &out.Identity
|
||||
*out = new(IdentityConfiguration)
|
||||
**out = **in
|
||||
}
|
||||
if in.KMS != nil {
|
||||
in, out := &in.KMS, &out.KMS
|
||||
*out = new(KMSConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfiguration.
|
||||
func (in *ProviderConfiguration) DeepCopy() *ProviderConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProviderConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResourceConfiguration) DeepCopyInto(out *ResourceConfiguration) {
|
||||
*out = *in
|
||||
if in.Resources != nil {
|
||||
in, out := &in.Resources, &out.Resources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Providers != nil {
|
||||
in, out := &in.Providers, &out.Providers
|
||||
*out = make([]ProviderConfiguration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceConfiguration.
|
||||
func (in *ResourceConfiguration) DeepCopy() *ResourceConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ResourceConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecretboxConfiguration) DeepCopyInto(out *SecretboxConfiguration) {
|
||||
*out = *in
|
||||
if in.Keys != nil {
|
||||
in, out := &in.Keys, &out.Keys
|
||||
*out = make([]Key, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretboxConfiguration.
|
||||
func (in *SecretboxConfiguration) DeepCopy() *SecretboxConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SecretboxConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
46
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.defaults.go
generated
vendored
Normal file
46
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.defaults.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +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 v1
|
||||
|
||||
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(&EncryptionConfiguration{}, func(obj interface{}) { SetObjectDefaults_EncryptionConfiguration(obj.(*EncryptionConfiguration)) })
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetObjectDefaults_EncryptionConfiguration(in *EncryptionConfiguration) {
|
||||
for i := range in.Resources {
|
||||
a := &in.Resources[i]
|
||||
for j := range a.Providers {
|
||||
b := &a.Providers[j]
|
||||
if b.KMS != nil {
|
||||
SetDefaults_KMSConfiguration(b.KMS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
261
vendor/k8s.io/apiserver/pkg/apis/config/validation/validation.go
generated
vendored
Normal file
261
vendor/k8s.io/apiserver/pkg/apis/config/validation/validation.go
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
/*
|
||||
Copyright 2019 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 validates EncryptionConfiguration.
|
||||
package validation
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/apis/config"
|
||||
)
|
||||
|
||||
const (
|
||||
moreThanOneElementErr = "more than one provider specified in a single element, should split into different list elements"
|
||||
keyLenErrFmt = "secret is not of the expected length, got %d, expected one of %v"
|
||||
unsupportedSchemeErrFmt = "unsupported scheme %q for KMS provider, only unix is supported"
|
||||
unsupportedKMSAPIVersionErrFmt = "unsupported apiVersion %s for KMS provider, only v1 and v2 are supported"
|
||||
atLeastOneRequiredErrFmt = "at least one %s is required"
|
||||
invalidURLErrFmt = "invalid endpoint for kms provider, error: parse %s: net/url: invalid control character in URL"
|
||||
mandatoryFieldErrFmt = "%s is a mandatory field for a %s"
|
||||
base64EncodingErr = "secrets must be base64 encoded"
|
||||
zeroOrNegativeErrFmt = "%s should be a positive value"
|
||||
nonZeroErrFmt = "%s should be a positive value, or negative to disable"
|
||||
encryptionConfigNilErr = "EncryptionConfiguration can't be nil"
|
||||
invalidKMSConfigNameErrFmt = "invalid KMS provider name %s, must not contain ':'"
|
||||
duplicateKMSConfigNameErrFmt = "duplicate KMS provider name %s, names must be unique"
|
||||
)
|
||||
|
||||
var (
|
||||
// See https://golang.org/pkg/crypto/aes/#NewCipher for details on supported key sizes for AES.
|
||||
aesKeySizes = []int{16, 24, 32}
|
||||
|
||||
// See https://godoc.org/golang.org/x/crypto/nacl/secretbox#Open for details on the supported key sizes for Secretbox.
|
||||
secretBoxKeySizes = []int{32}
|
||||
|
||||
root = field.NewPath("resources")
|
||||
)
|
||||
|
||||
// ValidateEncryptionConfiguration validates a v1.EncryptionConfiguration.
|
||||
func ValidateEncryptionConfiguration(c *config.EncryptionConfiguration, reload bool) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if c == nil {
|
||||
allErrs = append(allErrs, field.Required(root, "EncryptionConfiguration can't be nil"))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if len(c.Resources) == 0 {
|
||||
allErrs = append(allErrs, field.Required(root, fmt.Sprintf(atLeastOneRequiredErrFmt, root)))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// kmsProviderNames is used to track config names to ensure they are unique.
|
||||
kmsProviderNames := sets.NewString()
|
||||
for i, conf := range c.Resources {
|
||||
r := root.Index(i).Child("resources")
|
||||
p := root.Index(i).Child("providers")
|
||||
|
||||
if len(conf.Resources) == 0 {
|
||||
allErrs = append(allErrs, field.Required(r, fmt.Sprintf(atLeastOneRequiredErrFmt, r)))
|
||||
}
|
||||
|
||||
if len(conf.Providers) == 0 {
|
||||
allErrs = append(allErrs, field.Required(p, fmt.Sprintf(atLeastOneRequiredErrFmt, p)))
|
||||
}
|
||||
|
||||
for j, provider := range conf.Providers {
|
||||
path := p.Index(j)
|
||||
allErrs = append(allErrs, validateSingleProvider(provider, path)...)
|
||||
|
||||
switch {
|
||||
case provider.KMS != nil:
|
||||
allErrs = append(allErrs, validateKMSConfiguration(provider.KMS, path.Child("kms"), kmsProviderNames, reload)...)
|
||||
kmsProviderNames.Insert(provider.KMS.Name)
|
||||
case provider.AESGCM != nil:
|
||||
allErrs = append(allErrs, validateKeys(provider.AESGCM.Keys, path.Child("aesgcm").Child("keys"), aesKeySizes)...)
|
||||
case provider.AESCBC != nil:
|
||||
allErrs = append(allErrs, validateKeys(provider.AESCBC.Keys, path.Child("aescbc").Child("keys"), aesKeySizes)...)
|
||||
case provider.Secretbox != nil:
|
||||
allErrs = append(allErrs, validateKeys(provider.Secretbox.Keys, path.Child("secretbox").Child("keys"), secretBoxKeySizes)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateSingleProvider(provider config.ProviderConfiguration, fieldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
found := 0
|
||||
|
||||
if provider.KMS != nil {
|
||||
found++
|
||||
}
|
||||
if provider.AESGCM != nil {
|
||||
found++
|
||||
}
|
||||
if provider.AESCBC != nil {
|
||||
found++
|
||||
}
|
||||
if provider.Secretbox != nil {
|
||||
found++
|
||||
}
|
||||
if provider.Identity != nil {
|
||||
found++
|
||||
}
|
||||
|
||||
if found == 0 {
|
||||
return append(allErrs, field.Invalid(fieldPath, provider, "provider does not contain any of the expected providers: KMS, AESGCM, AESCBC, Secretbox, Identity"))
|
||||
}
|
||||
|
||||
if found > 1 {
|
||||
return append(allErrs, field.Invalid(fieldPath, provider, moreThanOneElementErr))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateKeys(keys []config.Key, fieldPath *field.Path, expectedLen []int) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if len(keys) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf(atLeastOneRequiredErrFmt, "keys")))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
for i, key := range keys {
|
||||
allErrs = append(allErrs, validateKey(key, fieldPath.Index(i), expectedLen)...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateKey(key config.Key, fieldPath *field.Path, expectedLen []int) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if key.Name == "" {
|
||||
allErrs = append(allErrs, field.Required(fieldPath.Child("name"), fmt.Sprintf(mandatoryFieldErrFmt, "name", "key")))
|
||||
}
|
||||
|
||||
if key.Secret == "" {
|
||||
allErrs = append(allErrs, field.Required(fieldPath.Child("secret"), fmt.Sprintf(mandatoryFieldErrFmt, "secret", "key")))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
secret, err := base64.StdEncoding.DecodeString(key.Secret)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath.Child("secret"), "REDACTED", base64EncodingErr))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
lenMatched := false
|
||||
for _, l := range expectedLen {
|
||||
if len(secret) == l {
|
||||
lenMatched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !lenMatched {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath.Child("secret"), "REDACTED", fmt.Sprintf(keyLenErrFmt, len(secret), expectedLen)))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateKMSConfiguration(c *config.KMSConfiguration, fieldPath *field.Path, kmsProviderNames sets.String, reload bool) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, validateKMSConfigName(c, fieldPath.Child("name"), kmsProviderNames, reload)...)
|
||||
allErrs = append(allErrs, validateKMSTimeout(c, fieldPath.Child("timeout"))...)
|
||||
allErrs = append(allErrs, validateKMSEndpoint(c, fieldPath.Child("endpoint"))...)
|
||||
allErrs = append(allErrs, validateKMSCacheSize(c, fieldPath.Child("cachesize"))...)
|
||||
allErrs = append(allErrs, validateKMSAPIVersion(c, fieldPath.Child("apiVersion"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateKMSCacheSize(c *config.KMSConfiguration, fieldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if *c.CacheSize == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath, *c.CacheSize, fmt.Sprintf(nonZeroErrFmt, "cachesize")))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateKMSTimeout(c *config.KMSConfiguration, fieldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if c.Timeout.Duration <= 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath, c.Timeout, fmt.Sprintf(zeroOrNegativeErrFmt, "timeout")))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateKMSEndpoint(c *config.KMSConfiguration, fieldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(c.Endpoint) == 0 {
|
||||
return append(allErrs, field.Invalid(fieldPath, "", fmt.Sprintf(mandatoryFieldErrFmt, "endpoint", "kms")))
|
||||
}
|
||||
|
||||
u, err := url.Parse(c.Endpoint)
|
||||
if err != nil {
|
||||
return append(allErrs, field.Invalid(fieldPath, c.Endpoint, fmt.Sprintf("invalid endpoint for kms provider, error: %v", err)))
|
||||
}
|
||||
|
||||
if u.Scheme != "unix" {
|
||||
return append(allErrs, field.Invalid(fieldPath, c.Endpoint, fmt.Sprintf(unsupportedSchemeErrFmt, u.Scheme)))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateKMSAPIVersion(c *config.KMSConfiguration, fieldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if c.APIVersion != "v1" && c.APIVersion != "v2" {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath, c.APIVersion, fmt.Sprintf(unsupportedKMSAPIVersionErrFmt, "apiVersion")))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateKMSConfigName(c *config.KMSConfiguration, fieldPath *field.Path, kmsProviderNames sets.String, reload bool) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if c.Name == "" {
|
||||
allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf(mandatoryFieldErrFmt, "name", "provider")))
|
||||
}
|
||||
|
||||
// kms v2 providers are not allowed to have a ":" in their name
|
||||
if c.APIVersion != "v1" && strings.Contains(c.Name, ":") {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath, c.Name, fmt.Sprintf(invalidKMSConfigNameErrFmt, c.Name)))
|
||||
}
|
||||
|
||||
// kms v2 providers name must always be unique across all kms providers (v1 and v2)
|
||||
// kms v1 provider names must be unique across all kms providers (v1 and v2) when hot reloading of encryption configuration is enabled (reload=true)
|
||||
if reload || c.APIVersion != "v1" {
|
||||
if kmsProviderNames.Has(c.Name) {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath, c.Name, fmt.Sprintf(duplicateKMSConfigNameErrFmt, c.Name)))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
228
vendor/k8s.io/apiserver/pkg/apis/config/zz_generated.deepcopy.go
generated
vendored
Normal file
228
vendor/k8s.io/apiserver/pkg/apis/config/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +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 config
|
||||
|
||||
import (
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
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 *AESConfiguration) DeepCopyInto(out *AESConfiguration) {
|
||||
*out = *in
|
||||
if in.Keys != nil {
|
||||
in, out := &in.Keys, &out.Keys
|
||||
*out = make([]Key, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AESConfiguration.
|
||||
func (in *AESConfiguration) DeepCopy() *AESConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AESConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EncryptionConfiguration) DeepCopyInto(out *EncryptionConfiguration) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Resources != nil {
|
||||
in, out := &in.Resources, &out.Resources
|
||||
*out = make([]ResourceConfiguration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EncryptionConfiguration.
|
||||
func (in *EncryptionConfiguration) DeepCopy() *EncryptionConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EncryptionConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *EncryptionConfiguration) 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 *IdentityConfiguration) DeepCopyInto(out *IdentityConfiguration) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityConfiguration.
|
||||
func (in *IdentityConfiguration) DeepCopy() *IdentityConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IdentityConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KMSConfiguration) DeepCopyInto(out *KMSConfiguration) {
|
||||
*out = *in
|
||||
if in.CacheSize != nil {
|
||||
in, out := &in.CacheSize, &out.CacheSize
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.Timeout != nil {
|
||||
in, out := &in.Timeout, &out.Timeout
|
||||
*out = new(v1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KMSConfiguration.
|
||||
func (in *KMSConfiguration) DeepCopy() *KMSConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KMSConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Key) DeepCopyInto(out *Key) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Key.
|
||||
func (in *Key) DeepCopy() *Key {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Key)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProviderConfiguration) DeepCopyInto(out *ProviderConfiguration) {
|
||||
*out = *in
|
||||
if in.AESGCM != nil {
|
||||
in, out := &in.AESGCM, &out.AESGCM
|
||||
*out = new(AESConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AESCBC != nil {
|
||||
in, out := &in.AESCBC, &out.AESCBC
|
||||
*out = new(AESConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Secretbox != nil {
|
||||
in, out := &in.Secretbox, &out.Secretbox
|
||||
*out = new(SecretboxConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Identity != nil {
|
||||
in, out := &in.Identity, &out.Identity
|
||||
*out = new(IdentityConfiguration)
|
||||
**out = **in
|
||||
}
|
||||
if in.KMS != nil {
|
||||
in, out := &in.KMS, &out.KMS
|
||||
*out = new(KMSConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfiguration.
|
||||
func (in *ProviderConfiguration) DeepCopy() *ProviderConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProviderConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResourceConfiguration) DeepCopyInto(out *ResourceConfiguration) {
|
||||
*out = *in
|
||||
if in.Resources != nil {
|
||||
in, out := &in.Resources, &out.Resources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Providers != nil {
|
||||
in, out := &in.Providers, &out.Providers
|
||||
*out = make([]ProviderConfiguration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceConfiguration.
|
||||
func (in *ResourceConfiguration) DeepCopy() *ResourceConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ResourceConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecretboxConfiguration) DeepCopyInto(out *SecretboxConfiguration) {
|
||||
*out = *in
|
||||
if in.Keys != nil {
|
||||
in, out := &in.Keys, &out.Keys
|
||||
*out = make([]Key, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretboxConfiguration.
|
||||
func (in *SecretboxConfiguration) DeepCopy() *SecretboxConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SecretboxConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
573
vendor/k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap/default.go
generated
vendored
Normal file
573
vendor/k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap/default.go
generated
vendored
Normal file
@ -0,0 +1,573 @@
|
||||
/*
|
||||
Copyright 2019 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 bootstrap
|
||||
|
||||
import (
|
||||
coordinationv1 "k8s.io/api/coordination/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
flowcontrol "k8s.io/api/flowcontrol/v1beta3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
// The objects that define an apiserver's initial behavior. The
|
||||
// registered defaulting procedures make no changes to these
|
||||
// particular objects (this is verified in the unit tests of the
|
||||
// internalbootstrap package; it can not be verified in this package
|
||||
// because that would require importing k8s.io/kubernetes).
|
||||
var (
|
||||
MandatoryPriorityLevelConfigurations = []*flowcontrol.PriorityLevelConfiguration{
|
||||
MandatoryPriorityLevelConfigurationCatchAll,
|
||||
MandatoryPriorityLevelConfigurationExempt,
|
||||
}
|
||||
MandatoryFlowSchemas = []*flowcontrol.FlowSchema{
|
||||
MandatoryFlowSchemaExempt,
|
||||
MandatoryFlowSchemaCatchAll,
|
||||
}
|
||||
)
|
||||
|
||||
// The objects that define the current suggested additional configuration
|
||||
var (
|
||||
SuggestedPriorityLevelConfigurations = []*flowcontrol.PriorityLevelConfiguration{
|
||||
// "system" priority-level is for the system components that affects self-maintenance of the
|
||||
// cluster and the availability of those running pods in the cluster, including kubelet and
|
||||
// kube-proxy.
|
||||
SuggestedPriorityLevelConfigurationSystem,
|
||||
// "node-high" priority-level is for the node health reporting. It is separated from "system"
|
||||
// to make sure that nodes are able to report their health even if kube-apiserver is not capable of
|
||||
// handling load caused by pod startup (fetching secrets, events etc).
|
||||
// NOTE: In large clusters 50% - 90% of all API calls use this priority-level.
|
||||
SuggestedPriorityLevelConfigurationNodeHigh,
|
||||
// "leader-election" is dedicated for controllers' leader-election, which majorly affects the
|
||||
// availability of any controller runs in the cluster.
|
||||
SuggestedPriorityLevelConfigurationLeaderElection,
|
||||
// "workload-high" is used by those workloads with higher priority but their failure won't directly
|
||||
// impact the existing running pods in the cluster, which includes kube-scheduler, and those well-known
|
||||
// built-in workloads such as "deployments", "replicasets" and other low-level custom workload which
|
||||
// is important for the cluster.
|
||||
SuggestedPriorityLevelConfigurationWorkloadHigh,
|
||||
// "workload-low" is used by those workloads with lower priority which availability only has a
|
||||
// minor impact on the cluster.
|
||||
SuggestedPriorityLevelConfigurationWorkloadLow,
|
||||
// "global-default" serves the rest traffic not handled by the other suggested flow-schemas above.
|
||||
SuggestedPriorityLevelConfigurationGlobalDefault,
|
||||
}
|
||||
SuggestedFlowSchemas = []*flowcontrol.FlowSchema{
|
||||
SuggestedFlowSchemaSystemNodes, // references "system" priority-level
|
||||
SuggestedFlowSchemaSystemNodeHigh, // references "node-high" priority-level
|
||||
SuggestedFlowSchemaProbes, // references "exempt" priority-level
|
||||
SuggestedFlowSchemaSystemLeaderElection, // references "leader-election" priority-level
|
||||
SuggestedFlowSchemaWorkloadLeaderElection, // references "leader-election" priority-level
|
||||
SuggestedFlowSchemaEndpointsController, // references "workload-high" priority-level
|
||||
SuggestedFlowSchemaKubeControllerManager, // references "workload-high" priority-level
|
||||
SuggestedFlowSchemaKubeScheduler, // references "workload-high" priority-level
|
||||
SuggestedFlowSchemaKubeSystemServiceAccounts, // references "workload-high" priority-level
|
||||
SuggestedFlowSchemaServiceAccounts, // references "workload-low" priority-level
|
||||
SuggestedFlowSchemaGlobalDefault, // references "global-default" priority-level
|
||||
}
|
||||
)
|
||||
|
||||
// Mandatory PriorityLevelConfiguration objects
|
||||
var (
|
||||
MandatoryPriorityLevelConfigurationExempt = newPriorityLevelConfiguration(
|
||||
flowcontrol.PriorityLevelConfigurationNameExempt,
|
||||
flowcontrol.PriorityLevelConfigurationSpec{
|
||||
Type: flowcontrol.PriorityLevelEnablementExempt,
|
||||
},
|
||||
)
|
||||
MandatoryPriorityLevelConfigurationCatchAll = newPriorityLevelConfiguration(
|
||||
flowcontrol.PriorityLevelConfigurationNameCatchAll,
|
||||
flowcontrol.PriorityLevelConfigurationSpec{
|
||||
Type: flowcontrol.PriorityLevelEnablementLimited,
|
||||
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
|
||||
NominalConcurrencyShares: 5,
|
||||
LendablePercent: pointer.Int32(0),
|
||||
LimitResponse: flowcontrol.LimitResponse{
|
||||
Type: flowcontrol.LimitResponseTypeReject,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
// Mandatory FlowSchema objects
|
||||
var (
|
||||
// "exempt" priority-level is used for preventing priority inversion and ensuring that sysadmin
|
||||
// requests are always possible.
|
||||
MandatoryFlowSchemaExempt = newFlowSchema(
|
||||
"exempt",
|
||||
flowcontrol.PriorityLevelConfigurationNameExempt,
|
||||
1, // matchingPrecedence
|
||||
"", // distinguisherMethodType
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: groups(user.SystemPrivilegedGroup),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{
|
||||
resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.APIGroupAll},
|
||||
[]string{flowcontrol.ResourceAll},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
true,
|
||||
),
|
||||
},
|
||||
NonResourceRules: []flowcontrol.NonResourcePolicyRule{
|
||||
nonResourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.NonResourceAll},
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
// "catch-all" priority-level only gets a minimal positive share of concurrency and won't be reaching
|
||||
// ideally unless you intentionally deleted the suggested "global-default".
|
||||
MandatoryFlowSchemaCatchAll = newFlowSchema(
|
||||
flowcontrol.FlowSchemaNameCatchAll,
|
||||
flowcontrol.PriorityLevelConfigurationNameCatchAll,
|
||||
10000, // matchingPrecedence
|
||||
flowcontrol.FlowDistinguisherMethodByUserType, // distinguisherMethodType
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: groups(user.AllUnauthenticated, user.AllAuthenticated),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{
|
||||
resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.APIGroupAll},
|
||||
[]string{flowcontrol.ResourceAll},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
true,
|
||||
),
|
||||
},
|
||||
NonResourceRules: []flowcontrol.NonResourcePolicyRule{
|
||||
nonResourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.NonResourceAll},
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
// Suggested PriorityLevelConfiguration objects
|
||||
var (
|
||||
// system priority-level
|
||||
SuggestedPriorityLevelConfigurationSystem = newPriorityLevelConfiguration(
|
||||
"system",
|
||||
flowcontrol.PriorityLevelConfigurationSpec{
|
||||
Type: flowcontrol.PriorityLevelEnablementLimited,
|
||||
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
|
||||
NominalConcurrencyShares: 30,
|
||||
LendablePercent: pointer.Int32(33),
|
||||
LimitResponse: flowcontrol.LimitResponse{
|
||||
Type: flowcontrol.LimitResponseTypeQueue,
|
||||
Queuing: &flowcontrol.QueuingConfiguration{
|
||||
Queues: 64,
|
||||
HandSize: 6,
|
||||
QueueLengthLimit: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
SuggestedPriorityLevelConfigurationNodeHigh = newPriorityLevelConfiguration(
|
||||
"node-high",
|
||||
flowcontrol.PriorityLevelConfigurationSpec{
|
||||
Type: flowcontrol.PriorityLevelEnablementLimited,
|
||||
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
|
||||
NominalConcurrencyShares: 40,
|
||||
LendablePercent: pointer.Int32(25),
|
||||
LimitResponse: flowcontrol.LimitResponse{
|
||||
Type: flowcontrol.LimitResponseTypeQueue,
|
||||
Queuing: &flowcontrol.QueuingConfiguration{
|
||||
Queues: 64,
|
||||
HandSize: 6,
|
||||
QueueLengthLimit: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
// leader-election priority-level
|
||||
SuggestedPriorityLevelConfigurationLeaderElection = newPriorityLevelConfiguration(
|
||||
"leader-election",
|
||||
flowcontrol.PriorityLevelConfigurationSpec{
|
||||
Type: flowcontrol.PriorityLevelEnablementLimited,
|
||||
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
|
||||
NominalConcurrencyShares: 10,
|
||||
LendablePercent: pointer.Int32(0),
|
||||
LimitResponse: flowcontrol.LimitResponse{
|
||||
Type: flowcontrol.LimitResponseTypeQueue,
|
||||
Queuing: &flowcontrol.QueuingConfiguration{
|
||||
Queues: 16,
|
||||
HandSize: 4,
|
||||
QueueLengthLimit: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
// workload-high priority-level
|
||||
SuggestedPriorityLevelConfigurationWorkloadHigh = newPriorityLevelConfiguration(
|
||||
"workload-high",
|
||||
flowcontrol.PriorityLevelConfigurationSpec{
|
||||
Type: flowcontrol.PriorityLevelEnablementLimited,
|
||||
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
|
||||
NominalConcurrencyShares: 40,
|
||||
LendablePercent: pointer.Int32(50),
|
||||
LimitResponse: flowcontrol.LimitResponse{
|
||||
Type: flowcontrol.LimitResponseTypeQueue,
|
||||
Queuing: &flowcontrol.QueuingConfiguration{
|
||||
Queues: 128,
|
||||
HandSize: 6,
|
||||
QueueLengthLimit: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
// workload-low priority-level
|
||||
SuggestedPriorityLevelConfigurationWorkloadLow = newPriorityLevelConfiguration(
|
||||
"workload-low",
|
||||
flowcontrol.PriorityLevelConfigurationSpec{
|
||||
Type: flowcontrol.PriorityLevelEnablementLimited,
|
||||
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
|
||||
NominalConcurrencyShares: 100,
|
||||
LendablePercent: pointer.Int32(90),
|
||||
LimitResponse: flowcontrol.LimitResponse{
|
||||
Type: flowcontrol.LimitResponseTypeQueue,
|
||||
Queuing: &flowcontrol.QueuingConfiguration{
|
||||
Queues: 128,
|
||||
HandSize: 6,
|
||||
QueueLengthLimit: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
// global-default priority-level
|
||||
SuggestedPriorityLevelConfigurationGlobalDefault = newPriorityLevelConfiguration(
|
||||
"global-default",
|
||||
flowcontrol.PriorityLevelConfigurationSpec{
|
||||
Type: flowcontrol.PriorityLevelEnablementLimited,
|
||||
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
|
||||
NominalConcurrencyShares: 20,
|
||||
LendablePercent: pointer.Int32(50),
|
||||
LimitResponse: flowcontrol.LimitResponse{
|
||||
Type: flowcontrol.LimitResponseTypeQueue,
|
||||
Queuing: &flowcontrol.QueuingConfiguration{
|
||||
Queues: 128,
|
||||
HandSize: 6,
|
||||
QueueLengthLimit: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
// Suggested FlowSchema objects.
|
||||
// Ordered by matching precedence, so that their interactions are easier
|
||||
// to follow while reading this source.
|
||||
var (
|
||||
// the following flow schema exempts probes
|
||||
SuggestedFlowSchemaProbes = newFlowSchema(
|
||||
"probes", "exempt", 2,
|
||||
"", // distinguisherMethodType
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: groups(user.AllUnauthenticated, user.AllAuthenticated),
|
||||
NonResourceRules: []flowcontrol.NonResourcePolicyRule{
|
||||
nonResourceRule(
|
||||
[]string{"get"},
|
||||
[]string{"/healthz", "/readyz", "/livez"}),
|
||||
},
|
||||
},
|
||||
)
|
||||
SuggestedFlowSchemaSystemLeaderElection = newFlowSchema(
|
||||
"system-leader-election", "leader-election", 100,
|
||||
flowcontrol.FlowDistinguisherMethodByUserType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: append(
|
||||
users(user.KubeControllerManager, user.KubeScheduler),
|
||||
kubeSystemServiceAccount(flowcontrol.NameAll)...),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{
|
||||
resourceRule(
|
||||
[]string{"get", "create", "update"},
|
||||
[]string{coordinationv1.GroupName},
|
||||
[]string{"leases"},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
false),
|
||||
},
|
||||
},
|
||||
)
|
||||
// We add an explicit rule for endpoint-controller with high precedence
|
||||
// to ensure that those calls won't get caught by the following
|
||||
// <workload-leader-election> flow-schema.
|
||||
//
|
||||
// TODO(#80289): Get rid of this rule once we get rid of support for
|
||||
// using endpoints and configmaps objects for leader election.
|
||||
SuggestedFlowSchemaEndpointsController = newFlowSchema(
|
||||
"endpoint-controller", "workload-high", 150,
|
||||
flowcontrol.FlowDistinguisherMethodByUserType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: append(
|
||||
users(user.KubeControllerManager),
|
||||
kubeSystemServiceAccount("endpoint-controller", "endpointslicemirroring-controller")...),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{
|
||||
resourceRule(
|
||||
[]string{"get", "create", "update"},
|
||||
[]string{corev1.GroupName},
|
||||
[]string{"endpoints"},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
false),
|
||||
},
|
||||
},
|
||||
)
|
||||
// TODO(#80289): Get rid of this rule once we get rid of support for
|
||||
// using endpoints and configmaps objects for leader election.
|
||||
SuggestedFlowSchemaWorkloadLeaderElection = newFlowSchema(
|
||||
"workload-leader-election", "leader-election", 200,
|
||||
flowcontrol.FlowDistinguisherMethodByUserType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: kubeSystemServiceAccount(flowcontrol.NameAll),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{
|
||||
resourceRule(
|
||||
[]string{"get", "create", "update"},
|
||||
[]string{corev1.GroupName},
|
||||
[]string{"endpoints", "configmaps"},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
false),
|
||||
resourceRule(
|
||||
[]string{"get", "create", "update"},
|
||||
[]string{coordinationv1.GroupName},
|
||||
[]string{"leases"},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
false),
|
||||
},
|
||||
},
|
||||
)
|
||||
SuggestedFlowSchemaSystemNodeHigh = newFlowSchema(
|
||||
"system-node-high", "node-high", 400,
|
||||
flowcontrol.FlowDistinguisherMethodByUserType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: groups(user.NodesGroup), // the nodes group
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{
|
||||
resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{corev1.GroupName},
|
||||
[]string{"nodes", "nodes/status"},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
true),
|
||||
resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{coordinationv1.GroupName},
|
||||
[]string{"leases"},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
false),
|
||||
},
|
||||
},
|
||||
)
|
||||
SuggestedFlowSchemaSystemNodes = newFlowSchema(
|
||||
"system-nodes", "system", 500,
|
||||
flowcontrol.FlowDistinguisherMethodByUserType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: groups(user.NodesGroup), // the nodes group
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.APIGroupAll},
|
||||
[]string{flowcontrol.ResourceAll},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
true)},
|
||||
NonResourceRules: []flowcontrol.NonResourcePolicyRule{
|
||||
nonResourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.NonResourceAll}),
|
||||
},
|
||||
},
|
||||
)
|
||||
SuggestedFlowSchemaKubeControllerManager = newFlowSchema(
|
||||
"kube-controller-manager", "workload-high", 800,
|
||||
flowcontrol.FlowDistinguisherMethodByNamespaceType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: users(user.KubeControllerManager),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.APIGroupAll},
|
||||
[]string{flowcontrol.ResourceAll},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
true)},
|
||||
NonResourceRules: []flowcontrol.NonResourcePolicyRule{
|
||||
nonResourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.NonResourceAll}),
|
||||
},
|
||||
},
|
||||
)
|
||||
SuggestedFlowSchemaKubeScheduler = newFlowSchema(
|
||||
"kube-scheduler", "workload-high", 800,
|
||||
flowcontrol.FlowDistinguisherMethodByNamespaceType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: users(user.KubeScheduler),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.APIGroupAll},
|
||||
[]string{flowcontrol.ResourceAll},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
true)},
|
||||
NonResourceRules: []flowcontrol.NonResourcePolicyRule{
|
||||
nonResourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.NonResourceAll}),
|
||||
},
|
||||
},
|
||||
)
|
||||
SuggestedFlowSchemaKubeSystemServiceAccounts = newFlowSchema(
|
||||
"kube-system-service-accounts", "workload-high", 900,
|
||||
flowcontrol.FlowDistinguisherMethodByNamespaceType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: kubeSystemServiceAccount(flowcontrol.NameAll),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.APIGroupAll},
|
||||
[]string{flowcontrol.ResourceAll},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
true)},
|
||||
NonResourceRules: []flowcontrol.NonResourcePolicyRule{
|
||||
nonResourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.NonResourceAll}),
|
||||
},
|
||||
},
|
||||
)
|
||||
SuggestedFlowSchemaServiceAccounts = newFlowSchema(
|
||||
"service-accounts", "workload-low", 9000,
|
||||
flowcontrol.FlowDistinguisherMethodByUserType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: groups(serviceaccount.AllServiceAccountsGroup),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.APIGroupAll},
|
||||
[]string{flowcontrol.ResourceAll},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
true)},
|
||||
NonResourceRules: []flowcontrol.NonResourcePolicyRule{
|
||||
nonResourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.NonResourceAll}),
|
||||
},
|
||||
},
|
||||
)
|
||||
SuggestedFlowSchemaGlobalDefault = newFlowSchema(
|
||||
"global-default", "global-default", 9900,
|
||||
flowcontrol.FlowDistinguisherMethodByUserType,
|
||||
flowcontrol.PolicyRulesWithSubjects{
|
||||
Subjects: groups(user.AllUnauthenticated, user.AllAuthenticated),
|
||||
ResourceRules: []flowcontrol.ResourcePolicyRule{resourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.APIGroupAll},
|
||||
[]string{flowcontrol.ResourceAll},
|
||||
[]string{flowcontrol.NamespaceEvery},
|
||||
true)},
|
||||
NonResourceRules: []flowcontrol.NonResourcePolicyRule{
|
||||
nonResourceRule(
|
||||
[]string{flowcontrol.VerbAll},
|
||||
[]string{flowcontrol.NonResourceAll}),
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
func newPriorityLevelConfiguration(name string, spec flowcontrol.PriorityLevelConfigurationSpec) *flowcontrol.PriorityLevelConfiguration {
|
||||
return &flowcontrol.PriorityLevelConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Annotations: map[string]string{
|
||||
flowcontrol.AutoUpdateAnnotationKey: "true",
|
||||
},
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
}
|
||||
|
||||
func newFlowSchema(name, plName string, matchingPrecedence int32, dmType flowcontrol.FlowDistinguisherMethodType, rules ...flowcontrol.PolicyRulesWithSubjects) *flowcontrol.FlowSchema {
|
||||
var dm *flowcontrol.FlowDistinguisherMethod
|
||||
if dmType != "" {
|
||||
dm = &flowcontrol.FlowDistinguisherMethod{Type: dmType}
|
||||
}
|
||||
return &flowcontrol.FlowSchema{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Annotations: map[string]string{
|
||||
flowcontrol.AutoUpdateAnnotationKey: "true",
|
||||
},
|
||||
},
|
||||
Spec: flowcontrol.FlowSchemaSpec{
|
||||
PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{
|
||||
Name: plName,
|
||||
},
|
||||
MatchingPrecedence: matchingPrecedence,
|
||||
DistinguisherMethod: dm,
|
||||
Rules: rules},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func groups(names ...string) []flowcontrol.Subject {
|
||||
ans := make([]flowcontrol.Subject, len(names))
|
||||
for idx, name := range names {
|
||||
ans[idx] = flowcontrol.Subject{
|
||||
Kind: flowcontrol.SubjectKindGroup,
|
||||
Group: &flowcontrol.GroupSubject{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func users(names ...string) []flowcontrol.Subject {
|
||||
ans := make([]flowcontrol.Subject, len(names))
|
||||
for idx, name := range names {
|
||||
ans[idx] = flowcontrol.Subject{
|
||||
Kind: flowcontrol.SubjectKindUser,
|
||||
User: &flowcontrol.UserSubject{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func kubeSystemServiceAccount(names ...string) []flowcontrol.Subject {
|
||||
subjects := []flowcontrol.Subject{}
|
||||
for _, name := range names {
|
||||
subjects = append(subjects, flowcontrol.Subject{
|
||||
Kind: flowcontrol.SubjectKindServiceAccount,
|
||||
ServiceAccount: &flowcontrol.ServiceAccountSubject{
|
||||
Name: name,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
})
|
||||
}
|
||||
return subjects
|
||||
}
|
||||
|
||||
func resourceRule(verbs []string, groups []string, resources []string, namespaces []string, clusterScoped bool) flowcontrol.ResourcePolicyRule {
|
||||
return flowcontrol.ResourcePolicyRule{
|
||||
Verbs: verbs,
|
||||
APIGroups: groups,
|
||||
Resources: resources,
|
||||
Namespaces: namespaces,
|
||||
ClusterScope: clusterScoped,
|
||||
}
|
||||
}
|
||||
|
||||
func nonResourceRule(verbs []string, nonResourceURLs []string) flowcontrol.NonResourcePolicyRule {
|
||||
return flowcontrol.NonResourcePolicyRule{Verbs: verbs, NonResourceURLs: nonResourceURLs}
|
||||
}
|
245
vendor/k8s.io/apiserver/pkg/audit/policy/checker.go
generated
vendored
Normal file
245
vendor/k8s.io/apiserver/pkg/audit/policy/checker.go
generated
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
Copyright 2017 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"
|
||||
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
auditinternal "k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultAuditLevel is the default level to audit at, if no policy rules are matched.
|
||||
DefaultAuditLevel = audit.LevelNone
|
||||
)
|
||||
|
||||
// NewPolicyRuleEvaluator creates a new policy rule evaluator.
|
||||
func NewPolicyRuleEvaluator(policy *audit.Policy) auditinternal.PolicyRuleEvaluator {
|
||||
for i, rule := range policy.Rules {
|
||||
policy.Rules[i].OmitStages = unionStages(policy.OmitStages, rule.OmitStages)
|
||||
}
|
||||
return &policyRuleEvaluator{*policy}
|
||||
}
|
||||
|
||||
func unionStages(stageLists ...[]audit.Stage) []audit.Stage {
|
||||
m := make(map[audit.Stage]bool)
|
||||
for _, sl := range stageLists {
|
||||
for _, s := range sl {
|
||||
m[s] = true
|
||||
}
|
||||
}
|
||||
result := make([]audit.Stage, 0, len(m))
|
||||
for key := range m {
|
||||
result = append(result, key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// NewFakePolicyRuleEvaluator creates a fake policy rule evaluator that returns
|
||||
// a constant level for all requests (for testing).
|
||||
func NewFakePolicyRuleEvaluator(level audit.Level, stage []audit.Stage) auditinternal.PolicyRuleEvaluator {
|
||||
return &fakePolicyRuleEvaluator{level, stage}
|
||||
}
|
||||
|
||||
type policyRuleEvaluator struct {
|
||||
audit.Policy
|
||||
}
|
||||
|
||||
func (p *policyRuleEvaluator) EvaluatePolicyRule(attrs authorizer.Attributes) auditinternal.RequestAuditConfigWithLevel {
|
||||
for _, rule := range p.Rules {
|
||||
if ruleMatches(&rule, attrs) {
|
||||
return auditinternal.RequestAuditConfigWithLevel{
|
||||
Level: rule.Level,
|
||||
RequestAuditConfig: auditinternal.RequestAuditConfig{
|
||||
OmitStages: rule.OmitStages,
|
||||
OmitManagedFields: isOmitManagedFields(&rule, p.OmitManagedFields),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return auditinternal.RequestAuditConfigWithLevel{
|
||||
Level: DefaultAuditLevel,
|
||||
RequestAuditConfig: auditinternal.RequestAuditConfig{
|
||||
OmitStages: p.OmitStages,
|
||||
OmitManagedFields: p.OmitManagedFields,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// isOmitManagedFields returns whether to omit managed fields from the request
|
||||
// and response bodies from being written to the API audit log.
|
||||
// If a user specifies OmitManagedFields inside a policy rule, that overrides
|
||||
// the global policy default in Policy.OmitManagedFields.
|
||||
func isOmitManagedFields(policyRule *audit.PolicyRule, policyDefault bool) bool {
|
||||
if policyRule.OmitManagedFields == nil {
|
||||
return policyDefault
|
||||
}
|
||||
|
||||
return *policyRule.OmitManagedFields
|
||||
}
|
||||
|
||||
// Check whether the rule matches the request attrs.
|
||||
func ruleMatches(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
|
||||
user := attrs.GetUser()
|
||||
if len(r.Users) > 0 {
|
||||
if user == nil || !hasString(r.Users, user.GetName()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(r.UserGroups) > 0 {
|
||||
if user == nil {
|
||||
return false
|
||||
}
|
||||
matched := false
|
||||
for _, group := range user.GetGroups() {
|
||||
if hasString(r.UserGroups, group) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(r.Verbs) > 0 {
|
||||
if !hasString(r.Verbs, attrs.GetVerb()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Namespaces) > 0 || len(r.Resources) > 0 {
|
||||
return ruleMatchesResource(r, attrs)
|
||||
}
|
||||
|
||||
if len(r.NonResourceURLs) > 0 {
|
||||
return ruleMatchesNonResource(r, attrs)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Check whether the rule's non-resource URLs match the request attrs.
|
||||
func ruleMatchesNonResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
|
||||
if attrs.IsResourceRequest() {
|
||||
return false
|
||||
}
|
||||
|
||||
path := attrs.GetPath()
|
||||
for _, spec := range r.NonResourceURLs {
|
||||
if pathMatches(path, spec) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether the path matches the path specification.
|
||||
func pathMatches(path, spec string) bool {
|
||||
// Allow wildcard match
|
||||
if spec == "*" {
|
||||
return true
|
||||
}
|
||||
// Allow exact match
|
||||
if spec == path {
|
||||
return true
|
||||
}
|
||||
// Allow a trailing * subpath match
|
||||
if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether the rule's resource fields match the request attrs.
|
||||
func ruleMatchesResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
|
||||
if !attrs.IsResourceRequest() {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(r.Namespaces) > 0 {
|
||||
if !hasString(r.Namespaces, attrs.GetNamespace()) { // Non-namespaced resources use the empty string.
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(r.Resources) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
apiGroup := attrs.GetAPIGroup()
|
||||
resource := attrs.GetResource()
|
||||
subresource := attrs.GetSubresource()
|
||||
combinedResource := resource
|
||||
// If subresource, the resource in the policy must match "(resource)/(subresource)"
|
||||
if subresource != "" {
|
||||
combinedResource = resource + "/" + subresource
|
||||
}
|
||||
|
||||
name := attrs.GetName()
|
||||
|
||||
for _, gr := range r.Resources {
|
||||
if gr.Group == apiGroup {
|
||||
if len(gr.Resources) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, res := range gr.Resources {
|
||||
if len(gr.ResourceNames) == 0 || hasString(gr.ResourceNames, name) {
|
||||
// match "*"
|
||||
if res == combinedResource || res == "*" {
|
||||
return true
|
||||
}
|
||||
// match "*/subresource"
|
||||
if len(subresource) > 0 && strings.HasPrefix(res, "*/") && subresource == strings.TrimPrefix(res, "*/") {
|
||||
return true
|
||||
}
|
||||
// match "resource/*"
|
||||
if strings.HasSuffix(res, "/*") && resource == strings.TrimSuffix(res, "/*") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Utility function to check whether a string slice contains a string.
|
||||
func hasString(slice []string, value string) bool {
|
||||
for _, s := range slice {
|
||||
if s == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type fakePolicyRuleEvaluator struct {
|
||||
level audit.Level
|
||||
stage []audit.Stage
|
||||
}
|
||||
|
||||
func (f *fakePolicyRuleEvaluator) EvaluatePolicyRule(_ authorizer.Attributes) auditinternal.RequestAuditConfigWithLevel {
|
||||
return auditinternal.RequestAuditConfigWithLevel{
|
||||
Level: f.level,
|
||||
RequestAuditConfig: auditinternal.RequestAuditConfig{
|
||||
OmitStages: f.stage,
|
||||
},
|
||||
}
|
||||
}
|
101
vendor/k8s.io/apiserver/pkg/audit/policy/reader.go
generated
vendored
Normal file
101
vendor/k8s.io/apiserver/pkg/audit/policy/reader.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright 2017 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"
|
||||
"io/ioutil"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
|
||||
"k8s.io/apiserver/pkg/apis/audit/validation"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
apiGroupVersions = []schema.GroupVersion{
|
||||
auditv1.SchemeGroupVersion,
|
||||
}
|
||||
apiGroupVersionSet = map[schema.GroupVersion]bool{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, gv := range apiGroupVersions {
|
||||
apiGroupVersionSet[gv] = true
|
||||
}
|
||||
}
|
||||
|
||||
func LoadPolicyFromFile(filePath string) (*auditinternal.Policy, error) {
|
||||
if filePath == "" {
|
||||
return nil, fmt.Errorf("file path not specified")
|
||||
}
|
||||
policyDef, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file path %q: %+v", filePath, err)
|
||||
}
|
||||
|
||||
ret, err := LoadPolicyFromBytes(policyDef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: from file %v", err.Error(), filePath)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func LoadPolicyFromBytes(policyDef []byte) (*auditinternal.Policy, error) {
|
||||
policy := &auditinternal.Policy{}
|
||||
strictDecoder := serializer.NewCodecFactory(audit.Scheme, serializer.EnableStrict).UniversalDecoder()
|
||||
|
||||
// Try strict decoding first.
|
||||
_, gvk, err := strictDecoder.Decode(policyDef, nil, policy)
|
||||
if err != nil {
|
||||
if !runtime.IsStrictDecodingError(err) {
|
||||
return nil, fmt.Errorf("failed decoding: %w", err)
|
||||
}
|
||||
var (
|
||||
lenientDecoder = audit.Codecs.UniversalDecoder(apiGroupVersions...)
|
||||
lenientErr error
|
||||
)
|
||||
_, gvk, lenientErr = lenientDecoder.Decode(policyDef, nil, policy)
|
||||
if lenientErr != nil {
|
||||
return nil, fmt.Errorf("failed lenient decoding: %w", lenientErr)
|
||||
}
|
||||
klog.Warningf("Audit policy contains errors, falling back to lenient decoding: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the policy file contained an apiVersion and kind.
|
||||
gv := schema.GroupVersion{Group: gvk.Group, Version: gvk.Version}
|
||||
if !apiGroupVersionSet[gv] {
|
||||
return nil, fmt.Errorf("unknown group version field %v in policy", gvk)
|
||||
}
|
||||
|
||||
if err := validation.ValidatePolicy(policy); err != nil {
|
||||
return nil, err.ToAggregate()
|
||||
}
|
||||
|
||||
policyCnt := len(policy.Rules)
|
||||
if policyCnt == 0 {
|
||||
return nil, fmt.Errorf("loaded illegal policy with 0 rules")
|
||||
}
|
||||
|
||||
klog.V(4).InfoS("Load audit policy rules success", "policyCnt", policyCnt)
|
||||
return policy, nil
|
||||
}
|
68
vendor/k8s.io/apiserver/pkg/audit/policy/util.go
generated
vendored
Normal file
68
vendor/k8s.io/apiserver/pkg/audit/policy/util.go
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2018 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 (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
)
|
||||
|
||||
// AllStages returns all possible stages
|
||||
func AllStages() sets.String {
|
||||
return sets.NewString(
|
||||
string(audit.StageRequestReceived),
|
||||
string(audit.StageResponseStarted),
|
||||
string(audit.StageResponseComplete),
|
||||
string(audit.StagePanic),
|
||||
)
|
||||
}
|
||||
|
||||
// AllLevels returns all possible levels
|
||||
func AllLevels() sets.String {
|
||||
return sets.NewString(
|
||||
string(audit.LevelNone),
|
||||
string(audit.LevelMetadata),
|
||||
string(audit.LevelRequest),
|
||||
string(audit.LevelRequestResponse),
|
||||
)
|
||||
}
|
||||
|
||||
// InvertStages subtracts the given array of stages from all stages
|
||||
func InvertStages(stages []audit.Stage) []audit.Stage {
|
||||
s := ConvertStagesToStrings(stages)
|
||||
a := AllStages()
|
||||
a.Delete(s...)
|
||||
return ConvertStringSetToStages(a)
|
||||
}
|
||||
|
||||
// ConvertStagesToStrings converts an array of stages to a string array
|
||||
func ConvertStagesToStrings(stages []audit.Stage) []string {
|
||||
s := make([]string, len(stages))
|
||||
for i, stage := range stages {
|
||||
s[i] = string(stage)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ConvertStringSetToStages converts a string set to an array of stages
|
||||
func ConvertStringSetToStages(set sets.String) []audit.Stage {
|
||||
stages := make([]audit.Stage, len(set))
|
||||
for i, stage := range set.List() {
|
||||
stages[i] = audit.Stage(stage)
|
||||
}
|
||||
return stages
|
||||
}
|
90
vendor/k8s.io/apiserver/pkg/authentication/authenticator/audagnostic.go
generated
vendored
Normal file
90
vendor/k8s.io/apiserver/pkg/authentication/authenticator/audagnostic.go
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2018 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 authenticator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func authenticate(ctx context.Context, implicitAuds Audiences, authenticate func() (*Response, bool, error)) (*Response, bool, error) {
|
||||
targetAuds, ok := AudiencesFrom(ctx)
|
||||
// We can remove this once api audiences is never empty. That will probably
|
||||
// be N releases after TokenRequest is GA.
|
||||
if !ok {
|
||||
return authenticate()
|
||||
}
|
||||
auds := implicitAuds.Intersect(targetAuds)
|
||||
if len(auds) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
resp, ok, err := authenticate()
|
||||
if err != nil || !ok {
|
||||
return nil, false, err
|
||||
}
|
||||
if len(resp.Audiences) > 0 {
|
||||
// maybe the authenticator was audience aware after all.
|
||||
return nil, false, fmt.Errorf("audience agnostic authenticator wrapped an authenticator that returned audiences: %q", resp.Audiences)
|
||||
}
|
||||
resp.Audiences = auds
|
||||
return resp, true, nil
|
||||
}
|
||||
|
||||
type audAgnosticRequestAuthenticator struct {
|
||||
implicit Audiences
|
||||
delegate Request
|
||||
}
|
||||
|
||||
var _ = Request(&audAgnosticRequestAuthenticator{})
|
||||
|
||||
func (a *audAgnosticRequestAuthenticator) AuthenticateRequest(req *http.Request) (*Response, bool, error) {
|
||||
return authenticate(req.Context(), a.implicit, func() (*Response, bool, error) {
|
||||
return a.delegate.AuthenticateRequest(req)
|
||||
})
|
||||
}
|
||||
|
||||
// WrapAudienceAgnosticRequest wraps an audience agnostic request authenticator
|
||||
// to restrict its accepted audiences to a set of implicit audiences.
|
||||
func WrapAudienceAgnosticRequest(implicit Audiences, delegate Request) Request {
|
||||
return &audAgnosticRequestAuthenticator{
|
||||
implicit: implicit,
|
||||
delegate: delegate,
|
||||
}
|
||||
}
|
||||
|
||||
type audAgnosticTokenAuthenticator struct {
|
||||
implicit Audiences
|
||||
delegate Token
|
||||
}
|
||||
|
||||
var _ = Token(&audAgnosticTokenAuthenticator{})
|
||||
|
||||
func (a *audAgnosticTokenAuthenticator) AuthenticateToken(ctx context.Context, tok string) (*Response, bool, error) {
|
||||
return authenticate(ctx, a.implicit, func() (*Response, bool, error) {
|
||||
return a.delegate.AuthenticateToken(ctx, tok)
|
||||
})
|
||||
}
|
||||
|
||||
// WrapAudienceAgnosticToken wraps an audience agnostic token authenticator to
|
||||
// restrict its accepted audiences to a set of implicit audiences.
|
||||
func WrapAudienceAgnosticToken(implicit Audiences, delegate Token) Token {
|
||||
return &audAgnosticTokenAuthenticator{
|
||||
implicit: implicit,
|
||||
delegate: delegate,
|
||||
}
|
||||
}
|
63
vendor/k8s.io/apiserver/pkg/authentication/authenticator/audiences.go
generated
vendored
Normal file
63
vendor/k8s.io/apiserver/pkg/authentication/authenticator/audiences.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2018 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 authenticator
|
||||
|
||||
import "context"
|
||||
|
||||
// Audiences is a container for the Audiences of a token.
|
||||
type Audiences []string
|
||||
|
||||
// The key type is unexported to prevent collisions
|
||||
type key int
|
||||
|
||||
const (
|
||||
// audiencesKey is the context key for request audiences.
|
||||
audiencesKey key = iota
|
||||
)
|
||||
|
||||
// WithAudiences returns a context that stores a request's expected audiences.
|
||||
func WithAudiences(ctx context.Context, auds Audiences) context.Context {
|
||||
return context.WithValue(ctx, audiencesKey, auds)
|
||||
}
|
||||
|
||||
// AudiencesFrom returns a request's expected audiences stored in the request context.
|
||||
func AudiencesFrom(ctx context.Context) (Audiences, bool) {
|
||||
auds, ok := ctx.Value(audiencesKey).(Audiences)
|
||||
return auds, ok
|
||||
}
|
||||
|
||||
// Has checks if Audiences contains a specific audiences.
|
||||
func (a Audiences) Has(taud string) bool {
|
||||
for _, aud := range a {
|
||||
if aud == taud {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Intersect intersects Audiences with a target Audiences and returns all
|
||||
// elements in both.
|
||||
func (a Audiences) Intersect(tauds Audiences) Audiences {
|
||||
selected := Audiences{}
|
||||
for _, taud := range tauds {
|
||||
if a.Has(taud) {
|
||||
selected = append(selected, taud)
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
65
vendor/k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go
generated
vendored
Normal file
65
vendor/k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2014 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 authenticator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// Token checks a string value against a backing authentication store and
|
||||
// returns a Response or an error if the token could not be checked.
|
||||
type Token interface {
|
||||
AuthenticateToken(ctx context.Context, token string) (*Response, bool, error)
|
||||
}
|
||||
|
||||
// Request attempts to extract authentication information from a request and
|
||||
// returns a Response or an error if the request could not be checked.
|
||||
type Request interface {
|
||||
AuthenticateRequest(req *http.Request) (*Response, bool, error)
|
||||
}
|
||||
|
||||
// TokenFunc is a function that implements the Token interface.
|
||||
type TokenFunc func(ctx context.Context, token string) (*Response, bool, error)
|
||||
|
||||
// AuthenticateToken implements authenticator.Token.
|
||||
func (f TokenFunc) AuthenticateToken(ctx context.Context, token string) (*Response, bool, error) {
|
||||
return f(ctx, token)
|
||||
}
|
||||
|
||||
// RequestFunc is a function that implements the Request interface.
|
||||
type RequestFunc func(req *http.Request) (*Response, bool, error)
|
||||
|
||||
// AuthenticateRequest implements authenticator.Request.
|
||||
func (f RequestFunc) AuthenticateRequest(req *http.Request) (*Response, bool, error) {
|
||||
return f(req)
|
||||
}
|
||||
|
||||
// Response is the struct returned by authenticator interfaces upon successful
|
||||
// authentication. It contains information about whether the authenticator
|
||||
// authenticated the request, information about the context of the
|
||||
// authentication, and information about the authenticated user.
|
||||
type Response struct {
|
||||
// Audiences is the set of audiences the authenticator was able to validate
|
||||
// the token against. If the authenticator is not audience aware, this field
|
||||
// will be empty.
|
||||
Audiences Audiences
|
||||
// User is the UserInfo associated with the authentication context.
|
||||
User user.Info
|
||||
}
|
126
vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory/delegating.go
generated
vendored
Normal file
126
vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory/delegating.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
Copyright 2016 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 authenticatorfactory
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/group"
|
||||
"k8s.io/apiserver/pkg/authentication/request/anonymous"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
|
||||
unionauth "k8s.io/apiserver/pkg/authentication/request/union"
|
||||
"k8s.io/apiserver/pkg/authentication/request/websocket"
|
||||
"k8s.io/apiserver/pkg/authentication/request/x509"
|
||||
"k8s.io/apiserver/pkg/authentication/token/cache"
|
||||
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||
webhooktoken "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
|
||||
authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
// DelegatingAuthenticatorConfig is the minimal configuration needed to create an authenticator
|
||||
// built to delegate authentication to a kube API server
|
||||
type DelegatingAuthenticatorConfig struct {
|
||||
Anonymous bool
|
||||
|
||||
// TokenAccessReviewClient is a client to do token review. It can be nil. Then every token is ignored.
|
||||
TokenAccessReviewClient authenticationclient.AuthenticationV1Interface
|
||||
|
||||
// TokenAccessReviewTimeout specifies a time limit for requests made by the authorization webhook client.
|
||||
TokenAccessReviewTimeout time.Duration
|
||||
|
||||
// WebhookRetryBackoff specifies the backoff parameters for the authentication webhook retry logic.
|
||||
// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
|
||||
// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
|
||||
WebhookRetryBackoff *wait.Backoff
|
||||
|
||||
// CacheTTL is the length of time that a token authentication answer will be cached.
|
||||
CacheTTL time.Duration
|
||||
|
||||
// CAContentProvider are the options for verifying incoming connections using mTLS and directly assigning to users.
|
||||
// Generally this is the CA bundle file used to authenticate client certificates
|
||||
// If this is nil, then mTLS will not be used.
|
||||
ClientCertificateCAContentProvider dynamiccertificates.CAContentProvider
|
||||
|
||||
APIAudiences authenticator.Audiences
|
||||
|
||||
RequestHeaderConfig *RequestHeaderConfig
|
||||
}
|
||||
|
||||
func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
|
||||
authenticators := []authenticator.Request{}
|
||||
securityDefinitions := spec.SecurityDefinitions{}
|
||||
|
||||
// front-proxy first, then remote
|
||||
// Add the front proxy authenticator if requested
|
||||
if c.RequestHeaderConfig != nil {
|
||||
requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
|
||||
c.RequestHeaderConfig.CAContentProvider.VerifyOptions,
|
||||
c.RequestHeaderConfig.AllowedClientNames,
|
||||
c.RequestHeaderConfig.UsernameHeaders,
|
||||
c.RequestHeaderConfig.GroupHeaders,
|
||||
c.RequestHeaderConfig.ExtraHeaderPrefixes,
|
||||
)
|
||||
authenticators = append(authenticators, requestHeaderAuthenticator)
|
||||
}
|
||||
|
||||
// x509 client cert auth
|
||||
if c.ClientCertificateCAContentProvider != nil {
|
||||
authenticators = append(authenticators, x509.NewDynamic(c.ClientCertificateCAContentProvider.VerifyOptions, x509.CommonNameUserConversion))
|
||||
}
|
||||
|
||||
if c.TokenAccessReviewClient != nil {
|
||||
if c.WebhookRetryBackoff == nil {
|
||||
return nil, nil, errors.New("retry backoff parameters for delegating authentication webhook has not been specified")
|
||||
}
|
||||
tokenAuth, err := webhooktoken.NewFromInterface(c.TokenAccessReviewClient, c.APIAudiences, *c.WebhookRetryBackoff, c.TokenAccessReviewTimeout, webhooktoken.AuthenticatorMetrics{
|
||||
RecordRequestTotal: RecordRequestTotal,
|
||||
RecordRequestLatency: RecordRequestLatency,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cachingTokenAuth := cache.New(tokenAuth, false, c.CacheTTL, c.CacheTTL)
|
||||
authenticators = append(authenticators, bearertoken.New(cachingTokenAuth), websocket.NewProtocolAuthenticator(cachingTokenAuth))
|
||||
|
||||
securityDefinitions["BearerToken"] = &spec.SecurityScheme{
|
||||
SecuritySchemeProps: spec.SecuritySchemeProps{
|
||||
Type: "apiKey",
|
||||
Name: "authorization",
|
||||
In: "header",
|
||||
Description: "Bearer Token authentication",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(authenticators) == 0 {
|
||||
if c.Anonymous {
|
||||
return anonymous.NewAuthenticator(), &securityDefinitions, nil
|
||||
}
|
||||
return nil, nil, errors.New("No authentication method configured")
|
||||
}
|
||||
|
||||
authenticator := group.NewAuthenticatedGroupAdder(unionauth.New(authenticators...))
|
||||
if c.Anonymous {
|
||||
authenticator = unionauth.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
||||
}
|
||||
return authenticator, &securityDefinitions, nil
|
||||
}
|
29
vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory/loopback.go
generated
vendored
Normal file
29
vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory/loopback.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2016 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 authenticatorfactory
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
"k8s.io/apiserver/pkg/authentication/token/tokenfile"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// NewFromTokens returns an authenticator.Request or an error
|
||||
func NewFromTokens(tokens map[string]*user.DefaultInfo, audiences authenticator.Audiences) authenticator.Request {
|
||||
return bearertoken.New(authenticator.WrapAudienceAgnosticToken(audiences, tokenfile.New(tokens)))
|
||||
}
|
69
vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory/metrics.go
generated
vendored
Normal file
69
vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory/metrics.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
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 authenticatorfactory
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
compbasemetrics "k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
type registerables []compbasemetrics.Registerable
|
||||
|
||||
// init registers all metrics.
|
||||
func init() {
|
||||
for _, metric := range metrics {
|
||||
legacyregistry.MustRegister(metric)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
requestTotal = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_delegated_authn_request_total",
|
||||
Help: "Number of HTTP requests partitioned by status code.",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"code"},
|
||||
)
|
||||
|
||||
requestLatency = compbasemetrics.NewHistogramVec(
|
||||
&compbasemetrics.HistogramOpts{
|
||||
Name: "apiserver_delegated_authn_request_duration_seconds",
|
||||
Help: "Request latency in seconds. Broken down by status code.",
|
||||
Buckets: []float64{0.25, 0.5, 0.7, 1, 1.5, 3, 5, 10},
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"code"},
|
||||
)
|
||||
|
||||
metrics = registerables{
|
||||
requestTotal,
|
||||
requestLatency,
|
||||
}
|
||||
)
|
||||
|
||||
// RecordRequestTotal increments the total number of requests for the delegated authentication.
|
||||
func RecordRequestTotal(ctx context.Context, code string) {
|
||||
requestTotal.WithContext(ctx).WithLabelValues(code).Inc()
|
||||
}
|
||||
|
||||
// RecordRequestLatency measures request latency in seconds for the delegated authentication. Broken down by status code.
|
||||
func RecordRequestLatency(ctx context.Context, code string, latency float64) {
|
||||
requestLatency.WithContext(ctx).WithLabelValues(code).Observe(latency)
|
||||
}
|
37
vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory/requestheader.go
generated
vendored
Normal file
37
vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory/requestheader.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2014 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 authenticatorfactory
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
|
||||
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||
)
|
||||
|
||||
type RequestHeaderConfig struct {
|
||||
// UsernameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
||||
UsernameHeaders headerrequest.StringSliceProvider
|
||||
// GroupHeaders are the headers to check (case-insensitively) for a group names. All values will be used.
|
||||
GroupHeaders headerrequest.StringSliceProvider
|
||||
// ExtraHeaderPrefixes are the head prefixes to check (case-insentively) for filling in
|
||||
// the user.Info.Extra. All values of all matching headers will be added.
|
||||
ExtraHeaderPrefixes headerrequest.StringSliceProvider
|
||||
// CAContentProvider the options for verifying incoming connections using mTLS. Generally this points to CA bundle file which is used verify the identity of the front proxy.
|
||||
// It may produce different options at will.
|
||||
CAContentProvider dynamiccertificates.CAContentProvider
|
||||
// AllowedClientNames is a list of common names that may be presented by the authenticating front proxy. Empty means: accept any.
|
||||
AllowedClientNames headerrequest.StringSliceProvider
|
||||
}
|
66
vendor/k8s.io/apiserver/pkg/authentication/group/authenticated_group_adder.go
generated
vendored
Normal file
66
vendor/k8s.io/apiserver/pkg/authentication/group/authenticated_group_adder.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2017 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 group
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// AuthenticatedGroupAdder adds system:authenticated group when appropriate
|
||||
type AuthenticatedGroupAdder struct {
|
||||
// Authenticator is delegated to make the authentication decision
|
||||
Authenticator authenticator.Request
|
||||
}
|
||||
|
||||
// NewAuthenticatedGroupAdder wraps a request authenticator, and adds the system:authenticated group when appropriate.
|
||||
// Authentication must succeed, the user must not be system:anonymous, the groups system:authenticated or system:unauthenticated must
|
||||
// not be present
|
||||
func NewAuthenticatedGroupAdder(auth authenticator.Request) authenticator.Request {
|
||||
return &AuthenticatedGroupAdder{auth}
|
||||
}
|
||||
|
||||
func (g *AuthenticatedGroupAdder) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
r, ok, err := g.Authenticator.AuthenticateRequest(req)
|
||||
if err != nil || !ok {
|
||||
return nil, ok, err
|
||||
}
|
||||
|
||||
if r.User.GetName() == user.Anonymous {
|
||||
return r, true, nil
|
||||
}
|
||||
for _, group := range r.User.GetGroups() {
|
||||
if group == user.AllAuthenticated || group == user.AllUnauthenticated {
|
||||
return r, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
newGroups := make([]string, 0, len(r.User.GetGroups())+1)
|
||||
newGroups = append(newGroups, r.User.GetGroups()...)
|
||||
newGroups = append(newGroups, user.AllAuthenticated)
|
||||
|
||||
ret := *r // shallow copy
|
||||
ret.User = &user.DefaultInfo{
|
||||
Name: r.User.GetName(),
|
||||
UID: r.User.GetUID(),
|
||||
Groups: newGroups,
|
||||
Extra: r.User.GetExtra(),
|
||||
}
|
||||
return &ret, true, nil
|
||||
}
|
57
vendor/k8s.io/apiserver/pkg/authentication/group/group_adder.go
generated
vendored
Normal file
57
vendor/k8s.io/apiserver/pkg/authentication/group/group_adder.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2016 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 group
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// GroupAdder adds groups to an authenticated user.Info
|
||||
type GroupAdder struct {
|
||||
// Authenticator is delegated to make the authentication decision
|
||||
Authenticator authenticator.Request
|
||||
// Groups are additional groups to add to the user.Info from a successful authentication
|
||||
Groups []string
|
||||
}
|
||||
|
||||
// NewGroupAdder wraps a request authenticator, and adds the specified groups to the returned user when authentication succeeds
|
||||
func NewGroupAdder(auth authenticator.Request, groups []string) authenticator.Request {
|
||||
return &GroupAdder{auth, groups}
|
||||
}
|
||||
|
||||
func (g *GroupAdder) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
r, ok, err := g.Authenticator.AuthenticateRequest(req)
|
||||
if err != nil || !ok {
|
||||
return nil, ok, err
|
||||
}
|
||||
|
||||
newGroups := make([]string, 0, len(r.User.GetGroups())+len(g.Groups))
|
||||
newGroups = append(newGroups, r.User.GetGroups()...)
|
||||
newGroups = append(newGroups, g.Groups...)
|
||||
|
||||
ret := *r // shallow copy
|
||||
ret.User = &user.DefaultInfo{
|
||||
Name: r.User.GetName(),
|
||||
UID: r.User.GetUID(),
|
||||
Groups: newGroups,
|
||||
Extra: r.User.GetExtra(),
|
||||
}
|
||||
return &ret, true, nil
|
||||
}
|
57
vendor/k8s.io/apiserver/pkg/authentication/group/token_group_adder.go
generated
vendored
Normal file
57
vendor/k8s.io/apiserver/pkg/authentication/group/token_group_adder.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2017 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 group
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// TokenGroupAdder adds groups to an authenticated user.Info
|
||||
type TokenGroupAdder struct {
|
||||
// Authenticator is delegated to make the authentication decision
|
||||
Authenticator authenticator.Token
|
||||
// Groups are additional groups to add to the user.Info from a successful authentication
|
||||
Groups []string
|
||||
}
|
||||
|
||||
// NewTokenGroupAdder wraps a token authenticator, and adds the specified groups to the returned user when authentication succeeds
|
||||
func NewTokenGroupAdder(auth authenticator.Token, groups []string) authenticator.Token {
|
||||
return &TokenGroupAdder{auth, groups}
|
||||
}
|
||||
|
||||
func (g *TokenGroupAdder) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
|
||||
r, ok, err := g.Authenticator.AuthenticateToken(ctx, token)
|
||||
if err != nil || !ok {
|
||||
return nil, ok, err
|
||||
}
|
||||
|
||||
newGroups := make([]string, 0, len(r.User.GetGroups())+len(g.Groups))
|
||||
newGroups = append(newGroups, r.User.GetGroups()...)
|
||||
newGroups = append(newGroups, g.Groups...)
|
||||
|
||||
ret := *r // shallow copy
|
||||
ret.User = &user.DefaultInfo{
|
||||
Name: r.User.GetName(),
|
||||
UID: r.User.GetUID(),
|
||||
Groups: newGroups,
|
||||
Extra: r.User.GetExtra(),
|
||||
}
|
||||
return &ret, true, nil
|
||||
}
|
43
vendor/k8s.io/apiserver/pkg/authentication/request/anonymous/anonymous.go
generated
vendored
Normal file
43
vendor/k8s.io/apiserver/pkg/authentication/request/anonymous/anonymous.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2016 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 anonymous
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
const (
|
||||
anonymousUser = user.Anonymous
|
||||
|
||||
unauthenticatedGroup = user.AllUnauthenticated
|
||||
)
|
||||
|
||||
func NewAuthenticator() authenticator.Request {
|
||||
return authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
auds, _ := authenticator.AudiencesFrom(req.Context())
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: anonymousUser,
|
||||
Groups: []string{unauthenticatedGroup},
|
||||
},
|
||||
Audiences: auds,
|
||||
}, true, nil
|
||||
})
|
||||
}
|
76
vendor/k8s.io/apiserver/pkg/authentication/request/bearertoken/bearertoken.go
generated
vendored
Normal file
76
vendor/k8s.io/apiserver/pkg/authentication/request/bearertoken/bearertoken.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2014 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 bearertoken
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
)
|
||||
|
||||
const (
|
||||
invalidTokenWithSpaceWarning = "the provided Authorization header contains extra space before the bearer token, and is ignored"
|
||||
)
|
||||
|
||||
type Authenticator struct {
|
||||
auth authenticator.Token
|
||||
}
|
||||
|
||||
func New(auth authenticator.Token) *Authenticator {
|
||||
return &Authenticator{auth}
|
||||
}
|
||||
|
||||
var invalidToken = errors.New("invalid bearer token")
|
||||
|
||||
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
auth := strings.TrimSpace(req.Header.Get("Authorization"))
|
||||
if auth == "" {
|
||||
return nil, false, nil
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 3)
|
||||
if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
|
||||
// Empty bearer tokens aren't valid
|
||||
if len(token) == 0 {
|
||||
// The space before the token case
|
||||
if len(parts) == 3 {
|
||||
warning.AddWarning(req.Context(), "", invalidTokenWithSpaceWarning)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
|
||||
// if we authenticated successfully, go ahead and remove the bearer token so that no one
|
||||
// is ever tempted to use it inside of the API server
|
||||
if ok {
|
||||
req.Header.Del("Authorization")
|
||||
}
|
||||
|
||||
// If the token authenticator didn't error, provide a default error
|
||||
if !ok && err == nil {
|
||||
err = invalidToken
|
||||
}
|
||||
|
||||
return resp, ok, err
|
||||
}
|
239
vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader.go
generated
vendored
Normal file
239
vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader.go
generated
vendored
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
Copyright 2016 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 headerrequest
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
x509request "k8s.io/apiserver/pkg/authentication/request/x509"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
utilcert "k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
// StringSliceProvider is a way to get a string slice value. It is heavily used for authentication headers among other places.
|
||||
type StringSliceProvider interface {
|
||||
// Value returns the current string slice. Callers should never mutate the returned value.
|
||||
Value() []string
|
||||
}
|
||||
|
||||
// StringSliceProviderFunc is a function that matches the StringSliceProvider interface
|
||||
type StringSliceProviderFunc func() []string
|
||||
|
||||
// Value returns the current string slice. Callers should never mutate the returned value.
|
||||
func (d StringSliceProviderFunc) Value() []string {
|
||||
return d()
|
||||
}
|
||||
|
||||
// StaticStringSlice a StringSliceProvider that returns a fixed value
|
||||
type StaticStringSlice []string
|
||||
|
||||
// Value returns the current string slice. Callers should never mutate the returned value.
|
||||
func (s StaticStringSlice) Value() []string {
|
||||
return s
|
||||
}
|
||||
|
||||
type requestHeaderAuthRequestHandler struct {
|
||||
// nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
||||
nameHeaders StringSliceProvider
|
||||
|
||||
// groupHeaders are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
|
||||
groupHeaders StringSliceProvider
|
||||
|
||||
// extraHeaderPrefixes are the head prefixes to check (case-insensitively) for filling in
|
||||
// the user.Info.Extra. All values of all matching headers will be added.
|
||||
extraHeaderPrefixes StringSliceProvider
|
||||
}
|
||||
|
||||
func New(nameHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trimmedExtraHeaderPrefixes, err := trimHeaders(extraHeaderPrefixes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewDynamic(
|
||||
StaticStringSlice(trimmedNameHeaders),
|
||||
StaticStringSlice(trimmedGroupHeaders),
|
||||
StaticStringSlice(trimmedExtraHeaderPrefixes),
|
||||
), nil
|
||||
}
|
||||
|
||||
func NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
|
||||
return &requestHeaderAuthRequestHandler{
|
||||
nameHeaders: nameHeaders,
|
||||
groupHeaders: groupHeaders,
|
||||
extraHeaderPrefixes: extraHeaderPrefixes,
|
||||
}
|
||||
}
|
||||
|
||||
func trimHeaders(headerNames ...string) ([]string, error) {
|
||||
ret := []string{}
|
||||
for _, headerName := range headerNames {
|
||||
trimmedHeader := strings.TrimSpace(headerName)
|
||||
if len(trimmedHeader) == 0 {
|
||||
return nil, fmt.Errorf("empty header %q", headerName)
|
||||
}
|
||||
ret = append(ret, trimmedHeader)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||
if len(clientCA) == 0 {
|
||||
return nil, fmt.Errorf("missing clientCA file")
|
||||
}
|
||||
|
||||
// Wrap with an x509 verifier
|
||||
caData, err := ioutil.ReadFile(clientCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %v", clientCA, err)
|
||||
}
|
||||
opts := x509request.DefaultVerifyOptions()
|
||||
opts.Roots = x509.NewCertPool()
|
||||
certs, err := utilcert.ParseCertsPEM(caData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading certs from %s: %v", clientCA, err)
|
||||
}
|
||||
for _, cert := range certs {
|
||||
opts.Roots.AddCert(cert)
|
||||
}
|
||||
|
||||
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trimmedExtraHeaderPrefixes, err := trimHeaders(extraHeaderPrefixes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewDynamicVerifyOptionsSecure(
|
||||
x509request.StaticVerifierFn(opts),
|
||||
StaticStringSlice(proxyClientNames),
|
||||
StaticStringSlice(trimmedNameHeaders),
|
||||
StaticStringSlice(trimmedGroupHeaders),
|
||||
StaticStringSlice(trimmedExtraHeaderPrefixes),
|
||||
), nil
|
||||
}
|
||||
|
||||
func NewDynamicVerifyOptionsSecure(verifyOptionFn x509request.VerifyOptionFunc, proxyClientNames, nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
|
||||
headerAuthenticator := NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes)
|
||||
|
||||
return x509request.NewDynamicCAVerifier(verifyOptionFn, headerAuthenticator, proxyClientNames)
|
||||
}
|
||||
|
||||
func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
name := headerValue(req.Header, a.nameHeaders.Value())
|
||||
if len(name) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
groups := allHeaderValues(req.Header, a.groupHeaders.Value())
|
||||
extra := newExtra(req.Header, a.extraHeaderPrefixes.Value())
|
||||
|
||||
// clear headers used for authentication
|
||||
for _, headerName := range a.nameHeaders.Value() {
|
||||
req.Header.Del(headerName)
|
||||
}
|
||||
for _, headerName := range a.groupHeaders.Value() {
|
||||
req.Header.Del(headerName)
|
||||
}
|
||||
for k := range extra {
|
||||
for _, prefix := range a.extraHeaderPrefixes.Value() {
|
||||
req.Header.Del(prefix + k)
|
||||
}
|
||||
}
|
||||
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: name,
|
||||
Groups: groups,
|
||||
Extra: extra,
|
||||
},
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func headerValue(h http.Header, headerNames []string) string {
|
||||
for _, headerName := range headerNames {
|
||||
headerValue := h.Get(headerName)
|
||||
if len(headerValue) > 0 {
|
||||
return headerValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func allHeaderValues(h http.Header, headerNames []string) []string {
|
||||
ret := []string{}
|
||||
for _, headerName := range headerNames {
|
||||
headerKey := http.CanonicalHeaderKey(headerName)
|
||||
values, ok := h[headerKey]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, headerValue := range values {
|
||||
if len(headerValue) > 0 {
|
||||
ret = append(ret, headerValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func unescapeExtraKey(encodedKey string) string {
|
||||
key, err := url.PathUnescape(encodedKey) // Decode %-encoded bytes.
|
||||
if err != nil {
|
||||
return encodedKey // Always record extra strings, even if malformed/unencoded.
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func newExtra(h http.Header, headerPrefixes []string) map[string][]string {
|
||||
ret := map[string][]string{}
|
||||
|
||||
// we have to iterate over prefixes first in order to have proper ordering inside the value slices
|
||||
for _, prefix := range headerPrefixes {
|
||||
for headerName, vv := range h {
|
||||
if !strings.HasPrefix(strings.ToLower(headerName), strings.ToLower(prefix)) {
|
||||
continue
|
||||
}
|
||||
|
||||
extraKey := unescapeExtraKey(strings.ToLower(headerName[len(prefix):]))
|
||||
ret[extraKey] = append(ret[extraKey], vv...)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
337
vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go
generated
vendored
Normal file
337
vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go
generated
vendored
Normal file
@ -0,0 +1,337 @@
|
||||
/*
|
||||
Copyright 2020 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 headerrequest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog/v2"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
authenticationRoleName = "extension-apiserver-authentication-reader"
|
||||
)
|
||||
|
||||
// RequestHeaderAuthRequestProvider a provider that knows how to dynamically fill parts of RequestHeaderConfig struct
|
||||
type RequestHeaderAuthRequestProvider interface {
|
||||
UsernameHeaders() []string
|
||||
GroupHeaders() []string
|
||||
ExtraHeaderPrefixes() []string
|
||||
AllowedClientNames() []string
|
||||
}
|
||||
|
||||
var _ RequestHeaderAuthRequestProvider = &RequestHeaderAuthRequestController{}
|
||||
|
||||
type requestHeaderBundle struct {
|
||||
UsernameHeaders []string
|
||||
GroupHeaders []string
|
||||
ExtraHeaderPrefixes []string
|
||||
AllowedClientNames []string
|
||||
}
|
||||
|
||||
// RequestHeaderAuthRequestController a controller that exposes a set of methods for dynamically filling parts of RequestHeaderConfig struct.
|
||||
// The methods are sourced from the config map which is being monitored by this controller.
|
||||
// The controller is primed from the server at the construction time for components that don't want to dynamically react to changes
|
||||
// in the config map.
|
||||
type RequestHeaderAuthRequestController struct {
|
||||
name string
|
||||
|
||||
configmapName string
|
||||
configmapNamespace string
|
||||
|
||||
client kubernetes.Interface
|
||||
configmapLister corev1listers.ConfigMapNamespaceLister
|
||||
configmapInformer cache.SharedIndexInformer
|
||||
configmapInformerSynced cache.InformerSynced
|
||||
|
||||
queue workqueue.RateLimitingInterface
|
||||
|
||||
// exportedRequestHeaderBundle is a requestHeaderBundle that contains the last read, non-zero length content of the configmap
|
||||
exportedRequestHeaderBundle atomic.Value
|
||||
|
||||
usernameHeadersKey string
|
||||
groupHeadersKey string
|
||||
extraHeaderPrefixesKey string
|
||||
allowedClientNamesKey string
|
||||
}
|
||||
|
||||
// NewRequestHeaderAuthRequestController creates a new controller that implements RequestHeaderAuthRequestController
|
||||
func NewRequestHeaderAuthRequestController(
|
||||
cmName string,
|
||||
cmNamespace string,
|
||||
client kubernetes.Interface,
|
||||
usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
|
||||
c := &RequestHeaderAuthRequestController{
|
||||
name: "RequestHeaderAuthRequestController",
|
||||
|
||||
client: client,
|
||||
|
||||
configmapName: cmName,
|
||||
configmapNamespace: cmNamespace,
|
||||
|
||||
usernameHeadersKey: usernameHeadersKey,
|
||||
groupHeadersKey: groupHeadersKey,
|
||||
extraHeaderPrefixesKey: extraHeaderPrefixesKey,
|
||||
allowedClientNamesKey: allowedClientNamesKey,
|
||||
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "RequestHeaderAuthRequestController"),
|
||||
}
|
||||
|
||||
// we construct our own informer because we need such a small subset of the information available. Just one namespace.
|
||||
c.configmapInformer = coreinformers.NewFilteredConfigMapInformer(client, c.configmapNamespace, 12*time.Hour, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, func(listOptions *metav1.ListOptions) {
|
||||
listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", c.configmapName).String()
|
||||
})
|
||||
|
||||
c.configmapInformer.AddEventHandler(cache.FilteringResourceEventHandler{
|
||||
FilterFunc: func(obj interface{}) bool {
|
||||
if cast, ok := obj.(*corev1.ConfigMap); ok {
|
||||
return cast.Name == c.configmapName && cast.Namespace == c.configmapNamespace
|
||||
}
|
||||
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
|
||||
if cast, ok := tombstone.Obj.(*corev1.ConfigMap); ok {
|
||||
return cast.Name == c.configmapName && cast.Namespace == c.configmapNamespace
|
||||
}
|
||||
}
|
||||
return true // always return true just in case. The checks are fairly cheap
|
||||
},
|
||||
Handler: cache.ResourceEventHandlerFuncs{
|
||||
// we have a filter, so any time we're called, we may as well queue. We only ever check one configmap
|
||||
// so we don't have to be choosy about our key.
|
||||
AddFunc: func(obj interface{}) {
|
||||
c.queue.Add(c.keyFn())
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
c.queue.Add(c.keyFn())
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
c.queue.Add(c.keyFn())
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
c.configmapLister = corev1listers.NewConfigMapLister(c.configmapInformer.GetIndexer()).ConfigMaps(c.configmapNamespace)
|
||||
c.configmapInformerSynced = c.configmapInformer.HasSynced
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) UsernameHeaders() []string {
|
||||
return c.loadRequestHeaderFor(c.usernameHeadersKey)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) GroupHeaders() []string {
|
||||
return c.loadRequestHeaderFor(c.groupHeadersKey)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) ExtraHeaderPrefixes() []string {
|
||||
return c.loadRequestHeaderFor(c.extraHeaderPrefixesKey)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) AllowedClientNames() []string {
|
||||
return c.loadRequestHeaderFor(c.allowedClientNamesKey)
|
||||
}
|
||||
|
||||
// Run starts RequestHeaderAuthRequestController controller and blocks until stopCh is closed.
|
||||
func (c *RequestHeaderAuthRequestController) Run(ctx context.Context, workers int) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer c.queue.ShutDown()
|
||||
|
||||
klog.Infof("Starting %s", c.name)
|
||||
defer klog.Infof("Shutting down %s", c.name)
|
||||
|
||||
go c.configmapInformer.Run(ctx.Done())
|
||||
|
||||
// wait for caches to fill before starting your work
|
||||
if !cache.WaitForNamedCacheSync(c.name, ctx.Done(), c.configmapInformerSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
// doesn't matter what workers say, only start one.
|
||||
go wait.Until(c.runWorker, time.Second, ctx.Done())
|
||||
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
// // RunOnce runs a single sync loop
|
||||
func (c *RequestHeaderAuthRequestController) RunOnce(ctx context.Context) error {
|
||||
configMap, err := c.client.CoreV1().ConfigMaps(c.configmapNamespace).Get(ctx, c.configmapName, metav1.GetOptions{})
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
// ignore, authConfigMap is nil now
|
||||
return nil
|
||||
case errors.IsForbidden(err):
|
||||
klog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+
|
||||
"'kubectl create rolebinding -n %s ROLEBINDING_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'",
|
||||
c.configmapName, c.configmapNamespace, c.configmapNamespace, authenticationRoleName)
|
||||
return err
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
return c.syncConfigMap(configMap)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) runWorker() {
|
||||
for c.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) processNextWorkItem() bool {
|
||||
dsKey, quit := c.queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer c.queue.Done(dsKey)
|
||||
|
||||
err := c.sync()
|
||||
if err == nil {
|
||||
c.queue.Forget(dsKey)
|
||||
return true
|
||||
}
|
||||
|
||||
utilruntime.HandleError(fmt.Errorf("%v failed with : %v", dsKey, err))
|
||||
c.queue.AddRateLimited(dsKey)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// sync reads the config and propagates the changes to exportedRequestHeaderBundle
|
||||
// which is exposed by the set of methods that are used to fill RequestHeaderConfig struct
|
||||
func (c *RequestHeaderAuthRequestController) sync() error {
|
||||
configMap, err := c.configmapLister.Get(c.configmapName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.syncConfigMap(configMap)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) syncConfigMap(configMap *corev1.ConfigMap) error {
|
||||
hasChanged, newRequestHeaderBundle, err := c.hasRequestHeaderBundleChanged(configMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasChanged {
|
||||
c.exportedRequestHeaderBundle.Store(newRequestHeaderBundle)
|
||||
klog.V(2).Infof("Loaded a new request header values for %v", c.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) hasRequestHeaderBundleChanged(cm *corev1.ConfigMap) (bool, *requestHeaderBundle, error) {
|
||||
currentHeadersBundle, err := c.getRequestHeaderBundleFromConfigMap(cm)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
rawHeaderBundle := c.exportedRequestHeaderBundle.Load()
|
||||
if rawHeaderBundle == nil {
|
||||
return true, currentHeadersBundle, nil
|
||||
}
|
||||
|
||||
// check to see if we have a change. If the values are the same, do nothing.
|
||||
loadedHeadersBundle, ok := rawHeaderBundle.(*requestHeaderBundle)
|
||||
if !ok {
|
||||
return true, currentHeadersBundle, nil
|
||||
}
|
||||
|
||||
if !equality.Semantic.DeepEqual(loadedHeadersBundle, currentHeadersBundle) {
|
||||
return true, currentHeadersBundle, nil
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap(cm *corev1.ConfigMap) (*requestHeaderBundle, error) {
|
||||
usernameHeaderCurrentValue, err := deserializeStrings(cm.Data[c.usernameHeadersKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupHeadersCurrentValue, err := deserializeStrings(cm.Data[c.groupHeadersKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extraHeaderPrefixesCurrentValue, err := deserializeStrings(cm.Data[c.extraHeaderPrefixesKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
allowedClientNamesCurrentValue, err := deserializeStrings(cm.Data[c.allowedClientNamesKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &requestHeaderBundle{
|
||||
UsernameHeaders: usernameHeaderCurrentValue,
|
||||
GroupHeaders: groupHeadersCurrentValue,
|
||||
ExtraHeaderPrefixes: extraHeaderPrefixesCurrentValue,
|
||||
AllowedClientNames: allowedClientNamesCurrentValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) loadRequestHeaderFor(key string) []string {
|
||||
rawHeaderBundle := c.exportedRequestHeaderBundle.Load()
|
||||
if rawHeaderBundle == nil {
|
||||
return nil // this can happen if we've been unable load data from the apiserver for some reason
|
||||
}
|
||||
headerBundle := rawHeaderBundle.(*requestHeaderBundle)
|
||||
|
||||
switch key {
|
||||
case c.usernameHeadersKey:
|
||||
return headerBundle.UsernameHeaders
|
||||
case c.groupHeadersKey:
|
||||
return headerBundle.GroupHeaders
|
||||
case c.extraHeaderPrefixesKey:
|
||||
return headerBundle.ExtraHeaderPrefixes
|
||||
case c.allowedClientNamesKey:
|
||||
return headerBundle.AllowedClientNames
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) keyFn() string {
|
||||
// this format matches DeletionHandlingMetaNamespaceKeyFunc for our single key
|
||||
return c.configmapNamespace + "/" + c.configmapName
|
||||
}
|
||||
|
||||
func deserializeStrings(in string) ([]string, error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var ret []string
|
||||
if err := json.Unmarshal([]byte(in), &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
71
vendor/k8s.io/apiserver/pkg/authentication/request/union/union.go
generated
vendored
Normal file
71
vendor/k8s.io/apiserver/pkg/authentication/request/union/union.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2014 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 union
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
)
|
||||
|
||||
// unionAuthRequestHandler authenticates requests using a chain of authenticator.Requests
|
||||
type unionAuthRequestHandler struct {
|
||||
// Handlers is a chain of request authenticators to delegate to
|
||||
Handlers []authenticator.Request
|
||||
// FailOnError determines whether an error returns short-circuits the chain
|
||||
FailOnError bool
|
||||
}
|
||||
|
||||
// New returns a request authenticator that validates credentials using a chain of authenticator.Request objects.
|
||||
// The entire chain is tried until one succeeds. If all fail, an aggregate error is returned.
|
||||
func New(authRequestHandlers ...authenticator.Request) authenticator.Request {
|
||||
if len(authRequestHandlers) == 1 {
|
||||
return authRequestHandlers[0]
|
||||
}
|
||||
return &unionAuthRequestHandler{Handlers: authRequestHandlers, FailOnError: false}
|
||||
}
|
||||
|
||||
// NewFailOnError returns a request authenticator that validates credentials using a chain of authenticator.Request objects.
|
||||
// The first error short-circuits the chain.
|
||||
func NewFailOnError(authRequestHandlers ...authenticator.Request) authenticator.Request {
|
||||
if len(authRequestHandlers) == 1 {
|
||||
return authRequestHandlers[0]
|
||||
}
|
||||
return &unionAuthRequestHandler{Handlers: authRequestHandlers, FailOnError: true}
|
||||
}
|
||||
|
||||
// AuthenticateRequest authenticates the request using a chain of authenticator.Request objects.
|
||||
func (authHandler *unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
var errlist []error
|
||||
for _, currAuthRequestHandler := range authHandler.Handlers {
|
||||
resp, ok, err := currAuthRequestHandler.AuthenticateRequest(req)
|
||||
if err != nil {
|
||||
if authHandler.FailOnError {
|
||||
return resp, ok, err
|
||||
}
|
||||
errlist = append(errlist, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok {
|
||||
return resp, ok, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false, utilerrors.NewAggregate(errlist)
|
||||
}
|
108
vendor/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go
generated
vendored
Normal file
108
vendor/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright 2017 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 websocket
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
)
|
||||
|
||||
const bearerProtocolPrefix = "base64url.bearer.authorization.k8s.io."
|
||||
|
||||
var protocolHeader = textproto.CanonicalMIMEHeaderKey("Sec-WebSocket-Protocol")
|
||||
|
||||
var errInvalidToken = errors.New("invalid bearer token")
|
||||
|
||||
// ProtocolAuthenticator allows a websocket connection to provide a bearer token as a subprotocol
|
||||
// in the format "base64url.bearer.authorization.<base64url-without-padding(bearer-token)>"
|
||||
type ProtocolAuthenticator struct {
|
||||
// auth is the token authenticator to use to validate the token
|
||||
auth authenticator.Token
|
||||
}
|
||||
|
||||
func NewProtocolAuthenticator(auth authenticator.Token) *ProtocolAuthenticator {
|
||||
return &ProtocolAuthenticator{auth}
|
||||
}
|
||||
|
||||
func (a *ProtocolAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
// Only accept websocket connections
|
||||
if !wsstream.IsWebSocketRequest(req) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
token := ""
|
||||
sawTokenProtocol := false
|
||||
filteredProtocols := []string{}
|
||||
for _, protocolHeader := range req.Header[protocolHeader] {
|
||||
for _, protocol := range strings.Split(protocolHeader, ",") {
|
||||
protocol = strings.TrimSpace(protocol)
|
||||
|
||||
if !strings.HasPrefix(protocol, bearerProtocolPrefix) {
|
||||
filteredProtocols = append(filteredProtocols, protocol)
|
||||
continue
|
||||
}
|
||||
|
||||
if sawTokenProtocol {
|
||||
return nil, false, errors.New("multiple base64.bearer.authorization tokens specified")
|
||||
}
|
||||
sawTokenProtocol = true
|
||||
|
||||
encodedToken := strings.TrimPrefix(protocol, bearerProtocolPrefix)
|
||||
decodedToken, err := base64.RawURLEncoding.DecodeString(encodedToken)
|
||||
if err != nil {
|
||||
return nil, false, errors.New("invalid base64.bearer.authorization token encoding")
|
||||
}
|
||||
if !utf8.Valid(decodedToken) {
|
||||
return nil, false, errors.New("invalid base64.bearer.authorization token")
|
||||
}
|
||||
token = string(decodedToken)
|
||||
}
|
||||
}
|
||||
|
||||
// Must pass at least one other subprotocol so that we can remove the one containing the bearer token,
|
||||
// and there is at least one to echo back to the client
|
||||
if len(token) > 0 && len(filteredProtocols) == 0 {
|
||||
return nil, false, errors.New("missing additional subprotocol")
|
||||
}
|
||||
|
||||
if len(token) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
|
||||
|
||||
// on success, remove the protocol with the token
|
||||
if ok {
|
||||
// https://tools.ietf.org/html/rfc6455#section-11.3.4 indicates the Sec-WebSocket-Protocol header may appear multiple times
|
||||
// in a request, and is logically the same as a single Sec-WebSocket-Protocol header field that contains all values
|
||||
req.Header.Set(protocolHeader, strings.Join(filteredProtocols, ","))
|
||||
}
|
||||
|
||||
// If the token authenticator didn't error, provide a default error
|
||||
if !ok && err == nil {
|
||||
err = errInvalidToken
|
||||
}
|
||||
|
||||
return resp, ok, err
|
||||
}
|
8
vendor/k8s.io/apiserver/pkg/authentication/request/x509/OWNERS
generated
vendored
Normal file
8
vendor/k8s.io/apiserver/pkg/authentication/request/x509/OWNERS
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- sig-auth-certificates-approvers
|
||||
reviewers:
|
||||
- sig-auth-certificates-reviewers
|
||||
labels:
|
||||
- sig/auth
|
19
vendor/k8s.io/apiserver/pkg/authentication/request/x509/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/apiserver/pkg/authentication/request/x509/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2014 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 x509 provides a request authenticator that validates and
|
||||
// extracts user information from client certificates
|
||||
package x509 // import "k8s.io/apiserver/pkg/authentication/request/x509"
|
71
vendor/k8s.io/apiserver/pkg/authentication/request/x509/verify_options.go
generated
vendored
Normal file
71
vendor/k8s.io/apiserver/pkg/authentication/request/x509/verify_options.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2019 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 x509
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
// StaticVerifierFn is a VerifyOptionFunc that always returns the same value. This allows verify options that cannot change.
|
||||
func StaticVerifierFn(opts x509.VerifyOptions) VerifyOptionFunc {
|
||||
return func() (x509.VerifyOptions, bool) {
|
||||
return opts, true
|
||||
}
|
||||
}
|
||||
|
||||
// NewStaticVerifierFromFile creates a new verification func from a file. It reads the content and then fails.
|
||||
// It will return a nil function if you pass an empty CA file.
|
||||
func NewStaticVerifierFromFile(clientCA string) (VerifyOptionFunc, error) {
|
||||
if len(clientCA) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Wrap with an x509 verifier
|
||||
var err error
|
||||
opts := DefaultVerifyOptions()
|
||||
opts.Roots, err = cert.NewPool(clientCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading certs from %s: %v", clientCA, err)
|
||||
}
|
||||
|
||||
return StaticVerifierFn(opts), nil
|
||||
}
|
||||
|
||||
// StringSliceProvider is a way to get a string slice value. It is heavily used for authentication headers among other places.
|
||||
type StringSliceProvider interface {
|
||||
// Value returns the current string slice. Callers should never mutate the returned value.
|
||||
Value() []string
|
||||
}
|
||||
|
||||
// StringSliceProviderFunc is a function that matches the StringSliceProvider interface
|
||||
type StringSliceProviderFunc func() []string
|
||||
|
||||
// Value returns the current string slice. Callers should never mutate the returned value.
|
||||
func (d StringSliceProviderFunc) Value() []string {
|
||||
return d()
|
||||
}
|
||||
|
||||
// StaticStringSlice a StringSliceProvider that returns a fixed value
|
||||
type StaticStringSlice []string
|
||||
|
||||
// Value returns the current string slice. Callers should never mutate the returned value.
|
||||
func (s StaticStringSlice) Value() []string {
|
||||
return s
|
||||
}
|
258
vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go
generated
vendored
Normal file
258
vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go
generated
vendored
Normal file
@ -0,0 +1,258 @@
|
||||
/*
|
||||
Copyright 2014 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 x509
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
/*
|
||||
* By default, the following metric is defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
*
|
||||
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||
* the metric stability policy.
|
||||
*/
|
||||
var clientCertificateExpirationHistogram = metrics.NewHistogram(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: "apiserver",
|
||||
Subsystem: "client",
|
||||
Name: "certificate_expiration_seconds",
|
||||
Help: "Distribution of the remaining lifetime on the certificate used to authenticate a request.",
|
||||
Buckets: []float64{
|
||||
0,
|
||||
1800, // 30 minutes
|
||||
3600, // 1 hour
|
||||
7200, // 2 hours
|
||||
21600, // 6 hours
|
||||
43200, // 12 hours
|
||||
86400, // 1 day
|
||||
172800, // 2 days
|
||||
345600, // 4 days
|
||||
604800, // 1 week
|
||||
2592000, // 1 month
|
||||
7776000, // 3 months
|
||||
15552000, // 6 months
|
||||
31104000, // 1 year
|
||||
},
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
)
|
||||
|
||||
func init() {
|
||||
legacyregistry.MustRegister(clientCertificateExpirationHistogram)
|
||||
}
|
||||
|
||||
// UserConversion defines an interface for extracting user info from a client certificate chain
|
||||
type UserConversion interface {
|
||||
User(chain []*x509.Certificate) (*authenticator.Response, bool, error)
|
||||
}
|
||||
|
||||
// UserConversionFunc is a function that implements the UserConversion interface.
|
||||
type UserConversionFunc func(chain []*x509.Certificate) (*authenticator.Response, bool, error)
|
||||
|
||||
// User implements x509.UserConversion
|
||||
func (f UserConversionFunc) User(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
|
||||
return f(chain)
|
||||
}
|
||||
|
||||
func columnSeparatedHex(d []byte) string {
|
||||
h := strings.ToUpper(hex.EncodeToString(d))
|
||||
var sb strings.Builder
|
||||
for i, r := range h {
|
||||
sb.WriteRune(r)
|
||||
if i%2 == 1 && i != len(h)-1 {
|
||||
sb.WriteRune(':')
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func certificateIdentifier(c *x509.Certificate) string {
|
||||
return fmt.Sprintf(
|
||||
"SN=%d, SKID=%s, AKID=%s",
|
||||
c.SerialNumber,
|
||||
columnSeparatedHex(c.SubjectKeyId),
|
||||
columnSeparatedHex(c.AuthorityKeyId),
|
||||
)
|
||||
}
|
||||
|
||||
// VerifyOptionFunc is function which provides a shallow copy of the VerifyOptions to the authenticator. This allows
|
||||
// for cases where the options (particularly the CAs) can change. If the bool is false, then the returned VerifyOptions
|
||||
// are ignored and the authenticator will express "no opinion". This allows a clear signal for cases where a CertPool
|
||||
// is eventually expected, but not currently present.
|
||||
type VerifyOptionFunc func() (x509.VerifyOptions, bool)
|
||||
|
||||
// Authenticator implements request.Authenticator by extracting user info from verified client certificates
|
||||
type Authenticator struct {
|
||||
verifyOptionsFn VerifyOptionFunc
|
||||
user UserConversion
|
||||
}
|
||||
|
||||
// New returns a request.Authenticator that verifies client certificates using the provided
|
||||
// VerifyOptions, and converts valid certificate chains into user.Info using the provided UserConversion
|
||||
func New(opts x509.VerifyOptions, user UserConversion) *Authenticator {
|
||||
return NewDynamic(StaticVerifierFn(opts), user)
|
||||
}
|
||||
|
||||
// NewDynamic returns a request.Authenticator that verifies client certificates using the provided
|
||||
// VerifyOptionFunc (which may be dynamic), and converts valid certificate chains into user.Info using the provided UserConversion
|
||||
func NewDynamic(verifyOptionsFn VerifyOptionFunc, user UserConversion) *Authenticator {
|
||||
return &Authenticator{verifyOptionsFn, user}
|
||||
}
|
||||
|
||||
// AuthenticateRequest authenticates the request using presented client certificates
|
||||
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Use intermediates, if provided
|
||||
optsCopy, ok := a.verifyOptionsFn()
|
||||
// if there are intentionally no verify options, then we cannot authenticate this request
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
|
||||
optsCopy.Intermediates = x509.NewCertPool()
|
||||
for _, intermediate := range req.TLS.PeerCertificates[1:] {
|
||||
optsCopy.Intermediates.AddCert(intermediate)
|
||||
}
|
||||
}
|
||||
|
||||
remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now())
|
||||
clientCertificateExpirationHistogram.WithContext(req.Context()).Observe(remaining.Seconds())
|
||||
chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf(
|
||||
"verifying certificate %s failed: %w",
|
||||
certificateIdentifier(req.TLS.PeerCertificates[0]),
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
var errlist []error
|
||||
for _, chain := range chains {
|
||||
user, ok, err := a.user.User(chain)
|
||||
if err != nil {
|
||||
errlist = append(errlist, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok {
|
||||
return user, ok, err
|
||||
}
|
||||
}
|
||||
return nil, false, utilerrors.NewAggregate(errlist)
|
||||
}
|
||||
|
||||
// Verifier implements request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
|
||||
type Verifier struct {
|
||||
verifyOptionsFn VerifyOptionFunc
|
||||
auth authenticator.Request
|
||||
|
||||
// allowedCommonNames contains the common names which a verified certificate is allowed to have.
|
||||
// If empty, all verified certificates are allowed.
|
||||
allowedCommonNames StringSliceProvider
|
||||
}
|
||||
|
||||
// NewVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
|
||||
func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCommonNames sets.String) authenticator.Request {
|
||||
return NewDynamicCAVerifier(StaticVerifierFn(opts), auth, StaticStringSlice(allowedCommonNames.List()))
|
||||
}
|
||||
|
||||
// NewDynamicCAVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
|
||||
func NewDynamicCAVerifier(verifyOptionsFn VerifyOptionFunc, auth authenticator.Request, allowedCommonNames StringSliceProvider) authenticator.Request {
|
||||
return &Verifier{verifyOptionsFn, auth, allowedCommonNames}
|
||||
}
|
||||
|
||||
// AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth
|
||||
func (a *Verifier) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Use intermediates, if provided
|
||||
optsCopy, ok := a.verifyOptionsFn()
|
||||
// if there are intentionally no verify options, then we cannot authenticate this request
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
|
||||
optsCopy.Intermediates = x509.NewCertPool()
|
||||
for _, intermediate := range req.TLS.PeerCertificates[1:] {
|
||||
optsCopy.Intermediates.AddCert(intermediate)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return a.auth.AuthenticateRequest(req)
|
||||
}
|
||||
|
||||
func (a *Verifier) verifySubject(subject pkix.Name) error {
|
||||
// No CN restrictions
|
||||
if len(a.allowedCommonNames.Value()) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Enforce CN restrictions
|
||||
for _, allowedCommonName := range a.allowedCommonNames.Value() {
|
||||
if allowedCommonName == subject.CommonName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("x509: subject with cn=%s is not in the allowed list", subject.CommonName)
|
||||
}
|
||||
|
||||
// DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time,
|
||||
// and requires certificates to be valid for client auth (x509.ExtKeyUsageClientAuth)
|
||||
func DefaultVerifyOptions() x509.VerifyOptions {
|
||||
return x509.VerifyOptions{
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
}
|
||||
|
||||
// CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName
|
||||
var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
|
||||
if len(chain[0].Subject.CommonName) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: chain[0].Subject.CommonName,
|
||||
Groups: chain[0].Subject.Organization,
|
||||
},
|
||||
}, true, nil
|
||||
})
|
183
vendor/k8s.io/apiserver/pkg/authentication/serviceaccount/util.go
generated
vendored
Normal file
183
vendor/k8s.io/apiserver/pkg/authentication/serviceaccount/util.go
generated
vendored
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
Copyright 2014 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 serviceaccount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
ServiceAccountUsernamePrefix = "system:serviceaccount:"
|
||||
ServiceAccountUsernameSeparator = ":"
|
||||
ServiceAccountGroupPrefix = "system:serviceaccounts:"
|
||||
AllServiceAccountsGroup = "system:serviceaccounts"
|
||||
// PodNameKey is the key used in a user's "extra" to specify the pod name of
|
||||
// the authenticating request.
|
||||
PodNameKey = "authentication.kubernetes.io/pod-name"
|
||||
// PodUIDKey is the key used in a user's "extra" to specify the pod UID of
|
||||
// the authenticating request.
|
||||
PodUIDKey = "authentication.kubernetes.io/pod-uid"
|
||||
)
|
||||
|
||||
// MakeUsername generates a username from the given namespace and ServiceAccount name.
|
||||
// The resulting username can be passed to SplitUsername to extract the original namespace and ServiceAccount name.
|
||||
func MakeUsername(namespace, name string) string {
|
||||
return ServiceAccountUsernamePrefix + namespace + ServiceAccountUsernameSeparator + name
|
||||
}
|
||||
|
||||
// MatchesUsername checks whether the provided username matches the namespace and name without
|
||||
// allocating. Use this when checking a service account namespace and name against a known string.
|
||||
func MatchesUsername(namespace, name string, username string) bool {
|
||||
if !strings.HasPrefix(username, ServiceAccountUsernamePrefix) {
|
||||
return false
|
||||
}
|
||||
username = username[len(ServiceAccountUsernamePrefix):]
|
||||
|
||||
if !strings.HasPrefix(username, namespace) {
|
||||
return false
|
||||
}
|
||||
username = username[len(namespace):]
|
||||
|
||||
if !strings.HasPrefix(username, ServiceAccountUsernameSeparator) {
|
||||
return false
|
||||
}
|
||||
username = username[len(ServiceAccountUsernameSeparator):]
|
||||
|
||||
return username == name
|
||||
}
|
||||
|
||||
var invalidUsernameErr = fmt.Errorf("Username must be in the form %s", MakeUsername("namespace", "name"))
|
||||
|
||||
// SplitUsername returns the namespace and ServiceAccount name embedded in the given username,
|
||||
// or an error if the username is not a valid name produced by MakeUsername
|
||||
func SplitUsername(username string) (string, string, error) {
|
||||
if !strings.HasPrefix(username, ServiceAccountUsernamePrefix) {
|
||||
return "", "", invalidUsernameErr
|
||||
}
|
||||
trimmed := strings.TrimPrefix(username, ServiceAccountUsernamePrefix)
|
||||
parts := strings.Split(trimmed, ServiceAccountUsernameSeparator)
|
||||
if len(parts) != 2 {
|
||||
return "", "", invalidUsernameErr
|
||||
}
|
||||
namespace, name := parts[0], parts[1]
|
||||
if len(apimachineryvalidation.ValidateNamespaceName(namespace, false)) != 0 {
|
||||
return "", "", invalidUsernameErr
|
||||
}
|
||||
if len(apimachineryvalidation.ValidateServiceAccountName(name, false)) != 0 {
|
||||
return "", "", invalidUsernameErr
|
||||
}
|
||||
return namespace, name, nil
|
||||
}
|
||||
|
||||
// MakeGroupNames generates service account group names for the given namespace
|
||||
func MakeGroupNames(namespace string) []string {
|
||||
return []string{
|
||||
AllServiceAccountsGroup,
|
||||
MakeNamespaceGroupName(namespace),
|
||||
}
|
||||
}
|
||||
|
||||
// MakeNamespaceGroupName returns the name of the group all service accounts in the namespace are included in
|
||||
func MakeNamespaceGroupName(namespace string) string {
|
||||
return ServiceAccountGroupPrefix + namespace
|
||||
}
|
||||
|
||||
// UserInfo returns a user.Info interface for the given namespace, service account name and UID
|
||||
func UserInfo(namespace, name, uid string) user.Info {
|
||||
return (&ServiceAccountInfo{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
UID: uid,
|
||||
}).UserInfo()
|
||||
}
|
||||
|
||||
type ServiceAccountInfo struct {
|
||||
Name, Namespace, UID string
|
||||
PodName, PodUID string
|
||||
}
|
||||
|
||||
func (sa *ServiceAccountInfo) UserInfo() user.Info {
|
||||
info := &user.DefaultInfo{
|
||||
Name: MakeUsername(sa.Namespace, sa.Name),
|
||||
UID: sa.UID,
|
||||
Groups: MakeGroupNames(sa.Namespace),
|
||||
}
|
||||
if sa.PodName != "" && sa.PodUID != "" {
|
||||
info.Extra = map[string][]string{
|
||||
PodNameKey: {sa.PodName},
|
||||
PodUIDKey: {sa.PodUID},
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// IsServiceAccountToken returns true if the secret is a valid api token for the service account
|
||||
func IsServiceAccountToken(secret *v1.Secret, sa *v1.ServiceAccount) bool {
|
||||
if secret.Type != v1.SecretTypeServiceAccountToken {
|
||||
return false
|
||||
}
|
||||
|
||||
name := secret.Annotations[v1.ServiceAccountNameKey]
|
||||
uid := secret.Annotations[v1.ServiceAccountUIDKey]
|
||||
if name != sa.Name {
|
||||
// Name must match
|
||||
return false
|
||||
}
|
||||
if len(uid) > 0 && uid != string(sa.UID) {
|
||||
// If UID is specified, it must match
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func GetOrCreateServiceAccount(coreClient v1core.CoreV1Interface, namespace, name string) (*v1.ServiceAccount, error) {
|
||||
sa, err := coreClient.ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
return sa, nil
|
||||
}
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the namespace if we can't verify it exists.
|
||||
// Tolerate errors, since we don't know whether this component has namespace creation permissions.
|
||||
if _, err := coreClient.Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}); apierrors.IsNotFound(err) {
|
||||
if _, err = coreClient.Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
klog.Warningf("create non-exist namespace %s failed:%v", namespace, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the service account
|
||||
sa, err = coreClient.ServiceAccounts(namespace).Create(context.TODO(), &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}}, metav1.CreateOptions{})
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
// If we're racing to init and someone else already created it, re-fetch
|
||||
return coreClient.ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
}
|
||||
return sa, err
|
||||
}
|
49
vendor/k8s.io/apiserver/pkg/authentication/token/cache/cache_simple.go
generated
vendored
Normal file
49
vendor/k8s.io/apiserver/pkg/authentication/token/cache/cache_simple.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2017 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 cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
type simpleCache struct {
|
||||
cache *utilcache.Expiring
|
||||
}
|
||||
|
||||
func newSimpleCache(clock clock.Clock) cache {
|
||||
return &simpleCache{cache: utilcache.NewExpiringWithClock(clock)}
|
||||
}
|
||||
|
||||
func (c *simpleCache) get(key string) (*cacheRecord, bool) {
|
||||
record, ok := c.cache.Get(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
value, ok := record.(*cacheRecord)
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (c *simpleCache) set(key string, value *cacheRecord, ttl time.Duration) {
|
||||
c.cache.Set(key, value, ttl)
|
||||
}
|
||||
|
||||
func (c *simpleCache) remove(key string) {
|
||||
c.cache.Delete(key)
|
||||
}
|
60
vendor/k8s.io/apiserver/pkg/authentication/token/cache/cache_striped.go
generated
vendored
Normal file
60
vendor/k8s.io/apiserver/pkg/authentication/token/cache/cache_striped.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2017 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 cache
|
||||
|
||||
import (
|
||||
"hash/fnv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// split cache lookups across N striped caches
|
||||
type stripedCache struct {
|
||||
stripeCount uint32
|
||||
hashFunc func(string) uint32
|
||||
caches []cache
|
||||
}
|
||||
|
||||
type hashFunc func(string) uint32
|
||||
type newCacheFunc func() cache
|
||||
|
||||
func newStripedCache(stripeCount int, hash hashFunc, newCacheFunc newCacheFunc) cache {
|
||||
caches := []cache{}
|
||||
for i := 0; i < stripeCount; i++ {
|
||||
caches = append(caches, newCacheFunc())
|
||||
}
|
||||
return &stripedCache{
|
||||
stripeCount: uint32(stripeCount),
|
||||
hashFunc: hash,
|
||||
caches: caches,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *stripedCache) get(key string) (*cacheRecord, bool) {
|
||||
return c.caches[c.hashFunc(key)%c.stripeCount].get(key)
|
||||
}
|
||||
func (c *stripedCache) set(key string, value *cacheRecord, ttl time.Duration) {
|
||||
c.caches[c.hashFunc(key)%c.stripeCount].set(key, value, ttl)
|
||||
}
|
||||
func (c *stripedCache) remove(key string) {
|
||||
c.caches[c.hashFunc(key)%c.stripeCount].remove(key)
|
||||
}
|
||||
|
||||
func fnvHashFunc(key string) uint32 {
|
||||
f := fnv.New32()
|
||||
f.Write([]byte(key))
|
||||
return f.Sum32()
|
||||
}
|
307
vendor/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go
generated
vendored
Normal file
307
vendor/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go
generated
vendored
Normal file
@ -0,0 +1,307 @@
|
||||
/*
|
||||
Copyright 2017 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
var errAuthnCrash = apierrors.NewInternalError(errors.New("authentication failed unexpectedly"))
|
||||
|
||||
const sharedLookupTimeout = 30 * time.Second
|
||||
|
||||
// cacheRecord holds the three return values of the authenticator.Token AuthenticateToken method
|
||||
type cacheRecord struct {
|
||||
resp *authenticator.Response
|
||||
ok bool
|
||||
err error
|
||||
|
||||
// this cache assumes token authn has no side-effects or temporal dependence.
|
||||
// neither of these are true for audit annotations set via AddAuditAnnotation.
|
||||
//
|
||||
// for audit annotations, the assumption is that for some period of time (cache TTL),
|
||||
// all requests with the same API audiences and the same bearer token result in the
|
||||
// same annotations. This may not be true if the authenticator sets an annotation
|
||||
// based on the current time, but that may be okay since cache TTLs are generally
|
||||
// small (seconds).
|
||||
annotations map[string]string
|
||||
warnings []*cacheWarning
|
||||
}
|
||||
|
||||
type cacheWarning struct {
|
||||
agent string
|
||||
text string
|
||||
}
|
||||
|
||||
type cachedTokenAuthenticator struct {
|
||||
authenticator authenticator.Token
|
||||
|
||||
cacheErrs bool
|
||||
successTTL time.Duration
|
||||
failureTTL time.Duration
|
||||
|
||||
cache cache
|
||||
group singleflight.Group
|
||||
|
||||
// hashPool is a per authenticator pool of hash.Hash (to avoid allocations from building the Hash)
|
||||
// HMAC with SHA-256 and a random key is used to prevent precomputation and length extension attacks
|
||||
// It also mitigates hash map DOS attacks via collisions (the inputs are supplied by untrusted users)
|
||||
hashPool *sync.Pool
|
||||
}
|
||||
|
||||
type cache interface {
|
||||
// given a key, return the record, and whether or not it existed
|
||||
get(key string) (value *cacheRecord, exists bool)
|
||||
// caches the record for the key
|
||||
set(key string, value *cacheRecord, ttl time.Duration)
|
||||
// removes the record for the key
|
||||
remove(key string)
|
||||
}
|
||||
|
||||
// New returns a token authenticator that caches the results of the specified authenticator. A ttl of 0 bypasses the cache.
|
||||
func New(authenticator authenticator.Token, cacheErrs bool, successTTL, failureTTL time.Duration) authenticator.Token {
|
||||
return newWithClock(authenticator, cacheErrs, successTTL, failureTTL, clock.RealClock{})
|
||||
}
|
||||
|
||||
func newWithClock(authenticator authenticator.Token, cacheErrs bool, successTTL, failureTTL time.Duration, clock clock.Clock) authenticator.Token {
|
||||
randomCacheKey := make([]byte, 32)
|
||||
if _, err := rand.Read(randomCacheKey); err != nil {
|
||||
panic(err) // rand should never fail
|
||||
}
|
||||
|
||||
return &cachedTokenAuthenticator{
|
||||
authenticator: authenticator,
|
||||
cacheErrs: cacheErrs,
|
||||
successTTL: successTTL,
|
||||
failureTTL: failureTTL,
|
||||
// Cache performance degrades noticeably when the number of
|
||||
// tokens in operation exceeds the size of the cache. It is
|
||||
// cheap to make the cache big in the second dimension below,
|
||||
// the memory is only consumed when that many tokens are being
|
||||
// used. Currently we advertise support 5k nodes and 10k
|
||||
// namespaces; a 32k entry cache is therefore a 2x safety
|
||||
// margin.
|
||||
cache: newStripedCache(32, fnvHashFunc, func() cache { return newSimpleCache(clock) }),
|
||||
|
||||
hashPool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return hmac.New(sha256.New, randomCacheKey)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AuthenticateToken implements authenticator.Token
|
||||
func (a *cachedTokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
|
||||
record := a.doAuthenticateToken(ctx, token)
|
||||
if !record.ok || record.err != nil {
|
||||
return nil, false, record.err
|
||||
}
|
||||
for key, value := range record.annotations {
|
||||
audit.AddAuditAnnotation(ctx, key, value)
|
||||
}
|
||||
for _, w := range record.warnings {
|
||||
warning.AddWarning(ctx, w.agent, w.text)
|
||||
}
|
||||
return record.resp, true, nil
|
||||
}
|
||||
|
||||
func (a *cachedTokenAuthenticator) doAuthenticateToken(ctx context.Context, token string) *cacheRecord {
|
||||
doneAuthenticating := stats.authenticating(ctx)
|
||||
|
||||
auds, audsOk := authenticator.AudiencesFrom(ctx)
|
||||
|
||||
key := keyFunc(a.hashPool, auds, token)
|
||||
if record, ok := a.cache.get(key); ok {
|
||||
// Record cache hit
|
||||
doneAuthenticating(true)
|
||||
return record
|
||||
}
|
||||
|
||||
// Record cache miss
|
||||
doneBlocking := stats.blocking(ctx)
|
||||
defer doneBlocking()
|
||||
defer doneAuthenticating(false)
|
||||
|
||||
c := a.group.DoChan(key, func() (val interface{}, _ error) {
|
||||
// always use one place to read and write the output of AuthenticateToken
|
||||
record := &cacheRecord{}
|
||||
|
||||
doneFetching := stats.fetching(ctx)
|
||||
// We're leaving the request handling stack so we need to handle crashes
|
||||
// ourselves. Log a stack trace and return a 500 if something panics.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// make sure to always return a record
|
||||
record.err = errAuthnCrash
|
||||
val = record
|
||||
|
||||
// Same as stdlib http server code. Manually allocate stack
|
||||
// trace buffer size to prevent excessively large logs
|
||||
const size = 64 << 10
|
||||
buf := make([]byte, size)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
klog.Errorf("%v\n%s", r, buf)
|
||||
}
|
||||
doneFetching(record.err == nil)
|
||||
}()
|
||||
|
||||
// Check again for a cached record. We may have raced with a fetch.
|
||||
if record, ok := a.cache.get(key); ok {
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// Detach the context because the lookup may be shared by multiple callers,
|
||||
// however propagate the audience.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), sharedLookupTimeout)
|
||||
defer cancel()
|
||||
|
||||
if audsOk {
|
||||
ctx = authenticator.WithAudiences(ctx, auds)
|
||||
}
|
||||
recorder := &recorder{}
|
||||
ctx = warning.WithWarningRecorder(ctx, recorder)
|
||||
|
||||
// since this is shared work between multiple requests, we have no way of knowing if any
|
||||
// particular request supports audit annotations. thus we always attempt to record them.
|
||||
ev := &auditinternal.Event{Level: auditinternal.LevelMetadata}
|
||||
ctx = audit.WithAuditContext(ctx)
|
||||
ac := audit.AuditContextFrom(ctx)
|
||||
ac.Event = ev
|
||||
|
||||
record.resp, record.ok, record.err = a.authenticator.AuthenticateToken(ctx, token)
|
||||
record.annotations = ev.Annotations
|
||||
record.warnings = recorder.extractWarnings()
|
||||
|
||||
if !a.cacheErrs && record.err != nil {
|
||||
return record, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case record.ok && a.successTTL > 0:
|
||||
a.cache.set(key, record, a.successTTL)
|
||||
case !record.ok && a.failureTTL > 0:
|
||||
a.cache.set(key, record, a.failureTTL)
|
||||
}
|
||||
|
||||
return record, nil
|
||||
})
|
||||
|
||||
select {
|
||||
case result := <-c:
|
||||
// we always set Val and never set Err
|
||||
return result.Val.(*cacheRecord)
|
||||
case <-ctx.Done():
|
||||
// fake a record on context cancel
|
||||
return &cacheRecord{err: ctx.Err()}
|
||||
}
|
||||
}
|
||||
|
||||
// keyFunc generates a string key by hashing the inputs.
|
||||
// This lowers the memory requirement of the cache and keeps tokens out of memory.
|
||||
func keyFunc(hashPool *sync.Pool, auds []string, token string) string {
|
||||
h := hashPool.Get().(hash.Hash)
|
||||
|
||||
h.Reset()
|
||||
|
||||
// try to force stack allocation
|
||||
var a [4]byte
|
||||
b := a[:]
|
||||
|
||||
writeLengthPrefixedString(h, b, token)
|
||||
// encode the length of audiences to avoid ambiguities
|
||||
writeLength(h, b, len(auds))
|
||||
for _, aud := range auds {
|
||||
writeLengthPrefixedString(h, b, aud)
|
||||
}
|
||||
|
||||
key := toString(h.Sum(nil)) // skip base64 encoding to save an allocation
|
||||
|
||||
hashPool.Put(h)
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
// writeLengthPrefixedString writes s with a length prefix to prevent ambiguities, i.e. "xy" + "z" == "x" + "yz"
|
||||
// the length of b is assumed to be 4 (b is mutated by this function to store the length of s)
|
||||
func writeLengthPrefixedString(w io.Writer, b []byte, s string) {
|
||||
writeLength(w, b, len(s))
|
||||
if _, err := w.Write(toBytes(s)); err != nil {
|
||||
panic(err) // Write() on hash never fails
|
||||
}
|
||||
}
|
||||
|
||||
// writeLength encodes length into b and then writes it via the given writer
|
||||
// the length of b is assumed to be 4
|
||||
func writeLength(w io.Writer, b []byte, length int) {
|
||||
binary.BigEndian.PutUint32(b, uint32(length))
|
||||
if _, err := w.Write(b); err != nil {
|
||||
panic(err) // Write() on hash never fails
|
||||
}
|
||||
}
|
||||
|
||||
// toBytes performs unholy acts to avoid allocations
|
||||
func toBytes(s string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(&s))
|
||||
}
|
||||
|
||||
// toString performs unholy acts to avoid allocations
|
||||
func toString(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// simple recorder that only appends warning
|
||||
type recorder struct {
|
||||
mu sync.Mutex
|
||||
warnings []*cacheWarning
|
||||
}
|
||||
|
||||
// AddWarning adds a warning to recorder.
|
||||
func (r *recorder) AddWarning(agent, text string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.warnings = append(r.warnings, &cacheWarning{agent: agent, text: text})
|
||||
}
|
||||
|
||||
func (r *recorder) extractWarnings() []*cacheWarning {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
warnings := r.warnings
|
||||
r.warnings = nil
|
||||
return warnings
|
||||
}
|
126
vendor/k8s.io/apiserver/pkg/authentication/token/cache/stats.go
generated
vendored
Normal file
126
vendor/k8s.io/apiserver/pkg/authentication/token/cache/stats.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
Copyright 2019 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
var (
|
||||
requestLatency = metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: "authentication",
|
||||
Subsystem: "token_cache",
|
||||
Name: "request_duration_seconds",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"status"},
|
||||
)
|
||||
requestCount = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: "authentication",
|
||||
Subsystem: "token_cache",
|
||||
Name: "request_total",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"status"},
|
||||
)
|
||||
fetchCount = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: "authentication",
|
||||
Subsystem: "token_cache",
|
||||
Name: "fetch_total",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"status"},
|
||||
)
|
||||
activeFetchCount = metrics.NewGaugeVec(
|
||||
&metrics.GaugeOpts{
|
||||
Namespace: "authentication",
|
||||
Subsystem: "token_cache",
|
||||
Name: "active_fetch_count",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"status"},
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
legacyregistry.MustRegister(
|
||||
requestLatency,
|
||||
requestCount,
|
||||
fetchCount,
|
||||
activeFetchCount,
|
||||
)
|
||||
}
|
||||
|
||||
const (
|
||||
hitTag = "hit"
|
||||
missTag = "miss"
|
||||
|
||||
fetchFailedTag = "error"
|
||||
fetchOkTag = "ok"
|
||||
|
||||
fetchInFlightTag = "in_flight"
|
||||
fetchBlockedTag = "blocked"
|
||||
)
|
||||
|
||||
type statsCollector struct{}
|
||||
|
||||
var stats = statsCollector{}
|
||||
|
||||
func (statsCollector) authenticating(ctx context.Context) func(hit bool) {
|
||||
start := time.Now()
|
||||
return func(hit bool) {
|
||||
var tag string
|
||||
if hit {
|
||||
tag = hitTag
|
||||
} else {
|
||||
tag = missTag
|
||||
}
|
||||
|
||||
latency := time.Since(start)
|
||||
|
||||
requestCount.WithContext(ctx).WithLabelValues(tag).Inc()
|
||||
requestLatency.WithContext(ctx).WithLabelValues(tag).Observe(float64(latency.Milliseconds()) / 1000)
|
||||
}
|
||||
}
|
||||
|
||||
func (statsCollector) blocking(ctx context.Context) func() {
|
||||
activeFetchCount.WithContext(ctx).WithLabelValues(fetchBlockedTag).Inc()
|
||||
return activeFetchCount.WithContext(ctx).WithLabelValues(fetchBlockedTag).Dec
|
||||
}
|
||||
|
||||
func (statsCollector) fetching(ctx context.Context) func(ok bool) {
|
||||
activeFetchCount.WithContext(ctx).WithLabelValues(fetchInFlightTag).Inc()
|
||||
return func(ok bool) {
|
||||
var tag string
|
||||
if ok {
|
||||
tag = fetchOkTag
|
||||
} else {
|
||||
tag = fetchFailedTag
|
||||
}
|
||||
|
||||
fetchCount.WithContext(ctx).WithLabelValues(tag).Inc()
|
||||
|
||||
activeFetchCount.WithContext(ctx).WithLabelValues(fetchInFlightTag).Dec()
|
||||
}
|
||||
}
|
99
vendor/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile.go
generated
vendored
Normal file
99
vendor/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2014 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 tokenfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type TokenAuthenticator struct {
|
||||
tokens map[string]*user.DefaultInfo
|
||||
}
|
||||
|
||||
// New returns a TokenAuthenticator for a single token
|
||||
func New(tokens map[string]*user.DefaultInfo) *TokenAuthenticator {
|
||||
return &TokenAuthenticator{
|
||||
tokens: tokens,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCSV returns a TokenAuthenticator, populated from a CSV file.
|
||||
// The CSV file must contain records in the format "token,username,useruid"
|
||||
func NewCSV(path string) (*TokenAuthenticator, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
recordNum := 0
|
||||
tokens := make(map[string]*user.DefaultInfo)
|
||||
reader := csv.NewReader(file)
|
||||
reader.FieldsPerRecord = -1
|
||||
for {
|
||||
record, err := reader.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(record) < 3 {
|
||||
return nil, fmt.Errorf("token file '%s' must have at least 3 columns (token, user name, user uid), found %d", path, len(record))
|
||||
}
|
||||
|
||||
recordNum++
|
||||
if record[0] == "" {
|
||||
klog.Warningf("empty token has been found in token file '%s', record number '%d'", path, recordNum)
|
||||
continue
|
||||
}
|
||||
|
||||
obj := &user.DefaultInfo{
|
||||
Name: record[1],
|
||||
UID: record[2],
|
||||
}
|
||||
if _, exist := tokens[record[0]]; exist {
|
||||
klog.Warningf("duplicate token has been found in token file '%s', record number '%d'", path, recordNum)
|
||||
}
|
||||
tokens[record[0]] = obj
|
||||
|
||||
if len(record) >= 4 {
|
||||
obj.Groups = strings.Split(record[3], ",")
|
||||
}
|
||||
}
|
||||
|
||||
return &TokenAuthenticator{
|
||||
tokens: tokens,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *TokenAuthenticator) AuthenticateToken(ctx context.Context, value string) (*authenticator.Response, bool, error) {
|
||||
user, ok := a.tokens[value]
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
return &authenticator.Response{User: user}, true, nil
|
||||
}
|
95
vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go
generated
vendored
Normal file
95
vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
Copyright 2016 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 authorizerfactory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// alwaysAllowAuthorizer is an implementation of authorizer.Attributes
|
||||
// which always says yes to an authorization request.
|
||||
// It is useful in tests and when using kubernetes in an open manner.
|
||||
type alwaysAllowAuthorizer struct{}
|
||||
|
||||
func (alwaysAllowAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
return []authorizer.ResourceRuleInfo{
|
||||
&authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
}, []authorizer.NonResourceRuleInfo{
|
||||
&authorizer.DefaultNonResourceRuleInfo{
|
||||
Verbs: []string{"*"},
|
||||
NonResourceURLs: []string{"*"},
|
||||
},
|
||||
}, false, nil
|
||||
}
|
||||
|
||||
func NewAlwaysAllowAuthorizer() *alwaysAllowAuthorizer {
|
||||
return new(alwaysAllowAuthorizer)
|
||||
}
|
||||
|
||||
// alwaysDenyAuthorizer is an implementation of authorizer.Attributes
|
||||
// which always says no to an authorization request.
|
||||
// It is useful in unit tests to force an operation to be forbidden.
|
||||
type alwaysDenyAuthorizer struct{}
|
||||
|
||||
func (alwaysDenyAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
|
||||
return authorizer.DecisionNoOpinion, "Everything is forbidden.", nil
|
||||
}
|
||||
|
||||
func (alwaysDenyAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, nil
|
||||
}
|
||||
|
||||
func NewAlwaysDenyAuthorizer() *alwaysDenyAuthorizer {
|
||||
return new(alwaysDenyAuthorizer)
|
||||
}
|
||||
|
||||
type privilegedGroupAuthorizer struct {
|
||||
groups []string
|
||||
}
|
||||
|
||||
func (r *privilegedGroupAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
if attr.GetUser() == nil {
|
||||
return authorizer.DecisionNoOpinion, "Error", errors.New("no user on request.")
|
||||
}
|
||||
for _, attr_group := range attr.GetUser().GetGroups() {
|
||||
for _, priv_group := range r.groups {
|
||||
if priv_group == attr_group {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
|
||||
// NewPrivilegedGroups is for use in loopback scenarios
|
||||
func NewPrivilegedGroups(groups ...string) *privilegedGroupAuthorizer {
|
||||
return &privilegedGroupAuthorizer{
|
||||
groups: groups,
|
||||
}
|
||||
}
|
62
vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/delegating.go
generated
vendored
Normal file
62
vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/delegating.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2016 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 authorizerfactory
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/plugin/pkg/authorizer/webhook"
|
||||
authorizationclient "k8s.io/client-go/kubernetes/typed/authorization/v1"
|
||||
)
|
||||
|
||||
// DelegatingAuthorizerConfig is the minimal configuration needed to create an authenticator
|
||||
// built to delegate authorization to a kube API server
|
||||
type DelegatingAuthorizerConfig struct {
|
||||
SubjectAccessReviewClient authorizationclient.AuthorizationV1Interface
|
||||
|
||||
// AllowCacheTTL is the length of time that a successful authorization response will be cached
|
||||
AllowCacheTTL time.Duration
|
||||
|
||||
// DenyCacheTTL is the length of time that an unsuccessful authorization response will be cached.
|
||||
// You generally want more responsive, "deny, try again" flows.
|
||||
DenyCacheTTL time.Duration
|
||||
|
||||
// WebhookRetryBackoff specifies the backoff parameters for the authorization webhook retry logic.
|
||||
// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
|
||||
// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
|
||||
WebhookRetryBackoff *wait.Backoff
|
||||
}
|
||||
|
||||
func (c DelegatingAuthorizerConfig) New() (authorizer.Authorizer, error) {
|
||||
if c.WebhookRetryBackoff == nil {
|
||||
return nil, errors.New("retry backoff parameters for delegating authorization webhook has not been specified")
|
||||
}
|
||||
|
||||
return webhook.NewFromInterface(
|
||||
c.SubjectAccessReviewClient,
|
||||
c.AllowCacheTTL,
|
||||
c.DenyCacheTTL,
|
||||
*c.WebhookRetryBackoff,
|
||||
webhook.AuthorizerMetrics{
|
||||
RecordRequestTotal: RecordRequestTotal,
|
||||
RecordRequestLatency: RecordRequestLatency,
|
||||
},
|
||||
)
|
||||
}
|
69
vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/metrics.go
generated
vendored
Normal file
69
vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/metrics.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
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 authorizerfactory
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
compbasemetrics "k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
type registerables []compbasemetrics.Registerable
|
||||
|
||||
// init registers all metrics
|
||||
func init() {
|
||||
for _, metric := range metrics {
|
||||
legacyregistry.MustRegister(metric)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
requestTotal = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_delegated_authz_request_total",
|
||||
Help: "Number of HTTP requests partitioned by status code.",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"code"},
|
||||
)
|
||||
|
||||
requestLatency = compbasemetrics.NewHistogramVec(
|
||||
&compbasemetrics.HistogramOpts{
|
||||
Name: "apiserver_delegated_authz_request_duration_seconds",
|
||||
Help: "Request latency in seconds. Broken down by status code.",
|
||||
Buckets: []float64{0.25, 0.5, 0.7, 1, 1.5, 3, 5, 10},
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"code"},
|
||||
)
|
||||
|
||||
metrics = registerables{
|
||||
requestTotal,
|
||||
requestLatency,
|
||||
}
|
||||
)
|
||||
|
||||
// RecordRequestTotal increments the total number of requests for the delegated authorization.
|
||||
func RecordRequestTotal(ctx context.Context, code string) {
|
||||
requestTotal.WithContext(ctx).WithLabelValues(code).Add(1)
|
||||
}
|
||||
|
||||
// RecordRequestLatency measures request latency in seconds for the delegated authorization. Broken down by status code.
|
||||
func RecordRequestLatency(ctx context.Context, code string, latency float64) {
|
||||
requestLatency.WithContext(ctx).WithLabelValues(code).Observe(latency)
|
||||
}
|
18
vendor/k8s.io/apiserver/pkg/authorization/path/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/apiserver/pkg/authorization/path/doc.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2018 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 path contains an authorizer that allows certain paths and path prefixes.
|
||||
package path // import "k8s.io/apiserver/pkg/authorization/path"
|
68
vendor/k8s.io/apiserver/pkg/authorization/path/path.go
generated
vendored
Normal file
68
vendor/k8s.io/apiserver/pkg/authorization/path/path.go
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2018 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 path
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// NewAuthorizer returns an authorizer which accepts a given set of paths.
|
||||
// Each path is either a fully matching path or it ends in * in case a prefix match is done. A leading / is optional.
|
||||
func NewAuthorizer(alwaysAllowPaths []string) (authorizer.Authorizer, error) {
|
||||
var prefixes []string
|
||||
paths := sets.NewString()
|
||||
for _, p := range alwaysAllowPaths {
|
||||
p = strings.TrimPrefix(p, "/")
|
||||
if len(p) == 0 {
|
||||
// matches "/"
|
||||
paths.Insert(p)
|
||||
continue
|
||||
}
|
||||
if strings.ContainsRune(p[:len(p)-1], '*') {
|
||||
return nil, fmt.Errorf("only trailing * allowed in %q", p)
|
||||
}
|
||||
if strings.HasSuffix(p, "*") {
|
||||
prefixes = append(prefixes, p[:len(p)-1])
|
||||
} else {
|
||||
paths.Insert(p)
|
||||
}
|
||||
}
|
||||
|
||||
return authorizer.AuthorizerFunc(func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
if a.IsResourceRequest() {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
|
||||
pth := strings.TrimPrefix(a.GetPath(), "/")
|
||||
if paths.Has(pth) {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(pth, prefix) {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}), nil
|
||||
}
|
106
vendor/k8s.io/apiserver/pkg/authorization/union/union.go
generated
vendored
Normal file
106
vendor/k8s.io/apiserver/pkg/authorization/union/union.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
Copyright 2014 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 union implements an authorizer that combines multiple subauthorizer.
|
||||
// The union authorizer iterates over each subauthorizer and returns the first
|
||||
// decision that is either an Allow decision or a Deny decision. If a
|
||||
// subauthorizer returns a NoOpinion, then the union authorizer moves onto the
|
||||
// next authorizer or, if the subauthorizer was the last authorizer, returns
|
||||
// NoOpinion as the aggregate decision. I.e. union authorizer creates an
|
||||
// aggregate decision and supports short-circuit allows and denies from
|
||||
// subauthorizers.
|
||||
package union
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// unionAuthzHandler authorizer against a chain of authorizer.Authorizer
|
||||
type unionAuthzHandler []authorizer.Authorizer
|
||||
|
||||
// New returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
|
||||
func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer {
|
||||
return unionAuthzHandler(authorizationHandlers)
|
||||
}
|
||||
|
||||
// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful
|
||||
func (authzHandler unionAuthzHandler) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
var (
|
||||
errlist []error
|
||||
reasonlist []string
|
||||
)
|
||||
|
||||
for _, currAuthzHandler := range authzHandler {
|
||||
decision, reason, err := currAuthzHandler.Authorize(ctx, a)
|
||||
|
||||
if err != nil {
|
||||
errlist = append(errlist, err)
|
||||
}
|
||||
if len(reason) != 0 {
|
||||
reasonlist = append(reasonlist, reason)
|
||||
}
|
||||
switch decision {
|
||||
case authorizer.DecisionAllow, authorizer.DecisionDeny:
|
||||
return decision, reason, err
|
||||
case authorizer.DecisionNoOpinion:
|
||||
// continue to the next authorizer
|
||||
}
|
||||
}
|
||||
|
||||
return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
|
||||
}
|
||||
|
||||
// unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver
|
||||
type unionAuthzRulesHandler []authorizer.RuleResolver
|
||||
|
||||
// NewRuleResolvers returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
|
||||
func NewRuleResolvers(authorizationHandlers ...authorizer.RuleResolver) authorizer.RuleResolver {
|
||||
return unionAuthzRulesHandler(authorizationHandlers)
|
||||
}
|
||||
|
||||
// RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful
|
||||
func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
var (
|
||||
errList []error
|
||||
resourceRulesList []authorizer.ResourceRuleInfo
|
||||
nonResourceRulesList []authorizer.NonResourceRuleInfo
|
||||
)
|
||||
incompleteStatus := false
|
||||
|
||||
for _, currAuthzHandler := range authzHandler {
|
||||
resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace)
|
||||
|
||||
if incomplete {
|
||||
incompleteStatus = true
|
||||
}
|
||||
if err != nil {
|
||||
errList = append(errList, err)
|
||||
}
|
||||
if len(resourceRules) > 0 {
|
||||
resourceRulesList = append(resourceRulesList, resourceRules...)
|
||||
}
|
||||
if len(nonResourceRules) > 0 {
|
||||
nonResourceRulesList = append(nonResourceRulesList, nonResourceRules...)
|
||||
}
|
||||
}
|
||||
|
||||
return resourceRulesList, nonResourceRulesList, incompleteStatus, utilerrors.NewAggregate(errList)
|
||||
}
|
47
vendor/k8s.io/apiserver/pkg/cel/errors.go
generated
vendored
Normal file
47
vendor/k8s.io/apiserver/pkg/cel/errors.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
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 cel
|
||||
|
||||
// Error is an implementation of the 'error' interface, which represents a
|
||||
// XValidation error.
|
||||
type Error struct {
|
||||
Type ErrorType
|
||||
Detail string
|
||||
}
|
||||
|
||||
var _ error = &Error{}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (v *Error) Error() string {
|
||||
return v.Detail
|
||||
}
|
||||
|
||||
// ErrorType is a machine readable value providing more detail about why
|
||||
// a XValidation is invalid.
|
||||
type ErrorType string
|
||||
|
||||
const (
|
||||
// ErrorTypeRequired is used to report withNullable values that are not
|
||||
// provided (e.g. empty strings, null values, or empty arrays). See
|
||||
// Required().
|
||||
ErrorTypeRequired ErrorType = "RuleRequired"
|
||||
// ErrorTypeInvalid is used to report malformed values
|
||||
ErrorTypeInvalid ErrorType = "RuleInvalid"
|
||||
// ErrorTypeInternal is used to report other errors that are not related
|
||||
// to user input. See InternalError().
|
||||
ErrorTypeInternal ErrorType = "InternalError"
|
||||
)
|
170
vendor/k8s.io/apiserver/pkg/cel/escaping.go
generated
vendored
Normal file
170
vendor/k8s.io/apiserver/pkg/cel/escaping.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
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 cel
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// celReservedSymbols is a list of RESERVED symbols defined in the CEL lexer.
|
||||
// No identifiers are allowed to collide with these symbols.
|
||||
// https://github.com/google/cel-spec/blob/master/doc/langdef.md#syntax
|
||||
var celReservedSymbols = sets.NewString(
|
||||
"true", "false", "null", "in",
|
||||
"as", "break", "const", "continue", "else",
|
||||
"for", "function", "if", "import", "let",
|
||||
"loop", "package", "namespace", "return", // !! 'namespace' is used heavily in Kubernetes
|
||||
"var", "void", "while",
|
||||
)
|
||||
|
||||
// expandMatcher matches the escape sequence, characters that are escaped, and characters that are unsupported
|
||||
var expandMatcher = regexp.MustCompile(`(__|[-./]|[^a-zA-Z0-9-./_])`)
|
||||
|
||||
// newCharacterFilter returns a boolean array to indicate the allowed characters
|
||||
func newCharacterFilter(characters string) []bool {
|
||||
maxChar := 0
|
||||
for _, c := range characters {
|
||||
if maxChar < int(c) {
|
||||
maxChar = int(c)
|
||||
}
|
||||
}
|
||||
filter := make([]bool, maxChar+1)
|
||||
|
||||
for _, c := range characters {
|
||||
filter[int(c)] = true
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
type escapeCheck struct {
|
||||
canSkipRegex bool
|
||||
invalidCharFound bool
|
||||
}
|
||||
|
||||
// skipRegexCheck checks if escape would be skipped.
|
||||
// if invalidCharFound is true, it must have invalid character; if invalidCharFound is false, not sure if it has invalid character or not
|
||||
func skipRegexCheck(ident string) escapeCheck {
|
||||
escapeCheck := escapeCheck{canSkipRegex: true, invalidCharFound: false}
|
||||
// skip escape if possible
|
||||
previous_underscore := false
|
||||
for _, c := range ident {
|
||||
if c == '/' || c == '-' || c == '.' {
|
||||
escapeCheck.canSkipRegex = false
|
||||
return escapeCheck
|
||||
}
|
||||
intc := int(c)
|
||||
if intc < 0 || intc >= len(validCharacterFilter) || !validCharacterFilter[intc] {
|
||||
escapeCheck.invalidCharFound = true
|
||||
return escapeCheck
|
||||
}
|
||||
if c == '_' && previous_underscore {
|
||||
escapeCheck.canSkipRegex = false
|
||||
return escapeCheck
|
||||
}
|
||||
|
||||
previous_underscore = c == '_'
|
||||
}
|
||||
return escapeCheck
|
||||
}
|
||||
|
||||
// validCharacterFilter indicates the allowed characters.
|
||||
var validCharacterFilter = newCharacterFilter("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")
|
||||
|
||||
// Escape escapes ident and returns a CEL identifier (of the form '[a-zA-Z_][a-zA-Z0-9_]*'), or returns
|
||||
// false if the ident does not match the supported input format of `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*`.
|
||||
// Escaping Rules:
|
||||
// - '__' escapes to '__underscores__'
|
||||
// - '.' escapes to '__dot__'
|
||||
// - '-' escapes to '__dash__'
|
||||
// - '/' escapes to '__slash__'
|
||||
// - Identifiers that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are: "true", "false",
|
||||
// "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if", "import", "let", loop", "package",
|
||||
// "namespace", "return".
|
||||
func Escape(ident string) (string, bool) {
|
||||
if len(ident) == 0 || ('0' <= ident[0] && ident[0] <= '9') {
|
||||
return "", false
|
||||
}
|
||||
if celReservedSymbols.Has(ident) {
|
||||
return "__" + ident + "__", true
|
||||
}
|
||||
|
||||
escapeCheck := skipRegexCheck(ident)
|
||||
if escapeCheck.invalidCharFound {
|
||||
return "", false
|
||||
}
|
||||
if escapeCheck.canSkipRegex {
|
||||
return ident, true
|
||||
}
|
||||
|
||||
ok := true
|
||||
ident = expandMatcher.ReplaceAllStringFunc(ident, func(s string) string {
|
||||
switch s {
|
||||
case "__":
|
||||
return "__underscores__"
|
||||
case ".":
|
||||
return "__dot__"
|
||||
case "-":
|
||||
return "__dash__"
|
||||
case "/":
|
||||
return "__slash__"
|
||||
default: // matched a unsupported supported
|
||||
ok = false
|
||||
return ""
|
||||
}
|
||||
})
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return ident, true
|
||||
}
|
||||
|
||||
var unexpandMatcher = regexp.MustCompile(`(_{2}[^_]+_{2})`)
|
||||
|
||||
// Unescape unescapes an CEL identifier containing the escape sequences described in Escape, or return false if the
|
||||
// string contains invalid escape sequences. The escaped input is expected to be a valid CEL identifier, but is
|
||||
// not checked.
|
||||
func Unescape(escaped string) (string, bool) {
|
||||
ok := true
|
||||
escaped = unexpandMatcher.ReplaceAllStringFunc(escaped, func(s string) string {
|
||||
contents := s[2 : len(s)-2]
|
||||
switch contents {
|
||||
case "underscores":
|
||||
return "__"
|
||||
case "dot":
|
||||
return "."
|
||||
case "dash":
|
||||
return "-"
|
||||
case "slash":
|
||||
return "/"
|
||||
}
|
||||
if celReservedSymbols.Has(contents) {
|
||||
if len(s) != len(escaped) {
|
||||
ok = false
|
||||
}
|
||||
return contents
|
||||
}
|
||||
ok = false
|
||||
return ""
|
||||
})
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return escaped, true
|
||||
}
|
268
vendor/k8s.io/apiserver/pkg/cel/library/cost.go
generated
vendored
Normal file
268
vendor/k8s.io/apiserver/pkg/cel/library/cost.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
/*
|
||||
Copyright 2022 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 library
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/google/cel-go/checker"
|
||||
"github.com/google/cel-go/common"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// CostEstimator implements CEL's interpretable.ActualCostEstimator and checker.CostEstimator.
|
||||
type CostEstimator struct {
|
||||
// SizeEstimator provides a CostEstimator.EstimateSize that this CostEstimator will delegate size estimation
|
||||
// calculations to if the size is not well known (i.e. a constant).
|
||||
SizeEstimator checker.CostEstimator
|
||||
}
|
||||
|
||||
func (l *CostEstimator) CallCost(function, overloadId string, args []ref.Val, result ref.Val) *uint64 {
|
||||
switch function {
|
||||
case "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
|
||||
var cost uint64
|
||||
if len(args) > 0 {
|
||||
cost += traversalCost(args[0]) // these O(n) operations all cost roughly the cost of a single traversal
|
||||
}
|
||||
return &cost
|
||||
case "url", "lowerAscii", "upperAscii", "substring", "trim":
|
||||
if len(args) >= 1 {
|
||||
cost := uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor))
|
||||
return &cost
|
||||
}
|
||||
case "replace", "split":
|
||||
if len(args) >= 1 {
|
||||
// cost is the traversal plus the construction of the result
|
||||
cost := uint64(math.Ceil(float64(actualSize(args[0])) * 2 * common.StringTraversalCostFactor))
|
||||
return &cost
|
||||
}
|
||||
case "join":
|
||||
if len(args) >= 1 {
|
||||
cost := uint64(math.Ceil(float64(actualSize(result)) * 2 * common.StringTraversalCostFactor))
|
||||
return &cost
|
||||
}
|
||||
case "find", "findAll":
|
||||
if len(args) >= 2 {
|
||||
strCost := uint64(math.Ceil((1.0 + float64(actualSize(args[0]))) * common.StringTraversalCostFactor))
|
||||
// We don't know how many expressions are in the regex, just the string length (a huge
|
||||
// improvement here would be to somehow get a count the number of expressions in the regex or
|
||||
// how many states are in the regex state machine and use that to measure regex cost).
|
||||
// For now, we're making a guess that each expression in a regex is typically at least 4 chars
|
||||
// in length.
|
||||
regexCost := uint64(math.Ceil(float64(actualSize(args[1])) * common.RegexStringLengthCostFactor))
|
||||
cost := strCost * regexCost
|
||||
return &cost
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
|
||||
// WARNING: Any changes to this code impact API compatibility! The estimated cost is used to determine which CEL rules may be written to a
|
||||
// CRD and any change (cost increases and cost decreases) are breaking.
|
||||
switch function {
|
||||
case "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
|
||||
if target != nil {
|
||||
// Charge 1 cost for comparing each element in the list
|
||||
elCost := checker.CostEstimate{Min: 1, Max: 1}
|
||||
// If the list contains strings or bytes, add the cost of traversing all the strings/bytes as a way
|
||||
// of estimating the additional comparison cost.
|
||||
if elNode := l.listElementNode(*target); elNode != nil {
|
||||
t := elNode.Type().GetPrimitive()
|
||||
if t == exprpb.Type_STRING || t == exprpb.Type_BYTES {
|
||||
sz := l.sizeEstimate(elNode)
|
||||
elCost = elCost.Add(sz.MultiplyByCostFactor(common.StringTraversalCostFactor))
|
||||
}
|
||||
return &checker.CallEstimate{CostEstimate: l.sizeEstimate(*target).MultiplyByCost(elCost)}
|
||||
} else { // the target is a string, which is supported by indexOf and lastIndexOf
|
||||
return &checker.CallEstimate{CostEstimate: l.sizeEstimate(*target).MultiplyByCostFactor(common.StringTraversalCostFactor)}
|
||||
}
|
||||
|
||||
}
|
||||
case "url":
|
||||
if len(args) == 1 {
|
||||
sz := l.sizeEstimate(args[0])
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor)}
|
||||
}
|
||||
case "lowerAscii", "upperAscii", "substring", "trim":
|
||||
if target != nil {
|
||||
sz := l.sizeEstimate(*target)
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor), ResultSize: &sz}
|
||||
}
|
||||
case "replace":
|
||||
if target != nil && len(args) >= 2 {
|
||||
sz := l.sizeEstimate(*target)
|
||||
toReplaceSz := l.sizeEstimate(args[0])
|
||||
replaceWithSz := l.sizeEstimate(args[1])
|
||||
// smallest possible result: smallest input size composed of the largest possible substrings being replaced by smallest possible replacement
|
||||
minSz := uint64(math.Ceil(float64(sz.Min)/float64(toReplaceSz.Max))) * replaceWithSz.Min
|
||||
// largest possible result: largest input size composed of the smallest possible substrings being replaced by largest possible replacement
|
||||
maxSz := uint64(math.Ceil(float64(sz.Max)/float64(toReplaceSz.Min))) * replaceWithSz.Max
|
||||
|
||||
// cost is the traversal plus the construction of the result
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &checker.SizeEstimate{Min: minSz, Max: maxSz}}
|
||||
}
|
||||
case "split":
|
||||
if target != nil {
|
||||
sz := l.sizeEstimate(*target)
|
||||
|
||||
// Worst case size is where is that a separator of "" is used, and each char is returned as a list element.
|
||||
max := sz.Max
|
||||
if len(args) > 1 {
|
||||
if c := args[1].Expr().GetConstExpr(); c != nil {
|
||||
max = uint64(c.GetInt64Value())
|
||||
}
|
||||
}
|
||||
// Cost is the traversal plus the construction of the result.
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &checker.SizeEstimate{Min: 0, Max: max}}
|
||||
}
|
||||
case "join":
|
||||
if target != nil {
|
||||
var sz checker.SizeEstimate
|
||||
listSize := l.sizeEstimate(*target)
|
||||
if elNode := l.listElementNode(*target); elNode != nil {
|
||||
elemSize := l.sizeEstimate(elNode)
|
||||
sz = listSize.Multiply(elemSize)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
sepSize := l.sizeEstimate(args[0])
|
||||
minSeparators := uint64(0)
|
||||
maxSeparators := uint64(0)
|
||||
if listSize.Min > 0 {
|
||||
minSeparators = listSize.Min - 1
|
||||
}
|
||||
if listSize.Max > 0 {
|
||||
maxSeparators = listSize.Max - 1
|
||||
}
|
||||
sz = sz.Add(sepSize.Multiply(checker.SizeEstimate{Min: minSeparators, Max: maxSeparators}))
|
||||
}
|
||||
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor), ResultSize: &sz}
|
||||
}
|
||||
case "find", "findAll":
|
||||
if target != nil && len(args) >= 1 {
|
||||
sz := l.sizeEstimate(*target)
|
||||
// Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0
|
||||
// in case where string is empty but regex is still expensive.
|
||||
strCost := sz.Add(checker.SizeEstimate{Min: 1, Max: 1}).MultiplyByCostFactor(common.StringTraversalCostFactor)
|
||||
// We don't know how many expressions are in the regex, just the string length (a huge
|
||||
// improvement here would be to somehow get a count the number of expressions in the regex or
|
||||
// how many states are in the regex state machine and use that to measure regex cost).
|
||||
// For now, we're making a guess that each expression in a regex is typically at least 4 chars
|
||||
// in length.
|
||||
regexCost := l.sizeEstimate(args[0]).MultiplyByCostFactor(common.RegexStringLengthCostFactor)
|
||||
// worst case size of result is that every char is returned as separate find result.
|
||||
return &checker.CallEstimate{CostEstimate: strCost.Multiply(regexCost), ResultSize: &checker.SizeEstimate{Min: 0, Max: sz.Max}}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func actualSize(value ref.Val) uint64 {
|
||||
if sz, ok := value.(traits.Sizer); ok {
|
||||
return uint64(sz.Size().(types.Int))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (l *CostEstimator) sizeEstimate(t checker.AstNode) checker.SizeEstimate {
|
||||
if sz := t.ComputedSize(); sz != nil {
|
||||
return *sz
|
||||
}
|
||||
if sz := l.EstimateSize(t); sz != nil {
|
||||
return *sz
|
||||
}
|
||||
return checker.SizeEstimate{Min: 0, Max: math.MaxUint64}
|
||||
}
|
||||
|
||||
func (l *CostEstimator) listElementNode(list checker.AstNode) checker.AstNode {
|
||||
if lt := list.Type().GetListType(); lt != nil {
|
||||
nodePath := list.Path()
|
||||
if nodePath != nil {
|
||||
// Provide path if we have it so that a OpenAPIv3 maxLength validation can be looked up, if it exists
|
||||
// for this node.
|
||||
path := make([]string, len(nodePath)+1)
|
||||
copy(path, nodePath)
|
||||
path[len(nodePath)] = "@items"
|
||||
return &itemsNode{path: path, t: lt.GetElemType(), expr: nil}
|
||||
} else {
|
||||
// Provide just the type if no path is available so that worst case size can be looked up based on type.
|
||||
return &itemsNode{t: lt.GetElemType(), expr: nil}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *CostEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate {
|
||||
if l.SizeEstimator != nil {
|
||||
return l.SizeEstimator.EstimateSize(element)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type itemsNode struct {
|
||||
path []string
|
||||
t *exprpb.Type
|
||||
expr *exprpb.Expr
|
||||
}
|
||||
|
||||
func (i *itemsNode) Path() []string {
|
||||
return i.path
|
||||
}
|
||||
|
||||
func (i *itemsNode) Type() *exprpb.Type {
|
||||
return i.t
|
||||
}
|
||||
|
||||
func (i *itemsNode) Expr() *exprpb.Expr {
|
||||
return i.expr
|
||||
}
|
||||
|
||||
func (i *itemsNode) ComputedSize() *checker.SizeEstimate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// traversalCost computes the cost of traversing a ref.Val as a data tree.
|
||||
func traversalCost(v ref.Val) uint64 {
|
||||
// TODO: This could potentially be optimized by sampling maps and lists instead of traversing.
|
||||
switch vt := v.(type) {
|
||||
case types.String:
|
||||
return uint64(float64(len(string(vt))) * common.StringTraversalCostFactor)
|
||||
case types.Bytes:
|
||||
return uint64(float64(len([]byte(vt))) * common.StringTraversalCostFactor)
|
||||
case traits.Lister:
|
||||
cost := uint64(0)
|
||||
for it := vt.Iterator(); it.HasNext() == types.True; {
|
||||
i := it.Next()
|
||||
cost += traversalCost(i)
|
||||
}
|
||||
return cost
|
||||
case traits.Mapper: // maps and objects
|
||||
cost := uint64(0)
|
||||
for it := vt.Iterator(); it.HasNext() == types.True; {
|
||||
k := it.Next()
|
||||
cost += traversalCost(k) + traversalCost(vt.Get(k))
|
||||
}
|
||||
return cost
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
34
vendor/k8s.io/apiserver/pkg/cel/library/libraries.go
generated
vendored
Normal file
34
vendor/k8s.io/apiserver/pkg/cel/library/libraries.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2022 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 library
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/ext"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
)
|
||||
|
||||
// ExtensionLibs declares the set of CEL extension libraries available everywhere CEL is used in Kubernetes.
|
||||
var ExtensionLibs = append(k8sExtensionLibs, ext.Strings())
|
||||
|
||||
var k8sExtensionLibs = []cel.EnvOption{
|
||||
URLs(),
|
||||
Regex(),
|
||||
Lists(),
|
||||
}
|
||||
|
||||
var ExtensionLibRegexOptimizations = []*interpreter.RegexOptimization{FindRegexOptimization, FindAllRegexOptimization}
|
312
vendor/k8s.io/apiserver/pkg/cel/library/lists.go
generated
vendored
Normal file
312
vendor/k8s.io/apiserver/pkg/cel/library/lists.go
generated
vendored
Normal file
@ -0,0 +1,312 @@
|
||||
/*
|
||||
Copyright 2022 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 library
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
)
|
||||
|
||||
// Lists provides a CEL function library extension of list utility functions.
|
||||
//
|
||||
// isSorted
|
||||
//
|
||||
// Returns true if the provided list of comparable elements is sorted, else returns false.
|
||||
//
|
||||
// <list<T>>.isSorted() <bool>, T must be a comparable type
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// [1, 2, 3].isSorted() // return true
|
||||
// ['a', 'b', 'b', 'c'].isSorted() // return true
|
||||
// [2.0, 1.0].isSorted() // return false
|
||||
// [1].isSorted() // return true
|
||||
// [].isSorted() // return true
|
||||
//
|
||||
// sum
|
||||
//
|
||||
// Returns the sum of the elements of the provided list. Supports CEL number (int, uint, double) and duration types.
|
||||
//
|
||||
// <list<T>>.sum() <T>, T must be a numeric type or a duration
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// [1, 3].sum() // returns 4
|
||||
// [1.0, 3.0].sum() // returns 4.0
|
||||
// ['1m', '1s'].sum() // returns '1m1s'
|
||||
// emptyIntList.sum() // returns 0
|
||||
// emptyDoubleList.sum() // returns 0.0
|
||||
// [].sum() // returns 0
|
||||
//
|
||||
// min / max
|
||||
//
|
||||
// Returns the minimum/maximum valued element of the provided list. Supports all comparable types.
|
||||
// If the list is empty, an error is returned.
|
||||
//
|
||||
// <list<T>>.min() <T>, T must be a comparable type
|
||||
// <list<T>>.max() <T>, T must be a comparable type
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// [1, 3].min() // returns 1
|
||||
// [1, 3].max() // returns 3
|
||||
// [].min() // error
|
||||
// [1].min() // returns 1
|
||||
// ([0] + emptyList).min() // returns 0
|
||||
//
|
||||
// indexOf / lastIndexOf
|
||||
//
|
||||
// Returns either the first or last positional index of the provided element in the list.
|
||||
// If the element is not found, -1 is returned. Supports all equatable types.
|
||||
//
|
||||
// <list<T>>.indexOf(<T>) <int>, T must be an equatable type
|
||||
// <list<T>>.lastIndexOf(<T>) <int>, T must be an equatable type
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// [1, 2, 2, 3].indexOf(2) // returns 1
|
||||
// ['a', 'b', 'b', 'c'].lastIndexOf('b') // returns 2
|
||||
// [1.0].indexOf(1.1) // returns -1
|
||||
// [].indexOf('string') // returns -1
|
||||
func Lists() cel.EnvOption {
|
||||
return cel.Lib(listsLib)
|
||||
}
|
||||
|
||||
var listsLib = &lists{}
|
||||
|
||||
type lists struct{}
|
||||
|
||||
var paramA = cel.TypeParamType("A")
|
||||
|
||||
// CEL typeParams can be used to constraint to a specific trait (e.g. traits.ComparableType) if the 1st operand is the type to constrain.
|
||||
// But the functions we need to constrain are <list<paramType>>, not just <paramType>.
|
||||
// Make sure the order of overload set is deterministic
|
||||
type namedCELType struct {
|
||||
typeName string
|
||||
celType *cel.Type
|
||||
}
|
||||
|
||||
var summableTypes = []namedCELType{
|
||||
{typeName: "int", celType: cel.IntType},
|
||||
{typeName: "uint", celType: cel.UintType},
|
||||
{typeName: "double", celType: cel.DoubleType},
|
||||
{typeName: "duration", celType: cel.DurationType},
|
||||
}
|
||||
|
||||
var zeroValuesOfSummableTypes = map[string]ref.Val{
|
||||
"int": types.Int(0),
|
||||
"uint": types.Uint(0),
|
||||
"double": types.Double(0.0),
|
||||
"duration": types.Duration{Duration: 0},
|
||||
}
|
||||
var comparableTypes = []namedCELType{
|
||||
{typeName: "int", celType: cel.IntType},
|
||||
{typeName: "uint", celType: cel.UintType},
|
||||
{typeName: "double", celType: cel.DoubleType},
|
||||
{typeName: "bool", celType: cel.BoolType},
|
||||
{typeName: "duration", celType: cel.DurationType},
|
||||
{typeName: "timestamp", celType: cel.TimestampType},
|
||||
{typeName: "string", celType: cel.StringType},
|
||||
{typeName: "bytes", celType: cel.BytesType},
|
||||
}
|
||||
|
||||
// WARNING: All library additions or modifications must follow
|
||||
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2876-crd-validation-expression-language#function-library-updates
|
||||
var listsLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"isSorted": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_is_sorted_bool", name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, cel.BoolType, cel.UnaryBinding(isSorted))
|
||||
}),
|
||||
"sum": templatedOverloads(summableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_sum_%s", name, name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(func(list ref.Val) ref.Val {
|
||||
return sum(
|
||||
func() ref.Val {
|
||||
return zeroValuesOfSummableTypes[name]
|
||||
})(list)
|
||||
}))
|
||||
}),
|
||||
"max": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_max_%s", name, name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(max()))
|
||||
}),
|
||||
"min": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_min_%s", name, name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(min()))
|
||||
}),
|
||||
"indexOf": {
|
||||
cel.MemberOverload("list_a_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
|
||||
cel.BinaryBinding(indexOf)),
|
||||
},
|
||||
"lastIndexOf": {
|
||||
cel.MemberOverload("list_a_last_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
|
||||
cel.BinaryBinding(lastIndexOf)),
|
||||
},
|
||||
}
|
||||
|
||||
func (*lists) CompileOptions() []cel.EnvOption {
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range listsLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*lists) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func isSorted(val ref.Val) ref.Val {
|
||||
var prev traits.Comparer
|
||||
iterable, ok := val.(traits.Iterable)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(val)
|
||||
}
|
||||
for it := iterable.Iterator(); it.HasNext() == types.True; {
|
||||
next := it.Next()
|
||||
nextCmp, ok := next.(traits.Comparer)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(next)
|
||||
}
|
||||
if prev != nil {
|
||||
cmp := prev.Compare(next)
|
||||
if cmp == types.IntOne {
|
||||
return types.False
|
||||
}
|
||||
}
|
||||
prev = nextCmp
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
func sum(init func() ref.Val) functions.UnaryOp {
|
||||
return func(val ref.Val) ref.Val {
|
||||
i := init()
|
||||
acc, ok := i.(traits.Adder)
|
||||
if !ok {
|
||||
// Should never happen since all passed in init values are valid
|
||||
return types.MaybeNoSuchOverloadErr(i)
|
||||
}
|
||||
iterable, ok := val.(traits.Iterable)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(val)
|
||||
}
|
||||
for it := iterable.Iterator(); it.HasNext() == types.True; {
|
||||
next := it.Next()
|
||||
nextAdder, ok := next.(traits.Adder)
|
||||
if !ok {
|
||||
// Should never happen for type checked CEL programs
|
||||
return types.MaybeNoSuchOverloadErr(next)
|
||||
}
|
||||
if acc != nil {
|
||||
s := acc.Add(next)
|
||||
sum, ok := s.(traits.Adder)
|
||||
if !ok {
|
||||
// Should never happen for type checked CEL programs
|
||||
return types.MaybeNoSuchOverloadErr(s)
|
||||
}
|
||||
acc = sum
|
||||
} else {
|
||||
acc = nextAdder
|
||||
}
|
||||
}
|
||||
return acc.(ref.Val)
|
||||
}
|
||||
}
|
||||
|
||||
func min() functions.UnaryOp {
|
||||
return cmp("min", types.IntOne)
|
||||
}
|
||||
|
||||
func max() functions.UnaryOp {
|
||||
return cmp("max", types.IntNegOne)
|
||||
}
|
||||
|
||||
func cmp(opName string, opPreferCmpResult ref.Val) functions.UnaryOp {
|
||||
return func(val ref.Val) ref.Val {
|
||||
var result traits.Comparer
|
||||
iterable, ok := val.(traits.Iterable)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(val)
|
||||
}
|
||||
for it := iterable.Iterator(); it.HasNext() == types.True; {
|
||||
next := it.Next()
|
||||
nextCmp, ok := next.(traits.Comparer)
|
||||
if !ok {
|
||||
// Should never happen for type checked CEL programs
|
||||
return types.MaybeNoSuchOverloadErr(next)
|
||||
}
|
||||
if result == nil {
|
||||
result = nextCmp
|
||||
} else {
|
||||
cmp := result.Compare(next)
|
||||
if cmp == opPreferCmpResult {
|
||||
result = nextCmp
|
||||
}
|
||||
}
|
||||
}
|
||||
if result == nil {
|
||||
return types.NewErr("%s called on empty list", opName)
|
||||
}
|
||||
return result.(ref.Val)
|
||||
}
|
||||
}
|
||||
|
||||
func indexOf(list ref.Val, item ref.Val) ref.Val {
|
||||
lister, ok := list.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(list)
|
||||
}
|
||||
sz := lister.Size().(types.Int)
|
||||
for i := types.Int(0); i < sz; i++ {
|
||||
if lister.Get(types.Int(i)).Equal(item) == types.True {
|
||||
return types.Int(i)
|
||||
}
|
||||
}
|
||||
return types.Int(-1)
|
||||
}
|
||||
|
||||
func lastIndexOf(list ref.Val, item ref.Val) ref.Val {
|
||||
lister, ok := list.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(list)
|
||||
}
|
||||
sz := lister.Size().(types.Int)
|
||||
for i := sz - 1; i >= 0; i-- {
|
||||
if lister.Get(types.Int(i)).Equal(item) == types.True {
|
||||
return types.Int(i)
|
||||
}
|
||||
}
|
||||
return types.Int(-1)
|
||||
}
|
||||
|
||||
// templatedOverloads returns overloads for each of the provided types. The template function is called with each type
|
||||
// name (map key) and type to construct the overloads.
|
||||
func templatedOverloads(types []namedCELType, template func(name string, t *cel.Type) cel.FunctionOpt) []cel.FunctionOpt {
|
||||
overloads := make([]cel.FunctionOpt, len(types))
|
||||
i := 0
|
||||
for _, t := range types {
|
||||
overloads[i] = template(t.typeName, t.celType)
|
||||
i++
|
||||
}
|
||||
return overloads
|
||||
}
|
187
vendor/k8s.io/apiserver/pkg/cel/library/regex.go
generated
vendored
Normal file
187
vendor/k8s.io/apiserver/pkg/cel/library/regex.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
Copyright 2022 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 library
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
)
|
||||
|
||||
// Regex provides a CEL function library extension of regex utility functions.
|
||||
//
|
||||
// find / findAll
|
||||
//
|
||||
// Returns substrings that match the provided regular expression. find returns the first match. findAll may optionally
|
||||
// be provided a limit. If the limit is set and >= 0, no more than the limit number of matches are returned.
|
||||
//
|
||||
// <string>.find(<string>) <string>
|
||||
// <string>.findAll(<string>) <list <string>>
|
||||
// <string>.findAll(<string>, <int>) <list <string>>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// "abc 123".find('[0-9]*') // returns '123'
|
||||
// "abc 123".find('xyz') // returns ''
|
||||
// "123 abc 456".findAll('[0-9]*') // returns ['123', '456']
|
||||
// "123 abc 456".findAll('[0-9]*', 1) // returns ['123']
|
||||
// "123 abc 456".findAll('xyz') // returns []
|
||||
func Regex() cel.EnvOption {
|
||||
return cel.Lib(regexLib)
|
||||
}
|
||||
|
||||
var regexLib = ®ex{}
|
||||
|
||||
type regex struct{}
|
||||
|
||||
var regexLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"find": {
|
||||
cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
|
||||
cel.BinaryBinding(find))},
|
||||
"findAll": {
|
||||
cel.MemberOverload("string_find_all_string", []*cel.Type{cel.StringType, cel.StringType},
|
||||
cel.ListType(cel.StringType),
|
||||
cel.BinaryBinding(func(str, regex ref.Val) ref.Val {
|
||||
return findAll(str, regex, types.Int(-1))
|
||||
})),
|
||||
cel.MemberOverload("string_find_all_string_int",
|
||||
[]*cel.Type{cel.StringType, cel.StringType, cel.IntType},
|
||||
cel.ListType(cel.StringType),
|
||||
cel.FunctionBinding(findAll)),
|
||||
},
|
||||
}
|
||||
|
||||
func (*regex) CompileOptions() []cel.EnvOption {
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range regexLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*regex) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func find(strVal ref.Val, regexVal ref.Val) ref.Val {
|
||||
str, ok := strVal.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(strVal)
|
||||
}
|
||||
regex, ok := regexVal.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(regexVal)
|
||||
}
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return types.NewErr("Illegal regex: %v", err.Error())
|
||||
}
|
||||
result := re.FindString(str)
|
||||
return types.String(result)
|
||||
}
|
||||
|
||||
func findAll(args ...ref.Val) ref.Val {
|
||||
argn := len(args)
|
||||
if argn < 2 || argn > 3 {
|
||||
return types.NoSuchOverloadErr()
|
||||
}
|
||||
str, ok := args[0].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[0])
|
||||
}
|
||||
regex, ok := args[1].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[1])
|
||||
}
|
||||
n := int64(-1)
|
||||
if argn == 3 {
|
||||
n, ok = args[2].Value().(int64)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[2])
|
||||
}
|
||||
}
|
||||
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return types.NewErr("Illegal regex: %v", err.Error())
|
||||
}
|
||||
|
||||
result := re.FindAllString(str, int(n))
|
||||
|
||||
return types.NewStringList(types.DefaultTypeAdapter, result)
|
||||
}
|
||||
|
||||
// FindRegexOptimization optimizes the 'find' function by compiling the regex pattern and
|
||||
// reporting any compilation errors at program creation time, and using the compiled regex pattern for all function
|
||||
// call invocations.
|
||||
var FindRegexOptimization = &interpreter.RegexOptimization{
|
||||
Function: "find",
|
||||
RegexIndex: 1,
|
||||
Factory: func(call interpreter.InterpretableCall, regexPattern string) (interpreter.InterpretableCall, error) {
|
||||
compiledRegex, err := regexp.Compile(regexPattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return interpreter.NewCall(call.ID(), call.Function(), call.OverloadID(), call.Args(), func(args ...ref.Val) ref.Val {
|
||||
if len(args) != 2 {
|
||||
return types.NoSuchOverloadErr()
|
||||
}
|
||||
in, ok := args[0].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[0])
|
||||
}
|
||||
return types.String(compiledRegex.FindString(in))
|
||||
}), nil
|
||||
},
|
||||
}
|
||||
|
||||
// FindAllRegexOptimization optimizes the 'findAll' function by compiling the regex pattern and
|
||||
// reporting any compilation errors at program creation time, and using the compiled regex pattern for all function
|
||||
// call invocations.
|
||||
var FindAllRegexOptimization = &interpreter.RegexOptimization{
|
||||
Function: "findAll",
|
||||
RegexIndex: 1,
|
||||
Factory: func(call interpreter.InterpretableCall, regexPattern string) (interpreter.InterpretableCall, error) {
|
||||
compiledRegex, err := regexp.Compile(regexPattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return interpreter.NewCall(call.ID(), call.Function(), call.OverloadID(), call.Args(), func(args ...ref.Val) ref.Val {
|
||||
argn := len(args)
|
||||
if argn < 2 || argn > 3 {
|
||||
return types.NoSuchOverloadErr()
|
||||
}
|
||||
str, ok := args[0].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[0])
|
||||
}
|
||||
n := int64(-1)
|
||||
if argn == 3 {
|
||||
n, ok = args[2].Value().(int64)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[2])
|
||||
}
|
||||
}
|
||||
|
||||
result := compiledRegex.FindAllString(str, int(n))
|
||||
return types.NewStringList(types.DefaultTypeAdapter, result)
|
||||
}), nil
|
||||
},
|
||||
}
|
236
vendor/k8s.io/apiserver/pkg/cel/library/urls.go
generated
vendored
Normal file
236
vendor/k8s.io/apiserver/pkg/cel/library/urls.go
generated
vendored
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
Copyright 2022 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 library
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
)
|
||||
|
||||
// URLs provides a CEL function library extension of URL parsing functions.
|
||||
//
|
||||
// url
|
||||
//
|
||||
// Converts a string to a URL or results in an error if the string is not a valid URL. The URL must be an absolute URI
|
||||
// or an absolute path.
|
||||
//
|
||||
// url(<string>) <URL>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// url('https://user:pass@example.com:80/path?query=val#fragment') // returns a URL
|
||||
// url('/absolute-path') // returns a URL
|
||||
// url('https://a:b:c/') // error
|
||||
// url('../relative-path') // error
|
||||
//
|
||||
// isURL
|
||||
//
|
||||
// Returns true if a string is a valid URL. The URL must be an absolute URI or an absolute path.
|
||||
//
|
||||
// isURL( <string>) <bool>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// isURL('https://user:pass@example.com:80/path?query=val#fragment') // returns true
|
||||
// isURL('/absolute-path') // returns true
|
||||
// isURL('https://a:b:c/') // returns false
|
||||
// isURL('../relative-path') // returns false
|
||||
//
|
||||
// getScheme / getHost / getHostname / getPort / getEscapedPath / getQuery
|
||||
//
|
||||
// Return the parsed components of a URL.
|
||||
//
|
||||
// - getScheme: If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getHostname: IPv6 addresses are returned with braces, e.g. "[::1]". If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getHost: IPv6 addresses are returned without braces, e.g. "::1". If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getEscapedPath: The string returned by getEscapedPath is URL escaped, e.g. "with space" becomes "with%20space".
|
||||
// If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getPort: If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getQuery: Returns the query parameters in "matrix" form where a repeated query key is interpreted to
|
||||
// mean that there are multiple values for that key. The keys and values are returned unescaped.
|
||||
// If absent in the URL, returns an empty map.
|
||||
//
|
||||
// <URL>.getScheme() <string>
|
||||
// <URL>.getHost() <string>
|
||||
// <URL>.getHostname() <string>
|
||||
// <URL>.getPort() <string>
|
||||
// <URL>.getEscapedPath() <string>
|
||||
// <URL>.getQuery() <map <string>, <list <string>>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// url('/path').getScheme() // returns ''
|
||||
// url('https://example.com/').getScheme() // returns 'https'
|
||||
// url('https://example.com:80/').getHost() // returns 'example.com:80'
|
||||
// url('https://example.com/').getHost() // returns 'example.com'
|
||||
// url('https://[::1]:80/').getHost() // returns '[::1]:80'
|
||||
// url('https://[::1]/').getHost() // returns '[::1]'
|
||||
// url('/path').getHost() // returns ''
|
||||
// url('https://example.com:80/').getHostname() // returns 'example.com'
|
||||
// url('https://127.0.0.1:80/').getHostname() // returns '127.0.0.1'
|
||||
// url('https://[::1]:80/').getHostname() // returns '::1'
|
||||
// url('/path').getHostname() // returns ''
|
||||
// url('https://example.com:80/').getPort() // returns '80'
|
||||
// url('https://example.com/').getPort() // returns ''
|
||||
// url('/path').getPort() // returns ''
|
||||
// url('https://example.com/path').getEscapedPath() // returns '/path'
|
||||
// url('https://example.com/path with spaces/').getEscapedPath() // returns '/path%20with%20spaces/'
|
||||
// url('https://example.com').getEscapedPath() // returns ''
|
||||
// url('https://example.com/path?k1=a&k2=b&k2=c').getQuery() // returns { 'k1': ['a'], 'k2': ['b', 'c']}
|
||||
// url('https://example.com/path?key with spaces=value with spaces').getQuery() // returns { 'key with spaces': ['value with spaces']}
|
||||
// url('https://example.com/path?').getQuery() // returns {}
|
||||
// url('https://example.com/path').getQuery() // returns {}
|
||||
func URLs() cel.EnvOption {
|
||||
return cel.Lib(urlsLib)
|
||||
}
|
||||
|
||||
var urlsLib = &urls{}
|
||||
|
||||
type urls struct{}
|
||||
|
||||
var urlLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"url": {
|
||||
cel.Overload("string_to_url", []*cel.Type{cel.StringType}, apiservercel.URLType,
|
||||
cel.UnaryBinding(stringToUrl))},
|
||||
"getScheme": {
|
||||
cel.MemberOverload("url_get_scheme", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getScheme))},
|
||||
"getHost": {
|
||||
cel.MemberOverload("url_get_host", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getHost))},
|
||||
"getHostname": {
|
||||
cel.MemberOverload("url_get_hostname", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getHostname))},
|
||||
"getPort": {
|
||||
cel.MemberOverload("url_get_port", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getPort))},
|
||||
"getEscapedPath": {
|
||||
cel.MemberOverload("url_get_escaped_path", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getEscapedPath))},
|
||||
"getQuery": {
|
||||
cel.MemberOverload("url_get_query", []*cel.Type{apiservercel.URLType},
|
||||
cel.MapType(cel.StringType, cel.ListType(cel.StringType)),
|
||||
cel.UnaryBinding(getQuery))},
|
||||
"isURL": {
|
||||
cel.Overload("is_url_string", []*cel.Type{cel.StringType}, cel.BoolType,
|
||||
cel.UnaryBinding(isURL))},
|
||||
}
|
||||
|
||||
func (*urls) CompileOptions() []cel.EnvOption {
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range urlLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*urls) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func stringToUrl(arg ref.Val) ref.Val {
|
||||
s, ok := arg.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
// Use ParseRequestURI to check the URL before conversion.
|
||||
// ParseRequestURI requires absolute URLs and is used by the OpenAPIv3 'uri' format.
|
||||
_, err := url.ParseRequestURI(s)
|
||||
if err != nil {
|
||||
return types.NewErr("URL parse error during conversion from string: %v", err)
|
||||
}
|
||||
// We must parse again with Parse since ParseRequestURI incorrectly parses URLs that contain a fragment
|
||||
// part and will incorrectly append the fragment to either the path or the query, depending on which it was adjacent to.
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
// Errors are not expected here since Parse is a more lenient parser than ParseRequestURI.
|
||||
return types.NewErr("URL parse error during conversion from string: %v", err)
|
||||
}
|
||||
return apiservercel.URL{URL: u}
|
||||
}
|
||||
|
||||
func getScheme(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.Scheme)
|
||||
}
|
||||
|
||||
func getHost(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.Host)
|
||||
}
|
||||
|
||||
func getHostname(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.Hostname())
|
||||
}
|
||||
|
||||
func getPort(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.Port())
|
||||
}
|
||||
|
||||
func getEscapedPath(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.EscapedPath())
|
||||
}
|
||||
|
||||
func getQuery(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
result := map[ref.Val]ref.Val{}
|
||||
for k, v := range u.Query() {
|
||||
result[types.String(k)] = types.NewStringList(types.DefaultTypeAdapter, v)
|
||||
}
|
||||
return types.NewRefValMap(types.DefaultTypeAdapter, result)
|
||||
}
|
||||
|
||||
func isURL(arg ref.Val) ref.Val {
|
||||
s, ok := arg.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
_, err := url.ParseRequestURI(s)
|
||||
return types.Bool(err == nil)
|
||||
}
|
48
vendor/k8s.io/apiserver/pkg/cel/limits.go
generated
vendored
Normal file
48
vendor/k8s.io/apiserver/pkg/cel/limits.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
const (
|
||||
// DefaultMaxRequestSizeBytes is the size of the largest request that will be accepted
|
||||
DefaultMaxRequestSizeBytes = int64(3 * 1024 * 1024)
|
||||
|
||||
// MaxDurationSizeJSON
|
||||
// OpenAPI duration strings follow RFC 3339, section 5.6 - see the comment on maxDatetimeSizeJSON
|
||||
MaxDurationSizeJSON = 32
|
||||
// MaxDatetimeSizeJSON
|
||||
// OpenAPI datetime strings follow RFC 3339, section 5.6, and the longest possible
|
||||
// such string is 9999-12-31T23:59:59.999999999Z, which has length 30 - we add 2
|
||||
// to allow for quotation marks
|
||||
MaxDatetimeSizeJSON = 32
|
||||
// MinDurationSizeJSON
|
||||
// Golang allows a string of 0 to be parsed as a duration, so that plus 2 to account for
|
||||
// quotation marks makes 3
|
||||
MinDurationSizeJSON = 3
|
||||
// JSONDateSize is the size of a date serialized as part of a JSON object
|
||||
// RFC 3339 dates require YYYY-MM-DD, and then we add 2 to allow for quotation marks
|
||||
JSONDateSize = 12
|
||||
// MinDatetimeSizeJSON is the minimal length of a datetime formatted as RFC 3339
|
||||
// RFC 3339 datetimes require a full date (YYYY-MM-DD) and full time (HH:MM:SS), and we add 3 for
|
||||
// quotation marks like always in addition to the capital T that separates the date and time
|
||||
MinDatetimeSizeJSON = 21
|
||||
// MinStringSize is the size of literal ""
|
||||
MinStringSize = 2
|
||||
// MinBoolSize is the length of literal true
|
||||
MinBoolSize = 4
|
||||
// MinNumberSize is the length of literal 0
|
||||
MinNumberSize = 1
|
||||
)
|
79
vendor/k8s.io/apiserver/pkg/cel/registry.go
generated
vendored
Normal file
79
vendor/k8s.io/apiserver/pkg/cel/registry.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
)
|
||||
|
||||
// Resolver declares methods to find policy templates and related configuration objects.
|
||||
type Resolver interface {
|
||||
// FindType returns a DeclType instance corresponding to the given fully-qualified name, if
|
||||
// present.
|
||||
FindType(name string) (*DeclType, bool)
|
||||
}
|
||||
|
||||
// NewRegistry create a registry for keeping track of environments and types
|
||||
// from a base cel.Env expression environment.
|
||||
func NewRegistry(stdExprEnv *cel.Env) *Registry {
|
||||
return &Registry{
|
||||
exprEnvs: map[string]*cel.Env{"": stdExprEnv},
|
||||
types: map[string]*DeclType{
|
||||
BoolType.TypeName(): BoolType,
|
||||
BytesType.TypeName(): BytesType,
|
||||
DoubleType.TypeName(): DoubleType,
|
||||
DurationType.TypeName(): DurationType,
|
||||
IntType.TypeName(): IntType,
|
||||
NullType.TypeName(): NullType,
|
||||
StringType.TypeName(): StringType,
|
||||
TimestampType.TypeName(): TimestampType,
|
||||
UintType.TypeName(): UintType,
|
||||
ListType.TypeName(): ListType,
|
||||
MapType.TypeName(): MapType,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Registry defines a repository of environment, schema, template, and type definitions.
|
||||
//
|
||||
// Registry instances are concurrency-safe.
|
||||
type Registry struct {
|
||||
rwMux sync.RWMutex
|
||||
exprEnvs map[string]*cel.Env
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
// FindType implements the Resolver interface method.
|
||||
func (r *Registry) FindType(name string) (*DeclType, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
typ, found := r.types[name]
|
||||
if found {
|
||||
return typ, true
|
||||
}
|
||||
return typ, found
|
||||
}
|
||||
|
||||
// SetType registers a DeclType descriptor by its fully qualified name.
|
||||
func (r *Registry) SetType(name string, declType *DeclType) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
r.types[name] = declType
|
||||
return nil
|
||||
}
|
552
vendor/k8s.io/apiserver/pkg/cel/types.go
generated
vendored
Normal file
552
vendor/k8s.io/apiserver/pkg/cel/types.go
generated
vendored
Normal file
@ -0,0 +1,552 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
noMaxLength = math.MaxInt
|
||||
)
|
||||
|
||||
// NewListType returns a parameterized list type with a specified element type.
|
||||
func NewListType(elem *DeclType, maxItems int64) *DeclType {
|
||||
return &DeclType{
|
||||
name: "list",
|
||||
ElemType: elem,
|
||||
MaxElements: maxItems,
|
||||
celType: cel.ListType(elem.CelType()),
|
||||
defaultValue: NewListValue(),
|
||||
// a list can always be represented as [] in JSON, so hardcode the min size
|
||||
// to 2
|
||||
MinSerializedSize: 2,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMapType returns a parameterized map type with the given key and element types.
|
||||
func NewMapType(key, elem *DeclType, maxProperties int64) *DeclType {
|
||||
return &DeclType{
|
||||
name: "map",
|
||||
KeyType: key,
|
||||
ElemType: elem,
|
||||
MaxElements: maxProperties,
|
||||
celType: cel.MapType(key.CelType(), elem.CelType()),
|
||||
defaultValue: NewMapValue(),
|
||||
// a map can always be represented as {} in JSON, so hardcode the min size
|
||||
// to 2
|
||||
MinSerializedSize: 2,
|
||||
}
|
||||
}
|
||||
|
||||
// NewObjectType creates an object type with a qualified name and a set of field declarations.
|
||||
func NewObjectType(name string, fields map[string]*DeclField) *DeclType {
|
||||
t := &DeclType{
|
||||
name: name,
|
||||
Fields: fields,
|
||||
celType: cel.ObjectType(name),
|
||||
traitMask: traits.FieldTesterType | traits.IndexerType,
|
||||
// an object could potentially be larger than the min size we default to here ({}),
|
||||
// but we rely upon the caller to change MinSerializedSize accordingly if they add
|
||||
// properties to the object
|
||||
MinSerializedSize: 2,
|
||||
}
|
||||
t.defaultValue = NewObjectValue(t)
|
||||
return t
|
||||
}
|
||||
|
||||
func NewSimpleTypeWithMinSize(name string, celType *cel.Type, zeroVal ref.Val, minSize int64) *DeclType {
|
||||
return &DeclType{
|
||||
name: name,
|
||||
celType: celType,
|
||||
defaultValue: zeroVal,
|
||||
MinSerializedSize: minSize,
|
||||
}
|
||||
}
|
||||
|
||||
// DeclType represents the universal type descriptor for OpenAPIv3 types.
|
||||
type DeclType struct {
|
||||
fmt.Stringer
|
||||
|
||||
name string
|
||||
// Fields contains a map of escaped CEL identifier field names to field declarations.
|
||||
Fields map[string]*DeclField
|
||||
KeyType *DeclType
|
||||
ElemType *DeclType
|
||||
TypeParam bool
|
||||
Metadata map[string]string
|
||||
MaxElements int64
|
||||
// MinSerializedSize represents the smallest possible size in bytes that
|
||||
// the DeclType could be serialized to in JSON.
|
||||
MinSerializedSize int64
|
||||
|
||||
celType *cel.Type
|
||||
traitMask int
|
||||
defaultValue ref.Val
|
||||
}
|
||||
|
||||
// MaybeAssignTypeName attempts to set the DeclType name to a fully qualified name, if the type
|
||||
// is of `object` type.
|
||||
//
|
||||
// The DeclType must return true for `IsObject` or this assignment will error.
|
||||
func (t *DeclType) MaybeAssignTypeName(name string) *DeclType {
|
||||
if t.IsObject() {
|
||||
objUpdated := false
|
||||
if t.name != "object" {
|
||||
name = t.name
|
||||
} else {
|
||||
objUpdated = true
|
||||
}
|
||||
fieldMap := make(map[string]*DeclField, len(t.Fields))
|
||||
for fieldName, field := range t.Fields {
|
||||
fieldType := field.Type
|
||||
fieldTypeName := fmt.Sprintf("%s.%s", name, fieldName)
|
||||
updated := fieldType.MaybeAssignTypeName(fieldTypeName)
|
||||
if updated == fieldType {
|
||||
fieldMap[fieldName] = field
|
||||
continue
|
||||
}
|
||||
objUpdated = true
|
||||
fieldMap[fieldName] = &DeclField{
|
||||
Name: fieldName,
|
||||
Type: updated,
|
||||
Required: field.Required,
|
||||
enumValues: field.enumValues,
|
||||
defaultValue: field.defaultValue,
|
||||
}
|
||||
}
|
||||
if !objUpdated {
|
||||
return t
|
||||
}
|
||||
return &DeclType{
|
||||
name: name,
|
||||
Fields: fieldMap,
|
||||
KeyType: t.KeyType,
|
||||
ElemType: t.ElemType,
|
||||
TypeParam: t.TypeParam,
|
||||
Metadata: t.Metadata,
|
||||
celType: cel.ObjectType(name),
|
||||
traitMask: t.traitMask,
|
||||
defaultValue: t.defaultValue,
|
||||
MinSerializedSize: t.MinSerializedSize,
|
||||
}
|
||||
}
|
||||
if t.IsMap() {
|
||||
elemTypeName := fmt.Sprintf("%s.@elem", name)
|
||||
updated := t.ElemType.MaybeAssignTypeName(elemTypeName)
|
||||
if updated == t.ElemType {
|
||||
return t
|
||||
}
|
||||
return NewMapType(t.KeyType, updated, t.MaxElements)
|
||||
}
|
||||
if t.IsList() {
|
||||
elemTypeName := fmt.Sprintf("%s.@idx", name)
|
||||
updated := t.ElemType.MaybeAssignTypeName(elemTypeName)
|
||||
if updated == t.ElemType {
|
||||
return t
|
||||
}
|
||||
return NewListType(updated, t.MaxElements)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ExprType returns the CEL expression type of this declaration.
|
||||
func (t *DeclType) ExprType() (*exprpb.Type, error) {
|
||||
return cel.TypeToExprType(t.celType)
|
||||
}
|
||||
|
||||
// CelType returns the CEL type of this declaration.
|
||||
func (t *DeclType) CelType() *cel.Type {
|
||||
return t.celType
|
||||
}
|
||||
|
||||
// FindField returns the DeclField with the given name if present.
|
||||
func (t *DeclType) FindField(name string) (*DeclField, bool) {
|
||||
f, found := t.Fields[name]
|
||||
return f, found
|
||||
}
|
||||
|
||||
// HasTrait implements the CEL ref.Type interface making this type declaration suitable for use
|
||||
// within the CEL evaluator.
|
||||
func (t *DeclType) HasTrait(trait int) bool {
|
||||
if t.traitMask&trait == trait {
|
||||
return true
|
||||
}
|
||||
if t.defaultValue == nil {
|
||||
return false
|
||||
}
|
||||
_, isDecl := t.defaultValue.Type().(*DeclType)
|
||||
if isDecl {
|
||||
return false
|
||||
}
|
||||
return t.defaultValue.Type().HasTrait(trait)
|
||||
}
|
||||
|
||||
// IsList returns whether the declaration is a `list` type which defines a parameterized element
|
||||
// type, but not a parameterized key type or fields.
|
||||
func (t *DeclType) IsList() bool {
|
||||
return t.KeyType == nil && t.ElemType != nil && t.Fields == nil
|
||||
}
|
||||
|
||||
// IsMap returns whether the declaration is a 'map' type which defines parameterized key and
|
||||
// element types, but not fields.
|
||||
func (t *DeclType) IsMap() bool {
|
||||
return t.KeyType != nil && t.ElemType != nil && t.Fields == nil
|
||||
}
|
||||
|
||||
// IsObject returns whether the declartion is an 'object' type which defined a set of typed fields.
|
||||
func (t *DeclType) IsObject() bool {
|
||||
return t.KeyType == nil && t.ElemType == nil && t.Fields != nil
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface method.
|
||||
func (t *DeclType) String() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// TypeName returns the fully qualified type name for the DeclType.
|
||||
func (t *DeclType) TypeName() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// DefaultValue returns the CEL ref.Val representing the default value for this object type,
|
||||
// if one exists.
|
||||
func (t *DeclType) DefaultValue() ref.Val {
|
||||
return t.defaultValue
|
||||
}
|
||||
|
||||
// FieldTypeMap constructs a map of the field and object types nested within a given type.
|
||||
func FieldTypeMap(path string, t *DeclType) map[string]*DeclType {
|
||||
if t.IsObject() && t.TypeName() != "object" {
|
||||
path = t.TypeName()
|
||||
}
|
||||
types := make(map[string]*DeclType)
|
||||
buildDeclTypes(path, t, types)
|
||||
return types
|
||||
}
|
||||
|
||||
func buildDeclTypes(path string, t *DeclType, types map[string]*DeclType) {
|
||||
// Ensure object types are properly named according to where they appear in the schema.
|
||||
if t.IsObject() {
|
||||
// Hack to ensure that names are uniquely qualified and work well with the type
|
||||
// resolution steps which require fully qualified type names for field resolution
|
||||
// to function properly.
|
||||
types[t.TypeName()] = t
|
||||
for name, field := range t.Fields {
|
||||
fieldPath := fmt.Sprintf("%s.%s", path, name)
|
||||
buildDeclTypes(fieldPath, field.Type, types)
|
||||
}
|
||||
}
|
||||
// Map element properties to type names if needed.
|
||||
if t.IsMap() {
|
||||
mapElemPath := fmt.Sprintf("%s.@elem", path)
|
||||
buildDeclTypes(mapElemPath, t.ElemType, types)
|
||||
types[path] = t
|
||||
}
|
||||
// List element properties.
|
||||
if t.IsList() {
|
||||
listIdxPath := fmt.Sprintf("%s.@idx", path)
|
||||
buildDeclTypes(listIdxPath, t.ElemType, types)
|
||||
types[path] = t
|
||||
}
|
||||
}
|
||||
|
||||
// DeclField describes the name, ordinal, and optionality of a field declaration within a type.
|
||||
type DeclField struct {
|
||||
Name string
|
||||
Type *DeclType
|
||||
Required bool
|
||||
enumValues []interface{}
|
||||
defaultValue interface{}
|
||||
}
|
||||
|
||||
func NewDeclField(name string, declType *DeclType, required bool, enumValues []interface{}, defaultValue interface{}) *DeclField {
|
||||
return &DeclField{
|
||||
Name: name,
|
||||
Type: declType,
|
||||
Required: required,
|
||||
enumValues: enumValues,
|
||||
defaultValue: defaultValue,
|
||||
}
|
||||
}
|
||||
|
||||
// TypeName returns the string type name of the field.
|
||||
func (f *DeclField) TypeName() string {
|
||||
return f.Type.TypeName()
|
||||
}
|
||||
|
||||
// DefaultValue returns the zero value associated with the field.
|
||||
func (f *DeclField) DefaultValue() ref.Val {
|
||||
if f.defaultValue != nil {
|
||||
return types.DefaultTypeAdapter.NativeToValue(f.defaultValue)
|
||||
}
|
||||
return f.Type.DefaultValue()
|
||||
}
|
||||
|
||||
// EnumValues returns the set of values that this field may take.
|
||||
func (f *DeclField) EnumValues() []ref.Val {
|
||||
if f.enumValues == nil || len(f.enumValues) == 0 {
|
||||
return []ref.Val{}
|
||||
}
|
||||
ev := make([]ref.Val, len(f.enumValues))
|
||||
for i, e := range f.enumValues {
|
||||
ev[i] = types.DefaultTypeAdapter.NativeToValue(e)
|
||||
}
|
||||
return ev
|
||||
}
|
||||
|
||||
// NewRuleTypes returns an Open API Schema-based type-system which is CEL compatible.
|
||||
func NewRuleTypes(kind string,
|
||||
declType *DeclType,
|
||||
res Resolver) (*RuleTypes, error) {
|
||||
// Note, if the schema indicates that it's actually based on another proto
|
||||
// then prefer the proto definition. For expressions in the proto, a new field
|
||||
// annotation will be needed to indicate the expected environment and type of
|
||||
// the expression.
|
||||
schemaTypes, err := newSchemaTypeProvider(kind, declType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if schemaTypes == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &RuleTypes{
|
||||
ruleSchemaDeclTypes: schemaTypes,
|
||||
resolver: res,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RuleTypes extends the CEL ref.TypeProvider interface and provides an Open API Schema-based
|
||||
// type-system.
|
||||
type RuleTypes struct {
|
||||
ref.TypeProvider
|
||||
ruleSchemaDeclTypes *schemaTypeProvider
|
||||
typeAdapter ref.TypeAdapter
|
||||
resolver Resolver
|
||||
}
|
||||
|
||||
// EnvOptions returns a set of cel.EnvOption values which includes the declaration set
|
||||
// as well as a custom ref.TypeProvider.
|
||||
//
|
||||
// Note, the standard declaration set includes 'rule' which is defined as the top-level rule-schema
|
||||
// type if one is configured.
|
||||
//
|
||||
// If the RuleTypes value is nil, an empty []cel.EnvOption set is returned.
|
||||
func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
if rt == nil {
|
||||
return []cel.EnvOption{}, nil
|
||||
}
|
||||
var ta ref.TypeAdapter = types.DefaultTypeAdapter
|
||||
tpa, ok := tp.(ref.TypeAdapter)
|
||||
if ok {
|
||||
ta = tpa
|
||||
}
|
||||
rtWithTypes := &RuleTypes{
|
||||
TypeProvider: tp,
|
||||
typeAdapter: ta,
|
||||
ruleSchemaDeclTypes: rt.ruleSchemaDeclTypes,
|
||||
resolver: rt.resolver,
|
||||
}
|
||||
for name, declType := range rt.ruleSchemaDeclTypes.types {
|
||||
tpType, found := tp.FindType(name)
|
||||
expT, err := declType.ExprType()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to get cel type: %s", err)
|
||||
}
|
||||
if found && !proto.Equal(tpType, expT) {
|
||||
return nil, fmt.Errorf(
|
||||
"type %s definition differs between CEL environment and rule", name)
|
||||
}
|
||||
}
|
||||
return []cel.EnvOption{
|
||||
cel.CustomTypeProvider(rtWithTypes),
|
||||
cel.CustomTypeAdapter(rtWithTypes),
|
||||
cel.Variable("rule", rt.ruleSchemaDeclTypes.root.CelType()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FindType attempts to resolve the typeName provided from the rule's rule-schema, or if not
|
||||
// from the embedded ref.TypeProvider.
|
||||
//
|
||||
// FindType overrides the default type-finding behavior of the embedded TypeProvider.
|
||||
//
|
||||
// Note, when the type name is based on the Open API Schema, the name will reflect the object path
|
||||
// where the type definition appears.
|
||||
func (rt *RuleTypes) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
if rt == nil {
|
||||
return nil, false
|
||||
}
|
||||
declType, found := rt.findDeclType(typeName)
|
||||
if found {
|
||||
expT, err := declType.ExprType()
|
||||
if err != nil {
|
||||
return expT, false
|
||||
}
|
||||
return expT, found
|
||||
}
|
||||
return rt.TypeProvider.FindType(typeName)
|
||||
}
|
||||
|
||||
// FindDeclType returns the CPT type description which can be mapped to a CEL type.
|
||||
func (rt *RuleTypes) FindDeclType(typeName string) (*DeclType, bool) {
|
||||
if rt == nil {
|
||||
return nil, false
|
||||
}
|
||||
return rt.findDeclType(typeName)
|
||||
}
|
||||
|
||||
// FindFieldType returns a field type given a type name and field name, if found.
|
||||
//
|
||||
// Note, the type name for an Open API Schema type is likely to be its qualified object path.
|
||||
// If, in the future an object instance rather than a type name were provided, the field
|
||||
// resolution might more accurately reflect the expected type model. However, in this case
|
||||
// concessions were made to align with the existing CEL interfaces.
|
||||
func (rt *RuleTypes) FindFieldType(typeName, fieldName string) (*ref.FieldType, bool) {
|
||||
st, found := rt.findDeclType(typeName)
|
||||
if !found {
|
||||
return rt.TypeProvider.FindFieldType(typeName, fieldName)
|
||||
}
|
||||
|
||||
f, found := st.Fields[fieldName]
|
||||
if found {
|
||||
ft := f.Type
|
||||
expT, err := ft.ExprType()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &ref.FieldType{
|
||||
Type: expT,
|
||||
}, true
|
||||
}
|
||||
// This could be a dynamic map.
|
||||
if st.IsMap() {
|
||||
et := st.ElemType
|
||||
expT, err := et.ExprType()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &ref.FieldType{
|
||||
Type: expT,
|
||||
}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// NativeToValue is an implementation of the ref.TypeAdapater interface which supports conversion
|
||||
// of rule values to CEL ref.Val instances.
|
||||
func (rt *RuleTypes) NativeToValue(val interface{}) ref.Val {
|
||||
return rt.typeAdapter.NativeToValue(val)
|
||||
}
|
||||
|
||||
// TypeNames returns the list of type names declared within the RuleTypes object.
|
||||
func (rt *RuleTypes) TypeNames() []string {
|
||||
typeNames := make([]string, len(rt.ruleSchemaDeclTypes.types))
|
||||
i := 0
|
||||
for name := range rt.ruleSchemaDeclTypes.types {
|
||||
typeNames[i] = name
|
||||
i++
|
||||
}
|
||||
return typeNames
|
||||
}
|
||||
|
||||
func (rt *RuleTypes) findDeclType(typeName string) (*DeclType, bool) {
|
||||
declType, found := rt.ruleSchemaDeclTypes.types[typeName]
|
||||
if found {
|
||||
return declType, true
|
||||
}
|
||||
declType, found = rt.resolver.FindType(typeName)
|
||||
if found {
|
||||
return declType, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func newSchemaTypeProvider(kind string, declType *DeclType) (*schemaTypeProvider, error) {
|
||||
if declType == nil {
|
||||
return nil, nil
|
||||
}
|
||||
root := declType.MaybeAssignTypeName(kind)
|
||||
types := FieldTypeMap(kind, root)
|
||||
return &schemaTypeProvider{
|
||||
root: root,
|
||||
types: types,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type schemaTypeProvider struct {
|
||||
root *DeclType
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
var (
|
||||
// AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the
|
||||
// types supported.
|
||||
AnyType = NewSimpleTypeWithMinSize("any", cel.AnyType, nil, 1)
|
||||
|
||||
// BoolType is equivalent to the CEL 'bool' type.
|
||||
BoolType = NewSimpleTypeWithMinSize("bool", cel.BoolType, types.False, MinBoolSize)
|
||||
|
||||
// BytesType is equivalent to the CEL 'bytes' type.
|
||||
BytesType = NewSimpleTypeWithMinSize("bytes", cel.BytesType, types.Bytes([]byte{}), MinStringSize)
|
||||
|
||||
// DoubleType is equivalent to the CEL 'double' type which is a 64-bit floating point value.
|
||||
DoubleType = NewSimpleTypeWithMinSize("double", cel.DoubleType, types.Double(0), MinNumberSize)
|
||||
|
||||
// DurationType is equivalent to the CEL 'duration' type.
|
||||
DurationType = NewSimpleTypeWithMinSize("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}, MinDurationSizeJSON)
|
||||
|
||||
// DateType is equivalent to the CEL 'date' type.
|
||||
DateType = NewSimpleTypeWithMinSize("date", cel.TimestampType, types.Timestamp{Time: time.Time{}}, JSONDateSize)
|
||||
|
||||
// DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be
|
||||
// determined at runtime rather than compile time.
|
||||
DynType = NewSimpleTypeWithMinSize("dyn", cel.DynType, nil, 1)
|
||||
|
||||
// IntType is equivalent to the CEL 'int' type which is a 64-bit signed int.
|
||||
IntType = NewSimpleTypeWithMinSize("int", cel.IntType, types.IntZero, MinNumberSize)
|
||||
|
||||
// NullType is equivalent to the CEL 'null_type'.
|
||||
NullType = NewSimpleTypeWithMinSize("null_type", cel.NullType, types.NullValue, 4)
|
||||
|
||||
// StringType is equivalent to the CEL 'string' type which is expected to be a UTF-8 string.
|
||||
// StringType values may either be string literals or expression strings.
|
||||
StringType = NewSimpleTypeWithMinSize("string", cel.StringType, types.String(""), MinStringSize)
|
||||
|
||||
// TimestampType corresponds to the well-known protobuf.Timestamp type supported within CEL.
|
||||
// Note that both the OpenAPI date and date-time types map onto TimestampType, so not all types
|
||||
// labeled as Timestamp will necessarily have the same MinSerializedSize.
|
||||
TimestampType = NewSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, JSONDateSize)
|
||||
|
||||
// UintType is equivalent to the CEL 'uint' type.
|
||||
UintType = NewSimpleTypeWithMinSize("uint", cel.UintType, types.Uint(0), 1)
|
||||
|
||||
// ListType is equivalent to the CEL 'list' type.
|
||||
ListType = NewListType(AnyType, noMaxLength)
|
||||
|
||||
// MapType is equivalent to the CEL 'map' type.
|
||||
MapType = NewMapType(AnyType, AnyType, noMaxLength)
|
||||
)
|
80
vendor/k8s.io/apiserver/pkg/cel/url.go
generated
vendored
Normal file
80
vendor/k8s.io/apiserver/pkg/cel/url.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
)
|
||||
|
||||
// URL provides a CEL representation of a URL.
|
||||
type URL struct {
|
||||
*url.URL
|
||||
}
|
||||
|
||||
var (
|
||||
URLObject = decls.NewObjectType("kubernetes.URL")
|
||||
typeValue = types.NewTypeValue("kubernetes.URL")
|
||||
URLType = cel.ObjectType("kubernetes.URL")
|
||||
)
|
||||
|
||||
// ConvertToNative implements ref.Val.ConvertToNative.
|
||||
func (d URL) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
if reflect.TypeOf(d.URL).AssignableTo(typeDesc) {
|
||||
return d.URL, nil
|
||||
}
|
||||
if reflect.TypeOf("").AssignableTo(typeDesc) {
|
||||
return d.URL.String(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("type conversion error from 'URL' to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// ConvertToType implements ref.Val.ConvertToType.
|
||||
func (d URL) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
switch typeVal {
|
||||
case typeValue:
|
||||
return d
|
||||
case types.TypeType:
|
||||
return typeValue
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", typeValue, typeVal)
|
||||
}
|
||||
|
||||
// Equal implements ref.Val.Equal.
|
||||
func (d URL) Equal(other ref.Val) ref.Val {
|
||||
otherDur, ok := other.(URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
return types.Bool(d.URL.String() == otherDur.URL.String())
|
||||
}
|
||||
|
||||
// Type implements ref.Val.Type.
|
||||
func (d URL) Type() ref.Type {
|
||||
return typeValue
|
||||
}
|
||||
|
||||
// Value implements ref.Val.Value.
|
||||
func (d URL) Value() interface{} {
|
||||
return d.URL
|
||||
}
|
769
vendor/k8s.io/apiserver/pkg/cel/value.go
generated
vendored
Normal file
769
vendor/k8s.io/apiserver/pkg/cel/value.go
generated
vendored
Normal file
@ -0,0 +1,769 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
)
|
||||
|
||||
// EncodeStyle is a hint for string encoding of parsed values.
|
||||
type EncodeStyle int
|
||||
|
||||
const (
|
||||
// BlockValueStyle is the default string encoding which preserves whitespace and newlines.
|
||||
BlockValueStyle EncodeStyle = iota
|
||||
|
||||
// FlowValueStyle indicates that the string is an inline representation of complex types.
|
||||
FlowValueStyle
|
||||
|
||||
// FoldedValueStyle is a multiline string with whitespace and newlines trimmed to a single
|
||||
// a whitespace. Repeated newlines are replaced with a single newline rather than a single
|
||||
// whitespace.
|
||||
FoldedValueStyle
|
||||
|
||||
// LiteralStyle is a multiline string that preserves newlines, but trims all other whitespace
|
||||
// to a single character.
|
||||
LiteralStyle
|
||||
)
|
||||
|
||||
// NewEmptyDynValue returns the zero-valued DynValue.
|
||||
func NewEmptyDynValue() *DynValue {
|
||||
// note: 0 is not a valid parse node identifier.
|
||||
dv, _ := NewDynValue(0, nil)
|
||||
return dv
|
||||
}
|
||||
|
||||
// NewDynValue returns a DynValue that corresponds to a parse node id and value.
|
||||
func NewDynValue(id int64, val interface{}) (*DynValue, error) {
|
||||
dv := &DynValue{ID: id}
|
||||
err := dv.SetValue(val)
|
||||
return dv, err
|
||||
}
|
||||
|
||||
// DynValue is a dynamically typed value used to describe unstructured content.
|
||||
// Whether the value has the desired type is determined by where it is used within the Instance or
|
||||
// Template, and whether there are schemas which might enforce a more rigid type definition.
|
||||
type DynValue struct {
|
||||
ID int64
|
||||
EncodeStyle EncodeStyle
|
||||
value interface{}
|
||||
exprValue ref.Val
|
||||
declType *DeclType
|
||||
}
|
||||
|
||||
// DeclType returns the policy model type of the dyn value.
|
||||
func (dv *DynValue) DeclType() *DeclType {
|
||||
return dv.declType
|
||||
}
|
||||
|
||||
// ConvertToNative is an implementation of the CEL ref.Val method used to adapt between CEL types
|
||||
// and Go-native types.
|
||||
//
|
||||
// The default behavior of this method is to first convert to a CEL type which has a well-defined
|
||||
// set of conversion behaviors and proxy to the CEL ConvertToNative method for the type.
|
||||
func (dv *DynValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
ev := dv.ExprValue()
|
||||
if types.IsError(ev) {
|
||||
return nil, ev.(*types.Err)
|
||||
}
|
||||
return ev.ConvertToNative(typeDesc)
|
||||
}
|
||||
|
||||
// Equal returns whether the dyn value is equal to a given CEL value.
|
||||
func (dv *DynValue) Equal(other ref.Val) ref.Val {
|
||||
dvType := dv.Type()
|
||||
otherType := other.Type()
|
||||
// Preserve CEL's homogeneous equality constraint.
|
||||
if dvType.TypeName() != otherType.TypeName() {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
switch v := dv.value.(type) {
|
||||
case ref.Val:
|
||||
return v.Equal(other)
|
||||
case PlainTextValue:
|
||||
return celBool(string(v) == other.Value().(string))
|
||||
case *MultilineStringValue:
|
||||
return celBool(v.Value == other.Value().(string))
|
||||
case time.Duration:
|
||||
otherDuration := other.Value().(time.Duration)
|
||||
return celBool(v == otherDuration)
|
||||
case time.Time:
|
||||
otherTimestamp := other.Value().(time.Time)
|
||||
return celBool(v.Equal(otherTimestamp))
|
||||
default:
|
||||
return celBool(reflect.DeepEqual(v, other.Value()))
|
||||
}
|
||||
}
|
||||
|
||||
// ExprValue converts the DynValue into a CEL value.
|
||||
func (dv *DynValue) ExprValue() ref.Val {
|
||||
return dv.exprValue
|
||||
}
|
||||
|
||||
// Value returns the underlying value held by this reference.
|
||||
func (dv *DynValue) Value() interface{} {
|
||||
return dv.value
|
||||
}
|
||||
|
||||
// SetValue updates the underlying value held by this reference.
|
||||
func (dv *DynValue) SetValue(value interface{}) error {
|
||||
dv.value = value
|
||||
var err error
|
||||
dv.exprValue, dv.declType, err = exprValue(value)
|
||||
return err
|
||||
}
|
||||
|
||||
// Type returns the CEL type for the given value.
|
||||
func (dv *DynValue) Type() ref.Type {
|
||||
return dv.ExprValue().Type()
|
||||
}
|
||||
|
||||
func exprValue(value interface{}) (ref.Val, *DeclType, error) {
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
return types.Bool(v), BoolType, nil
|
||||
case []byte:
|
||||
return types.Bytes(v), BytesType, nil
|
||||
case float64:
|
||||
return types.Double(v), DoubleType, nil
|
||||
case int64:
|
||||
return types.Int(v), IntType, nil
|
||||
case string:
|
||||
return types.String(v), StringType, nil
|
||||
case uint64:
|
||||
return types.Uint(v), UintType, nil
|
||||
case time.Duration:
|
||||
return types.Duration{Duration: v}, DurationType, nil
|
||||
case time.Time:
|
||||
return types.Timestamp{Time: v}, TimestampType, nil
|
||||
case types.Null:
|
||||
return v, NullType, nil
|
||||
case *ListValue:
|
||||
return v, ListType, nil
|
||||
case *MapValue:
|
||||
return v, MapType, nil
|
||||
case *ObjectValue:
|
||||
return v, v.objectType, nil
|
||||
default:
|
||||
return nil, unknownType, fmt.Errorf("unsupported type: (%T)%v", v, v)
|
||||
}
|
||||
}
|
||||
|
||||
// PlainTextValue is a text string literal which must not be treated as an expression.
|
||||
type PlainTextValue string
|
||||
|
||||
// MultilineStringValue is a multiline string value which has been parsed in a way which omits
|
||||
// whitespace as well as a raw form which preserves whitespace.
|
||||
type MultilineStringValue struct {
|
||||
Value string
|
||||
Raw string
|
||||
}
|
||||
|
||||
func newStructValue() *structValue {
|
||||
return &structValue{
|
||||
Fields: []*Field{},
|
||||
fieldMap: map[string]*Field{},
|
||||
}
|
||||
}
|
||||
|
||||
type structValue struct {
|
||||
Fields []*Field
|
||||
fieldMap map[string]*Field
|
||||
}
|
||||
|
||||
// AddField appends a MapField to the MapValue and indexes the field by name.
|
||||
func (sv *structValue) AddField(field *Field) {
|
||||
sv.Fields = append(sv.Fields, field)
|
||||
sv.fieldMap[field.Name] = field
|
||||
}
|
||||
|
||||
// ConvertToNative converts the MapValue type to a native go types.
|
||||
func (sv *structValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
if typeDesc.Kind() != reflect.Map &&
|
||||
typeDesc.Kind() != reflect.Struct &&
|
||||
typeDesc.Kind() != reflect.Pointer &&
|
||||
typeDesc.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// Unwrap pointers, but track their use.
|
||||
isPtr := false
|
||||
if typeDesc.Kind() == reflect.Pointer {
|
||||
tk := typeDesc
|
||||
typeDesc = typeDesc.Elem()
|
||||
if typeDesc.Kind() == reflect.Pointer {
|
||||
return nil, fmt.Errorf("unsupported type conversion to '%v'", tk)
|
||||
}
|
||||
isPtr = true
|
||||
}
|
||||
|
||||
if typeDesc.Kind() == reflect.Map {
|
||||
keyType := typeDesc.Key()
|
||||
if keyType.Kind() != reflect.String && keyType.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("object fields cannot be converted to type '%v'", keyType)
|
||||
}
|
||||
elemType := typeDesc.Elem()
|
||||
sz := len(sv.fieldMap)
|
||||
ntvMap := reflect.MakeMapWithSize(typeDesc, sz)
|
||||
for name, val := range sv.fieldMap {
|
||||
refVal, err := val.Ref.ConvertToNative(elemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntvMap.SetMapIndex(reflect.ValueOf(name), reflect.ValueOf(refVal))
|
||||
}
|
||||
return ntvMap.Interface(), nil
|
||||
}
|
||||
|
||||
if typeDesc.Kind() == reflect.Struct {
|
||||
ntvObjPtr := reflect.New(typeDesc)
|
||||
ntvObj := ntvObjPtr.Elem()
|
||||
for name, val := range sv.fieldMap {
|
||||
f := ntvObj.FieldByName(name)
|
||||
if !f.IsValid() {
|
||||
return nil, fmt.Errorf("type conversion error, no such field %s in type %v",
|
||||
name, typeDesc)
|
||||
}
|
||||
fv, err := val.Ref.ConvertToNative(f.Type())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Set(reflect.ValueOf(fv))
|
||||
}
|
||||
if isPtr {
|
||||
return ntvObjPtr.Interface(), nil
|
||||
}
|
||||
return ntvObj.Interface(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// GetField returns a MapField by name if one exists.
|
||||
func (sv *structValue) GetField(name string) (*Field, bool) {
|
||||
field, found := sv.fieldMap[name]
|
||||
return field, found
|
||||
}
|
||||
|
||||
// IsSet returns whether the given field, which is defined, has also been set.
|
||||
func (sv *structValue) IsSet(key ref.Val) ref.Val {
|
||||
k, ok := key.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(key)
|
||||
}
|
||||
name := string(k)
|
||||
_, found := sv.fieldMap[name]
|
||||
return celBool(found)
|
||||
}
|
||||
|
||||
// NewObjectValue creates a struct value with a schema type and returns the empty ObjectValue.
|
||||
func NewObjectValue(sType *DeclType) *ObjectValue {
|
||||
return &ObjectValue{
|
||||
structValue: newStructValue(),
|
||||
objectType: sType,
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectValue is a struct with a custom schema type which indicates the fields and types
|
||||
// associated with the structure.
|
||||
type ObjectValue struct {
|
||||
*structValue
|
||||
objectType *DeclType
|
||||
}
|
||||
|
||||
// ConvertToType is an implementation of the CEL ref.Val interface method.
|
||||
func (o *ObjectValue) ConvertToType(t ref.Type) ref.Val {
|
||||
if t == types.TypeType {
|
||||
return types.NewObjectTypeValue(o.objectType.TypeName())
|
||||
}
|
||||
if t.TypeName() == o.objectType.TypeName() {
|
||||
return o
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", o.Type(), t)
|
||||
}
|
||||
|
||||
// Equal returns true if the two object types are equal and their field values are equal.
|
||||
func (o *ObjectValue) Equal(other ref.Val) ref.Val {
|
||||
// Preserve CEL's homogeneous equality semantics.
|
||||
if o.objectType.TypeName() != other.Type().TypeName() {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
o2 := other.(traits.Indexer)
|
||||
for name := range o.objectType.Fields {
|
||||
k := types.String(name)
|
||||
v := o.Get(k)
|
||||
ov := o2.Get(k)
|
||||
vEq := v.Equal(ov)
|
||||
if vEq != types.True {
|
||||
return vEq
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Get returns the value of the specified field.
|
||||
//
|
||||
// If the field is set, its value is returned. If the field is not set, the default value for the
|
||||
// field is returned thus allowing for safe-traversal and preserving proto-like field traversal
|
||||
// semantics for Open API Schema backed types.
|
||||
func (o *ObjectValue) Get(name ref.Val) ref.Val {
|
||||
n, ok := name.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(n)
|
||||
}
|
||||
nameStr := string(n)
|
||||
field, found := o.fieldMap[nameStr]
|
||||
if found {
|
||||
return field.Ref.ExprValue()
|
||||
}
|
||||
fieldDef, found := o.objectType.Fields[nameStr]
|
||||
if !found {
|
||||
return types.NewErr("no such field: %s", nameStr)
|
||||
}
|
||||
defValue := fieldDef.DefaultValue()
|
||||
if defValue != nil {
|
||||
return defValue
|
||||
}
|
||||
return types.NewErr("no default for type: %s", fieldDef.TypeName())
|
||||
}
|
||||
|
||||
// Type returns the CEL type value of the object.
|
||||
func (o *ObjectValue) Type() ref.Type {
|
||||
return o.objectType
|
||||
}
|
||||
|
||||
// Value returns the Go-native representation of the object.
|
||||
func (o *ObjectValue) Value() interface{} {
|
||||
return o
|
||||
}
|
||||
|
||||
// NewMapValue returns an empty MapValue.
|
||||
func NewMapValue() *MapValue {
|
||||
return &MapValue{
|
||||
structValue: newStructValue(),
|
||||
}
|
||||
}
|
||||
|
||||
// MapValue declares an object with a set of named fields whose values are dynamically typed.
|
||||
type MapValue struct {
|
||||
*structValue
|
||||
}
|
||||
|
||||
// ConvertToObject produces an ObjectValue from the MapValue with the associated schema type.
|
||||
//
|
||||
// The conversion is shallow and the memory shared between the Object and Map as all references
|
||||
// to the map are expected to be replaced with the Object reference.
|
||||
func (m *MapValue) ConvertToObject(declType *DeclType) *ObjectValue {
|
||||
return &ObjectValue{
|
||||
structValue: m.structValue,
|
||||
objectType: declType,
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns whether the given key is contained in the MapValue.
|
||||
func (m *MapValue) Contains(key ref.Val) ref.Val {
|
||||
v, found := m.Find(key)
|
||||
if v != nil && types.IsUnknownOrError(v) {
|
||||
return v
|
||||
}
|
||||
return celBool(found)
|
||||
}
|
||||
|
||||
// ConvertToType converts the MapValue to another CEL type, if possible.
|
||||
func (m *MapValue) ConvertToType(t ref.Type) ref.Val {
|
||||
switch t {
|
||||
case types.MapType:
|
||||
return m
|
||||
case types.TypeType:
|
||||
return types.MapType
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", m.Type(), t)
|
||||
}
|
||||
|
||||
// Equal returns true if the maps are of the same size, have the same keys, and the key-values
|
||||
// from each map are equal.
|
||||
func (m *MapValue) Equal(other ref.Val) ref.Val {
|
||||
oMap, isMap := other.(traits.Mapper)
|
||||
if !isMap {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
if m.Size() != oMap.Size() {
|
||||
return types.False
|
||||
}
|
||||
for name, field := range m.fieldMap {
|
||||
k := types.String(name)
|
||||
ov, found := oMap.Find(k)
|
||||
if !found {
|
||||
return types.False
|
||||
}
|
||||
v := field.Ref.ExprValue()
|
||||
vEq := v.Equal(ov)
|
||||
if vEq != types.True {
|
||||
return vEq
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Find returns the value for the key in the map, if found.
|
||||
func (m *MapValue) Find(name ref.Val) (ref.Val, bool) {
|
||||
// Currently only maps with string keys are supported as this is best aligned with JSON,
|
||||
// and also much simpler to support.
|
||||
n, ok := name.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(n), true
|
||||
}
|
||||
nameStr := string(n)
|
||||
field, found := m.fieldMap[nameStr]
|
||||
if found {
|
||||
return field.Ref.ExprValue(), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Get returns the value for the key in the map, or error if not found.
|
||||
func (m *MapValue) Get(key ref.Val) ref.Val {
|
||||
v, found := m.Find(key)
|
||||
if found {
|
||||
return v
|
||||
}
|
||||
return types.ValOrErr(key, "no such key: %v", key)
|
||||
}
|
||||
|
||||
// Iterator produces a traits.Iterator which walks over the map keys.
|
||||
//
|
||||
// The Iterator is frequently used within comprehensions.
|
||||
func (m *MapValue) Iterator() traits.Iterator {
|
||||
keys := make([]ref.Val, len(m.fieldMap))
|
||||
i := 0
|
||||
for k := range m.fieldMap {
|
||||
keys[i] = types.String(k)
|
||||
i++
|
||||
}
|
||||
return &baseMapIterator{
|
||||
baseVal: &baseVal{},
|
||||
keys: keys,
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the number of keys in the map.
|
||||
func (m *MapValue) Size() ref.Val {
|
||||
return types.Int(len(m.Fields))
|
||||
}
|
||||
|
||||
// Type returns the CEL ref.Type for the map.
|
||||
func (m *MapValue) Type() ref.Type {
|
||||
return types.MapType
|
||||
}
|
||||
|
||||
// Value returns the Go-native representation of the MapValue.
|
||||
func (m *MapValue) Value() interface{} {
|
||||
return m
|
||||
}
|
||||
|
||||
type baseMapIterator struct {
|
||||
*baseVal
|
||||
keys []ref.Val
|
||||
idx int
|
||||
}
|
||||
|
||||
// HasNext implements the traits.Iterator interface method.
|
||||
func (it *baseMapIterator) HasNext() ref.Val {
|
||||
if it.idx < len(it.keys) {
|
||||
return types.True
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
// Next implements the traits.Iterator interface method.
|
||||
func (it *baseMapIterator) Next() ref.Val {
|
||||
key := it.keys[it.idx]
|
||||
it.idx++
|
||||
return key
|
||||
}
|
||||
|
||||
// Type implements the CEL ref.Val interface metohd.
|
||||
func (it *baseMapIterator) Type() ref.Type {
|
||||
return types.IteratorType
|
||||
}
|
||||
|
||||
// NewField returns a MapField instance with an empty DynValue that refers to the
|
||||
// specified parse node id and field name.
|
||||
func NewField(id int64, name string) *Field {
|
||||
return &Field{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Ref: NewEmptyDynValue(),
|
||||
}
|
||||
}
|
||||
|
||||
// Field specifies a field name and a reference to a dynamic value.
|
||||
type Field struct {
|
||||
ID int64
|
||||
Name string
|
||||
Ref *DynValue
|
||||
}
|
||||
|
||||
// NewListValue returns an empty ListValue instance.
|
||||
func NewListValue() *ListValue {
|
||||
return &ListValue{
|
||||
Entries: []*DynValue{},
|
||||
}
|
||||
}
|
||||
|
||||
// ListValue contains a list of dynamically typed entries.
|
||||
type ListValue struct {
|
||||
Entries []*DynValue
|
||||
initValueSet sync.Once
|
||||
valueSet map[ref.Val]struct{}
|
||||
}
|
||||
|
||||
// Add concatenates two lists together to produce a new CEL list value.
|
||||
func (lv *ListValue) Add(other ref.Val) ref.Val {
|
||||
oArr, isArr := other.(traits.Lister)
|
||||
if !isArr {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
szRight := len(lv.Entries)
|
||||
szLeft := int(oArr.Size().(types.Int))
|
||||
sz := szRight + szLeft
|
||||
combo := make([]ref.Val, sz)
|
||||
for i := 0; i < szRight; i++ {
|
||||
combo[i] = lv.Entries[i].ExprValue()
|
||||
}
|
||||
for i := 0; i < szLeft; i++ {
|
||||
combo[i+szRight] = oArr.Get(types.Int(i))
|
||||
}
|
||||
return types.DefaultTypeAdapter.NativeToValue(combo)
|
||||
}
|
||||
|
||||
// Append adds another entry into the ListValue.
|
||||
func (lv *ListValue) Append(entry *DynValue) {
|
||||
lv.Entries = append(lv.Entries, entry)
|
||||
// The append resets all previously built indices.
|
||||
lv.initValueSet = sync.Once{}
|
||||
}
|
||||
|
||||
// Contains returns whether the input `val` is equal to an element in the list.
|
||||
//
|
||||
// If any pair-wise comparison between the input value and the list element is an error, the
|
||||
// operation will return an error.
|
||||
func (lv *ListValue) Contains(val ref.Val) ref.Val {
|
||||
if types.IsUnknownOrError(val) {
|
||||
return val
|
||||
}
|
||||
lv.initValueSet.Do(lv.finalizeValueSet)
|
||||
if lv.valueSet != nil {
|
||||
_, found := lv.valueSet[val]
|
||||
if found {
|
||||
return types.True
|
||||
}
|
||||
// Instead of returning false, ensure that CEL's heterogeneous equality constraint
|
||||
// is satisfied by allowing pair-wise equality behavior to determine the outcome.
|
||||
}
|
||||
var err ref.Val
|
||||
sz := len(lv.Entries)
|
||||
for i := 0; i < sz; i++ {
|
||||
elem := lv.Entries[i]
|
||||
cmp := elem.Equal(val)
|
||||
b, ok := cmp.(types.Bool)
|
||||
if !ok && err == nil {
|
||||
err = types.MaybeNoSuchOverloadErr(cmp)
|
||||
}
|
||||
if b == types.True {
|
||||
return types.True
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
// ConvertToNative is an implementation of the CEL ref.Val method used to adapt between CEL types
|
||||
// and Go-native array-like types.
|
||||
func (lv *ListValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
// Non-list conversion.
|
||||
if typeDesc.Kind() != reflect.Slice &&
|
||||
typeDesc.Kind() != reflect.Array &&
|
||||
typeDesc.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("type conversion error from list to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// If the list is already assignable to the desired type return it.
|
||||
if reflect.TypeOf(lv).AssignableTo(typeDesc) {
|
||||
return lv, nil
|
||||
}
|
||||
|
||||
// List conversion.
|
||||
otherElem := typeDesc.Elem()
|
||||
|
||||
// Allow the element ConvertToNative() function to determine whether conversion is possible.
|
||||
sz := len(lv.Entries)
|
||||
nativeList := reflect.MakeSlice(typeDesc, int(sz), int(sz))
|
||||
for i := 0; i < sz; i++ {
|
||||
elem := lv.Entries[i]
|
||||
nativeElemVal, err := elem.ConvertToNative(otherElem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nativeList.Index(int(i)).Set(reflect.ValueOf(nativeElemVal))
|
||||
}
|
||||
return nativeList.Interface(), nil
|
||||
}
|
||||
|
||||
// ConvertToType converts the ListValue to another CEL type.
|
||||
func (lv *ListValue) ConvertToType(t ref.Type) ref.Val {
|
||||
switch t {
|
||||
case types.ListType:
|
||||
return lv
|
||||
case types.TypeType:
|
||||
return types.ListType
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", ListType, t)
|
||||
}
|
||||
|
||||
// Equal returns true if two lists are of the same size, and the values at each index are also
|
||||
// equal.
|
||||
func (lv *ListValue) Equal(other ref.Val) ref.Val {
|
||||
oArr, isArr := other.(traits.Lister)
|
||||
if !isArr {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
sz := types.Int(len(lv.Entries))
|
||||
if sz != oArr.Size() {
|
||||
return types.False
|
||||
}
|
||||
for i := types.Int(0); i < sz; i++ {
|
||||
cmp := lv.Get(i).Equal(oArr.Get(i))
|
||||
if cmp != types.True {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Get returns the value at the given index.
|
||||
//
|
||||
// If the index is negative or greater than the size of the list, an error is returned.
|
||||
func (lv *ListValue) Get(idx ref.Val) ref.Val {
|
||||
iv, isInt := idx.(types.Int)
|
||||
if !isInt {
|
||||
return types.ValOrErr(idx, "unsupported index: %v", idx)
|
||||
}
|
||||
i := int(iv)
|
||||
if i < 0 || i >= len(lv.Entries) {
|
||||
return types.NewErr("index out of bounds: %v", idx)
|
||||
}
|
||||
return lv.Entries[i].ExprValue()
|
||||
}
|
||||
|
||||
// Iterator produces a traits.Iterator suitable for use in CEL comprehension macros.
|
||||
func (lv *ListValue) Iterator() traits.Iterator {
|
||||
return &baseListIterator{
|
||||
getter: lv.Get,
|
||||
sz: len(lv.Entries),
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the number of elements in the list.
|
||||
func (lv *ListValue) Size() ref.Val {
|
||||
return types.Int(len(lv.Entries))
|
||||
}
|
||||
|
||||
// Type returns the CEL ref.Type for the list.
|
||||
func (lv *ListValue) Type() ref.Type {
|
||||
return types.ListType
|
||||
}
|
||||
|
||||
// Value returns the Go-native value.
|
||||
func (lv *ListValue) Value() interface{} {
|
||||
return lv
|
||||
}
|
||||
|
||||
// finalizeValueSet inspects the ListValue entries in order to make internal optimizations once all list
|
||||
// entries are known.
|
||||
func (lv *ListValue) finalizeValueSet() {
|
||||
valueSet := make(map[ref.Val]struct{})
|
||||
for _, e := range lv.Entries {
|
||||
switch e.value.(type) {
|
||||
case bool, float64, int64, string, uint64, types.Null, PlainTextValue:
|
||||
valueSet[e.ExprValue()] = struct{}{}
|
||||
default:
|
||||
lv.valueSet = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
lv.valueSet = valueSet
|
||||
}
|
||||
|
||||
type baseVal struct{}
|
||||
|
||||
func (*baseVal) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
return nil, fmt.Errorf("unsupported native conversion to: %v", typeDesc)
|
||||
}
|
||||
|
||||
func (*baseVal) ConvertToType(t ref.Type) ref.Val {
|
||||
return types.NewErr("unsupported type conversion to: %v", t)
|
||||
}
|
||||
|
||||
func (*baseVal) Equal(other ref.Val) ref.Val {
|
||||
return types.NewErr("unsupported equality test between instances")
|
||||
}
|
||||
|
||||
func (v *baseVal) Value() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
type baseListIterator struct {
|
||||
*baseVal
|
||||
getter func(idx ref.Val) ref.Val
|
||||
sz int
|
||||
idx int
|
||||
}
|
||||
|
||||
func (it *baseListIterator) HasNext() ref.Val {
|
||||
if it.idx < it.sz {
|
||||
return types.True
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
func (it *baseListIterator) Next() ref.Val {
|
||||
v := it.getter(types.Int(it.idx))
|
||||
it.idx++
|
||||
return v
|
||||
}
|
||||
|
||||
func (it *baseListIterator) Type() ref.Type {
|
||||
return types.IteratorType
|
||||
}
|
||||
|
||||
func celBool(pred bool) ref.Val {
|
||||
if pred {
|
||||
return types.True
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
var unknownType = &DeclType{name: "unknown", MinSerializedSize: 1}
|
134
vendor/k8s.io/apiserver/pkg/endpoints/deprecation/deprecation.go
generated
vendored
Normal file
134
vendor/k8s.io/apiserver/pkg/endpoints/deprecation/deprecation.go
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
Copyright 2020 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 deprecation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
type apiLifecycleDeprecated interface {
|
||||
APILifecycleDeprecated() (major, minor int)
|
||||
}
|
||||
|
||||
type apiLifecycleRemoved interface {
|
||||
APILifecycleRemoved() (major, minor int)
|
||||
}
|
||||
|
||||
type apiLifecycleReplacement interface {
|
||||
APILifecycleReplacement() schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// extract all digits at the beginning of the string
|
||||
var leadingDigits = regexp.MustCompile(`^(\d+)`)
|
||||
|
||||
// MajorMinor parses a numeric major/minor version from the provided version info.
|
||||
// The minor version drops all characters after the first non-digit character:
|
||||
//
|
||||
// version.Info{Major:"1", Minor:"2+"} -> 1,2
|
||||
// version.Info{Major:"1", Minor:"2.3-build4"} -> 1,2
|
||||
func MajorMinor(v version.Info) (int, int, error) {
|
||||
major, err := strconv.Atoi(v.Major)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
minor, err := strconv.Atoi(leadingDigits.FindString(v.Minor))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return major, minor, nil
|
||||
}
|
||||
|
||||
// IsDeprecated returns true if obj implements APILifecycleDeprecated() and returns
|
||||
// a major/minor version that is non-zero and is <= the specified current major/minor version.
|
||||
func IsDeprecated(obj runtime.Object, currentMajor, currentMinor int) bool {
|
||||
deprecated, isDeprecated := obj.(apiLifecycleDeprecated)
|
||||
if !isDeprecated {
|
||||
return false
|
||||
}
|
||||
|
||||
deprecatedMajor, deprecatedMinor := deprecated.APILifecycleDeprecated()
|
||||
// no deprecation version expressed
|
||||
if deprecatedMajor == 0 && deprecatedMinor == 0 {
|
||||
return false
|
||||
}
|
||||
// no current version info available
|
||||
if currentMajor == 0 && currentMinor == 0 {
|
||||
return true
|
||||
}
|
||||
// compare deprecation version to current version
|
||||
if deprecatedMajor > currentMajor {
|
||||
return false
|
||||
}
|
||||
if deprecatedMajor == currentMajor && deprecatedMinor > currentMinor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// RemovedRelease returns the major/minor version in which the given object is unavailable (in the form "<major>.<minor>")
|
||||
// if the object implements APILifecycleRemoved() to indicate a non-zero removal version, and returns an empty string otherwise.
|
||||
func RemovedRelease(obj runtime.Object) string {
|
||||
if removed, hasRemovalInfo := obj.(apiLifecycleRemoved); hasRemovalInfo {
|
||||
removedMajor, removedMinor := removed.APILifecycleRemoved()
|
||||
if removedMajor != 0 || removedMinor != 0 {
|
||||
return fmt.Sprintf("%d.%d", removedMajor, removedMinor)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WarningMessage returns a human-readable deprecation warning if the object implements APILifecycleDeprecated()
|
||||
// to indicate a non-zero deprecated major/minor version and has a populated GetObjectKind().GroupVersionKind().
|
||||
func WarningMessage(obj runtime.Object) string {
|
||||
deprecated, isDeprecated := obj.(apiLifecycleDeprecated)
|
||||
if !isDeprecated {
|
||||
return ""
|
||||
}
|
||||
|
||||
deprecatedMajor, deprecatedMinor := deprecated.APILifecycleDeprecated()
|
||||
if deprecatedMajor == 0 && deprecatedMinor == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
if gvk.Empty() {
|
||||
return ""
|
||||
}
|
||||
deprecationWarning := fmt.Sprintf("%s %s is deprecated in v%d.%d+", gvk.GroupVersion().String(), gvk.Kind, deprecatedMajor, deprecatedMinor)
|
||||
|
||||
if removed, hasRemovalInfo := obj.(apiLifecycleRemoved); hasRemovalInfo {
|
||||
removedMajor, removedMinor := removed.APILifecycleRemoved()
|
||||
if removedMajor != 0 || removedMinor != 0 {
|
||||
deprecationWarning = deprecationWarning + fmt.Sprintf(", unavailable in v%d.%d+", removedMajor, removedMinor)
|
||||
}
|
||||
}
|
||||
|
||||
if replaced, hasReplacement := obj.(apiLifecycleReplacement); hasReplacement {
|
||||
replacement := replaced.APILifecycleReplacement()
|
||||
if !replacement.Empty() {
|
||||
deprecationWarning = deprecationWarning + fmt.Sprintf("; use %s %s", replacement.GroupVersion().String(), replacement.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
return deprecationWarning
|
||||
}
|
72
vendor/k8s.io/apiserver/pkg/endpoints/discovery/addresses.go
generated
vendored
Normal file
72
vendor/k8s.io/apiserver/pkg/endpoints/discovery/addresses.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2016 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 discovery
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type Addresses interface {
|
||||
ServerAddressByClientCIDRs(net.IP) []metav1.ServerAddressByClientCIDR
|
||||
}
|
||||
|
||||
// DefaultAddresses is a default implementation of Addresses that will work in most cases
|
||||
type DefaultAddresses struct {
|
||||
// CIDRRules is a list of CIDRs and Addresses to use if a client is in the range
|
||||
CIDRRules []CIDRRule
|
||||
|
||||
// DefaultAddress is the address (hostname or IP and port) that should be used in
|
||||
// if no CIDR matches more specifically.
|
||||
DefaultAddress string
|
||||
}
|
||||
|
||||
// CIDRRule is a rule for adding an alternate path to the master based on matching CIDR
|
||||
type CIDRRule struct {
|
||||
IPRange net.IPNet
|
||||
|
||||
// Address is the address (hostname or IP and port) that should be used in
|
||||
// if this CIDR matches
|
||||
Address string
|
||||
}
|
||||
|
||||
func (d DefaultAddresses) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
|
||||
addressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
||||
{
|
||||
ClientCIDR: "0.0.0.0/0",
|
||||
ServerAddress: d.DefaultAddress,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rule := range d.CIDRRules {
|
||||
addressCIDRMap = append(addressCIDRMap, rule.ServerAddressByClientCIDRs(clientIP)...)
|
||||
}
|
||||
return addressCIDRMap
|
||||
}
|
||||
|
||||
func (d CIDRRule) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
|
||||
addressCIDRMap := []metav1.ServerAddressByClientCIDR{}
|
||||
|
||||
if d.IPRange.Contains(clientIP) {
|
||||
addressCIDRMap = append(addressCIDRMap, metav1.ServerAddressByClientCIDR{
|
||||
ClientCIDR: d.IPRange.String(),
|
||||
ServerAddress: d.Address,
|
||||
})
|
||||
}
|
||||
return addressCIDRMap
|
||||
}
|
84
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/etag.go
generated
vendored
Normal file
84
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/etag.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2022 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 aggregated
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
)
|
||||
|
||||
// This file exposes helper functions used for calculating the E-Tag header
|
||||
// used in discovery endpoint responses
|
||||
|
||||
// Attaches Cache-Busting functionality to an endpoint
|
||||
// - Sets ETag header to provided hash
|
||||
// - Replies with 304 Not Modified, if If-None-Match header matches hash
|
||||
//
|
||||
// hash should be the value of calculateETag on object. If hash is empty, then
|
||||
//
|
||||
// the object is simply serialized without E-Tag functionality
|
||||
func ServeHTTPWithETag(
|
||||
object runtime.Object,
|
||||
hash string,
|
||||
serializer runtime.NegotiatedSerializer,
|
||||
w http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
// ETag must be enclosed in double quotes:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
|
||||
quotedHash := strconv.Quote(hash)
|
||||
w.Header().Set("ETag", quotedHash)
|
||||
w.Header().Set("Vary", "Accept")
|
||||
w.Header().Set("Cache-Control", "public")
|
||||
|
||||
// If Request includes If-None-Match and matches hash, reply with 304
|
||||
// Otherwise, we delegate to the handler for actual content
|
||||
//
|
||||
// According to documentation, An Etag within an If-None-Match
|
||||
// header will be enclosed within doule quotes:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
|
||||
if clientCachedHash := req.Header.Get("If-None-Match"); quotedHash == clientCachedHash {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
|
||||
responsewriters.WriteObjectNegotiated(
|
||||
serializer,
|
||||
DiscoveryEndpointRestrictions,
|
||||
AggregatedDiscoveryGV,
|
||||
w,
|
||||
req,
|
||||
http.StatusOK,
|
||||
object,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func calculateETag(resources interface{}) (string, error) {
|
||||
serialized, err := json.Marshal(resources)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%X", sha512.Sum512(serialized)), nil
|
||||
}
|
171
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/fake.go
generated
vendored
Normal file
171
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/fake.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
Copyright 2022 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 aggregated
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
type FakeResourceManager interface {
|
||||
ResourceManager
|
||||
Expect() ResourceManager
|
||||
|
||||
HasExpectedNumberActions() bool
|
||||
Validate() error
|
||||
WaitForActions(ctx context.Context, timeout time.Duration) error
|
||||
}
|
||||
|
||||
func NewFakeResourceManager() FakeResourceManager {
|
||||
return &fakeResourceManager{}
|
||||
}
|
||||
|
||||
// a resource manager with helper functions for checking the actions
|
||||
// match expected. For Use in tests
|
||||
type fakeResourceManager struct {
|
||||
recorderResourceManager
|
||||
expect recorderResourceManager
|
||||
}
|
||||
|
||||
// a resource manager which instead of managing a discovery document,
|
||||
// simply records the calls to its interface functoins for testing
|
||||
type recorderResourceManager struct {
|
||||
lock sync.RWMutex
|
||||
Actions []recorderResourceManagerAction
|
||||
}
|
||||
|
||||
var _ ResourceManager = &fakeResourceManager{}
|
||||
var _ ResourceManager = &recorderResourceManager{}
|
||||
|
||||
// Storage type for a call to the resource manager
|
||||
type recorderResourceManagerAction struct {
|
||||
Type string
|
||||
Group string
|
||||
Version string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (f *fakeResourceManager) Expect() ResourceManager {
|
||||
return &f.expect
|
||||
}
|
||||
|
||||
func (f *fakeResourceManager) HasExpectedNumberActions() bool {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
f.expect.lock.RLock()
|
||||
defer f.expect.lock.RUnlock()
|
||||
|
||||
return len(f.Actions) >= len(f.expect.Actions)
|
||||
}
|
||||
|
||||
func (f *fakeResourceManager) Validate() error {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
f.expect.lock.RLock()
|
||||
defer f.expect.lock.RUnlock()
|
||||
|
||||
if !reflect.DeepEqual(f.expect.Actions, f.Actions) {
|
||||
return errors.New(cmp.Diff(f.expect.Actions, f.Actions))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeResourceManager) WaitForActions(ctx context.Context, timeout time.Duration) error {
|
||||
err := wait.PollImmediateWithContext(
|
||||
ctx,
|
||||
100*time.Millisecond, // try every 100ms
|
||||
timeout, // timeout after timeout
|
||||
func(ctx context.Context) (done bool, err error) {
|
||||
if f.HasExpectedNumberActions() {
|
||||
return true, f.Validate()
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *recorderResourceManager) SetGroupVersionPriority(gv metav1.GroupVersion, grouppriority, versionpriority int) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.Actions = append(f.Actions, recorderResourceManagerAction{
|
||||
Type: "SetGroupVersionPriority",
|
||||
Group: gv.Group,
|
||||
Version: gv.Version,
|
||||
Value: versionpriority,
|
||||
})
|
||||
}
|
||||
|
||||
func (f *recorderResourceManager) AddGroupVersion(groupName string, value apidiscoveryv2beta1.APIVersionDiscovery) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.Actions = append(f.Actions, recorderResourceManagerAction{
|
||||
Type: "AddGroupVersion",
|
||||
Group: groupName,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
func (f *recorderResourceManager) RemoveGroup(groupName string) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.Actions = append(f.Actions, recorderResourceManagerAction{
|
||||
Type: "RemoveGroup",
|
||||
Group: groupName,
|
||||
})
|
||||
|
||||
}
|
||||
func (f *recorderResourceManager) RemoveGroupVersion(gv metav1.GroupVersion) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.Actions = append(f.Actions, recorderResourceManagerAction{
|
||||
Type: "RemoveGroupVersion",
|
||||
Group: gv.Group,
|
||||
Version: gv.Version,
|
||||
})
|
||||
|
||||
}
|
||||
func (f *recorderResourceManager) SetGroups(values []apidiscoveryv2beta1.APIGroupDiscovery) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.Actions = append(f.Actions, recorderResourceManagerAction{
|
||||
Type: "SetGroups",
|
||||
Value: values,
|
||||
})
|
||||
}
|
||||
func (f *recorderResourceManager) WebService() *restful.WebService {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *recorderResourceManager) ServeHTTP(http.ResponseWriter, *http.Request) {
|
||||
panic("unimplemented")
|
||||
}
|
368
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/handler.go
generated
vendored
Normal file
368
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/handler.go
generated
vendored
Normal file
@ -0,0 +1,368 @@
|
||||
/*
|
||||
Copyright 2022 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 aggregated
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
|
||||
"sync/atomic"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// This handler serves the /apis endpoint for an aggregated list of
|
||||
// api resources indexed by their group version.
|
||||
type ResourceManager interface {
|
||||
// Adds knowledge of the given groupversion to the discovery document
|
||||
// If it was already being tracked, updates the stored APIVersionDiscovery
|
||||
// Thread-safe
|
||||
AddGroupVersion(groupName string, value apidiscoveryv2beta1.APIVersionDiscovery)
|
||||
|
||||
// Sets a priority to be used while sorting a specific group and
|
||||
// group-version. If two versions report different priorities for
|
||||
// the group, the higher one will be used. If the group is not
|
||||
// known, the priority is ignored. The priority for this version
|
||||
// is forgotten once the group-version is forgotten
|
||||
SetGroupVersionPriority(gv metav1.GroupVersion, grouppriority, versionpriority int)
|
||||
|
||||
// Removes all group versions for a given group
|
||||
// Thread-safe
|
||||
RemoveGroup(groupName string)
|
||||
|
||||
// Removes a specific groupversion. If all versions of a group have been
|
||||
// removed, then the entire group is unlisted.
|
||||
// Thread-safe
|
||||
RemoveGroupVersion(gv metav1.GroupVersion)
|
||||
|
||||
// Resets the manager's known list of group-versions and replaces them
|
||||
// with the given groups
|
||||
// Thread-Safe
|
||||
SetGroups([]apidiscoveryv2beta1.APIGroupDiscovery)
|
||||
|
||||
http.Handler
|
||||
}
|
||||
|
||||
type resourceDiscoveryManager struct {
|
||||
serializer runtime.NegotiatedSerializer
|
||||
// cache is an atomic pointer to avoid the use of locks
|
||||
cache atomic.Pointer[cachedGroupList]
|
||||
|
||||
// Writes protected by the lock.
|
||||
// List of all apigroups & resources indexed by the resource manager
|
||||
lock sync.RWMutex
|
||||
apiGroups map[string]*apidiscoveryv2beta1.APIGroupDiscovery
|
||||
versionPriorities map[metav1.GroupVersion]priorityInfo
|
||||
}
|
||||
|
||||
type priorityInfo struct {
|
||||
GroupPriorityMinimum int
|
||||
VersionPriority int
|
||||
}
|
||||
|
||||
func NewResourceManager() ResourceManager {
|
||||
scheme := runtime.NewScheme()
|
||||
codecs := serializer.NewCodecFactory(scheme)
|
||||
utilruntime.Must(apidiscoveryv2beta1.AddToScheme(scheme))
|
||||
return &resourceDiscoveryManager{serializer: codecs, versionPriorities: make(map[metav1.GroupVersion]priorityInfo)}
|
||||
}
|
||||
|
||||
func (rdm *resourceDiscoveryManager) SetGroupVersionPriority(gv metav1.GroupVersion, groupPriorityMinimum, versionPriority int) {
|
||||
rdm.lock.Lock()
|
||||
defer rdm.lock.Unlock()
|
||||
|
||||
rdm.versionPriorities[gv] = priorityInfo{
|
||||
GroupPriorityMinimum: groupPriorityMinimum,
|
||||
VersionPriority: versionPriority,
|
||||
}
|
||||
rdm.cache.Store(nil)
|
||||
}
|
||||
|
||||
func (rdm *resourceDiscoveryManager) SetGroups(groups []apidiscoveryv2beta1.APIGroupDiscovery) {
|
||||
rdm.lock.Lock()
|
||||
defer rdm.lock.Unlock()
|
||||
|
||||
rdm.apiGroups = nil
|
||||
rdm.cache.Store(nil)
|
||||
|
||||
for _, group := range groups {
|
||||
for _, version := range group.Versions {
|
||||
rdm.addGroupVersionLocked(group.Name, version)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter unused out priority entries
|
||||
for gv := range rdm.versionPriorities {
|
||||
entry, exists := rdm.apiGroups[gv.Group]
|
||||
if !exists {
|
||||
delete(rdm.versionPriorities, gv)
|
||||
continue
|
||||
}
|
||||
|
||||
containsVersion := false
|
||||
|
||||
for _, v := range entry.Versions {
|
||||
if v.Version == gv.Version {
|
||||
containsVersion = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !containsVersion {
|
||||
delete(rdm.versionPriorities, gv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rdm *resourceDiscoveryManager) AddGroupVersion(groupName string, value apidiscoveryv2beta1.APIVersionDiscovery) {
|
||||
rdm.lock.Lock()
|
||||
defer rdm.lock.Unlock()
|
||||
|
||||
rdm.addGroupVersionLocked(groupName, value)
|
||||
}
|
||||
|
||||
func (rdm *resourceDiscoveryManager) addGroupVersionLocked(groupName string, value apidiscoveryv2beta1.APIVersionDiscovery) {
|
||||
klog.Infof("Adding GroupVersion %s %s to ResourceManager", groupName, value.Version)
|
||||
|
||||
if rdm.apiGroups == nil {
|
||||
rdm.apiGroups = make(map[string]*apidiscoveryv2beta1.APIGroupDiscovery)
|
||||
}
|
||||
|
||||
if existing, groupExists := rdm.apiGroups[groupName]; groupExists {
|
||||
// If this version already exists, replace it
|
||||
versionExists := false
|
||||
|
||||
// Not very efficient, but in practice there are generally not many versions
|
||||
for i := range existing.Versions {
|
||||
if existing.Versions[i].Version == value.Version {
|
||||
// The new gv is the exact same as what is already in
|
||||
// the map. This is a noop and cache should not be
|
||||
// invalidated.
|
||||
if reflect.DeepEqual(existing.Versions[i], value) {
|
||||
return
|
||||
}
|
||||
existing.Versions[i] = value
|
||||
versionExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !versionExists {
|
||||
existing.Versions = append(existing.Versions, value)
|
||||
}
|
||||
|
||||
} else {
|
||||
group := &apidiscoveryv2beta1.APIGroupDiscovery{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: groupName,
|
||||
},
|
||||
Versions: []apidiscoveryv2beta1.APIVersionDiscovery{value},
|
||||
}
|
||||
rdm.apiGroups[groupName] = group
|
||||
}
|
||||
|
||||
gv := metav1.GroupVersion{Group: groupName, Version: value.Version}
|
||||
if _, ok := rdm.versionPriorities[gv]; !ok {
|
||||
rdm.versionPriorities[gv] = priorityInfo{
|
||||
GroupPriorityMinimum: 1000,
|
||||
VersionPriority: 15,
|
||||
}
|
||||
}
|
||||
|
||||
// Reset response document so it is recreated lazily
|
||||
rdm.cache.Store(nil)
|
||||
}
|
||||
|
||||
func (rdm *resourceDiscoveryManager) RemoveGroupVersion(apiGroup metav1.GroupVersion) {
|
||||
rdm.lock.Lock()
|
||||
defer rdm.lock.Unlock()
|
||||
group, exists := rdm.apiGroups[apiGroup.Group]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
modified := false
|
||||
for i := range group.Versions {
|
||||
if group.Versions[i].Version == apiGroup.Version {
|
||||
group.Versions = append(group.Versions[:i], group.Versions[i+1:]...)
|
||||
modified = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// If no modification was done, cache does not need to be cleared
|
||||
if !modified {
|
||||
return
|
||||
}
|
||||
|
||||
delete(rdm.versionPriorities, apiGroup)
|
||||
if len(group.Versions) == 0 {
|
||||
delete(rdm.apiGroups, group.Name)
|
||||
}
|
||||
|
||||
// Reset response document so it is recreated lazily
|
||||
rdm.cache.Store(nil)
|
||||
}
|
||||
|
||||
func (rdm *resourceDiscoveryManager) RemoveGroup(groupName string) {
|
||||
rdm.lock.Lock()
|
||||
defer rdm.lock.Unlock()
|
||||
|
||||
delete(rdm.apiGroups, groupName)
|
||||
|
||||
for k := range rdm.versionPriorities {
|
||||
if k.Group == groupName {
|
||||
delete(rdm.versionPriorities, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset response document so it is recreated lazily
|
||||
rdm.cache.Store(nil)
|
||||
}
|
||||
|
||||
// Prepares the api group list for serving by converting them from map into
|
||||
// list and sorting them according to insertion order
|
||||
func (rdm *resourceDiscoveryManager) calculateAPIGroupsLocked() []apidiscoveryv2beta1.APIGroupDiscovery {
|
||||
// Re-order the apiGroups by their priority.
|
||||
groups := []apidiscoveryv2beta1.APIGroupDiscovery{}
|
||||
for _, group := range rdm.apiGroups {
|
||||
copied := *group.DeepCopy()
|
||||
|
||||
// Re-order versions based on their priority. Use kube-aware string
|
||||
// comparison as a tie breaker
|
||||
sort.SliceStable(copied.Versions, func(i, j int) bool {
|
||||
iVersion := copied.Versions[i].Version
|
||||
jVersion := copied.Versions[j].Version
|
||||
|
||||
iPriority := rdm.versionPriorities[metav1.GroupVersion{Group: group.Name, Version: iVersion}].VersionPriority
|
||||
jPriority := rdm.versionPriorities[metav1.GroupVersion{Group: group.Name, Version: jVersion}].VersionPriority
|
||||
|
||||
// Sort by version string comparator if priority is equal
|
||||
if iPriority == jPriority {
|
||||
return version.CompareKubeAwareVersionStrings(iVersion, jVersion) > 0
|
||||
}
|
||||
|
||||
// i sorts before j if it has a higher priority
|
||||
return iPriority > jPriority
|
||||
})
|
||||
|
||||
groups = append(groups, *copied.DeepCopy())
|
||||
|
||||
}
|
||||
|
||||
// For each group, determine the highest minimum group priority and use that
|
||||
priorities := map[string]int{}
|
||||
for gv, info := range rdm.versionPriorities {
|
||||
if existing, exists := priorities[gv.Group]; exists {
|
||||
if existing < info.GroupPriorityMinimum {
|
||||
priorities[gv.Group] = info.GroupPriorityMinimum
|
||||
}
|
||||
} else {
|
||||
priorities[gv.Group] = info.GroupPriorityMinimum
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(groups, func(i, j int) bool {
|
||||
iName := groups[i].Name
|
||||
jName := groups[j].Name
|
||||
|
||||
// Default to 0 priority by default
|
||||
iPriority := priorities[iName]
|
||||
jPriority := priorities[jName]
|
||||
|
||||
// Sort discovery based on apiservice priority.
|
||||
// Duplicated from staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helpers.go
|
||||
if iPriority == jPriority {
|
||||
// Equal priority uses name to break ties
|
||||
return iName < jName
|
||||
}
|
||||
|
||||
// i sorts before j if it has a higher priority
|
||||
return iPriority > jPriority
|
||||
})
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
// Fetches from cache if it exists. If cache is empty, create it.
|
||||
func (rdm *resourceDiscoveryManager) fetchFromCache() *cachedGroupList {
|
||||
rdm.lock.RLock()
|
||||
defer rdm.lock.RUnlock()
|
||||
|
||||
cacheLoad := rdm.cache.Load()
|
||||
if cacheLoad != nil {
|
||||
return cacheLoad
|
||||
}
|
||||
response := apidiscoveryv2beta1.APIGroupDiscoveryList{
|
||||
Items: rdm.calculateAPIGroupsLocked(),
|
||||
}
|
||||
etag, err := calculateETag(response)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to calculate etag for discovery document: %s", etag)
|
||||
etag = ""
|
||||
}
|
||||
cached := &cachedGroupList{
|
||||
cachedResponse: response,
|
||||
cachedResponseETag: etag,
|
||||
}
|
||||
rdm.cache.Store(cached)
|
||||
return cached
|
||||
}
|
||||
|
||||
type cachedGroupList struct {
|
||||
cachedResponse apidiscoveryv2beta1.APIGroupDiscoveryList
|
||||
cachedResponseETag string
|
||||
}
|
||||
|
||||
func (rdm *resourceDiscoveryManager) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||
cache := rdm.fetchFromCache()
|
||||
response := cache.cachedResponse
|
||||
etag := cache.cachedResponseETag
|
||||
|
||||
if len(etag) > 0 {
|
||||
// Use proper e-tag headers if one is available
|
||||
ServeHTTPWithETag(
|
||||
&response,
|
||||
etag,
|
||||
rdm.serializer,
|
||||
resp,
|
||||
req,
|
||||
)
|
||||
} else {
|
||||
// Default to normal response in rare case etag is
|
||||
// not cached with the object for some reason.
|
||||
responsewriters.WriteObjectNegotiated(
|
||||
rdm.serializer,
|
||||
DiscoveryEndpointRestrictions,
|
||||
AggregatedDiscoveryGV,
|
||||
resp,
|
||||
req,
|
||||
http.StatusOK,
|
||||
&response,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
45
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/negotiation.go
generated
vendored
Normal file
45
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/negotiation.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2022 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 aggregated
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var AggregatedDiscoveryGV = schema.GroupVersion{Group: "apidiscovery.k8s.io", Version: "v2beta1"}
|
||||
|
||||
// Interface is from "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
|
||||
// DiscoveryEndpointRestrictions allows requests to /apis to provide a Content Negotiation GVK for aggregated discovery.
|
||||
var DiscoveryEndpointRestrictions = discoveryEndpointRestrictions{}
|
||||
|
||||
type discoveryEndpointRestrictions struct{}
|
||||
|
||||
func (discoveryEndpointRestrictions) AllowsMediaTypeTransform(mimeType string, mimeSubType string, gvk *schema.GroupVersionKind) bool {
|
||||
return IsAggregatedDiscoveryGVK(gvk)
|
||||
}
|
||||
|
||||
func (discoveryEndpointRestrictions) AllowsServerVersion(string) bool { return false }
|
||||
func (discoveryEndpointRestrictions) AllowsStreamSchema(s string) bool { return s == "watch" }
|
||||
|
||||
// IsAggregatedDiscoveryGVK checks if a provided GVK is the GVK for serving aggregated discovery.
|
||||
func IsAggregatedDiscoveryGVK(gvk *schema.GroupVersionKind) bool {
|
||||
if gvk != nil {
|
||||
return gvk.Group == "apidiscovery.k8s.io" && gvk.Version == "v2beta1" && gvk.Kind == "APIGroupDiscoveryList"
|
||||
}
|
||||
return false
|
||||
}
|
78
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/wrapper.go
generated
vendored
Normal file
78
vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/wrapper.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2022 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 aggregated
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
)
|
||||
|
||||
type WrappedHandler struct {
|
||||
s runtime.NegotiatedSerializer
|
||||
handler http.Handler
|
||||
aggHandler http.Handler
|
||||
}
|
||||
|
||||
func (wrapped *WrappedHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AggregatedDiscoveryEndpoint) {
|
||||
mediaType, _ := negotiation.NegotiateMediaTypeOptions(req.Header.Get("Accept"), wrapped.s.SupportedMediaTypes(), DiscoveryEndpointRestrictions)
|
||||
// mediaType.Convert looks at the request accept headers and is used to control whether the discovery document will be aggregated.
|
||||
if IsAggregatedDiscoveryGVK(mediaType.Convert) {
|
||||
wrapped.aggHandler.ServeHTTP(resp, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
wrapped.handler.ServeHTTP(resp, req)
|
||||
}
|
||||
|
||||
func (wrapped *WrappedHandler) restfulHandle(req *restful.Request, resp *restful.Response) {
|
||||
wrapped.ServeHTTP(resp.ResponseWriter, req.Request)
|
||||
}
|
||||
|
||||
func (wrapped *WrappedHandler) GenerateWebService(prefix string, returnType interface{}) *restful.WebService {
|
||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(wrapped.s)
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(prefix)
|
||||
ws.Doc("get available API versions")
|
||||
ws.Route(ws.GET("/").To(wrapped.restfulHandle).
|
||||
Doc("get available API versions").
|
||||
Operation("getAPIVersions").
|
||||
Produces(mediaTypes...).
|
||||
Consumes(mediaTypes...).
|
||||
Writes(returnType))
|
||||
return ws
|
||||
}
|
||||
|
||||
// WrapAggregatedDiscoveryToHandler wraps a handler with an option to
|
||||
// emit the aggregated discovery by passing in the aggregated
|
||||
// discovery type in content negotiation headers: eg: (Accept:
|
||||
// application/json;v=v2beta1;g=apidiscovery.k8s.io;as=APIGroupDiscoveryList)
|
||||
func WrapAggregatedDiscoveryToHandler(handler http.Handler, aggHandler http.Handler) *WrappedHandler {
|
||||
scheme := runtime.NewScheme()
|
||||
apidiscoveryv2beta1.AddToScheme(scheme)
|
||||
codecs := serializer.NewCodecFactory(scheme)
|
||||
return &WrappedHandler{codecs, handler, aggHandler}
|
||||
}
|
73
vendor/k8s.io/apiserver/pkg/endpoints/discovery/group.go
generated
vendored
Normal file
73
vendor/k8s.io/apiserver/pkg/endpoints/discovery/group.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2017 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 discovery
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
)
|
||||
|
||||
// APIGroupHandler creates a webservice serving the supported versions, preferred version, and name
|
||||
// of a group. E.g., such a web service will be registered at /apis/extensions.
|
||||
type APIGroupHandler struct {
|
||||
serializer runtime.NegotiatedSerializer
|
||||
group metav1.APIGroup
|
||||
}
|
||||
|
||||
func NewAPIGroupHandler(serializer runtime.NegotiatedSerializer, group metav1.APIGroup) *APIGroupHandler {
|
||||
if keepUnversioned(group.Name) {
|
||||
// Because in release 1.1, /apis/extensions returns response with empty
|
||||
// APIVersion, we use stripVersionNegotiatedSerializer to keep the
|
||||
// response backwards compatible.
|
||||
serializer = stripVersionNegotiatedSerializer{serializer}
|
||||
}
|
||||
|
||||
return &APIGroupHandler{
|
||||
serializer: serializer,
|
||||
group: group,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIGroupHandler) WebService() *restful.WebService {
|
||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(APIGroupPrefix + "/" + s.group.Name)
|
||||
ws.Doc("get information of a group")
|
||||
ws.Route(ws.GET("/").To(s.handle).
|
||||
Doc("get information of a group").
|
||||
Operation("getAPIGroup").
|
||||
Produces(mediaTypes...).
|
||||
Consumes(mediaTypes...).
|
||||
Writes(metav1.APIGroup{}))
|
||||
return ws
|
||||
}
|
||||
|
||||
// handle returns a handler which will return the api.GroupAndVersion of the group.
|
||||
func (s *APIGroupHandler) handle(req *restful.Request, resp *restful.Response) {
|
||||
s.ServeHTTP(resp.ResponseWriter, req.Request)
|
||||
}
|
||||
|
||||
func (s *APIGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK, &s.group, false)
|
||||
}
|
80
vendor/k8s.io/apiserver/pkg/endpoints/discovery/legacy.go
generated
vendored
Normal file
80
vendor/k8s.io/apiserver/pkg/endpoints/discovery/legacy.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2017 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 discovery
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
)
|
||||
|
||||
// legacyRootAPIHandler creates a webservice serving api group discovery.
|
||||
type legacyRootAPIHandler struct {
|
||||
// addresses is used to build cluster IPs for discovery.
|
||||
addresses Addresses
|
||||
apiPrefix string
|
||||
serializer runtime.NegotiatedSerializer
|
||||
}
|
||||
|
||||
func NewLegacyRootAPIHandler(addresses Addresses, serializer runtime.NegotiatedSerializer, apiPrefix string) *legacyRootAPIHandler {
|
||||
// Because in release 1.1, /apis returns response with empty APIVersion, we
|
||||
// use stripVersionNegotiatedSerializer to keep the response backwards
|
||||
// compatible.
|
||||
serializer = stripVersionNegotiatedSerializer{serializer}
|
||||
|
||||
return &legacyRootAPIHandler{
|
||||
addresses: addresses,
|
||||
apiPrefix: apiPrefix,
|
||||
serializer: serializer,
|
||||
}
|
||||
}
|
||||
|
||||
// AddApiWebService adds a service to return the supported api versions at the legacy /api.
|
||||
func (s *legacyRootAPIHandler) WebService() *restful.WebService {
|
||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(s.apiPrefix)
|
||||
ws.Doc("get available API versions")
|
||||
ws.Route(ws.GET("/").To(s.restfulHandle).
|
||||
Doc("get available API versions").
|
||||
Operation("getAPIVersions").
|
||||
Produces(mediaTypes...).
|
||||
Consumes(mediaTypes...).
|
||||
Writes(metav1.APIVersions{}))
|
||||
return ws
|
||||
}
|
||||
|
||||
func (s *legacyRootAPIHandler) restfulHandle(req *restful.Request, resp *restful.Response) {
|
||||
s.ServeHTTP(resp.ResponseWriter, req.Request)
|
||||
}
|
||||
|
||||
func (s *legacyRootAPIHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||
clientIP := utilnet.GetClientIP(req)
|
||||
apiVersions := &metav1.APIVersions{
|
||||
ServerAddressByClientCIDRs: s.addresses.ServerAddressByClientCIDRs(clientIP),
|
||||
Versions: []string{"v1"},
|
||||
}
|
||||
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, resp, req, http.StatusOK, apiVersions, false)
|
||||
}
|
135
vendor/k8s.io/apiserver/pkg/endpoints/discovery/root.go
generated
vendored
Normal file
135
vendor/k8s.io/apiserver/pkg/endpoints/discovery/root.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
Copyright 2017 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 discovery
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
restful "github.com/emicklei/go-restful/v3"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
)
|
||||
|
||||
// GroupManager is an interface that allows dynamic mutation of the existing webservice to handle
|
||||
// API groups being added or removed.
|
||||
type GroupManager interface {
|
||||
AddGroup(apiGroup metav1.APIGroup)
|
||||
RemoveGroup(groupName string)
|
||||
ServeHTTP(resp http.ResponseWriter, req *http.Request)
|
||||
WebService() *restful.WebService
|
||||
}
|
||||
|
||||
// rootAPIsHandler creates a webservice serving api group discovery.
|
||||
// The list of APIGroups may change while the server is running because additional resources
|
||||
// are registered or removed. It is not safe to cache the values.
|
||||
type rootAPIsHandler struct {
|
||||
// addresses is used to build cluster IPs for discovery.
|
||||
addresses Addresses
|
||||
|
||||
serializer runtime.NegotiatedSerializer
|
||||
|
||||
// Map storing information about all groups to be exposed in discovery response.
|
||||
// The map is from name to the group.
|
||||
lock sync.RWMutex
|
||||
apiGroups map[string]metav1.APIGroup
|
||||
// apiGroupNames preserves insertion order
|
||||
apiGroupNames []string
|
||||
}
|
||||
|
||||
func NewRootAPIsHandler(addresses Addresses, serializer runtime.NegotiatedSerializer) *rootAPIsHandler {
|
||||
// Because in release 1.1, /apis returns response with empty APIVersion, we
|
||||
// use stripVersionNegotiatedSerializer to keep the response backwards
|
||||
// compatible.
|
||||
serializer = stripVersionNegotiatedSerializer{serializer}
|
||||
|
||||
return &rootAPIsHandler{
|
||||
addresses: addresses,
|
||||
serializer: serializer,
|
||||
apiGroups: map[string]metav1.APIGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *rootAPIsHandler) AddGroup(apiGroup metav1.APIGroup) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
_, alreadyExists := s.apiGroups[apiGroup.Name]
|
||||
|
||||
s.apiGroups[apiGroup.Name] = apiGroup
|
||||
if !alreadyExists {
|
||||
s.apiGroupNames = append(s.apiGroupNames, apiGroup.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *rootAPIsHandler) RemoveGroup(groupName string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.apiGroups, groupName)
|
||||
for i := range s.apiGroupNames {
|
||||
if s.apiGroupNames[i] == groupName {
|
||||
s.apiGroupNames = append(s.apiGroupNames[:i], s.apiGroupNames[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *rootAPIsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
orderedGroups := []metav1.APIGroup{}
|
||||
for _, groupName := range s.apiGroupNames {
|
||||
orderedGroups = append(orderedGroups, s.apiGroups[groupName])
|
||||
}
|
||||
|
||||
clientIP := utilnet.GetClientIP(req)
|
||||
serverCIDR := s.addresses.ServerAddressByClientCIDRs(clientIP)
|
||||
groups := make([]metav1.APIGroup, len(orderedGroups))
|
||||
for i := range orderedGroups {
|
||||
groups[i] = orderedGroups[i]
|
||||
groups[i].ServerAddressByClientCIDRs = serverCIDR
|
||||
}
|
||||
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, resp, req, http.StatusOK, &metav1.APIGroupList{Groups: groups}, false)
|
||||
}
|
||||
|
||||
func (s *rootAPIsHandler) restfulHandle(req *restful.Request, resp *restful.Response) {
|
||||
s.ServeHTTP(resp.ResponseWriter, req.Request)
|
||||
}
|
||||
|
||||
// WebService returns a webservice serving api group discovery.
|
||||
// Note: during the server runtime apiGroups might change.
|
||||
func (s *rootAPIsHandler) WebService() *restful.WebService {
|
||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(APIGroupPrefix)
|
||||
ws.Doc("get available API versions")
|
||||
ws.Route(ws.GET("/").To(s.restfulHandle).
|
||||
Doc("get available API versions").
|
||||
Operation("getAPIVersions").
|
||||
Produces(mediaTypes...).
|
||||
Consumes(mediaTypes...).
|
||||
Writes(metav1.APIGroupList{}))
|
||||
return ws
|
||||
}
|
39
vendor/k8s.io/apiserver/pkg/endpoints/discovery/storageversionhash.go
generated
vendored
Normal file
39
vendor/k8s.io/apiserver/pkg/endpoints/discovery/storageversionhash.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright 2019 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 discovery
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// StorageVersionHash calculates the storage version hash for a
|
||||
// <group/version/kind> tuple.
|
||||
// WARNING: this function is subject to change. Clients shouldn't depend on
|
||||
// this function.
|
||||
func StorageVersionHash(group, version, kind string) string {
|
||||
gvk := group + "/" + version + "/" + kind
|
||||
if gvk == "" {
|
||||
return ""
|
||||
}
|
||||
bytes := sha256.Sum256([]byte(gvk))
|
||||
// Assuming there are N kinds in the cluster, and the hash is X-byte long,
|
||||
// the chance of colliding hash P(N,X) approximates to 1-e^(-(N^2)/2^(8X+1)).
|
||||
// P(10,000, 8) ~= 2.7*10^(-12), which is low enough.
|
||||
// See https://en.wikipedia.org/wiki/Birthday_problem#Approximations.
|
||||
return base64.StdEncoding.EncodeToString(bytes[:8])
|
||||
}
|
110
vendor/k8s.io/apiserver/pkg/endpoints/discovery/util.go
generated
vendored
Normal file
110
vendor/k8s.io/apiserver/pkg/endpoints/discovery/util.go
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
Copyright 2017 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 discovery
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const APIGroupPrefix = "/apis"
|
||||
|
||||
func keepUnversioned(group string) bool {
|
||||
return group == "" || group == "extensions"
|
||||
}
|
||||
|
||||
// stripVersionEncoder strips APIVersion field from the encoding output. It's
|
||||
// used to keep the responses at the discovery endpoints backward compatible
|
||||
// with release-1.1, when the responses have empty APIVersion.
|
||||
type stripVersionEncoder struct {
|
||||
encoder runtime.Encoder
|
||||
serializer runtime.Serializer
|
||||
identifier runtime.Identifier
|
||||
}
|
||||
|
||||
func newStripVersionEncoder(e runtime.Encoder, s runtime.Serializer) runtime.Encoder {
|
||||
return stripVersionEncoder{
|
||||
encoder: e,
|
||||
serializer: s,
|
||||
identifier: identifier(e),
|
||||
}
|
||||
}
|
||||
|
||||
func identifier(e runtime.Encoder) runtime.Identifier {
|
||||
result := map[string]string{
|
||||
"name": "stripVersion",
|
||||
}
|
||||
if e != nil {
|
||||
result["encoder"] = string(e.Identifier())
|
||||
}
|
||||
identifier, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
klog.Fatalf("Failed marshaling identifier for stripVersionEncoder: %v", err)
|
||||
}
|
||||
return runtime.Identifier(identifier)
|
||||
}
|
||||
|
||||
func (c stripVersionEncoder) Encode(obj runtime.Object, w io.Writer) error {
|
||||
if co, ok := obj.(runtime.CacheableObject); ok {
|
||||
return co.CacheEncode(c.Identifier(), c.doEncode, w)
|
||||
}
|
||||
return c.doEncode(obj, w)
|
||||
}
|
||||
|
||||
func (c stripVersionEncoder) doEncode(obj runtime.Object, w io.Writer) error {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := c.encoder.Encode(obj, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
roundTrippedObj, gvk, err := c.serializer.Decode(buf.Bytes(), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gvk.Group = ""
|
||||
gvk.Version = ""
|
||||
roundTrippedObj.GetObjectKind().SetGroupVersionKind(*gvk)
|
||||
return c.serializer.Encode(roundTrippedObj, w)
|
||||
}
|
||||
|
||||
// Identifier implements runtime.Encoder interface.
|
||||
func (c stripVersionEncoder) Identifier() runtime.Identifier {
|
||||
return c.identifier
|
||||
}
|
||||
|
||||
// stripVersionNegotiatedSerializer will return stripVersionEncoder when
|
||||
// EncoderForVersion is called. See comments for stripVersionEncoder.
|
||||
type stripVersionNegotiatedSerializer struct {
|
||||
runtime.NegotiatedSerializer
|
||||
}
|
||||
|
||||
func (n stripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
||||
serializer, ok := encoder.(runtime.Serializer)
|
||||
if !ok {
|
||||
// The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the
|
||||
// decoder. We do a best effort cast here (since this code path is only for backwards compatibility) to get access to the caller's
|
||||
// decoder.
|
||||
panic(fmt.Sprintf("Unable to extract serializer from %#v", encoder))
|
||||
}
|
||||
versioned := n.NegotiatedSerializer.EncoderForVersion(encoder, gv)
|
||||
return newStripVersionEncoder(versioned, serializer)
|
||||
}
|
83
vendor/k8s.io/apiserver/pkg/endpoints/discovery/version.go
generated
vendored
Normal file
83
vendor/k8s.io/apiserver/pkg/endpoints/discovery/version.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright 2017 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 discovery
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
restful "github.com/emicklei/go-restful/v3"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
)
|
||||
|
||||
type APIResourceLister interface {
|
||||
ListAPIResources() []metav1.APIResource
|
||||
}
|
||||
|
||||
type APIResourceListerFunc func() []metav1.APIResource
|
||||
|
||||
func (f APIResourceListerFunc) ListAPIResources() []metav1.APIResource {
|
||||
return f()
|
||||
}
|
||||
|
||||
// APIVersionHandler creates a webservice serving the supported resources for the version
|
||||
// E.g., such a web service will be registered at /apis/extensions/v1beta1.
|
||||
type APIVersionHandler struct {
|
||||
serializer runtime.NegotiatedSerializer
|
||||
|
||||
groupVersion schema.GroupVersion
|
||||
apiResourceLister APIResourceLister
|
||||
}
|
||||
|
||||
func NewAPIVersionHandler(serializer runtime.NegotiatedSerializer, groupVersion schema.GroupVersion, apiResourceLister APIResourceLister) *APIVersionHandler {
|
||||
if keepUnversioned(groupVersion.Group) {
|
||||
// Because in release 1.1, /apis/extensions returns response with empty
|
||||
// APIVersion, we use stripVersionNegotiatedSerializer to keep the
|
||||
// response backwards compatible.
|
||||
serializer = stripVersionNegotiatedSerializer{serializer}
|
||||
}
|
||||
|
||||
return &APIVersionHandler{
|
||||
serializer: serializer,
|
||||
groupVersion: groupVersion,
|
||||
apiResourceLister: apiResourceLister,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIVersionHandler) AddToWebService(ws *restful.WebService) {
|
||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
|
||||
ws.Route(ws.GET("/").To(s.handle).
|
||||
Doc("get available resources").
|
||||
Operation("getAPIResources").
|
||||
Produces(mediaTypes...).
|
||||
Consumes(mediaTypes...).
|
||||
Writes(metav1.APIResourceList{}))
|
||||
}
|
||||
|
||||
// handle returns a handler which will return the api.VersionAndVersion of the group.
|
||||
func (s *APIVersionHandler) handle(req *restful.Request, resp *restful.Response) {
|
||||
s.ServeHTTP(resp.ResponseWriter, req.Request)
|
||||
}
|
||||
|
||||
func (s *APIVersionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK,
|
||||
&metav1.APIResourceList{GroupVersion: s.groupVersion.String(), APIResources: s.apiResourceLister.ListAPIResources()}, false)
|
||||
}
|
18
vendor/k8s.io/apiserver/pkg/endpoints/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/apiserver/pkg/endpoints/doc.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2014 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 endpoints contains the generic code that provides a RESTful Kubernetes-style API service.
|
||||
package endpoints // import "k8s.io/apiserver/pkg/endpoints"
|
111
vendor/k8s.io/apiserver/pkg/endpoints/filterlatency/filterlatency.go
generated
vendored
Normal file
111
vendor/k8s.io/apiserver/pkg/endpoints/filterlatency/filterlatency.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
Copyright 2020 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 filterlatency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/server/httplog"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
type requestFilterRecordKeyType int
|
||||
|
||||
// requestFilterRecordKey is the context key for a request filter record struct.
|
||||
const requestFilterRecordKey requestFilterRecordKeyType = iota
|
||||
|
||||
const minFilterLatencyToLog = 100 * time.Millisecond
|
||||
|
||||
type requestFilterRecord struct {
|
||||
name string
|
||||
startedTimestamp time.Time
|
||||
}
|
||||
|
||||
// withRequestFilterRecord attaches the given request filter record to the parent context.
|
||||
func withRequestFilterRecord(parent context.Context, fr *requestFilterRecord) context.Context {
|
||||
return apirequest.WithValue(parent, requestFilterRecordKey, fr)
|
||||
}
|
||||
|
||||
// requestFilterRecordFrom returns the request filter record from the given context.
|
||||
func requestFilterRecordFrom(ctx context.Context) *requestFilterRecord {
|
||||
fr, _ := ctx.Value(requestFilterRecordKey).(*requestFilterRecord)
|
||||
return fr
|
||||
}
|
||||
|
||||
// TrackStarted measures the timestamp the given handler has started execution
|
||||
// by attaching a handler to the chain.
|
||||
func TrackStarted(handler http.Handler, tp trace.TracerProvider, name string) http.Handler {
|
||||
return trackStarted(handler, tp, name, clock.RealClock{})
|
||||
}
|
||||
|
||||
// TrackCompleted measures the timestamp the given handler has completed execution and then
|
||||
// it updates the corresponding metric with the filter latency duration.
|
||||
func TrackCompleted(handler http.Handler) http.Handler {
|
||||
return trackCompleted(handler, clock.RealClock{}, func(ctx context.Context, fr *requestFilterRecord, completedAt time.Time) {
|
||||
latency := completedAt.Sub(fr.startedTimestamp)
|
||||
metrics.RecordFilterLatency(ctx, fr.name, latency)
|
||||
if klog.V(3).Enabled() && latency > minFilterLatencyToLog {
|
||||
httplog.AddKeyValue(ctx, fmt.Sprintf("fl_%s", fr.name), latency.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func trackStarted(handler http.Handler, tp trace.TracerProvider, name string, clock clock.PassiveClock) http.Handler {
|
||||
// This is a noop if the tracing is disabled, since tp will be a NoopTracerProvider
|
||||
tracer := tp.Tracer("k8s.op/apiserver/pkg/endpoints/filterlatency")
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if fr := requestFilterRecordFrom(ctx); fr != nil {
|
||||
fr.name = name
|
||||
fr.startedTimestamp = clock.Now()
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
fr := &requestFilterRecord{
|
||||
name: name,
|
||||
startedTimestamp: clock.Now(),
|
||||
}
|
||||
ctx, _ = tracer.Start(ctx, name)
|
||||
r = r.WithContext(withRequestFilterRecord(ctx, fr))
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func trackCompleted(handler http.Handler, clock clock.PassiveClock, action func(context.Context, *requestFilterRecord, time.Time)) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// The previous filter has just completed.
|
||||
completedAt := clock.Now()
|
||||
|
||||
defer handler.ServeHTTP(w, r)
|
||||
|
||||
ctx := r.Context()
|
||||
if fr := requestFilterRecordFrom(ctx); fr != nil {
|
||||
action(ctx, fr, completedAt)
|
||||
}
|
||||
trace.SpanFromContext(ctx).End()
|
||||
})
|
||||
}
|
6
vendor/k8s.io/apiserver/pkg/endpoints/filters/OWNERS
generated
vendored
Normal file
6
vendor/k8s.io/apiserver/pkg/endpoints/filters/OWNERS
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- deads2k
|
||||
- sttts
|
||||
- soltysh
|
268
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go
generated
vendored
Normal file
268
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
/*
|
||||
Copyright 2016 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 filters
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||
)
|
||||
|
||||
// WithAudit decorates a http.Handler with audit logging information for all the
|
||||
// requests coming to the server. Audit level is decided according to requests'
|
||||
// attributes and audit policy. Logs are emitted to the audit sink to
|
||||
// process events. If sink or audit policy is nil, no decoration takes place.
|
||||
func WithAudit(handler http.Handler, sink audit.Sink, policy audit.PolicyRuleEvaluator, longRunningCheck request.LongRunningRequestCheck) http.Handler {
|
||||
if sink == nil || policy == nil {
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ac, err := evaluatePolicyAndCreateAuditEvent(req, policy)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err))
|
||||
responsewriters.InternalError(w, req, errors.New("failed to create audit event"))
|
||||
return
|
||||
}
|
||||
|
||||
if ac == nil || ac.Event == nil {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
ev := ac.Event
|
||||
|
||||
ctx := req.Context()
|
||||
omitStages := ac.RequestAuditConfig.OmitStages
|
||||
|
||||
ev.Stage = auditinternal.StageRequestReceived
|
||||
if processed := processAuditEvent(ctx, sink, ev, omitStages); !processed {
|
||||
audit.ApiserverAuditDroppedCounter.WithContext(ctx).Inc()
|
||||
responsewriters.InternalError(w, req, errors.New("failed to store audit event"))
|
||||
return
|
||||
}
|
||||
|
||||
// intercept the status code
|
||||
var longRunningSink audit.Sink
|
||||
if longRunningCheck != nil {
|
||||
ri, _ := request.RequestInfoFrom(ctx)
|
||||
if longRunningCheck(req, ri) {
|
||||
longRunningSink = sink
|
||||
}
|
||||
}
|
||||
respWriter := decorateResponseWriter(ctx, w, ev, longRunningSink, omitStages)
|
||||
|
||||
// send audit event when we leave this func, either via a panic or cleanly. In the case of long
|
||||
// running requests, this will be the second audit event.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
defer panic(r)
|
||||
ev.Stage = auditinternal.StagePanic
|
||||
ev.ResponseStatus = &metav1.Status{
|
||||
Code: http.StatusInternalServerError,
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: metav1.StatusReasonInternalError,
|
||||
Message: fmt.Sprintf("APIServer panic'd: %v", r),
|
||||
}
|
||||
processAuditEvent(ctx, sink, ev, omitStages)
|
||||
return
|
||||
}
|
||||
|
||||
// if no StageResponseStarted event was sent b/c neither a status code nor a body was sent, fake it here
|
||||
// But Audit-Id http header will only be sent when http.ResponseWriter.WriteHeader is called.
|
||||
fakedSuccessStatus := &metav1.Status{
|
||||
Code: http.StatusOK,
|
||||
Status: metav1.StatusSuccess,
|
||||
Message: "Connection closed early",
|
||||
}
|
||||
if ev.ResponseStatus == nil && longRunningSink != nil {
|
||||
ev.ResponseStatus = fakedSuccessStatus
|
||||
ev.Stage = auditinternal.StageResponseStarted
|
||||
processAuditEvent(ctx, longRunningSink, ev, omitStages)
|
||||
}
|
||||
|
||||
ev.Stage = auditinternal.StageResponseComplete
|
||||
if ev.ResponseStatus == nil {
|
||||
ev.ResponseStatus = fakedSuccessStatus
|
||||
}
|
||||
processAuditEvent(ctx, sink, ev, omitStages)
|
||||
}()
|
||||
handler.ServeHTTP(respWriter, req)
|
||||
})
|
||||
}
|
||||
|
||||
// evaluatePolicyAndCreateAuditEvent is responsible for evaluating the audit
|
||||
// policy configuration applicable to the request and create a new audit
|
||||
// event that will be written to the API audit log.
|
||||
// - error if anything bad happened
|
||||
func evaluatePolicyAndCreateAuditEvent(req *http.Request, policy audit.PolicyRuleEvaluator) (*audit.AuditContext, error) {
|
||||
ctx := req.Context()
|
||||
ac := audit.AuditContextFrom(ctx)
|
||||
if ac == nil {
|
||||
// Auditing not enabled.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
attribs, err := GetAuthorizerAttributes(ctx)
|
||||
if err != nil {
|
||||
return ac, fmt.Errorf("failed to GetAuthorizerAttributes: %v", err)
|
||||
}
|
||||
|
||||
ls := policy.EvaluatePolicyRule(attribs)
|
||||
audit.ObservePolicyLevel(ctx, ls.Level)
|
||||
ac.RequestAuditConfig = ls.RequestAuditConfig
|
||||
if ls.Level == auditinternal.LevelNone {
|
||||
// Don't audit.
|
||||
return ac, nil
|
||||
}
|
||||
|
||||
requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(ctx)
|
||||
if !ok {
|
||||
requestReceivedTimestamp = time.Now()
|
||||
}
|
||||
ev, err := audit.NewEventFromRequest(req, requestReceivedTimestamp, ls.Level, attribs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to complete audit event from request: %v", err)
|
||||
}
|
||||
|
||||
ac.Event = ev
|
||||
|
||||
return ac, nil
|
||||
}
|
||||
|
||||
// writeLatencyToAnnotation writes the latency incurred in different
|
||||
// layers of the apiserver to the annotations of the audit object.
|
||||
// it should be invoked after ev.StageTimestamp has been set appropriately.
|
||||
func writeLatencyToAnnotation(ctx context.Context, ev *auditinternal.Event) {
|
||||
// we will track latency in annotation only when the total latency
|
||||
// of the given request exceeds 500ms, this is in keeping with the
|
||||
// traces in rest/handlers for create, delete, update,
|
||||
// get, list, and deletecollection.
|
||||
const threshold = 500 * time.Millisecond
|
||||
latency := ev.StageTimestamp.Time.Sub(ev.RequestReceivedTimestamp.Time)
|
||||
if latency <= threshold {
|
||||
return
|
||||
}
|
||||
|
||||
// if we are tracking latency incurred inside different layers within
|
||||
// the apiserver, add these as annotation to the audit event object.
|
||||
layerLatencies := request.AuditAnnotationsFromLatencyTrackers(ctx)
|
||||
if len(layerLatencies) == 0 {
|
||||
// latency tracking is not enabled for this request
|
||||
return
|
||||
}
|
||||
|
||||
// record the total latency for this request, for convenience.
|
||||
layerLatencies["apiserver.latency.k8s.io/total"] = latency.String()
|
||||
audit.AddAuditAnnotationsMap(ctx, layerLatencies)
|
||||
}
|
||||
|
||||
func processAuditEvent(ctx context.Context, sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) bool {
|
||||
for _, stage := range omitStages {
|
||||
if ev.Stage == stage {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case ev.Stage == auditinternal.StageRequestReceived:
|
||||
ev.StageTimestamp = metav1.NewMicroTime(ev.RequestReceivedTimestamp.Time)
|
||||
case ev.Stage == auditinternal.StageResponseComplete:
|
||||
ev.StageTimestamp = metav1.NewMicroTime(time.Now())
|
||||
writeLatencyToAnnotation(ctx, ev)
|
||||
default:
|
||||
ev.StageTimestamp = metav1.NewMicroTime(time.Now())
|
||||
}
|
||||
|
||||
audit.ObserveEvent(ctx)
|
||||
return sink.ProcessEvents(ev)
|
||||
}
|
||||
|
||||
func decorateResponseWriter(ctx context.Context, responseWriter http.ResponseWriter, ev *auditinternal.Event, sink audit.Sink, omitStages []auditinternal.Stage) http.ResponseWriter {
|
||||
delegate := &auditResponseWriter{
|
||||
ctx: ctx,
|
||||
ResponseWriter: responseWriter,
|
||||
event: ev,
|
||||
sink: sink,
|
||||
omitStages: omitStages,
|
||||
}
|
||||
|
||||
return responsewriter.WrapForHTTP1Or2(delegate)
|
||||
}
|
||||
|
||||
var _ http.ResponseWriter = &auditResponseWriter{}
|
||||
var _ responsewriter.UserProvidedDecorator = &auditResponseWriter{}
|
||||
|
||||
// auditResponseWriter intercepts WriteHeader, sets it in the event. If the sink is set, it will
|
||||
// create immediately an event (for long running requests).
|
||||
type auditResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
ctx context.Context
|
||||
event *auditinternal.Event
|
||||
once sync.Once
|
||||
sink audit.Sink
|
||||
omitStages []auditinternal.Stage
|
||||
}
|
||||
|
||||
func (a *auditResponseWriter) Unwrap() http.ResponseWriter {
|
||||
return a.ResponseWriter
|
||||
}
|
||||
|
||||
func (a *auditResponseWriter) processCode(code int) {
|
||||
a.once.Do(func() {
|
||||
if a.event.ResponseStatus == nil {
|
||||
a.event.ResponseStatus = &metav1.Status{}
|
||||
}
|
||||
a.event.ResponseStatus.Code = int32(code)
|
||||
a.event.Stage = auditinternal.StageResponseStarted
|
||||
|
||||
if a.sink != nil {
|
||||
processAuditEvent(a.ctx, a.sink, a.event, a.omitStages)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (a *auditResponseWriter) Write(bs []byte) (int, error) {
|
||||
// the Go library calls WriteHeader internally if no code was written yet. But this will go unnoticed for us
|
||||
a.processCode(http.StatusOK)
|
||||
return a.ResponseWriter.Write(bs)
|
||||
}
|
||||
|
||||
func (a *auditResponseWriter) WriteHeader(code int) {
|
||||
a.processCode(code)
|
||||
a.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (a *auditResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
// fake a response status before protocol switch happens
|
||||
a.processCode(http.StatusSwitchingProtocols)
|
||||
|
||||
// the outer ResponseWriter object returned by WrapForHTTP1Or2 implements
|
||||
// http.Hijacker if the inner object (a.ResponseWriter) implements http.Hijacker.
|
||||
return a.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
65
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit_init.go
generated
vendored
Normal file
65
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit_init.go
generated
vendored
Normal 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 filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// WithAuditInit initializes the audit context and attaches the Audit-ID associated with a request.
|
||||
//
|
||||
// a. If the caller does not specify a value for Audit-ID in the request header, we generate a new audit ID
|
||||
// b. We echo the Audit-ID value to the caller via the response Header 'Audit-ID'.
|
||||
func WithAuditInit(handler http.Handler) http.Handler {
|
||||
return withAuditInit(handler, func() string {
|
||||
return uuid.New().String()
|
||||
})
|
||||
}
|
||||
|
||||
func withAuditInit(handler http.Handler, newAuditIDFunc func() string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := audit.WithAuditContext(r.Context())
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
auditID := r.Header.Get(auditinternal.HeaderAuditID)
|
||||
if len(auditID) == 0 {
|
||||
auditID = newAuditIDFunc()
|
||||
}
|
||||
|
||||
// Note: we save the user specified value of the Audit-ID header as is, no truncation is performed.
|
||||
audit.WithAuditID(ctx, types.UID(auditID))
|
||||
|
||||
// We echo the Audit-ID in to the response header.
|
||||
// It's not guaranteed Audit-ID http header is sent for all requests.
|
||||
// For example, when user run "kubectl exec", apiserver uses a proxy handler
|
||||
// to deal with the request, users can only get http headers returned by kubelet node.
|
||||
//
|
||||
// This filter will also be used by other aggregated api server(s). For an aggregated API
|
||||
// we don't want to see the same audit ID appearing more than once.
|
||||
if value := w.Header().Get(auditinternal.HeaderAuditID); len(value) == 0 {
|
||||
w.Header().Set(auditinternal.HeaderAuditID, auditID)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user