Repository: open-telemetry/opentelemetry-operator
Pull request #3763 (2025-03-03 22:28:39)

Allow for scoping TA to watch namespace
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 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.

Validation is performed at the admission webhook and TA startup to
validate that both fields are not set at the same time.

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 the '-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 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


swiatekm: >     * allowNamespaces is a comma-separated list of namespaces for the target
>       allocator to watch. If set to an empty string, it will list all
>       list all namespaces in the cluster. This is mutually exclusive with
>       denyNamespaces. Defaults to an empty string.
> 
>     * denyNamespaces is a comma-separated list of namespaces for the target
>       allocator to not watch. If set to an empty string, it will not exclude
>       any namespaces. This is mutually exclusive with allowNamespaces.
>       Defaults to an empty string.

Why do these need to be comma-separated lists represented by strings? This made sense when we were going to use an environment variable, but both in CRDs and YAML config files, we can just use a list of strings.

Also, if we want these options to be mutually-exclusive, we need to implement validation for that logic in the admission webhook.

Patch:
diff --git a/.chloggen/namespace-ta.yaml b/.chloggen/namespace-ta.yaml
new file mode 100644
index 00000000..e9150372
--- /dev/null
+++ b/.chloggen/namespace-ta.yaml
@@ -0,0 +1,19 @@
+# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
+change_type: enhancement
+
+# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
+component: target allocator
+
+# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: |
+  Add support for setting the allowNamespaces and denyNamespaces in the target allocator.
+
+# One or more tracking issues related to the change
+issues: [3086]
+
+# (Optional) One or more lines of additional information to render under the primary note.
+# These lines will be padded with 2 spaces and then inserted directly into the document.
+# Use pipe (|) for multiline entries.
+subtext: |
+  allowNamespaces can be set to an empty list to watch all namespaces (default) or to list of namespaces to watch.
+  denyNamespaces can be set to an empty list to deny watching any namespaces (default) or to a list of namespaces to deny watching.
diff --git a/apis/v1alpha1/targetallocator_webhook.go b/apis/v1alpha1/targetallocator_webhook.go
index 6e0ed989..68c0a656 100644
--- a/apis/v1alpha1/targetallocator_webhook.go
+++ b/apis/v1alpha1/targetallocator_webhook.go
@@ -116,6 +116,11 @@ func (w TargetAllocatorWebhook) validate(ctx context.Context, ta *TargetAllocato
 		if err != nil || len(warnings) > 0 {
 			return warnings, err
 		}
+
+		// Check to see that allowNamespaces and denyNamespaces are not both set at the same time
+		if len(ta.Spec.PrometheusCR.AllowNamespaces) > 0 && len(ta.Spec.PrometheusCR.DenyNamespaces) > 0 {
+			return warnings, fmt.Errorf("allowNamespaces and denyNamespaces are mutually exclusive")
+		}
 	}
 
 	return warnings, nil
