Skip to content

Commit 551ed20

Browse files
committed
Add automatic RBAC creation for prometheus receiver
Signed-off-by: Israel Blancas <[email protected]>
1 parent bfead6f commit 551ed20

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1317
-105
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: enhancement
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
5+
component: collector
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: Create RBAC automatically for the Prometheus receiver
9+
10+
# One or more tracking issues related to the change
11+
issues: [3078]
12+
13+
# (Optional) One or more lines of additional information to render under the primary note.
14+
# These lines will be padded with 2 spaces and then inserted directly into the document.
15+
# Use pipe (|) for multiline entries.
16+
subtext:

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ add-rbac-permissions-to-operator: manifests kustomize
210210
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/cronjobs.yaml
211211
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/daemonsets.yaml
212212
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/events.yaml
213+
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/endpoints.yaml
213214
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/extensions.yaml
214215
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/namespaces.yaml
215216
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/namespaces-status.yaml

apis/v1beta1/config.go

+74-4
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ type Config struct {
154154
}
155155

156156
// getRbacRulesForComponentKinds gets the RBAC Rules for the given ComponentKind(s).
157-
func (c *Config) getRbacRulesForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) ([]rbacv1.PolicyRule, error) {
157+
func (c *Config) getClusterRoleRbacRulesForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) ([]rbacv1.PolicyRule, error) {
158158
var rules []rbacv1.PolicyRule
159159
enabledComponents := c.GetEnabledComponents()
160160
for _, componentKind := range componentKinds {
@@ -180,7 +180,7 @@ func (c *Config) getRbacRulesForComponentKinds(logger logr.Logger, componentKind
180180
for componentName := range enabledComponents[componentKind] {
181181
// TODO: Clean up the naming here and make it simpler to use a retriever.
182182
parser := retriever(componentName)
183-
if parsedRules, err := parser.GetRBACRules(logger, cfg.Object[componentName]); err != nil {
183+
if parsedRules, err := parser.GetClusterRoleRules(logger, cfg.Object[componentName]); err != nil {
184184
return nil, err
185185
} else {
186186
rules = append(rules, parsedRules...)
@@ -190,6 +190,68 @@ func (c *Config) getRbacRulesForComponentKinds(logger logr.Logger, componentKind
190190
return rules, nil
191191
}
192192

193+
// getRbacRolesForComponentKinds gets the RBAC Roles for the given ComponentKind(s).
194+
func (c *Config) getRbacRolesForComponentKinds(logger logr.Logger, otelCollectorName string, componentKinds ...ComponentKind) ([]*rbacv1.Role, error) {
195+
var roles []*rbacv1.Role
196+
enabledComponents := c.GetEnabledComponents()
197+
for _, componentKind := range componentKinds {
198+
var retriever components.ParserRetriever
199+
var cfg AnyConfig
200+
switch componentKind {
201+
case KindReceiver:
202+
retriever = receivers.ReceiverFor
203+
cfg = c.Receivers
204+
case KindExporter:
205+
continue
206+
case KindProcessor:
207+
continue
208+
case KindExtension:
209+
continue
210+
}
211+
for componentName := range enabledComponents[componentKind] {
212+
// TODO: Clean up the naming here and make it simpler to use a retriever.
213+
parser := retriever(componentName)
214+
if parsedRoles, err := parser.GetRbacRoles(logger, otelCollectorName, cfg.Object[componentName]); err != nil {
215+
return nil, err
216+
} else {
217+
roles = append(roles, parsedRoles...)
218+
}
219+
}
220+
}
221+
return roles, nil
222+
}
223+
224+
// getRbacRoleBindingsForComponentKinds gets the RBAC RoleBindings for the given ComponentKind(s).
225+
func (c *Config) getRbacRoleBindingsForComponentKinds(logger logr.Logger, serviceAccountName string, otelCollectorName string, otelCollectorNamespace string, componentKinds ...ComponentKind) ([]*rbacv1.RoleBinding, error) {
226+
var roleBindings []*rbacv1.RoleBinding
227+
enabledComponents := c.GetEnabledComponents()
228+
for _, componentKind := range componentKinds {
229+
var retriever components.ParserRetriever
230+
var cfg AnyConfig
231+
switch componentKind {
232+
case KindReceiver:
233+
retriever = receivers.ReceiverFor
234+
cfg = c.Receivers
235+
case KindExporter:
236+
continue
237+
case KindProcessor:
238+
continue
239+
case KindExtension:
240+
continue
241+
}
242+
for componentName := range enabledComponents[componentKind] {
243+
// TODO: Clean up the naming here and make it simpler to use a retriever.
244+
parser := retriever(componentName)
245+
if parsedRoleBindings, err := parser.GetRbacRoleBindings(logger, otelCollectorName, cfg.Object[componentName], serviceAccountName, otelCollectorNamespace); err != nil {
246+
return nil, err
247+
} else {
248+
roleBindings = append(roleBindings, parsedRoleBindings...)
249+
}
250+
}
251+
}
252+
return roleBindings, nil
253+
}
254+
193255
// getPortsForComponentKinds gets the ports for the given ComponentKind(s).
194256
func (c *Config) getPortsForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) ([]corev1.ServicePort, error) {
195257
var ports []corev1.ServicePort
@@ -340,8 +402,16 @@ func (c *Config) GetEnvironmentVariables(logger logr.Logger) ([]corev1.EnvVar, e
340402
return c.getEnvironmentVariablesForComponentKinds(logger, KindReceiver)
341403
}
342404

343-
func (c *Config) GetAllRbacRules(logger logr.Logger) ([]rbacv1.PolicyRule, error) {
344-
return c.getRbacRulesForComponentKinds(logger, KindReceiver, KindExporter, KindProcessor)
405+
func (c *Config) GetAllClusterRoleRbacRules(logger logr.Logger) ([]rbacv1.PolicyRule, error) {
406+
return c.getClusterRoleRbacRulesForComponentKinds(logger, KindReceiver, KindExporter, KindProcessor)
407+
}
408+
409+
func (c *Config) GetAllRbacRoles(logger logr.Logger, otelCollectorName string) ([]*rbacv1.Role, error) {
410+
return c.getRbacRolesForComponentKinds(logger, otelCollectorName, KindReceiver, KindExporter, KindProcessor)
411+
}
412+
413+
func (c *Config) GetAllRbacRoleBindings(logger logr.Logger, serviceAccountName string, otelCollectorName string, otelCollectorNamespace string) ([]*rbacv1.RoleBinding, error) {
414+
return c.getRbacRoleBindingsForComponentKinds(logger, serviceAccountName, otelCollectorName, otelCollectorNamespace, KindReceiver, KindExporter, KindProcessor)
345415
}
346416

347417
func (c *Config) ApplyDefaults(logger logr.Logger) error {

controllers/common.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -158,16 +158,22 @@ func reconcileDesiredObjects(ctx context.Context, kubeClient client.Client, logg
158158
"object_kind", desired.GetObjectKind(),
159159
)
160160
if isNamespaceScoped(desired) {
161-
if setErr := ctrl.SetControllerReference(owner, desired, scheme); setErr != nil {
162-
l.Error(setErr, "failed to set controller owner reference to desired")
163-
errs = append(errs, setErr)
164-
continue
161+
switch desired.(type) {
162+
case *rbacv1.Role, *rbacv1.RoleBinding:
163+
l.Info("skipping setting controller reference for role or rolebinding")
164+
default:
165+
if setErr := ctrl.SetControllerReference(owner, desired, scheme); setErr != nil {
166+
l.Error(setErr, "failed to set controller owner reference to desired")
167+
errs = append(errs, setErr)
168+
continue
169+
}
165170
}
166171
}
167172
// existing is an object the controller runtime will hydrate for us
168173
// we obtain the existing object by deep copying the desired object because it's the most convenient way
169174
existing := desired.DeepCopyObject().(client.Object)
170175
mutateFn := manifests.MutateFuncFor(existing, desired)
176+
171177
var op controllerutil.OperationResult
172178
crudErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
173179
result, createOrUpdateErr := ctrl.CreateOrUpdate(ctx, kubeClient, existing, mutateFn)

controllers/opentelemetrycollector_controller.go

+28-8
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ var (
6060
ownedClusterObjectTypes = []client.Object{
6161
&rbacv1.ClusterRole{},
6262
&rbacv1.ClusterRoleBinding{},
63+
&rbacv1.Role{},
64+
&rbacv1.RoleBinding{},
6365
}
6466
)
6567

@@ -91,14 +93,14 @@ func (r *OpenTelemetryCollectorReconciler) findOtelOwnedObjects(ctx context.Cont
9193
client.InNamespace(params.OtelCol.Namespace),
9294
client.MatchingFields{resourceOwnerKey: params.OtelCol.Name},
9395
}
96+
rbacObjectsFound := false
9497
for _, objectType := range ownedObjectTypes {
98+
var objs map[types.UID]client.Object
9599
objs, err := getList(ctx, r, objectType, listOpts...)
96100
if err != nil {
97101
return nil, err
98102
}
99-
for uid, object := range objs {
100-
ownedObjects[uid] = object
101-
}
103+
102104
// save Collector ConfigMaps into a separate slice, we need to do additional filtering on them
103105
switch objectType.(type) {
104106
case *corev1.ConfigMap:
@@ -110,8 +112,20 @@ func (r *OpenTelemetryCollectorReconciler) findOtelOwnedObjects(ctx context.Cont
110112
configMap := object.(*corev1.ConfigMap)
111113
collectorConfigMaps = append(collectorConfigMaps, configMap)
112114
}
115+
case *rbacv1.ClusterRoleBinding, *rbacv1.ClusterRole, *rbacv1.RoleBinding, *rbacv1.Role:
116+
if params.Config.CreateRBACPermissions() == rbac.Available && !rbacObjectsFound {
117+
objs, err = r.findRBACObjects(ctx, params)
118+
if err != nil {
119+
return nil, err
120+
}
121+
rbacObjectsFound = true
122+
}
113123
default:
114124
}
125+
126+
for uid, object := range objs {
127+
ownedObjects[uid] = object
128+
}
115129
}
116130
// at this point we don't know if the most recent ConfigMap will still be the most recent after reconciliation, or
117131
// if a new one will be created. We keep one additional ConfigMap to account for this. The next reconciliation that
@@ -125,11 +139,15 @@ func (r *OpenTelemetryCollectorReconciler) findOtelOwnedObjects(ctx context.Cont
125139
return ownedObjects, nil
126140
}
127141

128-
// The cluster scope objects do not have owner reference.
129-
func (r *OpenTelemetryCollectorReconciler) findClusterRoleObjects(ctx context.Context, params manifests.Params) (map[types.UID]client.Object, error) {
142+
// findRBACObjects finds ClusterRoles, ClusterRoleBindings, Roles, and RoleBindings.
143+
// Those objects do not have owner references.
144+
// - ClusterRoles and ClusterRoleBindings cannot have owner references
145+
// - Roles and RoleBindings can exist in a different namespace than the OpenTelemetryCollector
146+
//
147+
// Users might switch off the RBAC creation feature on the operator which should remove existing RBAC.
148+
func (r *OpenTelemetryCollectorReconciler) findRBACObjects(ctx context.Context, params manifests.Params) (map[types.UID]client.Object, error) {
130149
ownedObjects := map[types.UID]client.Object{}
131-
// Remove cluster roles and bindings.
132-
// Users might switch off the RBAC creation feature on the operator which should remove existing RBAC.
150+
133151
listOpsCluster := &client.ListOptions{
134152
LabelSelector: labels.SelectorFromSet(
135153
manifestutils.SelectorLabels(params.OtelCol.ObjectMeta, collector.ComponentOpenTelemetryCollector)),
@@ -356,6 +374,8 @@ func (r *OpenTelemetryCollectorReconciler) GetOwnedResourceTypes() []client.Obje
356374
if r.config.CreateRBACPermissions() == rbac.Available {
357375
ownedResources = append(ownedResources, &rbacv1.ClusterRole{})
358376
ownedResources = append(ownedResources, &rbacv1.ClusterRoleBinding{})
377+
ownedResources = append(ownedResources, &rbacv1.Role{})
378+
ownedResources = append(ownedResources, &rbacv1.RoleBinding{})
359379
}
360380

361381
if featuregate.PrometheusOperatorIsAvailable.IsEnabled() && r.config.PrometheusCRAvailability() == prometheus.Available {
@@ -375,7 +395,7 @@ const collectorFinalizer = "opentelemetrycollector.opentelemetry.io/finalizer"
375395
func (r *OpenTelemetryCollectorReconciler) finalizeCollector(ctx context.Context, params manifests.Params) error {
376396
// The cluster scope objects do not have owner reference. They need to be deleted explicitly
377397
if params.Config.CreateRBACPermissions() == rbac.Available {
378-
objects, err := r.findClusterRoleObjects(ctx, params)
398+
objects, err := r.findRBACObjects(ctx, params)
379399
if err != nil {
380400
return err
381401
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package controllers
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
corev1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/runtime"
26+
"k8s.io/client-go/tools/record"
27+
ctrl "sigs.k8s.io/controller-runtime"
28+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
29+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
30+
31+
"github.com/open-telemetry/opentelemetry-operator/apis/v1beta1"
32+
"github.com/open-telemetry/opentelemetry-operator/internal/config"
33+
)
34+
35+
func TestReconcile(t *testing.T) {
36+
logger := zap.New()
37+
ctx := context.Background()
38+
39+
scheme := runtime.NewScheme()
40+
require.NoError(t, v1beta1.AddToScheme(scheme))
41+
require.NoError(t, corev1.AddToScheme(scheme))
42+
43+
tests := []struct {
44+
name string
45+
existingState []runtime.Object
46+
expectedResult ctrl.Result
47+
expectedError bool
48+
}{
49+
{
50+
name: "collector not found",
51+
existingState: []runtime.Object{},
52+
expectedResult: ctrl.Result{},
53+
expectedError: false,
54+
},
55+
{
56+
name: "unmanaged collector",
57+
existingState: []runtime.Object{
58+
&v1beta1.OpenTelemetryCollector{
59+
ObjectMeta: metav1.ObjectMeta{
60+
Name: "test-collector",
61+
Namespace: "default",
62+
},
63+
Spec: v1beta1.OpenTelemetryCollectorSpec{
64+
OpenTelemetryCommonFields: v1beta1.OpenTelemetryCommonFields{
65+
ManagementState: v1beta1.ManagementStateUnmanaged,
66+
},
67+
},
68+
},
69+
},
70+
expectedResult: ctrl.Result{},
71+
expectedError: false,
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
client := fake.NewClientBuilder().
78+
WithScheme(scheme).
79+
WithRuntimeObjects(tt.existingState...).
80+
Build()
81+
82+
r := &OpenTelemetryCollectorReconciler{
83+
Client: client,
84+
log: logger,
85+
scheme: scheme,
86+
config: config.New(),
87+
recorder: record.NewFakeRecorder(100),
88+
}
89+
90+
result, err := r.Reconcile(ctx, ctrl.Request{})
91+
92+
if tt.expectedError {
93+
assert.Error(t, err)
94+
} else {
95+
assert.NoError(t, err)
96+
}
97+
assert.Equal(t, tt.expectedResult, result)
98+
})
99+
}
100+
}
101+
102+
func TestNewReconciler(t *testing.T) {
103+
scheme := runtime.NewScheme()
104+
client := fake.NewClientBuilder().WithScheme(scheme).Build()
105+
recorder := record.NewFakeRecorder(100)
106+
logger := zap.New()
107+
cfg := config.New()
108+
109+
params := Params{
110+
Client: client,
111+
Recorder: recorder,
112+
Scheme: scheme,
113+
Log: logger,
114+
Config: cfg,
115+
}
116+
117+
r := NewReconciler(params)
118+
119+
assert.Equal(t, client, r.Client)
120+
assert.Equal(t, recorder, r.recorder)
121+
assert.Equal(t, scheme, r.scheme)
122+
assert.Equal(t, logger, r.log)
123+
assert.Equal(t, cfg, r.config)
124+
}

0 commit comments

Comments
 (0)