github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/caas/kubernetes/clientconfig/plugins.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package clientconfig 5 6 import ( 7 "github.com/juju/errors" 8 core "k8s.io/api/core/v1" 9 rbacv1 "k8s.io/api/rbac/v1" 10 k8serrors "k8s.io/apimachinery/pkg/api/errors" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 "k8s.io/client-go/kubernetes" 13 _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // load gcp auth plugin. 14 "k8s.io/client-go/tools/clientcmd" 15 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 16 ) 17 18 const ( 19 adminNameSpace = "kube-system" 20 clusterRoleName = "cluster-admin" 21 jujuServiceAccountName = "juju-service-account" 22 jujuClusterRoleBindingName = "juju-cluster-role-binding" 23 ) 24 25 // To regenerate the mocks for the kubernetes Client used by this package, 26 // mockgen -package mocks -destination mocks/rbacv1_mock.go k8s.io/client-go/kubernetes/typed/rbac/v1 RbacV1Interface,ClusterRoleBindingInterface,ClusterRoleInterface 27 // mockgen -package mocks -destination mocks/serviceaccount_mock.go k8s.io/client-go/kubernetes/typed/core/v1 ServiceAccountInterface 28 29 func newK8sClientSet(config *clientcmdapi.Config, contextName string) (*kubernetes.Clientset, error) { 30 clientCfg, err := clientcmd.NewNonInteractiveClientConfig( 31 *config, contextName, &clientcmd.ConfigOverrides{}, nil).ClientConfig() 32 if err != nil { 33 return nil, errors.Trace(err) 34 } 35 return kubernetes.NewForConfig(clientCfg) 36 } 37 38 func ensureJujuAdminServiceAccount( 39 clientset kubernetes.Interface, 40 config *clientcmdapi.Config, 41 contextName string, 42 ) (*clientcmdapi.Config, error) { 43 44 // ensure admin cluster role. 45 clusterRole, err := ensureClusterRole(clientset, clusterRoleName, adminNameSpace) 46 if err != nil { 47 return nil, errors.Annotatef( 48 err, "ensuring cluster role %q in namespace %q", clusterRoleName, adminNameSpace) 49 } 50 51 // create juju admin service account. 52 sa, err := ensureServiceAccount(clientset, jujuServiceAccountName, adminNameSpace) 53 if err != nil { 54 return nil, errors.Annotatef( 55 err, "ensuring service account %q in namespace %q", jujuServiceAccountName, adminNameSpace) 56 } 57 58 // ensure role binding for juju admin service account with admin cluster role. 59 _, err = ensureClusterRoleBinding(clientset, jujuClusterRoleBindingName, sa, clusterRole) 60 if err != nil { 61 return nil, errors.Annotatef(err, "ensuring cluster role binding %q", jujuClusterRoleBindingName) 62 } 63 64 // refresh service account to get the secret/token after cluster role binding created. 65 sa, err = getServiceAccount(clientset, jujuServiceAccountName, adminNameSpace) 66 if err != nil { 67 return nil, errors.Annotatef( 68 err, "refetching service account %q after cluster role binding created", jujuServiceAccountName) 69 } 70 71 // get bearer token of juju admin service account. 72 secret, err := getServiceAccountSecret(clientset, sa) 73 if err != nil { 74 return nil, errors.Annotatef(err, "fetching bearer token for service account %q", sa.Name) 75 } 76 77 replaceAuthProviderWithServiceAccountAuthData(contextName, config, secret) 78 return config, nil 79 } 80 81 func ensureClusterRole(clientset kubernetes.Interface, name, namespace string) (*rbacv1.ClusterRole, error) { 82 // try get first because it's more usual to reuse cluster role. 83 clusterRole, err := clientset.RbacV1().ClusterRoles().Get(name, metav1.GetOptions{}) 84 if err == nil { 85 return clusterRole, nil 86 } 87 if !k8serrors.IsNotFound(err) { 88 return nil, errors.Trace(err) 89 } 90 91 // No existing cluster role found, so create one. 92 // This cluster role will be granted extra privileges which requires proper 93 // permissions setup for the credential in kubeconfig file. 94 cr, err := clientset.RbacV1().ClusterRoles().Create(&rbacv1.ClusterRole{ 95 ObjectMeta: metav1.ObjectMeta{ 96 Name: name, 97 Namespace: namespace, 98 }, 99 Rules: []rbacv1.PolicyRule{ 100 { 101 APIGroups: []string{rbacv1.APIGroupAll}, 102 Resources: []string{rbacv1.ResourceAll}, 103 Verbs: []string{rbacv1.VerbAll}, 104 }, 105 { 106 NonResourceURLs: []string{rbacv1.NonResourceAll}, 107 Verbs: []string{rbacv1.VerbAll}, 108 }, 109 }, 110 }) 111 if err != nil && !k8serrors.IsAlreadyExists(err) { 112 return nil, errors.Trace(err) 113 } 114 return cr, nil 115 } 116 117 func ensureServiceAccount(clientset kubernetes.Interface, name, namespace string) (*core.ServiceAccount, error) { 118 _, err := clientset.CoreV1().ServiceAccounts(namespace).Create(&core.ServiceAccount{ 119 ObjectMeta: metav1.ObjectMeta{ 120 Name: name, 121 Namespace: namespace, 122 }, 123 }) 124 if err != nil && !k8serrors.IsAlreadyExists(err) { 125 return nil, errors.Trace(err) 126 } 127 return getServiceAccount(clientset, name, namespace) 128 } 129 130 func getServiceAccount(clientset kubernetes.Interface, name, namespace string) (*core.ServiceAccount, error) { 131 return clientset.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{}) 132 } 133 134 func ensureClusterRoleBinding( 135 clientset kubernetes.Interface, 136 name string, 137 sa *core.ServiceAccount, 138 cr *rbacv1.ClusterRole, 139 ) (*rbacv1.ClusterRoleBinding, error) { 140 rb, err := clientset.RbacV1().ClusterRoleBindings().Create(&rbacv1.ClusterRoleBinding{ 141 ObjectMeta: metav1.ObjectMeta{ 142 Name: name, 143 }, 144 RoleRef: rbacv1.RoleRef{ 145 Kind: "ClusterRole", 146 Name: cr.Name, 147 }, 148 Subjects: []rbacv1.Subject{ 149 { 150 Kind: rbacv1.ServiceAccountKind, 151 Name: sa.Name, 152 Namespace: sa.Namespace, 153 }, 154 }, 155 }) 156 if err != nil && !k8serrors.IsAlreadyExists(err) { 157 return nil, errors.Trace(err) 158 } 159 return rb, nil 160 } 161 162 func getServiceAccountSecret(clientset kubernetes.Interface, sa *core.ServiceAccount) (*core.Secret, error) { 163 if len(sa.Secrets) == 0 { 164 return nil, errors.NotFoundf("secret for service account %q", sa.Name) 165 } 166 return clientset.CoreV1().Secrets(sa.Namespace).Get(sa.Secrets[0].Name, metav1.GetOptions{}) 167 } 168 169 func replaceAuthProviderWithServiceAccountAuthData( 170 contextName string, 171 config *clientcmdapi.Config, 172 secret *core.Secret, 173 ) { 174 authName := config.Contexts[contextName].AuthInfo 175 currentAuth := config.AuthInfos[authName] 176 currentAuth.AuthProvider = nil 177 currentAuth.ClientCertificateData = secret.Data[core.ServiceAccountRootCAKey] 178 currentAuth.Token = string(secret.Data[core.ServiceAccountTokenKey]) 179 config.AuthInfos[authName] = currentAuth 180 }