从源码看AuthorizeNodeWithSelectors特性

简单介绍

旧的nodeAuthorizer不限制node对node/pod资源的list/watch/get操作,也就是可以拿到所有资源
开启AuthorizeNodeWithSelectors特性后限制node只能获取自身node以及属于自己node的pod

noderestriction限制create/delete等修改操作
AuthorizeNodeWithSelectors的nodeAuthorizer限制get/list/watch等读操作

源码

nodeAuthorize

pkg/kubeapiserver/authorizer/config.go中

创建authorizer
func (config Config) New(ctx context.Context, serverID string) (authorizer.Authorizer, authorizer.RuleResolver, error) {
    ...
    创建node authorizer
    r.nodeAuthorizer = node.NewAuthorizer(graph, nodeidentifier.NewDefaultNodeIdentifier(), bootstrappolicy.NodeRules())
    ...
}

plugin/pkg/auth/authorizer/node/node_authorizer.go中

创建node authorizer
func NewAuthorizer(graph *Graph, identifier nodeidentifier.NodeIdentifier, rules []rbacv1.PolicyRule) *NodeAuthorizer {
    return &NodeAuthorizer{
        graph:      graph,
        identifier: identifier,
        nodeRules:  rules,
        features:   utilfeature.DefaultFeatureGate,
    }
}

