github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/rbac.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provider 5 6 import ( 7 "context" 8 "fmt" 9 "reflect" 10 "sort" 11 "time" 12 13 jujuclock "github.com/juju/clock" 14 "github.com/juju/collections/set" 15 "github.com/juju/errors" 16 "github.com/juju/names/v5" 17 "github.com/juju/retry" 18 authenticationv1 "k8s.io/api/authentication/v1" 19 core "k8s.io/api/core/v1" 20 rbacv1 "k8s.io/api/rbac/v1" 21 k8serrors "k8s.io/apimachinery/pkg/api/errors" 22 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 k8slabels "k8s.io/apimachinery/pkg/labels" 24 "k8s.io/apimachinery/pkg/types" 25 26 "github.com/juju/juju/caas/kubernetes/provider/constants" 27 "github.com/juju/juju/caas/kubernetes/provider/resources" 28 k8sspecs "github.com/juju/juju/caas/kubernetes/provider/specs" 29 "github.com/juju/juju/caas/kubernetes/provider/utils" 30 environsbootstrap "github.com/juju/juju/environs/bootstrap" 31 ) 32 33 // AppNameForServiceAccount returns the juju application name associated with a 34 // given ServiceAccount. If app name cannot be obtained from the service 35 // account, errors.NotFound is returned. 36 func AppNameForServiceAccount(sa *core.ServiceAccount) (string, error) { 37 if appName, ok := sa.Labels[constants.LabelKubernetesAppName]; ok { 38 return appName, nil 39 } else if appName, ok := sa.Labels[constants.LegacyLabelKubernetesAppName]; ok { 40 return appName, nil 41 } 42 return "", errors.NotFoundf("application labels for service account %s", sa.Name) 43 } 44 45 // RBACLabels returns a set of labels that should be present for RBAC objects. 46 func RBACLabels(appName, model string, global, legacy bool) map[string]string { 47 labels := utils.LabelsForApp(appName, legacy) 48 if global { 49 labels = utils.LabelsMerge(labels, utils.LabelsForModel(model, legacy)) 50 } 51 return labels 52 } 53 54 func (k *kubernetesClient) ensureServiceAccountForApp( 55 appName string, annotations map[string]string, rbacDefinition k8sspecs.K8sRBACSpecConverter, 56 ) (cleanups []func(), err error) { 57 ctx := context.TODO() 58 59 prefixNameSpace := func(name string) string { 60 return fmt.Sprintf("%s-%s", k.namespace, name) 61 } 62 getBindingName := func(sa, cR k8sspecs.NameGetter) string { 63 if sa.GetName() == cR.GetName() { 64 return sa.GetName() 65 } 66 return fmt.Sprintf("%s-%s", sa.GetName(), cR.GetName()) 67 } 68 getSAMeta := func(name string) v1.ObjectMeta { 69 return v1.ObjectMeta{ 70 Name: name, 71 Namespace: k.namespace, 72 Labels: RBACLabels(appName, k.CurrentModel(), false, k.IsLegacyLabels()), 73 Annotations: annotations, 74 } 75 } 76 getRoleClusterRoleName := func(roleName, serviceAccountName string, index int, global bool) (out string) { 77 defer func() { 78 if global { 79 out = prefixNameSpace(out) 80 } 81 }() 82 if roleName != "" { 83 return roleName 84 } 85 out = serviceAccountName 86 if index == 0 { 87 return out 88 } 89 return fmt.Sprintf("%s%d", out, index) 90 } 91 getRoleMeta := func(roleName, serviceAccountName string, index int) v1.ObjectMeta { 92 return v1.ObjectMeta{ 93 Name: getRoleClusterRoleName(roleName, serviceAccountName, index, false), 94 Namespace: k.namespace, 95 Labels: RBACLabels(appName, k.CurrentModel(), false, k.IsLegacyLabels()), 96 Annotations: annotations, 97 } 98 } 99 getClusterRoleMeta := func(roleName, serviceAccountName string, index int) v1.ObjectMeta { 100 return v1.ObjectMeta{ 101 Name: getRoleClusterRoleName(roleName, serviceAccountName, index, true), 102 Namespace: k.namespace, 103 Labels: RBACLabels(appName, k.CurrentModel(), true, k.IsLegacyLabels()), 104 Annotations: annotations, 105 } 106 } 107 getBindingMeta := func(sa, role k8sspecs.NameGetter) v1.ObjectMeta { 108 return v1.ObjectMeta{ 109 Name: getBindingName(sa, role), 110 Namespace: k.namespace, 111 Labels: RBACLabels(appName, k.CurrentModel(), false, k.IsLegacyLabels()), 112 Annotations: annotations, 113 } 114 } 115 getClusterBindingMeta := func(sa, clusterRole k8sspecs.NameGetter) v1.ObjectMeta { 116 return v1.ObjectMeta{ 117 Name: getBindingName(sa, clusterRole), 118 Namespace: k.namespace, 119 Labels: RBACLabels(appName, k.CurrentModel(), true, k.IsLegacyLabels()), 120 Annotations: annotations, 121 } 122 } 123 124 serviceAccounts, roles, clusterroles, roleBindings, clusterRoleBindings := rbacDefinition.ToK8s( 125 getSAMeta, 126 getRoleMeta, 127 getClusterRoleMeta, 128 getBindingMeta, 129 getClusterBindingMeta, 130 ) 131 132 for _, spec := range serviceAccounts { 133 _, sacleanups, err := k.ensureServiceAccount(&spec) 134 cleanups = append(cleanups, sacleanups...) 135 if err != nil { 136 return cleanups, errors.Trace(err) 137 } 138 } 139 140 for _, spec := range roles { 141 _, rCleanups, err := k.ensureRole(&spec) 142 cleanups = append(cleanups, rCleanups...) 143 if err != nil { 144 return cleanups, errors.Trace(err) 145 } 146 } 147 for _, spec := range roleBindings { 148 _, rbCleanups, err := k.ensureRoleBinding(&spec) 149 cleanups = append(cleanups, rbCleanups...) 150 if err != nil { 151 return cleanups, errors.Trace(err) 152 } 153 } 154 155 for _, spec := range clusterroles { 156 cr := resources.ClusterRole{spec} 157 cRCleanups, err := cr.Ensure( 158 ctx, 159 k.client(), 160 resources.ClaimJujuOwnership, 161 ) 162 cleanups = append(cleanups, cRCleanups...) 163 if err != nil { 164 return cleanups, errors.Trace(err) 165 } 166 } 167 for _, spec := range clusterRoleBindings { 168 clusterRoleBinding := resources.NewClusterRoleBinding(spec.Name, &spec) 169 crbCleanups, err := clusterRoleBinding.Ensure( 170 ctx, 171 k.client(), 172 resources.ClaimJujuOwnership, 173 ) 174 cleanups = append(cleanups, crbCleanups...) 175 if err != nil { 176 return cleanups, errors.Trace(err) 177 } 178 } 179 180 return cleanups, nil 181 } 182 183 func (k *kubernetesClient) deleteAllServiceAccountResources(appName string) error { 184 selectorNamespaced := utils.LabelsToSelector( 185 RBACLabels(appName, k.CurrentModel(), false, k.IsLegacyLabels())) 186 selectorGlobal := utils.LabelsToSelector( 187 RBACLabels(appName, k.CurrentModel(), true, k.IsLegacyLabels())) 188 if err := k.deleteRoleBindings(selectorNamespaced); err != nil { 189 return errors.Trace(err) 190 } 191 if err := k.deleteClusterRoleBindings(selectorGlobal); err != nil { 192 return errors.Trace(err) 193 } 194 if err := k.deleteRoles(selectorNamespaced); err != nil { 195 return errors.Trace(err) 196 } 197 if err := k.deleteClusterRoles(selectorGlobal); err != nil { 198 return errors.Trace(err) 199 } 200 if err := k.deleteServiceAccounts(selectorNamespaced); err != nil { 201 return errors.Trace(err) 202 } 203 return nil 204 } 205 206 func (k *kubernetesClient) createServiceAccount(sa *core.ServiceAccount) (*core.ServiceAccount, error) { 207 if k.namespace == "" { 208 return nil, errNoNamespace 209 } 210 utils.PurifyResource(sa) 211 out, err := k.client().CoreV1().ServiceAccounts(k.namespace).Create(context.TODO(), sa, v1.CreateOptions{}) 212 if k8serrors.IsAlreadyExists(err) { 213 return nil, errors.AlreadyExistsf("service account %q", sa.GetName()) 214 } 215 return out, errors.Trace(err) 216 } 217 218 func (k *kubernetesClient) updateServiceAccount(sa *core.ServiceAccount) (*core.ServiceAccount, error) { 219 if k.namespace == "" { 220 return nil, errNoNamespace 221 } 222 out, err := k.client().CoreV1().ServiceAccounts(k.namespace).Update(context.TODO(), sa, v1.UpdateOptions{}) 223 if k8serrors.IsNotFound(err) { 224 return nil, errors.NotFoundf("service account %q", sa.GetName()) 225 } 226 return out, errors.Trace(err) 227 } 228 229 func (k *kubernetesClient) ensureServiceAccount(sa *core.ServiceAccount) (out *core.ServiceAccount, cleanups []func(), err error) { 230 out, err = k.createServiceAccount(sa) 231 if err == nil { 232 logger.Debugf("service account %q created", out.GetName()) 233 cleanups = append(cleanups, func() { _ = k.deleteServiceAccount(out.GetName(), out.GetUID()) }) 234 return out, cleanups, nil 235 } 236 if !errors.IsAlreadyExists(err) { 237 return nil, cleanups, errors.Trace(err) 238 } 239 _, err = k.listServiceAccount(sa.GetLabels()) 240 if err != nil { 241 if errors.IsNotFound(err) { 242 // sa.Name is already used for an existing service account. 243 return nil, cleanups, errors.AlreadyExistsf("service account %q", sa.GetName()) 244 } 245 return nil, cleanups, errors.Trace(err) 246 } 247 out, err = k.updateServiceAccount(sa) 248 logger.Debugf("updating service account %q", sa.GetName()) 249 return out, cleanups, errors.Trace(err) 250 } 251 252 func (k *kubernetesClient) getServiceAccount(name string) (*core.ServiceAccount, error) { 253 if k.namespace == "" { 254 return nil, errNoNamespace 255 } 256 out, err := k.client().CoreV1().ServiceAccounts(k.namespace).Get(context.TODO(), name, v1.GetOptions{}) 257 if k8serrors.IsNotFound(err) { 258 return nil, errors.NotFoundf("service account %q", name) 259 } 260 return out, errors.Trace(err) 261 } 262 263 func (k *kubernetesClient) deleteServiceAccount(name string, uid types.UID) error { 264 if k.namespace == "" { 265 return errNoNamespace 266 } 267 err := k.client().CoreV1().ServiceAccounts(k.namespace).Delete(context.TODO(), name, utils.NewPreconditionDeleteOptions(uid)) 268 if k8serrors.IsNotFound(err) { 269 return nil 270 } 271 return errors.Trace(err) 272 } 273 274 func (k *kubernetesClient) deleteServiceAccounts(selectors ...k8slabels.Selector) error { 275 for _, selector := range selectors { 276 err := k.client().CoreV1().ServiceAccounts(k.namespace).DeleteCollection( 277 context.TODO(), 278 v1.DeleteOptions{ 279 PropagationPolicy: constants.DefaultPropagationPolicy(), 280 }, v1.ListOptions{ 281 LabelSelector: selector.String(), 282 }) 283 if !k8serrors.IsNotFound(err) { 284 return errors.Trace(err) 285 } 286 } 287 return nil 288 } 289 290 func (k *kubernetesClient) listServiceAccount(labels map[string]string) ([]core.ServiceAccount, error) { 291 if k.namespace == "" { 292 return nil, errNoNamespace 293 } 294 listOps := v1.ListOptions{ 295 LabelSelector: utils.LabelsToSelector(labels).String(), 296 } 297 saList, err := k.client().CoreV1().ServiceAccounts(k.namespace).List(context.TODO(), listOps) 298 if err != nil { 299 return nil, errors.Trace(err) 300 } 301 if len(saList.Items) == 0 { 302 return nil, errors.NotFoundf("service account with labels %v", labels) 303 } 304 return saList.Items, nil 305 } 306 307 func (k *kubernetesClient) createRole(role *rbacv1.Role) (*rbacv1.Role, error) { 308 if k.namespace == "" { 309 return nil, errNoNamespace 310 } 311 utils.PurifyResource(role) 312 out, err := k.client().RbacV1().Roles(k.namespace).Create(context.TODO(), role, v1.CreateOptions{}) 313 if k8serrors.IsAlreadyExists(err) { 314 return nil, errors.AlreadyExistsf("role %q", role.GetName()) 315 } 316 return out, errors.Trace(err) 317 } 318 319 func (k *kubernetesClient) updateRole(role *rbacv1.Role) (*rbacv1.Role, error) { 320 if k.namespace == "" { 321 return nil, errNoNamespace 322 } 323 out, err := k.client().RbacV1().Roles(k.namespace).Update(context.TODO(), role, v1.UpdateOptions{}) 324 if k8serrors.IsNotFound(err) { 325 return nil, errors.NotFoundf("role %q", role.GetName()) 326 } 327 return out, errors.Trace(err) 328 } 329 330 func (k *kubernetesClient) ensureRole(role *rbacv1.Role) (out *rbacv1.Role, cleanups []func(), err error) { 331 out, err = k.createRole(role) 332 if err == nil { 333 logger.Debugf("role %q created", out.GetName()) 334 cleanups = append(cleanups, func() { _ = k.deleteRole(out.GetName(), out.GetUID()) }) 335 return out, cleanups, nil 336 } 337 if !errors.IsAlreadyExists(err) { 338 return nil, cleanups, errors.Trace(err) 339 } 340 _, err = k.listRoles(utils.LabelsToSelector(role.GetLabels())) 341 if err != nil { 342 if errors.IsNotFound(err) { 343 // role.Name is already used for an existing role. 344 return nil, cleanups, errors.AlreadyExistsf("role %q", role.GetName()) 345 } 346 return nil, cleanups, errors.Trace(err) 347 } 348 out, err = k.updateRole(role) 349 logger.Debugf("updating role %q", role.GetName()) 350 return out, cleanups, errors.Trace(err) 351 } 352 353 func (k *kubernetesClient) getRole(name string) (*rbacv1.Role, error) { 354 if k.namespace == "" { 355 return nil, errNoNamespace 356 } 357 out, err := k.client().RbacV1().Roles(k.namespace).Get(context.TODO(), name, v1.GetOptions{}) 358 if k8serrors.IsNotFound(err) { 359 return nil, errors.NotFoundf("role %q", name) 360 } 361 return out, errors.Trace(err) 362 } 363 364 func (k *kubernetesClient) deleteRole(name string, uid types.UID) error { 365 if k.namespace == "" { 366 return errNoNamespace 367 } 368 err := k.client().RbacV1().Roles(k.namespace).Delete(context.TODO(), name, utils.NewPreconditionDeleteOptions(uid)) 369 if k8serrors.IsNotFound(err) { 370 return nil 371 } 372 return errors.Trace(err) 373 } 374 375 func (k *kubernetesClient) deleteRoles(selectors ...k8slabels.Selector) error { 376 if k.namespace == "" { 377 return errNoNamespace 378 } 379 for _, selector := range selectors { 380 err := k.client().RbacV1().Roles(k.namespace).DeleteCollection( 381 context.TODO(), 382 v1.DeleteOptions{ 383 PropagationPolicy: constants.DefaultPropagationPolicy(), 384 }, v1.ListOptions{ 385 LabelSelector: selector.String(), 386 }) 387 if !k8serrors.IsNotFound(err) { 388 return errors.Trace(err) 389 } 390 } 391 return nil 392 } 393 394 func (k *kubernetesClient) listRoles(selector k8slabels.Selector) ([]rbacv1.Role, error) { 395 if k.namespace == "" { 396 return nil, errNoNamespace 397 } 398 listOps := v1.ListOptions{ 399 LabelSelector: selector.String(), 400 } 401 rList, err := k.client().RbacV1().Roles(k.namespace).List(context.TODO(), listOps) 402 if err != nil { 403 return nil, errors.Trace(err) 404 } 405 if len(rList.Items) == 0 { 406 return nil, errors.NotFoundf("role with selector %q", selector) 407 } 408 return rList.Items, nil 409 } 410 411 func (k *kubernetesClient) createClusterRole(clusterrole *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error) { 412 if k.namespace == "" { 413 return nil, errNoNamespace 414 } 415 utils.PurifyResource(clusterrole) 416 out, err := k.client().RbacV1().ClusterRoles().Create(context.TODO(), clusterrole, v1.CreateOptions{}) 417 if k8serrors.IsAlreadyExists(err) { 418 return nil, errors.AlreadyExistsf("clusterrole %q", clusterrole.GetName()) 419 } 420 return out, errors.Trace(err) 421 } 422 423 func (k *kubernetesClient) updateClusterRole(clusterrole *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error) { 424 if k.namespace == "" { 425 return nil, errNoNamespace 426 } 427 out, err := k.client().RbacV1().ClusterRoles().Update(context.TODO(), clusterrole, v1.UpdateOptions{}) 428 if k8serrors.IsNotFound(err) { 429 return nil, errors.NotFoundf("clusterrole %q", clusterrole.GetName()) 430 } 431 return out, errors.Trace(err) 432 } 433 434 func (k *kubernetesClient) getClusterRole(name string) (*rbacv1.ClusterRole, error) { 435 if k.namespace == "" { 436 return nil, errNoNamespace 437 } 438 out, err := k.client().RbacV1().ClusterRoles().Get(context.TODO(), name, v1.GetOptions{}) 439 if k8serrors.IsNotFound(err) { 440 return nil, errors.NotFoundf("clusterrole %q", name) 441 } 442 return out, errors.Trace(err) 443 } 444 445 func (k *kubernetesClient) deleteClusterRoles(selector k8slabels.Selector) error { 446 err := k.client().RbacV1().ClusterRoles().DeleteCollection(context.TODO(), v1.DeleteOptions{ 447 PropagationPolicy: constants.DefaultPropagationPolicy(), 448 }, v1.ListOptions{ 449 LabelSelector: selector.String(), 450 }) 451 if k8serrors.IsNotFound(err) { 452 return nil 453 } 454 return errors.Trace(err) 455 } 456 457 func (k *kubernetesClient) listClusterRoles(selector k8slabels.Selector) ([]rbacv1.ClusterRole, error) { 458 listOps := v1.ListOptions{ 459 LabelSelector: selector.String(), 460 } 461 cRList, err := k.client().RbacV1().ClusterRoles().List(context.TODO(), listOps) 462 if err != nil { 463 return nil, errors.Trace(err) 464 } 465 if len(cRList.Items) == 0 { 466 return nil, errors.NotFoundf("cluster role with selector %q", selector) 467 } 468 return cRList.Items, nil 469 } 470 471 func (k *kubernetesClient) createRoleBinding(rb *rbacv1.RoleBinding) (*rbacv1.RoleBinding, error) { 472 if k.namespace == "" { 473 return nil, errNoNamespace 474 } 475 utils.PurifyResource(rb) 476 out, err := k.client().RbacV1().RoleBindings(k.namespace).Create(context.TODO(), rb, v1.CreateOptions{}) 477 if k8serrors.IsAlreadyExists(err) { 478 return nil, errors.AlreadyExistsf("role binding %q", rb.GetName()) 479 } 480 return out, errors.Trace(err) 481 } 482 483 func ensureResourceDeleted(clock jujuclock.Clock, getResource func() error) error { 484 notReadyYetErr := errors.New("resource is still being deleted") 485 deletionChecker := func() error { 486 err := getResource() 487 if errors.IsNotFound(err) { 488 return nil 489 } 490 if err == nil { 491 return notReadyYetErr 492 } 493 return errors.Trace(err) 494 } 495 496 err := retry.Call(retry.CallArgs{ 497 Attempts: 10, 498 Delay: 2 * time.Second, 499 Clock: clock, 500 Func: deletionChecker, 501 IsFatalError: func(err error) bool { 502 return err != nil && err != notReadyYetErr 503 }, 504 NotifyFunc: func(error, int) { 505 logger.Debugf("waiting for resource to be deleted") 506 }, 507 }) 508 return errors.Trace(err) 509 } 510 511 func isRoleBindingEqual(a, b rbacv1.RoleBinding) bool { 512 sortSubjects := func(s []rbacv1.Subject) { 513 sort.Slice(s, func(i, j int) bool { 514 return s[i].Name+s[i].Namespace+s[i].Kind > s[j].Name+s[j].Namespace+s[j].Kind 515 }) 516 } 517 sortSubjects(a.Subjects) 518 sortSubjects(b.Subjects) 519 520 // We don't compare labels. 521 return reflect.DeepEqual(a.RoleRef, b.RoleRef) && 522 reflect.DeepEqual(a.Subjects, b.Subjects) && 523 reflect.DeepEqual(a.ObjectMeta.Annotations, b.ObjectMeta.Annotations) 524 } 525 526 func (k *kubernetesClient) ensureRoleBinding(rb *rbacv1.RoleBinding) (out *rbacv1.RoleBinding, cleanups []func(), err error) { 527 isFirstDeploy := false 528 // RoleRef is immutable, so delete first then re-create. 529 existing, err := k.getRoleBinding(rb.GetName()) 530 if errors.Is(err, errors.NotFound) { 531 isFirstDeploy = true 532 } else if err != nil { 533 return nil, cleanups, errors.Trace(err) 534 } 535 if existing != nil { 536 if isRoleBindingEqual(*existing, *rb) { 537 return existing, cleanups, nil 538 } 539 name := existing.GetName() 540 UID := existing.GetUID() 541 if err := k.deleteRoleBinding(name, UID); err != nil { 542 return nil, cleanups, errors.Trace(err) 543 } 544 545 if err := ensureResourceDeleted( 546 k.clock, 547 func() error { 548 _, err := k.getRoleBinding(name) 549 return errors.Trace(err) 550 }, 551 ); err != nil { 552 return nil, cleanups, errors.Trace(err) 553 } 554 } 555 out, err = k.createRoleBinding(rb) 556 if err != nil { 557 return nil, cleanups, errors.Trace(err) 558 } 559 if isFirstDeploy { 560 // only do cleanup for the first time, don't do this for existing deployments. 561 cleanups = append(cleanups, func() { _ = k.deleteRoleBinding(out.GetName(), out.GetUID()) }) 562 } 563 logger.Debugf("role binding %q created", rb.GetName()) 564 return out, cleanups, nil 565 } 566 567 func (k *kubernetesClient) getRoleBinding(name string) (*rbacv1.RoleBinding, error) { 568 if k.namespace == "" { 569 return nil, errNoNamespace 570 } 571 out, err := k.client().RbacV1().RoleBindings(k.namespace).Get(context.TODO(), name, v1.GetOptions{}) 572 if k8serrors.IsNotFound(err) { 573 return nil, errors.NotFoundf("role binding %q", name) 574 } 575 return out, errors.Trace(err) 576 } 577 578 func (k *kubernetesClient) deleteRoleBinding(name string, uid types.UID) error { 579 if k.namespace == "" { 580 return errNoNamespace 581 } 582 err := k.client().RbacV1().RoleBindings(k.namespace).Delete(context.TODO(), name, utils.NewPreconditionDeleteOptions(uid)) 583 if k8serrors.IsNotFound(err) { 584 return nil 585 } 586 return errors.Trace(err) 587 } 588 589 func (k *kubernetesClient) deleteRoleBindings(selectors ...k8slabels.Selector) error { 590 if k.namespace == "" { 591 return errNoNamespace 592 } 593 for _, selector := range selectors { 594 err := k.client().RbacV1().RoleBindings(k.namespace).DeleteCollection( 595 context.TODO(), 596 v1.DeleteOptions{ 597 PropagationPolicy: constants.DefaultPropagationPolicy(), 598 }, v1.ListOptions{ 599 LabelSelector: selector.String(), 600 }) 601 if !k8serrors.IsNotFound(err) { 602 return errors.Trace(err) 603 } 604 } 605 return nil 606 } 607 608 func (k *kubernetesClient) deleteClusterRoleBindings(selector k8slabels.Selector) error { 609 err := k.client().RbacV1().ClusterRoleBindings().DeleteCollection(context.TODO(), v1.DeleteOptions{ 610 PropagationPolicy: constants.DefaultPropagationPolicy(), 611 }, v1.ListOptions{ 612 LabelSelector: selector.String(), 613 }) 614 if k8serrors.IsNotFound(err) { 615 return nil 616 } 617 return errors.Trace(err) 618 } 619 620 func (k *kubernetesClient) listClusterRoleBindings(selector k8slabels.Selector) ([]rbacv1.ClusterRoleBinding, error) { 621 listOps := v1.ListOptions{ 622 LabelSelector: selector.String(), 623 } 624 cRBList, err := k.client().RbacV1().ClusterRoleBindings().List(context.TODO(), listOps) 625 if err != nil { 626 return nil, errors.Trace(err) 627 } 628 if len(cRBList.Items) == 0 { 629 return nil, errors.NotFoundf("cluster role binding with selector %q", selector) 630 } 631 return cRBList.Items, nil 632 } 633 634 // TODO: make this configurable. 635 var expiresInSeconds = int64(60 * 10) 636 637 // EnsureSecretAccessToken ensures the RBAC resources created and updated for the provided resource name. 638 func (k *kubernetesClient) EnsureSecretAccessToken(tag names.Tag, owned, read, removed []string) (string, error) { 639 appName := tag.Id() 640 if tag.Kind() == names.UnitTagKind { 641 appName, _ = names.UnitApplication(tag.Id()) 642 } 643 labels := utils.LabelsForApp(appName, k.IsLegacyLabels()) 644 645 objMeta := v1.ObjectMeta{ 646 Name: tag.String(), 647 Labels: labels, 648 Namespace: k.namespace, 649 } 650 651 sa := &core.ServiceAccount{ 652 ObjectMeta: objMeta, 653 AutomountServiceAccountToken: boolPtr(true), 654 } 655 _, _, err := k.ensureServiceAccount(sa) 656 if err != nil { 657 return "", errors.Annotatef(err, "cannot ensure service account %q", sa.GetName()) 658 } 659 660 if err := k.ensureBindingForSecretAccessToken(sa, objMeta, owned, read, removed); err != nil { 661 return "", errors.Trace(err) 662 } 663 664 treq := &authenticationv1.TokenRequest{ 665 Spec: authenticationv1.TokenRequestSpec{ 666 ExpirationSeconds: &expiresInSeconds, 667 }, 668 } 669 tr, err := k.client().CoreV1().ServiceAccounts(k.namespace).CreateToken(context.TODO(), sa.Name, treq, v1.CreateOptions{}) 670 if err != nil { 671 return "", errors.Annotatef(err, "cannot request a token for %q", sa.Name) 672 } 673 return tr.Status.Token, nil 674 } 675 676 func (k *kubernetesClient) ensureClusterBindingForSecretAccessToken(sa *core.ServiceAccount, objMeta v1.ObjectMeta, owned, read, removed []string) error { 677 objMeta.Name = fmt.Sprintf("%s-%s", k.namespace, objMeta.Name) 678 clusterRole, err := k.getClusterRole(objMeta.Name) 679 if err != nil && !errors.Is(err, errors.NotFound) { 680 return errors.Trace(err) 681 } 682 if errors.Is(err, errors.NotFound) { 683 clusterRole, err = k.createClusterRole( 684 &rbacv1.ClusterRole{ 685 ObjectMeta: objMeta, 686 Rules: rulesForSecretAccess(k.namespace, true, nil, owned, read, removed), 687 }, 688 ) 689 } else { 690 clusterRole.Rules = rulesForSecretAccess(k.namespace, true, clusterRole.Rules, owned, read, removed) 691 clusterRole, err = k.updateClusterRole(clusterRole) 692 } 693 if err != nil { 694 return errors.Trace(err) 695 } 696 bindingSpec := &rbacv1.ClusterRoleBinding{ 697 ObjectMeta: objMeta, 698 RoleRef: rbacv1.RoleRef{ 699 APIGroup: "rbac.authorization.k8s.io", 700 Kind: "ClusterRole", 701 Name: clusterRole.Name, 702 }, 703 Subjects: []rbacv1.Subject{ 704 { 705 Kind: "ServiceAccount", 706 Name: sa.Name, 707 Namespace: sa.Namespace, 708 }, 709 }, 710 } 711 clusterRoleBinding := resources.NewClusterRoleBinding(bindingSpec.Name, bindingSpec) 712 _, err = clusterRoleBinding.Ensure(context.TODO(), k.client(), resources.ClaimJujuOwnership) 713 if err != nil { 714 return errors.Trace(err) 715 } 716 717 // Ensure role binding exists before we return to avoid a race where a client 718 // attempts to perform an operation before the role is allowed. 719 return errors.Trace(retry.Call(retry.CallArgs{ 720 Func: func() error { 721 api := k.client().RbacV1().ClusterRoleBindings() 722 _, err := api.Get(context.TODO(), clusterRoleBinding.Name, v1.GetOptions{ResourceVersion: clusterRoleBinding.ResourceVersion}) 723 if k8serrors.IsNotFound(err) { 724 return errors.NewNotFound(err, "k8s") 725 } 726 return errors.Trace(err) 727 }, 728 IsFatalError: func(err error) bool { 729 return !errors.Is(err, errors.NotFound) 730 }, 731 Clock: jujuclock.WallClock, 732 Attempts: 5, 733 Delay: time.Second, 734 })) 735 } 736 737 func (k *kubernetesClient) ensureBindingForSecretAccessToken(sa *core.ServiceAccount, objMeta v1.ObjectMeta, owned, read, removed []string) error { 738 if k.Config().Name() == environsbootstrap.ControllerModelName { 739 return k.ensureClusterBindingForSecretAccessToken(sa, objMeta, owned, read, removed) 740 } 741 742 role, err := k.getRole(objMeta.Name) 743 if err != nil && !errors.Is(err, errors.NotFound) { 744 return errors.Trace(err) 745 } 746 if errors.Is(err, errors.NotFound) { 747 role, err = k.createRole( 748 &rbacv1.Role{ 749 ObjectMeta: objMeta, 750 Rules: rulesForSecretAccess(k.namespace, false, nil, owned, read, removed), 751 }, 752 ) 753 } else { 754 role.Rules = rulesForSecretAccess(k.namespace, false, role.Rules, owned, read, removed) 755 role, err = k.updateRole(role) 756 } 757 if err != nil { 758 return errors.Trace(err) 759 } 760 bindingSpec := &rbacv1.RoleBinding{ 761 ObjectMeta: objMeta, 762 RoleRef: rbacv1.RoleRef{ 763 APIGroup: "rbac.authorization.k8s.io", 764 Kind: "Role", 765 Name: role.Name, 766 }, 767 Subjects: []rbacv1.Subject{ 768 { 769 Kind: "ServiceAccount", 770 Name: sa.Name, 771 Namespace: sa.Namespace, 772 }, 773 }, 774 } 775 roleBinding := resources.NewRoleBinding(bindingSpec.Name, bindingSpec.Namespace, bindingSpec) 776 err = roleBinding.Apply(context.TODO(), k.client()) 777 if err != nil { 778 return errors.Trace(err) 779 } 780 781 // Ensure role binding exists before we return to avoid a race where a client 782 // attempts to perform an operation before the role is allowed. 783 return errors.Trace(retry.Call(retry.CallArgs{ 784 Func: func() error { 785 api := k.client().RbacV1().RoleBindings(k.namespace) 786 _, err := api.Get(context.TODO(), roleBinding.Name, v1.GetOptions{ResourceVersion: roleBinding.ResourceVersion}) 787 if k8serrors.IsNotFound(err) { 788 return errors.NewNotFound(err, "k8s") 789 } 790 return errors.Trace(err) 791 }, 792 IsFatalError: func(err error) bool { 793 return !errors.Is(err, errors.NotFound) 794 }, 795 Clock: jujuclock.WallClock, 796 Attempts: 5, 797 Delay: time.Second, 798 })) 799 } 800 801 func cleanRules(existing []rbacv1.PolicyRule, shouldRemove func(string) bool) []rbacv1.PolicyRule { 802 if len(existing) == 0 { 803 return nil 804 } 805 806 i := 0 807 for _, r := range existing { 808 if len(r.ResourceNames) == 1 && shouldRemove(r.ResourceNames[0]) { 809 continue 810 } 811 existing[i] = r 812 i++ 813 } 814 return existing[:i] 815 } 816 817 func rulesForSecretAccess( 818 namespace string, isControllerModel bool, 819 existing []rbacv1.PolicyRule, owned, read, removed []string, 820 ) []rbacv1.PolicyRule { 821 if len(existing) == 0 { 822 existing = []rbacv1.PolicyRule{ 823 { 824 APIGroups: []string{rbacv1.APIGroupAll}, 825 Resources: []string{"secrets"}, 826 Verbs: []string{ 827 "create", 828 "patch", // TODO: we really should only allow "create" but not patch but currently we uses .Apply() which requres patch!!! 829 }, 830 }, 831 } 832 if isControllerModel { 833 // We need to be able to list/get all namespaces for units in controller model. 834 existing = append(existing, rbacv1.PolicyRule{ 835 APIGroups: []string{rbacv1.APIGroupAll}, 836 Resources: []string{"namespaces"}, 837 Verbs: []string{"get", "list"}, 838 }) 839 } else { 840 // We just need to be able to list/get our own namespace for units in other models. 841 existing = append(existing, rbacv1.PolicyRule{ 842 APIGroups: []string{rbacv1.APIGroupAll}, 843 Resources: []string{"namespaces"}, 844 Verbs: []string{"get", "list"}, 845 ResourceNames: []string{namespace}, 846 }) 847 } 848 } 849 850 ownedIDs := set.NewStrings(owned...) 851 readIDs := set.NewStrings(read...) 852 removedIDs := set.NewStrings(removed...) 853 854 existing = cleanRules(existing, 855 func(s string) bool { 856 return ownedIDs.Contains(s) || readIDs.Contains(s) || removedIDs.Contains(s) 857 }, 858 ) 859 860 for _, rName := range owned { 861 if removedIDs.Contains(rName) { 862 continue 863 } 864 existing = append(existing, rbacv1.PolicyRule{ 865 APIGroups: []string{rbacv1.APIGroupAll}, 866 Resources: []string{"secrets"}, 867 Verbs: []string{rbacv1.VerbAll}, 868 ResourceNames: []string{rName}, 869 }) 870 } 871 for _, rName := range read { 872 if removedIDs.Contains(rName) { 873 continue 874 } 875 existing = append(existing, rbacv1.PolicyRule{ 876 APIGroups: []string{rbacv1.APIGroupAll}, 877 Resources: []string{"secrets"}, 878 Verbs: []string{"get"}, 879 ResourceNames: []string{rName}, 880 }) 881 } 882 return existing 883 } 884 885 // RemoveSecretAccessToken removes the RBAC resources for the provided resource name. 886 func (k *kubernetesClient) RemoveSecretAccessToken(unit names.Tag) error { 887 name := unit.String() 888 if err := k.deleteRoleBinding(name, ""); err != nil { 889 logger.Warningf("cannot delete service account %q", name) 890 } 891 if err := k.deleteRole(name, ""); err != nil { 892 logger.Warningf("cannot delete service account %q", name) 893 } 894 if err := k.deleteServiceAccount(name, ""); err != nil { 895 logger.Warningf("cannot delete service account %q", name) 896 } 897 return nil 898 }