Skip to content

OCPBUGS-29729: Refactor security context configuration in pod reconciler #3185

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

Closed
wants to merge 4 commits into from
Closed
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
19 changes: 9 additions & 10 deletions pkg/controller/operators/catalog/operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ import (
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
"github.com/operator-framework/operator-lifecycle-manager/pkg/fakes"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clientfake"
controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
Expand Down Expand Up @@ -2006,14 +2005,15 @@ func NewFakeOperator(ctx context.Context, namespace string, namespaces []string,
}
op.sources = grpc.NewSourceStore(config.logger, 1*time.Second, 5*time.Second, op.syncSourceState)
if op.reconciler == nil {
s := runtime.NewScheme()
err := k8sfake.AddToScheme(s)
if err != nil {
return nil, err
op.reconciler = &fakes.FakeRegistryReconcilerFactory{
ReconcilerForSourceStub: func(source *v1alpha1.CatalogSource) reconciler.RegistryReconciler {
return &fakes.FakeRegistryReconciler{
EnsureRegistryServerStub: func(logger *logrus.Entry, source *v1alpha1.CatalogSource) error {
return nil
},
}
},
}
applier := controllerclient.NewFakeApplier(s, "testowner")

op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, op.opClient, "test:pod", op.now, applier, 1001, "", "")
}

op.RunInformers(ctx)
Expand All @@ -2028,7 +2028,6 @@ func NewFakeOperator(ctx context.Context, namespace string, namespaces []string,

return op, nil
}

func installPlan(name, namespace string, phase v1alpha1.InstallPlanPhase, names ...string) *v1alpha1.InstallPlan {
return &v1alpha1.InstallPlan{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -2175,7 +2174,7 @@ func pod(t *testing.T, s v1alpha1.CatalogSource) *corev1.Pod {
Name: s.GetName(),
},
}
pod, err := reconciler.Pod(&s, "registry-server", "central-opm", "central-util", s.Spec.Image, serviceAccount, s.GetLabels(), s.GetAnnotations(), 5, 10, 1001)
pod, err := reconciler.Pod(&s, "registry-server", "central-opm", "central-util", s.Spec.Image, serviceAccount, s.GetLabels(), s.GetAnnotations(), 5, 10, 1001, v1alpha1.Legacy)
if err != nil {
t.Fatal(err)
}
Expand Down
47 changes: 38 additions & 9 deletions pkg/controller/registry/reconciler/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import (
// configMapCatalogSourceDecorator wraps CatalogSource to add additional methods
type configMapCatalogSourceDecorator struct {
*v1alpha1.CatalogSource
runAsUser int64
Reconciler *ConfigMapRegistryReconciler
runAsUser int64
}

const (
Expand Down Expand Up @@ -109,8 +110,32 @@ func (s *configMapCatalogSourceDecorator) Service() (*corev1.Service, error) {
return svc, nil
}

func (s *configMapCatalogSourceDecorator) getNamespaceSecurityContextConfig() (v1alpha1.SecurityConfig, error) {
namespace := s.GetNamespace()
if config, ok := s.Reconciler.namespacePSAConfigCache[namespace]; ok {
return config, nil
}
// Retrieve the client from the reconciler
client := s.Reconciler.OpClient

ns, err := client.KubernetesInterface().CoreV1().Namespaces().Get(context.TODO(), s.GetNamespace(), metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("error fetching namespace: %v", err)
}
// 'pod-security.kubernetes.io/enforce' is the label used for enforcing namespace level security,
// and 'restricted' is the value indicating a restricted security policy.
if val, exists := ns.Labels["pod-security.kubernetes.io/enforce"]; exists && val == "restricted" {
return v1alpha1.Restricted, nil
}

return v1alpha1.Legacy, nil
}
func (s *configMapCatalogSourceDecorator) Pod(image string) (*corev1.Pod, error) {
pod, err := Pod(s.CatalogSource, "configmap-registry-server", "", "", image, nil, s.Labels(), s.Annotations(), 5, 5, s.runAsUser)
securityContextConfig, err := s.getNamespaceSecurityContextConfig()
if err != nil {
return nil, err
}
pod, err := Pod(s.CatalogSource, "configmap-registry-server", "", "", image, nil, s.Labels(), s.Annotations(), 5, 5, s.runAsUser, securityContextConfig)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -183,11 +208,12 @@ func (s *configMapCatalogSourceDecorator) RoleBinding() *rbacv1.RoleBinding {
}

type ConfigMapRegistryReconciler struct {
now nowFunc
Lister operatorlister.OperatorLister
OpClient operatorclient.ClientInterface
Image string
createPodAsUser int64
now nowFunc
Lister operatorlister.OperatorLister
OpClient operatorclient.ClientInterface
Image string
createPodAsUser int64
namespacePSAConfigCache map[string]v1alpha1.SecurityConfig
}

var _ RegistryEnsurer = &ConfigMapRegistryReconciler{}
Expand Down Expand Up @@ -274,7 +300,10 @@ func (c *ConfigMapRegistryReconciler) currentPodsWithCorrectResourceVersion(sour

// EnsureRegistryServer ensures that all components of registry server are up to date.
func (c *ConfigMapRegistryReconciler) EnsureRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) error {
source := configMapCatalogSourceDecorator{catalogSource, c.createPodAsUser}
if c.namespacePSAConfigCache == nil {
c.namespacePSAConfigCache = make(map[string]v1alpha1.SecurityConfig)
}
source := configMapCatalogSourceDecorator{catalogSource, c, c.createPodAsUser}

image := c.Image
if source.Spec.SourceType == "grpc" {
Expand Down Expand Up @@ -449,7 +478,7 @@ func (c *ConfigMapRegistryReconciler) ensureService(source configMapCatalogSourc

// CheckRegistryServer returns true if the given CatalogSource is considered healthy; false otherwise.
func (c *ConfigMapRegistryReconciler) CheckRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) (healthy bool, err error) {
source := configMapCatalogSourceDecorator{catalogSource, c.createPodAsUser}
source := configMapCatalogSourceDecorator{catalogSource, c, c.createPodAsUser}

image := c.Image
if source.Spec.SourceType == "grpc" {
Expand Down
36 changes: 18 additions & 18 deletions pkg/controller/registry/reconciler/configmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,29 +185,28 @@ func validConfigMapCatalogSource(configMap *corev1.ConfigMap) *v1alpha1.CatalogS
}
}

func objectsForCatalogSource(t *testing.T, catsrc *v1alpha1.CatalogSource) []runtime.Object {
func objectsForCatalogSource(t *testing.T, catsrc *v1alpha1.CatalogSource, reconciler RegistryReconciler) []runtime.Object {
var objs []runtime.Object
switch catsrc.Spec.SourceType {
case v1alpha1.SourceTypeInternal, v1alpha1.SourceTypeConfigmap:
decorated := configMapCatalogSourceDecorator{catsrc, runAsUser}
decorated := configMapCatalogSourceDecorator{catsrc, reconciler.(*ConfigMapRegistryReconciler), runAsUser}
service, err := decorated.Service()
if err != nil {
t.Fatal(err)
}
pod, err := decorated.Pod(registryImageName)
serviceAccount := decorated.ServiceAccount()
pod, err := decorated.Pod(catsrc.Spec.Image)
if err != nil {
t.Fatal(err)
}
objs = clientfake.AddSimpleGeneratedNames(
clientfake.AddSimpleGeneratedName(pod),
objs = append(objs,
pod,
service,
decorated.ServiceAccount(),
decorated.Role(),
decorated.RoleBinding(),
serviceAccount,
)
case v1alpha1.SourceTypeGrpc:
if catsrc.Spec.Image != "" {
decorated := grpcCatalogSourceDecorator{CatalogSource: catsrc, createPodAsUser: runAsUser, opmImage: ""}
decorated := grpcCatalogSourceDecorator{CatalogSource: catsrc, Reconciler: reconciler.(*GrpcRegistryReconciler), createPodAsUser: runAsUser, opmImage: ""}
serviceAccount := decorated.ServiceAccount()
service, err := decorated.Service()
if err != nil {
Expand All @@ -217,7 +216,7 @@ func objectsForCatalogSource(t *testing.T, catsrc *v1alpha1.CatalogSource) []run
if err != nil {
t.Fatal(err)
}
objs = clientfake.AddSimpleGeneratedNames(
objs = append(objs,
pod,
service,
serviceAccount,
Expand All @@ -238,6 +237,7 @@ func objectsForCatalogSource(t *testing.T, catsrc *v1alpha1.CatalogSource) []run
Controller: &isController,
}})
}

return objs
}

Expand Down Expand Up @@ -334,7 +334,7 @@ func TestConfigMapRegistryReconciler(t *testing.T) {
testName: "ExistingRegistry/BadServiceAccount",
in: in{
cluster: cluster{
k8sObjs: append(modifyObjName(objectsForCatalogSource(t, validCatalogSource), &corev1.ServiceAccount{}, "badName"), validConfigMap),
k8sObjs: append(modifyObjName(objectsForCatalogSource(t, validCatalogSource, nil), &corev1.ServiceAccount{}, "badName"), validConfigMap),
},
catsrc: validCatalogSource,
},
Expand All @@ -352,7 +352,7 @@ func TestConfigMapRegistryReconciler(t *testing.T) {
testName: "ExistingRegistry/BadService",
in: in{
cluster: cluster{
k8sObjs: append(modifyObjName(objectsForCatalogSource(t, validCatalogSource), &corev1.Service{}, "badName"), validConfigMap),
k8sObjs: append(modifyObjName(objectsForCatalogSource(t, validCatalogSource, nil), &corev1.Service{}, "badName"), validConfigMap),
},
catsrc: validCatalogSource,
},
Expand All @@ -370,7 +370,7 @@ func TestConfigMapRegistryReconciler(t *testing.T) {
testName: "ExistingRegistry/BadServiceWithWrongHash",
in: in{
cluster: cluster{
k8sObjs: append(setLabel(objectsForCatalogSource(t, validCatalogSource), &corev1.Service{}, ServiceHashLabelKey, "wrongHash"), validConfigMap),
k8sObjs: append(setLabel(objectsForCatalogSource(t, validCatalogSource, nil), &corev1.Service{}, ServiceHashLabelKey, "wrongHash"), validConfigMap),
},
catsrc: validCatalogSource,
},
Expand All @@ -388,7 +388,7 @@ func TestConfigMapRegistryReconciler(t *testing.T) {
testName: "ExistingRegistry/BadPod",
in: in{
cluster: cluster{
k8sObjs: append(setLabel(objectsForCatalogSource(t, validCatalogSource), &corev1.Pod{}, CatalogSourceLabelKey, "badValue"), validConfigMap),
k8sObjs: append(setLabel(objectsForCatalogSource(t, validCatalogSource, nil), &corev1.Pod{}, CatalogSourceLabelKey, "badValue"), validConfigMap),
},
catsrc: validCatalogSource,
},
Expand All @@ -406,7 +406,7 @@ func TestConfigMapRegistryReconciler(t *testing.T) {
testName: "ExistingRegistry/BadRole",
in: in{
cluster: cluster{
k8sObjs: append(modifyObjName(objectsForCatalogSource(t, validCatalogSource), &rbacv1.Role{}, "badName"), validConfigMap),
k8sObjs: append(modifyObjName(objectsForCatalogSource(t, validCatalogSource, nil), &rbacv1.Role{}, "badName"), validConfigMap),
},
catsrc: validCatalogSource,
},
Expand All @@ -424,7 +424,7 @@ func TestConfigMapRegistryReconciler(t *testing.T) {
testName: "ExistingRegistry/BadRoleBinding",
in: in{
cluster: cluster{
k8sObjs: append(modifyObjName(objectsForCatalogSource(t, validCatalogSource), &rbacv1.RoleBinding{}, "badName"), validConfigMap),
k8sObjs: append(modifyObjName(objectsForCatalogSource(t, validCatalogSource, nil), &rbacv1.RoleBinding{}, "badName"), validConfigMap),
},
catsrc: validCatalogSource,
},
Expand All @@ -442,7 +442,7 @@ func TestConfigMapRegistryReconciler(t *testing.T) {
testName: "ExistingRegistry/OldPod",
in: in{
cluster: cluster{
k8sObjs: append(objectsForCatalogSource(t, validCatalogSource), validConfigMap),
k8sObjs: append(objectsForCatalogSource(t, validCatalogSource, nil), validConfigMap),
},
catsrc: outdatedCatalogSource,
},
Expand Down Expand Up @@ -479,7 +479,7 @@ func TestConfigMapRegistryReconciler(t *testing.T) {
}

// if no error, the reconciler should create the same set of kube objects every time
decorated := configMapCatalogSourceDecorator{tt.in.catsrc, runAsUser}
decorated := configMapCatalogSourceDecorator{tt.in.catsrc, rec.(*ConfigMapRegistryReconciler), runAsUser}

pod, err := decorated.Pod(registryImageName)
require.NoError(t, err)
Expand Down
46 changes: 38 additions & 8 deletions pkg/controller/registry/reconciler/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
// grpcCatalogSourceDecorator wraps CatalogSource to add additional methods
type grpcCatalogSourceDecorator struct {
*v1alpha1.CatalogSource
Reconciler *GrpcRegistryReconciler
createPodAsUser int64
opmImage string
utilImage string
Expand Down Expand Up @@ -65,6 +66,27 @@ func (s *grpcCatalogSourceDecorator) Labels() map[string]string {
}
}

func (s *grpcCatalogSourceDecorator) getNamespaceSecurityContextConfig() (v1alpha1.SecurityConfig, error) {
namespace := s.GetNamespace()
if config, ok := s.Reconciler.namespacePSAConfigCache[namespace]; ok {
return config, nil
}
// Retrieve the client from the reconciler
client := s.Reconciler.OpClient

ns, err := client.KubernetesInterface().CoreV1().Namespaces().Get(context.TODO(), s.GetNamespace(), metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("error fetching namespace: %v", err)
}
// 'pod-security.kubernetes.io/enforce' is the label used for enforcing namespace level security,
// and 'restricted' is the value indicating a restricted security policy.
if val, exists := ns.Labels["pod-security.kubernetes.io/enforce"]; exists && val == "restricted" {
return v1alpha1.Restricted, nil
}

return v1alpha1.Legacy, nil
}

func (s *grpcCatalogSourceDecorator) Annotations() map[string]string {
// TODO: Maybe something better than just a copy of all annotations would be to have a specific 'podMetadata' section in the CatalogSource?
return s.GetAnnotations()
Expand Down Expand Up @@ -131,7 +153,11 @@ func (s *grpcCatalogSourceDecorator) ServiceAccount() *corev1.ServiceAccount {
}

func (s *grpcCatalogSourceDecorator) Pod(serviceAccount *corev1.ServiceAccount) (*corev1.Pod, error) {
pod, err := Pod(s.CatalogSource, "registry-server", s.opmImage, s.utilImage, s.Spec.Image, serviceAccount, s.Labels(), s.Annotations(), 5, 10, s.createPodAsUser)
securityContextConfig, err := s.getNamespaceSecurityContextConfig()
if err != nil {
return nil, err
}
pod, err := Pod(s.CatalogSource, "registry-server", s.opmImage, s.utilImage, s.Spec.Image, serviceAccount, s.Labels(), s.Annotations(), 5, 10, s.createPodAsUser, securityContextConfig)
if err != nil {
return nil, err
}
Expand All @@ -140,13 +166,14 @@ func (s *grpcCatalogSourceDecorator) Pod(serviceAccount *corev1.ServiceAccount)
}

type GrpcRegistryReconciler struct {
now nowFunc
Lister operatorlister.OperatorLister
OpClient operatorclient.ClientInterface
SSAClient *controllerclient.ServerSideApplier
createPodAsUser int64
opmImage string
utilImage string
now nowFunc
Lister operatorlister.OperatorLister
OpClient operatorclient.ClientInterface
SSAClient *controllerclient.ServerSideApplier
createPodAsUser int64
opmImage string
utilImage string
namespacePSAConfigCache map[string]v1alpha1.SecurityConfig
}

var _ RegistryReconciler = &GrpcRegistryReconciler{}
Expand Down Expand Up @@ -246,6 +273,9 @@ func correctImages(source grpcCatalogSourceDecorator, pod *corev1.Pod) bool {

// EnsureRegistryServer ensures that all components of registry server are up to date.
func (c *GrpcRegistryReconciler) EnsureRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) error {
if c.namespacePSAConfigCache == nil {
c.namespacePSAConfigCache = make(map[string]v1alpha1.SecurityConfig)
}
source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage, utilImage: c.utilImage}

// if service status is nil, we force create every object to ensure they're created the first time
Expand Down
Loading