Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClusterRoleBindings and Services Installation: SSA #3182

Merged
merged 1 commit into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 48 additions & 82 deletions pkg/controller/install/certresources.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
rbacv1ac "k8s.io/client-go/applyconfigurations/rbac/v1"

"github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs"
Expand Down Expand Up @@ -241,43 +243,33 @@ func CalculateCertRotatesAt(certExpirationTime time.Time) time.Time {
func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deploymentName string, ca *certs.KeyPair, expiration time.Time, depSpec appsv1.DeploymentSpec, ports []corev1.ServicePort) (*appsv1.DeploymentSpec, []byte, error) {
logger := log.WithFields(log.Fields{})

// Create a service for the deployment
service := &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: ports,
Selector: depSpec.Selector.MatchLabels,
},
}
service.SetName(ServiceName(deploymentName))
service.SetNamespace(i.owner.GetNamespace())
ownerutil.AddNonBlockingOwner(service, i.owner)
service.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue})

existingService, err := i.strategyClient.GetOpLister().CoreV1().ServiceLister().Services(i.owner.GetNamespace()).Get(service.GetName())
if err == nil {
if !ownerutil.Adoptable(i.owner, existingService.GetOwnerReferences()) {
return nil, nil, fmt.Errorf("service %s not safe to replace: extraneous ownerreferences found", service.GetName())
}
service.SetOwnerReferences(existingService.GetOwnerReferences())

// Delete the Service to replace
deleteErr := i.strategyClient.GetOpClient().DeleteService(service.GetNamespace(), service.GetName(), &metav1.DeleteOptions{})
if deleteErr != nil && !apierrors.IsNotFound(deleteErr) {
return nil, nil, fmt.Errorf("could not delete existing service %s", service.GetName())
}
}

// Attempt to create the Service
_, err = i.strategyClient.GetOpClient().CreateService(service)
if err != nil {
logger.Warnf("could not create service %s", service.GetName())
return nil, nil, fmt.Errorf("could not create service %s: %s", service.GetName(), err.Error())
// apply Service
serviceName := ServiceName(deploymentName)
portsApplyConfig := []*corev1ac.ServicePortApplyConfiguration{}
for _, p := range ports {
ac := corev1ac.ServicePort().
WithName(p.Name).
WithPort(p.Port).
WithTargetPort(p.TargetPort)
portsApplyConfig = append(portsApplyConfig, ac)
}

svcApplyConfig := corev1ac.Service(serviceName, i.owner.GetNamespace()).
WithSpec(corev1ac.ServiceSpec().
WithPorts(portsApplyConfig...).
WithSelector(depSpec.Selector.MatchLabels)).
WithOwnerReferences(ownerutil.NonBlockingOwnerApplyConfiguration(i.owner)).
WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue})

if _, err := i.strategyClient.GetOpClient().ApplyService(svcApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}); err != nil {
log.Errorf("could not apply service %s: %s", *svcApplyConfig.Name, err.Error())
return nil, nil, err
}

// Create signed serving cert
hosts := []string{
fmt.Sprintf("%s.%s", service.GetName(), i.owner.GetNamespace()),
fmt.Sprintf("%s.%s.svc", service.GetName(), i.owner.GetNamespace()),
fmt.Sprintf("%s.%s", serviceName, i.owner.GetNamespace()),
fmt.Sprintf("%s.%s.svc", serviceName, i.owner.GetNamespace()),
}
servingPair, err := certGenerator.Generate(expiration, Organization, ca, hosts)
if err != nil {
Expand All @@ -288,14 +280,14 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
// Create Secret for serving cert
certPEM, privPEM, err := servingPair.ToPEM()
if err != nil {
logger.Warnf("unable to convert serving certificate and private key to PEM format for Service %s", service.GetName())
logger.Warnf("unable to convert serving certificate and private key to PEM format for Service %s", serviceName)
return nil, nil, err
}

// Add olmcahash as a label to the caPEM
caPEM, _, err := ca.ToPEM()
if err != nil {
logger.Warnf("unable to convert CA certificate to PEM format for Service %s", service)
logger.Warnf("unable to convert CA certificate to PEM format for Service %s", serviceName)
return nil, nil, err
}
caHash := certs.PEMSHA256(caPEM)
Expand All @@ -308,7 +300,7 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
},
Type: corev1.SecretTypeTLS,
}
secret.SetName(SecretName(service.GetName()))
secret.SetName(SecretName(serviceName))
secret.SetNamespace(i.owner.GetNamespace())
secret.SetAnnotations(map[string]string{OLMCAHashAnnotationKey: caHash})
secret.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue})
Expand Down Expand Up @@ -440,51 +432,25 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
return nil, nil, err
}