权限校验
func (r *NodeAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
    ...
    判断是否是资源请求
    if attrs.IsResourceRequest() {
        requestResource := schema.GroupResource{Group: attrs.GetAPIGroup(), Resource: attrs.GetResource()}
        switch requestResource {
    ...
    如果是node/pod资源请求且开启了AuthorizeNodeWithSelectors特性则执行新的权限校验方法
        case nodeResource:
            if r.features.Enabled(features.AuthorizeNodeWithSelectors) {
                return r.authorizeNode(nodeName, attrs)
            }
        case podResource:
            if r.features.Enabled(features.AuthorizeNodeWithSelectors) {
                return r.authorizePod(nodeName, attrs)
            }
        }
    ...
    非资源请求或者未开启AuthorizeNodeWithSelectors特性则执行旧的权限校验方法
    if rbac.RulesAllow(attrs, r.nodeRules...) {
        return authorizer.DecisionAllow, "", nil
    }
    return authorizer.DecisionNoOpinion, "", nil
}


node权限校验
func (r *NodeAuthorizer) authorizeNode(nodeName string, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
    ...
    switch attrs.GetSubresource() {
        create/update/patch则允许
        case "create", "update", "patch":
            return authorizer.DecisionAllow, "", nil
        case "get", "list", "watch":
            如果目标node是自身则允许
            switch attrs.GetName() {
            case nodeName:
                return authorizer.DecisionAllow, "", nil
}


pod权限校验
func (r *NodeAuthorizer) authorizePod(nodeName string, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
    ...
    switch attrs.GetSubresource() {
    case "":
        switch attrs.GetVerb() {
        case "get":
            return r.authorizeGet(nodeName, podVertexType, attrs)
        case "list", "watch":
            运行目标spec.nodeName为自身的pod
            reqs, _ := attrs.GetFieldSelector()
            for _, req := range reqs {
                if req.Field == "spec.nodeName" && req.Operator == selection.Equals && req.Value == nodeName {
                    return authorizer.DecisionAllow, "", nil
                }
            }
            检查目标pod是否属于当前自身node
            if attrs.GetName() != "" {
                return r.authorize(nodeName, podVertexType, attrs)
            }
    ...
}

plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go


var(
    ...
    读请求
    Read       = []string{"get", "list", "watch"}
    ...
)
策略规则
func NodeRules() []rbacv1.PolicyRule {
    nodePolicyRules := []rbacv1.PolicyRule{
        ...
        可以看到没有判断访问的node是否是自身node
        rbacv1helpers.NewRule("create", "get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
        ...
        可以看到没有判断访问的pod是否是属于自身node
        rbacv1helpers.NewRule(Read...).Groups(legacyGroup).Resources("pods").RuleOrDie(),
        ...
    }
    return nodePolicyRules
}

noderestriction

plugin/pkg/admission/noderestriction/admission.go中

准入控制
func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
    ...
    判断请求的资源
    switch a.GetResource().GroupResource() {
    case podResource:
        switch a.GetSubresource() {
        case "":
        检查是否可以操作pod
            return p.admitPod(nodeName, a)
    ...
    case nodeResource:
    检查是否可以操作node
        return p.admitNode(nodeName, a)
    ...
}


pod准入检查
func (p *Plugin) admitPod(nodeName string, a admission.Attributes) error {
    switch a.GetOperation() {
    case admission.Create:
        pod创建准入检查
        return p.admitPodCreate(nodeName, a)

    case admission.Delete:
        待删除的pod是否是自身node
        existingPod, err := p.podsGetter.Pods(a.GetNamespace()).Get(a.GetName())
        if apierrors.IsNotFound(err) {
            return err
        }
        if err != nil {
            return admission.NewForbidden(a, err)
        }
        if existingPod.Spec.NodeName != nodeName {
            return admission.NewForbidden(a, fmt.Errorf("node %q can only delete pods with spec.nodeName set to itself", nodeName))
        }
        return nil
}


pod创建准入检查
func (p *Plugin) admitPodCreate(nodeName string, a admission.Attributes) error {
    ...
    pod所属node为自身node
    if pod.Spec.NodeName != nodeName {
        return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with spec.nodeName set to itself", nodeName))
    }
    ...
    pod的OwnerReferences为自身node
    if len(pod.OwnerReferences) == 1 {
        owner := pod.OwnerReferences[0]
        if owner.APIVersion != v1.SchemeGroupVersion.String() ||
            owner.Kind != "Node" ||
            owner.Name != nodeName {
            return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with an owner reference set to itself", nodeName))
        }
        if owner.Controller == nil || !*owner.Controller {
            return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with a controller owner reference set to itself", nodeName))
        }
        if owner.BlockOwnerDeletion != nil && *owner.BlockOwnerDeletion {
            return admission.NewForbidden(a, fmt.Errorf("node %q must not set blockOwnerDeletion on an owner reference", nodeName))
        }

        // Verify the node UID.
        node, err := p.nodesGetter.Get(nodeName)
        if apierrors.IsNotFound(err) {
            return err
        }
        if err != nil {
            return admission.NewForbidden(a, fmt.Errorf("error looking up node %s to verify uid: %v", nodeName, err))
        }
        if owner.UID != node.UID {
            return admission.NewForbidden(a, fmt.Errorf("node %s UID mismatch: expected %s got %s", nodeName, owner.UID, node.UID))
        }
    }
    ...
}


node准入检查
func (p *Plugin) admitNode(nodeName string, a admission.Attributes) error {
    ...
    目标node是否是自身node
    if requestedName != nodeName {
        return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to modify node %q", nodeName, requestedName))
    }
    ...
}

http handler

authorize

staging/src/k8s.io/apiserver/pkg/server/config.go

构建http handler chain
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
    ...
    添加authorization middleware
    handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
    ...
}

staging/src/k8s.io/apiserver/pkg/endpoints/filters/authorization.go中

添加authorization middleware
func WithAuthorization(hhandler http.Handler, auth authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
    return withAuthorization(hhandler, auth, s, recordAuthorizationMetrics)
}

添加authorization middleware
func withAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer, metrics recordAuthorizationMetricsFunc) http.Handler {
    ...
    权限校验
    authorized, reason, err := a.Authorize(ctx, attributes)
    ...
    allow则进入下一级处理
    if authorized == authorizer.DecisionAllow {
        ...
        handler.ServeHTTP(w, req)
        return
    }
    报错则返回500
    if err != nil {
        ...
        responsewriters.InternalError(w, req, err)
        return
    }
    ...
    非allow(DecisionNoOpinion,DecisionDeny)则返回403
    responsewriters.Forbidden(ctx, attributes, w, req, reason, s)
}
添加伪装身份检查的middleware
func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
    ...
    权限校验
    decision, reason, err := a.Authorize(ctx, actingAsAttributes)
    报错或者非allow(DecisionNoOpinion,DecisionDeny)则返回403
    if err != nil || decision != authorizer.DecisionAllow {
        ...
        responsewriters.Forbidden(ctx, actingAsAttributes, w, req, reason, s)
        return
    }
    ...
}

admit

注册资源处理方法
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws restful.WebService) (metav1.APIResource, *storageversion.ResourceInfo, error) {
...
var handler restful.RouteFunction
注册资源创建处理方法
if isNamedCreater {
handler = restfulCreateNamedResource(namedCreater, reqScope, admit)
} else {
handler = restfulCreateResource(creater, reqScope, admit)
}
...
}


staging/src/k8s.io/apiserver/pkg/endpoints/installer.go中

rest创建资源处理方法
func restfulCreateResource(r rest.Creater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
handlers.CreateResource(r, &scope, admit)(res.ResponseWriter, req.Request)
}
}


staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go中

创建方法
func CreateResource(r rest.Creater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
}

创建方法
func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
...
构建请求方法
requestFunc := func() (runtime.Object, error) {
return r.Create(
ctx,
name,
obj,
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
options,
)
}
...
执行请求方法
result, err := requestFunc()
...
}


staging/src/k8s.io/apiserver/pkg/registry/rest/create.go中

admission校验
func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes admission.Attributes, o admission.ObjectInterfaces) ValidateObjectFunc {
...
finalAttributes := admission.NewAttributesRecord(
obj,
staticAttributes.GetOldObject(),
staticAttributes.GetKind(),
staticAttributes.GetNamespace(),
name,
staticAttributes.GetResource(),
staticAttributes.GetSubresource(),
staticAttributes.GetOperation(),
staticAttributes.GetOperationOptions(),
staticAttributes.IsDryRun(),
staticAttributes.GetUserInfo(),
)
if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
return nil
}
return validatingAdmission.Validate(ctx, finalAttributes, o)
...

}

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容