mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #40392 from madhusudancs/federation-kubefed-rbac
Automatic merge from submit-queue (batch tested with PRs 40392, 39242, 40579, 40628, 40713) [Federation][kubefed] Create a dedicated service account for federation controller manager in the host cluster and give it appropriate permissions. Ref: Issue #39555 cc @kubernetes/sig-federation-pr-reviews @kubernetes/sig-auth-misc @kubernetes/sig-auth-pr-reviews ```release-note kubefed init creates a service account for federation controller manager in the federation-system namespace and binds that service account to the federation-system:federation-controller-manager role that has read and list access on secrets in the federation-system namespace. ```
This commit is contained in:
commit
ac29a05908
@ -17,6 +17,7 @@ go_library(
|
||||
"//federation/pkg/kubefed/util:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/apis/rbac:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/kubectl/cmd/templates:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
@ -45,6 +46,7 @@ go_test(
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/extensions/v1beta1:go_default_library",
|
||||
"//pkg/apis/rbac/v1beta1:go_default_library",
|
||||
"//pkg/kubectl/cmd/testing:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/equality",
|
||||
|
@ -48,6 +48,7 @@ import (
|
||||
"k8s.io/kubernetes/federation/pkg/kubefed/util"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||
client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
@ -62,6 +63,17 @@ const (
|
||||
AdminCN = "admin"
|
||||
HostClusterLocalDNSZoneName = "cluster.local."
|
||||
|
||||
// User name used by federation controller manager to make
|
||||
// calls to federation API server.
|
||||
ControllerManagerUser = "federation-controller-manager"
|
||||
|
||||
// Name of the ServiceAccount used by the federation controller manager
|
||||
// to access the secrets in the host cluster.
|
||||
ControllerManagerSA = "federation-controller-manager"
|
||||
|
||||
// Group name of the legacy/core API group
|
||||
legacyAPIGroup = ""
|
||||
|
||||
lbAddrRetryInterval = 5 * time.Second
|
||||
podWaitInterval = 2 * time.Second
|
||||
)
|
||||
@ -220,7 +232,22 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman
|
||||
}
|
||||
|
||||
// 7. Create federation controller manager
|
||||
_, err = createControllerManager(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmName, image, cmKubeconfigName, dnsZoneName, dnsProvider, dryRun)
|
||||
// 7a. Create a service account in the host cluster for federation
|
||||
// controller manager.
|
||||
sa, err := createControllerManagerSA(hostClientset, initFlags.FederationSystemNamespace, dryRun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 7b. Create RBAC role and role binding for federation controller
|
||||
// manager service account.
|
||||
_, _, err = createRoleBindings(hostClientset, initFlags.FederationSystemNamespace, sa.Name, dryRun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 7c. Create federation controller manager deployment.
|
||||
_, err = createControllerManager(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmName, image, cmKubeconfigName, dnsZoneName, dnsProvider, sa.Name, dryRun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -375,7 +402,7 @@ func createControllerManagerKubeconfigSecret(clientset *client.Clientset, namesp
|
||||
config := kubeadmkubeconfigphase.MakeClientConfigWithCerts(
|
||||
fmt.Sprintf("https://%s", svcName),
|
||||
name,
|
||||
"federation-controller-manager",
|
||||
ControllerManagerUser,
|
||||
certutil.EncodeCertPEM(entKeyPairs.ca.Cert),
|
||||
certutil.EncodePrivateKeyPEM(entKeyPairs.controllerManager.Key),
|
||||
certutil.EncodeCertPEM(entKeyPairs.controllerManager.Cert),
|
||||
@ -520,7 +547,55 @@ func createAPIServer(clientset *client.Clientset, namespace, name, image, creden
|
||||
return clientset.Extensions().Deployments(namespace).Create(dep)
|
||||
}
|
||||
|
||||
func createControllerManager(clientset *client.Clientset, namespace, name, svcName, cmName, image, kubeconfigName, dnsZoneName, dnsProvider string, dryRun bool) (*extensions.Deployment, error) {
|
||||
func createControllerManagerSA(clientset *client.Clientset, namespace string, dryRun bool) (*api.ServiceAccount, error) {
|
||||
sa := &api.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ControllerManagerSA,
|
||||
Namespace: namespace,
|
||||
Labels: componentLabel,
|
||||
},
|
||||
}
|
||||
if dryRun {
|
||||
return sa, nil
|
||||
}
|
||||
return clientset.Core().ServiceAccounts(namespace).Create(sa)
|
||||
}
|
||||
|
||||
func createRoleBindings(clientset *client.Clientset, namespace, saName string, dryRun bool) (*rbac.Role, *rbac.RoleBinding, error) {
|
||||
roleName := "federation-system:federation-controller-manager"
|
||||
role := &rbac.Role{
|
||||
// a role to use for bootstrapping the federation-controller-manager so it can access
|
||||
// secrets in the host cluster to access other clusters.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: roleName,
|
||||
Namespace: namespace,
|
||||
Labels: componentLabel,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
rbac.NewRule("get", "list", "watch").Groups(legacyAPIGroup).Resources("secrets").RuleOrDie(),
|
||||
},
|
||||
}
|
||||
|
||||
rolebinding, err := rbac.NewRoleBinding(roleName, namespace).SAs(namespace, saName).Binding()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rolebinding.Labels = componentLabel
|
||||
|
||||
if dryRun {
|
||||
return role, &rolebinding, nil
|
||||
}
|
||||
|
||||
newRole, err := clientset.Rbac().Roles(namespace).Create(role)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
newRolebinding, err := clientset.Rbac().RoleBindings(namespace).Create(&rolebinding)
|
||||
return newRole, newRolebinding, err
|
||||
}
|
||||
|
||||
func createControllerManager(clientset *client.Clientset, namespace, name, svcName, cmName, image, kubeconfigName, dnsZoneName, dnsProvider, saName string, dryRun bool) (*extensions.Deployment, error) {
|
||||
dep := &extensions.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cmName,
|
||||
@ -578,6 +653,7 @@ func createControllerManager(clientset *client.Clientset, namespace, name, svcNa
|
||||
},
|
||||
},
|
||||
},
|
||||
ServiceAccountName: saName,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -45,6 +45,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||
rbacv1beta1 "k8s.io/kubernetes/pkg/apis/rbac/v1beta1"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
)
|
||||
@ -555,6 +556,62 @@ func fakeInitHostFactory(federationName, namespaceName, ip, dnsZoneName, image,
|
||||
},
|
||||
}
|
||||
|
||||
sa := v1.ServiceAccount{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ServiceAccount",
|
||||
APIVersion: testapi.Default.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "federation-controller-manager",
|
||||
Namespace: namespaceName,
|
||||
Labels: componentLabel,
|
||||
},
|
||||
}
|
||||
|
||||
role := rbacv1beta1.Role{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Role",
|
||||
APIVersion: testapi.Rbac.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "federation-system:federation-controller-manager",
|
||||
Namespace: namespaceName,
|
||||
Labels: componentLabel,
|
||||
},
|
||||
Rules: []rbacv1beta1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"secrets"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rolebinding := rbacv1beta1.RoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "RoleBinding",
|
||||
APIVersion: testapi.Rbac.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "federation-system:federation-controller-manager",
|
||||
Namespace: namespaceName,
|
||||
Labels: componentLabel,
|
||||
},
|
||||
Subjects: []rbacv1beta1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
APIVersion: "",
|
||||
Name: "federation-controller-manager",
|
||||
Namespace: "federation-system",
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1beta1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "Role",
|
||||
Name: "federation-system:federation-controller-manager",
|
||||
},
|
||||
}
|
||||
|
||||
apiserver := v1beta1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
@ -710,6 +767,8 @@ func fakeInitHostFactory(federationName, namespaceName, ip, dnsZoneName, image,
|
||||
},
|
||||
},
|
||||
},
|
||||
ServiceAccountName: "federation-controller-manager",
|
||||
DeprecatedServiceAccount: "federation-controller-manager",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -749,6 +808,7 @@ func fakeInitHostFactory(federationName, namespaceName, ip, dnsZoneName, image,
|
||||
|
||||
f, tf, codec, _ := cmdtesting.NewAPIFactory()
|
||||
extCodec := testapi.Extensions.Codec()
|
||||
rbacCodec := testapi.Rbac.Codec()
|
||||
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||
tf.ClientConfig = kubefedtesting.DefaultClientConfig()
|
||||
tf.Client = &fake.RESTClient{
|
||||
@ -853,6 +913,48 @@ func fakeInitHostFactory(federationName, namespaceName, ip, dnsZoneName, image,
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(extCodec, &want)}, nil
|
||||
case p == "/api/v1/namespaces/federation-system/pods" && m == http.MethodGet:
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &podList)}, nil
|
||||
case p == "/api/v1/namespaces/federation-system/serviceaccounts" && m == http.MethodPost:
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var got v1.ServiceAccount
|
||||
_, _, err = codec.Decode(body, nil, &got)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !api.Semantic.DeepEqual(got, sa) {
|
||||
return nil, fmt.Errorf("Unexpected service account object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, sa))
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &sa)}, nil
|
||||
case p == "/apis/rbac.authorization.k8s.io/v1beta1/namespaces/federation-system/roles" && m == http.MethodPost:
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var got rbacv1beta1.Role
|
||||
_, _, err = codec.Decode(body, nil, &got)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !api.Semantic.DeepEqual(got, role) {
|
||||
return nil, fmt.Errorf("Unexpected role object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, role))
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &role)}, nil
|
||||
case p == "/apis/rbac.authorization.k8s.io/v1beta1/namespaces/federation-system/rolebindings" && m == http.MethodPost:
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var got rbacv1beta1.RoleBinding
|
||||
_, _, err = codec.Decode(body, nil, &got)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !api.Semantic.DeepEqual(got, rolebinding) {
|
||||
return nil, fmt.Errorf("Unexpected rolebinding object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, rolebinding))
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &rolebinding)}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
|
@ -254,3 +254,74 @@ func (r *ClusterRoleBindingBuilder) Binding() (ClusterRoleBinding, error) {
|
||||
|
||||
return r.ClusterRoleBinding, nil
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=false
|
||||
// RoleBindingBuilder let's us attach methods. It is similar to
|
||||
// ClusterRoleBindingBuilder above.
|
||||
type RoleBindingBuilder struct {
|
||||
RoleBinding RoleBinding
|
||||
}
|
||||
|
||||
// NewRoleBinding creates a RoleBinding builder that can be used
|
||||
// to define the subjects of a role binding. At least one of
|
||||
// the `Groups`, `Users` or `SAs` method must be called before
|
||||
// calling the `Binding*` methods.
|
||||
func NewRoleBinding(roleName, namespace string) *RoleBindingBuilder {
|
||||
return &RoleBindingBuilder{
|
||||
RoleBinding: RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: roleName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
RoleRef: RoleRef{
|
||||
APIGroup: GroupName,
|
||||
Kind: "Role",
|
||||
Name: roleName,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Groups adds the specified groups as the subjects of the RoleBinding.
|
||||
func (r *RoleBindingBuilder) Groups(groups ...string) *RoleBindingBuilder {
|
||||
for _, group := range groups {
|
||||
r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: GroupKind, Name: group})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Users adds the specified users as the subjects of the RoleBinding.
|
||||
func (r *RoleBindingBuilder) Users(users ...string) *RoleBindingBuilder {
|
||||
for _, user := range users {
|
||||
r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: UserKind, Name: user})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// SAs adds the specified service accounts as the subjects of the
|
||||
// RoleBinding.
|
||||
func (r *RoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *RoleBindingBuilder {
|
||||
for _, saName := range serviceAccountNames {
|
||||
r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: ServiceAccountKind, Namespace: namespace, Name: saName})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// BindingOrDie calls the binding method and panics if there is an error.
|
||||
func (r *RoleBindingBuilder) BindingOrDie() RoleBinding {
|
||||
ret, err := r.Binding()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Binding builds and returns the RoleBinding API object from the builder
|
||||
// object.
|
||||
func (r *RoleBindingBuilder) Binding() (RoleBinding, error) {
|
||||
if len(r.RoleBinding.Subjects) == 0 {
|
||||
return RoleBinding{}, fmt.Errorf("subjects are required: %#v", r.RoleBinding)
|
||||
}
|
||||
|
||||
return r.RoleBinding, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user