從源碼看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中

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

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

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

權(quán)限校驗(yàn)
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特性則執(zhí)行新的權(quán)限校驗(yàn)方法
        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特性則執(zhí)行舊的權(quán)限校驗(yàn)方法
    if rbac.RulesAllow(attrs, r.nodeRules...) {
        return authorizer.DecisionAllow, "", nil
    }
    return authorizer.DecisionNoOpinion, "", nil
}


node權(quán)限校驗(yàn)
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":
            如果目標(biāo)node是自身則允許
            switch attrs.GetName() {
            case nodeName:
                return authorizer.DecisionAllow, "", nil
}


pod權(quán)限校驗(yàn)
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":
            運(yùn)行目標(biāo)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
                }
            }
            檢查目標(biāo)pod是否屬于當(dāng)前自身node
            if attrs.GetName() != "" {
                return r.authorize(nodeName, podVertexType, attrs)
            }
    ...
}

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


var(
    ...
    讀請求
    Read       = []string{"get", "list", "watch"}
    ...
)
策略規(guī)則
func NodeRules() []rbacv1.PolicyRule {
    nodePolicyRules := []rbacv1.PolicyRule{
        ...
        可以看到?jīng)]有判斷訪問的node是否是自身node
        rbacv1helpers.NewRule("create", "get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
        ...
        可以看到?jīng)]有判斷訪問的pod是否是屬于自身node
        rbacv1helpers.NewRule(Read...).Groups(legacyGroup).Resources("pods").RuleOrDie(),
        ...
    }
    return nodePolicyRules
}

noderestriction

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

準(zhǔn)入控制
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準(zhǔn)入檢查
func (p *Plugin) admitPod(nodeName string, a admission.Attributes) error {
    switch a.GetOperation() {
    case admission.Create:
        pod創(chuàng)建準(zhǔn)入檢查
        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創(chuàng)建準(zhǔn)入檢查
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準(zhǔn)入檢查
func (p *Plugin) admitNode(nodeName string, a admission.Attributes) error {
    ...
    目標(biāo)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

構(gòu)建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 {
    ...
    權(quán)限校驗(yàn)
    authorized, reason, err := a.Authorize(ctx, attributes)
    ...
    allow則進(jìn)入下一級(jí)處理
    if authorized == authorizer.DecisionAllow {
        ...
        handler.ServeHTTP(w, req)
        return
    }
    報(bào)錯(cuò)則返回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 {
    ...
    權(quán)限校驗(yàn)
    decision, reason, err := a.Authorize(ctx, actingAsAttributes)
    報(bào)錯(cuò)或者非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
注冊資源創(chuàng)建處理方法
if isNamedCreater {
handler = restfulCreateNamedResource(namedCreater, reqScope, admit)
} else {
handler = restfulCreateResource(creater, reqScope, admit)
}
...
}


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

rest創(chuàng)建資源處理方法
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中

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

創(chuàng)建方法
func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
...
構(gòu)建請求方法
requestFunc := func() (runtime.Object, error) {
return r.Create(
ctx,
name,
obj,
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
options,
)
}
...
執(zhí)行請求方法
result, err := requestFunc()
...
}


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

admission校驗(yàn)
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)
...

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容