github.com/spotahome/redis-operator@v1.2.4/service/k8s/rbac.go (about) 1 package k8s 2 3 import ( 4 "context" 5 6 rbacv1 "k8s.io/api/rbac/v1" 7 "k8s.io/apimachinery/pkg/api/errors" 8 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 "k8s.io/client-go/kubernetes" 10 11 "github.com/spotahome/redis-operator/log" 12 "github.com/spotahome/redis-operator/metrics" 13 ) 14 15 // RBAC is the service that knows how to interact with k8s to manage RBAC related resources. 16 type RBAC interface { 17 GetClusterRole(name string) (*rbacv1.ClusterRole, error) 18 GetRole(namespace, name string) (*rbacv1.Role, error) 19 GetRoleBinding(namespace, name string) (*rbacv1.RoleBinding, error) 20 CreateRole(namespace string, role *rbacv1.Role) error 21 UpdateRole(namespace string, role *rbacv1.Role) error 22 CreateOrUpdateRole(namespace string, binding *rbacv1.Role) error 23 CreateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error 24 UpdateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error 25 CreateOrUpdateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error 26 } 27 28 // NamespaceService is the Namespace service implementation using API calls to kubernetes. 29 type RBACService struct { 30 kubeClient kubernetes.Interface 31 logger log.Logger 32 metricsRecorder metrics.Recorder 33 } 34 35 // NewRBACService returns a new RBAC KubeService. 36 func NewRBACService(kubeClient kubernetes.Interface, logger log.Logger, metricsRecorder metrics.Recorder) *RBACService { 37 logger = logger.With("service", "k8s.rbac") 38 return &RBACService{ 39 kubeClient: kubeClient, 40 logger: logger, 41 metricsRecorder: metricsRecorder, 42 } 43 } 44 45 func (r *RBACService) GetClusterRole(name string) (*rbacv1.ClusterRole, error) { 46 clusterRole, err := r.kubeClient.RbacV1().ClusterRoles().Get(context.TODO(), name, metav1.GetOptions{}) 47 recordMetrics(metrics.NOT_APPLICABLE, "ClusterRole", name, "GET", err, r.metricsRecorder) 48 return clusterRole, err 49 } 50 51 func (r *RBACService) GetRole(namespace, name string) (*rbacv1.Role, error) { 52 role, err := r.kubeClient.RbacV1().Roles(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 53 recordMetrics(namespace, "Role", name, "GET", err, r.metricsRecorder) 54 return role, err 55 } 56 57 func (r *RBACService) GetRoleBinding(namespace, name string) (*rbacv1.RoleBinding, error) { 58 rolbinding, err := r.kubeClient.RbacV1().RoleBindings(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 59 recordMetrics(namespace, "RoleBinding", name, "GET", err, r.metricsRecorder) 60 return rolbinding, err 61 } 62 63 func (r *RBACService) DeleteRole(namespace, name string) error { 64 err := r.kubeClient.RbacV1().Roles(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) 65 recordMetrics(namespace, "Role", name, "DELETE", err, r.metricsRecorder) 66 if err != nil { 67 return err 68 } 69 r.logger.WithField("namespace", namespace).WithField("role", name).Debugf("role deleted") 70 return nil 71 } 72 73 func (r *RBACService) CreateRole(namespace string, role *rbacv1.Role) error { 74 _, err := r.kubeClient.RbacV1().Roles(namespace).Create(context.TODO(), role, metav1.CreateOptions{}) 75 recordMetrics(namespace, "Role", role.GetName(), "CREATE", err, r.metricsRecorder) 76 if err != nil { 77 return err 78 } 79 r.logger.WithField("namespace", namespace).WithField("role", role.Name).Debugf("role created") 80 return nil 81 } 82 83 func (s *RBACService) UpdateRole(namespace string, role *rbacv1.Role) error { 84 _, err := s.kubeClient.RbacV1().Roles(namespace).Update(context.TODO(), role, metav1.UpdateOptions{}) 85 recordMetrics(namespace, "Role", role.GetName(), "UPDATE", err, s.metricsRecorder) 86 if err != nil { 87 return err 88 } 89 s.logger.WithField("namespace", namespace).WithField("role", role.ObjectMeta.Name).Debugf("role updated") 90 return err 91 } 92 93 func (r *RBACService) CreateOrUpdateRole(namespace string, role *rbacv1.Role) error { 94 storedRole, err := r.GetRole(namespace, role.Name) 95 if err != nil { 96 // If no resource we need to create. 97 if errors.IsNotFound(err) { 98 return r.CreateRole(namespace, role) 99 } 100 return err 101 } 102 103 // Already exists, need to Update. 104 // Set the correct resource version to ensure we are on the latest version. This way the only valid 105 // namespace is our spec(https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency), 106 // we will replace the current namespace state. 107 role.ResourceVersion = storedRole.ResourceVersion 108 return r.UpdateRole(namespace, role) 109 } 110 111 func (r *RBACService) DeleteRoleBinding(namespace, name string) error { 112 err := r.kubeClient.RbacV1().RoleBindings(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) 113 recordMetrics(namespace, "RoleBinding", name, "DELETE", err, r.metricsRecorder) 114 if err != nil { 115 return err 116 } 117 r.logger.WithField("namespace", namespace).WithField("binding", name).Debugf("role binding deleted") 118 return nil 119 } 120 121 func (r *RBACService) CreateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error { 122 _, err := r.kubeClient.RbacV1().RoleBindings(namespace).Create(context.TODO(), binding, metav1.CreateOptions{}) 123 recordMetrics(namespace, "RoleBinding", binding.GetName(), "CREATE", err, r.metricsRecorder) 124 if err != nil { 125 return err 126 } 127 r.logger.WithField("namespace", namespace).WithField("binding", binding.Name).Debugf("role binding created") 128 return nil 129 } 130 131 func (r *RBACService) UpdateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error { 132 _, err := r.kubeClient.RbacV1().RoleBindings(namespace).Update(context.TODO(), binding, metav1.UpdateOptions{}) 133 recordMetrics(namespace, "Role", binding.GetName(), "UPDATE", err, r.metricsRecorder) 134 if err != nil { 135 return err 136 } 137 r.logger.WithField("namespace", namespace).WithField("binding", binding.Name).Debugf("role binding updated") 138 return nil 139 } 140 141 func (r *RBACService) CreateOrUpdateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error { 142 storedBinding, err := r.GetRoleBinding(namespace, binding.Name) 143 if err != nil { 144 // If no resource we need to create. 145 if errors.IsNotFound(err) { 146 return r.CreateRoleBinding(namespace, binding) 147 } 148 return err 149 } 150 151 // Check if the role ref has changed, roleref updates are not allowed, if changed then delete and create again the role binding. 152 // https://github.com/kubernetes/kubernetes/blob/0f0a5223dfc75337d03c9b80ae552ae8ef138eeb/pkg/apis/rbac/validation/validation.go#L157-L159 153 if storedBinding.RoleRef != binding.RoleRef { 154 r.logger.WithField("namespace", namespace).WithField("binding", binding.Name).Infof("roleref changed, need to recreate role binding resource") 155 if err := r.DeleteRoleBinding(namespace, binding.Name); err != nil { 156 return err 157 } 158 return r.CreateRoleBinding(namespace, binding) 159 } 160 161 // Already exists, need to Update. 162 // Set the correct resource version to ensure we are on the latest version. This way the only valid 163 // namespace is our spec(https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency), 164 // we will replace the current namespace state. 165 binding.ResourceVersion = storedBinding.ResourceVersion 166 return r.UpdateRoleBinding(namespace, binding) 167 }