build: move e2e dependencies into e2e/go.mod

Several packages are only used while running the e2e suite. These
packages are less important to update, as the they can not influence the
final executable that is part of the Ceph-CSI container-image.

By moving these dependencies out of the main Ceph-CSI go.mod, it is
easier to identify if a reported CVE affects Ceph-CSI, or only the
testing (like most of the Kubernetes CVEs).

Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
Niels de Vos
2025-03-04 08:57:28 +01:00
committed by mergify[bot]
parent 15da101b1b
commit bec6090996
8047 changed files with 1407827 additions and 3453 deletions

View File

@ -0,0 +1,184 @@
/*
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 authorizer
import (
"context"
"net/http"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/authentication/user"
)
// Attributes is an interface used by an Authorizer to get information about a request
// that is used to make an authorization decision.
type Attributes interface {
// GetUser returns the user.Info object to authorize
GetUser() user.Info
// GetVerb returns the kube verb associated with API requests (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy),
// or the lowercased HTTP verb associated with non-API requests (this includes get, put, post, patch, and delete)
GetVerb() string
// When IsReadOnly() == true, the request has no side effects, other than
// caching, logging, and other incidentals.
IsReadOnly() bool
// The namespace of the object, if a request is for a REST object.
GetNamespace() string
// The kind of object, if a request is for a REST object.
GetResource() string
// GetSubresource returns the subresource being requested, if present
GetSubresource() string
// GetName returns the name of the object as parsed off the request. This will not be present for all request types, but
// will be present for: get, update, delete
GetName() string
// The group of the resource, if a request is for a REST object.
GetAPIGroup() string
// GetAPIVersion returns the version of the group requested, if a request is for a REST object.
GetAPIVersion() string
// IsResourceRequest returns true for requests to API resources, like /api/v1/nodes,
// and false for non-resource endpoints like /api, /healthz
IsResourceRequest() bool
// GetPath returns the path of the request
GetPath() string
// ParseFieldSelector is lazy, thread-safe, and stores the parsed result and error.
// It returns an error if the field selector cannot be parsed.
// The returned requirements must be treated as readonly and not modified.
GetFieldSelector() (fields.Requirements, error)
// ParseLabelSelector is lazy, thread-safe, and stores the parsed result and error.
// It returns an error if the label selector cannot be parsed.
// The returned requirements must be treated as readonly and not modified.
GetLabelSelector() (labels.Requirements, error)
}
// Authorizer makes an authorization decision based on information gained by making
// zero or more calls to methods of the Attributes interface. It returns nil when an action is
// authorized, otherwise it returns an error.
type Authorizer interface {
Authorize(ctx context.Context, a Attributes) (authorized Decision, reason string, err error)
}
type AuthorizerFunc func(ctx context.Context, a Attributes) (Decision, string, error)
func (f AuthorizerFunc) Authorize(ctx context.Context, a Attributes) (Decision, string, error) {
return f(ctx, a)
}
// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
type RuleResolver interface {
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
RulesFor(ctx context.Context, user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
}
// RequestAttributesGetter provides a function that extracts Attributes from an http.Request
type RequestAttributesGetter interface {
GetRequestAttributes(user.Info, *http.Request) Attributes
}
// AttributesRecord implements Attributes interface.
type AttributesRecord struct {
User user.Info
Verb string
Namespace string
APIGroup string
APIVersion string
Resource string
Subresource string
Name string
ResourceRequest bool
Path string
FieldSelectorRequirements fields.Requirements
FieldSelectorParsingErr error
LabelSelectorRequirements labels.Requirements
LabelSelectorParsingErr error
}
func (a AttributesRecord) GetUser() user.Info {
return a.User
}
func (a AttributesRecord) GetVerb() string {
return a.Verb
}
func (a AttributesRecord) IsReadOnly() bool {
return a.Verb == "get" || a.Verb == "list" || a.Verb == "watch"
}
func (a AttributesRecord) GetNamespace() string {
return a.Namespace
}
func (a AttributesRecord) GetResource() string {
return a.Resource
}
func (a AttributesRecord) GetSubresource() string {
return a.Subresource
}
func (a AttributesRecord) GetName() string {
return a.Name
}
func (a AttributesRecord) GetAPIGroup() string {
return a.APIGroup
}
func (a AttributesRecord) GetAPIVersion() string {
return a.APIVersion
}
func (a AttributesRecord) IsResourceRequest() bool {
return a.ResourceRequest
}
func (a AttributesRecord) GetPath() string {
return a.Path
}
func (a AttributesRecord) GetFieldSelector() (fields.Requirements, error) {
return a.FieldSelectorRequirements, a.FieldSelectorParsingErr
}
func (a AttributesRecord) GetLabelSelector() (labels.Requirements, error) {
return a.LabelSelectorRequirements, a.LabelSelectorParsingErr
}
type Decision int
const (
// DecisionDeny means that an authorizer decided to deny the action.
DecisionDeny Decision = iota
// DecisionAllow means that an authorizer decided to allow the action.
DecisionAllow
// DecisionNoOpinion means that an authorizer has no opinion on whether
// to allow or deny an action.
DecisionNoOpinion
)

View 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 authorizer
type ResourceRuleInfo interface {
// GetVerbs returns a list of kubernetes resource API verbs.
GetVerbs() []string
// GetAPIGroups return the names of the APIGroup that contains the resources.
GetAPIGroups() []string
// GetResources return a list of resources the rule applies to.
GetResources() []string
// GetResourceNames return a white list of names that the rule applies to.
GetResourceNames() []string
}
// DefaultResourceRuleInfo holds information that describes a rule for the resource
type DefaultResourceRuleInfo struct {
Verbs []string
APIGroups []string
Resources []string
ResourceNames []string
}
func (i *DefaultResourceRuleInfo) GetVerbs() []string {
return i.Verbs
}
func (i *DefaultResourceRuleInfo) GetAPIGroups() []string {
return i.APIGroups
}
func (i *DefaultResourceRuleInfo) GetResources() []string {
return i.Resources
}
func (i *DefaultResourceRuleInfo) GetResourceNames() []string {
return i.ResourceNames
}
type NonResourceRuleInfo interface {
// GetVerbs returns a list of kubernetes resource API verbs.
GetVerbs() []string
// GetNonResourceURLs return a set of partial urls that a user should have access to.
GetNonResourceURLs() []string
}
// DefaultNonResourceRuleInfo holds information that describes a rule for the non-resource
type DefaultNonResourceRuleInfo struct {
Verbs []string
NonResourceURLs []string
}
func (i *DefaultNonResourceRuleInfo) GetVerbs() []string {
return i.Verbs
}
func (i *DefaultNonResourceRuleInfo) GetNonResourceURLs() []string {
return i.NonResourceURLs
}

View 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(ctx context.Context, 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(ctx context.Context, 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,
}
}

View File

@ -0,0 +1,69 @@
/*
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"
authorizationcel "k8s.io/apiserver/pkg/authorization/cel"
"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 authorizer
// built to delegate authorization to a kube API server
type DelegatingAuthorizerConfig struct {
SubjectAccessReviewClient authorizationclient.AuthorizationV1Interface
// Compiler is the CEL compiler to use for evaluating policies. If nil, a default compiler will be used.
Compiler authorizationcel.Compiler
// 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")
}
compiler := c.Compiler
if compiler == nil {
compiler = authorizationcel.NewDefaultCompiler()
}
return webhook.NewFromInterface(
c.SubjectAccessReviewClient,
c.AllowCacheTTL,
c.DenyCacheTTL,
*c.WebhookRetryBackoff,
authorizer.DecisionNoOpinion,
NewDelegatingAuthorizerMetrics(),
compiler,
)
}

View File

@ -0,0 +1,82 @@
/*
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"
"sync"
celmetrics "k8s.io/apiserver/pkg/authorization/cel"
webhookmetrics "k8s.io/apiserver/plugin/pkg/authorizer/webhook/metrics"
compbasemetrics "k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
)
var registerMetrics sync.Once
// RegisterMetrics registers authorizer metrics.
func RegisterMetrics() {
registerMetrics.Do(func() {
legacyregistry.MustRegister(requestTotal)
legacyregistry.MustRegister(requestLatency)
})
}
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"},
)
)
var _ = webhookmetrics.AuthorizerMetrics(delegatingAuthorizerMetrics{})
type delegatingAuthorizerMetrics struct {
// no-op for webhook metrics for now, delegating authorization reports original total/latency metrics
webhookmetrics.NoopWebhookMetrics
// no-op for matchCondition metrics for now, delegating authorization doesn't configure match conditions
celmetrics.NoopMatcherMetrics
}
func NewDelegatingAuthorizerMetrics() delegatingAuthorizerMetrics {
RegisterMetrics()
return delegatingAuthorizerMetrics{}
}
// RecordRequestTotal increments the total number of requests for the delegated authorization.
func (delegatingAuthorizerMetrics) 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 (delegatingAuthorizerMetrics) RecordRequestLatency(ctx context.Context, code string, latency float64) {
requestLatency.WithContext(ctx).WithLabelValues(code).Observe(latency)
}

View File

@ -0,0 +1,336 @@
/*
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 cel
import (
"fmt"
"github.com/google/cel-go/cel"
celast "github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/types/ref"
authorizationv1 "k8s.io/api/authorization/v1"
"k8s.io/apimachinery/pkg/util/version"
apiservercel "k8s.io/apiserver/pkg/cel"
"k8s.io/apiserver/pkg/cel/environment"
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
)
const (
subjectAccessReviewRequestVarName = "request"
fieldSelectorVarName = "fieldSelector"
labelSelectorVarName = "labelSelector"
)
// CompilationResult represents a compiled authorization cel expression.
type CompilationResult struct {
Program cel.Program
ExpressionAccessor ExpressionAccessor
// These track if a given expression uses fieldSelector and labelSelector,
// so construction of data passed to the CEL expression can be optimized if those fields are unused.
UsesFieldSelector bool
UsesLabelSelector bool
}
// EvaluationResult contains the minimal required fields and metadata of a cel evaluation
type EvaluationResult struct {
EvalResult ref.Val
ExpressionAccessor ExpressionAccessor
}
// Compiler is an interface for compiling CEL expressions with the desired environment mode.
type Compiler interface {
CompileCELExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error)
}
type compiler struct {
envSet *environment.EnvSet
}
// NewDefaultCompiler returns a new Compiler following the default compatibility version.
// Note: the compiler construction depends on feature gates and the compatibility version to be initialized.
func NewDefaultCompiler() Compiler {
return NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
}
// NewCompiler returns a new Compiler.
func NewCompiler(env *environment.EnvSet) Compiler {
return &compiler{
envSet: mustBuildEnv(env),
}
}
func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error) {
resultError := func(errorString string, errType apiservercel.ErrorType) (CompilationResult, error) {
err := &apiservercel.Error{
Type: errType,
Detail: errorString,
}
return CompilationResult{
ExpressionAccessor: expressionAccessor,
}, err
}
env, err := c.envSet.Env(environment.StoredExpressions)
if err != nil {
return resultError(fmt.Sprintf("unexpected error loading CEL environment: %v", err), apiservercel.ErrorTypeInternal)
}
ast, issues := env.Compile(expressionAccessor.GetExpression())
if issues != nil {
return resultError("compilation failed: "+issues.String(), apiservercel.ErrorTypeInvalid)
}
found := false
returnTypes := expressionAccessor.ReturnTypes()
for _, returnType := range returnTypes {
if ast.OutputType() == returnType {
found = true
break
}
}
if !found {
var reason string
if len(returnTypes) == 1 {
reason = fmt.Sprintf("must evaluate to %v but got %v", returnTypes[0].String(), ast.OutputType())
} else {
reason = fmt.Sprintf("must evaluate to one of %v", returnTypes)
}
return resultError(reason, apiservercel.ErrorTypeInvalid)
}
checkedExpr, err := cel.AstToCheckedExpr(ast)
if err != nil {
// should be impossible since env.Compile returned no issues
return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal)
}
celAST, err := celast.ToAST(checkedExpr)
if err != nil {
// should be impossible since env.Compile returned no issues
return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal)
}
var usesFieldSelector, usesLabelSelector bool
celast.PreOrderVisit(celast.NavigateAST(celAST), celast.NewExprVisitor(func(e celast.Expr) {
// we already know we use both, no need to inspect more
if usesFieldSelector && usesLabelSelector {
return
}
var fieldName string
switch e.Kind() {
case celast.SelectKind:
// simple select (.fieldSelector / .labelSelector)
fieldName = e.AsSelect().FieldName()
case celast.CallKind:
// optional select (.?fieldSelector / .?labelSelector)
if e.AsCall().FunctionName() != operators.OptSelect {
return
}
args := e.AsCall().Args()
// args[0] is the receiver (what comes before the `.?`), args[1] is the field name being optionally selected (what comes after the `.?`)
if len(args) != 2 || args[1].Kind() != celast.LiteralKind || args[1].AsLiteral().Type() != cel.StringType {
return
}
fieldName, _ = args[1].AsLiteral().Value().(string)
}
switch fieldName {
case fieldSelectorVarName:
usesFieldSelector = true
case labelSelectorVarName:
usesLabelSelector = true
}
}))
prog, err := env.Program(ast)
if err != nil {
return resultError("program instantiation failed: "+err.Error(), apiservercel.ErrorTypeInternal)
}
return CompilationResult{
Program: prog,
ExpressionAccessor: expressionAccessor,
UsesFieldSelector: usesFieldSelector,
UsesLabelSelector: usesLabelSelector,
}, nil
}
func mustBuildEnv(baseEnv *environment.EnvSet) *environment.EnvSet {
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
}
subjectAccessReviewSpecRequestType := buildRequestType(field, fields)
extended, err := baseEnv.Extend(
environment.VersionedOptions{
// we record this as 1.0 since it was available in the
// first version that supported this feature
IntroducedVersion: version.MajorMinor(1, 0),
EnvOptions: []cel.EnvOption{
cel.Variable(subjectAccessReviewRequestVarName, subjectAccessReviewSpecRequestType.CelType()),
},
DeclTypes: []*apiservercel.DeclType{
subjectAccessReviewSpecRequestType,
},
},
)
if err != nil {
panic(fmt.Sprintf("environment misconfigured: %v", err))
}
return extended
}
// buildRequestType generates a DeclType for SubjectAccessReviewSpec.
// if attributes are added here, also add to convertObjectToUnstructured.
func buildRequestType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
resourceAttributesType := buildResourceAttributesType(field, fields)
nonResourceAttributesType := buildNonResourceAttributesType(field, fields)
return apiservercel.NewObjectType("kubernetes.SubjectAccessReviewSpec", fields(
field("resourceAttributes", resourceAttributesType, false),
field("nonResourceAttributes", nonResourceAttributesType, false),
field("user", apiservercel.StringType, false),
field("groups", apiservercel.NewListType(apiservercel.StringType, -1), false),
field("extra", apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1), false),
field("uid", apiservercel.StringType, false),
))
}
// buildResourceAttributesType generates a DeclType for ResourceAttributes.
// if attributes are added here, also add to convertObjectToUnstructured.
func buildResourceAttributesType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
resourceAttributesFields := []*apiservercel.DeclField{
field("namespace", apiservercel.StringType, false),
field("verb", apiservercel.StringType, false),
field("group", apiservercel.StringType, false),
field("version", apiservercel.StringType, false),
field("resource", apiservercel.StringType, false),
field("subresource", apiservercel.StringType, false),
field("name", apiservercel.StringType, false),
}
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors) {
resourceAttributesFields = append(resourceAttributesFields, field("fieldSelector", buildFieldSelectorType(field, fields), false))
resourceAttributesFields = append(resourceAttributesFields, field("labelSelector", buildLabelSelectorType(field, fields), false))
}
return apiservercel.NewObjectType("kubernetes.ResourceAttributes", fields(resourceAttributesFields...))
}
func buildFieldSelectorType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
return apiservercel.NewObjectType("kubernetes.FieldSelectorAttributes", fields(
field("rawSelector", apiservercel.StringType, false),
field("requirements", apiservercel.NewListType(buildSelectorRequirementType(field, fields), -1), false),
))
}
func buildLabelSelectorType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
return apiservercel.NewObjectType("kubernetes.LabelSelectorAttributes", fields(
field("rawSelector", apiservercel.StringType, false),
field("requirements", apiservercel.NewListType(buildSelectorRequirementType(field, fields), -1), false),
))
}
func buildSelectorRequirementType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
return apiservercel.NewObjectType("kubernetes.SelectorRequirement", fields(
field("key", apiservercel.StringType, false),
field("operator", apiservercel.StringType, false),
field("values", apiservercel.NewListType(apiservercel.StringType, -1), false),
))
}
// buildNonResourceAttributesType generates a DeclType for NonResourceAttributes.
// if attributes are added here, also add to convertObjectToUnstructured.
func buildNonResourceAttributesType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
return apiservercel.NewObjectType("kubernetes.NonResourceAttributes", fields(
field("path", apiservercel.StringType, false),
field("verb", apiservercel.StringType, false),
))
}
func convertObjectToUnstructured(obj *authorizationv1.SubjectAccessReviewSpec, includeFieldSelector, includeLabelSelector bool) map[string]interface{} {
// Construct version containing every SubjectAccessReview user and string attribute field, even omitempty ones, for evaluation by CEL
extra := obj.Extra
if extra == nil {
extra = map[string]authorizationv1.ExtraValue{}
}
ret := map[string]interface{}{
"user": obj.User,
"groups": obj.Groups,
"uid": string(obj.UID),
"extra": extra,
}
if obj.ResourceAttributes != nil {
resourceAttributes := map[string]interface{}{
"namespace": obj.ResourceAttributes.Namespace,
"verb": obj.ResourceAttributes.Verb,
"group": obj.ResourceAttributes.Group,
"version": obj.ResourceAttributes.Version,
"resource": obj.ResourceAttributes.Resource,
"subresource": obj.ResourceAttributes.Subresource,
"name": obj.ResourceAttributes.Name,
}
if includeFieldSelector && obj.ResourceAttributes.FieldSelector != nil {
if len(obj.ResourceAttributes.FieldSelector.Requirements) > 0 {
requirements := make([]map[string]interface{}, 0, len(obj.ResourceAttributes.FieldSelector.Requirements))
for _, r := range obj.ResourceAttributes.FieldSelector.Requirements {
requirements = append(requirements, map[string]interface{}{
"key": r.Key,
"operator": r.Operator,
"values": r.Values,
})
}
resourceAttributes[fieldSelectorVarName] = map[string]interface{}{"requirements": requirements}
}
if len(obj.ResourceAttributes.FieldSelector.RawSelector) > 0 {
resourceAttributes[fieldSelectorVarName] = map[string]interface{}{"rawSelector": obj.ResourceAttributes.FieldSelector.RawSelector}
}
}
if includeLabelSelector && obj.ResourceAttributes.LabelSelector != nil {
if len(obj.ResourceAttributes.LabelSelector.Requirements) > 0 {
requirements := make([]map[string]interface{}, 0, len(obj.ResourceAttributes.LabelSelector.Requirements))
for _, r := range obj.ResourceAttributes.LabelSelector.Requirements {
requirements = append(requirements, map[string]interface{}{
"key": r.Key,
"operator": r.Operator,
"values": r.Values,
})
}
resourceAttributes[labelSelectorVarName] = map[string]interface{}{"requirements": requirements}
}
if len(obj.ResourceAttributes.LabelSelector.RawSelector) > 0 {
resourceAttributes[labelSelectorVarName] = map[string]interface{}{"rawSelector": obj.ResourceAttributes.LabelSelector.RawSelector}
}
}
ret["resourceAttributes"] = resourceAttributes
}
if obj.NonResourceAttributes != nil {
ret["nonResourceAttributes"] = map[string]string{
"verb": obj.NonResourceAttributes.Verb,
"path": obj.NonResourceAttributes.Path,
}
}
return ret
}

View File

@ -0,0 +1,41 @@
/*
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 cel
import (
celgo "github.com/google/cel-go/cel"
)
type ExpressionAccessor interface {
GetExpression() string
ReturnTypes() []*celgo.Type
}
var _ ExpressionAccessor = &SubjectAccessReviewMatchCondition{}
// SubjectAccessReviewMatchCondition is a CEL expression that maps a SubjectAccessReview request to a list of values.
type SubjectAccessReviewMatchCondition struct {
Expression string
}
func (v *SubjectAccessReviewMatchCondition) GetExpression() string {
return v.Expression
}
func (v *SubjectAccessReviewMatchCondition) ReturnTypes() []*celgo.Type {
return []*celgo.Type{celgo.BoolType}
}

View File

@ -0,0 +1,91 @@
/*
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 cel
import (
"context"
"fmt"
"time"
celgo "github.com/google/cel-go/cel"
authorizationv1 "k8s.io/api/authorization/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
type CELMatcher struct {
CompilationResults []CompilationResult
// These track if any expressions use fieldSelector and labelSelector,
// so construction of data passed to the CEL expression can be optimized if those fields are unused.
UsesLabelSelector bool
UsesFieldSelector bool
// These are optional fields which can be populated if metrics reporting is desired
Metrics MatcherMetrics
AuthorizerType string
AuthorizerName string
}
// eval evaluates the given SubjectAccessReview against all cel matchCondition expression
func (c *CELMatcher) Eval(ctx context.Context, r *authorizationv1.SubjectAccessReview) (bool, error) {
var evalErrors []error
metrics := c.Metrics
if metrics == nil {
metrics = NoopMatcherMetrics{}
}
start := time.Now()
defer func() {
metrics.RecordAuthorizationMatchConditionEvaluation(ctx, c.AuthorizerType, c.AuthorizerName, time.Since(start))
if len(evalErrors) > 0 {
metrics.RecordAuthorizationMatchConditionEvaluationFailure(ctx, c.AuthorizerType, c.AuthorizerName)
}
}()
va := map[string]interface{}{
"request": convertObjectToUnstructured(&r.Spec, c.UsesFieldSelector, c.UsesLabelSelector),
}
for _, compilationResult := range c.CompilationResults {
evalResult, _, err := compilationResult.Program.ContextEval(ctx, va)
if err != nil {
evalErrors = append(evalErrors, fmt.Errorf("cel evaluation error: expression '%v' resulted in error: %w", compilationResult.ExpressionAccessor.GetExpression(), err))
continue
}
if evalResult.Type() != celgo.BoolType {
evalErrors = append(evalErrors, fmt.Errorf("cel evaluation error: expression '%v' eval result type should be bool but got %W", compilationResult.ExpressionAccessor.GetExpression(), evalResult.Type()))
continue
}
match, ok := evalResult.Value().(bool)
if !ok {
evalErrors = append(evalErrors, fmt.Errorf("cel evaluation error: expression '%v' eval result value should be bool but got %W", compilationResult.ExpressionAccessor.GetExpression(), evalResult.Value()))
continue
}
// If at least one matchCondition successfully evaluates to FALSE,
// return early
if !match {
metrics.RecordAuthorizationMatchConditionExclusion(ctx, c.AuthorizerType, c.AuthorizerName)
return false, nil
}
}
// if there is any error, return
if len(evalErrors) > 0 {
return false, utilerrors.NewAggregate(evalErrors)
}
// return ALL matchConditions evaluate to TRUE successfully without error
return true, nil
}

View File

@ -0,0 +1,120 @@
/*
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 cel
import (
"context"
"sync"
"time"
"k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
)
// MatcherMetrics defines methods for reporting matchCondition metrics
type MatcherMetrics interface {
// RecordAuthorizationMatchConditionEvaluation records the total time taken to evaluate matchConditions for an Authorize() call to the given authorizer
RecordAuthorizationMatchConditionEvaluation(ctx context.Context, authorizerType, authorizerName string, elapsed time.Duration)
// RecordAuthorizationMatchConditionEvaluationFailure increments if any evaluation error was encountered evaluating matchConditions for an Authorize() call to the given authorizer
RecordAuthorizationMatchConditionEvaluationFailure(ctx context.Context, authorizerType, authorizerName string)
// RecordAuthorizationMatchConditionExclusion records increments when at least one matchCondition evaluates to false and excludes an Authorize() call to the given authorizer
RecordAuthorizationMatchConditionExclusion(ctx context.Context, authorizerType, authorizerName string)
}
type NoopMatcherMetrics struct{}
func (NoopMatcherMetrics) RecordAuthorizationMatchConditionEvaluation(ctx context.Context, authorizerType, authorizerName string, elapsed time.Duration) {
}
func (NoopMatcherMetrics) RecordAuthorizationMatchConditionEvaluationFailure(ctx context.Context, authorizerType, authorizerName string) {
}
func (NoopMatcherMetrics) RecordAuthorizationMatchConditionExclusion(ctx context.Context, authorizerType, authorizerName string) {
}
type matcherMetrics struct{}
func NewMatcherMetrics() MatcherMetrics {
RegisterMetrics()
return matcherMetrics{}
}
const (
namespace = "apiserver"
subsystem = "authorization"
)
var (
authorizationMatchConditionEvaluationErrorsTotal = metrics.NewCounterVec(
&metrics.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "match_condition_evaluation_errors_total",
Help: "Total number of errors when an authorization webhook encounters a match condition error split by authorizer type and name.",
StabilityLevel: metrics.ALPHA,
},
[]string{"type", "name"},
)
authorizationMatchConditionExclusionsTotal = metrics.NewCounterVec(
&metrics.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "match_condition_exclusions_total",
Help: "Total number of exclusions when an authorization webhook is skipped because match conditions exclude it.",
StabilityLevel: metrics.ALPHA,
},
[]string{"type", "name"},
)
authorizationMatchConditionEvaluationSeconds = metrics.NewHistogramVec(
&metrics.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "match_condition_evaluation_seconds",
Help: "Authorization match condition evaluation time in seconds, split by authorizer type and name.",
Buckets: []float64{0.001, 0.005, 0.01, 0.025, 0.1, 0.2, 0.25},
StabilityLevel: metrics.ALPHA,
},
[]string{"type", "name"},
)
)
var registerMetrics sync.Once
func RegisterMetrics() {
registerMetrics.Do(func() {
legacyregistry.MustRegister(authorizationMatchConditionEvaluationErrorsTotal)
legacyregistry.MustRegister(authorizationMatchConditionExclusionsTotal)
legacyregistry.MustRegister(authorizationMatchConditionEvaluationSeconds)
})
}
func ResetMetricsForTest() {
authorizationMatchConditionEvaluationErrorsTotal.Reset()
authorizationMatchConditionExclusionsTotal.Reset()
authorizationMatchConditionEvaluationSeconds.Reset()
}
func (matcherMetrics) RecordAuthorizationMatchConditionEvaluationFailure(ctx context.Context, authorizerType, authorizerName string) {
authorizationMatchConditionEvaluationErrorsTotal.WithContext(ctx).WithLabelValues(authorizerType, authorizerName).Inc()
}
func (matcherMetrics) RecordAuthorizationMatchConditionExclusion(ctx context.Context, authorizerType, authorizerName string) {
authorizationMatchConditionExclusionsTotal.WithContext(ctx).WithLabelValues(authorizerType, authorizerName).Inc()
}
func (matcherMetrics) RecordAuthorizationMatchConditionEvaluation(ctx context.Context, authorizerType, authorizerName string, elapsed time.Duration) {
elapsedSeconds := elapsed.Seconds()
authorizationMatchConditionEvaluationSeconds.WithContext(ctx).WithLabelValues(authorizerType, authorizerName).Observe(elapsedSeconds)
}

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

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

View 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(ctx context.Context, 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(ctx, 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)
}