Skip to content

Commit bccfb78

Browse files
committed
feat(ta): allowNamespaces and denyNamespaces
When the target allocator is configured to watch Prometheus custom resources in the cluster to discover targets, it is currently hard-coded to require a ClusterRole with a policy rule of listing namespaces. This prevents usage in environments where configuring ClusterRoles is not permitted i.e. in namespace-as-a-service setups where only Roles can be created. This change introduces two fields in the prometheusCR specification to allow configuring the namespaces that can be interacted with by the target allocator. - allowNamespaces is a list of namespaces for the target allocator to watch. If set to an empty list, it will list all list all namespaces in the cluster. This is mutually exclusive with denyNamespaces. Defaults to an empty list. - denyNamespaces is a list of namespaces for the target allocator to not watch. If set to an empty list, it will not exclude any namespaces. This is mutually exclusive with allowNamespaces. Defaults to an empty list. A new end to end test was created, e2e-targetallocator/targetallocator-namespace, to test setting allowNamespaces and denyNamespaces. The first part of the test sets up a a collector and TA in a namespace suffixed with '-top-secret'. The TA in this namespace is configured with the allowNamespaces to by this '-top-secret' namespace. Then a check is performed to make sure that it can discover the service monitor that is created by the otel-operator. This setup also doesn't make use of any ClusterRole as it only creates a Role to perform its task of discovering targets in its own namespace. The second part of the test sets up another collector and TA are created in a separate namespace without the '-top-secret' namespace with a ClusterRole. The denyNamespaces is set to the namespace suffixed with the '-top-secret' namespace, and later we check that we can't discover the ServiceMonitor that is in the 'top-secret' namespace. We also check that it can still discover the ServiceMonitor that is in its own namespace. Documentation was added to the otel-allocator README on how to set allowNamespaces to configure the scope that the TA is allowed to interact with. Fixes: #3086 Signed-off-by: Charlie Le <[email protected]>
1 parent 7943dca commit bccfb78

34 files changed

+798
-65
lines changed

.chloggen/namespace-ta.yaml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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: target allocator
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: |
9+
Add support for setting the allowNamespaces and denyNamespaces in the target allocator.
10+
11+
# One or more tracking issues related to the change
12+
issues: [3086]
13+
14+
# (Optional) One or more lines of additional information to render under the primary note.
15+
# These lines will be padded with 2 spaces and then inserted directly into the document.
16+
# Use pipe (|) for multiline entries.
17+
subtext: |
18+
allowNamespaces can be set to an empty list to watch all namespaces (default) or to list of namespaces to watch.
19+
denyNamespaces can be set to an empty list to deny watching any namespaces (default) or to a list of namespaces to deny watching.

apis/v1alpha1/targetallocator_webhook.go

+5
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ func (w TargetAllocatorWebhook) validate(ctx context.Context, ta *TargetAllocato
116116
if err != nil || len(warnings) > 0 {
117117
return warnings, err
118118
}
119+
120+
// Check to see that allowNamespaces and denyNamespaces are not both set at the same time
121+
if len(ta.Spec.PrometheusCR.AllowNamespaces) > 0 && len(ta.Spec.PrometheusCR.DenyNamespaces) > 0 {
122+
return warnings, fmt.Errorf("allowNamespaces and denyNamespaces are mutually exclusive")
123+
}
119124
}
120125

121126
return warnings, nil

apis/v1alpha1/targetallocator_webhook_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,19 @@ func TestTargetAllocatorValidatingWebhook(t *testing.T) {
301301
},
302302
expectedErr: "the OpenTelemetry Spec Ports configuration is incorrect",
303303
},
304+
{
305+
name: "allowNamespaces and denyNamespaces can't both be set",
306+
targetallocator: TargetAllocator{
307+
Spec: TargetAllocatorSpec{
308+
PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{
309+
Enabled: true,
310+
AllowNamespaces: []string{"ns1"},
311+
DenyNamespaces: []string{"ns2"},
312+
},
313+
},
314+
},
315+
expectedErr: "allowNamespaces and denyNamespaces are mutually exclusive",
316+
},
304317
}
305318

