rebase: update replaced k8s.io modules to v0.33.0

Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
Niels de Vos
2025-05-07 13:13:33 +02:00
committed by mergify[bot]
parent dd77e72800
commit 107407b44b
1723 changed files with 65035 additions and 175239 deletions

View File

@ -1,226 +0,0 @@
/*
Copyright 2024 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.
*/
// This file was duplicated from the auto-generated file by conversion-gen in
// k8s.io/kubernetes/pkg/apis/apidiscovery Unlike most k8s types discovery is
// served by all apiservers and conversion is needed by all apiservers. The
// concept of internal/hub type does not exist for discovery as we work directly
// with the versioned types.
// The conversion code here facilities conversion strictly between v2beta1 and
// v2 types. It is only necessary in k8s versions where mixed state could be
// possible before the full removal of the v2beta1 types. It is placed in this
// directory such that all apiservers can benefit from the conversion without
// having to implement their own if the client/server they're communicating with
// only supports one version.
// Once the v2beta1 types are removed (intended for Kubernetes v1.33), this file
// will be removed.
package v2
import (
unsafe "unsafe"
v2 "k8s.io/api/apidiscovery/v2"
v2beta1 "k8s.io/api/apidiscovery/v2beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*v2beta1.APIGroupDiscovery)(nil), (*v2.APIGroupDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2beta1APIGroupDiscoveryTov2APIGroupDiscovery(a.(*v2beta1.APIGroupDiscovery), b.(*v2.APIGroupDiscovery), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2.APIGroupDiscovery)(nil), (*v2beta1.APIGroupDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2APIGroupDiscoveryTov2beta1APIGroupDiscovery(a.(*v2.APIGroupDiscovery), b.(*v2beta1.APIGroupDiscovery), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta1.APIGroupDiscoveryList)(nil), (*v2.APIGroupDiscoveryList)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2beta1APIGroupDiscoveryListTov2APIGroupDiscoveryList(a.(*v2beta1.APIGroupDiscoveryList), b.(*v2.APIGroupDiscoveryList), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2.APIGroupDiscoveryList)(nil), (*v2beta1.APIGroupDiscoveryList)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2APIGroupDiscoveryListTov2beta1APIGroupDiscoveryList(a.(*v2.APIGroupDiscoveryList), b.(*v2beta1.APIGroupDiscoveryList), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta1.APIResourceDiscovery)(nil), (*v2.APIResourceDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2beta1APIResourceDiscoveryTov2APIResourceDiscovery(a.(*v2beta1.APIResourceDiscovery), b.(*v2.APIResourceDiscovery), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2.APIResourceDiscovery)(nil), (*v2beta1.APIResourceDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2APIResourceDiscoveryTov2beta1APIResourceDiscovery(a.(*v2.APIResourceDiscovery), b.(*v2beta1.APIResourceDiscovery), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta1.APISubresourceDiscovery)(nil), (*v2.APISubresourceDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2beta1APISubresourceDiscoveryTov2APISubresourceDiscovery(a.(*v2beta1.APISubresourceDiscovery), b.(*v2.APISubresourceDiscovery), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2.APISubresourceDiscovery)(nil), (*v2beta1.APISubresourceDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2APISubresourceDiscoveryTov2beta1APISubresourceDiscovery(a.(*v2.APISubresourceDiscovery), b.(*v2beta1.APISubresourceDiscovery), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta1.APIVersionDiscovery)(nil), (*v2.APIVersionDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2beta1APIVersionDiscoveryTov2APIVersionDiscovery(a.(*v2beta1.APIVersionDiscovery), b.(*v2.APIVersionDiscovery), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2.APIVersionDiscovery)(nil), (*v2beta1.APIVersionDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convertv2APIVersionDiscoveryTov2beta1APIVersionDiscovery(a.(*v2.APIVersionDiscovery), b.(*v2beta1.APIVersionDiscovery), scope)
}); err != nil {
return err
}
return nil
}
func autoConvertv2beta1APIGroupDiscoveryTov2APIGroupDiscovery(in *v2beta1.APIGroupDiscovery, out *v2.APIGroupDiscovery, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
out.Versions = *(*[]v2.APIVersionDiscovery)(unsafe.Pointer(&in.Versions))
return nil
}
// Convertv2beta1APIGroupDiscoveryTov2APIGroupDiscovery is an autogenerated conversion function.
func Convertv2beta1APIGroupDiscoveryTov2APIGroupDiscovery(in *v2beta1.APIGroupDiscovery, out *v2.APIGroupDiscovery, s conversion.Scope) error {
return autoConvertv2beta1APIGroupDiscoveryTov2APIGroupDiscovery(in, out, s)
}
func autoConvertv2APIGroupDiscoveryTov2beta1APIGroupDiscovery(in *v2.APIGroupDiscovery, out *v2beta1.APIGroupDiscovery, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
out.Versions = *(*[]v2beta1.APIVersionDiscovery)(unsafe.Pointer(&in.Versions))
return nil
}
// Convertv2APIGroupDiscoveryTov2beta1APIGroupDiscovery is an autogenerated conversion function.
func Convertv2APIGroupDiscoveryTov2beta1APIGroupDiscovery(in *v2.APIGroupDiscovery, out *v2beta1.APIGroupDiscovery, s conversion.Scope) error {
return autoConvertv2APIGroupDiscoveryTov2beta1APIGroupDiscovery(in, out, s)
}
func autoConvertv2beta1APIGroupDiscoveryListTov2APIGroupDiscoveryList(in *v2beta1.APIGroupDiscoveryList, out *v2.APIGroupDiscoveryList, s conversion.Scope) error {
out.ListMeta = in.ListMeta
out.Items = *(*[]v2.APIGroupDiscovery)(unsafe.Pointer(&in.Items))
return nil
}
// Convertv2beta1APIGroupDiscoveryListTov2APIGroupDiscoveryList is an autogenerated conversion function.
func Convertv2beta1APIGroupDiscoveryListTov2APIGroupDiscoveryList(in *v2beta1.APIGroupDiscoveryList, out *v2.APIGroupDiscoveryList, s conversion.Scope) error {
return autoConvertv2beta1APIGroupDiscoveryListTov2APIGroupDiscoveryList(in, out, s)
}
func autoConvertv2APIGroupDiscoveryListTov2beta1APIGroupDiscoveryList(in *v2.APIGroupDiscoveryList, out *v2beta1.APIGroupDiscoveryList, s conversion.Scope) error {
out.ListMeta = in.ListMeta
out.Items = *(*[]v2beta1.APIGroupDiscovery)(unsafe.Pointer(&in.Items))
return nil
}
// Convertv2APIGroupDiscoveryListTov2beta1APIGroupDiscoveryList is an autogenerated conversion function.
func Convertv2APIGroupDiscoveryListTov2beta1APIGroupDiscoveryList(in *v2.APIGroupDiscoveryList, out *v2beta1.APIGroupDiscoveryList, s conversion.Scope) error {
return autoConvertv2APIGroupDiscoveryListTov2beta1APIGroupDiscoveryList(in, out, s)
}
func autoConvertv2beta1APIResourceDiscoveryTov2APIResourceDiscovery(in *v2beta1.APIResourceDiscovery, out *v2.APIResourceDiscovery, s conversion.Scope) error {
out.Resource = in.Resource
out.ResponseKind = (*v1.GroupVersionKind)(unsafe.Pointer(in.ResponseKind))
out.Scope = v2.ResourceScope(in.Scope)
out.SingularResource = in.SingularResource
out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs))
out.ShortNames = *(*[]string)(unsafe.Pointer(&in.ShortNames))
out.Categories = *(*[]string)(unsafe.Pointer(&in.Categories))
out.Subresources = *(*[]v2.APISubresourceDiscovery)(unsafe.Pointer(&in.Subresources))
return nil
}
// Convertv2beta1APIResourceDiscoveryTov2APIResourceDiscovery is an autogenerated conversion function.
func Convertv2beta1APIResourceDiscoveryTov2APIResourceDiscovery(in *v2beta1.APIResourceDiscovery, out *v2.APIResourceDiscovery, s conversion.Scope) error {
return autoConvertv2beta1APIResourceDiscoveryTov2APIResourceDiscovery(in, out, s)
}
func autoConvertv2APIResourceDiscoveryTov2beta1APIResourceDiscovery(in *v2.APIResourceDiscovery, out *v2beta1.APIResourceDiscovery, s conversion.Scope) error {
out.Resource = in.Resource
out.ResponseKind = (*v1.GroupVersionKind)(unsafe.Pointer(in.ResponseKind))
out.Scope = v2beta1.ResourceScope(in.Scope)
out.SingularResource = in.SingularResource
out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs))
out.ShortNames = *(*[]string)(unsafe.Pointer(&in.ShortNames))
out.Categories = *(*[]string)(unsafe.Pointer(&in.Categories))
out.Subresources = *(*[]v2beta1.APISubresourceDiscovery)(unsafe.Pointer(&in.Subresources))
return nil
}
// Convertv2APIResourceDiscoveryTov2beta1APIResourceDiscovery is an autogenerated conversion function.
func Convertv2APIResourceDiscoveryTov2beta1APIResourceDiscovery(in *v2.APIResourceDiscovery, out *v2beta1.APIResourceDiscovery, s conversion.Scope) error {
return autoConvertv2APIResourceDiscoveryTov2beta1APIResourceDiscovery(in, out, s)
}
func autoConvertv2beta1APISubresourceDiscoveryTov2APISubresourceDiscovery(in *v2beta1.APISubresourceDiscovery, out *v2.APISubresourceDiscovery, s conversion.Scope) error {
out.Subresource = in.Subresource
out.ResponseKind = (*v1.GroupVersionKind)(unsafe.Pointer(in.ResponseKind))
out.AcceptedTypes = *(*[]v1.GroupVersionKind)(unsafe.Pointer(&in.AcceptedTypes))
out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs))
return nil
}
// Convertv2beta1APISubresourceDiscoveryTov2APISubresourceDiscovery is an autogenerated conversion function.
func Convertv2beta1APISubresourceDiscoveryTov2APISubresourceDiscovery(in *v2beta1.APISubresourceDiscovery, out *v2.APISubresourceDiscovery, s conversion.Scope) error {
return autoConvertv2beta1APISubresourceDiscoveryTov2APISubresourceDiscovery(in, out, s)
}
func autoConvertv2APISubresourceDiscoveryTov2beta1APISubresourceDiscovery(in *v2.APISubresourceDiscovery, out *v2beta1.APISubresourceDiscovery, s conversion.Scope) error {
out.Subresource = in.Subresource
out.ResponseKind = (*v1.GroupVersionKind)(unsafe.Pointer(in.ResponseKind))
out.AcceptedTypes = *(*[]v1.GroupVersionKind)(unsafe.Pointer(&in.AcceptedTypes))
out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs))
return nil
}
// Convertv2APISubresourceDiscoveryTov2beta1APISubresourceDiscovery is an autogenerated conversion function.
func Convertv2APISubresourceDiscoveryTov2beta1APISubresourceDiscovery(in *v2.APISubresourceDiscovery, out *v2beta1.APISubresourceDiscovery, s conversion.Scope) error {
return autoConvertv2APISubresourceDiscoveryTov2beta1APISubresourceDiscovery(in, out, s)
}
func autoConvertv2beta1APIVersionDiscoveryTov2APIVersionDiscovery(in *v2beta1.APIVersionDiscovery, out *v2.APIVersionDiscovery, s conversion.Scope) error {
out.Version = in.Version
out.Resources = *(*[]v2.APIResourceDiscovery)(unsafe.Pointer(&in.Resources))
out.Freshness = v2.DiscoveryFreshness(in.Freshness)
return nil
}
// Convertv2beta1APIVersionDiscoveryTov2APIVersionDiscovery is an autogenerated conversion function.
func Convertv2beta1APIVersionDiscoveryTov2APIVersionDiscovery(in *v2beta1.APIVersionDiscovery, out *v2.APIVersionDiscovery, s conversion.Scope) error {
return autoConvertv2beta1APIVersionDiscoveryTov2APIVersionDiscovery(in, out, s)
}
func autoConvertv2APIVersionDiscoveryTov2beta1APIVersionDiscovery(in *v2.APIVersionDiscovery, out *v2beta1.APIVersionDiscovery, s conversion.Scope) error {
out.Version = in.Version
out.Resources = *(*[]v2beta1.APIResourceDiscovery)(unsafe.Pointer(&in.Resources))
out.Freshness = v2beta1.DiscoveryFreshness(in.Freshness)
return nil
}
// Convertv2APIVersionDiscoveryTov2beta1APIVersionDiscovery is an autogenerated conversion function.
func Convertv2APIVersionDiscoveryTov2beta1APIVersionDiscovery(in *v2.APIVersionDiscovery, out *v2beta1.APIVersionDiscovery, s conversion.Scope) error {
return autoConvertv2APIVersionDiscoveryTov2beta1APIVersionDiscovery(in, out, s)
}

