github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/role.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 // newRole returns a new Role instance. 23 func newRole(name string, rules []v1.PolicyRule, cr *argoproj.ArgoCD) *v1.Role { 24 return &v1.Role{ 25 ObjectMeta: metav1.ObjectMeta{ 26 Name: generateResourceName(name, cr), 27 Namespace: cr.Namespace, 28 Labels: argoutil.LabelsForCluster(cr), 29 }, 30 Rules: rules, 31 } 32 } 33 34 func newRoleForApplicationSourceNamespaces(namespace string, rules []v1.PolicyRule, cr *argoproj.ArgoCD) *v1.Role { 35 return &v1.Role{ 36 ObjectMeta: metav1.ObjectMeta{ 37 Name: getRoleNameForApplicationSourceNamespaces(namespace, cr), 38 Namespace: namespace, 39 Labels: argoutil.LabelsForCluster(cr), 40 }, 41 Rules: rules, 42 } 43 } 44 45 func generateResourceName(argoComponentName string, cr *argoproj.ArgoCD) string { 46 return cr.Name + "-" + argoComponentName 47 } 48 49 // GenerateUniqueResourceName generates unique names for cluster scoped resources 50 func GenerateUniqueResourceName(argoComponentName string, cr *argoproj.ArgoCD) string { 51 return cr.Name + "-" + cr.Namespace + "-" + argoComponentName 52 } 53 54 func newClusterRole(name string, rules []v1.PolicyRule, cr *argoproj.ArgoCD) *v1.ClusterRole { 55 return &v1.ClusterRole{ 56 ObjectMeta: metav1.ObjectMeta{ 57 Name: GenerateUniqueResourceName(name, cr), 58 Labels: argoutil.LabelsForCluster(cr), 59 Annotations: argoutil.AnnotationsForCluster(cr), 60 }, 61 Rules: rules, 62 } 63 } 64 65 // reconcileRoles will ensure that all ArgoCD Service Accounts are configured. 66 func (r *ReconcileArgoCD) reconcileRoles(cr *argoproj.ArgoCD) error { 67 params := getPolicyRuleList(r.Client) 68 69 for _, param := range params { 70 if _, err := r.reconcileRole(param.name, param.policyRule, cr); err != nil { 71 return err 72 } 73 } 74 75 clusterParams := getPolicyRuleClusterRoleList() 76 77 for _, clusterParam := range clusterParams { 78 if _, err := r.reconcileClusterRole(clusterParam.name, clusterParam.policyRule, cr); err != nil { 79 return err 80 } 81 } 82 83 log.Info("reconciling roles for source namespaces") 84 policyRuleForApplicationSourceNamespaces := policyRuleForServerApplicationSourceNamespaces() 85 // reconcile roles is source namespaces for ArgoCD Server 86 if err := r.reconcileRoleForApplicationSourceNamespaces(common.ArgoCDServerComponent, policyRuleForApplicationSourceNamespaces, cr); err != nil { 87 return err 88 } 89 90 log.Info("performing cleanup for source namespaces") 91 // remove resources for namespaces not part of SourceNamespaces 92 if err := r.removeUnmanagedSourceNamespaceResources(cr); err != nil { 93 return err 94 } 95 96 return nil 97 } 98 99 // reconcileRole, reconciles the policy rules for different ArgoCD components, for each namespace 100 // Managed by a single instance of ArgoCD. 101 func (r *ReconcileArgoCD) reconcileRole(name string, policyRules []v1.PolicyRule, cr *argoproj.ArgoCD) ([]*v1.Role, error) { 102 var roles []*v1.Role 103 104 // create policy rules for each namespace 105 for _, namespace := range r.ManagedNamespaces.Items { 106 // If encountering a terminating namespace remove managed-by label from it and skip reconciliation - This should trigger 107 // clean-up of roles/rolebindings and removal of namespace from cluster secret 108 if namespace.DeletionTimestamp != nil { 109 if _, ok := namespace.Labels[common.ArgoCDManagedByLabel]; ok { 110 delete(namespace.Labels, common.ArgoCDManagedByLabel) 111 _ = r.Client.Update(context.TODO(), &namespace) 112 } 113 continue 114 } 115 116 list := &argoproj.ArgoCDList{} 117 listOption := &client.ListOptions{Namespace: namespace.Name} 118 err := r.Client.List(context.TODO(), list, listOption) 119 if err != nil { 120 return nil, err 121 } 122 // only skip creation of dex and redisHa roles for namespaces that no argocd instance is deployed in 123 if len(list.Items) < 1 { 124 // namespace doesn't contain argocd instance, so skipe all the ArgoCD internal roles 125 if cr.ObjectMeta.Namespace != namespace.Name && (name != common.ArgoCDApplicationControllerComponent && name != common.ArgoCDServerComponent) { 126 continue 127 } 128 } 129 customRole := getCustomRoleName(name) 130 role := newRole(name, policyRules, cr) 131 if err := applyReconcilerHook(cr, role, ""); err != nil { 132 return nil, err 133 } 134 role.Namespace = namespace.Name 135 existingRole := v1.Role{} 136 err = r.Client.Get(context.TODO(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, &existingRole) 137 if err != nil { 138 if !errors.IsNotFound(err) { 139 return nil, fmt.Errorf("failed to reconcile the role for the service account associated with %s : %s", name, err) 140 } 141 if customRole != "" { 142 continue // skip creating default role if custom cluster role is provided 143 } 144 roles = append(roles, role) 145 146 if name == common.ArgoCDDexServerComponent && !UseDex(cr) { 147 148 continue // Dex installation not requested, do nothing 149 } 150 151 // Only set ownerReferences for roles in same namespace as ArgoCD CR 152 if cr.Namespace == role.Namespace { 153 if err = controllerutil.SetControllerReference(cr, role, r.Scheme); err != nil { 154 return nil, fmt.Errorf("failed to set ArgoCD CR \"%s\" as owner for role \"%s\": %s", cr.Name, role.Name, err) 155 } 156 } 157 158 log.Info(fmt.Sprintf("creating role %s for Argo CD instance %s in namespace %s", role.Name, cr.Name, cr.Namespace)) 159 if err := r.Client.Create(context.TODO(), role); err != nil { 160 return nil, err 161 } 162 continue 163 } 164 165 // Delete the existing default role if custom role is specified 166 // or if there is an existing Role created for Dex but dex is disabled or not configured 167 if customRole != "" || 168 (name == common.ArgoCDDexServerComponent && !UseDex(cr)) { 169 170 log.Info("deleting the existing Dex role because dex is not configured") 171 if err := r.Client.Delete(context.TODO(), &existingRole); err != nil { 172 return nil, err 173 } 174 continue 175 } 176 177 // if the Rules differ, update the Role 178 if !reflect.DeepEqual(existingRole.Rules, role.Rules) { 179 existingRole.Rules = role.Rules 180 if err := r.Client.Update(context.TODO(), &existingRole); err != nil { 181 return nil, err 182 } 183 } 184 roles = append(roles, &existingRole) 185 } 186 return roles, nil 187 } 188 189 func (r *ReconcileArgoCD) reconcileRoleForApplicationSourceNamespaces(name string, policyRules []v1.PolicyRule, cr *argoproj.ArgoCD) error { 190 191 // create policy rules for each source namespace for ArgoCD Server 192 sourceNamespaces, err := r.getSourceNamespaces(cr) 193 if err != nil { 194 return err 195 } 196 197 for _, sourceNamespace := range sourceNamespaces { 198 namespace := &corev1.Namespace{} 199 if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: sourceNamespace}, namespace); err != nil { 200 return err 201 } 202 // do not reconcile roles for namespaces already containing managed-by label 203 // as it already contains roles with permissions to manipulate application resources 204 // reconciled during reconcilation of ManagedNamespaces 205 if value, ok := namespace.Labels[common.ArgoCDManagedByLabel]; ok && value != "" { 206 log.Info(fmt.Sprintf("Skipping reconciling resources for namespace %s as it is already managed-by namespace %s.", namespace.Name, value)) 207 // if managed-by-cluster-argocd label is also present, remove the namespace from the ManagedSourceNamespaces. 208 if val, ok1 := namespace.Labels[common.ArgoCDManagedByClusterArgoCDLabel]; ok1 && val == cr.Namespace { 209 delete(r.ManagedSourceNamespaces, namespace.Name) 210 if err := r.cleanupUnmanagedSourceNamespaceResources(cr, namespace.Name); err != nil { 211 log.Error(err, fmt.Sprintf("error cleaning up resources for namespace %s", namespace.Name)) 212 } 213 } 214 continue 215 } 216 217 // reconcile roles only if another ArgoCD instance is not already set as value for managed-by-cluster-argocd label 218 if value, ok := namespace.Labels[common.ArgoCDManagedByClusterArgoCDLabel]; ok && value != cr.Namespace { 219 log.Info(fmt.Sprintf("Namespace already has label set to argocd instance %s. Thus, skipping namespace %s", value, namespace.Name)) 220 continue 221 } 222 223 log.Info(fmt.Sprintf("Reconciling role for %s", namespace.Name)) 224 225 role := newRoleForApplicationSourceNamespaces(namespace.Name, policyRules, cr) 226 if err := applyReconcilerHook(cr, role, ""); err != nil { 227 return err 228 } 229 role.Namespace = namespace.Name 230 // patch rules if appset in source namespace is allowed 231 if contains(r.getApplicationSetSourceNamespaces(cr), sourceNamespace) { 232 role.Rules = append(role.Rules, policyRuleForServerApplicationSetSourceNamespaces()...) 233 } 234 235 created := false 236 existingRole := v1.Role{} 237 err := r.Client.Get(context.TODO(), types.NamespacedName{Name: role.Name, Namespace: namespace.Name}, &existingRole) 238 if err != nil { 239 if !errors.IsNotFound(err) { 240 return fmt.Errorf("failed to reconcile the role for the service account associated with %s : %s", name, err) 241 } 242 243 log.Info(fmt.Sprintf("creating role %s for Argo CD instance %s in namespace %s", role.Name, cr.Name, namespace)) 244 if err := r.Client.Create(context.TODO(), role); err != nil { 245 return err 246 } 247 created = true 248 } 249 250 // Get the latest value of namespace before updating it 251 if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: namespace.Name}, namespace); err != nil { 252 return err 253 } 254 // Update namespace with managed-by-cluster-argocd label 255 if namespace.Labels == nil { 256 namespace.Labels = make(map[string]string) 257 } 258 namespace.Labels[common.ArgoCDManagedByClusterArgoCDLabel] = cr.Namespace 259 if err := r.Client.Update(context.TODO(), namespace); err != nil { 260 log.Error(err, fmt.Sprintf("failed to add label from namespace [%s]", namespace.Name)) 261 } 262 263 // if the Rules differ, update the Role 264 if !created && !reflect.DeepEqual(existingRole.Rules, role.Rules) { 265 existingRole.Rules = role.Rules 266 if err := r.Client.Update(context.TODO(), &existingRole); err != nil { 267 return err 268 } 269 } 270 271 if _, ok := r.ManagedSourceNamespaces[sourceNamespace]; !ok { 272 if r.ManagedSourceNamespaces == nil { 273 r.ManagedSourceNamespaces = make(map[string]string) 274 } 275 r.ManagedSourceNamespaces[sourceNamespace] = "" 276 } 277 278 } 279 return nil 280 } 281 282 func (r *ReconcileArgoCD) reconcileClusterRole(name string, policyRules []v1.PolicyRule, cr *argoproj.ArgoCD) (*v1.ClusterRole, error) { 283 allowed := false 284 if allowedNamespace(cr.Namespace, os.Getenv("ARGOCD_CLUSTER_CONFIG_NAMESPACES")) { 285 allowed = true 286 } 287 clusterRole := newClusterRole(name, policyRules, cr) 288 if err := applyReconcilerHook(cr, clusterRole, ""); err != nil { 289 return nil, err 290 } 291 292 existingClusterRole := &v1.ClusterRole{} 293 err := r.Client.Get(context.TODO(), types.NamespacedName{Name: clusterRole.Name}, existingClusterRole) 294 if err != nil { 295 if !errors.IsNotFound(err) { 296 return nil, fmt.Errorf("failed to reconcile the cluster role for the service account associated with %s : %s", name, err) 297 } 298 if !allowed { 299 // Do Nothing 300 return nil, nil 301 } 302 return clusterRole, r.Client.Create(context.TODO(), clusterRole) 303 } 304 305 if !allowed { 306 return nil, r.Client.Delete(context.TODO(), existingClusterRole) 307 } 308 309 // if the Rules differ, update the Role 310 if !reflect.DeepEqual(existingClusterRole.Rules, clusterRole.Rules) { 311 existingClusterRole.Rules = clusterRole.Rules 312 if err := r.Client.Update(context.TODO(), existingClusterRole); err != nil { 313 return nil, err 314 } 315 } 316 return existingClusterRole, nil 317 } 318 319 func deleteClusterRoles(c client.Client, clusterRoleList *v1.ClusterRoleList) error { 320 for _, clusterRole := range clusterRoleList.Items { 321 if err := c.Delete(context.TODO(), &clusterRole); err != nil { 322 return fmt.Errorf("failed to delete ClusterRole %q during cleanup: %w", clusterRole.Name, err) 323 } 324 } 325 return nil 326 }