306319
for _, test := range tests {

apis/v1beta1/targetallocator_types.go

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ type TargetAllocatorPrometheusCR struct {
1212
// Enabled indicates whether to use a PrometheusOperator custom resources as targets or not.
1313
// +optional
1414
Enabled bool `json:"enabled,omitempty"`
15+
// AllowNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (allow list). This is mutually exclusive with DenyNamespaces.
16+
// +optional
17+
AllowNamespaces []string `json:"allowNamespaces,omitempty"`
18+
// DenyNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (deny list). This is mutually exclusive with AllowNamespaces.
19+
// +optional
20+
DenyNamespaces []string `json:"denyNamespaces,omitempty"`
1521
// Default interval between consecutive scrapes. Intervals set in ServiceMonitors and PodMonitors override it.
1622
//Equivalent to the same setting on the Prometheus CR.
1723
//

apis/v1beta1/zz_generated.deepcopy.go

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -7889,6 +7889,14 @@ spec:
78897889
type: object
78907890
prometheusCR:
78917891
properties:
7892+
allowNamespaces:
7893+
items:
7894+
type: string
7895+
type: array
7896+
denyNamespaces:
7897+
items:
7898+
type: string
7899+
type: array
78927900
enabled:
78937901
type: boolean
78947902
podMonitorSelector:

bundle/community/manifests/opentelemetry.io_targetallocators.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,14 @@ spec:
22572257
type: string
22582258
prometheusCR:
22592259
properties:
2260+
allowNamespaces:
2261+
items:
2262+
type: string
2263+
type: array
2264+
denyNamespaces:
2265+
items:
2266+
type: string
2267+
type: array
22602268
enabled:
22612269
type: boolean
22622270
podMonitorSelector:

bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -7889,6 +7889,14 @@ spec:
78897889
type: object
78907890
prometheusCR:
78917891
properties:
7892+
allowNamespaces:
7893+
items:
7894+
type: string
7895+
type: array
7896+
denyNamespaces:
7897+
items:
7898+
type: string
7899+
type: array
78927900
enabled:
78937901
type: boolean
78947902
podMonitorSelector:

bundle/openshift/manifests/opentelemetry.io_targetallocators.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,14 @@ spec:
22572257
type: string
22582258
prometheusCR:
22592259
properties:
2260+
allowNamespaces:
2261+
items:
2262+
type: string
2263+
type: array
2264+
denyNamespaces:
2265+
items:
2266+
type: string
2267+
type: array
22602268
enabled:
22612269
type: boolean
22622270
podMonitorSelector:

cmd/otel-allocator/README.md

+83-8
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,11 @@ Upstream documentation here: [PrometheusReceiver](https://github.com/open-teleme
191191

192192
### RBAC
193193

194-
Before the TargetAllocator can start scraping, you need to set up Kubernetes RBAC (role-based access controls) resources. This means that you need to have a `ServiceAccount` and corresponding cluster roles so that the TargetAllocator has access to all of the necessary resources to pull metrics from.
194+
Before the TargetAllocator can start scraping, you need to set up Kubernetes RBAC (role-based access controls) resources. This means that you need to have a `ServiceAccount` and corresponding ClusterRoles/Roles so that the TargetAllocator has access to all the necessary resources to pull metrics from.
195195

196-
You can create your own `ServiceAccount`, and reference it in `spec.targetAllocator.serviceAccount` in your `OpenTelemetryCollector` CR. You’ll then need to configure the `ClusterRole` and `ClusterRoleBinding` for this `ServiceAccount`, as per below.
196+
You can create your own `ServiceAccount`, and reference it in `spec.targetAllocator.serviceAccount` in your `OpenTelemetryCollector` CR. You’ll then need to configure the `ClusterRole` and `ClusterRoleBinding` or `Role` and `RoleBinding` for this `ServiceAccount`, as per below.
197+
198+
#### Cluster-scoped RBAC
197199

198200
```yaml
199201
targetAllocator:
@@ -204,11 +206,11 @@ You can create your own `ServiceAccount`, and reference it in `spec.targetAlloca
204206
```
205207

206208
> 🚨 **Note**: The Collector part of this same CR *also* has a serviceAccount key which only affects the collector and *not*
207-
the TargetAllocator.
209+
> the TargetAllocator.
208210

209-
If you omit the `ServiceAccount` name, the TargetAllocator creates a `ServiceAccount` for you. The `ServiceAccount`’s default name is a concatenation of the Collector name and the `-targetallocator` suffix. By default, this `ServiceAccount` has no defined policy, so you’ll need to create your own `ClusterRole` and `ClusterRoleBinding` for it, as per below.
211+
If you omit the `ServiceAccount` name, the TargetAllocator creates a `ServiceAccount` for you. The `ServiceAccount`’s default name is a concatenation of the Collector name and the `-targetallocator` suffix. By default, this `ServiceAccount` has no defined policy, so you’ll need to create your own `ClusterRole` and `ClusterRoleBinding` or `Role` and `RoleBinding` for it, as per below.
210212

211-
The role below will provide the minimum access required for the Target Allocator to query all the targets it needs based on any Prometheus configurations:
213+
The ClusterRole below will provide the minimum access required for the Target Allocator to query all the targets it needs based on any Prometheus configurations:
212214

213215
```yaml
214216
apiVersion: rbac.authorization.k8s.io/v1
@@ -242,7 +244,7 @@ rules:
242244
verbs: ["get"]
243245
```
244246

245-
If you enable the the `prometheusCR` (set `spec.targetAllocator.prometheusCR.enabled` to `true`) in the `OpenTelemetryCollector` CR, you will also need to define the following roles. These give the TargetAllocator access to the `PodMonitor` and `ServiceMonitor` CRs. It also gives namespace access to the `PodMonitor` and `ServiceMonitor`.
247+
If you enable the `prometheusCR` (set `spec.targetAllocator.prometheusCR.enabled` to `true`) in the `OpenTelemetryCollector` CR, you will also need to define the following ClusterRoles. These give the TargetAllocator access to the `PodMonitor` and `ServiceMonitor` CRs. It also gives namespace access to the `PodMonitor` and `ServiceMonitor`.
246248

247249
```yaml
248250
apiVersion: rbac.authorization.k8s.io/v1
@@ -263,8 +265,82 @@ rules:
263265
verbs: ["get", "list", "watch"]
264266
```
265267

266-
> ✨ The above roles can be combined into a single role.
268+
> ✨ The above ClusterRoles can be combined into a single ClusterRole.
269+
270+
#### Namespace-scoped RBAC
271+
272+
If you want to have the TargetAllocator watch a specific namespace, you can set the allowNamespaces field
273+
in the TargetAllocator's prometheusCR configuration. This is useful if you want to restrict the TargetAllocator to only watch Prometheus
274+
CRs in a specific namespace, and not have cluster-wide access.
275+
276+
```yaml
277+
targetAllocator:
278+
enabled: true
279+
serviceAccount: opentelemetry-targetallocator-sa
280+
prometheusCR:
281+
enabled: true
282+
allowNamespaces:
283+
- foo
284+
```
285+
286+
In this case, you will need to create a Role and RoleBinding instead of a ClusterRole and ClusterRoleBinding. The Role
287+
and RoleBinding should be created in the namespace specified by the allowNamespaces field.
267288

289+
```yaml
290+
apiVersion: rbac.authorization.k8s.io/v1
291+
kind: Role
292+
metadata:
293+
name: opentelemetry-targetallocator-role
294+
rules:
295+
- apiGroups:
296+
- ""
297+
resources:
298+
- pods
299+
- services
300+
- endpoints
301+
- configmaps
302+
- secrets
303+
- namespaces
304+
verbs:
305+
- get
306+
- watch
307+
- list
308+
- apiGroups:
309+
- apps
310+
resources:
311+
- statefulsets
312+
verbs:
313+
- get
314+
- watch
315+
- list
316+
- apiGroups:
317+
- discovery.k8s.io
318+
resources:
319+
- endpointslices
320+
verbs:
321+
- get
322+
- watch
323+
- list
324+
- apiGroups:
325+
- networking.k8s.io
326+
resources:
327+
- ingresses
328+
verbs:
329+
- get
330+
- watch
331+
- list
332+
- apiGroups:
333+
- monitoring.coreos.com
334+
resources:
335+
- servicemonitors
336+
- podmonitors
337+
- scrapeconfigs
338+
- probes
339+
verbs:
340+
- get
341+
- watch
342+
- list
343+
```
268344

269345
### Service / Pod monitor endpoint credentials
270346

@@ -420,4 +496,3 @@ Shards the received targets based on the discovered Collector instances
420496

421497
### Collector
422498
Client to watch for deployed Collector instances which will then provided to the Allocator.
423-

cmd/otel-allocator/internal/config/config.go

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type Config struct {
5353

5454
type PrometheusCRConfig struct {
5555
Enabled bool `yaml:"enabled,omitempty"`
56+
AllowNamespaces []string `yaml:"allow_namespaces,omitempty"`
57+
DenyNamespaces []string `yaml:"deny_namespaces,omitempty"`
5658
PodMonitorSelector *metav1.LabelSelector `yaml:"pod_monitor_selector,omitempty"`
5759
PodMonitorNamespaceSelector *metav1.LabelSelector `yaml:"pod_monitor_namespace_selector,omitempty"`
5860
ServiceMonitorSelector *metav1.LabelSelector `yaml:"service_monitor_selector,omitempty"`

cmd/otel-allocator/internal/watcher/promOperator.go

+27-4
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,30 @@ func NewPrometheusCRWatcher(ctx context.Context, logger logr.Logger, cfg allocat
5353
return nil, err
5454
}
5555

56-
factory := informers.NewMonitoringInformerFactories(map[string]struct{}{v1.NamespaceAll: {}}, map[string]struct{}{}, mClient, allocatorconfig.DefaultResyncTime, nil) //TODO decide what strategy to use regarding namespaces
56+
if len(cfg.PrometheusCR.AllowNamespaces) != 0 && len(cfg.PrometheusCR.DenyNamespaces) != 0 {
57+
return nil, fmt.Errorf("both allow and deny namespaces are set, only one should be set")
58+
}
59+
60+
allowList := map[string]struct{}{}
61+
if len(cfg.PrometheusCR.AllowNamespaces) != 0 {
62+
logger.Info("watching namespace(s)", "namespaces", cfg.PrometheusCR.AllowNamespaces)
63+
for _, ns := range cfg.PrometheusCR.AllowNamespaces {
64+
allowList[ns] = struct{}{}
65+
}
66+
} else {
67+
logger.Info("cfg.PrometheusCR.AllowNamespaces is unset, watching all namespaces")
68+
allowList = map[string]struct{}{v1.NamespaceAll: {}}
69+
}
70+
71+
denyList := map[string]struct{}{}
72+
if len(cfg.PrometheusCR.DenyNamespaces) != 0 {
73+
logger.Info("excluding namespace(s)", "namespaces", cfg.PrometheusCR.DenyNamespaces)
74+
for _, ns := range cfg.PrometheusCR.DenyNamespaces {
75+
denyList[ns] = struct{}{}
76+
}
77+
}
78+
79+
factory := informers.NewMonitoringInformerFactories(allowList, denyList, mClient, allocatorconfig.DefaultResyncTime, nil)
5780

5881
monitoringInformers, err := getInformers(factory)
5982
if err != nil {
@@ -99,7 +122,7 @@ func NewPrometheusCRWatcher(ctx context.Context, logger logr.Logger, cfg allocat
99122
logger.Error(err, "Retrying namespace informer creation in promOperator CRD watcher")
100123
return true
101124
}, func() error {
102-
nsMonInf, err = getNamespaceInformer(ctx, map[string]struct{}{v1.NamespaceAll: {}}, promLogger, clientset, operatorMetrics)
125+
nsMonInf, err = getNamespaceInformer(ctx, allowList, denyList, promLogger, clientset, operatorMetrics)
103126
return err
104127
})
105128
if getNamespaceInformerErr != nil {
@@ -149,7 +172,7 @@ type PrometheusCRWatcher struct {
149172
store *assets.StoreBuilder
150173
}
151174

152-
func getNamespaceInformer(ctx context.Context, allowList map[string]struct{}, promOperatorLogger *slog.Logger, clientset kubernetes.Interface, operatorMetrics *operator.Metrics) (cache.SharedIndexInformer, error) {
175+
func getNamespaceInformer(ctx context.Context, allowList, denyList map[string]struct{}, promOperatorLogger *slog.Logger, clientset kubernetes.Interface, operatorMetrics *operator.Metrics) (cache.SharedIndexInformer, error) {
153176
kubernetesVersion, err := clientset.Discovery().ServerVersion()
154177
if err != nil {
155178
return nil, err
@@ -165,7 +188,7 @@ func getNamespaceInformer(ctx context.Context, allowList map[string]struct{}, pr
165188
clientset.CoreV1(),
166189
clientset.AuthorizationV1().SelfSubjectAccessReviews(),
167190
allowList,
168-
map[string]struct{}{},
191+
denyList,
169192
)
170193
if err != nil {
171194
return nil, err

config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -7875,6 +7875,14 @@ spec:
78757875
type: object
78767876
prometheusCR:
78777877
properties:
7878+
allowNamespaces:
7879+
items:
7880+
type: string
7881+
type: array
7882+
denyNamespaces:
7883+
items:
7884+
type: string
7885+
type: array
78787886
enabled:
78797887
type: boolean
78807888
podMonitorSelector:

config/crd/bases/opentelemetry.io_targetallocators.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -2254,6 +2254,14 @@ spec:
22542254
type: string
22552255
prometheusCR:
22562256
properties:
2257+
allowNamespaces:
2258+
items:
2259+
type: string
2260+
type: array
2261+
denyNamespaces:
2262+
items:
2263+
type: string
2264+
type: array
22572265
enabled:
22582266
type: boolean
22592267
podMonitorSelector:

0 commit comments

Comments
 (0)