github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/rbac.go (about)

     1  package resolver
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/json"
     6  	"fmt"
     7  	"math/big"
     8  
     9  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    10  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
    11  	hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash"
    12  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
    13  	corev1 "k8s.io/api/core/v1"
    14  	rbacv1 "k8s.io/api/rbac/v1"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  )
    17  
    18  const maxNameLength = 63
    19  
    20  func generateName(base string, o interface{}) (string, error) {
    21  	hash, err := hashutil.DeepHashObject(o)
    22  	if err != nil {
    23  		return "", err
    24  	}
    25  	if len(base)+len(hash) > maxNameLength {
    26  		base = base[:maxNameLength-len(hash)-1]
    27  	}
    28  
    29  	return fmt.Sprintf("%s-%s", base, hash), nil
    30  }
    31  
    32  type OperatorPermissions struct {
    33  	ServiceAccount      *corev1.ServiceAccount
    34  	Roles               []*rbacv1.Role
    35  	RoleBindings        []*rbacv1.RoleBinding
    36  	ClusterRoles        []*rbacv1.ClusterRole
    37  	ClusterRoleBindings []*rbacv1.ClusterRoleBinding
    38  }
    39  
    40  func NewOperatorPermissions(serviceAccount *corev1.ServiceAccount) *OperatorPermissions {
    41  	return &OperatorPermissions{
    42  		ServiceAccount:      serviceAccount,
    43  		Roles:               []*rbacv1.Role{},
    44  		RoleBindings:        []*rbacv1.RoleBinding{},
    45  		ClusterRoles:        []*rbacv1.ClusterRole{},
    46  		ClusterRoleBindings: []*rbacv1.ClusterRoleBinding{},
    47  	}
    48  }
    49  
    50  func (o *OperatorPermissions) AddRole(role *rbacv1.Role) {
    51  	o.Roles = append(o.Roles, role)
    52  }
    53  
    54  func (o *OperatorPermissions) AddRoleBinding(roleBinding *rbacv1.RoleBinding) {
    55  	o.RoleBindings = append(o.RoleBindings, roleBinding)
    56  }
    57  
    58  func (o *OperatorPermissions) AddClusterRole(clusterRole *rbacv1.ClusterRole) {
    59  	o.ClusterRoles = append(o.ClusterRoles, clusterRole)
    60  }
    61  
    62  func (o *OperatorPermissions) AddClusterRoleBinding(clusterRoleBinding *rbacv1.ClusterRoleBinding) {
    63  	o.ClusterRoleBindings = append(o.ClusterRoleBindings, clusterRoleBinding)
    64  }
    65  
    66  const ContentHashLabelKey = "olm.permissions.hash"
    67  
    68  func PolicyRuleHashLabelValue(rules []rbacv1.PolicyRule) (string, error) {
    69  	raw, err := json.Marshal(rules)
    70  	if err != nil {
    71  		return "", err
    72  	}
    73  	return toBase62(sha256.Sum224(raw)), nil
    74  }
    75  
    76  func RoleReferenceAndSubjectHashLabelValue(roleRef rbacv1.RoleRef, subjects []rbacv1.Subject) (string, error) {
    77  	var container = struct {
    78  		RoleRef  rbacv1.RoleRef
    79  		Subjects []rbacv1.Subject
    80  	}{
    81  		RoleRef:  roleRef,
    82  		Subjects: subjects,
    83  	}
    84  	raw, err := json.Marshal(&container)
    85  	if err != nil {
    86  		return "", err
    87  	}
    88  	return toBase62(sha256.Sum224(raw)), nil
    89  }
    90  
    91  func toBase62(hash [28]byte) string {
    92  	var i big.Int
    93  	i.SetBytes(hash[:])
    94  	return i.Text(62)
    95  }
    96  
    97  func RBACForClusterServiceVersion(csv *v1alpha1.ClusterServiceVersion) (map[string]*OperatorPermissions, error) {
    98  	permissions := map[string]*OperatorPermissions{}
    99  
   100  	// Use a StrategyResolver to get the strategy details
   101  	strategyResolver := install.StrategyResolver{}
   102  	strategy, err := strategyResolver.UnmarshalStrategy(csv.Spec.InstallStrategy)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	// Assume the strategy is for a deployment
   108  	strategyDetailsDeployment, ok := strategy.(*v1alpha1.StrategyDetailsDeployment)
   109  	if !ok {
   110  		return nil, fmt.Errorf("could not assert strategy implementation as deployment for CSV %s", csv.GetName())
   111  	}
   112  
   113  	// Resolve Permissions
   114  	for _, permission := range strategyDetailsDeployment.Permissions {
   115  		// Create ServiceAccount if necessary
   116  		if _, ok := permissions[permission.ServiceAccountName]; !ok {
   117  			serviceAccount := &corev1.ServiceAccount{}
   118  			serviceAccount.SetNamespace(csv.GetNamespace())
   119  			serviceAccount.SetName(permission.ServiceAccountName)
   120  			ownerutil.AddNonBlockingOwner(serviceAccount, csv)
   121  
   122  			permissions[permission.ServiceAccountName] = NewOperatorPermissions(serviceAccount)
   123  		}
   124  
   125  		// Create Role
   126  		name, err := generateName(fmt.Sprintf("%s-%s", csv.GetName(), permission.ServiceAccountName), struct {
   127  			Name       string                                 `json:"name,omitempty"`
   128  			Permission v1alpha1.StrategyDeploymentPermissions `json:"permission"`
   129  		}{Name: csv.GetName(), Permission: permission})
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  		role := &rbacv1.Role{
   134  			ObjectMeta: metav1.ObjectMeta{
   135  				Name:            name,
   136  				Namespace:       csv.GetNamespace(),
   137  				OwnerReferences: []metav1.OwnerReference{ownerutil.NonBlockingOwner(csv)},
   138  				Labels:          ownerutil.OwnerLabel(csv, v1alpha1.ClusterServiceVersionKind),
   139  			},
   140  			Rules: permission.Rules,
   141  		}
   142  		hash, err := PolicyRuleHashLabelValue(permission.Rules)
   143  		if err != nil {
   144  			return nil, fmt.Errorf("failed to hash permission rules: %w", err)
   145  		}
   146  		role.Labels[ContentHashLabelKey] = hash
   147  		permissions[permission.ServiceAccountName].AddRole(role)
   148  
   149  		// Create RoleBinding
   150  		roleBinding := &rbacv1.RoleBinding{
   151  			ObjectMeta: metav1.ObjectMeta{
   152  				Name:            role.GetName(),
   153  				Namespace:       csv.GetNamespace(),
   154  				OwnerReferences: []metav1.OwnerReference{ownerutil.NonBlockingOwner(csv)},
   155  				Labels:          ownerutil.OwnerLabel(csv, v1alpha1.ClusterServiceVersionKind),
   156  			},
   157  			RoleRef: rbacv1.RoleRef{
   158  				Kind:     "Role",
   159  				Name:     role.GetName(),
   160  				APIGroup: rbacv1.GroupName},
   161  			Subjects: []rbacv1.Subject{{
   162  				Kind:      "ServiceAccount",
   163  				Name:      permission.ServiceAccountName,
   164  				Namespace: csv.GetNamespace(),
   165  			}},
   166  		}
   167  		hash, err = RoleReferenceAndSubjectHashLabelValue(roleBinding.RoleRef, roleBinding.Subjects)
   168  		if err != nil {
   169  			return nil, fmt.Errorf("failed to hash binding content: %w", err)
   170  		}
   171  		roleBinding.Labels[ContentHashLabelKey] = hash
   172  		permissions[permission.ServiceAccountName].AddRoleBinding(roleBinding)
   173  	}
   174  
   175  	// Resolve ClusterPermissions as StepResources
   176  	for _, permission := range strategyDetailsDeployment.ClusterPermissions {
   177  		// Create ServiceAccount if necessary
   178  		if _, ok := permissions[permission.ServiceAccountName]; !ok {
   179  			serviceAccount := &corev1.ServiceAccount{}
   180  			ownerutil.AddOwner(serviceAccount, csv, false, false)
   181  			serviceAccount.SetName(permission.ServiceAccountName)
   182  
   183  			permissions[permission.ServiceAccountName] = NewOperatorPermissions(serviceAccount)
   184  		}
   185  
   186  		// Create ClusterRole
   187  		name, err := generateName(csv.GetName(), struct {
   188  			Name       string                                 `json:"name,omitempty"`
   189  			Namespace  string                                 `json:"namespace,omitempty"`
   190  			Permission v1alpha1.StrategyDeploymentPermissions `json:"permission"`
   191  		}{Name: csv.GetName(), Namespace: csv.GetNamespace(), Permission: permission})
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		role := &rbacv1.ClusterRole{
   196  			ObjectMeta: metav1.ObjectMeta{
   197  				Name:   name,
   198  				Labels: ownerutil.OwnerLabel(csv, v1alpha1.ClusterServiceVersionKind),
   199  			},
   200  			Rules: permission.Rules,
   201  		}
   202  		hash, err := PolicyRuleHashLabelValue(permission.Rules)
   203  		if err != nil {
   204  			return nil, fmt.Errorf("failed to hash permission rules: %w", err)
   205  		}
   206  		role.Labels[ContentHashLabelKey] = hash
   207  		permissions[permission.ServiceAccountName].AddClusterRole(role)
   208  
   209  		// Create ClusterRoleBinding
   210  		roleBinding := &rbacv1.ClusterRoleBinding{
   211  			ObjectMeta: metav1.ObjectMeta{
   212  				Name:      role.GetName(),
   213  				Namespace: csv.GetNamespace(),
   214  				Labels:    ownerutil.OwnerLabel(csv, v1alpha1.ClusterServiceVersionKind),
   215  			},
   216  			RoleRef: rbacv1.RoleRef{
   217  				Kind:     "ClusterRole",
   218  				Name:     role.GetName(),
   219  				APIGroup: rbacv1.GroupName,
   220  			},
   221  			Subjects: []rbacv1.Subject{{
   222  				Kind:      "ServiceAccount",
   223  				Name:      permission.ServiceAccountName,
   224  				Namespace: csv.GetNamespace(),
   225  			}},
   226  		}
   227  		hash, err = RoleReferenceAndSubjectHashLabelValue(roleBinding.RoleRef, roleBinding.Subjects)
   228  		if err != nil {
   229  			return nil, fmt.Errorf("failed to hash binding content: %w", err)
   230  		}
   231  		roleBinding.Labels[ContentHashLabelKey] = hash
   232  		permissions[permission.ServiceAccountName].AddClusterRoleBinding(roleBinding)
   233  	}
   234  	return permissions, nil
   235  }