Skip to content

Commit 92de63d

Browse files
committed
Add CSV Namespace Plug-In
Signed-off-by: perdasilva <[email protected]>
1 parent f100a50 commit 92de63d

File tree

4 files changed

+524
-0
lines changed

4 files changed

+524
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package olm
2+
3+
import (
4+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/plugins"
5+
)
6+
7+
func init() {
8+
operatorPlugInFactoryFuncs = []plugins.OperatorPlugInFactoryFunc{
9+
// labels unlabeled non-payload openshift-* csv namespaces with
10+
// security.openshift.io/scc.podSecurityLabelSync: true
11+
plugins.NewCsvNamespaceLabelerPlugInfunc,
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package plugins
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/openshift/cluster-policy-controller/pkg/psalabelsyncer/nsexemptions"
9+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
10+
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
11+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/internal/pruning"
12+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubestate"
13+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
14+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
15+
"github.com/sirupsen/logrus"
16+
v1 "k8s.io/api/core/v1"
17+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
"k8s.io/client-go/informers"
19+
listerv1 "k8s.io/client-go/listers/core/v1"
20+
"k8s.io/client-go/tools/cache"
21+
"k8s.io/client-go/util/workqueue"
22+
)
23+
24+
const NamespaceLabelSyncerLabelKey = "security.openshift.io/scc.podSecurityLabelSync"
25+
26+
// csvNamespaceLabelerPlugin is responsible for labeling non-payload openshift-* namespaces
27+
// with the label "security.openshift.io/scc.podSecurityLabelSync=true" so that the PSA Label Syncer
28+
// see https://github.com/openshift/cluster-policy-controller/blob/master/pkg/psalabelsyncer/podsecurity_label_sync_controller.go
29+
// can help ensure that the operator payloads in the namespace continue to work even if they don't yet respect the
30+
// upstream Pod Security Admission controller, which will become active in k8s 1.25.
31+
// see https://kubernetes.io/docs/concepts/security/pod-security-admission/
32+
// If a CSV is created or modified, this controller will look at the csv's namespace. If it is a non-payload namespace,
33+
// if the namespace name is prefixed with 'openshift-', and if the namespace does not contain the label (whatever
34+
// value it may be set to), it will add the "security.openshift.io/scc.podSecurityLabelSync=true" to the namespace.
35+
type csvNamespaceLabelerPlugin struct {
36+
namespaceListerMap map[string]listerv1.NamespaceLister
37+
kubeClient operatorclient.ClientInterface
38+
externalClient versioned.Interface
39+
logger *logrus.Logger
40+
}
41+
42+
func NewCsvNamespaceLabelerPlugInfunc(ctx context.Context, config OperatorConfig, hostOperator HostOperator) (OperatorPlugin, error) {
43+
44+
if hostOperator == nil {
45+
return nil, fmt.Errorf("cannot initialize plugin: operator undefined")
46+
}
47+
48+
plugin := &csvNamespaceLabelerPlugin{
49+
kubeClient: config.OperatorClient(),
50+
externalClient: config.ExternalClient(),
51+
logger: config.Logger(),
52+
namespaceListerMap: map[string]listerv1.NamespaceLister{},
53+
}
54+
55+
for _, namespace := range config.WatchedNamespaces() {
56+
57+
// create a namespace informer
58+
namespaceInformer := informers.NewSharedInformerFactoryWithOptions(
59+
plugin.kubeClient.KubernetesInterface(),
60+
config.ResyncPeriod()(),
61+
informers.WithNamespace(namespace),
62+
).Core().V1().Namespaces()
63+
64+
if err := hostOperator.RegisterInformer(namespaceInformer.Informer()); err != nil {
65+
return nil, err
66+
}
67+
plugin.namespaceListerMap[namespace] = namespaceInformer.Lister()
68+
69+
// create a new csv informer and prune status to reduce memory footprint
70+
csvNamespaceLabelerInformer := cache.NewSharedIndexInformer(
71+
pruning.NewListerWatcher(
72+
plugin.externalClient,
73+
namespace,
74+
func(opts *metav1.ListOptions) {
75+
opts.LabelSelector = fmt.Sprintf("!%s", v1alpha1.CopiedLabelKey)
76+
},
77+
pruning.PrunerFunc(func(csv *v1alpha1.ClusterServiceVersion) {
78+
*csv = v1alpha1.ClusterServiceVersion{
79+
TypeMeta: csv.TypeMeta,
80+
ObjectMeta: csv.ObjectMeta,
81+
}
82+
}),
83+
),
84+
&v1alpha1.ClusterServiceVersion{},
85+
config.ResyncPeriod()(),
86+
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
87+
)
88+
89+
csvNamespaceLabelerPluginQueue := workqueue.NewNamedRateLimitingQueue(
90+
workqueue.DefaultControllerRateLimiter(),
91+
fmt.Sprintf("%s/csv-ns-labeler-plugin", namespace),
92+
)
93+
csvNamespaceLabelerPluginQueueInformer, err := queueinformer.NewQueueInformer(
94+
ctx,
95+
queueinformer.WithInformer(csvNamespaceLabelerInformer),
96+
queueinformer.WithLogger(config.Logger()),
97+
queueinformer.WithQueue(csvNamespaceLabelerPluginQueue),
98+
queueinformer.WithIndexer(csvNamespaceLabelerInformer.GetIndexer()),
99+
queueinformer.WithSyncer(plugin),
100+
)
101+
if err != nil {
102+
return nil, err
103+
}
104+
if err := hostOperator.RegisterQueueInformer(csvNamespaceLabelerPluginQueueInformer); err != nil {
105+
return nil, err
106+
}
107+
}
108+
109+
return plugin, nil
110+
}
111+
112+
func (p *csvNamespaceLabelerPlugin) Shutdown() error {
113+
return nil
114+
}
115+
116+
func (p *csvNamespaceLabelerPlugin) Sync(ctx context.Context, event kubestate.ResourceEvent) error {
117+
// only act on csv added and updated events
118+
if event.Type() != kubestate.ResourceAdded && event.Type() != kubestate.ResourceUpdated {
119+
return nil
120+
}
121+
122+
csv, ok := event.Resource().(*v1alpha1.ClusterServiceVersion)
123+
if !ok {
124+
return fmt.Errorf("event resource is not a ClusterServiceVersion")
125+
}
126+
127+
// ignore copied csvs
128+
// informer should already be filtering these out - but just in case
129+
if csv.IsCopied() {
130+
return nil
131+
}
132+
133+
// ignore non-openshift-* and payload openshift-* namespaces
134+
if !strings.HasPrefix(csv.GetNamespace(), "openshift-") || nsexemptions.IsNamespacePSALabelSyncExemptedInVendoredOCPVersion(csv.GetNamespace()) {
135+
return nil
136+
}
137+
138+
namespace, err := p.getNamespace(csv.GetNamespace())
139+
if err != nil {
140+
return fmt.Errorf("error getting csv namespace (%s) for label sync'er labeling", csv.GetNamespace())
141+
}
142+
143+
// add label sync'er label if it does not exist
144+
if _, ok := namespace.GetLabels()[NamespaceLabelSyncerLabelKey]; !ok {
145+
nsCopy := namespace.DeepCopy()
146+
if nsCopy.GetLabels() == nil {
147+
nsCopy.SetLabels(map[string]string{})
148+
}
149+
nsCopy.GetLabels()[NamespaceLabelSyncerLabelKey] = "true"
150+
if _, err := p.kubeClient.KubernetesInterface().CoreV1().Namespaces().Update(ctx, nsCopy, metav1.UpdateOptions{}); err != nil {
151+
return fmt.Errorf("error updating csv namespace (%s) with label sync'er label", nsCopy.GetNamespace())
152+
}
153+
154+
if p.logger != nil {
155+
p.logger.Infof("applied %s=true label to namespace %s", NamespaceLabelSyncerLabelKey, nsCopy.GetNamespace())
156+
}
157+
}
158+
159+
return nil
160+
}
161+
162+
func (p *csvNamespaceLabelerPlugin) getNamespace(namespace string) (*v1.Namespace, error) {
163+
lister, ok := p.namespaceListerMap[namespace]
164+
if !ok {
165+
lister, ok = p.namespaceListerMap[metav1.NamespaceAll]
166+
if !ok {
167+
return nil, fmt.Errorf("no namespace lister found for namespace: %s", namespace)
168+
}
169+
}
170+
return lister.Get(namespace)
171+
}

0 commit comments

Comments
 (0)