diff --git a/pkg/agent/flannel/setup.go b/pkg/agent/flannel/setup.go index a487bfe83b1a..e25b45f5e5f7 100644 --- a/pkg/agent/flannel/setup.go +++ b/pkg/agent/flannel/setup.go @@ -69,14 +69,21 @@ func Prepare(ctx context.Context, nodeConfig *config.Node) error { func Run(ctx context.Context, nodeConfig *config.Node) error { logrus.Infof("Starting flannel with backend %s", nodeConfig.FlannelBackend) - if err := util.WaitForRBACReady(ctx, nodeConfig.AgentConfig.KubeConfigK3sController, util.DefaultAPIServerReadyTimeout, authorizationv1.ResourceAttributes{ - Verb: "list", - Resource: "nodes", - }, ""); err != nil { - return errors.Wrap(err, "flannel failed to wait for RBAC") + kubeConfig := nodeConfig.AgentConfig.KubeConfigKubelet + resourceAttrs := authorizationv1.ResourceAttributes{Verb: "list", Resource: "nodes"} + + // Compatibility code for AuthorizeNodeWithSelectors feature-gate. + // If the kubelet cannot list nodes, then wait for the k3s-controller RBAC to become ready, and use that kubeconfig instead. + if canListNodes, err := util.CheckRBAC(ctx, kubeConfig, resourceAttrs, ""); err != nil { + return errors.Wrap(err, "failed to check if RBAC allows node list") + } else if !canListNodes { + kubeConfig = nodeConfig.AgentConfig.KubeConfigK3sController + if err := util.WaitForRBACReady(ctx, kubeConfig, util.DefaultAPIServerReadyTimeout, resourceAttrs, ""); err != nil { + return errors.Wrap(err, "flannel failed to wait for RBAC") + } } - coreClient, err := util.GetClientSet(nodeConfig.AgentConfig.KubeConfigK3sController) + coreClient, err := util.GetClientSet(kubeConfig) if err != nil { return err } @@ -90,7 +97,7 @@ func Run(ctx context.Context, nodeConfig *config.Node) error { return errors.Wrap(err, "failed to check netMode for flannel") } go func() { - err := flannel(ctx, nodeConfig.FlannelIface, nodeConfig.FlannelConfFile, nodeConfig.AgentConfig.KubeConfigK3sController, nodeConfig.FlannelIPv6Masq, netMode) + err := flannel(ctx, nodeConfig.FlannelIface, nodeConfig.FlannelConfFile, kubeConfig, nodeConfig.FlannelIPv6Masq, netMode) if err != nil && !errors.Is(err, context.Canceled) { logrus.Errorf("flannel exited: %v", err) os.Exit(1) diff --git a/pkg/util/api.go b/pkg/util/api.go index 4df9ad73a945..3590cdd98089 100644 --- a/pkg/util/api.go +++ b/pkg/util/api.go @@ -147,6 +147,34 @@ func WaitForRBACReady(ctx context.Context, kubeconfigPath string, timeout time.D return nil } +// CheckRBAC performs a single SelfSubjectAccessReview or SubjectAccessReview, returning a +// boolean indicating whether or not the requested access would be allowed. This is basically +// `kubectl auth can-i`. +func CheckRBAC(ctx context.Context, kubeconfigPath string, ra authorizationv1.ResourceAttributes, user string, groups ...string) (bool, error) { + restConfig, err := GetRESTConfig(kubeconfigPath) + if err != nil { + return false, err + } + authClient, err := authorizationv1client.NewForConfig(restConfig) + if err != nil { + return false, err + } + + var reviewFunc genericAccessReviewRequest + if len(user) == 0 && len(groups) == 0 { + reviewFunc = selfSubjectAccessReview(authClient, ra) + } else { + reviewFunc = subjectAccessReview(authClient, ra, user, groups) + } + + status, err := reviewFunc(ctx) + if err != nil { + return false, err + } + + return status.Allowed, nil +} + // selfSubjectAccessReview returns a function that makes SelfSubjectAccessReview requests using the // provided client and attributes, returning a status or error. func selfSubjectAccessReview(authClient *authorizationv1client.AuthorizationV1Client, ra authorizationv1.ResourceAttributes) genericAccessReviewRequest {