diff --git a/pkg/auth/authorizer/abac/abac.go b/pkg/auth/authorizer/abac/abac.go index 912977aa945..bc8a88d0762 100644 --- a/pkg/auth/authorizer/abac/abac.go +++ b/pkg/auth/authorizer/abac/abac.go @@ -241,6 +241,16 @@ func (pl PolicyList) Authorize(ctx context.Context, a authorizer.Attributes) (au // Then, add Caching only if needed. } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (pl PolicyList) ConditionsAwareAuthorize(ctx context.Context, a authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(pl.Authorize(ctx, a)) +} + +// EvaluateConditions is not supported by this authorizer. +func (PolicyList) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + // RulesFor returns rules for the given user and namespace. func (pl PolicyList) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { var ( diff --git a/pkg/controlplane/apiserver/config.go b/pkg/controlplane/apiserver/config.go index c646ef44336..9df8aeecb5e 100644 --- a/pkg/controlplane/apiserver/config.go +++ b/pkg/controlplane/apiserver/config.go @@ -243,7 +243,7 @@ func BuildGenericConfig( } // BuildAuthorizer constructs the authorizer. If authorization is not set in s, it returns nil, nil, false, nil -func BuildAuthorizer(ctx context.Context, s options.CompletedOptions, egressSelector *egressselector.EgressSelector, apiserverID string, versionedInformers clientgoinformers.SharedInformerFactory) (authorizer.UnconditionalAuthorizer, authorizer.RuleResolver, bool, error) { +func BuildAuthorizer(ctx context.Context, s options.CompletedOptions, egressSelector *egressselector.EgressSelector, apiserverID string, versionedInformers clientgoinformers.SharedInformerFactory) (authorizer.Authorizer, authorizer.RuleResolver, bool, error) { authorizationConfig, err := s.Authorization.ToAuthorizationConfig(versionedInformers) if err != nil { return nil, nil, false, err diff --git a/pkg/kubeapiserver/authorizer/config.go b/pkg/kubeapiserver/authorizer/config.go index 6147980e9ae..c7731dff17b 100644 --- a/pkg/kubeapiserver/authorizer/config.go +++ b/pkg/kubeapiserver/authorizer/config.go @@ -79,7 +79,7 @@ type Config struct { // stopCh is used to shut down config reload goroutines when the server is shutting down. // // Note: the cel compiler construction depends on feature gates and the compatibility version to be initialized. -func (config Config) New(ctx context.Context, serverID string) (authorizer.UnconditionalAuthorizer, authorizer.RuleResolver, error) { +func (config Config) New(ctx context.Context, serverID string) (authorizer.Authorizer, authorizer.RuleResolver, error) { if len(config.AuthorizationConfiguration.Authorizers) == 0 { return nil, nil, fmt.Errorf("at least one authorization mode must be passed") } diff --git a/pkg/kubeapiserver/authorizer/reload.go b/pkg/kubeapiserver/authorizer/reload.go index f64911b28a7..a596594ee41 100644 --- a/pkg/kubeapiserver/authorizer/reload.go +++ b/pkg/kubeapiserver/authorizer/reload.go @@ -74,7 +74,7 @@ type reloadableAuthorizerResolver struct { } type authorizerResolver struct { - authorizer authorizer.UnconditionalAuthorizer + authorizer authorizer.Authorizer ruleResolver authorizer.RuleResolver } @@ -82,18 +82,28 @@ func (r *reloadableAuthorizerResolver) Authorize(ctx context.Context, a authoriz return r.current.Load().authorizer.Authorize(ctx, a) } +// ConditionsAwareAuthorize delegates to the current authorizer. +func (r *reloadableAuthorizerResolver) ConditionsAwareAuthorize(ctx context.Context, a authorizer.Attributes) authorizer.ConditionsAwareDecision { + return r.current.Load().authorizer.ConditionsAwareAuthorize(ctx, a) +} + +// EvaluateConditions delegates to the current authorizer. +func (r *reloadableAuthorizerResolver) EvaluateConditions(ctx context.Context, decision authorizer.ConditionsAwareDecision, data authorizer.ConditionsData) (authorizer.Decision, string, error) { + return r.current.Load().authorizer.EvaluateConditions(ctx, decision, data) +} + func (r *reloadableAuthorizerResolver) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { return r.current.Load().ruleResolver.RulesFor(ctx, user, namespace) } // newForConfig constructs -func (r *reloadableAuthorizerResolver) newForConfig(authzConfig *authzconfig.AuthorizationConfiguration) (authorizer.UnconditionalAuthorizer, authorizer.RuleResolver, error) { +func (r *reloadableAuthorizerResolver) newForConfig(authzConfig *authzconfig.AuthorizationConfiguration) (authorizer.Authorizer, authorizer.RuleResolver, error) { if len(authzConfig.Authorizers) == 0 { return nil, nil, fmt.Errorf("at least one authorization mode must be passed") } var ( - authorizers []authorizer.UnconditionalAuthorizer + authorizers []authorizer.Authorizer ruleResolvers []authorizer.RuleResolver ) diff --git a/pkg/registry/admissionregistration/mutatingadmissionpolicy/authz_test.go b/pkg/registry/admissionregistration/mutatingadmissionpolicy/authz_test.go index cecadc792fe..5cd16c9fa34 100644 --- a/pkg/registry/admissionregistration/mutatingadmissionpolicy/authz_test.go +++ b/pkg/registry/admissionregistration/mutatingadmissionpolicy/authz_test.go @@ -34,7 +34,7 @@ func TestAuthorization(t *testing.T) { name string userInfo user.Info obj *admissionregistration.MutatingAdmissionPolicy - auth AuthFunc + auth authorizer.AuthorizerFunc resourceResolver resolver.ResourceResolverFunc expectErr bool }{ @@ -123,9 +123,3 @@ func TestAuthorization(t *testing.T) { }) } } - -type AuthFunc func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) - -func (f AuthFunc) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - return f(ctx, a) -} diff --git a/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/authz_test.go b/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/authz_test.go index f7fe0e390b6..29afea169b1 100644 --- a/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/authz_test.go +++ b/pkg/registry/admissionregistration/mutatingadmissionpolicybinding/authz_test.go @@ -37,7 +37,7 @@ func TestAuthorization(t *testing.T) { for _, tc := range []struct { name string userInfo user.Info - auth AuthFunc + auth authorizer.AuthorizerFunc policyGetter PolicyGetterFunc resourceResolver resolver.ResourceResolverFunc expectErrContains string @@ -242,12 +242,6 @@ func TestAuthorization(t *testing.T) { } } -type AuthFunc func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) - -func (f AuthFunc) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - return f(ctx, a) -} - type PolicyGetterFunc func(ctx context.Context, name string) (*admissionregistration.MutatingAdmissionPolicy, error) func (f PolicyGetterFunc) GetMutatingAdmissionPolicy(ctx context.Context, name string) (*admissionregistration.MutatingAdmissionPolicy, error) { diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicy/authz_test.go b/pkg/registry/admissionregistration/validatingadmissionpolicy/authz_test.go index 8b2e56a7baa..8eeabd4e555 100644 --- a/pkg/registry/admissionregistration/validatingadmissionpolicy/authz_test.go +++ b/pkg/registry/admissionregistration/validatingadmissionpolicy/authz_test.go @@ -34,7 +34,7 @@ func TestAuthorization(t *testing.T) { name string userInfo user.Info obj *admissionregistration.ValidatingAdmissionPolicy - auth AuthFunc + auth authorizer.AuthorizerFunc resourceResolver resolver.ResourceResolverFunc expectErr bool }{ @@ -122,9 +122,3 @@ func TestAuthorization(t *testing.T) { }) } } - -type AuthFunc func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) - -func (f AuthFunc) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - return f(ctx, a) -} diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/authz_test.go b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/authz_test.go index 554374f0e40..a6882b0bc33 100644 --- a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/authz_test.go +++ b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/authz_test.go @@ -37,7 +37,7 @@ func TestAuthorization(t *testing.T) { for _, tc := range []struct { name string userInfo user.Info - auth AuthFunc + auth authorizer.AuthorizerFunc policyGetter PolicyGetterFunc resourceResolver resolver.ResourceResolverFunc expectErrContains string @@ -220,12 +220,6 @@ func TestAuthorization(t *testing.T) { } } -type AuthFunc func(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) - -func (f AuthFunc) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - return f(ctx, a) -} - type PolicyGetterFunc func(ctx context.Context, name string) (*admissionregistration.ValidatingAdmissionPolicy, error) func (f PolicyGetterFunc) GetValidatingAdmissionPolicy(ctx context.Context, name string) (*admissionregistration.ValidatingAdmissionPolicy, error) { diff --git a/plugin/pkg/auth/authorizer/node/node_authorizer.go b/plugin/pkg/auth/authorizer/node/node_authorizer.go index d4fcb85df06..fb716bbeef9 100644 --- a/plugin/pkg/auth/authorizer/node/node_authorizer.go +++ b/plugin/pkg/auth/authorizer/node/node_authorizer.go @@ -106,6 +106,16 @@ func (r *NodeAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace return nil, nil, false, nil } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (r *NodeAuthorizer) ConditionsAwareAuthorize(ctx context.Context, attrs authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(r.Authorize(ctx, attrs)) +} + +// EvaluateConditions is not supported by this authorizer. +func (*NodeAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + func (r *NodeAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attributes) (authorizer.Decision, string, error) { nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser()) if !isNode { diff --git a/plugin/pkg/auth/authorizer/rbac/rbac.go b/plugin/pkg/auth/authorizer/rbac/rbac.go index 1d40af6180d..64d0127a3b4 100644 --- a/plugin/pkg/auth/authorizer/rbac/rbac.go +++ b/plugin/pkg/auth/authorizer/rbac/rbac.go @@ -129,6 +129,16 @@ func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes author return authorizer.DecisionNoOpinion, reason, nil } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (r *RBACAuthorizer) ConditionsAwareAuthorize(ctx context.Context, requestAttributes authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(r.Authorize(ctx, requestAttributes)) +} + +// EvaluateConditions is not supported by this authorizer. +func (*RBACAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + func (r *RBACAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { var ( resourceRules []authorizer.ResourceRuleInfo diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/authorizer/caching_authorizer.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/authorizer/caching_authorizer.go index 52d7a78f313..2294401f257 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/authorizer/caching_authorizer.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/authorizer/caching_authorizer.go @@ -34,7 +34,8 @@ type authzResult struct { err error } -var _ = authorizer.Authorizer(&cachingAuthorizer{}) +// For now, secondary authorization checks in admission only support unconditional authorization +var _ = authorizer.UnconditionalAuthorizer(&cachingAuthorizer{}) type cachingAuthorizer struct { authorizer authorizer.UnconditionalAuthorizer diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go index a7e1148365d..2605db81b5c 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go @@ -36,6 +36,16 @@ func (alwaysAllowAuthorizer) Authorize(ctx context.Context, a authorizer.Attribu return authorizer.DecisionAllow, "", nil } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (a alwaysAllowAuthorizer) ConditionsAwareAuthorize(ctx context.Context, attrs authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(a.Authorize(ctx, attrs)) +} + +// EvaluateConditions is not supported by this authorizer. +func (alwaysAllowAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + func (alwaysAllowAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { return []authorizer.ResourceRuleInfo{ &authorizer.DefaultResourceRuleInfo{ @@ -67,6 +77,16 @@ func (alwaysDenyAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut return authorizer.DecisionNoOpinion, "Everything is forbidden.", nil } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (d alwaysDenyAuthorizer) ConditionsAwareAuthorize(ctx context.Context, attrs authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(d.Authorize(ctx, attrs)) +} + +// EvaluateConditions is not supported by this authorizer. +func (alwaysDenyAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + func (alwaysDenyAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, nil } @@ -81,6 +101,16 @@ type privilegedGroupAuthorizer struct { groups []string } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (r *privilegedGroupAuthorizer) ConditionsAwareAuthorize(ctx context.Context, attr authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(r.Authorize(ctx, attr)) +} + +// EvaluateConditions is not supported by this authorizer. +func (r *privilegedGroupAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + 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.") diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/delegating.go b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/delegating.go index 2ca7a659ae2..483aedd4238 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/delegating.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/delegating.go @@ -48,7 +48,7 @@ type DelegatingAuthorizerConfig struct { WebhookRetryBackoff *wait.Backoff } -func (c DelegatingAuthorizerConfig) New() (authorizer.UnconditionalAuthorizer, error) { +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") } diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/authorization/metrics/metrics.go index 5664738933a..904a94d497b 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/metrics/metrics.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/metrics/metrics.go @@ -59,7 +59,7 @@ func RecordAuthorizationDecision(authorizerType, authorizerName, decision string authorizationDecisionsTotal.WithLabelValues(authorizerType, authorizerName, decision).Inc() } -func InstrumentedAuthorizer(authorizerType string, authorizerName string, delegate authorizer.UnconditionalAuthorizer) authorizer.UnconditionalAuthorizer { +func InstrumentedAuthorizer(authorizerType string, authorizerName string, delegate authorizer.Authorizer) authorizer.Authorizer { RegisterMetrics() return &instrumentedAuthorizer{ authorizerType: string(authorizerType), @@ -73,7 +73,7 @@ var _ = authorizer.Authorizer(&instrumentedAuthorizer{}) type instrumentedAuthorizer struct { authorizerType string authorizerName string - delegate authorizer.UnconditionalAuthorizer + delegate authorizer.Authorizer } func (a *instrumentedAuthorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) { @@ -92,3 +92,42 @@ func (a *instrumentedAuthorizer) Authorize(ctx context.Context, attributes autho } return decision, reason, err } + +// ConditionsAwareAuthorize delegates to the wrapped authorizer. +func (a *instrumentedAuthorizer) ConditionsAwareAuthorize(ctx context.Context, attributes authorizer.Attributes) authorizer.ConditionsAwareDecision { + decision := a.delegate.ConditionsAwareAuthorize(ctx, attributes) + switch { + case decision.IsNoOpinion(): + // non-terminal, not reported + case decision.IsAllowed(): + // matches SubjectAccessReview status.allowed field name + RecordAuthorizationDecision(a.authorizerType, a.authorizerName, "allowed") + case decision.IsDenied(): + // matches SubjectAccessReview status.denied field name + RecordAuthorizationDecision(a.authorizerType, a.authorizerName, "denied") + default: + // the ConditionsAwareDecision enforces that there are no other possible states + // than Allow/Deny/NoOpinion/ConditionsMap/Union. The latter two are conditional + // decisions. + RecordAuthorizationDecision(a.authorizerType, a.authorizerName, "conditional") + } + return decision +} + +// EvaluateConditions delegates to the wrapped authorizer, and registers the metric just like Authorize. +func (a *instrumentedAuthorizer) EvaluateConditions(ctx context.Context, unevaluatedDecision authorizer.ConditionsAwareDecision, data authorizer.ConditionsData) (authorizer.Decision, string, error) { + decision, reason, err := a.delegate.EvaluateConditions(ctx, unevaluatedDecision, data) + switch decision { + case authorizer.DecisionNoOpinion: + // non-terminal, not reported + case authorizer.DecisionAllow: + // matches SubjectAccessReview status.allowed field name + RecordAuthorizationDecision(a.authorizerType, a.authorizerName, "allowed") + case authorizer.DecisionDeny: + // matches SubjectAccessReview status.denied field name + RecordAuthorizationDecision(a.authorizerType, a.authorizerName, "denied") + default: + RecordAuthorizationDecision(a.authorizerType, a.authorizerName, "unknown") + } + return decision, reason, err +} diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/metrics/metrics_test.go b/staging/src/k8s.io/apiserver/pkg/authorization/metrics/metrics_test.go index a7a7dd6bc97..8afe8310285 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/metrics/metrics_test.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/metrics/metrics_test.go @@ -38,7 +38,9 @@ func TestRecordAuthorizationDecisionsTotal(t *testing.T) { RegisterMetrics() dummyAuthorizer := &dummyAuthorizer{} + dummyConditionalAuthorizer := &dummyConditionalAuthorizer{} a := InstrumentedAuthorizer("mytype", "myname", dummyAuthorizer) + ac := InstrumentedAuthorizer("myconditionaltype", "myconditionalname", dummyConditionalAuthorizer) // allow { @@ -53,6 +55,19 @@ func TestRecordAuthorizationDecisionsTotal(t *testing.T) { authorizationDecisionsTotal.Reset() } + // allow (conditional authorizer) + { + dummyConditionalAuthorizer.authorizeDecision = authorizer.ConditionsAwareDecisionAllow("", nil) + _, _, _ = ac.Authorize(context.Background(), nil) + expectedValue := prefix + ` + apiserver_authorization_decisions_total{decision="allowed",name="myconditionalname",type="myconditionaltype"} 1 + ` + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedValue), metrics...); err != nil { + t.Fatal(err) + } + authorizationDecisionsTotal.Reset() + } + // deny { dummyAuthorizer.decision = authorizer.DecisionDeny @@ -67,6 +82,20 @@ func TestRecordAuthorizationDecisionsTotal(t *testing.T) { authorizationDecisionsTotal.Reset() } + // deny (conditional authorizer) + { + dummyConditionalAuthorizer.authorizeDecision = authorizer.ConditionsAwareDecisionDeny("", nil) + _, _, _ = ac.Authorize(context.Background(), nil) + _, _, _ = ac.Authorize(context.Background(), nil) + expectedValue := prefix + ` + apiserver_authorization_decisions_total{decision="denied",name="myconditionalname",type="myconditionaltype"} 2 + ` + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedValue), metrics...); err != nil { + t.Fatal(err) + } + authorizationDecisionsTotal.Reset() + } + // no-opinion emits no metric { dummyAuthorizer.decision = authorizer.DecisionNoOpinion @@ -80,6 +109,19 @@ func TestRecordAuthorizationDecisionsTotal(t *testing.T) { authorizationDecisionsTotal.Reset() } + // no-opinion emits no metric (conditional authorizer) + { + dummyConditionalAuthorizer.authorizeDecision = authorizer.ConditionsAwareDecisionNoOpinion("", nil) + _, _, _ = ac.Authorize(context.Background(), nil) + _, _, _ = ac.Authorize(context.Background(), nil) + expectedValue := prefix + ` + ` + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedValue), metrics...); err != nil { + t.Fatal(err) + } + authorizationDecisionsTotal.Reset() + } + // unknown decision emits a metric { dummyAuthorizer.decision = authorizer.DecisionDeny + 10 @@ -92,7 +134,7 @@ func TestRecordAuthorizationDecisionsTotal(t *testing.T) { } authorizationDecisionsTotal.Reset() } - + // TODO(luxas): Add a test for getting a conditional decision from ConditionsAwareAuthorize, and evaluating a condition, once introduced } type dummyAuthorizer struct { @@ -103,3 +145,31 @@ type dummyAuthorizer struct { func (d *dummyAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attributes) (authorizer.Decision, string, error) { return d.decision, "", d.err } + +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (d *dummyAuthorizer) ConditionsAwareAuthorize(ctx context.Context, attrs authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(d.Authorize(ctx, attrs)) +} + +// EvaluateConditions is not supported by this authorizer. +func (*dummyAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + +type dummyConditionalAuthorizer struct { + authorizeDecision authorizer.ConditionsAwareDecision + evalDecision authorizer.Decision + evalErr error +} + +func (d *dummyConditionalAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attributes) (authorizer.Decision, string, error) { + return d.ConditionsAwareAuthorize(ctx, attrs).UnconditionalParts() +} + +func (d *dummyConditionalAuthorizer) ConditionsAwareAuthorize(ctx context.Context, attrs authorizer.Attributes) authorizer.ConditionsAwareDecision { + return d.authorizeDecision +} + +func (d *dummyConditionalAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return d.evalDecision, "", d.evalErr +} diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/path/path.go b/staging/src/k8s.io/apiserver/pkg/authorization/path/path.go index 13dae9ff52a..0e1ec233873 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/path/path.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/path/path.go @@ -27,7 +27,7 @@ import ( // 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.UnconditionalAuthorizer, error) { +func NewAuthorizer(alwaysAllowPaths []string) (authorizer.Authorizer, error) { var prefixes []string paths := sets.NewString() for _, p := range alwaysAllowPaths { diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go b/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go index 9486096b8b7..c048f1fb6e2 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go @@ -36,10 +36,10 @@ import ( var _ = authorizer.Authorizer(unionAuthzHandler{}) // unionAuthzHandler authorizer against a chain of authorizer.Authorizer -type unionAuthzHandler []authorizer.UnconditionalAuthorizer +type unionAuthzHandler []authorizer.Authorizer // New returns an authorizer that authorizes against a chain of authorizer.Authorizer objects -func New(authorizationHandlers ...authorizer.UnconditionalAuthorizer) authorizer.UnconditionalAuthorizer { +func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer { return unionAuthzHandler(authorizationHandlers) } @@ -70,6 +70,16 @@ func (authzHandler unionAuthzHandler) Authorize(ctx context.Context, a authorize return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist) } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (authzHandler unionAuthzHandler) ConditionsAwareAuthorize(ctx context.Context, a authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(authzHandler.Authorize(ctx, a)) +} + +// EvaluateConditions is not supported by this authorizer. +func (unionAuthzHandler) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + // unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver type unionAuthzRulesHandler []authorizer.RuleResolver diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go b/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go index 3b0313bd1b4..fa831472d8b 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go @@ -37,6 +37,16 @@ func (mock *mockAuthzHandler) Authorize(ctx context.Context, a authorizer.Attrib return mock.decision, "", mock.err } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (mock *mockAuthzHandler) ConditionsAwareAuthorize(ctx context.Context, a authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(mock.Authorize(ctx, a)) +} + +// EvaluateConditions is not supported by this authorizer. +func (*mockAuthzHandler) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + func TestAuthorizationSecondPasses(t *testing.T) { handler1 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion} handler2 := &mockAuthzHandler{decision: authorizer.DecisionAllow} @@ -223,15 +233,15 @@ func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.De func TestAuthorizationUnequivocalDeny(t *testing.T) { cs := []struct { - authorizers []authorizer.UnconditionalAuthorizer + authorizers []authorizer.Authorizer decision authorizer.Decision }{ { - authorizers: []authorizer.UnconditionalAuthorizer{}, + authorizers: []authorizer.Authorizer{}, decision: authorizer.DecisionNoOpinion, }, { - authorizers: []authorizer.UnconditionalAuthorizer{ + authorizers: []authorizer.Authorizer{ &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}, &mockAuthzHandler{decision: authorizer.DecisionAllow}, &mockAuthzHandler{decision: authorizer.DecisionDeny}, @@ -239,7 +249,7 @@ func TestAuthorizationUnequivocalDeny(t *testing.T) { decision: authorizer.DecisionAllow, }, { - authorizers: []authorizer.UnconditionalAuthorizer{ + authorizers: []authorizer.Authorizer{ &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}, &mockAuthzHandler{decision: authorizer.DecisionDeny}, &mockAuthzHandler{decision: authorizer.DecisionAllow}, @@ -247,7 +257,7 @@ func TestAuthorizationUnequivocalDeny(t *testing.T) { decision: authorizer.DecisionDeny, }, { - authorizers: []authorizer.UnconditionalAuthorizer{ + authorizers: []authorizer.Authorizer{ &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}, &mockAuthzHandler{decision: authorizer.DecisionDeny, err: errors.New("webhook failed closed")}, &mockAuthzHandler{decision: authorizer.DecisionAllow}, diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation.go index f1293b0ab9f..d652360a583 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation/constrained_impersonation.go @@ -275,7 +275,8 @@ func (t *impersonationModesTracker) getImpersonatedUser(ctx context.Context, wan return nil, errors.New("all impersonation modes failed") } -var _ = authorizer.Authorizer(&metricsAuthorizer{}) +// For now, constrained impersonation only supports unconditional authorization +var _ = authorizer.UnconditionalAuthorizer(&metricsAuthorizer{}) type metricsAuthorizer struct { delegate authorizer.UnconditionalAuthorizer diff --git a/staging/src/k8s.io/apiserver/pkg/server/config.go b/staging/src/k8s.io/apiserver/pkg/server/config.go index 894ce0ac604..f197b55cd1a 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -394,7 +394,7 @@ type AuthenticationInfo struct { type AuthorizationInfo struct { // Authorizer determines whether the subject is allowed to make the request based only // on the RequestURI - Authorizer authorizer.UnconditionalAuthorizer + Authorizer authorizer.Authorizer } func init() { diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go index 7c60c7d8b18..8be1a3a4650 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go @@ -534,6 +534,16 @@ func (authz *mockAuthorizer) Authorize(ctx context.Context, a authorizer.Attribu return authorizer.DecisionAllow, "", nil } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (authz *mockAuthorizer) ConditionsAwareAuthorize(ctx context.Context, a authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(authz.Authorize(ctx, a)) +} + +// EvaluateConditions is not supported by this authorizer. +func (*mockAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + type testGetterStorage struct { Version string } diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/authorization.go b/staging/src/k8s.io/apiserver/pkg/server/options/authorization.go index 191dbbc75bf..9b2dcb3fff7 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/authorization.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/authorization.go @@ -174,8 +174,8 @@ func (s *DelegatingAuthorizationOptions) ApplyTo(c *server.AuthorizationInfo) er return err } -func (s *DelegatingAuthorizationOptions) toAuthorizer(client kubernetes.Interface) (authorizer.UnconditionalAuthorizer, error) { - var authorizers []authorizer.UnconditionalAuthorizer +func (s *DelegatingAuthorizationOptions) toAuthorizer(client kubernetes.Interface) (authorizer.Authorizer, error) { + var authorizers []authorizer.Authorizer if len(s.AlwaysAllowGroups) > 0 { authorizers = append(authorizers, authorizerfactory.NewPrivilegedGroups(s.AlwaysAllowGroups...)) diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go b/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go index 98dbff452a5..ae1de7dbf2f 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go @@ -293,6 +293,16 @@ func (w *WebhookAuthorizer) Authorize(ctx context.Context, attr authorizer.Attri } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (w *WebhookAuthorizer) ConditionsAwareAuthorize(ctx context.Context, a authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(w.Authorize(ctx, a)) +} + +// EvaluateConditions is not supported by this authorizer. +func (*WebhookAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + func resourceAttributesFrom(attr authorizer.Attributes) *authorizationv1.ResourceAttributes { ret := &authorizationv1.ResourceAttributes{ Namespace: attr.GetNamespace(), diff --git a/test/integration/apiserver/flowcontrol/concurrency_util_test.go b/test/integration/apiserver/flowcontrol/concurrency_util_test.go index a69d64e7061..7942e1bf632 100644 --- a/test/integration/apiserver/flowcontrol/concurrency_util_test.go +++ b/test/integration/apiserver/flowcontrol/concurrency_util_test.go @@ -132,6 +132,16 @@ func (d *noxuDelayingAuthorizer) Authorize(ctx context.Context, a authorizer.Att return d.Authorizer.Authorize(ctx, a) } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (d *noxuDelayingAuthorizer) ConditionsAwareAuthorize(ctx context.Context, a authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(d.Authorize(ctx, a)) +} + +// EvaluateConditions is not supported by this authorizer. +func (*noxuDelayingAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + // TestConcurrencyIsolation tests the concurrency isolation between priority levels. // The test defines two priority levels for this purpose, and corresponding flow schemas. // To one priority level, this test sends many more concurrent requests than the configuration diff --git a/test/integration/auth/accessreview_test.go b/test/integration/auth/accessreview_test.go index 0e872c63c96..ab119943bfc 100644 --- a/test/integration/auth/accessreview_test.go +++ b/test/integration/auth/accessreview_test.go @@ -47,6 +47,16 @@ func (sarAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (au return authorizer.DecisionAllow, "you're not dave", nil } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (s sarAuthorizer) ConditionsAwareAuthorize(ctx context.Context, a authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(s.Authorize(ctx, a)) +} + +// EvaluateConditions is not supported by this authorizer. +func (sarAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + func alwaysAlice(req *http.Request) (*authenticator.Response, bool, error) { return &authenticator.Response{ User: &user.DefaultInfo{ diff --git a/test/integration/auth/auth_test.go b/test/integration/auth/auth_test.go index de6ffcbf98d..88c45890f43 100644 --- a/test/integration/auth/auth_test.go +++ b/test/integration/auth/auth_test.go @@ -814,6 +814,16 @@ func (impersonateAuthorizer) Authorize(ctx context.Context, a authorizer.Attribu return authorizer.DecisionNoOpinion, "I can't allow that. Go ask alice.", nil } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (i impersonateAuthorizer) ConditionsAwareAuthorize(ctx context.Context, a authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(i.Authorize(ctx, a)) +} + +// EvaluateConditions is not supported by this authorizer. +func (impersonateAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + func TestImpersonateIsForbidden(t *testing.T) { tCtx := ktesting.Init(t) kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{ @@ -1832,6 +1842,16 @@ func (a *trackingAuthorizer) Authorize(ctx context.Context, attributes authorize return authorizer.DecisionAllow, "", nil } +// ConditionsAwareAuthorize is not conditions-aware, converts the Authorize decision. +func (a *trackingAuthorizer) ConditionsAwareAuthorize(ctx context.Context, attributes authorizer.Attributes) authorizer.ConditionsAwareDecision { + return authorizer.ConditionsAwareDecisionFromParts(a.Authorize(ctx, attributes)) +} + +// EvaluateConditions is not supported by this authorizer. +func (a *trackingAuthorizer) EvaluateConditions(_ context.Context, _ authorizer.ConditionsAwareDecision, _ authorizer.ConditionsData) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "", authorizer.ErrorConditionEvaluationNotSupported +} + // TestAuthorizationAttributeDetermination tests that authorization attributes are built correctly func TestAuthorizationAttributeDetermination(t *testing.T) { tCtx := ktesting.Init(t) diff --git a/test/integration/auth/rbac_test.go b/test/integration/auth/rbac_test.go index aef1cd17813..5038aeae597 100644 --- a/test/integration/auth/rbac_test.go +++ b/test/integration/auth/rbac_test.go @@ -95,7 +95,7 @@ func (getter *testRESTOptionsGetter) GetRESTOptions(resource schema.GroupResourc return generic.RESTOptions{StorageConfig: storageConfig, Decorator: generic.UndecoratedStorage, ResourcePrefix: resource.Resource}, nil } -func newRBACAuthorizer(t *testing.T, config *controlplane.Config) (authorizer.UnconditionalAuthorizer, func()) { +func newRBACAuthorizer(t *testing.T, config *controlplane.Config) (authorizer.Authorizer, func()) { optsGetter := &testRESTOptionsGetter{config} roleRest, err := rolestore.NewREST(optsGetter) if err != nil { @@ -566,7 +566,7 @@ func TestRBAC(t *testing.T) { // Append our custom test authenticator config.ControlPlane.Generic.Authentication.Authenticator = unionauthn.New(config.ControlPlane.Generic.Authentication.Authenticator, authenticator) // Append our custom test authorizer - var rbacAuthz authorizer.UnconditionalAuthorizer + var rbacAuthz authorizer.Authorizer rbacAuthz, tearDownAuthorizerFn = newRBACAuthorizer(t, config) config.ControlPlane.Generic.Authorization.Authorizer = unionauthz.New(config.ControlPlane.Generic.Authorization.Authorizer, rbacAuthz) }, @@ -972,7 +972,7 @@ func TestRBACContextContamination(t *testing.T) { tearDownAuthorizerFn() } }() - var rbacAuthz authorizer.UnconditionalAuthorizer + var rbacAuthz authorizer.Authorizer _, kubeConfig, tearDownFn := framework.StartTestServer(context.Background(), t, framework.TestServerSetup{ ModifyServerRunOptions: func(opts *options.ServerRunOptions) { // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.