github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/olm/requirements.go (about) 1 package olm 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "strings" 9 10 "github.com/coreos/go-semver/semver" 11 "github.com/operator-framework/api/pkg/operators/v1alpha1" 12 listersv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" 13 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" 14 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/internal/alongside" 15 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" 16 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 17 "github.com/sirupsen/logrus" 18 rbacv1 "k8s.io/api/rbac/v1" 19 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/labels" 22 ) 23 24 func (a *Operator) minKubeVersionStatus(name string, minKubeVersion string) (met bool, statuses []v1alpha1.RequirementStatus) { 25 if minKubeVersion == "" { 26 return true, nil 27 } 28 29 status := v1alpha1.RequirementStatus{ 30 Group: "operators.coreos.com", 31 Version: "v1alpha1", 32 Kind: "ClusterServiceVersion", 33 Name: name, 34 } 35 36 // Retrieve server k8s version 37 serverVersionInfo, err := a.opClient.KubernetesInterface().Discovery().ServerVersion() 38 if err != nil { 39 status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied 40 status.Message = "Server version discovery error" 41 met = false 42 statuses = append(statuses, status) 43 return 44 } 45 46 serverVersion, err := semver.NewVersion(strings.Split(strings.TrimPrefix(serverVersionInfo.String(), "v"), "-")[0]) 47 if err != nil { 48 status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied 49 status.Message = "Server version parsing error" 50 met = false 51 statuses = append(statuses, status) 52 return 53 } 54 55 csvVersionInfo, err := semver.NewVersion(strings.TrimPrefix(minKubeVersion, "v")) 56 if err != nil { 57 status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied 58 status.Message = "CSV version parsing error" 59 met = false 60 statuses = append(statuses, status) 61 return 62 } 63 64 if csvVersionInfo.Compare(*serverVersion) > 0 { 65 status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied 66 status.Message = fmt.Sprintf("CSV version requirement not met: minKubeVersion (%s) > server version (%s)", minKubeVersion, serverVersion.String()) 67 met = false 68 statuses = append(statuses, status) 69 return 70 } 71 72 status.Status = v1alpha1.RequirementStatusReasonPresent 73 status.Message = fmt.Sprintf("CSV minKubeVersion (%s) less than server version (%s)", minKubeVersion, serverVersionInfo.String()) 74 met = true 75 statuses = append(statuses, status) 76 return 77 } 78 79 func (a *Operator) requirementStatus(strategyDetailsDeployment *v1alpha1.StrategyDetailsDeployment, csv *v1alpha1.ClusterServiceVersion) (met bool, statuses []v1alpha1.RequirementStatus) { 80 ownedCRDNames := make(map[string]bool) 81 for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { 82 ownedCRDNames[owned.Name] = true 83 } 84 85 crdDescs := csv.GetAllCRDDescriptions() 86 ownedAPIServiceDescs := csv.GetOwnedAPIServiceDescriptions() 87 requiredAPIServiceDescs := csv.GetRequiredAPIServiceDescriptions() 88 requiredNativeAPIs := csv.Spec.NativeAPIs 89 met = true 90 91 // Check for CRDs 92 for _, r := range crdDescs { 93 status := v1alpha1.RequirementStatus{ 94 Group: "apiextensions.k8s.io", 95 Version: "v1", 96 Kind: "CustomResourceDefinition", 97 Name: r.Name, 98 } 99 100 // check if CRD exists - this verifies group, version, and kind, so no need for GVK check via discovery 101 crd, err := a.opClient.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), r.Name, metav1.GetOptions{}) 102 if err != nil { 103 status.Status = v1alpha1.RequirementStatusReasonNotPresent 104 status.Message = "CRD is not present" 105 a.logger.Debugf("Setting 'met' to false, %v with status %v, with err: %v", r.Name, status, err) 106 met = false 107 statuses = append(statuses, status) 108 continue 109 } 110 111 if others := othersInstalledAlongside(crd, csv, a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(csv.GetNamespace())); len(others) > 0 && ownedCRDNames[crd.Name] { 112 status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied 113 status.Message = fmt.Sprintf("CRD installed alongside other CSV(s): %s", strings.Join(others, ", ")) 114 met = false 115 statuses = append(statuses, status) 116 continue 117 } 118 119 served := false 120 for _, version := range crd.Spec.Versions { 121 if version.Name == r.Version { 122 if version.Served { 123 served = true 124 } 125 break 126 } 127 } 128 129 if !served { 130 status.Status = v1alpha1.RequirementStatusReasonNotPresent 131 status.Message = "CRD version not served" 132 a.logger.Debugf("Setting 'met' to false, %v with status %v, CRD version %v not found", r.Name, status, r.Version) 133 met = false 134 statuses = append(statuses, status) 135 continue 136 } 137 138 // Check if CRD has successfully registered with k8s API 139 established := false 140 namesAccepted := false 141 for _, cdt := range crd.Status.Conditions { 142 switch cdt.Type { 143 case apiextensionsv1.Established: 144 if cdt.Status == apiextensionsv1.ConditionTrue { 145 established = true 146 } 147 case apiextensionsv1.NamesAccepted: 148 if cdt.Status == apiextensionsv1.ConditionTrue { 149 namesAccepted = true 150 } 151 } 152 } 153 154 if established && namesAccepted { 155 status.Status = v1alpha1.RequirementStatusReasonPresent 156 status.Message = "CRD is present and Established condition is true" 157 status.UUID = string(crd.GetUID()) 158 statuses = append(statuses, status) 159 } else { 160 status.Status = v1alpha1.RequirementStatusReasonNotAvailable 161 status.Message = "CRD is present but the Established condition is False (not available)" 162 met = false 163 a.logger.Debugf("Setting 'met' to false, %v with status %v, established=%v, namesAccepted=%v", r.Name, status, established, namesAccepted) 164 statuses = append(statuses, status) 165 } 166 } 167 168 // Check for required API services 169 for _, r := range requiredAPIServiceDescs { 170 name := fmt.Sprintf("%s.%s", r.Version, r.Group) 171 status := v1alpha1.RequirementStatus{ 172 Group: "apiregistration.k8s.io", 173 Version: "v1", 174 Kind: "APIService", 175 Name: name, 176 } 177 178 // Check if GVK exists 179 if ok, err := a.isGVKRegistered(r.Group, r.Version, r.Kind); !ok || err != nil { 180 status.Status = "NotPresent" 181 met = false 182 statuses = append(statuses, status) 183 continue 184 } 185 186 // Check if APIService is registered 187 apiService, err := a.lister.APIRegistrationV1().APIServiceLister().Get(name) 188 if err != nil { 189 status.Status = "NotPresent" 190 met = false 191 statuses = append(statuses, status) 192 continue 193 } 194 195 // Check if API is available 196 if !install.IsAPIServiceAvailable(apiService) { 197 status.Status = "NotPresent" 198 met = false 199 } else { 200 status.Status = "Present" 201 status.UUID = string(apiService.GetUID()) 202 } 203 statuses = append(statuses, status) 204 } 205 206 // Check owned API services 207 for _, r := range ownedAPIServiceDescs { 208 name := fmt.Sprintf("%s.%s", r.Version, r.Group) 209 status := v1alpha1.RequirementStatus{ 210 Group: "apiregistration.k8s.io", 211 Version: "v1", 212 Kind: "APIService", 213 Name: name, 214 } 215 216 found := false 217 for _, spec := range strategyDetailsDeployment.DeploymentSpecs { 218 if spec.Name == r.DeploymentName { 219 status.Status = "DeploymentFound" 220 statuses = append(statuses, status) 221 found = true 222 break 223 } 224 } 225 226 if !found { 227 status.Status = "DeploymentNotFound" 228 statuses = append(statuses, status) 229 met = false 230 } 231 } 232 233 for _, r := range requiredNativeAPIs { 234 name := fmt.Sprintf("%s.%s", r.Version, r.Group) 235 status := v1alpha1.RequirementStatus{ 236 Group: r.Group, 237 Version: r.Version, 238 Kind: r.Kind, 239 Name: name, 240 } 241 242 if ok, err := a.isGVKRegistered(r.Group, r.Version, r.Kind); !ok || err != nil { 243 status.Status = v1alpha1.RequirementStatusReasonNotPresent 244 status.Message = "Native API does not exist" 245 met = false 246 statuses = append(statuses, status) 247 continue 248 } else { 249 status.Status = v1alpha1.RequirementStatusReasonPresent 250 status.Message = "Native API exists" 251 statuses = append(statuses, status) 252 continue 253 } 254 } 255 256 return 257 } 258 259 // permissionStatus checks whether the given CSV's RBAC requirements are met in its namespace 260 func (a *Operator) permissionStatus(strategyDetailsDeployment *v1alpha1.StrategyDetailsDeployment, targetNamespace string, csv *v1alpha1.ClusterServiceVersion) (bool, []v1alpha1.RequirementStatus, error) { 261 statusesSet := map[string]v1alpha1.RequirementStatus{} 262 263 checkPermissions := func(permissions []v1alpha1.StrategyDeploymentPermissions, namespace string) (bool, error) { 264 met := true 265 for _, perm := range permissions { 266 saName := perm.ServiceAccountName 267 a.logger.Debugf("perm.ServiceAccountName: %s", saName) 268 269 var status v1alpha1.RequirementStatus 270 if stored, ok := statusesSet[saName]; !ok { 271 status = v1alpha1.RequirementStatus{ 272 Group: "", 273 Version: "v1", 274 Kind: "ServiceAccount", 275 Name: saName, 276 Status: v1alpha1.RequirementStatusReasonPresent, 277 Dependents: []v1alpha1.DependentStatus{}, 278 } 279 } else { 280 status = stored 281 } 282 283 // Ensure the ServiceAccount exists 284 sa, err := a.opClient.GetServiceAccount(csv.GetNamespace(), perm.ServiceAccountName) 285 if err != nil { 286 met = false 287 status.Status = v1alpha1.RequirementStatusReasonNotPresent 288 status.Message = "Service account does not exist" 289 statusesSet[saName] = status 290 continue 291 } 292 // Check SA's ownership 293 if ownerutil.IsOwnedByKind(sa, v1alpha1.ClusterServiceVersionKind) && !ownerutil.IsOwnedBy(sa, csv) { 294 met = false 295 status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied 296 status.Message = "Service account is owned by another ClusterServiceVersion" 297 statusesSet[saName] = status 298 continue 299 } 300 301 // Check if PolicyRules are satisfied 302 if a.informersFiltered { 303 // we don't hold the whole set of RBAC in memory, so we can't use the authorizer: 304 // check for rules we would have created ourselves first 305 var err error 306 var permissionMet bool 307 if namespace == metav1.NamespaceAll { 308 permissionMet, err = permissionsPreviouslyCreated[*rbacv1.ClusterRole, *rbacv1.ClusterRoleBinding]( 309 perm, csv, 310 a.lister.RbacV1().ClusterRoleLister().List, a.lister.RbacV1().ClusterRoleBindingLister().List, 311 ) 312 } else { 313 permissionMet, err = permissionsPreviouslyCreated[*rbacv1.Role, *rbacv1.RoleBinding]( 314 perm, csv, 315 a.lister.RbacV1().RoleLister().Roles(namespace).List, a.lister.RbacV1().RoleBindingLister().RoleBindings(namespace).List, 316 ) 317 } 318 if err != nil { 319 return false, err 320 } 321 if permissionMet { 322 // OLM previously made all the permissions we need, exit early 323 for _, rule := range perm.Rules { 324 dependent := v1alpha1.DependentStatus{ 325 Group: "rbac.authorization.k8s.io", 326 Kind: "PolicyRule", 327 Version: "v1", 328 Status: v1alpha1.DependentStatusReasonSatisfied, 329 } 330 marshalled, err := json.Marshal(rule) 331 if err != nil { 332 dependent.Status = v1alpha1.DependentStatusReasonNotSatisfied 333 dependent.Message = "rule unmarshallable" 334 status.Dependents = append(status.Dependents, dependent) 335 continue 336 } 337 338 var scope string 339 if namespace == metav1.NamespaceAll { 340 scope = "cluster" 341 } else { 342 scope = "namespaced" 343 } 344 dependent.Message = fmt.Sprintf("%s rule:%s", scope, marshalled) 345 status.Dependents = append(status.Dependents, dependent) 346 } 347 continue 348 } 349 } 350 // if we have not filtered our informers or if we were unable to detect the correct permissions, we have 351 // no choice but to page in the world and see if the user pre-created permissions for this CSV 352 ruleChecker := a.getRuleChecker()(csv) 353 if ruleChecker == nil { 354 return false, errors.New("could not create a rule checker (are we shutting down?)") 355 } 356 for _, rule := range perm.Rules { 357 dependent := v1alpha1.DependentStatus{ 358 Group: "rbac.authorization.k8s.io", 359 Kind: "PolicyRule", 360 Version: "v1", 361 } 362 363 marshalled, err := json.Marshal(rule) 364 if err != nil { 365 dependent.Status = v1alpha1.DependentStatusReasonNotSatisfied 366 dependent.Message = "rule unmarshallable" 367 status.Dependents = append(status.Dependents, dependent) 368 continue 369 } 370 371 var scope string 372 if namespace == metav1.NamespaceAll { 373 scope = "cluster" 374 } else { 375 scope = "namespaced" 376 } 377 dependent.Message = fmt.Sprintf("%s rule:%s", scope, marshalled) 378 379 satisfied, err := ruleChecker.RuleSatisfied(sa, namespace, rule) 380 if err != nil { 381 return false, err 382 } else if !satisfied { 383 met = false 384 dependent.Status = v1alpha1.DependentStatusReasonNotSatisfied 385 status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied 386 status.Message = "Policy rule not satisfied for service account" 387 } else { 388 dependent.Status = v1alpha1.DependentStatusReasonSatisfied 389 } 390 391 status.Dependents = append(status.Dependents, dependent) 392 } 393 394 statusesSet[saName] = status 395 } 396 397 return met, nil 398 } 399 400 permMet, err := checkPermissions(strategyDetailsDeployment.Permissions, targetNamespace) 401 if err != nil { 402 return false, nil, err 403 } 404 clusterPermMet, err := checkPermissions(strategyDetailsDeployment.ClusterPermissions, metav1.NamespaceAll) 405 if err != nil { 406 return false, nil, err 407 } 408 409 statuses := []v1alpha1.RequirementStatus{} 410 for key, status := range statusesSet { 411 a.logger.WithField("key", key).WithField("status", status).Tracef("appending permission status") 412 statuses = append(statuses, status) 413 } 414 415 return permMet && clusterPermMet, statuses, nil 416 } 417 418 func permissionsPreviouslyCreated[T, U metav1.Object]( 419 permission v1alpha1.StrategyDeploymentPermissions, 420 csv *v1alpha1.ClusterServiceVersion, 421 listRoles func(labels.Selector) ([]T, error), 422 listBindings func(labels.Selector) ([]U, error), 423 ) (bool, error) { 424 // first, find the (cluster)role 425 ruleHash, err := resolver.PolicyRuleHashLabelValue(permission.Rules) 426 if err != nil { 427 return false, fmt.Errorf("failed to hash permission rules: %w", err) 428 } 429 roleSelectorMap := ownerutil.OwnerLabel(csv, v1alpha1.ClusterServiceVersionKind) 430 roleSelectorMap[resolver.ContentHashLabelKey] = ruleHash 431 roles, err := listRoles(labels.SelectorFromSet(roleSelectorMap)) 432 if err != nil { 433 return false, err 434 } 435 436 if len(roles) == 0 { 437 return false, nil 438 } 439 440 // then, find the (cluster)rolebinding, if we found the role 441 bindingHash, err := resolver.RoleReferenceAndSubjectHashLabelValue(rbacv1.RoleRef{ 442 Kind: "Role", 443 Name: roles[0].GetName(), 444 APIGroup: rbacv1.GroupName, 445 }, 446 []rbacv1.Subject{{ 447 Kind: "ServiceAccount", 448 Name: permission.ServiceAccountName, 449 Namespace: csv.GetNamespace(), 450 }}, 451 ) 452 if err != nil { 453 return false, fmt.Errorf("failed to hash binding content: %w", err) 454 } 455 bindingSelectorMap := ownerutil.OwnerLabel(csv, v1alpha1.ClusterServiceVersionKind) 456 bindingSelectorMap[resolver.ContentHashLabelKey] = bindingHash 457 bindingSelectorSet := labels.Set{} 458 for key, value := range bindingSelectorMap { 459 bindingSelectorSet[key] = value 460 } 461 bindingSelector := labels.SelectorFromSet(bindingSelectorSet) 462 bindings, err := listBindings(bindingSelector) 463 return len(roles) > 0 && len(bindings) > 0, err 464 } 465 466 // requirementAndPermissionStatus returns the aggregate requirement and permissions statuses for the given CSV 467 func (a *Operator) requirementAndPermissionStatus(csv *v1alpha1.ClusterServiceVersion) (bool, []v1alpha1.RequirementStatus, error) { 468 allReqStatuses := []v1alpha1.RequirementStatus{} 469 // Use a StrategyResolver to unmarshal 470 strategyResolver := install.StrategyResolver{} 471 strategy, err := strategyResolver.UnmarshalStrategy(csv.Spec.InstallStrategy) 472 if err != nil { 473 return false, nil, err 474 } 475 476 // Assume the strategy is for a deployment 477 strategyDetailsDeployment, ok := strategy.(*v1alpha1.StrategyDetailsDeployment) 478 if !ok { 479 return false, nil, fmt.Errorf("could not cast install strategy as type %T", strategyDetailsDeployment) 480 } 481 482 // Check kubernetes version requirement between CSV and server 483 minKubeMet, minKubeStatus := a.minKubeVersionStatus(csv.GetName(), csv.Spec.MinKubeVersion) 484 if minKubeStatus != nil { 485 allReqStatuses = append(allReqStatuses, minKubeStatus...) 486 } 487 488 reqMet, reqStatuses := a.requirementStatus(strategyDetailsDeployment, csv) 489 allReqStatuses = append(allReqStatuses, reqStatuses...) 490 491 permMet, permStatuses, err := a.permissionStatus(strategyDetailsDeployment, csv.GetNamespace(), csv) 492 if err != nil { 493 return false, nil, err 494 } 495 496 // Aggregate requirement and permissions statuses 497 statuses := append(allReqStatuses, permStatuses...) 498 met := minKubeMet && reqMet && permMet 499 if !met { 500 a.logger.WithField("minKubeMet", minKubeMet).WithField("reqMet", reqMet).WithField("permMet", permMet).Debug("permissions/requirements not met") 501 } 502 503 return met, statuses, nil 504 } 505 506 func (a *Operator) isGVKRegistered(group, version, kind string) (bool, error) { 507 logger := a.logger.WithFields(logrus.Fields{ 508 "group": group, 509 "version": version, 510 "kind": kind, 511 }) 512 513 gv := metav1.GroupVersion{Group: group, Version: version} 514 resources, err := a.opClient.KubernetesInterface().Discovery().ServerResourcesForGroupVersion(gv.String()) 515 if err != nil { 516 logger.WithField("err", err).Info("could not query for GVK in api discovery") 517 return false, err 518 } 519 520 for _, r := range resources.APIResources { 521 if r.Kind == kind { 522 return true, nil 523 } 524 } 525 526 logger.Info("couldn't find GVK in api discovery") 527 return false, nil 528 } 529 530 // othersInstalledAlongside returns the names of all 531 // ClusterServiceVersions alongside which the given object was 532 // installed, that are not the named CSV and are directly or 533 // transitively replaced by the named CSV. 534 func othersInstalledAlongside(o metav1.Object, target *v1alpha1.ClusterServiceVersion, lister listersv1alpha1.ClusterServiceVersionNamespaceLister) []string { 535 csvsByName := make(map[string]*v1alpha1.ClusterServiceVersion) 536 for _, nn := range (alongside.Annotator{}).FromObject(o) { 537 if nn.Namespace != target.GetNamespace() { 538 continue 539 } 540 if nn.Name == target.GetName() { 541 return nil 542 } 543 csv, err := lister.Get(nn.Name) 544 if err != nil || csv.IsCopied() { 545 continue 546 } 547 csvsByName[csv.GetName()] = csv 548 } 549 550 replacees := make(map[string]string) 551 for current, csv := range csvsByName { 552 if _, ok := csvsByName[csv.Spec.Replaces]; ok { 553 replacees[current] = csv.Spec.Replaces 554 } 555 } 556 if target.Spec.Replaces != "" { 557 replacees[target.GetName()] = target.Spec.Replaces 558 } 559 560 ancestors := make(map[string]struct{}) 561 for current := target.GetName(); current != ""; { 562 replacee, ok := replacees[current] 563 if ok { 564 ancestors[replacee] = struct{}{} 565 } 566 delete(replacees, current) // avoid cycles 567 current = replacee 568 } 569 570 var names []string 571 for each := range csvsByName { 572 if _, ok := ancestors[each]; ok && each != target.GetName() { 573 names = append(names, each) 574 } 575 } 576 return names 577 }