// create ClusterRoleBinding to system:auth-delegator Role
authDelegatorClusterRoleBinding := &rbacv1.ClusterRoleBinding{
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
APIGroup: "",
Name: depSpec.Template.Spec.ServiceAccountName,
Namespace: i.owner.GetNamespace(),
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "system:auth-delegator",
},
}
authDelegatorClusterRoleBinding.SetName(AuthDelegatorClusterRoleBindingName(service.GetName()))
authDelegatorClusterRoleBinding.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue})

existingAuthDelegatorClusterRoleBinding, err := i.strategyClient.GetOpLister().RbacV1().ClusterRoleBindingLister().Get(authDelegatorClusterRoleBinding.GetName())
if err == nil {
// Check if the only owners are this CSV or in this CSV's replacement chain.
if ownerutil.AdoptableLabels(existingAuthDelegatorClusterRoleBinding.GetLabels(), true, i.owner) {
logger.WithFields(log.Fields{"obj": "authDelegatorCRB", "labels": existingAuthDelegatorClusterRoleBinding.GetLabels()}).Debug("adopting")
if err := ownerutil.AddOwnerLabels(authDelegatorClusterRoleBinding, i.owner); err != nil {
return nil, nil, err
}
}

// Attempt an update.
if _, err := i.strategyClient.GetOpClient().UpdateClusterRoleBinding(authDelegatorClusterRoleBinding); err != nil {
logger.Warnf("could not update auth delegator clusterrolebinding %s", authDelegatorClusterRoleBinding.GetName())
return nil, nil, err
}
} else if apierrors.IsNotFound(err) {
// Create the role.
if err := ownerutil.AddOwnerLabels(authDelegatorClusterRoleBinding, i.owner); err != nil {
return nil, nil, err
}
_, err = i.strategyClient.GetOpClient().CreateClusterRoleBinding(authDelegatorClusterRoleBinding)
if err != nil {
log.Warnf("could not create auth delegator clusterrolebinding %s", authDelegatorClusterRoleBinding.GetName())
return nil, nil, err
}
} else {
// apply ClusterRoleBinding to system:auth-delegator Role
crbLabels := map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}
for key, val := range ownerutil.OwnerLabel(i.owner, i.owner.GetObjectKind().GroupVersionKind().Kind) {
crbLabels[key] = val
}
crbApplyConfig := rbacv1ac.ClusterRoleBinding(AuthDelegatorClusterRoleBindingName(serviceName)).
WithSubjects(rbacv1ac.Subject().
WithKind("ServiceAccount").
WithAPIGroup("").
WithName(depSpec.Template.Spec.ServiceAccountName).
WithNamespace(i.owner.GetNamespace())).
WithRoleRef(rbacv1ac.RoleRef().
WithAPIGroup("rbac.authorization.k8s.io").
WithKind("ClusterRole").
WithName("system:auth-delegator")).
WithLabels(crbLabels)

if _, err = i.strategyClient.GetOpClient().ApplyClusterRoleBinding(crbApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}); err != nil {
log.Errorf("could not apply auth delegator clusterrolebinding %s: %s", *crbApplyConfig.Name, err.Error())
return nil, nil, err
}

Expand All @@ -504,7 +470,7 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
Name: "extension-apiserver-authentication-reader",
},
}
authReaderRoleBinding.SetName(AuthReaderRoleBindingName(service.GetName()))
authReaderRoleBinding.SetName(AuthReaderRoleBindingName(serviceName))
authReaderRoleBinding.SetNamespace(KubeSystem)
authReaderRoleBinding.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue})

Expand Down
128 changes: 107 additions & 21 deletions pkg/controller/install/certresources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
rbacv1ac "k8s.io/client-go/applyconfigurations/rbac/v1"

"github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/wrappers"
Expand Down Expand Up @@ -153,7 +155,6 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
{
name: "adds certs to deployment spec",
mockExternal: func(mockOpClient *operatorclientmocks.MockClientInterface, fakeLister *operatorlisterfakes.FakeOperatorLister, namespace string, args args) {
mockOpClient.EXPECT().DeleteService(namespace, "test-service", &metav1.DeleteOptions{}).Return(nil)
service := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test-service",
Expand All @@ -167,7 +168,24 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
Selector: selector(t, "test=label").MatchLabels,
},
}
mockOpClient.EXPECT().CreateService(&service).Return(&service, nil)