View File

@ -1,19 +0,0 @@
/*
Copyright 2024 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.
*/
// +groupName=apidiscovery.k8s.io
package v2

View File

@ -1,39 +0,0 @@
/*
Copyright 2024 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 v2
import (
apidiscoveryv2 "k8s.io/api/apidiscovery/v2"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "apidiscovery.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v2"}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
SchemeBuilder = &apidiscoveryv2.SchemeBuilder
// AddToScheme adds api to a scheme
AddToScheme = SchemeBuilder.AddToScheme
)

View File

@ -1,821 +0,0 @@
/*
Copyright 2023 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 (
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"time"
celgo "github.com/google/cel-go/cel"
"github.com/google/cel-go/common/operators"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
v1 "k8s.io/api/authorization/v1"
"k8s.io/api/authorization/v1beta1"
"k8s.io/apimachinery/pkg/util/sets"
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/apiserver/pkg/apis/apiserver"
authenticationcel "k8s.io/apiserver/pkg/authentication/cel"
authorizationcel "k8s.io/apiserver/pkg/authorization/cel"
"k8s.io/apiserver/pkg/cel"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/util/cert"
)
// ValidateAuthenticationConfiguration validates a given AuthenticationConfiguration.
func ValidateAuthenticationConfiguration(compiler authenticationcel.Compiler, c *api.AuthenticationConfiguration, disallowedIssuers []string) field.ErrorList {
root := field.NewPath("jwt")
var allErrs field.ErrorList
// We allow 0 authenticators in the authentication configuration.
// This allows us to support scenarios where the API server is initially set up without
// any authenticators and then authenticators are added later via dynamic config.
if len(c.JWT) > 64 {
allErrs = append(allErrs, field.TooMany(root, len(c.JWT), 64))
return allErrs
}
seenIssuers := sets.New[string]()
seenDiscoveryURLs := sets.New[string]()
for i, a := range c.JWT {
fldPath := root.Index(i)
_, errs := validateJWTAuthenticator(compiler, a, fldPath, sets.New(disallowedIssuers...), utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthenticationConfiguration))
allErrs = append(allErrs, errs...)
if seenIssuers.Has(a.Issuer.URL) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("issuer").Child("url"), a.Issuer.URL))
}
seenIssuers.Insert(a.Issuer.URL)
if len(a.Issuer.DiscoveryURL) > 0 {
if seenDiscoveryURLs.Has(a.Issuer.DiscoveryURL) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("issuer").Child("discoveryURL"), a.Issuer.DiscoveryURL))
}
seenDiscoveryURLs.Insert(a.Issuer.DiscoveryURL)
}
}
if c.Anonymous != nil {
if !utilfeature.DefaultFeatureGate.Enabled(features.AnonymousAuthConfigurableEndpoints) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("anonymous"), "anonymous is not supported when AnonymousAuthConfigurableEnpoints feature gate is disabled"))
}
if !c.Anonymous.Enabled && len(c.Anonymous.Conditions) > 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath("anonymous", "conditions"), c.Anonymous.Conditions, "enabled should be set to true when conditions are defined"))
}
}
return allErrs
}
// CompileAndValidateJWTAuthenticator validates a given JWTAuthenticator and returns a CELMapper with the compiled
// CEL expressions for claim mappings and validation rules.
// This is exported for use in oidc package.
func CompileAndValidateJWTAuthenticator(compiler authenticationcel.Compiler, authenticator api.JWTAuthenticator, disallowedIssuers []string) (authenticationcel.CELMapper, field.ErrorList) {
return validateJWTAuthenticator(compiler, authenticator, nil, sets.New(disallowedIssuers...), utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthenticationConfiguration))
}
func validateJWTAuthenticator(compiler authenticationcel.Compiler, authenticator api.JWTAuthenticator, fldPath *field.Path, disallowedIssuers sets.Set[string], structuredAuthnFeatureEnabled bool) (authenticationcel.CELMapper, field.ErrorList) {
var allErrs field.ErrorList
state := &validationState{}
allErrs = append(allErrs, validateIssuer(authenticator.Issuer, disallowedIssuers, fldPath.Child("issuer"), structuredAuthnFeatureEnabled)...)
allErrs = append(allErrs, validateClaimValidationRules(compiler, state, authenticator.ClaimValidationRules, fldPath.Child("claimValidationRules"), structuredAuthnFeatureEnabled)...)
allErrs = append(allErrs, validateClaimMappings(compiler, state, authenticator.ClaimMappings, fldPath.Child("claimMappings"), structuredAuthnFeatureEnabled)...)
allErrs = append(allErrs, validateUserValidationRules(compiler, state, authenticator.UserValidationRules, fldPath.Child("userValidationRules"), structuredAuthnFeatureEnabled)...)
return state.mapper, allErrs
}
type validationState struct {
mapper authenticationcel.CELMapper
usesEmailClaim bool
usesEmailVerifiedClaim bool
}
func validateIssuer(issuer api.Issuer, disallowedIssuers sets.Set[string], fldPath *field.Path, structuredAuthnFeatureEnabled bool) field.ErrorList {
var allErrs field.ErrorList
allErrs = append(allErrs, validateIssuerURL(issuer.URL, disallowedIssuers, fldPath.Child("url"))...)
allErrs = append(allErrs, validateIssuerDiscoveryURL(issuer.URL, issuer.DiscoveryURL, fldPath.Child("discoveryURL"), structuredAuthnFeatureEnabled)...)
allErrs = append(allErrs, validateAudiences(issuer.Audiences, issuer.AudienceMatchPolicy, fldPath.Child("audiences"), fldPath.Child("audienceMatchPolicy"), structuredAuthnFeatureEnabled)...)
allErrs = append(allErrs, validateCertificateAuthority(issuer.CertificateAuthority, fldPath.Child("certificateAuthority"))...)
return allErrs
}
func validateIssuerURL(issuerURL string, disallowedIssuers sets.Set[string], fldPath *field.Path) field.ErrorList {
if len(issuerURL) == 0 {
return field.ErrorList{field.Required(fldPath, "URL is required")}
}
return validateURL(issuerURL, disallowedIssuers, fldPath)
}
func validateIssuerDiscoveryURL(issuerURL, issuerDiscoveryURL string, fldPath *field.Path, structuredAuthnFeatureEnabled bool) field.ErrorList {
var allErrs field.ErrorList
if len(issuerDiscoveryURL) == 0 {
return nil
}
if !structuredAuthnFeatureEnabled {
allErrs = append(allErrs, field.Invalid(fldPath, issuerDiscoveryURL, "discoveryURL is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
}
if len(issuerURL) > 0 && strings.TrimRight(issuerURL, "/") == strings.TrimRight(issuerDiscoveryURL, "/") {
allErrs = append(allErrs, field.Invalid(fldPath, issuerDiscoveryURL, "discoveryURL must be different from URL"))
}
// issuerDiscoveryURL is not an issuer URL and does not need to validated against any set of disallowed issuers
allErrs = append(allErrs, validateURL(issuerDiscoveryURL, nil, fldPath)...)
return allErrs
}
func validateURL(issuerURL string, disallowedIssuers sets.Set[string], fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if disallowedIssuers.Has(issuerURL) {
allErrs = append(allErrs, field.Invalid(fldPath, issuerURL, fmt.Sprintf("URL must not overlap with disallowed issuers: %s", sets.List(disallowedIssuers))))
}
u, err := url.Parse(issuerURL)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, issuerURL, err.Error()))
return allErrs
}
if u.Scheme != "https" {
allErrs = append(allErrs, field.Invalid(fldPath, issuerURL, "URL scheme must be https"))
}
if u.User != nil {
allErrs = append(allErrs, field.Invalid(fldPath, issuerURL, "URL must not contain a username or password"))
}
if len(u.RawQuery) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath, issuerURL, "URL must not contain a query"))
}
if len(u.Fragment) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath, issuerURL, "URL must not contain a fragment"))
}
return allErrs
}
func validateAudiences(audiences []string, audienceMatchPolicy api.AudienceMatchPolicyType, fldPath, audienceMatchPolicyFldPath *field.Path, structuredAuthnFeatureEnabled bool) field.ErrorList {
var allErrs field.ErrorList
if len(audiences) == 0 {
allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf(atLeastOneRequiredErrFmt, fldPath)))
return allErrs
}
if len(audiences) > 1 && !structuredAuthnFeatureEnabled {
allErrs = append(allErrs, field.Invalid(fldPath, audiences, "multiple audiences are not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
}
seenAudiences := sets.NewString()
for i, audience := range audiences {
fldPath := fldPath.Index(i)
if len(audience) == 0 {
allErrs = append(allErrs, field.Required(fldPath, "audience can't be empty"))
}
if seenAudiences.Has(audience) {
allErrs = append(allErrs, field.Duplicate(fldPath, audience))
}
seenAudiences.Insert(audience)
}
if len(audiences) > 1 && audienceMatchPolicy != api.AudienceMatchPolicyMatchAny {
allErrs = append(allErrs, field.Invalid(audienceMatchPolicyFldPath, audienceMatchPolicy, "audienceMatchPolicy must be MatchAny for multiple audiences"))
}
if len(audiences) == 1 && (len(audienceMatchPolicy) > 0 && audienceMatchPolicy != api.AudienceMatchPolicyMatchAny) {
allErrs = append(allErrs, field.Invalid(audienceMatchPolicyFldPath, audienceMatchPolicy, "audienceMatchPolicy must be empty or MatchAny for single audience"))
}
return allErrs
}
func validateCertificateAuthority(certificateAuthority string, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if len(certificateAuthority) == 0 {
return allErrs
}
_, err := cert.NewPoolFromBytes([]byte(certificateAuthority))
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, "<omitted>", err.Error()))
}
return allErrs
}
func validateClaimValidationRules(compiler authenticationcel.Compiler, state *validationState, rules []api.ClaimValidationRule, fldPath *field.Path, structuredAuthnFeatureEnabled bool) field.ErrorList {
var allErrs field.ErrorList
seenClaims := sets.NewString()
seenExpressions := sets.NewString()
var compilationResults []authenticationcel.CompilationResult
for i, rule := range rules {
fldPath := fldPath.Index(i)
if len(rule.Expression) > 0 && !structuredAuthnFeatureEnabled {
allErrs = append(allErrs, field.Invalid(fldPath.Child("expression"), rule.Expression, "expression is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
}
switch {
case len(rule.Claim) > 0 && len(rule.Expression) > 0:
allErrs = append(allErrs, field.Invalid(fldPath, rule.Claim, "claim and expression can't both be set"))
case len(rule.Claim) == 0 && len(rule.Expression) == 0:
allErrs = append(allErrs, field.Required(fldPath, "claim or expression is required"))
case len(rule.Claim) > 0:
if len(rule.Message) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("message"), rule.Message, "message can't be set when claim is set"))
}
if seenClaims.Has(rule.Claim) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("claim"), rule.Claim))
}
seenClaims.Insert(rule.Claim)
case len(rule.Expression) > 0:
if len(rule.RequiredValue) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("requiredValue"), rule.RequiredValue, "requiredValue can't be set when expression is set"))
}
if seenExpressions.Has(rule.Expression) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("expression"), rule.Expression))
continue
}
seenExpressions.Insert(rule.Expression)
compilationResult, err := compileClaimsCELExpression(compiler, &authenticationcel.ClaimValidationCondition{
Expression: rule.Expression,
Message: rule.Message,
}, fldPath.Child("expression"))
if err != nil {
allErrs = append(allErrs, err)
continue
}
if compilationResult != nil {
compilationResults = append(compilationResults, *compilationResult)
}
}
}
if structuredAuthnFeatureEnabled && len(compilationResults) > 0 {
state.mapper.ClaimValidationRules = authenticationcel.NewClaimsMapper(compilationResults)
state.usesEmailVerifiedClaim = state.usesEmailVerifiedClaim || anyUsesEmailVerifiedClaim(compilationResults)
}
return allErrs
}
func validateClaimMappings(compiler authenticationcel.Compiler, state *validationState, m api.ClaimMappings, fldPath *field.Path, structuredAuthnFeatureEnabled bool) field.ErrorList {
var allErrs field.ErrorList
if !structuredAuthnFeatureEnabled {
if len(m.Username.Expression) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("username").Child("expression"), m.Username.Expression, "expression is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
}
if len(m.Groups.Expression) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("groups").Child("expression"), m.Groups.Expression, "expression is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
}
if len(m.UID.Claim) > 0 || len(m.UID.Expression) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), "", "uid claim mapping is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
}
if len(m.Extra) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("extra"), "", "extra claim mapping is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
}
}
compilationResult, err := validatePrefixClaimOrExpression(compiler, m.Username, fldPath.Child("username"), true)
if err != nil {
allErrs = append(allErrs, err...)
} else if compilationResult != nil && structuredAuthnFeatureEnabled {
state.usesEmailClaim = state.usesEmailClaim || usesEmailClaim(compilationResult.AST)
state.usesEmailVerifiedClaim = state.usesEmailVerifiedClaim || usesEmailVerifiedClaim(compilationResult.AST)
state.mapper.Username = authenticationcel.NewClaimsMapper([]authenticationcel.CompilationResult{*compilationResult})
}
compilationResult, err = validatePrefixClaimOrExpression(compiler, m.Groups, fldPath.Child("groups"), false)
if err != nil {
allErrs = append(allErrs, err...)
} else if compilationResult != nil && structuredAuthnFeatureEnabled {
state.mapper.Groups = authenticationcel.NewClaimsMapper([]authenticationcel.CompilationResult{*compilationResult})
}
switch {
case len(m.UID.Claim) > 0 && len(m.UID.Expression) > 0:
allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), "", "claim and expression can't both be set"))
case len(m.UID.Expression) > 0:
compilationResult, err := compileClaimsCELExpression(compiler, &authenticationcel.ClaimMappingExpression{
Expression: m.UID.Expression,
}, fldPath.Child("uid").Child("expression"))
if err != nil {
allErrs = append(allErrs, err)
} else if structuredAuthnFeatureEnabled && compilationResult != nil {
state.mapper.UID = authenticationcel.NewClaimsMapper([]authenticationcel.CompilationResult{*compilationResult})
}
}
var extraCompilationResults []authenticationcel.CompilationResult
seenExtraKeys := sets.NewString()
for i, mapping := range m.Extra {
fldPath := fldPath.Child("extra").Index(i)
// Key should be namespaced to the authenticator or authenticator/authorizer pair making use of them.
// For instance: "example.org/foo" instead of "foo".
// xref: https://github.com/kubernetes/kubernetes/blob/3825e206cb162a7ad7431a5bdf6a065ae8422cf7/staging/src/k8s.io/apiserver/pkg/authentication/user/user.go#L31-L41
// IsDomainPrefixedPath checks for non-empty key and that the key is prefixed with a domain name.
allErrs = append(allErrs, utilvalidation.IsDomainPrefixedPath(fldPath.Child("key"), mapping.Key)...)
if mapping.Key != strings.ToLower(mapping.Key) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), mapping.Key, "key must be lowercase"))
}
if isKubernetesDomainPrefix(mapping.Key) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), mapping.Key, "k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use"))
}
if seenExtraKeys.Has(mapping.Key) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("key"), mapping.Key))
continue
}
seenExtraKeys.Insert(mapping.Key)
if len(mapping.ValueExpression) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("valueExpression"), "valueExpression is required"))
continue
}
compilationResult, err := compileClaimsCELExpression(compiler, &authenticationcel.ExtraMappingExpression{
Key: mapping.Key,
Expression: mapping.ValueExpression,
}, fldPath.Child("valueExpression"))
if err != nil {
allErrs = append(allErrs, err)
continue
}
if compilationResult != nil {
extraCompilationResults = append(extraCompilationResults, *compilationResult)
}
}
if structuredAuthnFeatureEnabled && len(extraCompilationResults) > 0 {
state.mapper.Extra = authenticationcel.NewClaimsMapper(extraCompilationResults)
state.usesEmailVerifiedClaim = state.usesEmailVerifiedClaim || anyUsesEmailVerifiedClaim(extraCompilationResults)
}
if structuredAuthnFeatureEnabled && state.usesEmailClaim && !state.usesEmailVerifiedClaim {
allErrs = append(allErrs, field.Invalid(fldPath.Child("username", "expression"), m.Username.Expression,
"claims.email_verified must be used in claimMappings.username.expression or claimMappings.extra[*].valueExpression or claimValidationRules[*].expression when claims.email is used in claimMappings.username.expression"))
}
return allErrs
}
func isKubernetesDomainPrefix(key string) bool {
domainPrefix := getDomainPrefix(key)
if domainPrefix == "kubernetes.io" || strings.HasSuffix(domainPrefix, ".kubernetes.io") {
return true
}
if domainPrefix == "k8s.io" || strings.HasSuffix(domainPrefix, ".k8s.io") {
return true
}
return false
}
func getDomainPrefix(key string) string {
if parts := strings.SplitN(key, "/", 2); len(parts) == 2 {
return parts[0]
}
return ""
}
func usesEmailClaim(ast *celgo.Ast) bool {
return hasSelectExp(ast.Expr(), "claims", "email")
}
func anyUsesEmailVerifiedClaim(results []authenticationcel.CompilationResult) bool {
for _, result := range results {
if usesEmailVerifiedClaim(result.AST) {
return true
}
}
return false
}
func usesEmailVerifiedClaim(ast *celgo.Ast) bool {
return hasSelectExp(ast.Expr(), "claims", "email_verified")
}
func hasSelectExp(exp *exprpb.Expr, operand, field string) bool {
if exp == nil {
return false
}
switch e := exp.ExprKind.(type) {
case *exprpb.Expr_ConstExpr,
*exprpb.Expr_IdentExpr:
return false
case *exprpb.Expr_SelectExpr:
s := e.SelectExpr
if s == nil {
return false
}
if isIdentOperand(s.Operand, operand) && s.Field == field {
return true
}
return hasSelectExp(s.Operand, operand, field)
case *exprpb.Expr_CallExpr:
c := e.CallExpr
if c == nil {
return false
}
if c.Target == nil && c.Function == operators.OptSelect && len(c.Args) == 2 &&
isIdentOperand(c.Args[0], operand) && isConstField(c.Args[1], field) {
return true
}
for _, arg := range c.Args {
if hasSelectExp(arg, operand, field) {
return true
}
}
return hasSelectExp(c.Target, operand, field)
case *exprpb.Expr_ListExpr:
l := e.ListExpr
if l == nil {
return false
}
for _, element := range l.Elements {
if hasSelectExp(element, operand, field) {
return true
}
}
return false
case *exprpb.Expr_StructExpr:
s := e.StructExpr
if s == nil {
return false
}
for _, entry := range s.Entries {
if hasSelectExp(entry.GetMapKey(), operand, field) {
return true
}
if hasSelectExp(entry.Value, operand, field) {
return true
}
}
return false
case *exprpb.Expr_ComprehensionExpr:
c := e.ComprehensionExpr
if c == nil {
return false
}
return hasSelectExp(c.IterRange, operand, field) ||
hasSelectExp(c.AccuInit, operand, field) ||
hasSelectExp(c.LoopCondition, operand, field) ||
hasSelectExp(c.LoopStep, operand, field) ||
hasSelectExp(c.Result, operand, field)
default:
return false
}
}
func isIdentOperand(exp *exprpb.Expr, operand string) bool {
if len(operand) == 0 {
return false // sanity check against default values
}
id := exp.GetIdentExpr() // does not panic even if exp is nil
return id != nil && id.Name == operand
}
func isConstField(exp *exprpb.Expr, field string) bool {
if len(field) == 0 {
return false // sanity check against default values
}
c := exp.GetConstExpr() // does not panic even if exp is nil
return c != nil && c.GetStringValue() == field // does not panic even if c is not a string
}
func validatePrefixClaimOrExpression(compiler authenticationcel.Compiler, mapping api.PrefixedClaimOrExpression, fldPath *field.Path, claimOrExpressionRequired bool) (*authenticationcel.CompilationResult, field.ErrorList) {
var allErrs field.ErrorList
var compilationResult *authenticationcel.CompilationResult
switch {
case len(mapping.Expression) > 0 && len(mapping.Claim) > 0:
allErrs = append(allErrs, field.Invalid(fldPath, "", "claim and expression can't both be set"))
case len(mapping.Expression) == 0 && len(mapping.Claim) == 0 && claimOrExpressionRequired:
allErrs = append(allErrs, field.Required(fldPath, "claim or expression is required"))
case len(mapping.Expression) > 0:
var err *field.Error
if mapping.Prefix != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("prefix"), *mapping.Prefix, "prefix can't be set when expression is set"))
}
compilationResult, err = compileClaimsCELExpression(compiler, &authenticationcel.ClaimMappingExpression{
Expression: mapping.Expression,
}, fldPath.Child("expression"))
if err != nil {
allErrs = append(allErrs, err)
}
case len(mapping.Claim) > 0:
if mapping.Prefix == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("prefix"), "prefix is required when claim is set. It can be set to an empty string to disable prefixing"))
}
}
return compilationResult, allErrs
}
func validateUserValidationRules(compiler authenticationcel.Compiler, state *validationState, rules []api.UserValidationRule, fldPath *field.Path, structuredAuthnFeatureEnabled bool) field.ErrorList {
var allErrs field.ErrorList
var compilationResults []authenticationcel.CompilationResult
if len(rules) > 0 && !structuredAuthnFeatureEnabled {
allErrs = append(allErrs, field.Invalid(fldPath, "", "user validation rules are not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
}
seenExpressions := sets.NewString()
for i, rule := range rules {
fldPath := fldPath.Index(i)
if len(rule.Expression) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("expression"), "expression is required"))
continue
}
if seenExpressions.Has(rule.Expression) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("expression"), rule.Expression))
continue
}
seenExpressions.Insert(rule.Expression)
compilationResult, err := compileUserCELExpression(compiler, &authenticationcel.UserValidationCondition{
Expression: rule.Expression,
Message: rule.Message,
}, fldPath.Child("expression"))
if err != nil {
allErrs = append(allErrs, err)
continue
}
if compilationResult != nil {
compilationResults = append(compilationResults, *compilationResult)
}
}
if structuredAuthnFeatureEnabled && len(compilationResults) > 0 {
state.mapper.UserValidationRules = authenticationcel.NewUserMapper(compilationResults)
}
return allErrs
}
func compileClaimsCELExpression(compiler authenticationcel.Compiler, expression authenticationcel.ExpressionAccessor, fldPath *field.Path) (*authenticationcel.CompilationResult, *field.Error) {
compilationResult, err := compiler.CompileClaimsExpression(expression)
if err != nil {
return nil, convertCELErrorToValidationError(fldPath, expression.GetExpression(), err)
}
return &compilationResult, nil
}
func compileUserCELExpression(compiler authenticationcel.Compiler, expression authenticationcel.ExpressionAccessor, fldPath *field.Path) (*authenticationcel.CompilationResult, *field.Error) {
compilationResult, err := compiler.CompileUserExpression(expression)
if err != nil {
return nil, convertCELErrorToValidationError(fldPath, expression.GetExpression(), err)
}
return &compilationResult, nil
}
// ValidateAuthorizationConfiguration validates a given AuthorizationConfiguration.
func ValidateAuthorizationConfiguration(compiler authorizationcel.Compiler, fldPath *field.Path, c *api.AuthorizationConfiguration, knownTypes sets.Set[string], repeatableTypes sets.Set[string]) field.ErrorList {
allErrs := field.ErrorList{}
if len(c.Authorizers) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("authorizers"), "at least one authorization mode must be defined"))
}
seenAuthorizerTypes := sets.NewString()
seenAuthorizerNames := sets.NewString()
for i, a := range c.Authorizers {
fldPath := fldPath.Child("authorizers").Index(i)
aType := string(a.Type)
if aType == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("type"), ""))
continue
}
if !knownTypes.Has(aType) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), aType, sets.List(knownTypes)))
continue
}
if seenAuthorizerTypes.Has(aType) && !repeatableTypes.Has(aType) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("type"), aType))
continue
}
seenAuthorizerTypes.Insert(aType)
if len(a.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
} else if seenAuthorizerNames.Has(a.Name) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), a.Name))
} else if errs := utilvalidation.IsDNS1123Subdomain(a.Name); len(errs) != 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), a.Name, fmt.Sprintf("authorizer name is invalid: %s", strings.Join(errs, ", "))))
}
seenAuthorizerNames.Insert(a.Name)
switch a.Type {
case api.TypeWebhook:
if a.Webhook == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("webhook"), "required when type=Webhook"))
continue
}
allErrs = append(allErrs, ValidateWebhookConfiguration(compiler, fldPath, a.Webhook)...)
default:
if a.Webhook != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("webhook"), "non-null", "may only be specified when type=Webhook"))
}
}
}
return allErrs
}
func ValidateWebhookConfiguration(compiler authorizationcel.Compiler, fldPath *field.Path, c *api.WebhookConfiguration) field.ErrorList {
allErrs := field.ErrorList{}
if c.Timeout.Duration == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("timeout"), ""))
} else if c.Timeout.Duration > 30*time.Second || c.Timeout.Duration < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("timeout"), c.Timeout.Duration.String(), "must be > 0s and <= 30s"))
}
if c.AuthorizedTTL.Duration == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("authorizedTTL"), ""))
} else if c.AuthorizedTTL.Duration < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("authorizedTTL"), c.AuthorizedTTL.Duration.String(), "must be > 0s"))
}
if c.UnauthorizedTTL.Duration == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("unauthorizedTTL"), ""))
} else if c.UnauthorizedTTL.Duration < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("unauthorizedTTL"), c.UnauthorizedTTL.Duration.String(), "must be > 0s"))
}
switch c.SubjectAccessReviewVersion {
case "":
allErrs = append(allErrs, field.Required(fldPath.Child("subjectAccessReviewVersion"), ""))
case "v1":
_ = &v1.SubjectAccessReview{}
case "v1beta1":
_ = &v1beta1.SubjectAccessReview{}
default:
allErrs = append(allErrs, field.NotSupported(fldPath.Child("subjectAccessReviewVersion"), c.SubjectAccessReviewVersion, []string{"v1", "v1beta1"}))
}
switch c.MatchConditionSubjectAccessReviewVersion {
case "":
if len(c.MatchConditions) > 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("matchConditionSubjectAccessReviewVersion"), "required if match conditions are specified"))
}
case "v1":
_ = &v1.SubjectAccessReview{}
default:
allErrs = append(allErrs, field.NotSupported(fldPath.Child("matchConditionSubjectAccessReviewVersion"), c.MatchConditionSubjectAccessReviewVersion, []string{"v1"}))
}
switch c.FailurePolicy {
case "":
allErrs = append(allErrs, field.Required(fldPath.Child("failurePolicy"), ""))
case api.FailurePolicyNoOpinion, api.FailurePolicyDeny:
default:
allErrs = append(allErrs, field.NotSupported(fldPath.Child("failurePolicy"), c.FailurePolicy, []string{"NoOpinion", "Deny"}))
}
switch c.ConnectionInfo.Type {
case "":
allErrs = append(allErrs, field.Required(fldPath.Child("connectionInfo", "type"), ""))
case api.AuthorizationWebhookConnectionInfoTypeInCluster:
if c.ConnectionInfo.KubeConfigFile != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("connectionInfo", "kubeConfigFile"), *c.ConnectionInfo.KubeConfigFile, "can only be set when type=KubeConfigFile"))
}
case api.AuthorizationWebhookConnectionInfoTypeKubeConfigFile:
if c.ConnectionInfo.KubeConfigFile == nil || *c.ConnectionInfo.KubeConfigFile == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("connectionInfo", "kubeConfigFile"), ""))
} else if !filepath.IsAbs(*c.ConnectionInfo.KubeConfigFile) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("connectionInfo", "kubeConfigFile"), *c.ConnectionInfo.KubeConfigFile, "must be an absolute path"))
} else if info, err := os.Stat(*c.ConnectionInfo.KubeConfigFile); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("connectionInfo", "kubeConfigFile"), *c.ConnectionInfo.KubeConfigFile, fmt.Sprintf("error loading file: %v", err)))
} else if !info.Mode().IsRegular() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("connectionInfo", "kubeConfigFile"), *c.ConnectionInfo.KubeConfigFile, "must be a regular file"))
}
default:
allErrs = append(allErrs, field.NotSupported(fldPath.Child("connectionInfo", "type"), c.ConnectionInfo, []string{api.AuthorizationWebhookConnectionInfoTypeInCluster, api.AuthorizationWebhookConnectionInfoTypeKubeConfigFile}))
}
_, errs := compileMatchConditions(compiler, c.MatchConditions, fldPath, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthorizationConfiguration))
allErrs = append(allErrs, errs...)
return allErrs
}
// ValidateAndCompileMatchConditions validates a given webhook's matchConditions.
// This is exported for use in authz package.
func ValidateAndCompileMatchConditions(compiler authorizationcel.Compiler, matchConditions []api.WebhookMatchCondition) (*authorizationcel.CELMatcher, field.ErrorList) {
return compileMatchConditions(compiler, matchConditions, nil, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthorizationConfiguration))
}
func compileMatchConditions(compiler authorizationcel.Compiler, matchConditions []api.WebhookMatchCondition, fldPath *field.Path, structuredAuthzFeatureEnabled bool) (*authorizationcel.CELMatcher, field.ErrorList) {
var allErrs field.ErrorList
// should fail when match conditions are used without feature enabled
if len(matchConditions) > 0 && !structuredAuthzFeatureEnabled {
allErrs = append(allErrs, field.Invalid(fldPath.Child("matchConditions"), "", "matchConditions are not supported when StructuredAuthorizationConfiguration feature gate is disabled"))
}
if len(matchConditions) > 64 {
allErrs = append(allErrs, field.TooMany(fldPath.Child("matchConditions"), len(matchConditions), 64))
return nil, allErrs
}
seenExpressions := sets.NewString()
var compilationResults []authorizationcel.CompilationResult
var usesFieldSelector, usesLabelSelector bool
for i, condition := range matchConditions {
fldPath := fldPath.Child("matchConditions").Index(i).Child("expression")
if len(strings.TrimSpace(condition.Expression)) == 0 {
allErrs = append(allErrs, field.Required(fldPath, ""))
continue
}
if seenExpressions.Has(condition.Expression) {
allErrs = append(allErrs, field.Duplicate(fldPath, condition.Expression))
continue
}
seenExpressions.Insert(condition.Expression)
compilationResult, err := compileMatchConditionsExpression(fldPath, compiler, condition.Expression)
if err != nil {
allErrs = append(allErrs, err)
continue
}
compilationResults = append(compilationResults, compilationResult)
usesFieldSelector = usesFieldSelector || compilationResult.UsesFieldSelector
usesLabelSelector = usesLabelSelector || compilationResult.UsesLabelSelector
}
if len(compilationResults) == 0 {
return nil, allErrs
}
return &authorizationcel.CELMatcher{
CompilationResults: compilationResults,
UsesFieldSelector: usesFieldSelector,
UsesLabelSelector: usesLabelSelector,
}, allErrs
}
func compileMatchConditionsExpression(fldPath *field.Path, compiler authorizationcel.Compiler, expression string) (authorizationcel.CompilationResult, *field.Error) {
authzExpression := &authorizationcel.SubjectAccessReviewMatchCondition{
Expression: expression,
}
compilationResult, err := compiler.CompileCELExpression(authzExpression)
if err != nil {
return compilationResult, convertCELErrorToValidationError(fldPath, authzExpression.GetExpression(), err)
}
return compilationResult, nil
}
func convertCELErrorToValidationError(fldPath *field.Path, expression string, err error) *field.Error {
var celErr *cel.Error
if errors.As(err, &celErr) {
switch celErr.Type {
case cel.ErrorTypeRequired:
return field.Required(fldPath, celErr.Detail)
case cel.ErrorTypeInvalid:
return field.Invalid(fldPath, expression, celErr.Detail)
default:
return field.InternalError(fldPath, celErr)
}
}
return field.InternalError(fldPath, fmt.Errorf("error is not cel error: %w", err))
}

View File

@ -1,451 +0,0 @@
/*
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/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/apis/apiserver"
)
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: %v"
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"
eventsGroupErr = "'*.events.k8s.io' objects are stored using the 'events' API group in etcd. Use 'events' instead in the config file"
extensionsGroupErr = "'extensions' group has been removed and cannot be used for encryption"
starResourceErr = "use '*.' to encrypt all the resources from core API group or *.* to encrypt all resources"
overlapErr = "using overlapping resources such as 'secrets' and '*.' in the same resource list is not allowed as they will be masked"
nonRESTAPIResourceErr = "resources which do not have REST API/s cannot be encrypted"
resourceNameErr = "resource name should not contain capital letters"
resourceAcrossGroupErr = "encrypting the same resource across groups is not supported"
duplicateResourceErr = "the same resource cannot be specified multiple times"
)
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}
)
// ValidateEncryptionConfiguration validates a v1.EncryptionConfiguration.
func ValidateEncryptionConfiguration(c *apiserver.EncryptionConfiguration, reload bool) field.ErrorList {
root := field.NewPath("resources")
allErrs := field.ErrorList{}
if c == nil {
allErrs = append(allErrs, field.Required(root, encryptionConfigNilErr))
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.New[string]()
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)))
}
allErrs = append(allErrs, validateResourceOverlap(conf.Resources, r)...)
allErrs = append(allErrs, validateResourceNames(conf.Resources, 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
}
var anyGroupAnyResource = schema.GroupResource{
Group: "*",
Resource: "*",
}
func validateResourceOverlap(resources []string, fieldPath *field.Path) field.ErrorList {
if len(resources) < 2 { // cannot have overlap with a single resource
return nil
}
var allErrs field.ErrorList
r := make([]schema.GroupResource, 0, len(resources))
for _, resource := range resources {
r = append(r, schema.ParseGroupResource(resource))
}
var hasOverlap, hasDuplicate bool
for i, r1 := range r {
for j, r2 := range r {
if i == j {
continue
}
if r1 == r2 && !hasDuplicate {
hasDuplicate = true
continue
}
if hasOverlap {
continue
}
if r1 == anyGroupAnyResource {
hasOverlap = true
continue
}
if r1.Group != r2.Group {
continue
}
if r1.Resource == "*" || r2.Resource == "*" {
hasOverlap = true
continue
}
}
}
if hasDuplicate {
allErrs = append(
allErrs,
field.Invalid(
fieldPath,
resources,
duplicateResourceErr,
),
)
}
if hasOverlap {
allErrs = append(
allErrs,
field.Invalid(
fieldPath,
resources,
overlapErr,
),
)
}
return allErrs
}
func validateResourceNames(resources []string, fieldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
for j, res := range resources {
jj := fieldPath.Index(j)
// check if resource name has capital letters
if hasCapital(res) {
allErrs = append(
allErrs,
field.Invalid(
jj,
resources[j],
resourceNameErr,
),
)
continue
}
// check if resource is '*'
if res == "*" {
allErrs = append(
allErrs,
field.Invalid(
jj,
resources[j],
starResourceErr,
),
)
continue
}
// check if resource is:
// 'apiserveripinfo' OR
// 'serviceipallocations' OR
// 'servicenodeportallocations' OR
if res == "apiserveripinfo" ||
res == "serviceipallocations" ||
res == "servicenodeportallocations" {
allErrs = append(
allErrs,
field.Invalid(
jj,
resources[j],
nonRESTAPIResourceErr,
),
)
continue
}
// check if group is 'events.k8s.io'
gr := schema.ParseGroupResource(res)
if gr.Group == "events.k8s.io" {
allErrs = append(
allErrs,
field.Invalid(
jj,
resources[j],
eventsGroupErr,
),
)
continue
}
// check if group is 'extensions'
if gr.Group == "extensions" {
allErrs = append(
allErrs,
field.Invalid(
jj,
resources[j],
extensionsGroupErr,
),
)
continue
}
// disallow resource.* as encrypting the same resource across groups does not make sense
if gr.Group == "*" && gr.Resource != "*" {
allErrs = append(
allErrs,
field.Invalid(
jj,
resources[j],
resourceAcrossGroupErr,
),
)
continue
}
}
return allErrs
}
func validateSingleProvider(provider apiserver.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 []apiserver.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 apiserver.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 *apiserver.KMSConfiguration, fieldPath *field.Path, kmsProviderNames sets.Set[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 *apiserver.KMSConfiguration, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// In defaulting, we set the cache size to the default value only when API version is v1.
// So, for v2 API version, we expect the cache size field to be nil.
if c.APIVersion != "v1" && c.CacheSize != nil {
allErrs = append(allErrs, field.Invalid(fieldPath, *c.CacheSize, "cachesize is not supported in v2"))
}
if c.APIVersion == "v1" && *c.CacheSize == 0 {
allErrs = append(allErrs, field.Invalid(fieldPath, *c.CacheSize, fmt.Sprintf(nonZeroErrFmt, "cachesize")))
}
return allErrs
}
func validateKMSTimeout(c *apiserver.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 *apiserver.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(invalidURLErrFmt, err)))
}
if u.Scheme != "unix" {
return append(allErrs, field.Invalid(fieldPath, c.Endpoint, fmt.Sprintf(unsupportedSchemeErrFmt, u.Scheme)))
}
return allErrs
}
func validateKMSAPIVersion(c *apiserver.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 *apiserver.KMSConfiguration, fieldPath *field.Path, kmsProviderNames sets.Set[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
}
func hasCapital(input string) bool {
return strings.ToLower(input) != input
}

View File

@ -1,33 +0,0 @@
/*
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))
}

View File

@ -1,133 +0,0 @@
/*
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
}

View File

@ -1,577 +0,0 @@
/*
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/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/utils/ptr"
)
// 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,
Exempt: &flowcontrol.ExemptPriorityLevelConfiguration{
NominalConcurrencyShares: ptr.To(int32(0)),
LendablePercent: ptr.To(int32(0)),
},
},
)
MandatoryPriorityLevelConfigurationCatchAll = newPriorityLevelConfiguration(
flowcontrol.PriorityLevelConfigurationNameCatchAll,
flowcontrol.PriorityLevelConfigurationSpec{
Type: flowcontrol.PriorityLevelEnablementLimited,
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
NominalConcurrencyShares: ptr.To(int32(5)),
LendablePercent: ptr.To(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: ptr.To(int32(30)),
LendablePercent: ptr.To(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: ptr.To(int32(40)),
LendablePercent: ptr.To(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: ptr.To(int32(10)),
LendablePercent: ptr.To(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: ptr.To(int32(40)),
LendablePercent: ptr.To(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: ptr.To(int32(100)),
LendablePercent: ptr.To(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: ptr.To(int32(20)),
LendablePercent: ptr.To(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}
}