Skip to content

Commit

Permalink
Add checking of the namespace PSA restrictions
Browse files Browse the repository at this point in the history
Signed-off-by: btofel <[email protected]>
  • Loading branch information
bentito committed Apr 4, 2024
1 parent f7d09c6 commit f19d68a
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 120 deletions.
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
48 changes: 39 additions & 9 deletions pkg/controller/registry/reconciler/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"fmt"

operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash"
"github.com/pkg/errors"
Expand All @@ -25,7 +26,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 +111,32 @@ func (s *configMapCatalogSourceDecorator) Service() (*corev1.Service, error) {
return svc, nil
}

func (s *configMapCatalogSourceDecorator) getNamespaceSecurityContextConfig() (operatorsv1alpha1.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 operatorsv1alpha1.Restricted, nil
}

return operatorsv1alpha1.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 +209,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]operatorsv1alpha1.SecurityConfig
}

var _ RegistryEnsurer = &ConfigMapRegistryReconciler{}
Expand Down Expand Up @@ -274,7 +301,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]operatorsv1alpha1.SecurityConfig)
}
source := configMapCatalogSourceDecorator{catalogSource, c, c.createPodAsUser}

image := c.Image
if source.Spec.SourceType == "grpc" {
Expand Down Expand Up @@ -449,7 +479,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
47 changes: 39 additions & 8 deletions pkg/controller/registry/reconciler/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/google/go-cmp/cmp"
"github.com/operator-framework/api/pkg/operators/v1alpha1"
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client"
hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash"
Expand All @@ -32,6 +33,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 +67,27 @@ func (s *grpcCatalogSourceDecorator) Labels() map[string]string {
}
}

func (s *grpcCatalogSourceDecorator) getNamespaceSecurityContextConfig() (operatorsv1alpha1.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 operatorsv1alpha1.Restricted, nil
}

return operatorsv1alpha1.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 +154,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 +167,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]operatorsv1alpha1.SecurityConfig
}

var _ RegistryReconciler = &GrpcRegistryReconciler{}
Expand Down Expand Up @@ -246,6 +274,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]operatorsv1alpha1.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

0 comments on commit f19d68a

Please sign in to comment.