portsApplyConfig := []*corev1ac.ServicePortApplyConfiguration{}
for _, p := range args.ports {
ac := corev1ac.ServicePort().
WithName(p.Name).
WithPort(p.Port).
WithTargetPort(p.TargetPort)
portsApplyConfig = append(portsApplyConfig, ac)
}

svcApplyConfig := corev1ac.Service(service.Name, service.Namespace).
WithSpec(corev1ac.ServiceSpec().
WithPorts(portsApplyConfig...).
WithSelector(selector(t, "test=label").MatchLabels)).
WithOwnerReferences(ownerutil.NonBlockingOwnerApplyConfiguration(&v1alpha1.ClusterServiceVersion{})).
WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue})

mockOpClient.EXPECT().ApplyService(svcApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(&service, nil)

hosts := []string{
fmt.Sprintf("%s.%s", service.GetName(), namespace),
Expand Down Expand Up @@ -255,7 +273,22 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
},
}

mockOpClient.EXPECT().UpdateClusterRoleBinding(authDelegatorClusterRoleBinding).Return(authDelegatorClusterRoleBinding, nil)
crbLabels := map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}
for key, val := range ownerutil.OwnerLabel(ownerutil.Owner(&v1alpha1.ClusterServiceVersion{}), owner.GetObjectKind().GroupVersionKind().Kind) {
crbLabels[key] = val
}
crbApplyConfig := rbacv1ac.ClusterRoleBinding(AuthDelegatorClusterRoleBindingName(service.GetName())).
WithSubjects(rbacv1ac.Subject().
WithKind("ServiceAccount").
WithAPIGroup("").
WithName(args.depSpec.Template.Spec.ServiceAccountName).
WithNamespace("")). // Empty owner with no namespace
WithRoleRef(rbacv1ac.RoleRef().
WithAPIGroup("rbac.authorization.k8s.io").
WithKind("ClusterRole").
WithName("system:auth-delegator")).
WithLabels(crbLabels)
mockOpClient.EXPECT().ApplyClusterRoleBinding(crbApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(authDelegatorClusterRoleBinding, nil)

authReaderRoleBinding := &rbacv1.RoleBinding{
Subjects: []rbacv1.Subject{
Expand Down Expand Up @@ -381,7 +414,6 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
{
name: "doesn't add duplicate service ownerrefs",
mockExternal: func(mockOpClient *operatorclientmocks.MockClientInterface, fakeLister *operatorlisterfakes.FakeOperatorLister, namespace string, args args) {
mockOpClient.EXPECT().DeleteService(namespace, "test-service", &metav1.DeleteOptions{}).Return(nil)
service := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test-service",
Expand All @@ -396,7 +428,23 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
Selector: selector(t, "test=label").MatchLabels,
},
}
mockOpClient.EXPECT().CreateService(&service).Return(&service, nil)
portsApplyConfig := []*corev1ac.ServicePortApplyConfiguration{}
for _, p := range args.ports {
ac := corev1ac.ServicePort().
WithName(p.Name).
WithPort(p.Port).
WithTargetPort(p.TargetPort)
portsApplyConfig = append(portsApplyConfig, ac)
}

svcApplyConfig := corev1ac.Service(service.Name, service.Namespace).
WithSpec(corev1ac.ServiceSpec().
WithPorts(portsApplyConfig...).
WithSelector(selector(t, "test=label").MatchLabels)).
WithOwnerReferences(ownerutil.NonBlockingOwnerApplyConfiguration(owner)).
WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue})

mockOpClient.EXPECT().ApplyService(svcApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(&service, nil)

hosts := []string{
fmt.Sprintf("%s.%s", service.GetName(), namespace),
Expand Down Expand Up @@ -484,7 +532,23 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
},
}

mockOpClient.EXPECT().UpdateClusterRoleBinding(authDelegatorClusterRoleBinding).Return(authDelegatorClusterRoleBinding, nil)
crbLabels := map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}
for key, val := range ownerutil.OwnerLabel(owner, owner.GetObjectKind().GroupVersionKind().Kind) {
crbLabels[key] = val
}
crbApplyConfig := rbacv1ac.ClusterRoleBinding(service.GetName() + "-system:auth-delegator").
WithSubjects(rbacv1ac.Subject().
WithKind("ServiceAccount").
WithAPIGroup("").
WithName("test-sa").
WithNamespace(namespace)).
WithRoleRef(rbacv1ac.RoleRef().
WithAPIGroup("rbac.authorization.k8s.io").
WithKind("ClusterRole").
WithName("system:auth-delegator")).
WithLabels(crbLabels)

