diff --git a/.chloggen/rbac-monolithic.yaml b/.chloggen/rbac-monolithic.yaml new file mode 100755 index 000000000..1acc78db3 --- /dev/null +++ b/.chloggen/rbac-monolithic.yaml @@ -0,0 +1,26 @@ +# 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. tempostack, tempomonolithic, github action) +component: tempomonolithic + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add support for query RBAC + +# One or more tracking issues related to the change +issues: [1131] + +# (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: | + This feature allows users to apply query RBAC in the multitenancy mode. + The RBAC allows filtering span/resource/scope attributes and events based on the namespaces which a user querying the data can access. + For instance, a user can only see attributes from namespaces it can access. + + ```yaml + spec: + query: + rbac: + enabled: true + ``` diff --git a/api/tempo/v1alpha1/tempomonolithic_defaults.go b/api/tempo/v1alpha1/tempomonolithic_defaults.go index bf04eb36c..aa81df192 100644 --- a/api/tempo/v1alpha1/tempomonolithic_defaults.go +++ b/api/tempo/v1alpha1/tempomonolithic_defaults.go @@ -102,4 +102,7 @@ func (r *TempoMonolithic) Default(ctrlConfig configv1alpha1.ProjectConfig) { if r.Spec.Timeout.Duration == 0 { r.Spec.Timeout = defaultTimeout } + if r.Spec.Query == nil { + r.Spec.Query = &MonolithicQuerySpec{} + } } diff --git a/api/tempo/v1alpha1/tempomonolithic_defaults_test.go b/api/tempo/v1alpha1/tempomonolithic_defaults_test.go index edf831e97..33b0c7208 100644 --- a/api/tempo/v1alpha1/tempomonolithic_defaults_test.go +++ b/api/tempo/v1alpha1/tempomonolithic_defaults_test.go @@ -49,6 +49,7 @@ func TestMonolithicDefault(t *testing.T) { }, Management: "Managed", Timeout: metav1.Duration{Duration: time.Second * 30}, + Query: &MonolithicQuerySpec{}, }, }, }, @@ -83,6 +84,7 @@ func TestMonolithicDefault(t *testing.T) { }, Management: "Managed", Timeout: metav1.Duration{Duration: time.Second * 30}, + Query: &MonolithicQuerySpec{}, }, }, }, @@ -109,6 +111,7 @@ func TestMonolithicDefault(t *testing.T) { }, Management: "Unmanaged", Timeout: metav1.Duration{Duration: time.Second * 30}, + Query: &MonolithicQuerySpec{}, }, }, expected: &TempoMonolithic{ @@ -131,6 +134,7 @@ func TestMonolithicDefault(t *testing.T) { }, Management: "Unmanaged", Timeout: metav1.Duration{Duration: time.Second * 30}, + Query: &MonolithicQuerySpec{}, }, }, }, @@ -203,6 +207,7 @@ func TestMonolithicDefault(t *testing.T) { }, Management: "Managed", Timeout: metav1.Duration{Duration: time.Second * 30}, + Query: &MonolithicQuerySpec{}, }, }, }, @@ -278,6 +283,7 @@ func TestMonolithicDefault(t *testing.T) { }, Management: "Managed", Timeout: metav1.Duration{Duration: time.Second * 30}, + Query: &MonolithicQuerySpec{}, }, }, }, @@ -345,6 +351,7 @@ func TestMonolithicDefault(t *testing.T) { }, Management: "Managed", Timeout: metav1.Duration{Duration: time.Second * 30}, + Query: &MonolithicQuerySpec{}, }, }, }, @@ -412,6 +419,7 @@ func TestMonolithicDefault(t *testing.T) { }, Management: "Managed", Timeout: metav1.Duration{Duration: time.Second * 30}, + Query: &MonolithicQuerySpec{}, }, }, }, @@ -478,6 +486,60 @@ func TestMonolithicDefault(t *testing.T) { }, Management: "Managed", Timeout: metav1.Duration{Duration: time.Hour}, + Query: &MonolithicQuerySpec{}, + }, + }, + }, + { + name: "query defined", + input: &TempoMonolithic{ + ObjectMeta: v1.ObjectMeta{ + Name: "test", + Namespace: "testns", + }, + Spec: TempoMonolithicSpec{ + Storage: &MonolithicStorageSpec{ + Traces: MonolithicTracesStorageSpec{ + Backend: "memory", + Size: &twoGBQuantity, + }, + }, + Query: &MonolithicQuerySpec{ + RBAC: RBACSpec{ + Enabled: true, + }, + }, + }, + }, + expected: &TempoMonolithic{ + ObjectMeta: v1.ObjectMeta{ + Name: "test", + Namespace: "testns", + }, + Spec: TempoMonolithicSpec{ + Ingestion: &MonolithicIngestionSpec{ + OTLP: &MonolithicIngestionOTLPSpec{ + GRPC: &MonolithicIngestionOTLPProtocolsGRPCSpec{ + Enabled: true, + }, + HTTP: &MonolithicIngestionOTLPProtocolsHTTPSpec{ + Enabled: true, + }, + }, + }, + Storage: &MonolithicStorageSpec{ + Traces: MonolithicTracesStorageSpec{ + Backend: "memory", + Size: &twoGBQuantity, + }, + }, + Management: "Managed", + Timeout: metav1.Duration{Duration: time.Second * 30}, + Query: &MonolithicQuerySpec{ + RBAC: RBACSpec{ + Enabled: true, + }, + }, }, }, }, diff --git a/api/tempo/v1alpha1/tempomonolithic_types.go b/api/tempo/v1alpha1/tempomonolithic_types.go index b6db8fe0a..55d22fc2c 100644 --- a/api/tempo/v1alpha1/tempomonolithic_types.go +++ b/api/tempo/v1alpha1/tempomonolithic_types.go @@ -68,9 +68,26 @@ type TempoMonolithicSpec struct { // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Extra Configuration",xDescriptors="urn:alm:descriptor:com.tectonic.ui:advanced" ExtraConfig *ExtraConfigSpec `json:"extraConfig,omitempty"` + // Query defines query configuration. + // + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Query Configuration",xDescriptors="urn:alm:descriptor:com.tectonic.ui:advanced" + Query *MonolithicQuerySpec `json:"query,omitempty"` + MonolithicSchedulerSpec `json:",inline"` } +// MonolithicQuerySpec defines the query configuration. +type MonolithicQuerySpec struct { + // RBAC defines query RBAC options. + // This option can be used only with multi-tenancy. + // + // +optional + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Query RBAC Settings" + RBAC RBACSpec `json:"rbac,omitempty"` +} + // MonolithicStorageSpec defines the storage for the Tempo deployment. type MonolithicStorageSpec struct { // Traces defines the storage configuration for traces. diff --git a/api/tempo/v1alpha1/tempostack_types.go b/api/tempo/v1alpha1/tempostack_types.go index 97b1c4f91..4b4fce313 100644 --- a/api/tempo/v1alpha1/tempostack_types.go +++ b/api/tempo/v1alpha1/tempostack_types.go @@ -592,7 +592,7 @@ type TempoGatewaySpec struct { // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Gateway Ingress Settings" Ingress IngressSpec `json:"ingress,omitempty"` - // RBAC defines RBAC options. + // RBAC defines query RBAC options. // // +optional // +kubebuilder:validation:Optional diff --git a/api/tempo/v1alpha1/zz_generated.deepcopy.go b/api/tempo/v1alpha1/zz_generated.deepcopy.go index 033ffd25a..5f9008e42 100644 --- a/api/tempo/v1alpha1/zz_generated.deepcopy.go +++ b/api/tempo/v1alpha1/zz_generated.deepcopy.go @@ -758,6 +758,22 @@ func (in *MonolithicObservabilitySpec) DeepCopy() *MonolithicObservabilitySpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MonolithicQuerySpec) DeepCopyInto(out *MonolithicQuerySpec) { + *out = *in + out.RBAC = in.RBAC +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MonolithicQuerySpec. +func (in *MonolithicQuerySpec) DeepCopy() *MonolithicQuerySpec { + if in == nil { + return nil + } + out := new(MonolithicQuerySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MonolithicSchedulerSpec) DeepCopyInto(out *MonolithicSchedulerSpec) { *out = *in @@ -1394,6 +1410,11 @@ func (in *TempoMonolithicSpec) DeepCopyInto(out *TempoMonolithicSpec) { *out = new(ExtraConfigSpec) (*in).DeepCopyInto(*out) } + if in.Query != nil { + in, out := &in.Query, &out.Query + *out = new(MonolithicQuerySpec) + **out = **in + } in.MonolithicSchedulerSpec.DeepCopyInto(&out.MonolithicSchedulerSpec) } diff --git a/bundle/community/manifests/tempo-operator.clusterserviceversion.yaml b/bundle/community/manifests/tempo-operator.clusterserviceversion.yaml index 0af213ad1..afdb839cd 100644 --- a/bundle/community/manifests/tempo-operator.clusterserviceversion.yaml +++ b/bundle/community/manifests/tempo-operator.clusterserviceversion.yaml @@ -74,7 +74,7 @@ metadata: capabilities: Deep Insights categories: Logging & Tracing,Monitoring containerImage: ghcr.io/grafana/tempo-operator/tempo-operator:v0.15.2 - createdAt: "2025-02-24T14:25:33Z" + createdAt: "2025-02-25T15:15:23Z" description: Create and manage deployments of Tempo, a high-scale distributed tracing backend. operatorframework.io/cluster-monitoring: "true" @@ -459,6 +459,19 @@ spec: - description: ServiceMonitors defines the ServiceMonitor configuration. displayName: Service Monitors path: observability.metrics.serviceMonitors + - description: Query defines query configuration. + displayName: Query Configuration + path: query + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: |- + RBAC defines query RBAC options. + This option can be used only with multi-tenancy. + displayName: Query RBAC Settings + path: query.rbac + - description: Enabled defines if the query RBAC should be enabled. + displayName: Query RBAC Enabled + path: query.rbac.enabled - description: ServiceAccount defines the Service Account to use for all Tempo components. displayName: Service Account @@ -995,7 +1008,7 @@ spec: all pods of this component. displayName: PodSecurityContext path: template.gateway.podSecurityContext - - description: RBAC defines RBAC options. + - description: RBAC defines query RBAC options. displayName: Query RBAC Settings path: template.gateway.rbac - description: Enabled defines if the query RBAC should be enabled. diff --git a/bundle/community/manifests/tempo.grafana.com_tempomonolithics.yaml b/bundle/community/manifests/tempo.grafana.com_tempomonolithics.yaml index 68dc52397..ef6f30243 100644 --- a/bundle/community/manifests/tempo.grafana.com_tempomonolithics.yaml +++ b/bundle/community/manifests/tempo.grafana.com_tempomonolithics.yaml @@ -1531,6 +1531,19 @@ spec: type: object type: object type: object + query: + description: Query defines query configuration. + properties: + rbac: + description: |- + RBAC defines query RBAC options. + This option can be used only with multi-tenancy. + properties: + enabled: + description: Enabled defines if the query RBAC should be enabled. + type: boolean + type: object + type: object resources: description: Resources defines the compute resource requirements of the Tempo container. diff --git a/bundle/community/manifests/tempo.grafana.com_tempostacks.yaml b/bundle/community/manifests/tempo.grafana.com_tempostacks.yaml index 62e18a445..cf1355afe 100644 --- a/bundle/community/manifests/tempo.grafana.com_tempostacks.yaml +++ b/bundle/community/manifests/tempo.grafana.com_tempostacks.yaml @@ -1435,7 +1435,7 @@ spec: type: string type: object rbac: - description: RBAC defines RBAC options. + description: RBAC defines query RBAC options. properties: enabled: description: Enabled defines if the query RBAC should diff --git a/bundle/openshift/manifests/tempo-operator.clusterserviceversion.yaml b/bundle/openshift/manifests/tempo-operator.clusterserviceversion.yaml index 63ef6ecb4..ad98ec923 100644 --- a/bundle/openshift/manifests/tempo-operator.clusterserviceversion.yaml +++ b/bundle/openshift/manifests/tempo-operator.clusterserviceversion.yaml @@ -74,7 +74,7 @@ metadata: capabilities: Deep Insights categories: Logging & Tracing,Monitoring containerImage: ghcr.io/grafana/tempo-operator/tempo-operator:v0.15.2 - createdAt: "2025-02-24T14:25:31Z" + createdAt: "2025-02-25T15:15:22Z" description: Create and manage deployments of Tempo, a high-scale distributed tracing backend. operatorframework.io/cluster-monitoring: "true" @@ -459,6 +459,19 @@ spec: - description: ServiceMonitors defines the ServiceMonitor configuration. displayName: Service Monitors path: observability.metrics.serviceMonitors + - description: Query defines query configuration. + displayName: Query Configuration + path: query + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: |- + RBAC defines query RBAC options. + This option can be used only with multi-tenancy. + displayName: Query RBAC Settings + path: query.rbac + - description: Enabled defines if the query RBAC should be enabled. + displayName: Query RBAC Enabled + path: query.rbac.enabled - description: ServiceAccount defines the Service Account to use for all Tempo components. displayName: Service Account @@ -995,7 +1008,7 @@ spec: all pods of this component. displayName: PodSecurityContext path: template.gateway.podSecurityContext - - description: RBAC defines RBAC options. + - description: RBAC defines query RBAC options. displayName: Query RBAC Settings path: template.gateway.rbac - description: Enabled defines if the query RBAC should be enabled. diff --git a/bundle/openshift/manifests/tempo.grafana.com_tempomonolithics.yaml b/bundle/openshift/manifests/tempo.grafana.com_tempomonolithics.yaml index 68dc52397..ef6f30243 100644 --- a/bundle/openshift/manifests/tempo.grafana.com_tempomonolithics.yaml +++ b/bundle/openshift/manifests/tempo.grafana.com_tempomonolithics.yaml @@ -1531,6 +1531,19 @@ spec: type: object type: object type: object + query: + description: Query defines query configuration. + properties: + rbac: + description: |- + RBAC defines query RBAC options. + This option can be used only with multi-tenancy. + properties: + enabled: + description: Enabled defines if the query RBAC should be enabled. + type: boolean + type: object + type: object resources: description: Resources defines the compute resource requirements of the Tempo container. diff --git a/bundle/openshift/manifests/tempo.grafana.com_tempostacks.yaml b/bundle/openshift/manifests/tempo.grafana.com_tempostacks.yaml index 62e18a445..cf1355afe 100644 --- a/bundle/openshift/manifests/tempo.grafana.com_tempostacks.yaml +++ b/bundle/openshift/manifests/tempo.grafana.com_tempostacks.yaml @@ -1435,7 +1435,7 @@ spec: type: string type: object rbac: - description: RBAC defines RBAC options. + description: RBAC defines query RBAC options. properties: enabled: description: Enabled defines if the query RBAC should diff --git a/config/crd/bases/tempo.grafana.com_tempomonolithics.yaml b/config/crd/bases/tempo.grafana.com_tempomonolithics.yaml index ebf73af55..fbf7f3390 100644 --- a/config/crd/bases/tempo.grafana.com_tempomonolithics.yaml +++ b/config/crd/bases/tempo.grafana.com_tempomonolithics.yaml @@ -1527,6 +1527,19 @@ spec: type: object type: object type: object + query: + description: Query defines query configuration. + properties: + rbac: + description: |- + RBAC defines query RBAC options. + This option can be used only with multi-tenancy. + properties: + enabled: + description: Enabled defines if the query RBAC should be enabled. + type: boolean + type: object + type: object resources: description: Resources defines the compute resource requirements of the Tempo container. diff --git a/config/crd/bases/tempo.grafana.com_tempostacks.yaml b/config/crd/bases/tempo.grafana.com_tempostacks.yaml index a0d8b7da1..049920047 100644 --- a/config/crd/bases/tempo.grafana.com_tempostacks.yaml +++ b/config/crd/bases/tempo.grafana.com_tempostacks.yaml @@ -1431,7 +1431,7 @@ spec: type: string type: object rbac: - description: RBAC defines RBAC options. + description: RBAC defines query RBAC options. properties: enabled: description: Enabled defines if the query RBAC should diff --git a/config/manifests/community/bases/tempo-operator.clusterserviceversion.yaml b/config/manifests/community/bases/tempo-operator.clusterserviceversion.yaml index f9d46281e..f26ac76a0 100644 --- a/config/manifests/community/bases/tempo-operator.clusterserviceversion.yaml +++ b/config/manifests/community/bases/tempo-operator.clusterserviceversion.yaml @@ -388,6 +388,19 @@ spec: - description: ServiceMonitors defines the ServiceMonitor configuration. displayName: Service Monitors path: observability.metrics.serviceMonitors + - description: Query defines query configuration. + displayName: Query Configuration + path: query + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: |- + RBAC defines query RBAC options. + This option can be used only with multi-tenancy. + displayName: Query RBAC Settings + path: query.rbac + - description: Enabled defines if the query RBAC should be enabled. + displayName: Query RBAC Enabled + path: query.rbac.enabled - description: ServiceAccount defines the Service Account to use for all Tempo components. displayName: Service Account @@ -924,7 +937,7 @@ spec: all pods of this component. displayName: PodSecurityContext path: template.gateway.podSecurityContext - - description: RBAC defines RBAC options. + - description: RBAC defines query RBAC options. displayName: Query RBAC Settings path: template.gateway.rbac - description: Enabled defines if the query RBAC should be enabled. diff --git a/config/manifests/openshift/bases/tempo-operator.clusterserviceversion.yaml b/config/manifests/openshift/bases/tempo-operator.clusterserviceversion.yaml index 07b510280..df11474a0 100644 --- a/config/manifests/openshift/bases/tempo-operator.clusterserviceversion.yaml +++ b/config/manifests/openshift/bases/tempo-operator.clusterserviceversion.yaml @@ -388,6 +388,19 @@ spec: - description: ServiceMonitors defines the ServiceMonitor configuration. displayName: Service Monitors path: observability.metrics.serviceMonitors + - description: Query defines query configuration. + displayName: Query Configuration + path: query + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: |- + RBAC defines query RBAC options. + This option can be used only with multi-tenancy. + displayName: Query RBAC Settings + path: query.rbac + - description: Enabled defines if the query RBAC should be enabled. + displayName: Query RBAC Enabled + path: query.rbac.enabled - description: ServiceAccount defines the Service Account to use for all Tempo components. displayName: Service Account @@ -924,7 +937,7 @@ spec: all pods of this component. displayName: PodSecurityContext path: template.gateway.podSecurityContext - - description: RBAC defines RBAC options. + - description: RBAC defines query RBAC options. displayName: Query RBAC Settings path: template.gateway.rbac - description: Enabled defines if the query RBAC should be enabled. diff --git a/docs/spec/tempo.grafana.com_tempomonolithics.yaml b/docs/spec/tempo.grafana.com_tempomonolithics.yaml index 87c39b640..ae9e2c528 100644 --- a/docs/spec/tempo.grafana.com_tempomonolithics.yaml +++ b/docs/spec/tempo.grafana.com_tempomonolithics.yaml @@ -111,6 +111,9 @@ spec: # TempoMonolithicSpec defines the desir enabled: false # Enabled defines if PrometheusRule objects should be created for this Tempo deployment. serviceMonitors: # ServiceMonitors defines the ServiceMonitor configuration. enabled: false # Enabled defines if ServiceMonitor objects should be created for this Tempo deployment. + query: # Query defines query configuration. + rbac: # RBAC defines query RBAC options. This option can be used only with multi-tenancy. + enabled: false # Enabled defines if the query RBAC should be enabled. serviceAccount: "" # ServiceAccount defines the Service Account to use for all Tempo components. storage: # Storage defines the storage configuration. traces: # Traces defines the storage configuration for traces. diff --git a/docs/spec/tempo.grafana.com_tempostacks.yaml b/docs/spec/tempo.grafana.com_tempostacks.yaml index 7811f68b9..f1ead0f08 100644 --- a/docs/spec/tempo.grafana.com_tempostacks.yaml +++ b/docs/spec/tempo.grafana.com_tempostacks.yaml @@ -206,7 +206,7 @@ spec: # TempoStackSpec defines the desired st route: # Route defines the options for the OpenShift route. termination: "" # Termination defines the termination type. The default is "edge". type: "" # Type defines the type of Ingress for the Jaeger Query UI. Currently ingress, route and none are supported. - rbac: # RBAC defines RBAC options. + rbac: # RBAC defines query RBAC options. enabled: false # Enabled defines if the query RBAC should be enabled. ingester: # Ingester defines the ingester component spec. podSecurityContext: # PodSecurityContext defines security context will be applied to all pods of this component. diff --git a/internal/manifests/monolithic/statefulset.go b/internal/manifests/monolithic/statefulset.go index 7d2a7e315..5f0cb9fd8 100644 --- a/internal/manifests/monolithic/statefulset.go +++ b/internal/manifests/monolithic/statefulset.go @@ -431,6 +431,10 @@ func configureGateway(opts Options, sts *appsv1.StatefulSet) error { args = append(args, fmt.Sprintf("--traces.read.endpoint=http://localhost:%d", manifestutils.PortJaegerQuery)) // Jaeger UI upstream } + if tempo.Spec.Query != nil && tempo.Spec.Query.RBAC.Enabled { + args = append(args, "--traces.query-rbac=true") + } + if opts.CtrlConfig.Gates.OpenShift.ServingCertsService { args = append(args, []string{ fmt.Sprintf("--tls.server.cert-file=%s", path.Join(servingCertDir, "tls.crt")), // TLS of public HTTP (8080) and gRPC (8090) server diff --git a/internal/manifests/monolithic/statefulset_test.go b/internal/manifests/monolithic/statefulset_test.go index 3de1df570..1c79f6d5e 100644 --- a/internal/manifests/monolithic/statefulset_test.go +++ b/internal/manifests/monolithic/statefulset_test.go @@ -994,3 +994,289 @@ func TestStatefulsetGateway(t *testing.T) { }, }, sts.Spec.Template.Spec.Containers[4]) } + +func TestStatefulsetGatewayRBAC(t *testing.T) { + opts := Options{ + CtrlConfig: configv1alpha1.ProjectConfig{ + DefaultImages: configv1alpha1.ImagesSpec{ + TempoGateway: "quay.io/observatorium/api:x.y.z", + TempoGatewayOpa: "quay.io/observatorium/opa-openshift:x.y.z", + }, + Gates: configv1alpha1.FeatureGates{ + OpenShift: configv1alpha1.OpenShiftFeatureGates{ + ServingCertsService: true, + }, + }, + }, + Tempo: v1alpha1.TempoMonolithic{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sample", + Namespace: "default", + }, + Spec: v1alpha1.TempoMonolithicSpec{ + Timeout: metav1.Duration{Duration: time.Second * 5}, + Storage: &v1alpha1.MonolithicStorageSpec{ + Traces: v1alpha1.MonolithicTracesStorageSpec{ + Backend: "memory", + }, + }, + Ingestion: &v1alpha1.MonolithicIngestionSpec{ + OTLP: &v1alpha1.MonolithicIngestionOTLPSpec{ + GRPC: &v1alpha1.MonolithicIngestionOTLPProtocolsGRPCSpec{ + Enabled: true, + }, + }, + }, + JaegerUI: &v1alpha1.MonolithicJaegerUISpec{ + Enabled: false, + }, + Query: &v1alpha1.MonolithicQuerySpec{ + RBAC: v1alpha1.RBACSpec{ + Enabled: true, + }, + }, + Multitenancy: &v1alpha1.MonolithicMultitenancySpec{ + Enabled: true, + TenantsSpec: v1alpha1.TenantsSpec{ + Mode: v1alpha1.ModeOpenShift, + Authentication: []v1alpha1.AuthenticationSpec{ + { + TenantName: "dev", + TenantID: "1610b0c3-c509-4592-a256-a1871353dbfa", + }, + { + TenantName: "prod", + TenantID: "1610b0c3-c509-4592-a256-a1871353dbfb", + }, + }, + }, + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1Gi"), + corev1.ResourceMemory: resource.MustParse("2Gi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("3Gi"), + corev1.ResourceMemory: resource.MustParse("4Gi"), + }, + }, + }, + }, + }, + } + sts, err := BuildTempoStatefulset(opts, map[string]string{}) + require.NoError(t, err) + + require.Equal(t, corev1.Container{ + Name: "tempo-gateway", + Image: "quay.io/observatorium/api:x.y.z", + Env: proxy.ReadProxyVarsFromEnv(), + Args: []string{ + "--web.listen=0.0.0.0:8080", + "--web.internal.listen=0.0.0.0:8081", + "--traces.tenant-header=x-scope-orgid", + "--traces.tempo.endpoint=http://localhost:3200", + "--traces.write-timeout=5s", + "--rbac.config=/etc/tempo-gateway/rbac/rbac.yaml", + "--tenants.config=/etc/tempo-gateway/tenants/tenants.yaml", + "--log.level=info", + "--grpc.listen=0.0.0.0:8090", + "--traces.write.otlpgrpc.endpoint=localhost:4317", + "--traces.write.otlphttp.endpoint=http://localhost:4318", + "--traces.query-rbac=true", + "--tls.server.cert-file=/etc/tempo-gateway/serving-cert/tls.crt", + "--tls.server.key-file=/etc/tempo-gateway/serving-cert/tls.key", + "--tls.healthchecks.server-ca-file=/etc/tempo-gateway/serving-ca/service-ca.crt", + "--tls.healthchecks.server-name=tempo-sample-gateway.default.svc.cluster.local", + "--web.healthchecks.url=https://localhost:8080", + "--tls.client-auth-type=NoClientCert", + }, + Ports: []corev1.ContainerPort{ + { + Name: "public", + ContainerPort: 8080, + Protocol: corev1.ProtocolTCP, + }, + { + Name: "internal", + ContainerPort: 8081, + Protocol: corev1.ProtocolTCP, + }, + { + Name: "grpc-public", + ContainerPort: 8090, + Protocol: corev1.ProtocolTCP, + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/live", + Port: intstr.FromString("internal"), + Scheme: corev1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 2, + PeriodSeconds: 30, + FailureThreshold: 10, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/ready", + Port: intstr.FromString("internal"), + Scheme: corev1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 1, + PeriodSeconds: 5, + FailureThreshold: 12, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "gateway-rbac", + ReadOnly: true, + MountPath: "/etc/tempo-gateway/rbac", + }, + { + Name: "gateway-tenants", + ReadOnly: true, + MountPath: "/etc/tempo-gateway/tenants", + }, + { + Name: "tempo-sample-serving-cabundle", + ReadOnly: true, + MountPath: "/etc/tempo-gateway/serving-ca", + }, + { + Name: "tempo-sample-gateway-serving-cert", + ReadOnly: true, + MountPath: "/etc/tempo-gateway/serving-cert", + }, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1Gi"), + corev1.ResourceMemory: resource.MustParse("2Gi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("3Gi"), + corev1.ResourceMemory: resource.MustParse("4Gi"), + }, + }, + SecurityContext: manifestutils.TempoContainerSecurityContext(), + }, sts.Spec.Template.Spec.Containers[1]) + + require.Equal(t, []corev1.Volume{ + { + Name: "gateway-rbac", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "tempo-sample-gateway", + }, + Items: []corev1.KeyToPath{ + { + Key: "rbac.yaml", + Path: "rbac.yaml", + }, + }, + }, + }, + }, + { + Name: "gateway-tenants", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "tempo-sample-gateway", + Items: []corev1.KeyToPath{ + { + Key: "tenants.yaml", + Path: "tenants.yaml", + }, + }, + }, + }, + }, + { + Name: "tempo-sample-serving-cabundle", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "tempo-sample-serving-cabundle", + }, + }, + }, + }, + { + Name: "tempo-sample-gateway-serving-cert", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "tempo-sample-gateway-serving-cert", + }, + }, + }, + }, sts.Spec.Template.Spec.Volumes[2:]) + + require.Equal(t, corev1.Container{ + Name: "tempo-gateway-opa", + Image: "quay.io/observatorium/opa-openshift:x.y.z", + Args: []string{ + "--log.level=warn", + "--opa.admin-groups=system:cluster-admins,cluster-admin,dedicated-admin", + "--opa.matcher=kubernetes_namespace_name", + "--web.listen=:8082", + "--web.internal.listen=:8083", + "--web.healthchecks.url=http://localhost:8082", + "--opa.package=tempomonolithic", + "--openshift.mappings=dev=tempo.grafana.com", + "--openshift.mappings=prod=tempo.grafana.com", + }, + Ports: []corev1.ContainerPort{ + { + Name: "public", + ContainerPort: 8082, + Protocol: corev1.ProtocolTCP, + }, + { + Name: "opa-metrics", + ContainerPort: 8083, + Protocol: corev1.ProtocolTCP, + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/live", + Port: intstr.FromInt(8083), + Scheme: corev1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 2, + PeriodSeconds: 30, + FailureThreshold: 10, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/ready", + Port: intstr.FromInt(8083), + Scheme: corev1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 1, + PeriodSeconds: 5, + FailureThreshold: 12, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1Gi"), + corev1.ResourceMemory: resource.MustParse("2Gi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("3Gi"), + corev1.ResourceMemory: resource.MustParse("4Gi"), + }, + }, + }, sts.Spec.Template.Spec.Containers[2]) +} diff --git a/internal/webhooks/tempomonolithic_webhook.go b/internal/webhooks/tempomonolithic_webhook.go index 8c260c858..8bd5cb4b2 100644 --- a/internal/webhooks/tempomonolithic_webhook.go +++ b/internal/webhooks/tempomonolithic_webhook.go @@ -141,11 +141,24 @@ func (v *monolithicValidator) validateJaegerUI(tempo tempov1alpha1.TempoMonolith "the openshiftRoute feature gate must be enabled to create a route for Jaeger UI", )} } + if tempo.Spec.Query != nil && tempo.Spec.Query.RBAC.Enabled && tempo.Spec.JaegerUI.Enabled { + return field.ErrorList{ + field.Invalid(field.NewPath("spec", "rbac", "enabled"), tempo.Spec.Query.RBAC.Enabled, + "cannot enable RBAC and jaeger query at the same time. The Jaeger UI does not support query RBAC", + )} + } return nil } func (v *monolithicValidator) validateMultitenancy(tempo tempov1alpha1.TempoMonolithic) field.ErrorList { + if tempo.Spec.Query != nil && tempo.Spec.Query.RBAC.Enabled && (tempo.Spec.Multitenancy == nil || !tempo.Spec.Multitenancy.Enabled) { + return field.ErrorList{ + field.Invalid(field.NewPath("spec", "rbac", "enabled"), tempo.Spec.Query.RBAC.Enabled, + "RBAC can only be enabled if multi-tenancy is enabled", + )} + } + if !tempo.Spec.Multitenancy.IsGatewayEnabled() { return nil } diff --git a/internal/webhooks/tempomonolithic_webhook_test.go b/internal/webhooks/tempomonolithic_webhook_test.go index 61fdc5e08..54110c0e3 100644 --- a/internal/webhooks/tempomonolithic_webhook_test.go +++ b/internal/webhooks/tempomonolithic_webhook_test.go @@ -148,6 +148,70 @@ func TestMonolithicValidate(t *testing.T) { "spec.tenants.authorization should not be defined in openshift mode", )}, }, + { + name: "RBAC and jaeger UI enabled", + tempo: v1alpha1.TempoMonolithic{ + Spec: v1alpha1.TempoMonolithicSpec{ + Query: &v1alpha1.MonolithicQuerySpec{ + RBAC: v1alpha1.RBACSpec{ + Enabled: true, + }, + }, + JaegerUI: &v1alpha1.MonolithicJaegerUISpec{ + Enabled: true, + }, + Multitenancy: &v1alpha1.MonolithicMultitenancySpec{ + Enabled: true, + }, + }, + }, + warnings: admission.Warnings{}, + errors: field.ErrorList{field.Invalid( + field.NewPath("spec", "rbac", "enabled"), + true, + "cannot enable RBAC and jaeger query at the same time. The Jaeger UI does not support query RBAC", + )}, + }, + + { + name: "RBAC and multitenancy disabled", + tempo: v1alpha1.TempoMonolithic{ + Spec: v1alpha1.TempoMonolithicSpec{ + Query: &v1alpha1.MonolithicQuerySpec{ + RBAC: v1alpha1.RBACSpec{ + Enabled: true, + }, + }, + Multitenancy: &v1alpha1.MonolithicMultitenancySpec{ + Enabled: false, + }, + }, + }, + warnings: admission.Warnings{}, + errors: field.ErrorList{field.Invalid( + field.NewPath("spec", "rbac", "enabled"), + true, + "RBAC can only be enabled if multi-tenancy is enabled", + )}, + }, + { + name: "RBAC and multitenancy nil", + tempo: v1alpha1.TempoMonolithic{ + Spec: v1alpha1.TempoMonolithicSpec{ + Query: &v1alpha1.MonolithicQuerySpec{ + RBAC: v1alpha1.RBACSpec{ + Enabled: true, + }, + }, + }, + }, + warnings: admission.Warnings{}, + errors: field.ErrorList{field.Invalid( + field.NewPath("spec", "rbac", "enabled"), + true, + "RBAC can only be enabled if multi-tenancy is enabled", + )}, + }, // observability {