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  }