mockOpClient.EXPECT().ApplyClusterRoleBinding(crbApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(authDelegatorClusterRoleBinding, nil)

authReaderRoleBinding := &rbacv1.RoleBinding{
Subjects: []rbacv1.Subject{
Expand Down Expand Up @@ -602,9 +666,8 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
},
},
{
name: "labels an unlabelled secret if present",
name: "labels an unlabelled secret if present; creates Service and ClusterRoleBinding if not existing",
mockExternal: func(mockOpClient *operatorclientmocks.MockClientInterface, fakeLister *operatorlisterfakes.FakeOperatorLister, namespace string, args args) {
mockOpClient.EXPECT().DeleteService(namespace, "test-service", &metav1.DeleteOptions{}).Return(nil)
service := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test-service",
Expand All @@ -618,7 +681,24 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
Selector: selector(t, "test=label").MatchLabels,
},
}
mockOpClient.EXPECT().CreateService(&service).Return(&service, nil)

portsApplyConfig := []*corev1ac.ServicePortApplyConfiguration{}
for _, p := range args.ports {
ac := corev1ac.ServicePort().
WithName(p.Name).
WithPort(p.Port).
WithTargetPort(p.TargetPort)
portsApplyConfig = append(portsApplyConfig, ac)
}

svcApplyConfig := corev1ac.Service(service.Name, service.Namespace).
WithSpec(corev1ac.ServiceSpec().
WithPorts(portsApplyConfig...).
WithSelector(selector(t, "test=label").MatchLabels)).
WithOwnerReferences(ownerutil.NonBlockingOwnerApplyConfiguration(&v1alpha1.ClusterServiceVersion{})).
WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue})

mockOpClient.EXPECT().ApplyService(svcApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(&service, nil)

hosts := []string{
fmt.Sprintf("%s.%s", service.GetName(), namespace),
Expand Down Expand Up @@ -715,8 +795,22 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
Name: "system:auth-delegator",
},
}

mockOpClient.EXPECT().UpdateClusterRoleBinding(authDelegatorClusterRoleBinding).Return(authDelegatorClusterRoleBinding, nil)
crbLabels := map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}
for key, val := range ownerutil.OwnerLabel(ownerutil.Owner(&v1alpha1.ClusterServiceVersion{}), owner.GetObjectKind().GroupVersionKind().Kind) {
crbLabels[key] = val
}
crbApplyConfig := rbacv1ac.ClusterRoleBinding(AuthDelegatorClusterRoleBindingName(service.GetName())).
WithSubjects(rbacv1ac.Subject().WithKind("ServiceAccount").
WithAPIGroup("").
WithName("test-sa").
WithNamespace(namespace)).
WithRoleRef(rbacv1ac.RoleRef().
WithAPIGroup("rbac.authorization.k8s.io").
WithKind("ClusterRole").
WithName("system:auth-delegator")).
WithLabels(crbLabels)

mockOpClient.EXPECT().ApplyClusterRoleBinding(crbApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(authDelegatorClusterRoleBinding, nil)

authReaderRoleBinding := &rbacv1.RoleBinding{
Subjects: []rbacv1.Subject{
Expand All @@ -740,13 +834,7 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
mockOpClient.EXPECT().UpdateRoleBinding(authReaderRoleBinding).Return(authReaderRoleBinding, nil)
},
state: fakeState{
existingService: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
ownerutil.NonBlockingOwner(&v1alpha1.ClusterServiceVersion{}),
},
},
},
existingService: nil,
// unlabelled secret won't be in cache
getSecretError: errors.NewNotFound(schema.GroupResource{
Group: "",
Expand All @@ -758,9 +846,7 @@ func TestInstallCertRequirementsForDeployment(t *testing.T) {
existingRoleBinding: &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{},
},
existingClusterRoleBinding: &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{},
},
existingClusterRoleBinding: nil,
},
fields: fields{
owner: &v1alpha1.ClusterServiceVersion{},
Expand Down
Loading
Loading