diff --git a/apis/v1beta1/targetallocator_types.go b/apis/v1beta1/targetallocator_types.go
index 6ee96beb..80e6a625 100644
--- a/apis/v1beta1/targetallocator_types.go
+++ b/apis/v1beta1/targetallocator_types.go
@@ -12,6 +12,12 @@ type TargetAllocatorPrometheusCR struct {
 	// Enabled indicates whether to use a PrometheusOperator custom resources as targets or not.
 	// +optional
 	Enabled bool `json:"enabled,omitempty"`
+	// AllowNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (allow list). This is mutually exclusive with DenyNamespaces.
+	// +optional
+	AllowNamespaces []string `json:"allowNamespaces,omitempty"`
+	// DenyNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (deny list). This is mutually exclusive with AllowNamespaces.
+	// +optional
+	DenyNamespaces []string `json:"denyNamespaces,omitempty"`
 	// Default interval between consecutive scrapes. Intervals set in ServiceMonitors and PodMonitors override it.
 	//Equivalent to the same setting on the Prometheus CR.
 	//
diff --git a/apis/v1beta1/zz_generated.deepcopy.go b/apis/v1beta1/zz_generated.deepcopy.go
index e50cee37..da29269f 100644
--- a/apis/v1beta1/zz_generated.deepcopy.go
+++ b/apis/v1beta1/zz_generated.deepcopy.go
@@ -761,6 +761,16 @@ func (in *TargetAllocatorEmbedded) DeepCopy() *TargetAllocatorEmbedded {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TargetAllocatorPrometheusCR) DeepCopyInto(out *TargetAllocatorPrometheusCR) {
 	*out = *in
+	if in.AllowNamespaces != nil {
+		in, out := &in.AllowNamespaces, &out.AllowNamespaces
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.DenyNamespaces != nil {
+		in, out := &in.DenyNamespaces, &out.DenyNamespaces
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 	if in.ScrapeInterval != nil {
 		in, out := &in.ScrapeInterval, &out.ScrapeInterval
 		*out = new(metav1.Duration)
diff --git a/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml
index 6edc5e5f..1ce98fc9 100644
--- a/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml
+++ b/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml
@@ -7889,6 +7889,14 @@ spec:
                     type: object
                   prometheusCR:
                     properties:
+                      allowNamespaces:
+                        items:
+                          type: string
+                        type: array
+                      denyNamespaces:
+                        items:
+                          type: string
+                        type: array
                       enabled:
                         type: boolean
                       podMonitorSelector:
diff --git a/bundle/community/manifests/opentelemetry.io_targetallocators.yaml b/bundle/community/manifests/opentelemetry.io_targetallocators.yaml
index 30b62d7b..55f539a8 100644
--- a/bundle/community/manifests/opentelemetry.io_targetallocators.yaml
+++ b/bundle/community/manifests/opentelemetry.io_targetallocators.yaml
@@ -2257,6 +2257,14 @@ spec:
                 type: string
               prometheusCR:
                 properties:
+                  allowNamespaces:
+                    items:
+                      type: string
+                    type: array
+                  denyNamespaces:
+                    items:
+                      type: string
+                    type: array
                   enabled:
                     type: boolean
                   podMonitorSelector:
diff --git a/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml
index 6edc5e5f..1ce98fc9 100644
--- a/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml
+++ b/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml
@@ -7889,6 +7889,14 @@ spec:
                     type: object
                   prometheusCR:
                     properties:
+                      allowNamespaces:
+                        items:
+                          type: string
+                        type: array
+                      denyNamespaces:
+                        items:
+                          type: string
+                        type: array
                       enabled:
                         type: boolean
                       podMonitorSelector:
diff --git a/bundle/openshift/manifests/opentelemetry.io_targetallocators.yaml b/bundle/openshift/manifests/opentelemetry.io_targetallocators.yaml
index 30b62d7b..55f539a8 100644
--- a/bundle/openshift/manifests/opentelemetry.io_targetallocators.yaml
+++ b/bundle/openshift/manifests/opentelemetry.io_targetallocators.yaml
@@ -2257,6 +2257,14 @@ spec:
                 type: string
               prometheusCR:
                 properties:
+                  allowNamespaces:
+                    items:
+                      type: string
+                    type: array
+                  denyNamespaces:
+                    items:
+                      type: string
+                    type: array
                   enabled:
                     type: boolean
                   podMonitorSelector:
diff --git a/cmd/otel-allocator/README.md b/cmd/otel-allocator/README.md
index 6db10581..1a564063 100644
--- a/cmd/otel-allocator/README.md
+++ b/cmd/otel-allocator/README.md
@@ -191,9 +191,11 @@ Upstream documentation here: [PrometheusReceiver](https://github.com/open-teleme
 
 ### RBAC
 
-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.
+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.
 
-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.
+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.
+
+#### Cluster-scoped RBAC
 
 ```yaml
   targetAllocator:
@@ -204,11 +206,11 @@ You can create your own `ServiceAccount`, and reference it in `spec.targetAlloca
 ```
 
 > 🚨 **Note**: The Collector part of this same CR *also* has a serviceAccount key which only affects the collector and *not*
-the TargetAllocator.
+> the TargetAllocator.
 
-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.
+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.
 
-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:
+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:
 
 ```yaml
 apiVersion: rbac.authorization.k8s.io/v1
@@ -242,7 +244,7 @@ rules:
   verbs: ["get"]
 ```
 
-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`.
+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`.
 
 ```yaml
 apiVersion: rbac.authorization.k8s.io/v1
@@ -263,8 +265,82 @@ rules:
   verbs: ["get", "list", "watch"]
 ```
 
-> ✨ The above roles can be combined into a single role.
+> ✨ The above ClusterRoles can be combined into a single ClusterRole.
+ 
+#### Namespace-scoped RBAC
+
+If you want to have the TargetAllocator watch a specific namespace, you can set the allowNamespaces field 
+in the TargetAllocator's prometheusCR configuration. This is useful if you want to restrict the TargetAllocator to only watch Prometheus
+CRs in a specific namespace, and not have cluster-wide access.
+
+```yaml
+  targetAllocator:
+    enabled: true
+    serviceAccount: opentelemetry-targetallocator-sa
+    prometheusCR:
+      enabled: true
+      allowNamespaces: 
+      - foo
+```
+
+In this case, you will need to create a Role and RoleBinding instead of a ClusterRole and ClusterRoleBinding. The Role
+and RoleBinding should be created in the namespace specified by the allowNamespaces field.
 
+```yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: opentelemetry-targetallocator-role
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - services
+      - endpoints
+      - configmaps
+      - secrets
+      - namespaces
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - apps
+    resources:
+      - statefulsets
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - discovery.k8s.io
+    resources:
+      - endpointslices
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - networking.k8s.io
+    resources:
+      - ingresses
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - monitoring.coreos.com
+    resources:
+      - servicemonitors
+      - podmonitors
+      - scrapeconfigs
+      - probes
+    verbs:
+      - get
+      - watch
+      - list
+```
 
 ### Service / Pod monitor endpoint credentials
 
@@ -420,4 +496,3 @@ Shards the received targets based on the discovered Collector instances
 
 ### Collector
 Client to watch for deployed Collector instances which will then provided to the Allocator. 
-
diff --git a/cmd/otel-allocator/internal/config/config.go b/cmd/otel-allocator/internal/config/config.go
index ab840939..296ece16 100644
--- a/cmd/otel-allocator/internal/config/config.go
+++ b/cmd/otel-allocator/internal/config/config.go
@@ -20,6 +20,7 @@ import (
 	_ "github.com/prometheus/prometheus/discovery/install"
 	"github.com/spf13/pflag"
 	"gopkg.in/yaml.v2"
+	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
@@ -53,6 +54,8 @@ type Config struct {
 
 type PrometheusCRConfig struct {
 	Enabled                         bool                  `yaml:"enabled,omitempty"`
+	AllowNamespaces                 []string              `yaml:"allow_namespaces,omitempty"`
+	DenyNamespaces                  []string              `yaml:"deny_namespaces,omitempty"`
 	PodMonitorSelector              *metav1.LabelSelector `yaml:"pod_monitor_selector,omitempty"`
 	PodMonitorNamespaceSelector     *metav1.LabelSelector `yaml:"pod_monitor_namespace_selector,omitempty"`
 	ServiceMonitorSelector          *metav1.LabelSelector `yaml:"service_monitor_selector,omitempty"`
@@ -342,6 +345,9 @@ func ValidateConfig(config *Config) error {
 	if config.CollectorNamespace == "" {
 		return fmt.Errorf("collector namespace must be set")
 	}
+	if len(config.PrometheusCR.AllowNamespaces) != 0 && len(config.PrometheusCR.DenyNamespaces) != 0 {
+		return fmt.Errorf("only one of allowNamespaces or denyNamespaces can be set")
+	}
 	return nil
 }
 
@@ -367,3 +373,25 @@ func (c HTTPSServerConfig) NewTLSConfig() (*tls.Config, error) {
 	}
 	return tlsConfig, nil
 }
+
+// GetAllowDenyLists returns the allow and deny lists as maps. If the allow list is empty, it defaults to all namespaces.
+// If the deny list is empty, it defaults to an empty map.
+func (c PrometheusCRConfig) GetAllowDenyLists() (map[string]struct{}, map[string]struct{}) {
+	allowList := map[string]struct{}{}
+	if len(c.AllowNamespaces) != 0 {
+		for _, ns := range c.AllowNamespaces {
+			allowList[ns] = struct{}{}
+		}
+	} else {
+		allowList = map[string]struct{}{v1.NamespaceAll: {}}
+	}
+
+	denyList := map[string]struct{}{}
+	if len(c.DenyNamespaces) != 0 {
+		for _, ns := range c.DenyNamespaces {
+			denyList[ns] = struct{}{}
+		}
+	}
+
+	return allowList, denyList
+}
diff --git a/cmd/otel-allocator/internal/watcher/promOperator.go b/cmd/otel-allocator/internal/watcher/promOperator.go
index 89e395cc..cb85fdc0 100644
--- a/cmd/otel-allocator/internal/watcher/promOperator.go
+++ b/cmd/otel-allocator/internal/watcher/promOperator.go
@@ -53,7 +53,9 @@ func NewPrometheusCRWatcher(ctx context.Context, logger logr.Logger, cfg allocat
 		return nil, err
 	}
 
-	factory := informers.NewMonitoringInformerFactories(map[string]struct{}{v1.NamespaceAll: {}}, map[string]struct{}{}, mClient, allocatorconfig.DefaultResyncTime, nil) //TODO decide what strategy to use regarding namespaces
+	allowList, denyList := cfg.PrometheusCR.GetAllowDenyLists()
+
+	factory := informers.NewMonitoringInformerFactories(allowList, denyList, mClient, allocatorconfig.DefaultResyncTime, nil)
 
 	monitoringInformers, err := getInformers(factory)
 	if err != nil {
@@ -102,7 +104,7 @@ func NewPrometheusCRWatcher(ctx context.Context, logger logr.Logger, cfg allocat
 			logger.Error(err, "Retrying namespace informer creation in promOperator CRD watcher")
 			return true
 		}, func() error {
-			nsMonInf, err = getNamespaceInformer(ctx, map[string]struct{}{v1.NamespaceAll: {}}, promLogger, clientset, operatorMetrics)
+			nsMonInf, err = getNamespaceInformer(ctx, allowList, denyList, promLogger, clientset, operatorMetrics)
 			return err
 		})
 	if getNamespaceInformerErr != nil {
@@ -152,7 +154,7 @@ type PrometheusCRWatcher struct {
 	store                           *assets.StoreBuilder
 }
 
-func getNamespaceInformer(ctx context.Context, allowList map[string]struct{}, promOperatorLogger *slog.Logger, clientset kubernetes.Interface, operatorMetrics *operator.Metrics) (cache.SharedIndexInformer, error) {
+func getNamespaceInformer(ctx context.Context, allowList, denyList map[string]struct{}, promOperatorLogger *slog.Logger, clientset kubernetes.Interface, operatorMetrics *operator.Metrics) (cache.SharedIndexInformer, error) {
 	kubernetesVersion, err := clientset.Discovery().ServerVersion()
 	if err != nil {
 		return nil, err
@@ -168,7 +170,7 @@ func getNamespaceInformer(ctx context.Context, allowList map[string]struct{}, pr
 		clientset.CoreV1(),
 		clientset.AuthorizationV1().SelfSubjectAccessReviews(),
 		allowList,
-		map[string]struct{}{},
+		denyList,
 	)
 	if err != nil {
 		return nil, err
diff --git a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml
index 79ccfbd2..7f40cdd8 100644
--- a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml
+++ b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml
@@ -7875,6 +7875,14 @@ spec:
                     type: object
                   prometheusCR:
                     properties:
+                      allowNamespaces:
+                        items:
+                          type: string
+                        type: array
+                      denyNamespaces:
+                        items:
+                          type: string
+                        type: array
                       enabled:
                         type: boolean
                       podMonitorSelector:
diff --git a/config/crd/bases/opentelemetry.io_targetallocators.yaml b/config/crd/bases/opentelemetry.io_targetallocators.yaml
index 00c4db7e..ce8432e9 100644
--- a/config/crd/bases/opentelemetry.io_targetallocators.yaml
+++ b/config/crd/bases/opentelemetry.io_targetallocators.yaml
@@ -2254,6 +2254,14 @@ spec:
                 type: string
               prometheusCR:
                 properties:
+                  allowNamespaces:
+                    items:
+                      type: string
+                    type: array
+                  denyNamespaces:
+                    items:
+                      type: string
+                    type: array
                   enabled:
                     type: boolean
                   podMonitorSelector:
diff --git a/docs/api/opentelemetrycollectors.md b/docs/api/opentelemetrycollectors.md
index 859e6e9d..f4a29a46 100644
--- a/docs/api/opentelemetrycollectors.md
+++ b/docs/api/opentelemetrycollectors.md
@@ -33283,6 +33283,20 @@ All CR instances which the ServiceAccount has access to will be retrieved. This
         </tr>
     </thead>
     <tbody><tr>
+        <td><b>allowNamespaces</b></td>
+        <td>[]string</td>
+        <td>
+          AllowNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (allow list). This is mutually exclusive with DenyNamespaces.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>denyNamespaces</b></td>
+        <td>[]string</td>
+        <td>
+          DenyNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (deny list). This is mutually exclusive with AllowNamespaces.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
         <td><b>enabled</b></td>
         <td>boolean</td>
         <td>
diff --git a/docs/api/targetallocators.md b/docs/api/targetallocators.md
index f9643bc0..99a7591e 100644
--- a/docs/api/targetallocators.md
+++ b/docs/api/targetallocators.md
@@ -9480,6 +9480,20 @@ PrometheusCR defines the configuration for the retrieval of PrometheusOperator C
         </tr>
     </thead>
     <tbody><tr>
+        <td><b>allowNamespaces</b></td>
+        <td>[]string</td>
+        <td>
+          AllowNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (allow list). This is mutually exclusive with DenyNamespaces.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>denyNamespaces</b></td>
+        <td>[]string</td>
+        <td>
+          DenyNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (deny list). This is mutually exclusive with AllowNamespaces.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
         <td><b>enabled</b></td>
         <td>boolean</td>
         <td>
diff --git a/internal/manifests/targetallocator/configmap.go b/internal/manifests/targetallocator/configmap.go
index caf2256a..ed980d73 100644
--- a/internal/manifests/targetallocator/configmap.go
+++ b/internal/manifests/targetallocator/configmap.go
@@ -94,6 +94,14 @@ func ConfigMap(params Params) (*corev1.ConfigMap, error) {
 			prometheusCRConfig["scrape_interval"] = taSpec.PrometheusCR.ScrapeInterval.Duration
 		}
 
+		if taSpec.PrometheusCR.AllowNamespaces != nil {
+			prometheusCRConfig["allow_namespaces"] = taSpec.PrometheusCR.AllowNamespaces
+		}
+
+		if taSpec.PrometheusCR.DenyNamespaces != nil {
+			prometheusCRConfig["deny_namespaces"] = taSpec.PrometheusCR.DenyNamespaces
+		}
+
 		prometheusCRConfig["service_monitor_selector"] = taSpec.PrometheusCR.ServiceMonitorSelector
 
 		prometheusCRConfig["pod_monitor_selector"] = taSpec.PrometheusCR.PodMonitorSelector


Test patch:
diff --git a/apis/v1alpha1/targetallocator_webhook_test.go b/apis/v1alpha1/targetallocator_webhook_test.go
index b5e9fd5a..dbdfd2e2 100644
--- a/apis/v1alpha1/targetallocator_webhook_test.go
+++ b/apis/v1alpha1/targetallocator_webhook_test.go
@@ -301,6 +301,19 @@ func TestTargetAllocatorValidatingWebhook(t *testing.T) {
 			},
 			expectedErr: "the OpenTelemetry Spec Ports configuration is incorrect",
 		},
+		{
+			name: "allowNamespaces and denyNamespaces can't both be set",
+			targetallocator: TargetAllocator{
+				Spec: TargetAllocatorSpec{
+					PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{
+						Enabled:         true,
+						AllowNamespaces: []string{"ns1"},
+						DenyNamespaces:  []string{"ns2"},
+					},
+				},
+			},
+			expectedErr: "allowNamespaces and denyNamespaces are mutually exclusive",
+		},
 	}
 
 	for _, test := range tests {
diff --git a/cmd/otel-allocator/internal/config/config_test.go b/cmd/otel-allocator/internal/config/config_test.go
index b34653ee..78040ea1 100644
--- a/cmd/otel-allocator/internal/config/config_test.go
+++ b/cmd/otel-allocator/internal/config/config_test.go
@@ -16,6 +16,7 @@ import (
 	"github.com/prometheus/prometheus/discovery/file"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -538,6 +539,18 @@ func TestValidateConfig(t *testing.T) {
 			},
 			expectedErr: nil,
 		},
+		{
+			name: "both allowNamespaces and denyNamespaces set",
+			fileConfig: Config{
+				PrometheusCR: PrometheusCRConfig{
+					Enabled:         true,
+					AllowNamespaces: []string{"ns1"},
+					DenyNamespaces:  []string{"ns2"},
+				},
+				CollectorNamespace: "default",
+			},
+			expectedErr: fmt.Errorf("only one of allowNamespaces or denyNamespaces can be set"),
+		},
 	}
 
 	for _, tc := range testCases {
@@ -548,3 +561,46 @@ func TestValidateConfig(t *testing.T) {
 		})
 	}
 }
+
+func TestGetAllowDenyLists(t *testing.T) {
+	testCases := []struct {
+		name              string
+		promCRConfig      PrometheusCRConfig
+		expectedAllowList map[string]struct{}
+		expectedDenyList  map[string]struct{}
+	}{
+		{
+			name:              "no allow or deny namespaces",
+			promCRConfig:      PrometheusCRConfig{Enabled: true},
+			expectedAllowList: map[string]struct{}{v1.NamespaceAll: {}},
+			expectedDenyList:  map[string]struct{}{},
+		},
+		{
+			name:              "allow namespaces",
+			promCRConfig:      PrometheusCRConfig{Enabled: true, AllowNamespaces: []string{"ns1"}},
+			expectedAllowList: map[string]struct{}{"ns1": {}},
+			expectedDenyList:  map[string]struct{}{},
+		},
+		{
+			name:              "deny namespaces",
+			promCRConfig:      PrometheusCRConfig{Enabled: true, DenyNamespaces: []string{"ns2"}},
+			expectedAllowList: map[string]struct{}{v1.NamespaceAll: {}},
+			expectedDenyList:  map[string]struct{}{"ns2": {}},
+		},
+		{
+			name:              "both allow and deny namespaces",
+			promCRConfig:      PrometheusCRConfig{Enabled: true, AllowNamespaces: []string{"ns1"}, DenyNamespaces: []string{"ns2"}},
+			expectedAllowList: map[string]struct{}{"ns1": {}},
+			expectedDenyList:  map[string]struct{}{"ns2": {}},
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			allowList, denyList := tc.promCRConfig.GetAllowDenyLists()
+			assert.Equal(t, tc.expectedAllowList, allowList)
+			assert.Equal(t, tc.expectedDenyList, denyList)
+		})
+	}
+}
diff --git a/tests/e2e-targetallocator-cr/00-assert.yaml b/tests/e2e-targetallocator-cr/00-assert.yaml
index cc345158..45476aa1 100644
--- a/tests/e2e-targetallocator-cr/00-assert.yaml
+++ b/tests/e2e-targetallocator-cr/00-assert.yaml
@@ -165,4 +165,4 @@ spec:
       app.kubernetes.io/managed-by: opentelemetry-operator
       app.kubernetes.io/name: cr-targetallocator
       app.kubernetes.io/part-of: opentelemetry
-  maxUnavailable: 1
\ No newline at end of file
+  maxUnavailable: 1
diff --git a/tests/e2e-targetallocator-cr/00-install.yaml b/tests/e2e-targetallocator-cr/00-install.yaml
index 0ab5658f..7145e7cb 100644
--- a/tests/e2e-targetallocator-cr/00-install.yaml
+++ b/tests/e2e-targetallocator-cr/00-install.yaml
@@ -1,3 +1,39 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: (join('-', ['cr-targetallocator', $namespace]))
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - namespaces
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups:
+      - monitoring.coreos.com
+    resources:
+      - servicemonitors
+    verbs:
+      - get
+      - list
+      - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: (join('-', ['cr-targetallocator', $namespace]))
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: (join('-', ['cr-targetallocator', $namespace]))
+subjects:
+  - kind: ServiceAccount
+    name: cr-targetallocator
+    namespace: ($namespace)
+---
 apiVersion: opentelemetry.io/v1alpha1
 kind: TargetAllocator
 metadata:
@@ -6,4 +42,4 @@ spec:
   scrapeConfigs:
     - job_name: prometheus
       static_configs:
-      - targets: [ "localhost:9090" ]
\ No newline at end of file
+      - targets: [ "localhost:9090" ]
diff --git a/tests/e2e-targetallocator-cr/01-assert.yaml b/tests/e2e-targetallocator-cr/01-assert.yaml
index 0c0873c5..546e3eaa 100644
--- a/tests/e2e-targetallocator-cr/01-assert.yaml
+++ b/tests/e2e-targetallocator-cr/01-assert.yaml
@@ -76,6 +76,7 @@ status:
 ---
 apiVersion: v1
 data:
+  # Note: The content of this file must match exactly (careful about the order of keys) the content of the file in the ConfigMap
   targetallocator.yaml: |
     allocation_strategy: consistent-hashing
     collector_selector: null
@@ -136,4 +137,4 @@ spec:
       app.kubernetes.io/managed-by: opentelemetry-operator
       app.kubernetes.io/name: cr-targetallocator
       app.kubernetes.io/part-of: opentelemetry
-  maxUnavailable: 1
\ No newline at end of file
+  maxUnavailable: 1
diff --git a/tests/e2e-targetallocator-cr/01-install.yaml b/tests/e2e-targetallocator-cr/01-install.yaml
index 543cccb9..d7b04211 100644
--- a/tests/e2e-targetallocator-cr/01-install.yaml
+++ b/tests/e2e-targetallocator-cr/01-install.yaml
@@ -1,38 +1,4 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: (join('-', ['cr-targetallocator', $namespace]))
-rules:
-  - apiGroups:
-      - ""
-    resources:
-      - pods
-      - namespaces
-    verbs:
-      - get
-      - list
-      - watch
-  - apiGroups:
-      - monitoring.coreos.com
-    resources:
-      - servicemonitors
-    verbs:
-      - get
-      - list
-      - watch
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: (join('-', ['cr-targetallocator', $namespace]))
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: (join('-', ['cr-targetallocator', $namespace]))
-subjects:
-  - kind: ServiceAccount
-    name: cr-targetallocator
-    namespace: ($namespace)
+
 ---
 apiVersion: opentelemetry.io/v1alpha1
 kind: TargetAllocator
@@ -72,4 +38,4 @@ spec:
     - mountPath: /usr/share/testvolume
       name: testvolume
   volumes:
-    - name: testvolume
\ No newline at end of file
+    - name: testvolume
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/assert-job-failed.yaml b/tests/e2e-targetallocator/targetallocator-namespace/assert-job-failed.yaml
new file mode 100644
index 00000000..e0bae174
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/assert-job-failed.yaml
@@ -0,0 +1,7 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: check-metrics
+  namespace: ($namespace)
+status:
+  failed: 1
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/assert-job-succeeded.yaml b/tests/e2e-targetallocator/targetallocator-namespace/assert-job-succeeded.yaml
new file mode 100644
index 00000000..e0044e72
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/assert-job-succeeded.yaml
@@ -0,0 +1,7 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: check-metrics
+  namespace: ($namespace)
+status:
+  succeeded: 1
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/assert-workloads-ready.yaml b/tests/e2e-targetallocator/targetallocator-namespace/assert-workloads-ready.yaml
new file mode 100644
index 00000000..34ebca5b
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/assert-workloads-ready.yaml
@@ -0,0 +1,17 @@
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: prometheus-cr-collector
+  namespace: ($namespace)
+status:
+  readyReplicas: 1
+  replicas: 1
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: cr-targetallocator
+  namespace: ($namespace)
+status:
+  readyReplicas: 1
+  replicas: 1
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/chainsaw-test.yaml b/tests/e2e-targetallocator/targetallocator-namespace/chainsaw-test.yaml
new file mode 100755
index 00000000..72363a49
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/chainsaw-test.yaml
@@ -0,0 +1,108 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: targetallocator-namespace
+spec:
+  bindings:
+    - name: denyNamespaces
+      value: (join('-', [$namespace, 'top-secret']))
+    - name: endpoint
+      value: "http://cr-targetallocator/scrape_configs"
+    - name: pattern
+      value: ""
+    - name: retries
+      value: "30"
+  catch:
+    - podLogs:
+        selector: app.kubernetes.io/managed-by=opentelemetry-operator
+  steps:
+    # Sets up resources in a namespace that we shouldn't be able to read from
+    - name: install-into-deny-namespace
+      bindings:
+        - name: namespace
+          value: ($denyNamespaces)
+      try:
+        - create:
+            resource:
+              apiVersion: v1
+              kind: Namespace
+              metadata:
+                name: ($denyNamespaces)
+        - apply:
+            file: resources/serviceaccounts.yaml
+        - apply:
+            file: resources/rbac.yaml
+        - apply:
+            file: resources/ta-allow-namespaces.yaml
+        - apply:
+            file: resources/otelcol.yaml
+    - name: wait-until-top-secret-install-ready
+      bindings:
+        - name: namespace
+          value: ($denyNamespaces)
+      try:
+        - assert:
+            file: assert-workloads-ready.yaml
+    # The TA should be able to discover targets from its own namespace
+    - name: check-top-secret-ta-can-discover-its-own-service-monitor
+      bindings:
+        - name: namespace
+          value: ($denyNamespaces)
+        - name: pattern
+          value: (join('/', ['serviceMonitor', $denyNamespaces, 'cr-targetallocator']))
+      try:
+        - apply:
+            file: resources/job-check-metrics.yaml
+    - name: assert-top-secret-job-succeeded
+      bindings:
+        - name: namespace
+          value: ($denyNamespaces)
+      try:
+        - assert:
+            file: assert-job-succeeded.yaml
+    # Sets up resources in the original namespace and then makes sure that we can't discover the denied namespace
+    - name: install-with-deny-namespace-set-to-top-secret-configured-earlier
+      try:
+        - apply:
+            file: resources/serviceaccounts.yaml
+        - apply:
+            file: resources/clusterrbac.yaml
+        - apply:
+            file: resources/ta-deny-namespaces.yaml
+        - apply:
+            file: resources/otelcol.yaml
+    - name: wait-until-install-with-deny-namespace-set-to-top-secret-configured-earlier-is-ready
+      try:
+        - assert:
+            file: assert-workloads-ready.yaml
+    # Make sure TA can still discover its own namespace first
+    - name: check-ta-can-discover-its-own-service-monitor
+      bindings:
+        - name: pattern
+          value: (join('/', ['serviceMonitor', $namespace, 'cr-targetallocator']))
+      try:
+        - apply:
+            file: resources/job-check-metrics.yaml
+    - name: assert-job-succeeded
+      try:
+        - assert:
+            file: assert-job-succeeded.yaml
+    - name: delete-job
+      try:
+        - delete:
+            file: resources/job-check-metrics.yaml
+    # Make sure TA can't discover the denied namespace
+    - name: check-ta-cant-discover-denied-namespace
+      bindings:
+        - name: pattern
+          value: (join('/', ['serviceMonitor', $denyNamespaces, 'cr-targetallocator']))
+        - name: retries
+          value: "5"
+      try:
+        - apply:
+            file: resources/job-check-metrics.yaml
+    - name: assert-job-failed-since-ta-cant-discover-denied-namespace
+      try:
+        - assert:
+            file: assert-job-failed.yaml
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/clusterrbac.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/clusterrbac.yaml
new file mode 100644
index 00000000..3fffb25d
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/clusterrbac.yaml
@@ -0,0 +1,103 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: (join('-', ['ta', $namespace]))
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - services
+      - endpoints
+      - configmaps
+      - secrets
+      - namespaces
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - apps
+    resources:
+      - statefulsets
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - discovery.k8s.io
+    resources:
+      - endpointslices
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - networking.k8s.io
+    resources:
+      - ingresses
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - monitoring.coreos.com
+    resources:
+      - servicemonitors
+      - podmonitors
+      - scrapeconfigs
+      - probes
+    verbs:
+      - get
+      - watch
+      - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: (join('-', ['collector', $namespace]))
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - services
+      - endpoints
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - networking.k8s.io
+    resources:
+      - ingresses
+    verbs:
+      - get
+      - watch
+      - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: (join('-', ['ta', $namespace]))
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: (join('-', ['ta', $namespace]))
+subjects:
+  - kind: ServiceAccount
+    name: ta
+    namespace: ($namespace)
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: (join('-', ['collector', $namespace]))
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: (join('-', ['collector', $namespace]))
+subjects:
+  - kind: ServiceAccount
+    name: collector
+    namespace: ($namespace)
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/job-check-metrics.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/job-check-metrics.yaml
new file mode 100644
index 00000000..c5fa2b33
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/job-check-metrics.yaml
@@ -0,0 +1,33 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: check-metrics
+  namespace: ($namespace)
+spec:
+  template:
+    spec:
+      restartPolicy: Never
+      containers:
+        - name: check-metrics
+          image: curlimages/curl
+          env:
+            - name: endpoint
+              value: ($endpoint)
+            - name: pattern
+              value: ($pattern)
+            - name: retries
+              value: ($retries)
+          args:
+            - /bin/sh
+            - -c
+            - |
+              for i in $(seq $retries); do
+                if curl -m 1 -s $endpoint | grep "$pattern"; then
+                  echo "Found $pattern in $endpoint" 
+                  exit 0
+                fi
+                echo "Waiting for $pattern in $endpoint"
+                sleep 5
+              done
+              echo "Failed to find $pattern in $endpoint"
+              exit 1
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/otelcol.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/otelcol.yaml
new file mode 100644
index 00000000..8d2cba4c
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/otelcol.yaml
@@ -0,0 +1,35 @@
+apiVersion: opentelemetry.io/v1beta1
+kind: OpenTelemetryCollector
+metadata:
+  name: prometheus-cr
+  namespace: ($namespace)
+  labels:
+    opentelemetry.io/target-allocator: cr
+spec:
+  observability:
+    metrics:
+      disablePrometheusAnnotations: true
+      enableMetrics: true
+  config:
+    receivers:
+      prometheus:
+        config:
+          scrape_configs: []
+
+    processors:
+
+    exporters:
+      prometheus:
+        endpoint: 0.0.0.0:9090
+    service:
+      pipelines:
+        metrics:
+          receivers: [prometheus]
+          exporters: [prometheus]
+      telemetry:
+        logs:
+          level: "DEBUG"
+          development: true
+          encoding: "json"
+  mode: statefulset
+  serviceAccount: collector
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/rbac.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/rbac.yaml
new file mode 100644
index 00000000..1f45fe12
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/rbac.yaml
@@ -0,0 +1,107 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: ta
+  namespace: ($namespace)
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  - services
+  - endpoints
+  - configmaps
+  - secrets
+  - namespaces
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - apps
+  resources:
+  - statefulsets
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - discovery.k8s.io
+  resources:
+  - endpointslices
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - ingresses
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - monitoring.coreos.com
+  resources:
+  - servicemonitors
+  - podmonitors
+  - scrapeconfigs
+  - probes
+  verbs:
+  - get
+  - watch
+  - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: collector
+  namespace: ($namespace)
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  - services
+  - endpoints
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - ingresses
+  verbs:
+  - get
+  - watch
+  - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: ta
+  namespace: ($namespace)
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: ta
+subjects:
+- kind: ServiceAccount
+  name: ta
+  namespace: ($namespace)
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: collector
+  namespace: ($namespace)
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: collector
+subjects:
+- kind: ServiceAccount
+  name: collector
+  namespace: ($namespace)
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/serviceaccounts.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/serviceaccounts.yaml
new file mode 100644
index 00000000..19c24ed3
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/serviceaccounts.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+automountServiceAccountToken: true
+kind: ServiceAccount
+metadata:
+  name: ta
+  namespace: ($namespace)
+---
+apiVersion: v1
+automountServiceAccountToken: true
+kind: ServiceAccount
+metadata:
+  name: collector
+  namespace: ($namespace)
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-allow-namespaces.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-allow-namespaces.yaml
new file mode 100644
index 00000000..410d3c48
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-allow-namespaces.yaml
@@ -0,0 +1,22 @@
+apiVersion: opentelemetry.io/v1alpha1
+kind: TargetAllocator
+metadata:
+  name: cr
+  namespace: ($namespace)
+spec:
+  args:
+    "zap-log-level": "debug"
+  prometheusCR:
+    enabled: true
+    allowNamespaces:
+    - ($namespace)
+    scrapeInterval: 1s
+    scrapeConfigSelector: {}
+    probeSelector: {}
+    serviceMonitorSelector: {}
+    podMonitorSelector: {}
+  observability:
+    metrics:
+      disablePrometheusAnnotations: true
+      enableMetrics: true
+  serviceAccount: ta
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-deny-namespaces.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-deny-namespaces.yaml
new file mode 100644
index 00000000..02a426a0
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-deny-namespaces.yaml
@@ -0,0 +1,22 @@
+apiVersion: opentelemetry.io/v1alpha1
+kind: TargetAllocator
+metadata:
+  name: cr
+  namespace: ($namespace)
+spec:
+  args:
+    "zap-log-level": "debug"
+  prometheusCR:
+    enabled: true
+    denyNamespaces:
+    - ($denyNamespaces)
+    scrapeInterval: 1s
+    scrapeConfigSelector: {}
+    probeSelector: {}
+    serviceMonitorSelector: {}
+    podMonitorSelector: {}
+  observability:
+    metrics:
+      disablePrometheusAnnotations: true
+      enableMetrics: true
+  serviceAccount: ta