github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/rolebinding.go (about) 1 package argocd 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "reflect" 8 9 corev1 "k8s.io/api/core/v1" 10 v1 "k8s.io/api/rbac/v1" 11 "k8s.io/apimachinery/pkg/api/errors" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/types" 14 "sigs.k8s.io/controller-runtime/pkg/client" 15 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 16 17 argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" 18 "github.com/argoproj-labs/argocd-operator/common" 19 "github.com/argoproj-labs/argocd-operator/controllers/argoutil" 20 ) 21 22 // newClusterRoleBinding returns a new ClusterRoleBinding instance. 23 func newClusterRoleBinding(cr *argoproj.ArgoCD) *v1.ClusterRoleBinding { 24 return &v1.ClusterRoleBinding{ 25 ObjectMeta: metav1.ObjectMeta{ 26 Name: cr.Name, 27 Labels: argoutil.LabelsForCluster(cr), 28 Annotations: argoutil.AnnotationsForCluster(cr), 29 }, 30 } 31 } 32 33 // newClusterRoleBindingWithname creates a new ClusterRoleBinding with the given name for the given ArgCD. 34 func newClusterRoleBindingWithname(name string, cr *argoproj.ArgoCD) *v1.ClusterRoleBinding { 35 roleBinding := newClusterRoleBinding(cr) 36 roleBinding.Name = GenerateUniqueResourceName(name, cr) 37 38 labels := roleBinding.ObjectMeta.Labels 39 labels[common.ArgoCDKeyName] = name 40 roleBinding.ObjectMeta.Labels = labels 41 42 return roleBinding 43 } 44 45 // newRoleBinding returns a new RoleBinding instance. 46 func newRoleBinding(cr *argoproj.ArgoCD) *v1.RoleBinding { 47 return &v1.RoleBinding{ 48 ObjectMeta: metav1.ObjectMeta{ 49 Name: cr.Name, 50 Labels: argoutil.LabelsForCluster(cr), 51 Annotations: argoutil.AnnotationsForCluster(cr), 52 Namespace: cr.Namespace, 53 }, 54 } 55 } 56 57 // newRoleBindingForSupportNamespaces returns a new RoleBinding instance. 58 func newRoleBindingForSupportNamespaces(cr *argoproj.ArgoCD, namespace string) *v1.RoleBinding { 59 return &v1.RoleBinding{ 60 ObjectMeta: metav1.ObjectMeta{ 61 Name: getRoleBindingNameForSourceNamespaces(cr.Name, namespace), 62 Labels: argoutil.LabelsForCluster(cr), 63 Annotations: argoutil.AnnotationsForCluster(cr), 64 Namespace: namespace, 65 }, 66 } 67 } 68 69 func getRoleBindingNameForSourceNamespaces(argocdName, targetNamespace string) string { 70 return fmt.Sprintf("%s_%s", argocdName, targetNamespace) 71 } 72 73 // newRoleBindingWithname creates a new RoleBinding with the given name for the given ArgCD. 74 func newRoleBindingWithname(name string, cr *argoproj.ArgoCD) *v1.RoleBinding { 75 roleBinding := newRoleBinding(cr) 76 roleBinding.ObjectMeta.Name = fmt.Sprintf("%s-%s", cr.Name, name) 77 78 labels := roleBinding.ObjectMeta.Labels 79 labels[common.ArgoCDKeyName] = name 80 roleBinding.ObjectMeta.Labels = labels 81 82 return roleBinding 83 } 84 85 // reconcileRoleBindings will ensure that all ArgoCD RoleBindings are configured. 86 func (r *ReconcileArgoCD) reconcileRoleBindings(cr *argoproj.ArgoCD) error { 87 params := getPolicyRuleList(r.Client) 88 89 for _, param := range params { 90 if err := r.reconcileRoleBinding(param.name, param.policyRule, cr); err != nil { 91 return fmt.Errorf("error reconciling roleBinding for %q: %w", param.name, err) 92 } 93 } 94 95 return nil 96 } 97 98 // reconcileRoleBinding, creates RoleBindings for every role and associates it with the right ServiceAccount. 99 // This would create RoleBindings for all the namespaces managed by the ArgoCD instance. 100 func (r *ReconcileArgoCD) reconcileRoleBinding(name string, rules []v1.PolicyRule, cr *argoproj.ArgoCD) error { 101 var sa *corev1.ServiceAccount 102 var error error 103 104 if sa, error = r.reconcileServiceAccount(name, cr); error != nil { 105 return error 106 } 107 108 if _, error = r.reconcileRole(name, rules, cr); error != nil { 109 return error 110 } 111 112 for _, namespace := range r.ManagedNamespaces.Items { 113 // If encountering a terminating namespace remove managed-by label from it and skip reconciliation - This should trigger 114 // clean-up of roles/rolebindings and removal of namespace from cluster secret 115 if namespace.DeletionTimestamp != nil { 116 if _, ok := namespace.Labels[common.ArgoCDManagedByLabel]; ok { 117 delete(namespace.Labels, common.ArgoCDManagedByLabel) 118 _ = r.Client.Update(context.TODO(), &namespace) 119 } 120 continue 121 } 122 123 list := &argoproj.ArgoCDList{} 124 listOption := &client.ListOptions{Namespace: namespace.Name} 125 err := r.Client.List(context.TODO(), list, listOption) 126 if err != nil { 127 return err 128 } 129 // only skip creation of dex and redisHa rolebindings for namespaces that no argocd instance is deployed in 130 if len(list.Items) < 1 { 131 // namespace doesn't contain argocd instance, so skipe all the ArgoCD internal roles 132 if cr.ObjectMeta.Namespace != namespace.Name && (name != common.ArgoCDApplicationControllerComponent && name != common.ArgoCDServerComponent) { 133 continue 134 } 135 } 136 // get expected name 137 roleBinding := newRoleBindingWithname(name, cr) 138 roleBinding.Namespace = namespace.Name 139 140 // fetch existing rolebinding by name 141 existingRoleBinding := &v1.RoleBinding{} 142 err = r.Client.Get(context.TODO(), types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, existingRoleBinding) 143 roleBindingExists := true 144 if err != nil { 145 if !errors.IsNotFound(err) { 146 return fmt.Errorf("failed to get the rolebinding associated with %s : %s", name, err) 147 } 148 149 if name == common.ArgoCDDexServerComponent && !UseDex(cr) { 150 continue // Dex installation is not requested, do nothing 151 } 152 153 roleBindingExists = false 154 } 155 156 roleBinding.Subjects = []v1.Subject{ 157 { 158 Kind: v1.ServiceAccountKind, 159 Name: sa.Name, 160 Namespace: sa.Namespace, 161 }, 162 } 163 164 customRoleName := getCustomRoleName(name) 165 if customRoleName != "" { 166 roleBinding.RoleRef = v1.RoleRef{ 167 APIGroup: v1.GroupName, 168 Kind: "ClusterRole", 169 Name: customRoleName, 170 } 171 } else { 172 roleBinding.RoleRef = v1.RoleRef{ 173 APIGroup: v1.GroupName, 174 Kind: "Role", 175 Name: generateResourceName(name, cr), 176 } 177 } 178 179 if roleBindingExists { 180 if name == common.ArgoCDDexServerComponent && !UseDex(cr) { 181 // Delete any existing RoleBinding created for Dex since dex uninstallation is requested 182 log.Info("deleting the existing Dex roleBinding because dex uninstallation is requested") 183 if err = r.Client.Delete(context.TODO(), existingRoleBinding); err != nil { 184 return err 185 } 186 continue 187 } 188 189 // if the RoleRef changes, delete the existing role binding and create a new one 190 if !reflect.DeepEqual(roleBinding.RoleRef, existingRoleBinding.RoleRef) { 191 if err = r.Client.Delete(context.TODO(), existingRoleBinding); err != nil { 192 return err 193 } 194 } else { 195 // if the Subjects differ, update the role bindings 196 if !reflect.DeepEqual(roleBinding.Subjects, existingRoleBinding.Subjects) { 197 existingRoleBinding.Subjects = roleBinding.Subjects 198 if err = r.Client.Update(context.TODO(), existingRoleBinding); err != nil { 199 return err 200 } 201 } 202 continue 203 } 204 } 205 206 // Only set ownerReferences for role bindings in same namespaces as Argo CD CR 207 if cr.Namespace == roleBinding.Namespace { 208 if err = controllerutil.SetControllerReference(cr, roleBinding, r.Scheme); err != nil { 209 return fmt.Errorf("failed to set ArgoCD CR \"%s\" as owner for roleBinding \"%s\": %s", cr.Name, roleBinding.Name, err) 210 } 211 } 212 213 log.Info(fmt.Sprintf("creating rolebinding %s for Argo CD instance %s in namespace %s", roleBinding.Name, cr.Name, cr.Namespace)) 214 if err = r.Client.Create(context.TODO(), roleBinding); err != nil { 215 return err 216 } 217 } 218 219 // reconcile rolebindings only for ArgoCDServerComponent 220 if name == common.ArgoCDServerComponent { 221 222 // reconcile rolebindings for all source namespaces for argocd-server 223 sourceNamespaces, err := r.getSourceNamespaces(cr) 224 if err != nil { 225 return err 226 } 227 for _, sourceNamespace := range sourceNamespaces { 228 namespace := &corev1.Namespace{} 229 if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: sourceNamespace}, namespace); err != nil { 230 return err 231 } 232 233 // do not reconcile rolebindings for namespaces already containing managed-by label 234 // as it already contains rolebindings with permissions to manipulate application resources 235 // reconciled during reconcilation of ManagedNamespaces 236 if value, ok := namespace.Labels[common.ArgoCDManagedByLabel]; ok { 237 log.Info(fmt.Sprintf("Skipping reconciling resources for namespace %s as it is already managed-by namespace %s.", namespace.Name, value)) 238 continue 239 } 240 241 list := &argoproj.ArgoCDList{} 242 listOption := &client.ListOptions{Namespace: namespace.Name} 243 err := r.Client.List(context.TODO(), list, listOption) 244 if err != nil { 245 log.Info(err.Error()) 246 return err 247 } 248 249 // get expected name 250 roleBinding := newRoleBindingWithNameForApplicationSourceNamespaces(namespace.Name, cr) 251 roleBinding.Namespace = namespace.Name 252 253 roleBinding.RoleRef = v1.RoleRef{ 254 APIGroup: v1.GroupName, 255 Kind: "Role", 256 Name: getRoleNameForApplicationSourceNamespaces(namespace.Name, cr), 257 } 258 259 // fetch existing rolebinding by name 260 existingRoleBinding := &v1.RoleBinding{} 261 err = r.Client.Get(context.TODO(), types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, existingRoleBinding) 262 roleBindingExists := true 263 if err != nil { 264 if !errors.IsNotFound(err) { 265 return fmt.Errorf("failed to get the rolebinding associated with %s : %s", name, err) 266 } 267 log.Info(fmt.Sprintf("Existing rolebinding %s", err.Error())) 268 roleBindingExists = false 269 } 270 271 roleBinding.Subjects = []v1.Subject{ 272 { 273 Kind: v1.ServiceAccountKind, 274 Name: getServiceAccountName(cr.Name, common.ArgoCDServerComponent), 275 Namespace: cr.Namespace, 276 }, 277 { 278 Kind: v1.ServiceAccountKind, 279 Name: getServiceAccountName(cr.Name, common.ArgoCDApplicationControllerComponent), 280 Namespace: cr.Namespace, 281 }, 282 } 283 284 if roleBindingExists { 285 // reconcile role bindings for namespaces already containing managed-by-cluster-argocd label only 286 if n, ok := namespace.Labels[common.ArgoCDManagedByClusterArgoCDLabel]; !ok || n != cr.Namespace { 287 continue 288 } 289 // if the RoleRef changes, delete the existing role binding and create a new one 290 if !reflect.DeepEqual(roleBinding.RoleRef, existingRoleBinding.RoleRef) { 291 if err = r.Client.Delete(context.TODO(), existingRoleBinding); err != nil { 292 return err 293 } 294 } else { 295 // if the Subjects differ, update the role bindings 296 if !reflect.DeepEqual(roleBinding.Subjects, existingRoleBinding.Subjects) { 297 existingRoleBinding.Subjects = roleBinding.Subjects 298 if err = r.Client.Update(context.TODO(), existingRoleBinding); err != nil { 299 return err 300 } 301 } 302 continue 303 } 304 } 305 306 log.Info(fmt.Sprintf("creating rolebinding %s for Argo CD instance %s in namespace %s", roleBinding.Name, cr.Name, namespace)) 307 if err = r.Client.Create(context.TODO(), roleBinding); err != nil { 308 return err 309 } 310 } 311 } 312 return nil 313 } 314 315 func getCustomRoleName(name string) string { 316 if name == common.ArgoCDApplicationControllerComponent { 317 return os.Getenv(common.ArgoCDControllerClusterRoleEnvName) 318 } 319 if name == common.ArgoCDServerComponent { 320 return os.Getenv(common.ArgoCDServerClusterRoleEnvName) 321 } 322 return "" 323 } 324 325 // Returns the name of the role for the source namespaces for ArgoCDServer in the format of "sourceNamespace_targetNamespace_argocd-server" 326 func getRoleNameForApplicationSourceNamespaces(targetNamespace string, cr *argoproj.ArgoCD) string { 327 return fmt.Sprintf("%s_%s", cr.Name, targetNamespace) 328 } 329 330 // newRoleBindingWithNameForApplicationSourceNamespaces creates a new RoleBinding with the given name for the source namespaces of ArgoCD Server. 331 func newRoleBindingWithNameForApplicationSourceNamespaces(namespace string, cr *argoproj.ArgoCD) *v1.RoleBinding { 332 roleBinding := newRoleBindingForSupportNamespaces(cr, namespace) 333 334 labels := roleBinding.ObjectMeta.Labels 335 labels[common.ArgoCDKeyName] = roleBinding.ObjectMeta.Name 336 roleBinding.ObjectMeta.Labels = labels 337 338 return roleBinding 339 } 340 341 func (r *ReconcileArgoCD) reconcileClusterRoleBinding(name string, role *v1.ClusterRole, cr *argoproj.ArgoCD) error { 342 343 // get expected name 344 roleBinding := newClusterRoleBindingWithname(name, cr) 345 // fetch existing rolebinding by name 346 err := r.Client.Get(context.TODO(), types.NamespacedName{Name: roleBinding.Name}, roleBinding) 347 roleBindingExists := true 348 if err != nil { 349 if !errors.IsNotFound(err) { 350 return err 351 } 352 roleBindingExists = false 353 roleBinding = newClusterRoleBindingWithname(name, cr) 354 } 355 356 if roleBindingExists && role == nil { 357 return r.Client.Delete(context.TODO(), roleBinding) 358 } 359 360 if !roleBindingExists && role == nil { 361 // DO Nothing 362 return nil 363 } 364 365 roleBinding.Subjects = []v1.Subject{ 366 { 367 Kind: v1.ServiceAccountKind, 368 Name: generateResourceName(name, cr), 369 Namespace: cr.Namespace, 370 }, 371 } 372 roleBinding.RoleRef = v1.RoleRef{ 373 APIGroup: v1.GroupName, 374 Kind: "ClusterRole", 375 Name: GenerateUniqueResourceName(name, cr), 376 } 377 378 if cr.Namespace == roleBinding.Namespace { 379 if err = controllerutil.SetControllerReference(cr, roleBinding, r.Scheme); err != nil { 380 return fmt.Errorf("failed to set ArgoCD CR \"%s\" as owner for roleBinding \"%s\": %s", cr.Name, roleBinding.Name, err) 381 } 382 } 383 384 if roleBindingExists { 385 return r.Client.Update(context.TODO(), roleBinding) 386 } 387 return r.Client.Create(context.TODO(), roleBinding) 388 } 389 390 func deleteClusterRoleBindings(c client.Client, clusterBindingList *v1.ClusterRoleBindingList) error { 391 for _, clusterBinding := range clusterBindingList.Items { 392 if err := c.Delete(context.TODO(), &clusterBinding); err != nil { 393 return fmt.Errorf("failed to delete ClusterRoleBinding %q during cleanup: %w", clusterBinding.Name, err) 394 } 395 } 396 return nil 397 }