github.com/argoproj/argo-cd@v1.8.7/util/clusterauth/clusterauth.go (about) 1 package clusterauth 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 jwt "github.com/dgrijalva/jwt-go/v4" 10 log "github.com/sirupsen/logrus" 11 corev1 "k8s.io/api/core/v1" 12 rbacv1 "k8s.io/api/rbac/v1" 13 apierr "k8s.io/apimachinery/pkg/api/errors" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/util/wait" 16 "k8s.io/client-go/kubernetes" 17 ) 18 19 // ArgoCDManagerServiceAccount is the name of the service account for managing a cluster 20 const ( 21 ArgoCDManagerServiceAccount = "argocd-manager" 22 ArgoCDManagerClusterRole = "argocd-manager-role" 23 ArgoCDManagerClusterRoleBinding = "argocd-manager-role-binding" 24 ) 25 26 // ArgoCDManagerPolicyRules are the policies to give argocd-manager 27 var ArgoCDManagerClusterPolicyRules = []rbacv1.PolicyRule{ 28 { 29 APIGroups: []string{"*"}, 30 Resources: []string{"*"}, 31 Verbs: []string{"*"}, 32 }, 33 { 34 NonResourceURLs: []string{"*"}, 35 Verbs: []string{"*"}, 36 }, 37 } 38 39 // ArgoCDManagerNamespacePolicyRules are the namespace level policies to give argocd-manager 40 var ArgoCDManagerNamespacePolicyRules = []rbacv1.PolicyRule{ 41 { 42 APIGroups: []string{"*"}, 43 Resources: []string{"*"}, 44 Verbs: []string{"*"}, 45 }, 46 } 47 48 // CreateServiceAccount creates a service account in a given namespace 49 func CreateServiceAccount( 50 clientset kubernetes.Interface, 51 serviceAccountName string, 52 namespace string, 53 ) error { 54 serviceAccount := corev1.ServiceAccount{ 55 TypeMeta: metav1.TypeMeta{ 56 APIVersion: "v1", 57 Kind: "ServiceAccount", 58 }, 59 ObjectMeta: metav1.ObjectMeta{ 60 Name: serviceAccountName, 61 Namespace: namespace, 62 }, 63 } 64 _, err := clientset.CoreV1().ServiceAccounts(namespace).Create(context.Background(), &serviceAccount, metav1.CreateOptions{}) 65 if err != nil { 66 if !apierr.IsAlreadyExists(err) { 67 return fmt.Errorf("Failed to create service account %q in namespace %q: %v", serviceAccountName, namespace, err) 68 } 69 log.Infof("ServiceAccount %q already exists in namespace %q", serviceAccountName, namespace) 70 return nil 71 } 72 log.Infof("ServiceAccount %q created in namespace %q", serviceAccountName, namespace) 73 return nil 74 } 75 76 func upsert(kind string, name string, create func() (interface{}, error), update func() (interface{}, error)) error { 77 _, err := create() 78 if err != nil { 79 if !apierr.IsAlreadyExists(err) { 80 return fmt.Errorf("Failed to create %s %q: %v", kind, name, err) 81 } 82 _, err = update() 83 if err != nil { 84 return fmt.Errorf("Failed to update %s %q: %v", kind, name, err) 85 } 86 log.Infof("%s %q updated", kind, name) 87 } else { 88 log.Infof("%s %q created", kind, name) 89 } 90 return nil 91 } 92 93 func upsertClusterRole(clientset kubernetes.Interface, name string, rules []rbacv1.PolicyRule) error { 94 clusterRole := rbacv1.ClusterRole{ 95 TypeMeta: metav1.TypeMeta{ 96 APIVersion: "rbac.authorization.k8s.io/v1", 97 Kind: "ClusterRole", 98 }, 99 ObjectMeta: metav1.ObjectMeta{ 100 Name: name, 101 }, 102 Rules: rules, 103 } 104 return upsert("ClusterRole", name, func() (interface{}, error) { 105 return clientset.RbacV1().ClusterRoles().Create(context.Background(), &clusterRole, metav1.CreateOptions{}) 106 }, func() (interface{}, error) { 107 return clientset.RbacV1().ClusterRoles().Update(context.Background(), &clusterRole, metav1.UpdateOptions{}) 108 }) 109 } 110 111 func upsertRole(clientset kubernetes.Interface, name string, namespace string, rules []rbacv1.PolicyRule) error { 112 role := rbacv1.Role{ 113 TypeMeta: metav1.TypeMeta{ 114 APIVersion: "rbac.authorization.k8s.io/v1", 115 Kind: "Role", 116 }, 117 ObjectMeta: metav1.ObjectMeta{ 118 Name: name, 119 }, 120 Rules: rules, 121 } 122 return upsert("Role", fmt.Sprintf("%s/%s", namespace, name), func() (interface{}, error) { 123 return clientset.RbacV1().Roles(namespace).Create(context.Background(), &role, metav1.CreateOptions{}) 124 }, func() (interface{}, error) { 125 return clientset.RbacV1().Roles(namespace).Update(context.Background(), &role, metav1.UpdateOptions{}) 126 }) 127 } 128 129 func upsertClusterRoleBinding(clientset kubernetes.Interface, name string, clusterRoleName string, subject rbacv1.Subject) error { 130 roleBinding := rbacv1.ClusterRoleBinding{ 131 TypeMeta: metav1.TypeMeta{ 132 APIVersion: "rbac.authorization.k8s.io/v1", 133 Kind: "ClusterRoleBinding", 134 }, 135 ObjectMeta: metav1.ObjectMeta{ 136 Name: name, 137 }, 138 RoleRef: rbacv1.RoleRef{ 139 APIGroup: "rbac.authorization.k8s.io", 140 Kind: "ClusterRole", 141 Name: clusterRoleName, 142 }, 143 Subjects: []rbacv1.Subject{subject}, 144 } 145 return upsert("ClusterRoleBinding", name, func() (interface{}, error) { 146 return clientset.RbacV1().ClusterRoleBindings().Create(context.Background(), &roleBinding, metav1.CreateOptions{}) 147 }, func() (interface{}, error) { 148 return clientset.RbacV1().ClusterRoleBindings().Update(context.Background(), &roleBinding, metav1.UpdateOptions{}) 149 }) 150 } 151 152 func upsertRoleBinding(clientset kubernetes.Interface, name string, roleName string, namespace string, subject rbacv1.Subject) error { 153 roleBinding := rbacv1.RoleBinding{ 154 TypeMeta: metav1.TypeMeta{ 155 APIVersion: "rbac.authorization.k8s.io/v1", 156 Kind: "RoleBinding", 157 }, 158 ObjectMeta: metav1.ObjectMeta{ 159 Name: name, 160 }, 161 RoleRef: rbacv1.RoleRef{ 162 APIGroup: "rbac.authorization.k8s.io", 163 Kind: "Role", 164 Name: roleName, 165 }, 166 Subjects: []rbacv1.Subject{subject}, 167 } 168 return upsert("RoleBinding", fmt.Sprintf("%s/%s", namespace, name), func() (interface{}, error) { 169 return clientset.RbacV1().RoleBindings(namespace).Create(context.Background(), &roleBinding, metav1.CreateOptions{}) 170 }, func() (interface{}, error) { 171 return clientset.RbacV1().RoleBindings(namespace).Update(context.Background(), &roleBinding, metav1.UpdateOptions{}) 172 }) 173 } 174 175 // InstallClusterManagerRBAC installs RBAC resources for a cluster manager to operate a cluster. Returns a token 176 func InstallClusterManagerRBAC(clientset kubernetes.Interface, ns string, namespaces []string) (string, error) { 177 178 err := CreateServiceAccount(clientset, ArgoCDManagerServiceAccount, ns) 179 if err != nil { 180 return "", err 181 } 182 183 if len(namespaces) == 0 { 184 err = upsertClusterRole(clientset, ArgoCDManagerClusterRole, ArgoCDManagerClusterPolicyRules) 185 if err != nil { 186 return "", err 187 } 188 189 err = upsertClusterRoleBinding(clientset, ArgoCDManagerClusterRoleBinding, ArgoCDManagerClusterRole, rbacv1.Subject{ 190 Kind: rbacv1.ServiceAccountKind, 191 Name: ArgoCDManagerServiceAccount, 192 Namespace: ns, 193 }) 194 if err != nil { 195 return "", err 196 } 197 } else { 198 for _, namespace := range namespaces { 199 err = upsertRole(clientset, ArgoCDManagerClusterRole, namespace, ArgoCDManagerNamespacePolicyRules) 200 if err != nil { 201 return "", err 202 } 203 204 err = upsertRoleBinding(clientset, ArgoCDManagerClusterRoleBinding, ArgoCDManagerClusterRole, namespace, rbacv1.Subject{ 205 Kind: rbacv1.ServiceAccountKind, 206 Name: ArgoCDManagerServiceAccount, 207 Namespace: ns, 208 }) 209 if err != nil { 210 return "", err 211 } 212 } 213 } 214 215 return GetServiceAccountBearerToken(clientset, ns, ArgoCDManagerServiceAccount) 216 } 217 218 // GetServiceAccountBearerToken will attempt to get the provided service account until it 219 // exists, iterate the secrets associated with it looking for one of type 220 // kubernetes.io/service-account-token, and return it's token if found. 221 func GetServiceAccountBearerToken(clientset kubernetes.Interface, ns string, sa string) (string, error) { 222 var serviceAccount *corev1.ServiceAccount 223 var secret *corev1.Secret 224 var err error 225 err = wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) { 226 serviceAccount, err = clientset.CoreV1().ServiceAccounts(ns).Get(context.Background(), sa, metav1.GetOptions{}) 227 if err != nil { 228 return false, err 229 } 230 // Scan all secrets looking for one of the correct type: 231 for _, oRef := range serviceAccount.Secrets { 232 var getErr error 233 secret, err = clientset.CoreV1().Secrets(ns).Get(context.Background(), oRef.Name, metav1.GetOptions{}) 234 if err != nil { 235 return false, fmt.Errorf("Failed to retrieve secret %q: %v", oRef.Name, getErr) 236 } 237 if secret.Type == corev1.SecretTypeServiceAccountToken { 238 return true, nil 239 } 240 } 241 return false, nil 242 }) 243 if err != nil { 244 return "", fmt.Errorf("Failed to wait for service account secret: %v", err) 245 } 246 token, ok := secret.Data["token"] 247 if !ok { 248 return "", fmt.Errorf("Secret %q for service account %q did not have a token", secret.Name, serviceAccount) 249 } 250 return string(token), nil 251 } 252 253 // UninstallClusterManagerRBAC removes RBAC resources for a cluster manager to operate a cluster 254 func UninstallClusterManagerRBAC(clientset kubernetes.Interface) error { 255 return UninstallRBAC(clientset, "kube-system", ArgoCDManagerClusterRoleBinding, ArgoCDManagerClusterRole, ArgoCDManagerServiceAccount) 256 } 257 258 // UninstallRBAC uninstalls RBAC related resources for a binding, role, and service account 259 func UninstallRBAC(clientset kubernetes.Interface, namespace, bindingName, roleName, serviceAccount string) error { 260 if err := clientset.RbacV1().ClusterRoleBindings().Delete(context.Background(), bindingName, metav1.DeleteOptions{}); err != nil { 261 if !apierr.IsNotFound(err) { 262 return fmt.Errorf("Failed to delete ClusterRoleBinding: %v", err) 263 } 264 log.Infof("ClusterRoleBinding %q not found", bindingName) 265 } else { 266 log.Infof("ClusterRoleBinding %q deleted", bindingName) 267 } 268 269 if err := clientset.RbacV1().ClusterRoles().Delete(context.Background(), roleName, metav1.DeleteOptions{}); err != nil { 270 if !apierr.IsNotFound(err) { 271 return fmt.Errorf("Failed to delete ClusterRole: %v", err) 272 } 273 log.Infof("ClusterRole %q not found", roleName) 274 } else { 275 log.Infof("ClusterRole %q deleted", roleName) 276 } 277 278 if err := clientset.CoreV1().ServiceAccounts(namespace).Delete(context.Background(), serviceAccount, metav1.DeleteOptions{}); err != nil { 279 if !apierr.IsNotFound(err) { 280 return fmt.Errorf("Failed to delete ServiceAccount: %v", err) 281 } 282 log.Infof("ServiceAccount %q in namespace %q not found", serviceAccount, namespace) 283 } else { 284 log.Infof("ServiceAccount %q deleted", serviceAccount) 285 } 286 return nil 287 } 288 289 type ServiceAccountClaims struct { 290 Sub string `json:"sub"` 291 Iss string `json:"iss"` 292 Namespace string `json:"kubernetes.io/serviceaccount/namespace"` 293 SecretName string `json:"kubernetes.io/serviceaccount/secret.name"` 294 ServiceAccountName string `json:"kubernetes.io/serviceaccount/service-account.name"` 295 ServiceAccountUID string `json:"kubernetes.io/serviceaccount/service-account.uid"` 296 } 297 298 // Valid satisfies the jwt.Claims interface to enable JWT parsing 299 func (sac *ServiceAccountClaims) Valid(helper *jwt.ValidationHelper) error { 300 return nil 301 } 302 303 // ParseServiceAccountToken parses a Kubernetes service account token 304 func ParseServiceAccountToken(token string) (*ServiceAccountClaims, error) { 305 parser := &jwt.Parser{ 306 ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()), 307 } 308 var claims ServiceAccountClaims 309 _, _, err := parser.ParseUnverified(token, &claims) 310 if err != nil { 311 return nil, fmt.Errorf("Failed to parse service account token: %s", err) 312 } 313 return &claims, nil 314 } 315 316 // GenerateNewClusterManagerSecret creates a new secret derived with same metadata as existing one 317 // and waits until the secret is populated with a bearer token 318 func GenerateNewClusterManagerSecret(clientset kubernetes.Interface, claims *ServiceAccountClaims) (*corev1.Secret, error) { 319 secretsClient := clientset.CoreV1().Secrets(claims.Namespace) 320 existingSecret, err := secretsClient.Get(context.Background(), claims.SecretName, metav1.GetOptions{}) 321 if err != nil { 322 return nil, err 323 } 324 var newSecret corev1.Secret 325 secretNameSplit := strings.Split(claims.SecretName, "-") 326 if len(secretNameSplit) > 0 { 327 secretNameSplit = secretNameSplit[:len(secretNameSplit)-1] 328 } 329 newSecret.Type = corev1.SecretTypeServiceAccountToken 330 newSecret.GenerateName = strings.Join(secretNameSplit, "-") + "-" 331 newSecret.Annotations = existingSecret.Annotations 332 // We will create an empty secret and let kubernetes populate the data 333 newSecret.Data = nil 334 335 created, err := secretsClient.Create(context.Background(), &newSecret, metav1.CreateOptions{}) 336 if err != nil { 337 return nil, err 338 } 339 340 err = wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) { 341 created, err = secretsClient.Get(context.Background(), created.Name, metav1.GetOptions{}) 342 if err != nil { 343 return false, err 344 } 345 if len(created.Data) == 0 { 346 return false, nil 347 } 348 return true, nil 349 }) 350 if err != nil { 351 return nil, fmt.Errorf("Timed out waiting for secret to generate new token") 352 } 353 return created, nil 354 } 355 356 // RotateServiceAccountSecrets rotates the entries in the service accounts secrets list 357 func RotateServiceAccountSecrets(clientset kubernetes.Interface, claims *ServiceAccountClaims, newSecret *corev1.Secret) error { 358 // 1. update service account secrets list with new secret name while also removing the old name 359 saClient := clientset.CoreV1().ServiceAccounts(claims.Namespace) 360 sa, err := saClient.Get(context.Background(), claims.ServiceAccountName, metav1.GetOptions{}) 361 if err != nil { 362 return err 363 } 364 var newSecretsList []corev1.ObjectReference 365 alreadyPresent := false 366 for _, objRef := range sa.Secrets { 367 if objRef.Name == claims.SecretName { 368 continue 369 } 370 if objRef.Name == newSecret.Name { 371 alreadyPresent = true 372 } 373 newSecretsList = append(newSecretsList, objRef) 374 } 375 if !alreadyPresent { 376 sa.Secrets = append(newSecretsList, corev1.ObjectReference{Name: newSecret.Name}) 377 } 378 _, err = saClient.Update(context.Background(), sa, metav1.UpdateOptions{}) 379 if err != nil { 380 return err 381 } 382 383 // 2. delete existing secret object 384 secretsClient := clientset.CoreV1().Secrets(claims.Namespace) 385 err = secretsClient.Delete(context.Background(), claims.SecretName, metav1.DeleteOptions{}) 386 if !apierr.IsNotFound(err) { 387 return err 388 } 389 return nil 390 }