mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-14 05:36:12 +00:00
Include empty string attributes for CEL authz evaluation
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
"github.com/google/cel-go/common/types/ref"
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
|
||||||
|
authorizationv1 "k8s.io/api/authorization/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/version"
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
"k8s.io/apiserver/pkg/cel/environment"
|
"k8s.io/apiserver/pkg/cel/environment"
|
||||||
@@ -143,6 +144,7 @@ func mustBuildEnv(baseEnv *environment.EnvSet) *environment.EnvSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildRequestType generates a DeclType for SubjectAccessReviewSpec.
|
// 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 {
|
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)
|
resourceAttributesType := buildResourceAttributesType(field, fields)
|
||||||
nonResourceAttributesType := buildNonResourceAttributesType(field, fields)
|
nonResourceAttributesType := buildNonResourceAttributesType(field, fields)
|
||||||
@@ -157,6 +159,7 @@ func buildRequestType(field func(name string, declType *apiservercel.DeclType, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildResourceAttributesType generates a DeclType for ResourceAttributes.
|
// 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 {
|
func buildResourceAttributesType(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.ResourceAttributes", fields(
|
return apiservercel.NewObjectType("kubernetes.ResourceAttributes", fields(
|
||||||
field("namespace", apiservercel.StringType, false),
|
field("namespace", apiservercel.StringType, false),
|
||||||
@@ -170,9 +173,42 @@ func buildResourceAttributesType(field func(name string, declType *apiservercel.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildNonResourceAttributesType generates a DeclType for NonResourceAttributes.
|
// 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 {
|
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(
|
return apiservercel.NewObjectType("kubernetes.NonResourceAttributes", fields(
|
||||||
field("path", apiservercel.StringType, false),
|
field("path", apiservercel.StringType, false),
|
||||||
field("verb", apiservercel.StringType, false),
|
field("verb", apiservercel.StringType, false),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertObjectToUnstructured(obj *authorizationv1.SubjectAccessReviewSpec) 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 {
|
||||||
|
ret["resourceAttributes"] = map[string]string{
|
||||||
|
"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 obj.NonResourceAttributes != nil {
|
||||||
|
ret["nonResourceAttributes"] = map[string]string{
|
||||||
|
"verb": obj.NonResourceAttributes.Verb,
|
||||||
|
"path": obj.NonResourceAttributes.Path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
@@ -21,8 +21,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
celgo "github.com/google/cel-go/cel"
|
celgo "github.com/google/cel-go/cel"
|
||||||
|
|
||||||
authorizationv1 "k8s.io/api/authorization/v1"
|
authorizationv1 "k8s.io/api/authorization/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,12 +33,8 @@ type CELMatcher struct {
|
|||||||
// eval evaluates the given SubjectAccessReview against all cel matchCondition expression
|
// eval evaluates the given SubjectAccessReview against all cel matchCondition expression
|
||||||
func (c *CELMatcher) Eval(ctx context.Context, r *authorizationv1.SubjectAccessReview) (bool, error) {
|
func (c *CELMatcher) Eval(ctx context.Context, r *authorizationv1.SubjectAccessReview) (bool, error) {
|
||||||
var evalErrors []error
|
var evalErrors []error
|
||||||
specValObject, err := convertObjectToUnstructured(&r.Spec)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("authz celMatcher eval error: convert SubjectAccessReviewSpec object to unstructured failed: %w", err)
|
|
||||||
}
|
|
||||||
va := map[string]interface{}{
|
va := map[string]interface{}{
|
||||||
"request": specValObject,
|
"request": convertObjectToUnstructured(&r.Spec),
|
||||||
}
|
}
|
||||||
for _, compilationResult := range c.CompilationResults {
|
for _, compilationResult := range c.CompilationResults {
|
||||||
evalResult, _, err := compilationResult.Program.ContextEval(ctx, va)
|
evalResult, _, err := compilationResult.Program.ContextEval(ctx, va)
|
||||||
@@ -68,14 +64,3 @@ func (c *CELMatcher) Eval(ctx context.Context, r *authorizationv1.SubjectAccessR
|
|||||||
// return ALL matchConditions evaluate to TRUE successfully without error
|
// return ALL matchConditions evaluate to TRUE successfully without error
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertObjectToUnstructured(obj *authorizationv1.SubjectAccessReviewSpec) (map[string]interface{}, error) {
|
|
||||||
if obj == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
ret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
@@ -704,6 +704,7 @@ func TestStructuredAuthzConfigFeatureEnablement(t *testing.T) {
|
|||||||
Name: "alice",
|
Name: "alice",
|
||||||
UID: "1",
|
UID: "1",
|
||||||
Groups: []string{"group1", "group2"},
|
Groups: []string{"group1", "group2"},
|
||||||
|
Extra: map[string][]string{"key1": {"a", "b", "c"}},
|
||||||
},
|
},
|
||||||
ResourceRequest: true,
|
ResourceRequest: true,
|
||||||
Namespace: "kittensandponies",
|
Namespace: "kittensandponies",
|
||||||
@@ -798,6 +799,7 @@ func TestV1WebhookMatchConditions(t *testing.T) {
|
|||||||
Name: "alice",
|
Name: "alice",
|
||||||
UID: "1",
|
UID: "1",
|
||||||
Groups: []string{"group1", "group2"},
|
Groups: []string{"group1", "group2"},
|
||||||
|
Extra: map[string][]string{"key1": {"a", "b", "c"}},
|
||||||
},
|
},
|
||||||
ResourceRequest: true,
|
ResourceRequest: true,
|
||||||
Namespace: "kittensandponies",
|
Namespace: "kittensandponies",
|
||||||
@@ -848,6 +850,18 @@ func TestV1WebhookMatchConditions(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Expression: "('group1' in request.groups)",
|
Expression: "('group1' in request.groups)",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Expression: "('key1' in request.extra)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Expression: "!('key2' in request.extra)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Expression: "('a' in request.extra['key1'])",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Expression: "!('z' in request.extra['key1'])",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Expression: "has(request.resourceAttributes) && request.resourceAttributes.namespace == 'kittensandponies'",
|
Expression: "has(request.resourceAttributes) && request.resourceAttributes.namespace == 'kittensandponies'",
|
||||||
},
|
},
|
||||||
@@ -1028,13 +1042,10 @@ func TestV1WebhookMatchConditions(t *testing.T) {
|
|||||||
expectedCompileErr: "",
|
expectedCompileErr: "",
|
||||||
// default decisionOnError in newWithBackoff to skip
|
// default decisionOnError in newWithBackoff to skip
|
||||||
expectedDecision: authorizer.DecisionNoOpinion,
|
expectedDecision: authorizer.DecisionNoOpinion,
|
||||||
expectedEvalErr: "[cel evaluation error: expression 'request.user == 'bob'' resulted in error: no such key: user, cel evaluation error: expression 'has(request.nonResourceAttributes) && request.nonResourceAttributes.verb == 'get'' resulted in error: no such key: verb]",
|
expectedEvalErr: "cel evaluation error: expression 'request.resourceAttributes.verb == 'get'' resulted in error: no such key: resourceAttributes",
|
||||||
expressions: []apiserver.WebhookMatchCondition{
|
expressions: []apiserver.WebhookMatchCondition{
|
||||||
{
|
{
|
||||||
Expression: "request.user == 'bob'",
|
Expression: "request.resourceAttributes.verb == 'get'",
|
||||||
},
|
|
||||||
{
|
|
||||||
Expression: "has(request.nonResourceAttributes) && request.nonResourceAttributes.verb == 'get'",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user