diff --git a/.env b/.env index 837a943b2e..0febc0b23e 100644 --- a/.env +++ b/.env @@ -11,13 +11,14 @@ ENV_GARDENER_MIN_NODES=1 ENV_GARDENER_MAX_NODES=2 ## Dependencies -ENV_ISTIO_VERSION=1.23.2 +ENV_ISTIO_VERSION=1.23.1 ENV_GORELEASER_VERSION=v1.23.0 ## Default Docker Images ENV_FLUENTBIT_EXPORTER_IMAGE="europe-docker.pkg.dev/kyma-project/prod/directory-size-exporter:v20250910-86122076" ENV_FLUENTBIT_IMAGE="europe-docker.pkg.dev/kyma-project/prod/external/fluent/fluent-bit:4.2.0" ENV_OTEL_COLLECTOR_IMAGE="europe-docker.pkg.dev/kyma-project/prod/kyma-otel-collector:0.141.0-main" +ENV_OTEL_COLLECTOR_CONTRIB_IMAGE="otel/opentelemetry-collector-contrib:0.141.0" ENV_SELFMONITOR_IMAGE="europe-docker.pkg.dev/kyma-project/prod/tpi/telemetry-self-monitor:3.7.3-f4e3fab" ENV_TEST_TELEMETRYGEN_IMAGE="ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.141.0" ENV_ALPINE_IMAGE="europe-docker.pkg.dev/kyma-project/prod/external/library/alpine:3.22.2" diff --git a/apis/telemetry/v1alpha1/shared_types.go b/apis/telemetry/v1alpha1/shared_types.go index 4139fd86f8..857afed492 100644 --- a/apis/telemetry/v1alpha1/shared_types.go +++ b/apis/telemetry/v1alpha1/shared_types.go @@ -41,6 +41,7 @@ const ( // OTLPOutput OTLP output configuration // +kubebuilder:validation:XValidation:rule="(has(self.path) && size(self.path) > 0) ? self.protocol == 'http' : true",message="Path is only available with HTTP protocol" +// +kubebuilder:validation:XValidation:rule="(has(self.authentication) && has(self.authentication.oauth2) && self.protocol == 'grpc' && has(self.tls)) ? !(has(self.tls.insecure) && self.tls.insecure == true) : true",message="OAuth2 authentication requires TLS when using gRPC protocol" type OTLPOutput struct { // Protocol defines the OTLP protocol (`http` or `grpc`). Default is `grpc`. // +kubebuilder:validation:Optional @@ -63,10 +64,15 @@ type OTLPOutput struct { TLS *OTLPTLS `json:"tls,omitempty"` } +// AuthenticationOptions OTLP output authentication options +// +kubebuilder:validation:XValidation:rule="!(has(self.basic) && has(self.oauth2))",message="Only one authentication method can be specified" type AuthenticationOptions struct { // Basic activates `Basic` authentication for the destination providing relevant Secrets. // +kubebuilder:validation:Optional Basic *BasicAuthOptions `json:"basic,omitempty"` + // OAuth2 activates `OAuth2` authentication for the destination providing relevant Secrets. + // +kubebuilder:validation:Optional + OAuth2 *OAuth2Options `json:"oauth2,omitempty"` } type BasicAuthOptions struct { @@ -78,6 +84,26 @@ type BasicAuthOptions struct { Password ValueType `json:"password"` } +// OAuth2Options contains OAuth2 authentication options. +type OAuth2Options struct { + // TokenURL contains the OAuth2 token endpoint URL or a Secret reference. + // +kubebuilder:validation:XValidation:rule="(self.value != '' ) || (has(self.valueFrom))", message="tokenURL' missing" + // +kubebuilder:validation:XValidation:rule="(self.value != '' ) ? (isURL(self.value)) : true", message="'tokenURL' must be a valid URL" + TokenURL ValueType `json:"tokenURL"` + // ClientID contains the OAuth2 client ID or a Secret reference. + // +kubebuilder:validation:XValidation:rule="(self.value != '' ) || (has(self.valueFrom))", message="'clientID' missing" + ClientID ValueType `json:"clientID"` + // ClientSecret contains the OAuth2 client secret or a Secret reference. + // +kubebuilder:validation:XValidation:rule="(self.value != '' ) || (has(self.valueFrom))", message="clientSecret' missing" + ClientSecret ValueType `json:"clientSecret"` + // Scopes contains optional OAuth2 scopes. + // +kubebuilder:validation:Optional + Scopes []string `json:"scopes,omitempty"` + // Params contains optional additional OAuth2 parameters that are sent to the token endpoint. + // +kubebuilder:validation:Optional + Params map[string]string `json:"params,omitempty"` +} + type Header struct { // Defines the header value. ValueType `json:",inline"` diff --git a/apis/telemetry/v1alpha1/shared_types_conversion.go b/apis/telemetry/v1alpha1/shared_types_conversion.go index b62d9e6b34..5819bfb2a8 100644 --- a/apis/telemetry/v1alpha1/shared_types_conversion.go +++ b/apis/telemetry/v1alpha1/shared_types_conversion.go @@ -173,7 +173,8 @@ func convertAuthenticationToBeta(a *AuthenticationOptions) *telemetryv1beta1.Aut } return &telemetryv1beta1.AuthenticationOptions{ - Basic: convertBasicAuthToBeta(a.Basic), + Basic: convertBasicAuthToBeta(a.Basic), + OAuth2: convertOAuth2ToBeta(a.OAuth2), } } @@ -183,7 +184,8 @@ func convertAuthenticationToAlpha(a *telemetryv1beta1.AuthenticationOptions) *Au } return &AuthenticationOptions{ - Basic: convertBasicAuthToAlpha(a.Basic), + Basic: convertBasicAuthToAlpha(a.Basic), + OAuth2: convertOAuth2ToAlpha(a.OAuth2), } } @@ -209,6 +211,34 @@ func convertBasicAuthToAlpha(b *telemetryv1beta1.BasicAuthOptions) *BasicAuthOpt } } +func convertOAuth2ToBeta(o *OAuth2Options) *telemetryv1beta1.OAuth2Options { + if o == nil { + return nil + } + + return &telemetryv1beta1.OAuth2Options{ + TokenURL: convertValueTypeToBeta(o.TokenURL), + ClientID: convertValueTypeToBeta(o.ClientID), + ClientSecret: convertValueTypeToBeta(o.ClientSecret), + Scopes: append([]string{}, o.Scopes...), + Params: make(map[string]string), + } +} + +func convertOAuth2ToAlpha(o *telemetryv1beta1.OAuth2Options) *OAuth2Options { + if o == nil { + return nil + } + + return &OAuth2Options{ + TokenURL: convertValueTypeToAlpha(o.TokenURL), + ClientID: convertValueTypeToAlpha(o.ClientID), + ClientSecret: convertValueTypeToAlpha(o.ClientSecret), + Scopes: append([]string{}, o.Scopes...), + Params: make(map[string]string), + } +} + func convertHeadersToBeta(hs []Header) []telemetryv1beta1.Header { var out []telemetryv1beta1.Header for _, h := range hs { diff --git a/apis/telemetry/v1alpha1/zz_generated.deepcopy.go b/apis/telemetry/v1alpha1/zz_generated.deepcopy.go index 1b06b64002..d4c7532a18 100644 --- a/apis/telemetry/v1alpha1/zz_generated.deepcopy.go +++ b/apis/telemetry/v1alpha1/zz_generated.deepcopy.go @@ -33,6 +33,11 @@ func (in *AuthenticationOptions) DeepCopyInto(out *AuthenticationOptions) { *out = new(BasicAuthOptions) (*in).DeepCopyInto(*out) } + if in.OAuth2 != nil { + in, out := &in.OAuth2, &out.OAuth2 + *out = new(OAuth2Options) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthenticationOptions. @@ -874,6 +879,36 @@ func (in *NamespaceSelector) DeepCopy() *NamespaceSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OAuth2Options) DeepCopyInto(out *OAuth2Options) { + *out = *in + in.TokenURL.DeepCopyInto(&out.TokenURL) + in.ClientID.DeepCopyInto(&out.ClientID) + in.ClientSecret.DeepCopyInto(&out.ClientSecret) + if in.Scopes != nil { + in, out := &in.Scopes, &out.Scopes + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Params != nil { + in, out := &in.Params, &out.Params + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuth2Options. +func (in *OAuth2Options) DeepCopy() *OAuth2Options { + if in == nil { + return nil + } + out := new(OAuth2Options) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OTLPInput) DeepCopyInto(out *OTLPInput) { *out = *in diff --git a/apis/telemetry/v1beta1/shared_types.go b/apis/telemetry/v1beta1/shared_types.go index 8d4e0bb685..6fa9510199 100644 --- a/apis/telemetry/v1beta1/shared_types.go +++ b/apis/telemetry/v1beta1/shared_types.go @@ -43,6 +43,7 @@ const ( // OTLPOutput OTLP output configuration // +kubebuilder:validation:XValidation:rule="(has(self.path) && size(self.path) > 0) ? self.protocol == 'http' : true",message="Path is only available with HTTP protocol" +// +kubebuilder:validation:XValidation:rule="(has(self.authentication) && has(self.authentication.oauth2) && self.protocol == 'grpc' && has(self.tls)) ? !(has(self.tls.insecure) && self.tls.insecure == true) : true",message="OAuth2 authentication requires TLS when using gRPC protocol" type OTLPOutput struct { // Protocol defines the OTLP protocol (`http` or `grpc`). Default is `grpc`. // +kubebuilder:validation:Optional @@ -65,10 +66,15 @@ type OTLPOutput struct { TLS *OutputTLS `json:"tls,omitempty"` } +// AuthenticationOptions OTLP output authentication options +// +kubebuilder:validation:XValidation:rule="!(has(self.basic) && has(self.oauth2))",message="Only one authentication method can be specified" type AuthenticationOptions struct { // Basic activates `Basic` authentication for the destination providing relevant Secrets. // +kubebuilder:validation:Optional Basic *BasicAuthOptions `json:"basic,omitempty"` + // OAuth2 activates `OAuth2` authentication for the destination providing relevant Secrets. + // +kubebuilder:validation:Optional + OAuth2 *OAuth2Options `json:"oauth2,omitempty"` } type BasicAuthOptions struct { @@ -80,6 +86,24 @@ type BasicAuthOptions struct { Password ValueType `json:"password"` } +type OAuth2Options struct { + // TokenURL contains the OAuth2 token endpoint URL or a Secret reference. + // +kubebuilder:validation:Required + TokenURL ValueType `json:"tokenURL"` + // ClientID contains the OAuth2 client ID or a Secret reference. + // +kubebuilder:validation:Required + ClientID ValueType `json:"clientID"` + // ClientSecret contains the OAuth2 client secret or a Secret reference. + // +kubebuilder:validation:Required + ClientSecret ValueType `json:"clientSecret"` + // Scopes contains optional OAuth2 scopes. + // +kubebuilder:validation:Optional + Scopes []string `json:"scopes,omitempty"` + // Params contains optional additional OAuth2 parameters that are sent to the token endpoint. + // +kubebuilder:validation:Optional + Params map[string]string `json:"params,omitempty"` +} + type Header struct { // Defines the header value. ValueType `json:",inline"` diff --git a/apis/telemetry/v1beta1/zz_generated.deepcopy.go b/apis/telemetry/v1beta1/zz_generated.deepcopy.go index c156e7ff10..35b22710eb 100644 --- a/apis/telemetry/v1beta1/zz_generated.deepcopy.go +++ b/apis/telemetry/v1beta1/zz_generated.deepcopy.go @@ -33,6 +33,11 @@ func (in *AuthenticationOptions) DeepCopyInto(out *AuthenticationOptions) { *out = new(BasicAuthOptions) (*in).DeepCopyInto(*out) } + if in.OAuth2 != nil { + in, out := &in.OAuth2, &out.OAuth2 + *out = new(OAuth2Options) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthenticationOptions. @@ -827,6 +832,36 @@ func (in *NamespaceSelector) DeepCopy() *NamespaceSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OAuth2Options) DeepCopyInto(out *OAuth2Options) { + *out = *in + in.TokenURL.DeepCopyInto(&out.TokenURL) + in.ClientID.DeepCopyInto(&out.ClientID) + in.ClientSecret.DeepCopyInto(&out.ClientSecret) + if in.Scopes != nil { + in, out := &in.Scopes, &out.Scopes + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Params != nil { + in, out := &in.Params, &out.Params + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuth2Options. +func (in *OAuth2Options) DeepCopy() *OAuth2Options { + if in == nil { + return nil + } + out := new(OAuth2Options) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OTLPInput) DeepCopyInto(out *OTLPInput) { *out = *in diff --git a/dependencies/populateimages/main.go b/dependencies/populateimages/main.go index 6c8a0e9417..2da61b2438 100644 --- a/dependencies/populateimages/main.go +++ b/dependencies/populateimages/main.go @@ -18,8 +18,9 @@ var templates = map[string]string{ package testkit const ( - DefaultTelemetryGenImage = "{{ .ENV_TEST_TELEMETRYGEN_IMAGE }}" - DefaultOTelCollectorImage = "{{ .ENV_OTEL_COLLECTOR_IMAGE }}" + DefaultTelemetryGenImage = "{{ .ENV_TEST_TELEMETRYGEN_IMAGE }}" + DefaultOTelCollectorContribImage = "{{ .ENV_OTEL_COLLECTOR_CONTRIB_IMAGE }}" + DefaultOTelCollectorImage = "{{ .ENV_OTEL_COLLECTOR_IMAGE }}" ) `, } diff --git a/docs/user/integrate-otlp-backend/README.md b/docs/user/integrate-otlp-backend/README.md index 21d71f667f..d109d795ce 100644 --- a/docs/user/integrate-otlp-backend/README.md +++ b/docs/user/integrate-otlp-backend/README.md @@ -40,7 +40,7 @@ Ensure the port in your endpoint URL is correct for the chosen protocol. ## Set Up Authentication -For each pipeline, add authentication details (like user names, passwords, certificates, or tokens) to connect securely to your observability backend. You can use mutual TLS (mTLS), custom headers, or Basic Authentication. +For each pipeline, add authentication details (like user names, passwords, certificates, or tokens) to connect securely to your observability backend. You can use mutual TLS (mTLS), custom headers, OAuth2, or Basic Authentication. While you can choose to add your authentication details from plain text, it’s recommended to store these sensitive details in a Kubernetes `Secret` and reference the Secret's keys in your pipeline configuration. When you rotate the `Secret` and update its values, Telemetry Manager detects the changes and applies the new `Secret` to your setup. @@ -88,6 +88,40 @@ While you can choose to add your authentication details from plain text, it’s key: token ``` +- To use OAuth2 for authentication, configure the `authentication.oauth2` section. + + ```yaml + ... + output: + otlp: + endpoint: + valueFrom: + secretKeyRef: + name: backend + namespace: default + key: endpoint + authentication: + oauth2: + clientId: + valueFrom: + secretKeyRef: + name: backend + namespace: default + key: clientId + clientSecret: + valueFrom: + secretKeyRef: + name: backend + namespace: default + key: clientSecret + tokenUrl: + valueFrom: + secretKeyRef: + name: backend + namespace: default + key: tokenUrl + ``` + - To use a username and password for authentication, configure the `authentication.basic` section. ```yaml diff --git a/docs/user/resources/02-logpipeline.md b/docs/user/resources/02-logpipeline.md index 4e4a21f462..b53c186c9c 100644 --- a/docs/user/resources/02-logpipeline.md +++ b/docs/user/resources/02-logpipeline.md @@ -198,6 +198,30 @@ For details, see the [LogPipeline specification file](https://github.com/kyma-pr | **output.​otlp.​authentication.​basic.​user.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | | **output.​otlp.​authentication.​basic.​user.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | | **output.​otlp.​authentication.​basic.​user.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | +| **output.​otlp.​authentication.​oauth2** | object | OAuth2 activates `OAuth2` authentication for the destination providing relevant Secrets. | +| **output.​otlp.​authentication.​oauth2.​clientID** (required) | object | ClientID contains the OAuth2 client ID or a Secret reference. | +| **output.​otlp.​authentication.​oauth2.​clientID.​value** | string | Value as plain text. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef** (required) | object | SecretKeyRef refers to the value of a specific key in a Secret. You must provide `name` and `namespace` of the Secret, as well as the name of the `key`. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientSecret** (required) | object | ClientSecret contains the OAuth2 client secret or a Secret reference. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​value** | string | Value as plain text. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef** (required) | object | SecretKeyRef refers to the value of a specific key in a Secret. You must provide `name` and `namespace` of the Secret, as well as the name of the `key`. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | +| **output.​otlp.​authentication.​oauth2.​params** | map\[string\]string | Params contains optional additional OAuth2 parameters that are sent to the token endpoint. | +| **output.​otlp.​authentication.​oauth2.​scopes** | \[\]string | Scopes contains optional OAuth2 scopes. | +| **output.​otlp.​authentication.​oauth2.​tokenURL** (required) | object | TokenURL contains the OAuth2 token endpoint URL or a Secret reference. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​value** | string | Value as plain text. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef** (required) | object | SecretKeyRef refers to the value of a specific key in a Secret. You must provide `name` and `namespace` of the Secret, as well as the name of the `key`. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | | **output.​otlp.​endpoint** (required) | object | Endpoint defines the host and port (`:`) of an OTLP endpoint. | | **output.​otlp.​endpoint.​value** | string | Value as plain text. | | **output.​otlp.​endpoint.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | diff --git a/docs/user/resources/04-tracepipeline.md b/docs/user/resources/04-tracepipeline.md index c2ae634b22..d6acb2615a 100644 --- a/docs/user/resources/04-tracepipeline.md +++ b/docs/user/resources/04-tracepipeline.md @@ -82,6 +82,30 @@ For details, see the [TracePipeline specification file](https://github.com/kyma- | **output.​otlp.​authentication.​basic.​user.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | | **output.​otlp.​authentication.​basic.​user.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | | **output.​otlp.​authentication.​basic.​user.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | +| **output.​otlp.​authentication.​oauth2** | object | OAuth2 activates `OAuth2` authentication for the destination providing relevant Secrets. | +| **output.​otlp.​authentication.​oauth2.​clientID** (required) | object | ClientID contains the OAuth2 client ID or a Secret reference. | +| **output.​otlp.​authentication.​oauth2.​clientID.​value** | string | Value as plain text. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef** (required) | object | SecretKeyRef refers to the value of a specific key in a Secret. You must provide `name` and `namespace` of the Secret, as well as the name of the `key`. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientSecret** (required) | object | ClientSecret contains the OAuth2 client secret or a Secret reference. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​value** | string | Value as plain text. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef** (required) | object | SecretKeyRef refers to the value of a specific key in a Secret. You must provide `name` and `namespace` of the Secret, as well as the name of the `key`. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | +| **output.​otlp.​authentication.​oauth2.​params** | map\[string\]string | Params contains optional additional OAuth2 parameters that are sent to the token endpoint. | +| **output.​otlp.​authentication.​oauth2.​scopes** | \[\]string | Scopes contains optional OAuth2 scopes. | +| **output.​otlp.​authentication.​oauth2.​tokenURL** (required) | object | TokenURL contains the OAuth2 token endpoint URL or a Secret reference. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​value** | string | Value as plain text. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef** (required) | object | SecretKeyRef refers to the value of a specific key in a Secret. You must provide `name` and `namespace` of the Secret, as well as the name of the `key`. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | | **output.​otlp.​endpoint** (required) | object | Endpoint defines the host and port (`:`) of an OTLP endpoint. | | **output.​otlp.​endpoint.​value** | string | Value as plain text. | | **output.​otlp.​endpoint.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | diff --git a/docs/user/resources/05-metricpipeline.md b/docs/user/resources/05-metricpipeline.md index 766aba6648..1836df18bd 100644 --- a/docs/user/resources/05-metricpipeline.md +++ b/docs/user/resources/05-metricpipeline.md @@ -139,6 +139,30 @@ For details, see the [MetricPipeline specification file](https://github.com/kyma | **output.​otlp.​authentication.​basic.​user.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | | **output.​otlp.​authentication.​basic.​user.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | | **output.​otlp.​authentication.​basic.​user.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | +| **output.​otlp.​authentication.​oauth2** | object | OAuth2 activates `OAuth2` authentication for the destination providing relevant Secrets. | +| **output.​otlp.​authentication.​oauth2.​clientID** (required) | object | ClientID contains the OAuth2 client ID or a Secret reference. | +| **output.​otlp.​authentication.​oauth2.​clientID.​value** | string | Value as plain text. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef** (required) | object | SecretKeyRef refers to the value of a specific key in a Secret. You must provide `name` and `namespace` of the Secret, as well as the name of the `key`. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientID.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientSecret** (required) | object | ClientSecret contains the OAuth2 client secret or a Secret reference. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​value** | string | Value as plain text. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef** (required) | object | SecretKeyRef refers to the value of a specific key in a Secret. You must provide `name` and `namespace` of the Secret, as well as the name of the `key`. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | +| **output.​otlp.​authentication.​oauth2.​clientSecret.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | +| **output.​otlp.​authentication.​oauth2.​params** | map\[string\]string | Params contains optional additional OAuth2 parameters that are sent to the token endpoint. | +| **output.​otlp.​authentication.​oauth2.​scopes** | \[\]string | Scopes contains optional OAuth2 scopes. | +| **output.​otlp.​authentication.​oauth2.​tokenURL** (required) | object | TokenURL contains the OAuth2 token endpoint URL or a Secret reference. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​value** | string | Value as plain text. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef** (required) | object | SecretKeyRef refers to the value of a specific key in a Secret. You must provide `name` and `namespace` of the Secret, as well as the name of the `key`. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef.​key** (required) | string | Key defines the name of the attribute of the Secret holding the referenced value. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef.​name** (required) | string | Name of the Secret containing the referenced value. | +| **output.​otlp.​authentication.​oauth2.​tokenURL.​valueFrom.​secretKeyRef.​namespace** (required) | string | Namespace containing the Secret with the referenced value. | | **output.​otlp.​endpoint** (required) | object | Endpoint defines the host and port (`:`) of an OTLP endpoint. | | **output.​otlp.​endpoint.​value** | string | Value as plain text. | | **output.​otlp.​endpoint.​valueFrom** | object | ValueFrom is the value as a reference to a resource. | diff --git a/helm/charts/default/templates/telemetry.kyma-project.io_logpipelines.yaml b/helm/charts/default/templates/telemetry.kyma-project.io_logpipelines.yaml index a199f3fe4b..d04b3500aa 100644 --- a/helm/charts/default/templates/telemetry.kyma-project.io_logpipelines.yaml +++ b/helm/charts/default/templates/telemetry.kyma-project.io_logpipelines.yaml @@ -693,7 +693,180 @@ spec: - password - user type: object + oauth2: + description: OAuth2 activates `OAuth2` authentication + for the destination providing relevant Secrets. + properties: + clientID: + description: ClientID contains the OAuth2 client ID + or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: '''clientID'' missing' + rule: (self.value != '' ) || (has(self.valueFrom)) + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + clientSecret: + description: ClientSecret contains the OAuth2 client + secret or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: clientSecret' missing + rule: (self.value != '' ) || (has(self.valueFrom)) + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + params: + additionalProperties: + type: string + description: Params contains optional additional OAuth2 + parameters that are sent to the token endpoint. + type: object + scopes: + description: Scopes contains optional OAuth2 scopes. + items: + type: string + type: array + tokenURL: + description: TokenURL contains the OAuth2 token endpoint + URL or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: tokenURL' missing + rule: (self.value != '' ) || (has(self.valueFrom)) + - message: '''tokenURL'' must be a valid URL' + rule: '(self.value != '''' ) ? (isURL(self.value)) + : true' + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + required: + - clientID + - clientSecret + - tokenURL + type: object type: object + x-kubernetes-validations: + - message: Only one authentication method can be specified + rule: '!(has(self.basic) && has(self.oauth2))' endpoint: description: Endpoint defines the host and port (`:`) of an OTLP endpoint. @@ -965,6 +1138,11 @@ spec: - message: Path is only available with HTTP protocol rule: '(has(self.path) && size(self.path) > 0) ? self.protocol == ''http'' : true' + - message: OAuth2 authentication requires TLS when using gRPC + protocol + rule: '(has(self.authentication) && has(self.authentication.oauth2) + && self.protocol == ''grpc'' && has(self.tls)) ? !(has(self.tls.insecure) + && self.tls.insecure == true) : true' type: object x-kubernetes-validations: - message: Switching to or away from OTLP output is not supported. diff --git a/helm/charts/default/templates/telemetry.kyma-project.io_metricpipelines.yaml b/helm/charts/default/templates/telemetry.kyma-project.io_metricpipelines.yaml index 1a9c917e87..ed8abafde6 100644 --- a/helm/charts/default/templates/telemetry.kyma-project.io_metricpipelines.yaml +++ b/helm/charts/default/templates/telemetry.kyma-project.io_metricpipelines.yaml @@ -476,7 +476,180 @@ spec: - password - user type: object + oauth2: + description: OAuth2 activates `OAuth2` authentication + for the destination providing relevant Secrets. + properties: + clientID: + description: ClientID contains the OAuth2 client ID + or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: '''clientID'' missing' + rule: (self.value != '' ) || (has(self.valueFrom)) + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + clientSecret: + description: ClientSecret contains the OAuth2 client + secret or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: clientSecret' missing + rule: (self.value != '' ) || (has(self.valueFrom)) + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + params: + additionalProperties: + type: string + description: Params contains optional additional OAuth2 + parameters that are sent to the token endpoint. + type: object + scopes: + description: Scopes contains optional OAuth2 scopes. + items: + type: string + type: array + tokenURL: + description: TokenURL contains the OAuth2 token endpoint + URL or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: tokenURL' missing + rule: (self.value != '' ) || (has(self.valueFrom)) + - message: '''tokenURL'' must be a valid URL' + rule: '(self.value != '''' ) ? (isURL(self.value)) + : true' + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + required: + - clientID + - clientSecret + - tokenURL + type: object type: object + x-kubernetes-validations: + - message: Only one authentication method can be specified + rule: '!(has(self.basic) && has(self.oauth2))' endpoint: description: Endpoint defines the host and port (`:`) of an OTLP endpoint. @@ -748,6 +921,11 @@ spec: - message: Path is only available with HTTP protocol rule: '(has(self.path) && size(self.path) > 0) ? self.protocol == ''http'' : true' + - message: OAuth2 authentication requires TLS when using gRPC + protocol + rule: '(has(self.authentication) && has(self.authentication.oauth2) + && self.protocol == ''grpc'' && has(self.tls)) ? !(has(self.tls.insecure) + && self.tls.insecure == true) : true' required: - otlp type: object diff --git a/helm/charts/default/templates/telemetry.kyma-project.io_tracepipelines.yaml b/helm/charts/default/templates/telemetry.kyma-project.io_tracepipelines.yaml index 98429472e0..578205c959 100644 --- a/helm/charts/default/templates/telemetry.kyma-project.io_tracepipelines.yaml +++ b/helm/charts/default/templates/telemetry.kyma-project.io_tracepipelines.yaml @@ -190,7 +190,180 @@ spec: - password - user type: object + oauth2: + description: OAuth2 activates `OAuth2` authentication + for the destination providing relevant Secrets. + properties: + clientID: + description: ClientID contains the OAuth2 client ID + or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: '''clientID'' missing' + rule: (self.value != '' ) || (has(self.valueFrom)) + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + clientSecret: + description: ClientSecret contains the OAuth2 client + secret or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: clientSecret' missing + rule: (self.value != '' ) || (has(self.valueFrom)) + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + params: + additionalProperties: + type: string + description: Params contains optional additional OAuth2 + parameters that are sent to the token endpoint. + type: object + scopes: + description: Scopes contains optional OAuth2 scopes. + items: + type: string + type: array + tokenURL: + description: TokenURL contains the OAuth2 token endpoint + URL or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: tokenURL' missing + rule: (self.value != '' ) || (has(self.valueFrom)) + - message: '''tokenURL'' must be a valid URL' + rule: '(self.value != '''' ) ? (isURL(self.value)) + : true' + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + required: + - clientID + - clientSecret + - tokenURL + type: object type: object + x-kubernetes-validations: + - message: Only one authentication method can be specified + rule: '!(has(self.basic) && has(self.oauth2))' endpoint: description: Endpoint defines the host and port (`:`) of an OTLP endpoint. @@ -462,6 +635,11 @@ spec: - message: Path is only available with HTTP protocol rule: '(has(self.path) && size(self.path) > 0) ? self.protocol == ''http'' : true' + - message: OAuth2 authentication requires TLS when using gRPC + protocol + rule: '(has(self.authentication) && has(self.authentication.oauth2) + && self.protocol == ''grpc'' && has(self.tls)) ? !(has(self.tls.insecure) + && self.tls.insecure == true) : true' required: - otlp type: object diff --git a/helm/charts/experimental/templates/telemetry.kyma-project.io_logpipelines.yaml b/helm/charts/experimental/templates/telemetry.kyma-project.io_logpipelines.yaml index f895cccd60..bb4929c698 100644 --- a/helm/charts/experimental/templates/telemetry.kyma-project.io_logpipelines.yaml +++ b/helm/charts/experimental/templates/telemetry.kyma-project.io_logpipelines.yaml @@ -693,7 +693,171 @@ spec: - password - user type: object + oauth2: + description: OAuth2 activates `OAuth2` authentication + for the destination providing relevant Secrets. + properties: + clientID: + description: ClientID contains the OAuth2 client ID + or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + clientSecret: + description: ClientSecret contains the OAuth2 client + secret or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + params: + additionalProperties: + type: string + description: Params contains optional additional OAuth2 + parameters that are sent to the token endpoint. + type: object + scopes: + description: Scopes contains optional OAuth2 scopes. + items: + type: string + type: array + tokenURL: + description: TokenURL contains the OAuth2 token endpoint + URL or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + required: + - clientID + - clientSecret + - tokenURL + type: object type: object + x-kubernetes-validations: + - message: Only one authentication method can be specified + rule: '!(has(self.basic) && has(self.oauth2))' endpoint: description: Endpoint defines the host and port (`:`) of an OTLP endpoint. @@ -965,6 +1129,11 @@ spec: - message: Path is only available with HTTP protocol rule: '(has(self.path) && size(self.path) > 0) ? self.protocol == ''http'' : true' + - message: OAuth2 authentication requires TLS to be configured + when using gRPC protocol + rule: '(has(self.authentication) && has(self.authentication.oauth2) + && self.protocol == ''grpc'') ? (has(self.tls) && !(has(self.tls.insecure) + && self.tls.insecure == true)) : true' type: object x-kubernetes-validations: - message: Switching to or away from OTLP output is not supported. @@ -1800,7 +1969,168 @@ spec: - password - user type: object + oauth2: + description: OAuth2 activates `OAuth2` authentication + for the destination providing relevant Secrets. + properties: + clientID: + description: ClientID contains the OAuth2 client ID + or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Exactly one of 'value' or 'valueFrom' must + be set + rule: has(self.value) != has(self.valueFrom) + clientSecret: + description: ClientSecret contains the OAuth2 client + secret or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Exactly one of 'value' or 'valueFrom' must + be set + rule: has(self.value) != has(self.valueFrom) + params: + additionalProperties: + type: string + description: Params contains optional additional OAuth2 + parameters that are sent to the token endpoint. + type: object + scopes: + description: Scopes contains optional OAuth2 scopes. + items: + type: string + type: array + tokenURL: + description: TokenURL contains the OAuth2 token endpoint + URL or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Exactly one of 'value' or 'valueFrom' must + be set + rule: has(self.value) != has(self.valueFrom) + required: + - clientID + - clientSecret + - tokenURL + type: object type: object + x-kubernetes-validations: + - message: Only one authentication method can be specified + rule: '!(has(self.basic) && has(self.oauth2))' endpoint: description: Endpoint defines the host and port (`:`) of an OTLP endpoint. @@ -2073,6 +2403,11 @@ spec: - message: Path is only available with HTTP protocol rule: '(has(self.path) && size(self.path) > 0) ? self.protocol == ''http'' : true' + - message: OAuth2 authentication requires TLS to be configured + when using gRPC protocol + rule: '(has(self.authentication) && has(self.authentication.oauth2) + && self.protocol == ''grpc'') ? (has(self.tls) && !(has(self.tls.insecure) + && self.tls.insecure == true)) : true' type: object x-kubernetes-validations: - message: Switching to or away from OTLP output is not supported. diff --git a/helm/charts/experimental/templates/telemetry.kyma-project.io_metricpipelines.yaml b/helm/charts/experimental/templates/telemetry.kyma-project.io_metricpipelines.yaml index 39c6b1e837..a789dac1d2 100644 --- a/helm/charts/experimental/templates/telemetry.kyma-project.io_metricpipelines.yaml +++ b/helm/charts/experimental/templates/telemetry.kyma-project.io_metricpipelines.yaml @@ -476,7 +476,171 @@ spec: - password - user type: object + oauth2: + description: OAuth2 activates `OAuth2` authentication + for the destination providing relevant Secrets. + properties: + clientID: + description: ClientID contains the OAuth2 client ID + or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + clientSecret: + description: ClientSecret contains the OAuth2 client + secret or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + params: + additionalProperties: + type: string + description: Params contains optional additional OAuth2 + parameters that are sent to the token endpoint. + type: object + scopes: + description: Scopes contains optional OAuth2 scopes. + items: + type: string + type: array + tokenURL: + description: TokenURL contains the OAuth2 token endpoint + URL or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + required: + - clientID + - clientSecret + - tokenURL + type: object type: object + x-kubernetes-validations: + - message: Only one authentication method can be specified + rule: '!(has(self.basic) && has(self.oauth2))' endpoint: description: Endpoint defines the host and port (`:`) of an OTLP endpoint. @@ -748,6 +912,11 @@ spec: - message: Path is only available with HTTP protocol rule: '(has(self.path) && size(self.path) > 0) ? self.protocol == ''http'' : true' + - message: OAuth2 authentication requires TLS to be configured + when using gRPC protocol + rule: '(has(self.authentication) && has(self.authentication.oauth2) + && self.protocol == ''grpc'') ? (has(self.tls) && !(has(self.tls.insecure) + && self.tls.insecure == true)) : true' required: - otlp type: object @@ -1297,7 +1466,168 @@ spec: - password - user type: object + oauth2: + description: OAuth2 activates `OAuth2` authentication + for the destination providing relevant Secrets. + properties: + clientID: + description: ClientID contains the OAuth2 client ID + or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Exactly one of 'value' or 'valueFrom' must + be set + rule: has(self.value) != has(self.valueFrom) + clientSecret: + description: ClientSecret contains the OAuth2 client + secret or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Exactly one of 'value' or 'valueFrom' must + be set + rule: has(self.value) != has(self.valueFrom) + params: + additionalProperties: + type: string + description: Params contains optional additional OAuth2 + parameters that are sent to the token endpoint. + type: object + scopes: + description: Scopes contains optional OAuth2 scopes. + items: + type: string + type: array + tokenURL: + description: TokenURL contains the OAuth2 token endpoint + URL or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Exactly one of 'value' or 'valueFrom' must + be set + rule: has(self.value) != has(self.valueFrom) + required: + - clientID + - clientSecret + - tokenURL + type: object type: object + x-kubernetes-validations: + - message: Only one authentication method can be specified + rule: '!(has(self.basic) && has(self.oauth2))' endpoint: description: Endpoint defines the host and port (`:`) of an OTLP endpoint. @@ -1570,6 +1900,11 @@ spec: - message: Path is only available with HTTP protocol rule: '(has(self.path) && size(self.path) > 0) ? self.protocol == ''http'' : true' + - message: OAuth2 authentication requires TLS to be configured + when using gRPC protocol + rule: '(has(self.authentication) && has(self.authentication.oauth2) + && self.protocol == ''grpc'') ? (has(self.tls) && !(has(self.tls.insecure) + && self.tls.insecure == true)) : true' required: - otlp type: object diff --git a/helm/charts/experimental/templates/telemetry.kyma-project.io_tracepipelines.yaml b/helm/charts/experimental/templates/telemetry.kyma-project.io_tracepipelines.yaml index 0be09bc427..49b8c003ec 100644 --- a/helm/charts/experimental/templates/telemetry.kyma-project.io_tracepipelines.yaml +++ b/helm/charts/experimental/templates/telemetry.kyma-project.io_tracepipelines.yaml @@ -190,7 +190,171 @@ spec: - password - user type: object + oauth2: + description: OAuth2 activates `OAuth2` authentication + for the destination providing relevant Secrets. + properties: + clientID: + description: ClientID contains the OAuth2 client ID + or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + clientSecret: + description: ClientSecret contains the OAuth2 client + secret or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + params: + additionalProperties: + type: string + description: Params contains optional additional OAuth2 + parameters that are sent to the token endpoint. + type: object + scopes: + description: Scopes contains optional OAuth2 scopes. + items: + type: string + type: array + tokenURL: + description: TokenURL contains the OAuth2 token endpoint + URL or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Only one of 'value' or 'valueFrom' can + be set + rule: '!(has(self.value) && size(self.value) > 0 + && has(self.valueFrom))' + required: + - clientID + - clientSecret + - tokenURL + type: object type: object + x-kubernetes-validations: + - message: Only one authentication method can be specified + rule: '!(has(self.basic) && has(self.oauth2))' endpoint: description: Endpoint defines the host and port (`:`) of an OTLP endpoint. @@ -462,6 +626,11 @@ spec: - message: Path is only available with HTTP protocol rule: '(has(self.path) && size(self.path) > 0) ? self.protocol == ''http'' : true' + - message: OAuth2 authentication requires TLS to be configured + when using gRPC protocol + rule: '(has(self.authentication) && has(self.authentication.oauth2) + && self.protocol == ''grpc'') ? (has(self.tls) && !(has(self.tls.insecure) + && self.tls.insecure == true)) : true' required: - otlp type: object @@ -724,7 +893,168 @@ spec: - password - user type: object + oauth2: + description: OAuth2 activates `OAuth2` authentication + for the destination providing relevant Secrets. + properties: + clientID: + description: ClientID contains the OAuth2 client ID + or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Exactly one of 'value' or 'valueFrom' must + be set + rule: has(self.value) != has(self.valueFrom) + clientSecret: + description: ClientSecret contains the OAuth2 client + secret or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Exactly one of 'value' or 'valueFrom' must + be set + rule: has(self.value) != has(self.valueFrom) + params: + additionalProperties: + type: string + description: Params contains optional additional OAuth2 + parameters that are sent to the token endpoint. + type: object + scopes: + description: Scopes contains optional OAuth2 scopes. + items: + type: string + type: array + tokenURL: + description: TokenURL contains the OAuth2 token endpoint + URL or a Secret reference. + properties: + value: + description: Value as plain text. + type: string + valueFrom: + description: ValueFrom is the value as a reference + to a resource. + properties: + secretKeyRef: + description: SecretKeyRef refers to the value + of a specific key in a Secret. You must + provide `name` and `namespace` of the Secret, + as well as the name of the `key`. + properties: + key: + description: Key defines the name of the + attribute of the Secret holding the + referenced value. + minLength: 1 + type: string + name: + description: Name of the Secret containing + the referenced value. + minLength: 1 + type: string + namespace: + description: Namespace containing the + Secret with the referenced value. + minLength: 1 + type: string + required: + - key + - name + - namespace + type: object + required: + - secretKeyRef + type: object + type: object + x-kubernetes-validations: + - message: Exactly one of 'value' or 'valueFrom' must + be set + rule: has(self.value) != has(self.valueFrom) + required: + - clientID + - clientSecret + - tokenURL + type: object type: object + x-kubernetes-validations: + - message: Only one authentication method can be specified + rule: '!(has(self.basic) && has(self.oauth2))' endpoint: description: Endpoint defines the host and port (`:`) of an OTLP endpoint. @@ -997,6 +1327,11 @@ spec: - message: Path is only available with HTTP protocol rule: '(has(self.path) && size(self.path) > 0) ? self.protocol == ''http'' : true' + - message: OAuth2 authentication requires TLS to be configured + when using gRPC protocol + rule: '(has(self.authentication) && has(self.authentication.oauth2) + && self.protocol == ''grpc'') ? (has(self.tls) && !(has(self.tls.insecure) + && self.tls.insecure == true)) : true' required: - otlp type: object diff --git a/internal/otelcollector/config/common/component_builder.go b/internal/otelcollector/config/common/component_builder.go index 0923733808..74d57bd032 100644 --- a/internal/otelcollector/config/common/component_builder.go +++ b/internal/otelcollector/config/common/component_builder.go @@ -194,11 +194,15 @@ func (cb *ComponentBuilder[T]) AddExporter(componentIDFunc ComponentIDFunc[T], c } } -func (cb *ComponentBuilder[T]) AddExtension(componentID string, extensionConfig any) { +func (cb *ComponentBuilder[T]) AddExtension(componentID string, extensionConfig any, extensionEnvVars EnvVars) { if _, found := cb.Config.Extensions[componentID]; !found { cb.Config.Extensions[componentID] = extensionConfig } + if extensionEnvVars != nil { + maps.Copy(cb.EnvVars, extensionEnvVars) + } + // Ensure the extension is added to the service only once extensions := cb.Config.Service.Extensions if slices.Contains(extensions, componentID) { diff --git a/internal/otelcollector/config/common/constants.go b/internal/otelcollector/config/common/constants.go index fa9907e990..d3cc326303 100644 --- a/internal/otelcollector/config/common/constants.go +++ b/internal/otelcollector/config/common/constants.go @@ -186,4 +186,5 @@ const ( ComponentIDFileStorageExtension = "file_storage" ComponentIDHealthCheckExtension = "health_check" ComponentIDPprofExtension = "pprof" + ComponentIDOAuth2Extension = "oauth2client/%s" // dynamically filled with pipeline name ) diff --git a/internal/otelcollector/config/common/env_vars.go b/internal/otelcollector/config/common/env_vars.go new file mode 100644 index 0000000000..044758316f --- /dev/null +++ b/internal/otelcollector/config/common/env_vars.go @@ -0,0 +1,263 @@ +package common + +import ( + "bytes" + "context" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/client" + + telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" + sharedtypesutils "github.com/kyma-project/telemetry-manager/internal/utils/sharedtypes" +) + +const ( + basicAuthHeaderVariablePrefix = "BASIC_AUTH_HEADER" + otlpEndpointVariablePrefix = "OTLP_ENDPOINT" + tlsConfigCertVariablePrefix = "OTLP_TLS_CERT_PEM" + tlsConfigKeyVariablePrefix = "OTLP_TLS_KEY_PEM" + tlsConfigCaVariablePrefix = "OTLP_TLS_CA_PEM" + oauth2TokenURLVariablePrefix = "OAUTH2_TOKEN_URL" //nolint:gosec // G101: This is a variable name prefix, not a credential + oauth2ClientIDVariablePrefix = "OAUTH2_CLIENT_ID" //nolint:gosec // G101: This is a variable name prefix, not a credential + oauth2ClientSecretVariablePrefix = "OAUTH2_CLIENT_SECRET" //nolint:gosec // G101: This is a variable name prefix, not a credential +) + +// ============================================================================= +// Env Vars Builders +// ============================================================================= + +func makeOTLPExporterEnvVars(ctx context.Context, c client.Reader, output *telemetryv1alpha1.OTLPOutput, pipelineName string) (map[string][]byte, error) { + var err error + + secretData := make(map[string][]byte) + + err = makeBasicAuthEnvVar(ctx, c, secretData, output, pipelineName) + if err != nil { + return nil, err + } + + err = makeOTLPEndpointEnvVar(ctx, c, secretData, output, pipelineName) + if err != nil { + return nil, err + } + + err = makeHeaderEnvVar(ctx, c, secretData, output, pipelineName) + if err != nil { + return nil, err + } + + err = makeTLSEnvVar(ctx, c, secretData, output, pipelineName) + if err != nil { + return nil, err + } + + return secretData, nil +} + +func makeOAuth2ExtensionEnvVars(ctx context.Context, c client.Reader, oauth2Options *telemetryv1alpha1.OAuth2Options, pipelineName string) (map[string][]byte, error) { + var err error + + secretData := make(map[string][]byte) + + err = makeTokenURLEnvVar(ctx, c, secretData, oauth2Options, pipelineName) + if err != nil { + return nil, err + } + + err = makeClientIDEnvVar(ctx, c, secretData, oauth2Options, pipelineName) + if err != nil { + return nil, err + } + + err = makeClientSecretEnvVar(ctx, c, secretData, oauth2Options, pipelineName) + if err != nil { + return nil, err + } + + return secretData, nil +} + +func makeBasicAuthEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, output *telemetryv1alpha1.OTLPOutput, pipelineName string) error { + if isBasicAuthEnabled(output.Authentication) { + username, err := sharedtypesutils.ResolveValue(ctx, c, output.Authentication.Basic.User) + if err != nil { + return err + } + + password, err := sharedtypesutils.ResolveValue(ctx, c, output.Authentication.Basic.Password) + if err != nil { + return err + } + + basicAuthHeader := formatBasicAuthHeader(string(username), string(password)) + basicAuthHeaderVariable := formatEnvVarKey(basicAuthHeaderVariablePrefix, sanitizeEnvVarName(pipelineName)) + secretData[basicAuthHeaderVariable] = []byte(basicAuthHeader) + } + + return nil +} + +func makeOTLPEndpointEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, output *telemetryv1alpha1.OTLPOutput, pipelineName string) error { + otlpEndpointVariable := formatEnvVarKey(otlpEndpointVariablePrefix, pipelineName) + + endpointURL, err := resolveEndpointURL(ctx, c, output) + if err != nil { + return err + } + + secretData[otlpEndpointVariable] = endpointURL + + return err +} + +func makeHeaderEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, output *telemetryv1alpha1.OTLPOutput, pipelineName string) error { + for _, header := range output.Headers { + key := formatHeaderEnvVarKey(header, pipelineName) + + value, err := sharedtypesutils.ResolveValue(ctx, c, header.ValueType) + if err != nil { + return err + } + + secretData[key] = prefixHeaderValue(header, value) + } + + return nil +} + +func makeTLSEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, output *telemetryv1alpha1.OTLPOutput, pipelineName string) error { + if output.TLS != nil { + if sharedtypesutils.IsValid(output.TLS.CA) { + ca, err := sharedtypesutils.ResolveValue(ctx, c, *output.TLS.CA) + if err != nil { + return err + } + + tlsConfigCaVariable := formatEnvVarKey(tlsConfigCaVariablePrefix, pipelineName) + secretData[tlsConfigCaVariable] = ca + } + + if sharedtypesutils.IsValid(output.TLS.Cert) && sharedtypesutils.IsValid(output.TLS.Key) { + cert, err := sharedtypesutils.ResolveValue(ctx, c, *output.TLS.Cert) + if err != nil { + return err + } + + key, err := sharedtypesutils.ResolveValue(ctx, c, *output.TLS.Key) + if err != nil { + return err + } + + // Make a best effort replacement of linebreaks in cert/key if present. + sanitizedCert := bytes.ReplaceAll(cert, []byte("\\n"), []byte("\n")) + sanitizedKey := bytes.ReplaceAll(key, []byte("\\n"), []byte("\n")) + + tlsConfigCertVariable := formatEnvVarKey(tlsConfigCertVariablePrefix, pipelineName) + secretData[tlsConfigCertVariable] = sanitizedCert + + tlsConfigKeyVariable := formatEnvVarKey(tlsConfigKeyVariablePrefix, pipelineName) + secretData[tlsConfigKeyVariable] = sanitizedKey + } + } + + return nil +} + +func makeTokenURLEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, oauth2Options *telemetryv1alpha1.OAuth2Options, pipelineName string) error { + if oauth2Options != nil && sharedtypesutils.IsValid(&oauth2Options.TokenURL) { + tokenURL, err := sharedtypesutils.ResolveValue(ctx, c, oauth2Options.TokenURL) + if err != nil { + return err + } + + tokenURLVariable := formatEnvVarKey(oauth2TokenURLVariablePrefix, sanitizeEnvVarName(pipelineName)) + secretData[tokenURLVariable] = tokenURL + } + + return nil +} + +func makeClientIDEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, oauth2Options *telemetryv1alpha1.OAuth2Options, pipelineName string) error { + if oauth2Options != nil && sharedtypesutils.IsValid(&oauth2Options.ClientID) { + clientID, err := sharedtypesutils.ResolveValue(ctx, c, oauth2Options.ClientID) + if err != nil { + return err + } + + clientIDVariable := formatEnvVarKey(oauth2ClientIDVariablePrefix, sanitizeEnvVarName(pipelineName)) + secretData[clientIDVariable] = clientID + } + + return nil +} + +func makeClientSecretEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, oauth2Options *telemetryv1alpha1.OAuth2Options, pipelineName string) error { + if oauth2Options != nil && sharedtypesutils.IsValid(&oauth2Options.ClientSecret) { + clientSecret, err := sharedtypesutils.ResolveValue(ctx, c, oauth2Options.ClientSecret) + if err != nil { + return err + } + + clientSecretVariable := formatEnvVarKey(oauth2ClientSecretVariablePrefix, sanitizeEnvVarName(pipelineName)) + secretData[clientSecretVariable] = clientSecret + } + + return nil +} + +// ============================================================================= +// Helper Functions +// ============================================================================= + +func prefixHeaderValue(header telemetryv1alpha1.Header, value []byte) []byte { + if len(strings.TrimSpace(header.Prefix)) > 0 { + return fmt.Appendf(nil, "%s %s", strings.TrimSpace(header.Prefix), string(value)) + } + + return value +} + +func resolveEndpointURL(ctx context.Context, c client.Reader, output *telemetryv1alpha1.OTLPOutput) ([]byte, error) { + endpoint, err := sharedtypesutils.ResolveValue(ctx, c, output.Endpoint) + if err != nil { + return nil, err + } + + if len(output.Path) > 0 { + u, err := url.Parse(string(endpoint)) + if err != nil { + return nil, err + } + + u.Path = path.Join(u.Path, output.Path) + + return []byte(u.String()), nil + } + + return endpoint, nil +} + +func formatBasicAuthHeader(username string, password string) string { + return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(username+":"+password))) +} + +func formatEnvVarKey(prefix string, pipelineName string) string { + return fmt.Sprintf("%s_%s", prefix, sanitizeEnvVarName(pipelineName)) +} + +func formatHeaderEnvVarKey(header telemetryv1alpha1.Header, pipelineName string) string { + return fmt.Sprintf("HEADER_%s_%s", sanitizeEnvVarName(pipelineName), sanitizeEnvVarName(header.Name)) +} + +func sanitizeEnvVarName(input string) string { + result := input + result = strings.ToUpper(result) + result = strings.ReplaceAll(result, ".", "_") + result = strings.ReplaceAll(result, "-", "_") + + return result +} diff --git a/internal/otelcollector/config/common/oauth2extension_config_builder.go b/internal/otelcollector/config/common/oauth2extension_config_builder.go new file mode 100644 index 0000000000..4a1916a91c --- /dev/null +++ b/internal/otelcollector/config/common/oauth2extension_config_builder.go @@ -0,0 +1,55 @@ +package common + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + + telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" +) + +// ============================================================================= +// OAuth2 EXTENSION CONFIG BUILDER +// ============================================================================= + +type OAuth2ExtensionConfigBuilder struct { + reader client.Reader + oauth2Options *telemetryv1alpha1.OAuth2Options + pipelineName string + signalType string +} + +func NewOAuth2ExtensionConfigBuilder(reader client.Reader, oauth2Options *telemetryv1alpha1.OAuth2Options, pipelineName string, signalType string) *OAuth2ExtensionConfigBuilder { + return &OAuth2ExtensionConfigBuilder{ + reader: reader, + oauth2Options: oauth2Options, + pipelineName: pipelineName, + signalType: signalType, + } +} + +func (cb *OAuth2ExtensionConfigBuilder) OAuth2ExtensionConfig(ctx context.Context) (*OAuth2Extension, EnvVars, error) { + envVars, err := makeOAuth2ExtensionEnvVars(ctx, cb.reader, cb.oauth2Options, cb.pipelineName) + if err != nil { + return nil, nil, fmt.Errorf("failed to make env vars: %w", err) + } + + extensionsConfig := makeExtensionConfig(cb.oauth2Options, cb.pipelineName) + + return extensionsConfig, envVars, nil +} + +func makeExtensionConfig(oauth2Options *telemetryv1alpha1.OAuth2Options, pipelineName string) *OAuth2Extension { + return &OAuth2Extension{ + TokenURL: fmt.Sprintf("${%s}", formatEnvVarKey(oauth2TokenURLVariablePrefix, pipelineName)), + ClientID: fmt.Sprintf("${%s}", formatEnvVarKey(oauth2ClientIDVariablePrefix, pipelineName)), + ClientSecret: fmt.Sprintf("${%s}", formatEnvVarKey(oauth2ClientSecretVariablePrefix, pipelineName)), + Scopes: oauth2Options.Scopes, + Params: oauth2Options.Params, + } +} + +func OAuth2ExtensionID(pipelineName string) string { + return fmt.Sprintf(ComponentIDOAuth2Extension, pipelineName) +} diff --git a/internal/otelcollector/config/common/oauth2extension_config_builder_test.go b/internal/otelcollector/config/common/oauth2extension_config_builder_test.go new file mode 100644 index 0000000000..dfba69f730 --- /dev/null +++ b/internal/otelcollector/config/common/oauth2extension_config_builder_test.go @@ -0,0 +1,40 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/require" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" +) + +func TestOAuth2ExtensionID(t *testing.T) { + require.Equal(t, "oauth2client/test", OAuth2ExtensionID("test")) +} + +func TestMakeExtensionConfig(t *testing.T) { + oauth2Options := &telemetryv1alpha1.OAuth2Options{ + TokenURL: telemetryv1alpha1.ValueType{Value: "token-url"}, + ClientID: telemetryv1alpha1.ValueType{Value: "client-id"}, + ClientSecret: telemetryv1alpha1.ValueType{Value: "client-secret"}, + } + + cb := NewOAuth2ExtensionConfigBuilder(fake.NewClientBuilder().Build(), oauth2Options, "test", SignalTypeTrace) + oauth2ExtensionConfig, envVars, err := cb.OAuth2ExtensionConfig(t.Context()) + require.NoError(t, err) + require.NotNil(t, envVars) + + require.NotNil(t, envVars["OAUTH2_TOKEN_URL_TEST"]) + require.Equal(t, envVars["OAUTH2_TOKEN_URL_TEST"], []byte("token-url")) + + require.NotNil(t, envVars["OAUTH2_CLIENT_ID_TEST"]) + require.Equal(t, envVars["OAUTH2_CLIENT_ID_TEST"], []byte("client-id")) + + require.NotNil(t, envVars["OAUTH2_CLIENT_SECRET_TEST"]) + require.Equal(t, envVars["OAUTH2_CLIENT_SECRET_TEST"], []byte("client-secret")) + + require.Equal(t, "${OAUTH2_TOKEN_URL_TEST}", oauth2ExtensionConfig.TokenURL) + require.Equal(t, "${OAUTH2_CLIENT_ID_TEST}", oauth2ExtensionConfig.ClientID) + require.Equal(t, "${OAUTH2_CLIENT_SECRET_TEST}", oauth2ExtensionConfig.ClientSecret) +} diff --git a/internal/otelcollector/config/common/otlpexporter_config_builder.go b/internal/otelcollector/config/common/otlpexporter_config_builder.go index 91c826d62b..4899908b24 100644 --- a/internal/otelcollector/config/common/otlpexporter_config_builder.go +++ b/internal/otelcollector/config/common/otlpexporter_config_builder.go @@ -36,19 +36,19 @@ func NewOTLPExporterConfigBuilder(reader client.Reader, otlpOutput *telemetryv1a } func (cb *OTLPExporterConfigBuilder) OTLPExporterConfig(ctx context.Context) (*OTLPExporter, EnvVars, error) { - envVars, err := makeEnvVars(ctx, cb.reader, cb.otlpOutput, cb.pipelineName) + envVars, err := makeOTLPExporterEnvVars(ctx, cb.reader, cb.otlpOutput, cb.pipelineName) if err != nil { return nil, nil, fmt.Errorf("failed to make env vars: %w", err) } - exportersConfig := makeExportersConfig(cb.otlpOutput, cb.pipelineName, envVars, cb.queueSize, cb.signalType) + exportersConfig := makeExporterConfig(cb.otlpOutput, cb.pipelineName, envVars, cb.queueSize, cb.signalType) return exportersConfig, envVars, nil } -func makeExportersConfig(otlpOutput *telemetryv1alpha1.OTLPOutput, pipelineName string, envVars map[string][]byte, queueSize int, signalType string) *OTLPExporter { +func makeExporterConfig(otlpOutput *telemetryv1alpha1.OTLPOutput, pipelineName string, envVars map[string][]byte, queueSize int, signalType string) *OTLPExporter { headers := makeHeaders(otlpOutput, pipelineName) - otlpEndpointVariable := makeOTLPEndpointVariable(pipelineName) + otlpEndpointVariable := formatEnvVarKey(otlpEndpointVariablePrefix, pipelineName) otlpEndpointValue := string(envVars[otlpEndpointVariable]) tlsConfig := makeTLSConfig(otlpOutput, otlpEndpointValue, pipelineName) @@ -88,6 +88,12 @@ func makeExportersConfig(otlpOutput *telemetryv1alpha1.OTLPOutput, pipelineName otlpExporterConfig.LogsEndpoint = fmt.Sprintf("${%s}", otlpEndpointVariable) } + if otlpOutput.Authentication != nil && otlpOutput.Authentication.OAuth2 != nil { + otlpExporterConfig.Auth = Auth{ + Authenticator: fmt.Sprintf(ComponentIDOAuth2Extension, pipelineName), + } + } + return &otlpExporterConfig } @@ -114,15 +120,15 @@ func makeTLSConfig(output *telemetryv1alpha1.OTLPOutput, otlpEndpointValue, pipe cfg.InsecureSkipVerify = output.TLS.InsecureSkipVerify if sharedtypesutils.IsValid(output.TLS.CA) { - cfg.CAPem = fmt.Sprintf("${%s}", makeTLSCaVariable(pipelineName)) + cfg.CAPem = fmt.Sprintf("${%s}", formatEnvVarKey(tlsConfigCaVariablePrefix, pipelineName)) } if sharedtypesutils.IsValid(output.TLS.Cert) { - cfg.CertPem = fmt.Sprintf("${%s}", makeTLSCertVariable(pipelineName)) + cfg.CertPem = fmt.Sprintf("${%s}", formatEnvVarKey(tlsConfigCertVariablePrefix, pipelineName)) } if sharedtypesutils.IsValid(output.TLS.Key) { - cfg.KeyPem = fmt.Sprintf("${%s}", makeTLSKeyVariable(pipelineName)) + cfg.KeyPem = fmt.Sprintf("${%s}", formatEnvVarKey(tlsConfigKeyVariablePrefix, pipelineName)) } return cfg @@ -131,13 +137,13 @@ func makeTLSConfig(output *telemetryv1alpha1.OTLPOutput, otlpEndpointValue, pipe func makeHeaders(output *telemetryv1alpha1.OTLPOutput, pipelineName string) map[string]string { headers := make(map[string]string) - if output.Authentication != nil && sharedtypesutils.IsValid(&output.Authentication.Basic.User) && sharedtypesutils.IsValid(&output.Authentication.Basic.Password) { - basicAuthHeaderVariable := makeBasicAuthHeaderVariable(pipelineName) + if isBasicAuthEnabled(output.Authentication) { + basicAuthHeaderVariable := formatEnvVarKey(basicAuthHeaderVariablePrefix, pipelineName) headers["Authorization"] = fmt.Sprintf("${%s}", basicAuthHeaderVariable) } for _, header := range output.Headers { - headers[header.Name] = fmt.Sprintf("${%s}", makeHeaderVariable(header, pipelineName)) + headers[header.Name] = fmt.Sprintf("${%s}", formatHeaderEnvVarKey(header, pipelineName)) } return headers @@ -146,3 +152,10 @@ func makeHeaders(output *telemetryv1alpha1.OTLPOutput, pipelineName string) map[ func isInsecureOutput(endpoint string) bool { return len(strings.TrimSpace(endpoint)) > 0 && strings.HasPrefix(endpoint, "http://") } + +func isBasicAuthEnabled(authOptions *telemetryv1alpha1.AuthenticationOptions) bool { + return authOptions != nil && + authOptions.Basic != nil && + sharedtypesutils.IsValid(&authOptions.Basic.User) && + sharedtypesutils.IsValid(&authOptions.Basic.Password) +} diff --git a/internal/otelcollector/config/common/otlpexporter_config_builder_test.go b/internal/otelcollector/config/common/otlpexporter_config_builder_test.go index 6b78d4e5a7..b2f5a3e9f7 100644 --- a/internal/otelcollector/config/common/otlpexporter_config_builder_test.go +++ b/internal/otelcollector/config/common/otlpexporter_config_builder_test.go @@ -22,7 +22,7 @@ func TestExorterIDDefault(t *testing.T) { require.Equal(t, "otlp/test", ExporterID("", "test")) } -func TestMakeConfig(t *testing.T) { +func TestMakeExporterConfig(t *testing.T) { output := &telemetryv1alpha1.OTLPOutput{ Endpoint: telemetryv1alpha1.ValueType{Value: "otlp-endpoint"}, } @@ -45,7 +45,7 @@ func TestMakeConfig(t *testing.T) { require.Equal(t, "300s", otlpExporterConfig.RetryOnFailure.MaxElapsedTime) } -func TestMakeConfigTraceWithPath(t *testing.T) { +func TestMakeExporterConfigTraceWithPath(t *testing.T) { output := &telemetryv1alpha1.OTLPOutput{ Endpoint: telemetryv1alpha1.ValueType{Value: "otlp-endpoint"}, Path: "/v1/test", @@ -64,7 +64,7 @@ func TestMakeConfigTraceWithPath(t *testing.T) { require.Empty(t, otlpExporterConfig.Endpoint) } -func TestMakeConfigMetricWithPath(t *testing.T) { +func TestMakeExporterConfigMetricWithPath(t *testing.T) { output := &telemetryv1alpha1.OTLPOutput{ Endpoint: telemetryv1alpha1.ValueType{Value: "otlp-endpoint"}, Path: "/v1/test", @@ -83,7 +83,7 @@ func TestMakeConfigMetricWithPath(t *testing.T) { require.Empty(t, otlpExporterConfig.Endpoint) } -func TestMakeExporterWithBasicAuth(t *testing.T) { +func TestMakeExporterConfigWithBasicAuth(t *testing.T) { output := &telemetryv1alpha1.OTLPOutput{ Endpoint: telemetryv1alpha1.ValueType{Value: "otlp-endpoint"}, Authentication: &telemetryv1alpha1.AuthenticationOptions{ @@ -108,6 +108,27 @@ func TestMakeExporterWithBasicAuth(t *testing.T) { require.Equal(t, envVars["BASIC_AUTH_HEADER_TEST"], []byte("Basic "+base64UserPass)) } +func TestMakeExporterConfigWithOAuth2(t *testing.T) { + output := &telemetryv1alpha1.OTLPOutput{ + Endpoint: telemetryv1alpha1.ValueType{Value: "otlp-endpoint"}, + Authentication: &telemetryv1alpha1.AuthenticationOptions{ + OAuth2: &telemetryv1alpha1.OAuth2Options{ + TokenURL: telemetryv1alpha1.ValueType{Value: "token-url"}, + ClientID: telemetryv1alpha1.ValueType{Value: "client-id"}, + ClientSecret: telemetryv1alpha1.ValueType{Value: "client-secret"}, + }, + }, + } + + cb := NewOTLPExporterConfigBuilder(fake.NewClientBuilder().Build(), output, "test", 512, SignalTypeTrace) + otlpExporterConfig, envVars, err := cb.OTLPExporterConfig(t.Context()) + require.NoError(t, err) + require.NotNil(t, envVars) + + require.NotNil(t, otlpExporterConfig.Auth) + require.Equal(t, otlpExporterConfig.Auth.Authenticator, "oauth2client/test") +} + func TestMakeExporterConfigWithCustomHeaders(t *testing.T) { headers := []telemetryv1alpha1.Header{ { diff --git a/internal/otelcollector/config/common/otlpexporter_env_vars.go b/internal/otelcollector/config/common/otlpexporter_env_vars.go deleted file mode 100644 index abb068855c..0000000000 --- a/internal/otelcollector/config/common/otlpexporter_env_vars.go +++ /dev/null @@ -1,221 +0,0 @@ -package common - -import ( - "bytes" - "context" - "encoding/base64" - "errors" - "fmt" - "net/url" - "path" - "strings" - - "sigs.k8s.io/controller-runtime/pkg/client" - - telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" - sharedtypesutils "github.com/kyma-project/telemetry-manager/internal/utils/sharedtypes" - "github.com/kyma-project/telemetry-manager/internal/validators/secretref" -) - -const ( - basicAuthHeaderVariablePrefix = "BASIC_AUTH_HEADER" - otlpEndpointVariablePrefix = "OTLP_ENDPOINT" - tlsConfigCertVariablePrefix = "OTLP_TLS_CERT_PEM" - tlsConfigKeyVariablePrefix = "OTLP_TLS_KEY_PEM" - tlsConfigCaVariablePrefix = "OTLP_TLS_CA_PEM" -) - -var ( - ErrValueOrSecretRefUndefined = errors.New("either value or secret key reference must be defined") -) - -func makeEnvVars(ctx context.Context, c client.Reader, output *telemetryv1alpha1.OTLPOutput, pipelineName string) (map[string][]byte, error) { - var err error - - secretData := make(map[string][]byte) - - err = makeAuthenticationEnvVar(ctx, c, secretData, output, pipelineName) - if err != nil { - return nil, err - } - - err = makeOTLPEndpointEnvVar(ctx, c, secretData, output, pipelineName) - if err != nil { - return nil, err - } - - err = makeHeaderEnvVar(ctx, c, secretData, output, pipelineName) - if err != nil { - return nil, err - } - - err = makeTLSEnvVar(ctx, c, secretData, output, pipelineName) - if err != nil { - return nil, err - } - - return secretData, nil -} - -func makeAuthenticationEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, output *telemetryv1alpha1.OTLPOutput, pipelineName string) error { - if output.Authentication != nil && sharedtypesutils.IsValid(&output.Authentication.Basic.User) && sharedtypesutils.IsValid(&output.Authentication.Basic.Password) { - username, err := ResolveValue(ctx, c, output.Authentication.Basic.User) - if err != nil { - return err - } - - password, err := ResolveValue(ctx, c, output.Authentication.Basic.Password) - if err != nil { - return err - } - - basicAuthHeader := formatBasicAuthHeader(string(username), string(password)) - basicAuthHeaderVariable := fmt.Sprintf("%s_%s", basicAuthHeaderVariablePrefix, sanitizeEnvVarName(pipelineName)) - secretData[basicAuthHeaderVariable] = []byte(basicAuthHeader) - } - - return nil -} - -func makeOTLPEndpointEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, output *telemetryv1alpha1.OTLPOutput, pipelineName string) error { - otlpEndpointVariable := makeOTLPEndpointVariable(pipelineName) - - endpointURL, err := resolveEndpointURL(ctx, c, output) - if err != nil { - return err - } - - secretData[otlpEndpointVariable] = endpointURL - - return err -} - -func makeHeaderEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, output *telemetryv1alpha1.OTLPOutput, pipelineName string) error { - for _, header := range output.Headers { - key := makeHeaderVariable(header, pipelineName) - - value, err := ResolveValue(ctx, c, header.ValueType) - if err != nil { - return err - } - - secretData[key] = prefixHeaderValue(header, value) - } - - return nil -} - -func makeTLSEnvVar(ctx context.Context, c client.Reader, secretData map[string][]byte, output *telemetryv1alpha1.OTLPOutput, pipelineName string) error { - if output.TLS != nil { - if sharedtypesutils.IsValid(output.TLS.CA) { - ca, err := ResolveValue(ctx, c, *output.TLS.CA) - if err != nil { - return err - } - - tlsConfigCaVariable := makeTLSCaVariable(pipelineName) - secretData[tlsConfigCaVariable] = ca - } - - if sharedtypesutils.IsValid(output.TLS.Cert) && sharedtypesutils.IsValid(output.TLS.Key) { - cert, err := ResolveValue(ctx, c, *output.TLS.Cert) - if err != nil { - return err - } - - key, err := ResolveValue(ctx, c, *output.TLS.Key) - if err != nil { - return err - } - - // Make a best effort replacement of linebreaks in cert/key if present. - sanitizedCert := bytes.ReplaceAll(cert, []byte("\\n"), []byte("\n")) - sanitizedKey := bytes.ReplaceAll(key, []byte("\\n"), []byte("\n")) - - tlsConfigCertVariable := makeTLSCertVariable(pipelineName) - secretData[tlsConfigCertVariable] = sanitizedCert - - tlsConfigKeyVariable := makeTLSKeyVariable(pipelineName) - secretData[tlsConfigKeyVariable] = sanitizedKey - } - } - - return nil -} - -func prefixHeaderValue(header telemetryv1alpha1.Header, value []byte) []byte { - if len(strings.TrimSpace(header.Prefix)) > 0 { - return fmt.Appendf(nil, "%s %s", strings.TrimSpace(header.Prefix), string(value)) - } - - return value -} - -func resolveEndpointURL(ctx context.Context, c client.Reader, output *telemetryv1alpha1.OTLPOutput) ([]byte, error) { - endpoint, err := ResolveValue(ctx, c, output.Endpoint) - if err != nil { - return nil, err - } - - if len(output.Path) > 0 { - u, err := url.Parse(string(endpoint)) - if err != nil { - return nil, err - } - - u.Path = path.Join(u.Path, output.Path) - - return []byte(u.String()), nil - } - - return endpoint, nil -} - -func formatBasicAuthHeader(username string, password string) string { - return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(username+":"+password))) -} - -func ResolveValue(ctx context.Context, c client.Reader, value telemetryv1alpha1.ValueType) ([]byte, error) { - if value.Value != "" { - return []byte(value.Value), nil - } - - if value.ValueFrom.SecretKeyRef != nil { - return secretref.GetValue(ctx, c, *value.ValueFrom.SecretKeyRef) - } - - return nil, ErrValueOrSecretRefUndefined -} - -func makeOTLPEndpointVariable(pipelineName string) string { - return fmt.Sprintf("%s_%s", otlpEndpointVariablePrefix, sanitizeEnvVarName(pipelineName)) -} - -func makeBasicAuthHeaderVariable(pipelineName string) string { - return fmt.Sprintf("%s_%s", basicAuthHeaderVariablePrefix, sanitizeEnvVarName(pipelineName)) -} - -func makeHeaderVariable(header telemetryv1alpha1.Header, pipelineName string) string { - return fmt.Sprintf("HEADER_%s_%s", sanitizeEnvVarName(pipelineName), sanitizeEnvVarName(header.Name)) -} - -func makeTLSCertVariable(pipelineName string) string { - return fmt.Sprintf("%s_%s", tlsConfigCertVariablePrefix, sanitizeEnvVarName(pipelineName)) -} - -func makeTLSKeyVariable(pipelineName string) string { - return fmt.Sprintf("%s_%s", tlsConfigKeyVariablePrefix, sanitizeEnvVarName(pipelineName)) -} - -func makeTLSCaVariable(pipelineName string) string { - return fmt.Sprintf("%s_%s", tlsConfigCaVariablePrefix, sanitizeEnvVarName(pipelineName)) -} - -func sanitizeEnvVarName(input string) string { - result := input - result = strings.ToUpper(result) - result = strings.ReplaceAll(result, ".", "_") - result = strings.ReplaceAll(result, "-", "_") - - return result -} diff --git a/internal/otelcollector/config/common/types.go b/internal/otelcollector/config/common/types.go index 5cb2ff17cb..0fa6ce64c6 100644 --- a/internal/otelcollector/config/common/types.go +++ b/internal/otelcollector/config/common/types.go @@ -19,17 +19,25 @@ type Config struct { // EXTENSION TYPES // ============================================================================= -type K8sLeaderElector struct { +type K8sLeaderElectorExtension struct { AuthType string `yaml:"auth_type,omitempty"` LeaseName string `yaml:"lease_name,omitempty"` LeaseNamespace string `yaml:"lease_namespace,omitempty"` } -type FileStorage struct { +type FileStorageExtension struct { CreateDirectory bool `yaml:"create_directory,omitempty"` Directory string `yaml:"directory,omitempty"` } +type OAuth2Extension struct { + TokenURL string `yaml:"token_url"` + ClientID string `yaml:"client_id"` + ClientSecret string `yaml:"client_secret"` + Scopes []string `yaml:"scopes,omitempty"` + Params map[string]string `yaml:"endpoint_params,omitempty"` +} + // ============================================================================= // SERVICE TYPES // ============================================================================= @@ -107,6 +115,7 @@ type OTLPExporter struct { TLS TLS `yaml:"tls,omitempty"` SendingQueue SendingQueue `yaml:"sending_queue,omitempty"` RetryOnFailure RetryOnFailure `yaml:"retry_on_failure,omitempty"` + Auth Auth `yaml:"auth,omitempty"` } type TLS struct { @@ -129,6 +138,10 @@ type RetryOnFailure struct { MaxElapsedTime string `yaml:"max_elapsed_time"` } +type Auth struct { + Authenticator string `yaml:"authenticator"` +} + // ============================================================================= // PROCESSOR TYPES // ============================================================================= diff --git a/internal/otelcollector/config/logagent/config_builder.go b/internal/otelcollector/config/logagent/config_builder.go index b35058b8e7..1985a1f895 100644 --- a/internal/otelcollector/config/logagent/config_builder.go +++ b/internal/otelcollector/config/logagent/config_builder.go @@ -35,14 +35,21 @@ type BuildOptions struct { func (b *Builder) Build(ctx context.Context, pipelines []telemetryv1alpha1.LogPipeline, opts BuildOptions) (*common.Config, common.EnvVars, error) { b.Config = common.NewConfig() - b.AddExtension(common.ComponentIDFileStorageExtension, &common.FileStorage{ + b.AddExtension(common.ComponentIDFileStorageExtension, &common.FileStorageExtension{ CreateDirectory: true, Directory: filepath.Join(otelcollector.CheckpointVolumePath, checkpointVolumePathSubdir), - }) + }, nil) b.EnvVars = make(common.EnvVars) for _, pipeline := range pipelines { pipelineID := formatLogServicePipelineID(&pipeline) + + if shouldEnableOAuth2(&pipeline) { + if err := b.addOAuth2Extension(ctx, &pipeline); err != nil { + return nil, nil, err + } + } + if err := b.AddServicePipeline(ctx, &pipeline, pipelineID, b.addFileLogReceiver(), b.addMemoryLimiterProcessor(), @@ -183,6 +190,28 @@ func (b *Builder) addOTLPExporter() buildComponentFunc { ) } +func (b *Builder) addOAuth2Extension(ctx context.Context, pipeline *telemetryv1alpha1.LogPipeline) error { + oauth2ExtensionID := common.OAuth2ExtensionID(pipeline.Name) + + oauth2ExtensionConfig, oauth2ExtensionEnvVars, err := common.NewOAuth2ExtensionConfigBuilder( + b.Reader, + pipeline.Spec.Output.OTLP.Authentication.OAuth2, + pipeline.Name, + common.SignalTypeTrace, + ).OAuth2ExtensionConfig(ctx) + if err != nil { + return fmt.Errorf("failed to build OAuth2 extension for pipeline %s: %w", pipeline.Name, err) + } + + b.AddExtension(oauth2ExtensionID, oauth2ExtensionConfig, oauth2ExtensionEnvVars) + + return nil +} + +func shouldEnableOAuth2(tp *telemetryv1alpha1.LogPipeline) bool { + return tp.Spec.Output.OTLP.Authentication != nil && tp.Spec.Output.OTLP.Authentication.OAuth2 != nil +} + func formatLogServicePipelineID(lp *telemetryv1alpha1.LogPipeline) string { return fmt.Sprintf("logs/%s", lp.Name) } diff --git a/internal/otelcollector/config/logagent/config_builder_test.go b/internal/otelcollector/config/logagent/config_builder_test.go index 6f55325b2b..91cda0cdc3 100644 --- a/internal/otelcollector/config/logagent/config_builder_test.go +++ b/internal/otelcollector/config/logagent/config_builder_test.go @@ -142,6 +142,24 @@ func TestBuildConfig(t *testing.T) { }, goldenFileName: "user-defined-transform-filter.yaml", }, + { + name: "pipeline using OAuth2 authentication", + pipelines: []telemetryv1alpha1.LogPipeline{ + testutils.NewLogPipelineBuilder(). + WithName("test"). + WithApplicationInput(true). + WithOTLPOutput( + testutils.OTLPProtocol("http"), + ). + WithOAuth2( + testutils.OAuth2ClientID("client-id"), + testutils.OAuth2ClientSecret("client-secret"), + testutils.OAuth2TokenURL("https://auth.example.com/oauth2/token"), + testutils.OAuth2Scopes([]string{"logs"}), + ).Build(), + }, + goldenFileName: "oauth2-authentication.yaml", + }, } buildOptions := BuildOptions{ diff --git a/internal/otelcollector/config/logagent/testdata/oauth2-authentication.yaml b/internal/otelcollector/config/logagent/testdata/oauth2-authentication.yaml new file mode 100644 index 0000000000..8ee621856d --- /dev/null +++ b/internal/otelcollector/config/logagent/testdata/oauth2-authentication.yaml @@ -0,0 +1,243 @@ +extensions: + file_storage: + create_directory: true + directory: /tmp/telemetry-log-agent/file-log-receiver + health_check: + endpoint: ${MY_POD_IP}:13133 + oauth2client/test: + token_url: ${OAUTH2_TOKEN_URL_TEST} + client_id: ${OAUTH2_CLIENT_ID_TEST} + client_secret: ${OAUTH2_CLIENT_SECRET_TEST} + scopes: + - logs + pprof: + endpoint: 127.0.0.1:1777 +service: + pipelines: + logs/test: + receivers: + - filelog/test + processors: + - memory_limiter + - transform/set-instrumentation-scope-runtime + - k8sattributes + - resource/insert-cluster-attributes + - service_enrichment + - resource/drop-kyma-attributes + exporters: + - otlphttp/test + telemetry: + metrics: + readers: + - pull: + exporter: + prometheus: + host: ${MY_POD_IP} + port: 8888 + logs: + level: info + encoding: json + extensions: + - health_check + - pprof + - file_storage + - oauth2client/test +receivers: + filelog/test: + exclude: + - /var/log/pods/kyma-system_telemetry-fluent-bit-*/fluent-bit/*.log + - /var/log/pods/kyma-system_telemetry-log-agent-*/collector/*.log + - /var/log/pods/kyma-system_*system-logs-agent-*/collector/*.log + - /var/log/pods/kyma-system_*system-logs-collector-*/collector/*.log + - /var/log/pods/kyma-system_*/*/*.log + - /var/log/pods/kube-system_*/*/*.log + - /var/log/pods/istio-system_*/*/*.log + include: + - /var/log/pods/*_*/*/*.log + include_file_name: false + include_file_path: true + start_at: beginning + storage: file_storage + retry_on_failure: + enabled: true + initial_interval: 5s + max_interval: 30s + max_elapsed_time: 300s + operators: + - id: containerd-parser + type: container + add_metadata_from_file_path: true + format: containerd + - id: move-to-log-stream + type: move + from: attributes["stream"] + to: attributes["log.iostream"] + if: attributes["stream"] != nil + - id: drop-attribute-log-tag + type: remove + field: attributes["logtag"] + - id: body-router + type: router + routes: + - expr: body matches '^{.*}$' + output: json-parser + default: noop + - id: json-parser + type: json_parser + parse_from: body + parse_to: attributes + - id: remove-body + type: remove + field: body + - id: move-message-to-body + type: move + from: attributes["message"] + to: body + if: attributes["message"] != nil + - id: move-msg-to-body + type: move + from: attributes["msg"] + to: body + if: attributes["msg"] != nil + - id: parse-level + type: severity_parser + if: attributes["level"] != nil + parse_from: attributes["level"] + - id: remove-level + type: remove + if: attributes["level"] != nil + field: attributes["level"] + - id: parse-log-level + type: severity_parser + if: attributes["log.level"] != nil + parse_from: attributes["log.level"] + - id: remove-log-level + type: remove + if: attributes["log.level"] != nil + field: attributes["log.level"] + - id: trace-router + type: router + routes: + - expr: attributes["trace_id"] != nil + output: trace-parser + - expr: attributes["traceparent"] != nil and attributes["traceparent"] matches '^[0-9a-f]{2}-(?P[0-9a-f]{32})-(?P[0-9a-f]{16})-(?P[0-9a-f]{2})$' + output: trace-parent-parser + default: noop + - id: trace-parent-parser + type: regex_parser + parse_from: attributes["traceparent"] + regex: ^[0-9a-f]{2}-(?P[0-9a-f]{32})-(?P[0-9a-f]{16})-(?P[0-9a-f]{2})$ + trace: + trace_id: + parse_from: attributes["trace_id"] + span_id: + parse_from: attributes["span_id"] + trace_flags: + parse_from: attributes["trace_flags"] + output: remove-trace-parent + - id: trace-parser + type: trace_parser + trace_id: + parse_from: attributes["trace_id"] + span_id: + parse_from: attributes["span_id"] + trace_flags: + parse_from: attributes["trace_flags"] + output: remove-trace-id + - id: remove-trace-parent + type: remove + field: attributes["traceparent"] + - id: remove-trace-id + type: remove + if: attributes["trace_id"] != nil + field: attributes["trace_id"] + - id: remove-span-id + type: remove + if: attributes["span_id"] != nil + field: attributes["span_id"] + - id: remove-trace-flags + type: remove + if: attributes["trace_flags"] != nil + field: attributes["trace_flags"] + - id: noop + type: noop +processors: + k8sattributes: + auth_type: serviceAccount + passthrough: false + extract: + metadata: + - k8s.pod.name + - k8s.node.name + - k8s.namespace.name + - k8s.deployment.name + - k8s.statefulset.name + - k8s.daemonset.name + - k8s.cronjob.name + - k8s.job.name + labels: + - from: pod + key: app.kubernetes.io/name + tag_name: kyma.kubernetes_io_app_name + - from: pod + key: app + tag_name: kyma.app_name + - from: node + key: topology.kubernetes.io/region + tag_name: cloud.region + - from: node + key: topology.kubernetes.io/zone + tag_name: cloud.availability_zone + - from: node + key: node.kubernetes.io/instance-type + tag_name: host.type + - from: node + key: kubernetes.io/arch + tag_name: host.arch + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: resource_attribute + name: k8s.pod.uid + - sources: + - from: connection + memory_limiter: + check_interval: 5s + limit_percentage: 80 + spike_limit_percentage: 25 + resource/drop-kyma-attributes: + attributes: + - action: delete + pattern: kyma.* + resource/insert-cluster-attributes: + attributes: + - action: insert + key: k8s.cluster.name + value: test-cluster + - action: insert + key: k8s.cluster.uid + - action: insert + key: cloud.provider + value: azure + service_enrichment: + resource_attributes: + - kyma.kubernetes_io_app_name + - kyma.app_name + transform/set-instrumentation-scope-runtime: + error_mode: ignore + log_statements: + - statements: + - set(scope.version, "main") + - set(scope.name, "io.kyma-project.telemetry/runtime") +exporters: + otlphttp/test: + endpoint: ${OTLP_ENDPOINT_TEST} + retry_on_failure: + enabled: true + initial_interval: 5s + max_interval: 30s + max_elapsed_time: 300s + auth: + authenticator: oauth2client/test diff --git a/internal/otelcollector/config/loggateway/config_builder.go b/internal/otelcollector/config/loggateway/config_builder.go index 670c8aef6d..032803351b 100644 --- a/internal/otelcollector/config/loggateway/config_builder.go +++ b/internal/otelcollector/config/loggateway/config_builder.go @@ -38,6 +38,13 @@ func (b *Builder) Build(ctx context.Context, pipelines []telemetryv1alpha1.LogPi for _, pipeline := range pipelines { pipelineID := formatLogServicePipelineID(&pipeline) + + if shouldEnableOAuth2(&pipeline) { + if err := b.addOAuth2Extension(ctx, &pipeline); err != nil { + return nil, nil, err + } + } + if err := b.AddServicePipeline(ctx, &pipeline, pipelineID, b.addOTLPReceiver(), b.addMemoryLimiterProcessor(), @@ -251,6 +258,24 @@ func (b *Builder) addOTLPExporter(queueSize int) buildComponentFunc { ) } +func (b *Builder) addOAuth2Extension(ctx context.Context, pipeline *telemetryv1alpha1.LogPipeline) error { + oauth2ExtensionID := common.OAuth2ExtensionID(pipeline.Name) + + oauth2ExtensionConfig, oauth2ExtensionEnvVars, err := common.NewOAuth2ExtensionConfigBuilder( + b.Reader, + pipeline.Spec.Output.OTLP.Authentication.OAuth2, + pipeline.Name, + common.SignalTypeTrace, + ).OAuth2ExtensionConfig(ctx) + if err != nil { + return fmt.Errorf("failed to build OAuth2 extension for pipeline %s: %w", pipeline.Name, err) + } + + b.AddExtension(oauth2ExtensionID, oauth2ExtensionConfig, oauth2ExtensionEnvVars) + + return nil +} + // Helper functions func namespaceFilterProcessorConfig(namespaceSelector *telemetryv1alpha1.NamespaceSelector) *FilterProcessor { @@ -310,6 +335,10 @@ func shouldFilterByNamespace(namespaceSelector *telemetryv1alpha1.NamespaceSelec return namespaceSelector != nil && (len(namespaceSelector.Include) > 0 || len(namespaceSelector.Exclude) > 0) } +func shouldEnableOAuth2(tp *telemetryv1alpha1.LogPipeline) bool { + return tp.Spec.Output.OTLP.Authentication != nil && tp.Spec.Output.OTLP.Authentication.OAuth2 != nil +} + func formatLogServicePipelineID(lp *telemetryv1alpha1.LogPipeline) string { return fmt.Sprintf("logs/%s", lp.Name) } diff --git a/internal/otelcollector/config/loggateway/config_builder_test.go b/internal/otelcollector/config/loggateway/config_builder_test.go index 5c7acfc81d..839b7b3215 100644 --- a/internal/otelcollector/config/loggateway/config_builder_test.go +++ b/internal/otelcollector/config/loggateway/config_builder_test.go @@ -145,6 +145,24 @@ func TestBuildConfig(t *testing.T) { }, goldenFileName: "user-defined-transform-filter.yaml", }, + { + name: "pipeline using OAuth2 authentication", + pipelines: []telemetryv1alpha1.LogPipeline{ + testutils.NewLogPipelineBuilder(). + WithName("test"). + WithApplicationInput(true). + WithOTLPOutput( + testutils.OTLPProtocol("http"), + ). + WithOAuth2( + testutils.OAuth2ClientID("client-id"), + testutils.OAuth2ClientSecret("client-secret"), + testutils.OAuth2TokenURL("https://auth.example.com/oauth2/token"), + testutils.OAuth2Scopes([]string{"logs"}), + ).Build(), + }, + goldenFileName: "oauth2-authentication.yaml", + }, } buildOptions := BuildOptions{ diff --git a/internal/otelcollector/config/loggateway/testdata/oauth2-authentication.yaml b/internal/otelcollector/config/loggateway/testdata/oauth2-authentication.yaml new file mode 100644 index 0000000000..7867ec39eb --- /dev/null +++ b/internal/otelcollector/config/loggateway/testdata/oauth2-authentication.yaml @@ -0,0 +1,141 @@ +extensions: + health_check: + endpoint: ${MY_POD_IP}:13133 + oauth2client/test: + token_url: ${OAUTH2_TOKEN_URL_TEST} + client_id: ${OAUTH2_CLIENT_ID_TEST} + client_secret: ${OAUTH2_CLIENT_SECRET_TEST} + scopes: + - logs + pprof: + endpoint: 127.0.0.1:1777 +service: + pipelines: + logs/test: + receivers: + - otlp + processors: + - memory_limiter + - transform/set-observed-time-if-zero + - k8sattributes + - istio_noise_filter + - resource/insert-cluster-attributes + - service_enrichment + - resource/drop-kyma-attributes + - istio_enrichment + - batch + exporters: + - otlphttp/test + telemetry: + metrics: + readers: + - pull: + exporter: + prometheus: + host: ${MY_POD_IP} + port: 8888 + logs: + level: info + encoding: json + extensions: + - health_check + - pprof + - oauth2client/test +receivers: + otlp: + protocols: + http: + endpoint: ${MY_POD_IP}:4318 + grpc: + endpoint: ${MY_POD_IP}:4317 +processors: + batch: + send_batch_size: 512 + timeout: 10s + send_batch_max_size: 512 + istio_enrichment: + scope_version: 1.0.0 + istio_noise_filter: {} + k8sattributes: + auth_type: serviceAccount + passthrough: false + extract: + metadata: + - k8s.pod.name + - k8s.node.name + - k8s.namespace.name + - k8s.deployment.name + - k8s.statefulset.name + - k8s.daemonset.name + - k8s.cronjob.name + - k8s.job.name + labels: + - from: pod + key: app.kubernetes.io/name + tag_name: kyma.kubernetes_io_app_name + - from: pod + key: app + tag_name: kyma.app_name + - from: node + key: topology.kubernetes.io/region + tag_name: cloud.region + - from: node + key: topology.kubernetes.io/zone + tag_name: cloud.availability_zone + - from: node + key: node.kubernetes.io/instance-type + tag_name: host.type + - from: node + key: kubernetes.io/arch + tag_name: host.arch + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: resource_attribute + name: k8s.pod.uid + - sources: + - from: connection + memory_limiter: + check_interval: 1s + limit_percentage: 75 + spike_limit_percentage: 15 + resource/drop-kyma-attributes: + attributes: + - action: delete + pattern: kyma.* + resource/insert-cluster-attributes: + attributes: + - action: insert + key: k8s.cluster.name + value: ${KUBERNETES_SERVICE_HOST} + - action: insert + key: k8s.cluster.uid + - action: insert + key: cloud.provider + value: test-cloud-provider + service_enrichment: + resource_attributes: + - kyma.kubernetes_io_app_name + - kyma.app_name + transform/set-observed-time-if-zero: + error_mode: ignore + log_statements: + - statements: + - set(log.observed_time, Now()) + conditions: + - log.observed_time_unix_nano == 0 +exporters: + otlphttp/test: + endpoint: ${OTLP_ENDPOINT_TEST} + sending_queue: + enabled: true + queue_size: 256 + retry_on_failure: + enabled: true + initial_interval: 5s + max_interval: 30s + max_elapsed_time: 300s + auth: + authenticator: oauth2client/test diff --git a/internal/otelcollector/config/metricagent/config_builder.go b/internal/otelcollector/config/metricagent/config_builder.go index 48186f9be6..9d1240510b 100644 --- a/internal/otelcollector/config/metricagent/config_builder.go +++ b/internal/otelcollector/config/metricagent/config_builder.go @@ -67,11 +67,12 @@ func (b *Builder) Build(ctx context.Context, pipelines []telemetryv1alpha1.Metri b.Config = common.NewConfig() b.AddExtension(common.ComponentIDK8sLeaderElectorExtension, - common.K8sLeaderElector{ + common.K8sLeaderElectorExtension{ AuthType: "serviceAccount", LeaseName: common.K8sLeaderElectorK8sCluster, LeaseNamespace: opts.AgentNamespace, }, + nil, ) b.EnvVars = make(common.EnvVars) @@ -169,6 +170,12 @@ func (b *Builder) Build(ctx context.Context, pipelines []telemetryv1alpha1.Metri istioInputEnabled := metricpipelineutils.IsIstioInputEnabled(pipeline.Spec.Input) queueSize := common.BatchingMaxQueueSize / len(pipelines) + if shouldEnableOAuth2(&pipeline) { + if err := b.addOAuth2Extension(ctx, &pipeline); err != nil { + return nil, nil, err + } + } + if err := b.AddServicePipeline(ctx, &pipeline, outputPipelineID, // Receivers // Metrics are received from either the enrichment pipeline or directly from input pipelines, @@ -874,6 +881,26 @@ func inputRoutingConnectorConfig(outputPipelineIDs []string) common.RoutingConne } } +// Authentication extensions + +func (b *Builder) addOAuth2Extension(ctx context.Context, pipeline *telemetryv1alpha1.MetricPipeline) error { + oauth2ExtensionID := common.OAuth2ExtensionID(pipeline.Name) + + oauth2ExtensionConfig, oauth2ExtensionEnvVars, err := common.NewOAuth2ExtensionConfigBuilder( + b.Reader, + pipeline.Spec.Output.OTLP.Authentication.OAuth2, + pipeline.Name, + common.SignalTypeTrace, + ).OAuth2ExtensionConfig(ctx) + if err != nil { + return fmt.Errorf("failed to build OAuth2 extension for pipeline %s: %w", pipeline.Name, err) + } + + b.AddExtension(oauth2ExtensionID, oauth2ExtensionConfig, oauth2ExtensionEnvVars) + + return nil +} + // Helper functions for formatting IDs func formatOutputPipelineIDs(pipelines []telemetryv1alpha1.MetricPipeline) []string { @@ -1076,6 +1103,10 @@ func shouldFilterByNamespace(namespaceSelector *telemetryv1alpha1.NamespaceSelec return namespaceSelector != nil && (len(namespaceSelector.Include) > 0 || len(namespaceSelector.Exclude) > 0) } +func shouldEnableOAuth2(tp *telemetryv1alpha1.MetricPipeline) bool { + return tp.Spec.Output.OTLP.Authentication != nil && tp.Spec.Output.OTLP.Authentication.OAuth2 != nil +} + // Processor configuration functions (merged from processors.go) func dropServiceNameProcessorConfig() *common.ResourceProcessor { diff --git a/internal/otelcollector/config/metricagent/config_builder_test.go b/internal/otelcollector/config/metricagent/config_builder_test.go index 3df1645e60..066852acbf 100644 --- a/internal/otelcollector/config/metricagent/config_builder_test.go +++ b/internal/otelcollector/config/metricagent/config_builder_test.go @@ -389,6 +389,24 @@ func TestBuildConfig(t *testing.T) { }).Build(), }, }, + { + name: "pipeline using OAuth2 authentication", + pipelines: []telemetryv1alpha1.MetricPipeline{ + testutils.NewMetricPipelineBuilder(). + WithName("test"). + WithOTLPInput(true). + WithOTLPOutput( + testutils.OTLPProtocol("http"), + ). + WithOAuth2( + testutils.OAuth2ClientID("client-id"), + testutils.OAuth2ClientSecret("client-secret"), + testutils.OAuth2TokenURL("https://auth.example.com/oauth2/token"), + testutils.OAuth2Scopes([]string{"metrics"}), + ).Build(), + }, + goldenFileName: "oauth2-authentication.yaml", + }, } for _, tt := range tests { diff --git a/internal/otelcollector/config/metricagent/testdata/oauth2-authentication.yaml b/internal/otelcollector/config/metricagent/testdata/oauth2-authentication.yaml new file mode 100644 index 0000000000..fbf446ee1b --- /dev/null +++ b/internal/otelcollector/config/metricagent/testdata/oauth2-authentication.yaml @@ -0,0 +1,139 @@ +extensions: + health_check: + endpoint: ${MY_POD_IP}:13133 + k8s_leader_elector: + auth_type: serviceAccount + lease_name: telemetry-metric-agent-k8scluster + oauth2client/test: + token_url: ${OAUTH2_TOKEN_URL_TEST} + client_id: ${OAUTH2_CLIENT_ID_TEST} + client_secret: ${OAUTH2_CLIENT_SECRET_TEST} + scopes: + - metrics + pprof: + endpoint: 127.0.0.1:1777 +service: + pipelines: + metrics/enrichment-conditional: + receivers: [] + processors: + - k8sattributes + - service_enrichment + exporters: + - routing/enrichment + metrics/output-test: + receivers: [] + processors: + - filter/drop-envoy-metrics-if-disabled + - resource/insert-cluster-attributes + - resource/drop-skip-enrichment-attribute + - resource/drop-kyma-attributes + - batch + exporters: + - otlphttp/test + telemetry: + metrics: + readers: + - pull: + exporter: + prometheus: + host: ${MY_POD_IP} + port: 8888 + logs: + level: info + encoding: json + extensions: + - health_check + - pprof + - k8s_leader_elector + - oauth2client/test +receivers: {} +processors: + batch: + send_batch_size: 1024 + timeout: 10s + send_batch_max_size: 1024 + filter/drop-envoy-metrics-if-disabled: + error_mode: ignore + metrics: + metric: + - IsMatch(name, "^envoy_.*") and resource.attributes["kyma.input.name"] == "istio" + k8sattributes: + auth_type: serviceAccount + passthrough: false + extract: + metadata: + - k8s.pod.name + - k8s.node.name + - k8s.namespace.name + - k8s.deployment.name + - k8s.statefulset.name + - k8s.daemonset.name + - k8s.cronjob.name + - k8s.job.name + labels: + - from: pod + key: app.kubernetes.io/name + tag_name: kyma.kubernetes_io_app_name + - from: pod + key: app + tag_name: kyma.app_name + - from: node + key: topology.kubernetes.io/region + tag_name: cloud.region + - from: node + key: topology.kubernetes.io/zone + tag_name: cloud.availability_zone + - from: node + key: node.kubernetes.io/instance-type + tag_name: host.type + - from: node + key: kubernetes.io/arch + tag_name: host.arch + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: resource_attribute + name: k8s.pod.uid + - sources: + - from: connection + resource/drop-kyma-attributes: + attributes: + - action: delete + pattern: kyma.* + resource/drop-skip-enrichment-attribute: + attributes: + - action: delete + key: io.kyma-project.telemetry.skip_enrichment + resource/insert-cluster-attributes: + attributes: + - action: insert + key: k8s.cluster.name + - action: insert + key: k8s.cluster.uid + service_enrichment: + resource_attributes: + - kyma.kubernetes_io_app_name + - kyma.app_name +exporters: + otlphttp/test: + endpoint: ${OTLP_ENDPOINT_TEST} + tls: + insecure: true + sending_queue: + enabled: true + queue_size: 256 + retry_on_failure: + enabled: true + initial_interval: 5s + max_interval: 30s + max_elapsed_time: 300s + auth: + authenticator: oauth2client/test +connectors: + routing/enrichment: + default_pipelines: [] + error_mode: ignore + table: [] diff --git a/internal/otelcollector/config/metricgateway/config_builder.go b/internal/otelcollector/config/metricgateway/config_builder.go index 0f0f088595..096d9e2cf2 100644 --- a/internal/otelcollector/config/metricgateway/config_builder.go +++ b/internal/otelcollector/config/metricgateway/config_builder.go @@ -33,11 +33,12 @@ type BuildOptions struct { func (b *Builder) Build(ctx context.Context, pipelines []telemetryv1alpha1.MetricPipeline, opts BuildOptions) (*common.Config, common.EnvVars, error) { b.Config = common.NewConfig() b.AddExtension(common.ComponentIDK8sLeaderElectorExtension, - common.K8sLeaderElector{ + common.K8sLeaderElectorExtension{ AuthType: "serviceAccount", LeaseName: common.K8sLeaderElectorKymaStats, LeaseNamespace: opts.GatewayNamespace, }, + nil, ) b.EnvVars = make(common.EnvVars) @@ -74,6 +75,13 @@ func (b *Builder) Build(ctx context.Context, pipelines []telemetryv1alpha1.Metri for _, pipeline := range pipelines { outputPipelineID := formatOutputServicePipelineID(&pipeline) + + if shouldEnableOAuth2(&pipeline) { + if err := b.addOAuth2Extension(ctx, &pipeline); err != nil { + return nil, nil, err + } + } + if err := b.AddServicePipeline(ctx, &pipeline, outputPipelineID, b.addReceiverForEnrichmentForwarder(), // Input source filters if otlp is disabled @@ -346,6 +354,26 @@ func (b *Builder) addOTLPExporter(queueSize int) buildComponentFunc { ) } +// Authentication extensions + +func (b *Builder) addOAuth2Extension(ctx context.Context, pipeline *telemetryv1alpha1.MetricPipeline) error { + oauth2ExtensionID := common.OAuth2ExtensionID(pipeline.Name) + + oauth2ExtensionConfig, oauth2ExtensionEnvVars, err := common.NewOAuth2ExtensionConfigBuilder( + b.Reader, + pipeline.Spec.Output.OTLP.Authentication.OAuth2, + pipeline.Name, + common.SignalTypeTrace, + ).OAuth2ExtensionConfig(ctx) + if err != nil { + return fmt.Errorf("failed to build OAuth2 extension for pipeline %s: %w", pipeline.Name, err) + } + + b.AddExtension(oauth2ExtensionID, oauth2ExtensionConfig, oauth2ExtensionEnvVars) + + return nil +} + // Helper functions func shouldFilterByNamespace(namespaceSelector *telemetryv1alpha1.NamespaceSelector) bool { @@ -402,6 +430,11 @@ func formatUserDefinedTransformProcessorID(mp *telemetryv1alpha1.MetricPipeline) func formatUserDefinedFilterProcessorID(mp *telemetryv1alpha1.MetricPipeline) string { return fmt.Sprintf(common.ComponentIDUserDefinedFilterProcessor, mp.Name) } + func formatOutputServicePipelineID(mp *telemetryv1alpha1.MetricPipeline) string { return fmt.Sprintf("metrics/%s-output", mp.Name) } + +func shouldEnableOAuth2(tp *telemetryv1alpha1.MetricPipeline) bool { + return tp.Spec.Output.OTLP.Authentication != nil && tp.Spec.Output.OTLP.Authentication.OAuth2 != nil +} diff --git a/internal/otelcollector/config/metricgateway/config_builder_test.go b/internal/otelcollector/config/metricgateway/config_builder_test.go index 9283e4ce78..ce4a04f098 100644 --- a/internal/otelcollector/config/metricgateway/config_builder_test.go +++ b/internal/otelcollector/config/metricgateway/config_builder_test.go @@ -187,6 +187,24 @@ func TestMakeConfig(t *testing.T) { }).Build(), }, }, + { + name: "pipeline using OAuth2 authentication", + pipelines: []telemetryv1alpha1.MetricPipeline{ + testutils.NewMetricPipelineBuilder(). + WithName("test"). + WithOTLPInput(true). + WithOTLPOutput( + testutils.OTLPProtocol("http"), + ). + WithOAuth2( + testutils.OAuth2ClientID("client-id"), + testutils.OAuth2ClientSecret("client-secret"), + testutils.OAuth2TokenURL("https://auth.example.com/oauth2/token"), + testutils.OAuth2Scopes([]string{"metrics"}), + ).Build(), + }, + goldenFileName: "oauth2-authentication.yaml", + }, } buildOptions := BuildOptions{ diff --git a/internal/otelcollector/config/metricgateway/testdata/oauth2-authentication.yaml b/internal/otelcollector/config/metricgateway/testdata/oauth2-authentication.yaml new file mode 100644 index 0000000000..5141ec7af9 --- /dev/null +++ b/internal/otelcollector/config/metricgateway/testdata/oauth2-authentication.yaml @@ -0,0 +1,191 @@ +extensions: + health_check: + endpoint: ${MY_POD_IP}:13133 + k8s_leader_elector: + auth_type: serviceAccount + lease_name: telemetry-metric-gateway-kymastats + oauth2client/test: + token_url: ${OAUTH2_TOKEN_URL_TEST} + client_id: ${OAUTH2_CLIENT_ID_TEST} + client_secret: ${OAUTH2_CLIENT_SECRET_TEST} + scopes: + - metrics + pprof: + endpoint: 127.0.0.1:1777 +service: + pipelines: + metrics/enrichment: + receivers: + - forward/input + processors: + - memory_limiter + - transform/set-instrumentation-scope-kyma + - k8sattributes + - service_enrichment + - resource/insert-cluster-attributes + exporters: + - forward/enrichment + metrics/input-kyma-stats: + receivers: + - kymastats + processors: + - resource/set-kyma-input-name-kyma + exporters: + - forward/input + metrics/input-otlp: + receivers: + - otlp + processors: + - resource/set-kyma-input-name-otlp + exporters: + - forward/input + metrics/test-output: + receivers: + - forward/enrichment + processors: + - resource/drop-kyma-attributes + - batch + exporters: + - otlphttp/test + telemetry: + metrics: + readers: + - pull: + exporter: + prometheus: + host: ${MY_POD_IP} + port: 8888 + logs: + level: info + encoding: json + extensions: + - health_check + - pprof + - k8s_leader_elector + - oauth2client/test +receivers: + kymastats: + auth_type: serviceAccount + collection_interval: 30s + resources: + - group: operator.kyma-project.io + version: v1alpha1 + resource: telemetries + - group: telemetry.kyma-project.io + version: v1alpha1 + resource: logpipelines + - group: telemetry.kyma-project.io + version: v1alpha1 + resource: tracepipelines + - group: telemetry.kyma-project.io + version: v1alpha1 + resource: metricpipelines + k8s_leader_elector: k8s_leader_elector + otlp: + protocols: + http: + endpoint: ${MY_POD_IP}:4318 + grpc: + endpoint: ${MY_POD_IP}:4317 +processors: + batch: + send_batch_size: 1024 + timeout: 10s + send_batch_max_size: 1024 + k8sattributes: + auth_type: serviceAccount + passthrough: false + extract: + metadata: + - k8s.pod.name + - k8s.node.name + - k8s.namespace.name + - k8s.deployment.name + - k8s.statefulset.name + - k8s.daemonset.name + - k8s.cronjob.name + - k8s.job.name + labels: + - from: pod + key: app.kubernetes.io/name + tag_name: kyma.kubernetes_io_app_name + - from: pod + key: app + tag_name: kyma.app_name + - from: node + key: topology.kubernetes.io/region + tag_name: cloud.region + - from: node + key: topology.kubernetes.io/zone + tag_name: cloud.availability_zone + - from: node + key: node.kubernetes.io/instance-type + tag_name: host.type + - from: node + key: kubernetes.io/arch + tag_name: host.arch + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: resource_attribute + name: k8s.pod.uid + - sources: + - from: connection + memory_limiter: + check_interval: 1s + limit_percentage: 75 + spike_limit_percentage: 15 + resource/drop-kyma-attributes: + attributes: + - action: delete + pattern: kyma.* + resource/insert-cluster-attributes: + attributes: + - action: insert + key: k8s.cluster.name + value: ${KUBERNETES_SERVICE_HOST} + - action: insert + key: k8s.cluster.uid + - action: insert + key: cloud.provider + value: test-cloud-provider + resource/set-kyma-input-name-kyma: + attributes: + - action: insert + key: kyma.input.name + value: kyma + resource/set-kyma-input-name-otlp: + attributes: + - action: insert + key: kyma.input.name + value: otlp + service_enrichment: + resource_attributes: + - kyma.kubernetes_io_app_name + - kyma.app_name + transform/set-instrumentation-scope-kyma: + error_mode: ignore + metric_statements: + - statements: + - set(scope.version, "") where scope.name == "github.com/kyma-project/opentelemetry-collector-components/receiver/kymastatsreceiver" + - set(scope.name, "io.kyma-project.telemetry/kyma") where scope.name == "github.com/kyma-project/opentelemetry-collector-components/receiver/kymastatsreceiver" +exporters: + otlphttp/test: + endpoint: ${OTLP_ENDPOINT_TEST} + tls: + insecure: true + sending_queue: + enabled: true + queue_size: 256 + retry_on_failure: + enabled: true + initial_interval: 5s + max_interval: 30s + max_elapsed_time: 300s + auth: + authenticator: oauth2client/test +connectors: + forward/enrichment: {} + forward/input: {} diff --git a/internal/otelcollector/config/tracegateway/config_builder.go b/internal/otelcollector/config/tracegateway/config_builder.go index 031e88e71f..2a6210a28e 100644 --- a/internal/otelcollector/config/tracegateway/config_builder.go +++ b/internal/otelcollector/config/tracegateway/config_builder.go @@ -36,6 +36,13 @@ func (b *Builder) Build(ctx context.Context, pipelines []telemetryv1alpha1.Trace for _, pipeline := range pipelines { pipelineID := formatTraceServicePipelineID(&pipeline) + + if shouldEnableOAuth2(&pipeline) { + if err := b.addOAuth2Extension(ctx, &pipeline); err != nil { + return nil, nil, err + } + } + if err := b.AddServicePipeline(ctx, &pipeline, pipelineID, b.addOTLPReceiver(), b.addMemoryLimiterProcessor(), @@ -197,6 +204,28 @@ func (b *Builder) addOTLPExporter(queueSize int) buildComponentFunc { ) } +func (b *Builder) addOAuth2Extension(ctx context.Context, pipeline *telemetryv1alpha1.TracePipeline) error { + oauth2ExtensionID := common.OAuth2ExtensionID(pipeline.Name) + + oauth2ExtensionConfig, oauth2ExtensionEnvVars, err := common.NewOAuth2ExtensionConfigBuilder( + b.Reader, + pipeline.Spec.Output.OTLP.Authentication.OAuth2, + pipeline.Name, + common.SignalTypeTrace, + ).OAuth2ExtensionConfig(ctx) + if err != nil { + return fmt.Errorf("failed to build OAuth2 extension for pipeline %s: %w", pipeline.Name, err) + } + + b.AddExtension(oauth2ExtensionID, oauth2ExtensionConfig, oauth2ExtensionEnvVars) + + return nil +} + +func shouldEnableOAuth2(tp *telemetryv1alpha1.TracePipeline) bool { + return tp.Spec.Output.OTLP.Authentication != nil && tp.Spec.Output.OTLP.Authentication.OAuth2 != nil +} + func formatTraceServicePipelineID(tp *telemetryv1alpha1.TracePipeline) string { return fmt.Sprintf("traces/%s", tp.Name) } diff --git a/internal/otelcollector/config/tracegateway/config_builder_test.go b/internal/otelcollector/config/tracegateway/config_builder_test.go index 498756a4ef..954ae05f08 100644 --- a/internal/otelcollector/config/tracegateway/config_builder_test.go +++ b/internal/otelcollector/config/tracegateway/config_builder_test.go @@ -111,6 +111,23 @@ func TestBuildConfig(t *testing.T) { }, goldenFileName: "user-defined-transform-filter.yaml", }, + { + name: "pipeline using OAuth2 authentication", + pipelines: []telemetryv1alpha1.TracePipeline{ + testutils.NewTracePipelineBuilder(). + WithName("test"). + WithOTLPOutput( + testutils.OTLPProtocol("http"), + ). + WithOAuth2( + testutils.OAuth2ClientID("client-id"), + testutils.OAuth2ClientSecret("client-secret"), + testutils.OAuth2TokenURL("https://auth.example.com/oauth2/token"), + testutils.OAuth2Scopes([]string{"traces"}), + ).Build(), + }, + goldenFileName: "oauth2-authentication.yaml", + }, } buildOptions := BuildOptions{ diff --git a/internal/otelcollector/config/tracegateway/testdata/oauth2-authentication.yaml b/internal/otelcollector/config/tracegateway/testdata/oauth2-authentication.yaml new file mode 100644 index 0000000000..19b1c34686 --- /dev/null +++ b/internal/otelcollector/config/tracegateway/testdata/oauth2-authentication.yaml @@ -0,0 +1,130 @@ +extensions: + health_check: + endpoint: ${MY_POD_IP}:13133 + oauth2client/test: + token_url: ${OAUTH2_TOKEN_URL_TEST} + client_id: ${OAUTH2_CLIENT_ID_TEST} + client_secret: ${OAUTH2_CLIENT_SECRET_TEST} + scopes: + - traces + pprof: + endpoint: 127.0.0.1:1777 +service: + pipelines: + traces/test: + receivers: + - otlp + processors: + - memory_limiter + - k8sattributes + - istio_noise_filter + - resource/insert-cluster-attributes + - service_enrichment + - resource/drop-kyma-attributes + - batch + exporters: + - otlphttp/test + telemetry: + metrics: + readers: + - pull: + exporter: + prometheus: + host: ${MY_POD_IP} + port: 8888 + logs: + level: info + encoding: json + extensions: + - health_check + - pprof + - oauth2client/test +receivers: + otlp: + protocols: + http: + endpoint: ${MY_POD_IP}:4318 + grpc: + endpoint: ${MY_POD_IP}:4317 +processors: + batch: + send_batch_size: 512 + timeout: 10s + send_batch_max_size: 512 + istio_noise_filter: {} + k8sattributes: + auth_type: serviceAccount + passthrough: false + extract: + metadata: + - k8s.pod.name + - k8s.node.name + - k8s.namespace.name + - k8s.deployment.name + - k8s.statefulset.name + - k8s.daemonset.name + - k8s.cronjob.name + - k8s.job.name + labels: + - from: pod + key: app.kubernetes.io/name + tag_name: kyma.kubernetes_io_app_name + - from: pod + key: app + tag_name: kyma.app_name + - from: node + key: topology.kubernetes.io/region + tag_name: cloud.region + - from: node + key: topology.kubernetes.io/zone + tag_name: cloud.availability_zone + - from: node + key: node.kubernetes.io/instance-type + tag_name: host.type + - from: node + key: kubernetes.io/arch + tag_name: host.arch + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: resource_attribute + name: k8s.pod.uid + - sources: + - from: connection + memory_limiter: + check_interval: 1s + limit_percentage: 75 + spike_limit_percentage: 15 + resource/drop-kyma-attributes: + attributes: + - action: delete + pattern: kyma.* + resource/insert-cluster-attributes: + attributes: + - action: insert + key: k8s.cluster.name + value: ${KUBERNETES_SERVICE_HOST} + - action: insert + key: k8s.cluster.uid + - action: insert + key: cloud.provider + value: test-cloud-provider + service_enrichment: + resource_attributes: + - kyma.kubernetes_io_app_name + - kyma.app_name +exporters: + otlphttp/test: + endpoint: ${OTLP_ENDPOINT_TEST} + sending_queue: + enabled: true + queue_size: 256 + retry_on_failure: + enabled: true + initial_interval: 5s + max_interval: 30s + max_elapsed_time: 300s + auth: + authenticator: oauth2client/test diff --git a/internal/reconciler/metricpipeline/reconciler_test.go b/internal/reconciler/metricpipeline/reconciler_test.go index 129bcb141e..8b8fb00082 100644 --- a/internal/reconciler/metricpipeline/reconciler_test.go +++ b/internal/reconciler/metricpipeline/reconciler_test.go @@ -539,7 +539,7 @@ func TestTLSCertificateValidation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - pipeline := testutils.NewMetricPipelineBuilder().WithOTLPOutput(testutils.OTLPClientTLSFromString("ca", "fooCert", "fooKey")).Build() + pipeline := testutils.NewMetricPipelineBuilder().WithOTLPOutput(testutils.OTLPClientMTLSFromString("ca", "fooCert", "fooKey")).Build() fakeClient := newTestClient(t, &pipeline) gatewayConfigBuilderMock := &mocks.GatewayConfigBuilder{} diff --git a/internal/reconciler/tracepipeline/reconciler_test.go b/internal/reconciler/tracepipeline/reconciler_test.go index d8f230ee79..61efe2086a 100644 --- a/internal/reconciler/tracepipeline/reconciler_test.go +++ b/internal/reconciler/tracepipeline/reconciler_test.go @@ -414,7 +414,7 @@ func TestTLSCertificateValidation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - pipeline := testutils.NewTracePipelineBuilder().WithOTLPOutput(testutils.OTLPClientTLSFromString("ca", "fooCert", "fooKey")).Build() + pipeline := testutils.NewTracePipelineBuilder().WithOTLPOutput(testutils.OTLPClientMTLSFromString("ca", "fooCert", "fooKey")).Build() fakeClient := newTestClient(t, &pipeline) gatewayConfigBuilderMock := &mocks.GatewayConfigBuilder{} diff --git a/internal/utils/metricpipeline/metricpipeline.go b/internal/utils/metricpipeline/metricpipeline.go index b54ebaf708..5ef36577a9 100644 --- a/internal/utils/metricpipeline/metricpipeline.go +++ b/internal/utils/metricpipeline/metricpipeline.go @@ -11,8 +11,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" - "github.com/kyma-project/telemetry-manager/internal/otelcollector/config/common" "github.com/kyma-project/telemetry-manager/internal/otelcollector/ports" + sharedtypesutils "github.com/kyma-project/telemetry-manager/internal/utils/sharedtypes" ) func IsIstioInputEnabled(input telemetryv1alpha1.MetricPipelineInput) bool { @@ -120,7 +120,7 @@ func OTLPOutputPorts(ctx context.Context, c client.Reader, allPipelines []teleme backendPorts := []string{} for _, pipeline := range allPipelines { - endpoint, err := common.ResolveValue(ctx, c, pipeline.Spec.Output.OTLP.Endpoint) + endpoint, err := sharedtypesutils.ResolveValue(ctx, c, pipeline.Spec.Output.OTLP.Endpoint) if err != nil { return nil, fmt.Errorf("failed to resolve the value of the OTLP output endpoint: %w", err) } diff --git a/internal/utils/sharedtypes/sharedtypes.go b/internal/utils/sharedtypes/sharedtypes.go index c95b99a917..43652c73c0 100644 --- a/internal/utils/sharedtypes/sharedtypes.go +++ b/internal/utils/sharedtypes/sharedtypes.go @@ -1,7 +1,17 @@ package sharedtypes import ( + "context" + "errors" + + "sigs.k8s.io/controller-runtime/pkg/client" + telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" + "github.com/kyma-project/telemetry-manager/internal/validators/secretref" +) + +var ( + ErrValueOrSecretRefUndefined = errors.New("either value or secret key reference must be defined") ) func IsValid(v *telemetryv1alpha1.ValueType) bool { @@ -19,3 +29,15 @@ func IsValid(v *telemetryv1alpha1.ValueType) bool { v.ValueFrom.SecretKeyRef.Key != "" && v.ValueFrom.SecretKeyRef.Namespace != "" } + +func ResolveValue(ctx context.Context, c client.Reader, value telemetryv1alpha1.ValueType) ([]byte, error) { + if value.Value != "" { + return []byte(value.Value), nil + } + + if value.ValueFrom.SecretKeyRef != nil { + return secretref.GetValue(ctx, c, *value.ValueFrom.SecretKeyRef) + } + + return nil, ErrValueOrSecretRefUndefined +} diff --git a/internal/utils/sharedtypes/sharedtypes_test.go b/internal/utils/sharedtypes/sharedtypes_test.go new file mode 100644 index 0000000000..823a87036b --- /dev/null +++ b/internal/utils/sharedtypes/sharedtypes_test.go @@ -0,0 +1,181 @@ +package sharedtypes + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" +) + +func TestIsValid(t *testing.T) { + tests := []struct { + name string + input *telemetryv1alpha1.ValueType + expected bool + }{ + { + name: "nil value", + input: nil, + expected: false, + }, + { + name: "non-empty value field", + input: &telemetryv1alpha1.ValueType{Value: "test"}, + expected: true, + }, + { + name: "empty value and nil ValueFrom", + input: &telemetryv1alpha1.ValueType{Value: ""}, + expected: false, + }, + { + name: "valid SecretKeyRef", + input: &telemetryv1alpha1.ValueType{ + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: "secret", + Key: "key", + Namespace: "default", + }, + }, + }, + expected: true, + }, + { + name: "SecretKeyRef missing name", + input: &telemetryv1alpha1.ValueType{ + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Key: "key", + Namespace: "default", + }, + }, + }, + expected: false, + }, + { + name: "SecretKeyRef missing key", + input: &telemetryv1alpha1.ValueType{ + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: "secret", + Namespace: "default", + }, + }, + }, + expected: false, + }, + { + name: "SecretKeyRef missing namespace", + input: &telemetryv1alpha1.ValueType{ + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: "secret", + Key: "key", + }, + }, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := IsValid(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestResolveValue(t *testing.T) { + ctx := context.Background() + + testSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: "default", + }, + Data: map[string][]byte{ + "test-key": []byte("secret-value"), + }, + } + + client := fake.NewClientBuilder().WithObjects(&testSecret).Build() + + tests := []struct { + name string + value telemetryv1alpha1.ValueType + expected []byte + expectedErr bool + errType error + }{ + { + name: "resolve from direct value", + value: telemetryv1alpha1.ValueType{Value: "direct-value"}, + expected: []byte("direct-value"), + expectedErr: false, + }, + { + name: "resolve from undefined value", + value: telemetryv1alpha1.ValueType{ + Value: "", + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: nil, + }, + }, + expected: nil, + expectedErr: true, + errType: ErrValueOrSecretRefUndefined, + }, + { + name: "resolve from secret", + value: telemetryv1alpha1.ValueType{ + Value: "", + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: "test-secret", + Key: "test-key", + Namespace: "default", + }, + }, + }, + expected: []byte("secret-value"), + expectedErr: false, + }, + { + name: "direct value takes precedence over secret", + value: telemetryv1alpha1.ValueType{ + Value: "direct-value", + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: "test-secret", + Key: "test-key", + Namespace: "default", + }, + }, + }, + expected: []byte("direct-value"), + expectedErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := ResolveValue(ctx, client, tt.value) + if tt.expectedErr { + require.Error(t, err) + assert.True(t, errors.Is(err, tt.errType)) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected, result) + } + }) + } +} diff --git a/internal/utils/test/log_pipeline_builder.go b/internal/utils/test/log_pipeline_builder.go index a15ed9f3c4..b3354bbd84 100644 --- a/internal/utils/test/log_pipeline_builder.go +++ b/internal/utils/test/log_pipeline_builder.go @@ -24,6 +24,7 @@ type LogPipelineBuilder struct { httpOutput *telemetryv1alpha1.LogPipelineHTTPOutput otlpOutput *telemetryv1alpha1.OTLPOutput + oauth2 *telemetryv1alpha1.OAuth2Options customOutput string fluentBitFilters []telemetryv1alpha1.LogPipelineFilter @@ -277,6 +278,29 @@ func (b *LogPipelineBuilder) WithOTLPOutput(opts ...OTLPOutputOption) *LogPipeli return b } +func (b *LogPipelineBuilder) WithOAuth2(opts ...OAuth2Option) *LogPipelineBuilder { + if b.oauth2 == nil { + b.oauth2 = &telemetryv1alpha1.OAuth2Options{} + } + + for _, opt := range opts { + opt(b.oauth2) + } + + // Set OAuth2 on the OTLP output authentication + if b.otlpOutput == nil { + b.otlpOutput = defaultOTLPOutput() + } + + if b.otlpOutput.Authentication == nil { + b.otlpOutput.Authentication = &telemetryv1alpha1.AuthenticationOptions{} + } + + b.otlpOutput.Authentication.OAuth2 = b.oauth2 + + return b +} + func (b *LogPipelineBuilder) WithCustomOutput(custom string) *LogPipelineBuilder { b.customOutput = custom return b diff --git a/internal/utils/test/metric_pipeline_builder.go b/internal/utils/test/metric_pipeline_builder.go index 5ec93f908a..0d7eab0a08 100644 --- a/internal/utils/test/metric_pipeline_builder.go +++ b/internal/utils/test/metric_pipeline_builder.go @@ -24,6 +24,7 @@ type MetricPipelineBuilder struct { inOTLP *telemetryv1alpha1.OTLPInput outOTLP *telemetryv1alpha1.OTLPOutput + oauth2 *telemetryv1alpha1.OAuth2Options transforms []telemetryv1alpha1.TransformSpec filter []telemetryv1alpha1.FilterSpec @@ -388,6 +389,25 @@ func (b *MetricPipelineBuilder) WithOTLPOutput(opts ...OTLPOutputOption) *Metric return b } +func (b *MetricPipelineBuilder) WithOAuth2(opts ...OAuth2Option) *MetricPipelineBuilder { + if b.oauth2 == nil { + b.oauth2 = &telemetryv1alpha1.OAuth2Options{} + } + + for _, opt := range opts { + opt(b.oauth2) + } + + // Set OAuth2 on the OTLP output authentication + if b.outOTLP.Authentication == nil { + b.outOTLP.Authentication = &telemetryv1alpha1.AuthenticationOptions{} + } + + b.outOTLP.Authentication.OAuth2 = b.oauth2 + + return b +} + func (b *MetricPipelineBuilder) WithTransform(transform telemetryv1alpha1.TransformSpec) *MetricPipelineBuilder { b.transforms = append(b.transforms, transform) return b diff --git a/internal/utils/test/pipeline_opts.go b/internal/utils/test/pipeline_opts.go index 1cab9aa7e7..3dd0caad5a 100644 --- a/internal/utils/test/pipeline_opts.go +++ b/internal/utils/test/pipeline_opts.go @@ -14,6 +14,19 @@ func OTLPEndpoint(endpoint string) OTLPOutputOption { } } +func OTLPOAuth2(oauth2Opts ...OAuth2Option) OTLPOutputOption { + return func(output *telemetryv1alpha1.OTLPOutput) { + oauth2opts := &telemetryv1alpha1.OAuth2Options{} + for _, opt := range oauth2Opts { + opt(oauth2opts) + } + + output.Authentication = &telemetryv1alpha1.AuthenticationOptions{ + OAuth2: oauth2opts, + } + } +} + func OTLPEndpointFromSecret(secretName, secretNamespace, endpointKey string) OTLPOutputOption { return func(output *telemetryv1alpha1.OTLPOutput) { output.Endpoint = telemetryv1alpha1.ValueType{ @@ -78,7 +91,8 @@ func OTLPCustomHeader(name, value, prefix string) OTLPOutputOption { } } -func OTLPClientTLSFromString(ca, cert, key string) OTLPOutputOption { +// OTLPClientMTLSFromString sets the mTLS configuration for the OTLP output +func OTLPClientMTLSFromString(ca, cert, key string) OTLPOutputOption { return func(output *telemetryv1alpha1.OTLPOutput) { output.TLS = &telemetryv1alpha1.OTLPTLS{ CA: &telemetryv1alpha1.ValueType{Value: ca}, @@ -88,12 +102,41 @@ func OTLPClientTLSFromString(ca, cert, key string) OTLPOutputOption { } } +// OTLPClientTLS sets the TLS configuration for the OTLP output (it does not include client certs) func OTLPClientTLS(tls *telemetryv1alpha1.OTLPTLS) OTLPOutputOption { return func(output *telemetryv1alpha1.OTLPOutput) { output.TLS = tls } } +func OTLPClientTLSFromString(ca string) OTLPOutputOption { + return func(output *telemetryv1alpha1.OTLPOutput) { + output.TLS = &telemetryv1alpha1.OTLPTLS{ + CA: &telemetryv1alpha1.ValueType{Value: ca}, + } + } +} + +func OTLPInsecure(insecure bool) OTLPOutputOption { + return func(output *telemetryv1alpha1.OTLPOutput) { + if output.TLS == nil { + output.TLS = &telemetryv1alpha1.OTLPTLS{} + } + + output.TLS.Insecure = insecure + } +} + +func OTLPInsecureSkipVerify(insecure bool) OTLPOutputOption { + return func(output *telemetryv1alpha1.OTLPOutput) { + if output.TLS == nil { + output.TLS = &telemetryv1alpha1.OTLPTLS{} + } + + output.TLS.InsecureSkipVerify = insecure + } +} + func OTLPProtocol(protocol string) OTLPOutputOption { return func(output *telemetryv1alpha1.OTLPOutput) { output.Protocol = protocol @@ -106,6 +149,80 @@ func OTLPEndpointPath(path string) OTLPOutputOption { } } +type OAuth2Option func(oauth2 *telemetryv1alpha1.OAuth2Options) + +func OAuth2ClientID(clientID string) OAuth2Option { + return func(oauth2 *telemetryv1alpha1.OAuth2Options) { + oauth2.ClientID = telemetryv1alpha1.ValueType{Value: clientID} + } +} + +func OAuth2ClientIDFromSecret(secretName, secretNamespace, key string) OAuth2Option { + return func(oauth2 *telemetryv1alpha1.OAuth2Options) { + oauth2.ClientID = telemetryv1alpha1.ValueType{ + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: secretName, + Namespace: secretNamespace, + Key: key, + }, + }, + } + } +} + +func OAuth2ClientSecret(clientSecret string) OAuth2Option { + return func(oauth2 *telemetryv1alpha1.OAuth2Options) { + oauth2.ClientSecret = telemetryv1alpha1.ValueType{Value: clientSecret} + } +} + +func OAuth2ClientSecretFromSecret(secretName, secretNamespace, key string) OAuth2Option { + return func(oauth2 *telemetryv1alpha1.OAuth2Options) { + oauth2.ClientSecret = telemetryv1alpha1.ValueType{ + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: secretName, + Namespace: secretNamespace, + Key: key, + }, + }, + } + } +} + +func OAuth2TokenURL(tokenURL string) OAuth2Option { + return func(oauth2 *telemetryv1alpha1.OAuth2Options) { + oauth2.TokenURL = telemetryv1alpha1.ValueType{Value: tokenURL} + } +} + +func OAuth2TokenURLFromSecret(secretName, secretNamespace, key string) OAuth2Option { + return func(oauth2 *telemetryv1alpha1.OAuth2Options) { + oauth2.TokenURL = telemetryv1alpha1.ValueType{ + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: secretName, + Namespace: secretNamespace, + Key: key, + }, + }, + } + } +} + +func OAuth2Scopes(scopes []string) OAuth2Option { + return func(oauth2 *telemetryv1alpha1.OAuth2Options) { + oauth2.Scopes = scopes + } +} + +func OAuth2Params(params map[string]string) OAuth2Option { + return func(oauth2 *telemetryv1alpha1.OAuth2Options) { + oauth2.Params = params + } +} + type HTTPOutputOption func(output *telemetryv1alpha1.LogPipelineHTTPOutput) func HTTPClientTLSFromString(ca, cert, key string) HTTPOutputOption { diff --git a/internal/utils/test/trace_pipeline_builder.go b/internal/utils/test/trace_pipeline_builder.go index be5bed3dfa..2baaf5632e 100644 --- a/internal/utils/test/trace_pipeline_builder.go +++ b/internal/utils/test/trace_pipeline_builder.go @@ -20,6 +20,7 @@ type TracePipelineBuilder struct { filters []telemetryv1alpha1.FilterSpec statusConditions []metav1.Condition outOTLP *telemetryv1alpha1.OTLPOutput + oauth2 *telemetryv1alpha1.OAuth2Options } func NewTracePipelineBuilder() *TracePipelineBuilder { @@ -59,6 +60,25 @@ func (b *TracePipelineBuilder) WithOTLPOutput(opts ...OTLPOutputOption) *TracePi return b } +func (b *TracePipelineBuilder) WithOAuth2(opts ...OAuth2Option) *TracePipelineBuilder { + if b.oauth2 == nil { + b.oauth2 = &telemetryv1alpha1.OAuth2Options{} + } + + for _, opt := range opts { + opt(b.oauth2) + } + + // Set OAuth2 on the OTLP output authentication + if b.outOTLP.Authentication == nil { + b.outOTLP.Authentication = &telemetryv1alpha1.AuthenticationOptions{} + } + + b.outOTLP.Authentication.OAuth2 = b.oauth2 + + return b +} + func (b *TracePipelineBuilder) WithTransform(transform telemetryv1alpha1.TransformSpec) *TracePipelineBuilder { b.transforms = append(b.transforms, transform) return b diff --git a/internal/validators/secretref/secret_ref.go b/internal/validators/secretref/secret_ref.go index 2d4cbbf6bf..06c42f056e 100644 --- a/internal/validators/secretref/secret_ref.go +++ b/internal/validators/secretref/secret_ref.go @@ -153,15 +153,19 @@ func getSecretRefsInOTLPOutput(otlpOut *telemetryv1alpha1.OTLPOutput) []telemetr refs = appendIfSecretRef(refs, &otlpOut.Authentication.Basic.Password) } + if otlpOut.Authentication != nil && otlpOut.Authentication.OAuth2 != nil { + refs = appendIfSecretRef(refs, &otlpOut.Authentication.OAuth2.TokenURL) + refs = appendIfSecretRef(refs, &otlpOut.Authentication.OAuth2.ClientID) + refs = appendIfSecretRef(refs, &otlpOut.Authentication.OAuth2.ClientSecret) + } + for _, header := range otlpOut.Headers { refs = appendIfSecretRef(refs, &header.ValueType) } if otlpOut.TLS != nil && !otlpOut.TLS.Insecure { refs = appendIfSecretRef(refs, otlpOut.TLS.CA) - refs = appendIfSecretRef(refs, otlpOut.TLS.Cert) - refs = appendIfSecretRef(refs, otlpOut.TLS.Key) } diff --git a/internal/validators/secretref/secret_ref_test.go b/internal/validators/secretref/secret_ref_test.go index 89b9aaf783..e3910b54b3 100644 --- a/internal/validators/secretref/secret_ref_test.go +++ b/internal/validators/secretref/secret_ref_test.go @@ -288,6 +288,49 @@ func TestTracePipeline_GetSecretRefs(t *testing.T) { {Name: "secret-3", Namespace: "default"}, }, }, + { + name: "oauth2", + pipelineName: "test-pipeline", + given: &telemetryv1alpha1.OTLPOutput{ + Authentication: &telemetryv1alpha1.AuthenticationOptions{ + OAuth2: &telemetryv1alpha1.OAuth2Options{ + TokenURL: telemetryv1alpha1.ValueType{ + Value: "", + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: "secret-1", + Namespace: "default", + Key: "token-url", + }}, + }, + ClientID: telemetryv1alpha1.ValueType{ + Value: "", + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: "secret-2", + Namespace: "default", + Key: "client-id", + }}, + }, + ClientSecret: telemetryv1alpha1.ValueType{ + Value: "", + ValueFrom: &telemetryv1alpha1.ValueFromSource{ + SecretKeyRef: &telemetryv1alpha1.SecretKeyRef{ + Name: "secret-3", + Namespace: "default", + Key: "client-secret", + }}, + }, + }, + }, + }, + + expected: []telemetryv1alpha1.SecretKeyRef{ + {Name: "secret-1", Namespace: "default", Key: "token-url"}, + {Name: "secret-2", Namespace: "default", Key: "client-id"}, + {Name: "secret-3", Namespace: "default", Key: "client-secret"}, + }, + }, } for _, test := range tests { diff --git a/test/e2e/logs/misc/mtls_cert_key_pair_dont_match_test.go b/test/e2e/logs/misc/mtls_cert_key_pair_dont_match_test.go index 2972e22eaf..7176d218eb 100644 --- a/test/e2e/logs/misc/mtls_cert_key_pair_dont_match_test.go +++ b/test/e2e/logs/misc/mtls_cert_key_pair_dont_match_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSCertKeyDontMatch_OTel(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelLogsMisc) + suite.RegisterTestCase(t, suite.LabelLogsMisc, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -32,13 +32,13 @@ func TestMTLSCertKeyDontMatch_OTel(t *testing.T) { _, clientCertsCreatedAgain, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithTLS(*serverCertsDefault)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithMTLS(*serverCertsDefault)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( clientCertsDefault.CaCertPem.String(), clientCertsDefault.ClientCertPem.String(), clientCertsCreatedAgain.ClientKeyPem.String(), // Use different key @@ -90,7 +90,7 @@ func TestMTLSCertKeyDontMatch_FluentBit(t *testing.T) { _, clientCertsCreatedAgain, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithTLS(*serverCertsDefault)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithMTLS(*serverCertsDefault)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). diff --git a/test/e2e/logs/misc/mtls_expired_cert_test.go b/test/e2e/logs/misc/mtls_expired_cert_test.go index bbf94450ff..ac78c358b9 100644 --- a/test/e2e/logs/misc/mtls_expired_cert_test.go +++ b/test/e2e/logs/misc/mtls_expired_cert_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSExpiredCert_OTel(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelLogsMisc) + suite.RegisterTestCase(t, suite.LabelLogsMisc, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -31,13 +31,13 @@ func TestMTLSExpiredCert_OTel(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithTLS(*expiredServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithMTLS(*expiredServerCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( expiredClientCerts.CaCertPem.String(), expiredClientCerts.ClientCertPem.String(), expiredClientCerts.ClientKeyPem.String(), @@ -87,7 +87,7 @@ func TestMTLSExpiredCert_FluentBit(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithTLS(*expiredServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithMTLS(*expiredServerCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). diff --git a/test/e2e/logs/misc/mtls_invalid_ca_test.go b/test/e2e/logs/misc/mtls_invalid_ca_test.go index 32ce8552f3..b385264e29 100644 --- a/test/e2e/logs/misc/mtls_invalid_ca_test.go +++ b/test/e2e/logs/misc/mtls_invalid_ca_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSInvalidCA_OTel(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelLogsMisc) + suite.RegisterTestCase(t, suite.LabelLogsMisc, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -31,13 +31,13 @@ func TestMTLSInvalidCA_OTel(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithTLS(*invalidServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithMTLS(*invalidServerCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( invalidClientCerts.CaCertPem.String(), invalidClientCerts.ClientCertPem.String(), invalidClientCerts.ClientKeyPem.String(), @@ -87,7 +87,7 @@ func TestMTLSInvalidCA_FluentBit(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithTLS(*invalidServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithMTLS(*invalidServerCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). diff --git a/test/e2e/logs/misc/mtls_invalid_cert_test.go b/test/e2e/logs/misc/mtls_invalid_cert_test.go index 5384851228..6135c09879 100644 --- a/test/e2e/logs/misc/mtls_invalid_cert_test.go +++ b/test/e2e/logs/misc/mtls_invalid_cert_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSInvalidCert_OTel(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelLogsMisc) + suite.RegisterTestCase(t, suite.LabelLogsMisc, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -31,13 +31,13 @@ func TestMTLSInvalidCert_OTel(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithTLS(*invalidServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithMTLS(*invalidServerCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( invalidClientCerts.CaCertPem.String(), invalidClientCerts.ClientCertPem.String(), invalidClientCerts.ClientKeyPem.String(), @@ -87,7 +87,7 @@ func TestMTLSInvalidCert_FluentBit(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithTLS(*invalidServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithMTLS(*invalidServerCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). diff --git a/test/e2e/logs/misc/reject_creation_test.go b/test/e2e/logs/misc/reject_creation_test.go index dfec0fee0f..f6aafffe9d 100644 --- a/test/e2e/logs/misc/reject_creation_test.go +++ b/test/e2e/logs/misc/reject_creation_test.go @@ -27,6 +27,7 @@ func TestRejectLogPipelineCreation(t *testing.T) { var backenEndpoint = backendHost + ":" + strconv.Itoa(backendPort) tests := []struct { + name string pipeline telemetryv1alpha1.LogPipeline errorMsg string field string @@ -35,20 +36,16 @@ func TestRejectLogPipelineCreation(t *testing.T) { }{ // output general { + name: "no-output", pipeline: telemetryv1alpha1.LogPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "no-output", - }, Spec: telemetryv1alpha1.LogPipelineSpec{}, }, errorMsg: "Exactly one output out of 'custom', 'http' or 'otlp' must be defined", field: "spec.output", }, { + name: "valuefrom-accepts-only-one-option", pipeline: telemetryv1alpha1.LogPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "valuefrom-accepts-only-one-option", - }, Spec: telemetryv1alpha1.LogPipelineSpec{ Output: telemetryv1alpha1.LogPipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -70,10 +67,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.otlp.endpoint", }, { + name: "secretkeyref-requires-key", pipeline: telemetryv1alpha1.LogPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretkeyref-requires-key", - }, Spec: telemetryv1alpha1.LogPipelineSpec{ Output: telemetryv1alpha1.LogPipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -93,10 +88,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.otlp.endpoint.valueFrom.secretKeyRef.key", }, { + name: "secretkeyref-requires-namespace", pipeline: telemetryv1alpha1.LogPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretkeyref-requires-namespace", - }, Spec: telemetryv1alpha1.LogPipelineSpec{ Output: telemetryv1alpha1.LogPipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -116,10 +109,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.otlp.endpoint.valueFrom.secretKeyRef.namespace", }, { + name: "secretkeyref-requires-name", pipeline: telemetryv1alpha1.LogPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretkeyref-requires-name", - }, Spec: telemetryv1alpha1.LogPipelineSpec{ Output: telemetryv1alpha1.LogPipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -140,8 +131,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { }, // otlp output { + name: "otlp-output-with-default-proto-and-path", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-output-with-default-proto-and-path"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPEndpointPath("/v1/dummy"), @@ -151,8 +142,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.otlp", }, { + name: "otlp-output-with-grpc-proto-and-path", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-output-with-grpc-proto-and-path"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPEndpointPath("/v1/dummy"), @@ -163,8 +154,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.otlp", }, { + name: "otlp-output-with-non-valid-proto", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-output-with-non-valid-proto"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPProtocol("icke"), @@ -175,8 +166,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.otlp.protocol", }, { + name: "otlp-output-basic-auth-secretref-missing-password-key", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-output-basic-auth-secretref-missing-password-key"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPBasicAuthFromSecret("name", "namespace", "user", ""), @@ -186,8 +177,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.otlp.authentication.basic.password.valueFrom.secretKeyRef.key", }, { + name: "otlp-output-basic-auth-secretref-missing-user-key", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-output-basic-auth-secretref-missing-user-key"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPBasicAuthFromSecret("name", "namespace", "", "password"), @@ -197,8 +188,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.otlp.authentication.basic.user.valueFrom.secretKeyRef.key", }, { + name: "otlp-output-tls-missing-key", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-output-tls-missing-key"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPClientTLS(&telemetryv1alpha1.OTLPTLS{ @@ -211,8 +202,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.otlp.tls", }, { + name: "otlp-output-tls-missing-cert", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-output-tls-missing-cert"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPClientTLS(&telemetryv1alpha1.OTLPTLS{ @@ -224,10 +215,56 @@ func TestRejectLogPipelineCreation(t *testing.T) { errorMsg: "Can define either both 'cert' and 'key', or neither", field: "spec.output.otlp.tls", }, - // otlp input { + name: "otlp-output-oauth2-invalid-token-url", + pipeline: testutils.NewLogPipelineBuilder(). + WithOTLPOutput( + testutils.OTLPEndpoint(backenEndpoint), + testutils.OTLPOAuth2( + testutils.OAuth2ClientSecret("clientsecret"), + testutils.OAuth2ClientID("clientid"), + testutils.OAuth2TokenURL("../not-a-url"), + ), + ). + Build(), + errorMsg: "Invalid value: \"object\": 'tokenURL' must be a valid URL", + field: "spec.output.otlp.authentication.oauth2.tokenURL", + }, + { + name: "otlp-output-oauth2-missing-client-id", + pipeline: testutils.NewLogPipelineBuilder(). + WithOTLPOutput( + testutils.OTLPEndpoint(backenEndpoint), + testutils.OTLPOAuth2( + testutils.OAuth2ClientSecret("clientsecret"), + testutils.OAuth2TokenURL("https://auth.example.com/token"), + ), + ). + Build(), + errorMsg: "Invalid value: \"object\": no such key: value evaluating rule: 'clientID' missing", + field: "spec.output.otlp.authentication.oauth2.clientID", + }, + { + name: "otlp-output-oauth2-insecure", + pipeline: testutils.NewLogPipelineBuilder(). + WithOTLPOutput( + testutils.OTLPEndpoint(backenEndpoint), + testutils.OTLPOAuth2( + testutils.OAuth2ClientID("clientid"), + testutils.OAuth2ClientSecret("clientsecret"), + testutils.OAuth2TokenURL("https://auth.example.com/token"), + ), + testutils.OTLPClientTLS(&telemetryv1alpha1.OTLPTLS{ + Insecure: true, + }), + ). + Build(), + errorMsg: "OAuth2 authentication requires TLS when using gRPC protocol", + field: "spec.output.otlp", + }, + { + name: "otlp-input-namespaces-not-exclusive", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-input-namespaces-not-exclusive"). WithOTLPInput(true, testutils.ExcludeNamespaces("ns1"), testutils.IncludeNamespaces("ns2"), @@ -238,8 +275,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.input.otlp.namespaces", }, { + name: "otlp-input-namespaces-include-invalid", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-input-namespaces-include-invalid"). WithOTLPInput(true, testutils.IncludeNamespaces("Test"), ). @@ -249,8 +286,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.input.otlp.namespaces.include[0]", }, { + name: "otlp-input-namespaces-include-too-long", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-input-namespaces-include-too-long"). WithOTLPInput(true, testutils.IncludeNamespaces(veryLongString), ). @@ -261,8 +298,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { causes: 2, }, { + name: "otlp-input-namespaces-exclude-invalid", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-input-namespaces-exclude-invalid"). WithOTLPInput(true, testutils.ExcludeNamespaces("Test"), ). @@ -272,8 +309,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.input.otlp.namespaces.exclude[0]", }, { + name: "otlp-input-namespaces-exclude-too-long", pipeline: testutils.NewLogPipelineBuilder(). - WithName("otlp-input-namespaces-exclude-too-long"). WithOTLPInput(true, testutils.ExcludeNamespaces(veryLongString), ). @@ -285,8 +322,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { }, // http output { + name: "http-output-tls-missing-key", pipeline: testutils.NewLogPipelineBuilder(). - WithName("http-output-tls-missing-key"). WithHTTPOutput( testutils.HTTPHost(backendHost), testutils.HTTPPort(backendPort), @@ -299,8 +336,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.http.tls", }, { + name: "http-output-tls-missing-cert", pipeline: testutils.NewLogPipelineBuilder(). - WithName("http-output-tls-missing-cert"). WithHTTPOutput( testutils.HTTPHost(backendHost), testutils.HTTPPort(backendPort), @@ -313,6 +350,7 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output.http.tls", }, { + name: "http-output-uri-wrong-pattern", pipeline: telemetryv1alpha1.LogPipeline{ ObjectMeta: metav1.ObjectMeta{ Name: "http-output-uri-wrong-pattern", @@ -331,8 +369,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { }, // application input { + name: "application-input-namespaces-exclude-system-not-exclusive", pipeline: testutils.NewLogPipelineBuilder(). - WithName("application-input-namespaces-include-exclude-not-exclusive"). WithApplicationInput(true). WithIncludeNamespaces("ns1"). WithExcludeNamespaces("ns2"). @@ -342,8 +380,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.input.application.namespaces", }, { + name: "application-input-namespaces-include-exclude-not-exclusive", pipeline: testutils.NewLogPipelineBuilder(). - WithName("application-input-namespaces-include-system-not-exclusive"). WithApplicationInput(true). WithIncludeNamespaces("ns1"). WithSystemNamespaces(true). @@ -353,8 +391,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.input.application.namespaces", }, { + name: "application-input-containers-not-exclusive", pipeline: testutils.NewLogPipelineBuilder(). - WithName("application-input-containers-not-exclusive"). WithApplicationInput(true). WithIncludeContainers("c1"). WithExcludeContainers("c2"). @@ -364,8 +402,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.input.application.containers", }, { + name: "application-input-namespaces-include-invalid", pipeline: testutils.NewLogPipelineBuilder(). - WithName("application-input-namespaces-include-invalid"). WithApplicationInput(true). WithIncludeNamespaces("*"). WithOTLPOutput(). @@ -374,8 +412,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.input.application.namespaces.include[0]", }, { + name: "application-input-namespaces-include-too-long", pipeline: testutils.NewLogPipelineBuilder(). - WithName("application-input-namespaces-include-too-long"). WithApplicationInput(true). WithIncludeNamespaces(veryLongString). WithOTLPOutput(). @@ -385,8 +423,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { causes: 2, }, { + name: "application-input-namespaces-exclude-invalid", pipeline: testutils.NewLogPipelineBuilder(). - WithName("application-input-namespaces-exclude-invalid"). WithApplicationInput(true). WithExcludeNamespaces("a*a"). WithOTLPOutput(). @@ -395,8 +433,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.input.application.namespaces.exclude[0]", }, { + name: "application-input-namespaces-exclude-too-long", pipeline: testutils.NewLogPipelineBuilder(). - WithName("application-input-namespaces-exclude-too-long"). WithApplicationInput(true). WithExcludeNamespaces(veryLongString). WithOTLPOutput(). @@ -407,8 +445,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { }, // files validation { + name: "files-name-required", pipeline: testutils.NewLogPipelineBuilder(). - WithName("files-name-required"). WithFile("", "icke"). WithHTTPOutput(). Build(), @@ -416,8 +454,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.files[0].name", }, { + name: "files-content-required", pipeline: testutils.NewLogPipelineBuilder(). - WithName("files-content-required"). WithFile("file1", ""). WithHTTPOutput(). Build(), @@ -426,8 +464,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { }, // variables validation { + name: "variables-name-required", pipeline: testutils.NewLogPipelineBuilder(). - WithName("variables-name-required"). WithVariable("", "secName", "secNs", "secKey"). WithHTTPOutput(). Build(), @@ -435,6 +473,7 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.variables[0].name", }, { + name: "variables-valuefrom-required", pipeline: telemetryv1alpha1.LogPipeline{ ObjectMeta: metav1.ObjectMeta{ Name: "variables-valuefrom-required", @@ -456,8 +495,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { }, // legacy validations { + name: "multiple-outputs", pipeline: testutils.NewLogPipelineBuilder(). - WithName("multiple-outputs"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), ). @@ -467,8 +506,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec.output", }, { + name: "legacy-http-output-using-otlp-input", pipeline: testutils.NewLogPipelineBuilder(). - WithName("legacy-http-output-using-otlp-input"). WithHTTPOutput(). WithOTLPInput(true). Build(), @@ -476,8 +515,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec", }, { + name: "legacy-custom-output-using-otlp-input", pipeline: testutils.NewLogPipelineBuilder(). - WithName("legacy-custom-output-using-otlp-input"). WithCustomOutput("name icke"). WithOTLPInput(true). Build(), @@ -485,8 +524,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec", }, { + name: "legacy-drop-labels-with-otlp-output", pipeline: testutils.NewLogPipelineBuilder(). - WithName("legacy-drop-labels-with-otlp-output"). WithApplicationInput(true). WithDropLabels(false). WithOTLPOutput( @@ -497,8 +536,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec", }, { + name: "legacy-keep-annotations-with-otlp-output", pipeline: testutils.NewLogPipelineBuilder(). - WithName("legacy-keep-annotations-with-otlp-output"). WithApplicationInput(true). WithKeepAnnotations(false). WithOTLPOutput( @@ -509,8 +548,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec", }, { + name: "legacy-files-with-otlp-output", pipeline: testutils.NewLogPipelineBuilder(). - WithName("legacy-files-with-otlp-output"). WithApplicationInput(false). WithFile("file1.json", "icke"). WithOTLPOutput( @@ -521,8 +560,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec", }, { + name: "legacy-filters-with-otlp-output", pipeline: testutils.NewLogPipelineBuilder(). - WithName("legacy-filters-with-otlp-output"). WithApplicationInput(false). WithCustomFilter("name grep"). WithOTLPOutput( @@ -533,8 +572,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec", }, { + name: "legacy-variables-with-otlp-output", pipeline: testutils.NewLogPipelineBuilder(). - WithName("legacy-variables-with-otlp-output"). WithApplicationInput(false). WithVariable("var1", "secName", "secNs", "secKey"). WithOTLPOutput( @@ -545,8 +584,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec", }, { + name: "legacy-transform-with-http-output", pipeline: testutils.NewLogPipelineBuilder(). - WithName("legacy-transform-with-http-output"). WithApplicationInput(false). WithTransform(telemetryv1alpha1.TransformSpec{ Statements: []string{"set(attributes[\"log.level\"], \"error\")", "set(body, \"transformed1\")"}, @@ -557,8 +596,8 @@ func TestRejectLogPipelineCreation(t *testing.T) { field: "spec", }, { + name: "legacy-filter-with-http-output", pipeline: testutils.NewLogPipelineBuilder(). - WithName("legacy-filter-with-http-output"). WithApplicationInput(false). WithFilter(telemetryv1alpha1.FilterSpec{ Conditions: []string{"isMatch(log.attributes[\"log.level\"], \"error\"))"}, @@ -570,12 +609,10 @@ func TestRejectLogPipelineCreation(t *testing.T) { }, } for _, tc := range tests { - if tc.label == "" { - tc.label = suite.LabelLogsMisc - } + t.Run(tc.name, func(t *testing.T) { + suite.RegisterTestCase(t, tc.label, suite.LabelLogsMisc) - t.Run(tc.label, func(t *testing.T) { - suite.RegisterTestCase(t, tc.label) + tc.pipeline.Name = tc.name resources := []client.Object{&tc.pipeline} diff --git a/test/e2e/logs/shared/mtls_about_to_expire_cert_test.go b/test/e2e/logs/shared/mtls_about_to_expire_cert_test.go index 39b42fe222..d00fb0b622 100644 --- a/test/e2e/logs/shared/mtls_about_to_expire_cert_test.go +++ b/test/e2e/logs/shared/mtls_about_to_expire_cert_test.go @@ -50,7 +50,7 @@ func TestMTLSAboutToExpireCert_OTel(t *testing.T) { } for _, tc := range tests { t.Run(tc.label, func(t *testing.T) { - suite.RegisterTestCase(t, tc.label) + suite.RegisterTestCase(t, tc.label, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix(tc.label) @@ -64,14 +64,14 @@ func TestMTLSAboutToExpireCert_OTel(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithTLS(*serverCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithMTLS(*serverCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). WithInput(tc.inputBuilder(genNs)). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( clientCerts.CaCertPem.String(), clientCerts.ClientCertPem.String(), clientCerts.ClientKeyPem.String(), @@ -132,7 +132,7 @@ func TestMTLSAboutToExpireCert_FluentBit(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithTLS(*serverCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithMTLS(*serverCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). diff --git a/test/e2e/logs/shared/mtls_test.go b/test/e2e/logs/shared/mtls_test.go index 2ed876e6b8..1b2c073ac5 100644 --- a/test/e2e/logs/shared/mtls_test.go +++ b/test/e2e/logs/shared/mtls_test.go @@ -47,7 +47,7 @@ func TestMTLS_OTel(t *testing.T) { } for _, tc := range tests { t.Run(tc.label, func(t *testing.T) { - suite.RegisterTestCase(t, tc.label) + suite.RegisterTestCase(t, tc.label, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix(tc.label) @@ -59,14 +59,14 @@ func TestMTLS_OTel(t *testing.T) { serverCerts, clientCerts, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithTLS(*serverCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, kitbackend.WithMTLS(*serverCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). WithInput(tc.inputBuilder(genNs)). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( clientCerts.CaCertPem.String(), clientCerts.ClientCertPem.String(), clientCerts.ClientKeyPem.String()), @@ -111,7 +111,7 @@ func TestMTLS_FluentBit(t *testing.T) { serverCerts, clientCerts, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithTLS(*serverCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsFluentBit, kitbackend.WithMTLS(*serverCerts)) pipeline := testutils.NewLogPipelineBuilder(). WithName(pipelineName). diff --git a/test/e2e/logs/shared/oauth2_test.go b/test/e2e/logs/shared/oauth2_test.go new file mode 100644 index 0000000000..fe4956c471 --- /dev/null +++ b/test/e2e/logs/shared/oauth2_test.go @@ -0,0 +1,113 @@ +package shared + +import ( + "testing" + + . "github.com/onsi/gomega" + "sigs.k8s.io/controller-runtime/pkg/client" + + telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" + testutils "github.com/kyma-project/telemetry-manager/internal/utils/test" + "github.com/kyma-project/telemetry-manager/test/testkit/assert" + kitk8s "github.com/kyma-project/telemetry-manager/test/testkit/k8s" + kitkyma "github.com/kyma-project/telemetry-manager/test/testkit/kyma" + kitbackend "github.com/kyma-project/telemetry-manager/test/testkit/mocks/backend" + "github.com/kyma-project/telemetry-manager/test/testkit/mocks/oauth2mock" + "github.com/kyma-project/telemetry-manager/test/testkit/mocks/stdoutloggen" + "github.com/kyma-project/telemetry-manager/test/testkit/mocks/telemetrygen" + "github.com/kyma-project/telemetry-manager/test/testkit/suite" + "github.com/kyma-project/telemetry-manager/test/testkit/unique" +) + +func TestOAuth2(t *testing.T) { + tests := []struct { + label string + inputBuilder func(includeNs string) telemetryv1alpha1.LogPipelineInput + logGeneratorBuilder func(ns string) client.Object + expectAgent bool + }{ + { + label: suite.LabelLogAgent, + inputBuilder: func(includeNs string) telemetryv1alpha1.LogPipelineInput { + return testutils.BuildLogPipelineApplicationInput(testutils.ExtIncludeNamespaces(includeNs)) + }, + logGeneratorBuilder: func(ns string) client.Object { + return stdoutloggen.NewDeployment(ns).K8sObject() + }, + expectAgent: true, + }, + { + label: suite.LabelLogGateway, + inputBuilder: func(includeNs string) telemetryv1alpha1.LogPipelineInput { + return testutils.BuildLogPipelineOTLPInput(testutils.IncludeNamespaces(includeNs)) + }, + logGeneratorBuilder: func(ns string) client.Object { + return telemetrygen.NewDeployment(ns, telemetrygen.SignalTypeLogs).K8sObject() + }, + }, + } + + for _, tc := range tests { + t.Run(tc.label, func(t *testing.T) { + suite.RegisterTestCase(t, tc.label, suite.LabelOAuth2) + + var ( + uniquePrefix = unique.Prefix(tc.label) + pipelineName = uniquePrefix() + backendNs = uniquePrefix("backend") + genNs = uniquePrefix("gen") + ) + + oauth2server := oauth2mock.New(backendNs) + + serverCerts, _, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() + Expect(err).ToNot(HaveOccurred()) + + backend := kitbackend.New(backendNs, kitbackend.SignalTypeLogsOTel, + kitbackend.WithTLS(*serverCerts), + kitbackend.WithOIDCAuth(oauth2server.IssuerURL(), oauth2server.Audience()), + ) + + pipeline := testutils.NewLogPipelineBuilder(). + WithName(pipelineName). + WithInput(tc.inputBuilder(genNs)). + WithOTLPOutput( + testutils.OTLPEndpoint(backend.Endpoint()), + testutils.OTLPOAuth2( + testutils.OAuth2ClientID("the-mock-does-not-verify"), + testutils.OAuth2ClientSecret("the-mock-does-not-verify"), + testutils.OAuth2TokenURL(oauth2server.TokenEndpoint()), + testutils.OAuth2Params(map[string]string{"grant_type": "client_credentials"}), + ), + testutils.OTLPClientTLSFromString(serverCerts.CaCertPem.String()), + ). + Build() + + resources := []client.Object{ + kitk8s.NewNamespace(backendNs).K8sObject(), + kitk8s.NewNamespace(genNs).K8sObject(), + &pipeline, + tc.logGeneratorBuilder(genNs), + } + + resources = append(resources, oauth2server.K8sObjects()...) + resources = append(resources, backend.K8sObjects()...) + + t.Cleanup(func() { + Expect(kitk8s.DeleteObjects(resources...)).To(Succeed()) + }) + Expect(kitk8s.CreateObjects(t, resources...)).To(Succeed()) + + assert.DeploymentReady(t, oauth2server.NamespacedName()) + assert.BackendReachable(t, backend) + assert.DeploymentReady(t, kitkyma.LogGatewayName) + + if tc.expectAgent { + assert.DaemonSetReady(t, kitkyma.LogAgentName) + } + + assert.OTelLogPipelineHealthy(t, pipelineName) + assert.OTelLogsFromNamespaceDelivered(t, backend, genNs) + }) + } +} diff --git a/test/e2e/metrics/agent/runtime_input_test.go b/test/e2e/metrics/agent/runtime_input_test.go index 6f83ed2e2c..6c877302b1 100644 --- a/test/e2e/metrics/agent/runtime_input_test.go +++ b/test/e2e/metrics/agent/runtime_input_test.go @@ -28,7 +28,7 @@ import ( ) func TestRuntimeInput(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelGardener, suite.LabelMetricAgentSetC) + suite.RegisterTestCase(t, suite.LabelGardener, suite.LabelMetricAgentSetB) const ( podNetworkErrorsMetric = "k8s.pod.network.errors" diff --git a/test/e2e/metrics/agent/runtime_node_namespace_test.go b/test/e2e/metrics/agent/runtime_node_namespace_test.go index 7ca9aa0566..6b4f93002c 100644 --- a/test/e2e/metrics/agent/runtime_node_namespace_test.go +++ b/test/e2e/metrics/agent/runtime_node_namespace_test.go @@ -19,7 +19,7 @@ import ( ) func TestRuntimeNodeNamespace(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelMetricAgentSetC) + suite.RegisterTestCase(t, suite.LabelMetricAgentSetB) var ( uniquePrefix = unique.Prefix() diff --git a/test/e2e/metrics/agent/service_name_test.go b/test/e2e/metrics/agent/service_name_test.go index 5275bde4b4..71fdf041af 100644 --- a/test/e2e/metrics/agent/service_name_test.go +++ b/test/e2e/metrics/agent/service_name_test.go @@ -18,7 +18,7 @@ import ( ) func TestServiceName(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelMetricAgentSetC) + suite.RegisterTestCase(t, suite.LabelMetricAgentSetB) const ( jobName = "job" diff --git a/test/e2e/metrics/misc/mtls_cert_key_pair_dont_match_test.go b/test/e2e/metrics/misc/mtls_cert_key_pair_dont_match_test.go index c496cd9969..5e33775db8 100644 --- a/test/e2e/metrics/misc/mtls_cert_key_pair_dont_match_test.go +++ b/test/e2e/metrics/misc/mtls_cert_key_pair_dont_match_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSCertKeyPairDontMatch(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelMetricsMisc) + suite.RegisterTestCase(t, suite.LabelMetricsMisc, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -32,13 +32,13 @@ func TestMTLSCertKeyPairDontMatch(t *testing.T) { _, clientCertsCreatedAgain, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithTLS(*serverCertsDefault)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithMTLS(*serverCertsDefault)) pipeline := testutils.NewMetricPipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( clientCertsDefault.CaCertPem.String(), clientCertsDefault.ClientCertPem.String(), clientCertsCreatedAgain.ClientKeyPem.String(), // Use different key diff --git a/test/e2e/metrics/misc/mtls_expired_cert_test.go b/test/e2e/metrics/misc/mtls_expired_cert_test.go index 02631e48e6..8b2e6ec697 100644 --- a/test/e2e/metrics/misc/mtls_expired_cert_test.go +++ b/test/e2e/metrics/misc/mtls_expired_cert_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSExpiredCert(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelMetricsMisc) + suite.RegisterTestCase(t, suite.LabelMetricsMisc, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -31,13 +31,13 @@ func TestMTLSExpiredCert(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithTLS(*expiredServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithMTLS(*expiredServerCerts)) pipeline := testutils.NewMetricPipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( expiredClientCerts.CaCertPem.String(), expiredClientCerts.ClientCertPem.String(), expiredClientCerts.ClientKeyPem.String(), diff --git a/test/e2e/metrics/misc/mtls_invalid_ca_test.go b/test/e2e/metrics/misc/mtls_invalid_ca_test.go index 76a3790cfa..a96a98330b 100644 --- a/test/e2e/metrics/misc/mtls_invalid_ca_test.go +++ b/test/e2e/metrics/misc/mtls_invalid_ca_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSInvalidCA(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelMetricsMisc) + suite.RegisterTestCase(t, suite.LabelMetricsMisc, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -31,13 +31,13 @@ func TestMTLSInvalidCA(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithTLS(*invalidServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithMTLS(*invalidServerCerts)) pipeline := testutils.NewMetricPipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( invalidClientCerts.CaCertPem.String(), invalidClientCerts.ClientCertPem.String(), invalidClientCerts.ClientKeyPem.String(), diff --git a/test/e2e/metrics/misc/mtls_invalid_cert_test.go b/test/e2e/metrics/misc/mtls_invalid_cert_test.go index 9265192702..479ccd8e73 100644 --- a/test/e2e/metrics/misc/mtls_invalid_cert_test.go +++ b/test/e2e/metrics/misc/mtls_invalid_cert_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSInvalidCert(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelMetricsMisc) + suite.RegisterTestCase(t, suite.LabelMetricsMisc, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -31,13 +31,13 @@ func TestMTLSInvalidCert(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithTLS(*invalidServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithMTLS(*invalidServerCerts)) pipeline := testutils.NewMetricPipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( invalidClientCerts.CaCertPem.String(), invalidClientCerts.ClientCertPem.String(), invalidClientCerts.ClientKeyPem.String(), diff --git a/test/e2e/metrics/misc/mtls_missing_key_test.go b/test/e2e/metrics/misc/mtls_missing_key_test.go index 47cb6b4e78..4cc63fda55 100644 --- a/test/e2e/metrics/misc/mtls_missing_key_test.go +++ b/test/e2e/metrics/misc/mtls_missing_key_test.go @@ -31,7 +31,7 @@ func TestMTLSMissingKey(t *testing.T) { serverCerts, clientCerts, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithTLS(*serverCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithMTLS(*serverCerts)) pipeline := testutils.NewMetricPipelineBuilder(). WithName(pipelineName). diff --git a/test/e2e/metrics/misc/reject_creation_test.go b/test/e2e/metrics/misc/reject_creation_test.go index ad0072462b..54b420736f 100644 --- a/test/e2e/metrics/misc/reject_creation_test.go +++ b/test/e2e/metrics/misc/reject_creation_test.go @@ -7,7 +7,6 @@ import ( . "github.com/onsi/gomega" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" @@ -27,6 +26,7 @@ func TestRejectPipelineCreation(t *testing.T) { var backenEndpoint = backendHost + ":" + strconv.Itoa(backendPort) tests := []struct { + name string pipeline telemetryv1alpha1.MetricPipeline errorMsg string field string @@ -34,10 +34,8 @@ func TestRejectPipelineCreation(t *testing.T) { }{ // output general { + name: "no-output", pipeline: telemetryv1alpha1.MetricPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "no-output", - }, Spec: telemetryv1alpha1.MetricPipelineSpec{}, }, errorMsg: "must be of type object", @@ -45,10 +43,8 @@ func TestRejectPipelineCreation(t *testing.T) { causes: 2, }, { + name: "valuefrom-accepts-only-one-option", pipeline: telemetryv1alpha1.MetricPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "valuefrom-accepts-only-one-option", - }, Spec: telemetryv1alpha1.MetricPipelineSpec{ Output: telemetryv1alpha1.MetricPipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -70,10 +66,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.output.otlp.endpoint", }, { + name: "secretkeyref-requires-key", pipeline: telemetryv1alpha1.MetricPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretkeyref-requires-key", - }, Spec: telemetryv1alpha1.MetricPipelineSpec{ Output: telemetryv1alpha1.MetricPipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -93,10 +87,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.output.otlp.endpoint.valueFrom.secretKeyRef.key", }, { + name: "secretkeyref-requires-namespace", pipeline: telemetryv1alpha1.MetricPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretkeyref-requires-namespace", - }, Spec: telemetryv1alpha1.MetricPipelineSpec{ Output: telemetryv1alpha1.MetricPipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -116,10 +108,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.output.otlp.endpoint.valueFrom.secretKeyRef.namespace", }, { + name: "secretkeyref-requires-name", pipeline: telemetryv1alpha1.MetricPipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretkeyref-requires-name", - }, Spec: telemetryv1alpha1.MetricPipelineSpec{ Output: telemetryv1alpha1.MetricPipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -140,8 +130,8 @@ func TestRejectPipelineCreation(t *testing.T) { }, // otlp output { + name: "otlp-output-with-default-proto-and-path", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-output-with-default-proto-and-path"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPEndpointPath("/v1/dummy"), @@ -151,8 +141,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.output.otlp", }, { + name: "otlp-output-with-grpc-proto-and-path", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-output-with-grpc-proto-and-path"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPEndpointPath("/v1/dummy"), @@ -163,8 +153,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.output.otlp", }, { + name: "otlp-output-with-non-valid-proto", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-output-with-non-valid-proto"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPProtocol("icke"), @@ -175,8 +165,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.output.otlp.protocol", }, { + name: "otlp-output-basic-auth-secretref-missing-password-key", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-output-basic-auth-secretref-missing-password-key"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPBasicAuthFromSecret("name", "namespace", "user", ""), @@ -186,8 +176,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.output.otlp.authentication.basic.password.valueFrom.secretKeyRef.key", }, { + name: "otlp-output-basic-auth-secretref-missing-user-key", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-output-basic-auth-secretref-missing-user-key"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPBasicAuthFromSecret("name", "namespace", "", "password"), @@ -197,8 +187,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.output.otlp.authentication.basic.user.valueFrom.secretKeyRef.key", }, { + name: "otlp-output-tls-missing-key", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-output-tls-missing-key"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPClientTLS(&telemetryv1alpha1.OTLPTLS{ @@ -211,8 +201,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.output.otlp.tls", }, { + name: "otlp-output-tls-missing-cert", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-output-tls-missing-cert"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPClientTLS(&telemetryv1alpha1.OTLPTLS{ @@ -224,10 +214,57 @@ func TestRejectPipelineCreation(t *testing.T) { errorMsg: "Can define either both 'cert' and 'key', or neither", field: "spec.output.otlp.tls", }, + { + name: "otlp-output-oauth2-invalid-token-url", + pipeline: testutils.NewMetricPipelineBuilder(). + WithOTLPOutput( + testutils.OTLPEndpoint(backenEndpoint), + testutils.OTLPOAuth2( + testutils.OAuth2ClientSecret("clientsecret"), + testutils.OAuth2ClientID("clientid"), + testutils.OAuth2TokenURL("../not-a-url"), + ), + ). + Build(), + errorMsg: "Invalid value: \"object\": 'tokenURL' must be a valid URL", + field: "spec.output.otlp.authentication.oauth2.tokenURL", + }, + { + name: "otlp-output-oauth2-missing-client-id", + pipeline: testutils.NewMetricPipelineBuilder(). + WithOTLPOutput( + testutils.OTLPEndpoint(backenEndpoint), + testutils.OTLPOAuth2( + testutils.OAuth2ClientSecret("clientsecret"), + testutils.OAuth2TokenURL("https://auth.example.com/token"), + ), + ). + Build(), + errorMsg: "Invalid value: \"object\": no such key: value evaluating rule: 'clientID' missing", + field: "spec.output.otlp.authentication.oauth2.clientID", + }, + { + name: "otlp-output-oauth2-insecure", + pipeline: testutils.NewMetricPipelineBuilder(). + WithOTLPOutput( + testutils.OTLPEndpoint(backenEndpoint), + testutils.OTLPOAuth2( + testutils.OAuth2ClientID("clientid"), + testutils.OAuth2ClientSecret("clientsecret"), + testutils.OAuth2TokenURL("https://auth.example.com/token"), + ), + testutils.OTLPClientTLS(&telemetryv1alpha1.OTLPTLS{ + Insecure: true, + }), + ). + Build(), + errorMsg: "OAuth2 authentication requires TLS when using gRPC protocol", + field: "spec.output.otlp", + }, // otlp input { + name: "otlp-input-namespaces-not-exclusive", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-input-namespaces-not-exclusive"). WithOTLPInput(true, testutils.ExcludeNamespaces("ns1"), testutils.IncludeNamespaces("ns2"), @@ -238,8 +275,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.input.otlp.namespaces", }, { + name: "otlp-input-namespaces-include-invalid", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-input-namespaces-include-invalid"). WithOTLPInput(true, testutils.IncludeNamespaces("aa!"), ). @@ -249,8 +286,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.input.otlp.namespaces.include[0]", }, { + name: "otlp-input-namespaces-exclude-invalid", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("otlp-input-namespaces-exclude-invalid"). WithOTLPInput(true, testutils.ExcludeNamespaces("aa!"), ). @@ -261,8 +298,8 @@ func TestRejectPipelineCreation(t *testing.T) { }, // prometheus input { + name: "prometheus-input-namespaces-include-invalid", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("prometheus-input-namespaces-include-invalid"). WithPrometheusInput(true, testutils.IncludeNamespaces("aa-"), ). @@ -272,8 +309,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.input.prometheus.namespaces.include[0]", }, { + name: "prometheus-input-namespaces-exclude-invalid", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("prometheus-input-namespaces-exclude-invalid"). WithPrometheusInput(true, testutils.ExcludeNamespaces("-aa"), ). @@ -284,8 +321,8 @@ func TestRejectPipelineCreation(t *testing.T) { }, // istio input { + name: "istio-input-namespaces-include-invalid", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("istio-input-namespaces-include-invalid"). WithIstioInput(true, testutils.IncludeNamespaces("#"), ). @@ -295,8 +332,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.input.istio.namespaces.include[0]", }, { + name: "istio-input-namespaces-exclude-invalid", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("istio-input-namespaces-exclude-invalid"). WithIstioInput(true, testutils.ExcludeNamespaces("/"), ). @@ -307,8 +344,8 @@ func TestRejectPipelineCreation(t *testing.T) { }, // runtime input { + name: "istio-input-namespaces-include-invalid", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("istio-input-namespaces-include-invalid"). WithRuntimeInput(true, testutils.IncludeNamespaces("aa", "bb", "??"), ). @@ -318,8 +355,8 @@ func TestRejectPipelineCreation(t *testing.T) { field: "spec.input.runtime.namespaces.include[2]", }, { + name: "istio-input-namespaces-exclude-invalid", pipeline: testutils.NewMetricPipelineBuilder(). - WithName("istio-input-namespaces-exclude-invalid"). WithRuntimeInput(true, testutils.ExcludeNamespaces("öö", "aa", "bb"), ). @@ -330,9 +367,11 @@ func TestRejectPipelineCreation(t *testing.T) { }, } for _, tc := range tests { - t.Run(suite.LabelMisc, func(t *testing.T) { + t.Run(tc.name, func(t *testing.T) { suite.RegisterTestCase(t, suite.LabelMisc) + tc.pipeline.Name = tc.name + resources := []client.Object{&tc.pipeline} t.Cleanup(func() { diff --git a/test/e2e/metrics/shared/mtls_about_to_expire_cert_test.go b/test/e2e/metrics/shared/mtls_about_to_expire_cert_test.go index 4ba5fc0468..3e918d1a82 100644 --- a/test/e2e/metrics/shared/mtls_about_to_expire_cert_test.go +++ b/test/e2e/metrics/shared/mtls_about_to_expire_cert_test.go @@ -58,7 +58,7 @@ func TestMTLSAboutToExpireCert(t *testing.T) { for _, tc := range tests { t.Run(tc.label, func(t *testing.T) { - suite.RegisterTestCase(t, tc.label) + suite.RegisterTestCase(t, tc.label, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix(tc.label) @@ -72,14 +72,14 @@ func TestMTLSAboutToExpireCert(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithTLS(*serverCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithMTLS(*serverCerts)) pipeline := testutils.NewMetricPipelineBuilder(). WithName(pipelineName). WithInput(tc.inputBuilder(genNs)). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( clientCerts.CaCertPem.String(), clientCerts.ClientCertPem.String(), clientCerts.ClientKeyPem.String(), diff --git a/test/e2e/metrics/shared/mtls_test.go b/test/e2e/metrics/shared/mtls_test.go index 6a1d98ae87..ab433e2696 100644 --- a/test/e2e/metrics/shared/mtls_test.go +++ b/test/e2e/metrics/shared/mtls_test.go @@ -27,7 +27,7 @@ func TestMTLS(t *testing.T) { generatorBuilder func(ns string) []client.Object }{ { - label: suite.LabelMetricAgentSetB, + label: suite.LabelMetricAgentSetC, inputBuilder: func(includeNs string) telemetryv1alpha1.MetricPipelineInput { return testutils.BuildMetricPipelineRuntimeInput(testutils.IncludeNamespaces(includeNs)) }, @@ -55,7 +55,7 @@ func TestMTLS(t *testing.T) { for _, tc := range tests { t.Run(tc.label, func(t *testing.T) { - suite.RegisterTestCase(t, tc.label) + suite.RegisterTestCase(t, suite.LabelMTLS, tc.label) var ( uniquePrefix = unique.Prefix(tc.label) @@ -67,14 +67,14 @@ func TestMTLS(t *testing.T) { serverCerts, clientCerts, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithTLS(*serverCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, kitbackend.WithMTLS(*serverCerts)) pipeline := testutils.NewMetricPipelineBuilder(). WithName(pipelineName). WithInput(tc.inputBuilder(genNs)). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( clientCerts.CaCertPem.String(), clientCerts.ClientCertPem.String(), clientCerts.ClientKeyPem.String(), diff --git a/test/e2e/metrics/shared/multi_pipeline_broken_test.go b/test/e2e/metrics/shared/multi_pipeline_broken_test.go index bd18d049a7..baae82170b 100644 --- a/test/e2e/metrics/shared/multi_pipeline_broken_test.go +++ b/test/e2e/metrics/shared/multi_pipeline_broken_test.go @@ -29,7 +29,7 @@ func TestMultiPipelineBroken(t *testing.T) { generatorBuilder func(ns string) []client.Object }{ { - label: suite.LabelMetricAgentSetB, + label: suite.LabelMetricAgentSetC, inputBuilder: func(includeNs string) telemetryv1alpha1.MetricPipelineInput { return testutils.BuildMetricPipelineRuntimeInput(testutils.IncludeNamespaces(includeNs)) }, diff --git a/test/e2e/metrics/shared/multi_pipeline_fanout_test.go b/test/e2e/metrics/shared/multi_pipeline_fanout_test.go index 322f9e3946..ab00d135bd 100644 --- a/test/e2e/metrics/shared/multi_pipeline_fanout_test.go +++ b/test/e2e/metrics/shared/multi_pipeline_fanout_test.go @@ -24,7 +24,7 @@ import ( ) func TestMultiPipelineFanout_Agent(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelMetricAgentSetB) + suite.RegisterTestCase(t, suite.LabelMetricAgentSetC) var ( uniquePrefix = unique.Prefix() diff --git a/test/e2e/metrics/shared/namespace_selector_test.go b/test/e2e/metrics/shared/namespace_selector_test.go index bccb06b8c7..06cf827bb5 100644 --- a/test/e2e/metrics/shared/namespace_selector_test.go +++ b/test/e2e/metrics/shared/namespace_selector_test.go @@ -27,7 +27,7 @@ func TestNamespaceSelector(t *testing.T) { generatorBuilder func(ns1, ns2 string) []client.Object }{ { - label: suite.LabelMetricAgentSetB, + label: suite.LabelMetricAgentSetC, inputBuilder: func(includeNss, excludeNss []string) telemetryv1alpha1.MetricPipelineInput { var opts []testutils.NamespaceSelectorOptions if len(includeNss) > 0 { diff --git a/test/e2e/metrics/shared/oauth2_test.go b/test/e2e/metrics/shared/oauth2_test.go new file mode 100644 index 0000000000..e6b6f505e1 --- /dev/null +++ b/test/e2e/metrics/shared/oauth2_test.go @@ -0,0 +1,131 @@ +package shared + +import ( + "fmt" + "testing" + + . "github.com/onsi/gomega" + "sigs.k8s.io/controller-runtime/pkg/client" + + telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" + "github.com/kyma-project/telemetry-manager/internal/otelcollector/ports" + testutils "github.com/kyma-project/telemetry-manager/internal/utils/test" + "github.com/kyma-project/telemetry-manager/test/testkit/assert" + kitk8s "github.com/kyma-project/telemetry-manager/test/testkit/k8s" + kitkyma "github.com/kyma-project/telemetry-manager/test/testkit/kyma" + "github.com/kyma-project/telemetry-manager/test/testkit/metrics/runtime" + kitbackend "github.com/kyma-project/telemetry-manager/test/testkit/mocks/backend" + "github.com/kyma-project/telemetry-manager/test/testkit/mocks/oauth2mock" + "github.com/kyma-project/telemetry-manager/test/testkit/mocks/prommetricgen" + "github.com/kyma-project/telemetry-manager/test/testkit/mocks/telemetrygen" + "github.com/kyma-project/telemetry-manager/test/testkit/suite" + "github.com/kyma-project/telemetry-manager/test/testkit/unique" +) + +func TestOAuth2(t *testing.T) { + tests := []struct { + label string + inputBuilder func(includeNs string) telemetryv1alpha1.MetricPipelineInput + generatorBuilder func(ns string) []client.Object + }{ + { + label: suite.LabelMetricAgentSetC, + inputBuilder: func(includeNs string) telemetryv1alpha1.MetricPipelineInput { + return testutils.BuildMetricPipelineRuntimeInput(testutils.IncludeNamespaces(includeNs)) + }, + generatorBuilder: func(ns string) []client.Object { + generator := prommetricgen.New(ns) + + return []client.Object{ + generator.Pod().WithPrometheusAnnotations(prommetricgen.SchemeHTTP).K8sObject(), + generator.Service().WithPrometheusAnnotations(prommetricgen.SchemeHTTP).K8sObject(), + } + }, + }, + { + label: suite.LabelMetricGatewaySetC, + inputBuilder: func(includeNs string) telemetryv1alpha1.MetricPipelineInput { + return testutils.BuildMetricPipelineOTLPInput(testutils.IncludeNamespaces(includeNs)) + }, + generatorBuilder: func(ns string) []client.Object { + return []client.Object{ + telemetrygen.NewPod(ns, telemetrygen.SignalTypeMetrics).K8sObject(), + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.label, func(t *testing.T) { + suite.RegisterTestCase(t, tc.label, suite.LabelOAuth2) + + var ( + uniquePrefix = unique.Prefix(tc.label, suite.LabelOAuth2) + pipelineName = uniquePrefix() + backendNs = uniquePrefix("backend") + genNs = uniquePrefix("gen") + ) + + oauth2server := oauth2mock.New(backendNs) + + serverCerts, _, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() + Expect(err).ToNot(HaveOccurred()) + + backend := kitbackend.New(backendNs, kitbackend.SignalTypeMetrics, + kitbackend.WithTLS(*serverCerts), + kitbackend.WithOIDCAuth(oauth2server.IssuerURL(), oauth2server.Audience()), + ) + + pipeline := testutils.NewMetricPipelineBuilder(). + WithName(pipelineName). + WithInput(tc.inputBuilder(genNs)). + WithOTLPOutput( + testutils.OTLPEndpoint(fmt.Sprintf("%s:%d", backend.Host(), backend.Port())), + testutils.OTLPOAuth2( + testutils.OAuth2ClientID("the-mock-does-not-verify"), + testutils.OAuth2ClientSecret("the-mock-does-not-verify"), + testutils.OAuth2TokenURL(oauth2server.TokenEndpoint()), + testutils.OAuth2Params(map[string]string{"grant_type": "client_credentials"}), + ), + testutils.OTLPClientTLSFromString(serverCerts.CaCertPem.String()), + ). + Build() + + resources := []client.Object{ + kitk8s.NewNamespace(backendNs).K8sObject(), + kitk8s.NewNamespace(genNs).K8sObject(), + &pipeline, + } + resources = append(resources, tc.generatorBuilder(genNs)...) + resources = append(resources, oauth2server.K8sObjects()...) + resources = append(resources, backend.K8sObjects()...) + + t.Cleanup(func() { + Expect(kitk8s.DeleteObjects(resources...)).To(Succeed()) + }) + Expect(kitk8s.CreateObjects(t, resources...)).To(Succeed()) + + assert.DeploymentReady(t, oauth2server.NamespacedName()) + assert.BackendReachable(t, backend) + assert.DeploymentReady(t, kitkyma.MetricGatewayName) + + if suite.ExpectAgent(tc.label) { + assert.DaemonSetReady(t, kitkyma.MetricAgentName) + } + + assert.MetricPipelineHealthy(t, pipelineName) + + if suite.ExpectAgent(tc.label) { + assert.MetricsFromNamespaceDelivered(t, backend, genNs, runtime.DefaultMetricsNames) + + agentMetricsURL := suite.ProxyClient.ProxyURLForService(kitkyma.MetricAgentMetricsService.Namespace, kitkyma.MetricAgentMetricsService.Name, "metrics", ports.Metrics) + assert.EmitsOTelCollectorMetrics(t, agentMetricsURL) + } else { + assert.MetricsFromNamespaceDelivered(t, backend, genNs, telemetrygen.MetricNames) + + gatewayMetricsURL := suite.ProxyClient.ProxyURLForService(kitkyma.MetricGatewayMetricsService.Namespace, kitkyma.MetricGatewayMetricsService.Name, "metrics", ports.Metrics) + assert.EmitsOTelCollectorMetrics(t, gatewayMetricsURL) + } + }) + } +} diff --git a/test/e2e/misc/telemetry_test.go b/test/e2e/misc/telemetry_test.go index 9e5f9171b4..d089f5edf4 100644 --- a/test/e2e/misc/telemetry_test.go +++ b/test/e2e/misc/telemetry_test.go @@ -62,7 +62,14 @@ func TestTelemetry(t *testing.T) { }) Expect(kitk8s.CreateObjects(t, resources...)).To(Succeed()) - Eventually(func(g Gomega) { + assertTelemtryCRExistsAndHasCorrectEndpointsInStatus(logGRPCEndpoint, logHTTPEndpoint, traceGRPCEndpoint, traceHTTPEndpoint, metricGRPCEndpoint, metricHTTPEndpoint) + assertValidatingWebhookConfiguration() + assertWebhookCA() + assertWebhookSecretReconcilation() +} + +func assertTelemtryCRExistsAndHasCorrectEndpointsInStatus(logGRPCEndpoint string, logHTTPEndpoint string, traceGRPCEndpoint string, traceHTTPEndpoint string, metricGRPCEndpoint string, metricHTTPEndpoint string) bool { + return Eventually(func(g Gomega) { var telemetry operatorv1alpha1.Telemetry g.Expect(suite.K8sClient.Get(suite.Ctx, kitkyma.TelemetryName, &telemetry)).Should(Succeed()) @@ -78,10 +85,6 @@ func TestTelemetry(t *testing.T) { g.Expect(telemetry.Status.Endpoints.Metrics.GRPC).Should(Equal(metricGRPCEndpoint)) g.Expect(telemetry.Status.Endpoints.Metrics.HTTP).Should(Equal(metricHTTPEndpoint)) }, periodic.EventuallyTimeout, periodic.DefaultInterval).Should(Succeed()) - - assertValidatingWebhookConfiguration() - assertWebhookCA() - assertWebhookSecretReconcilation() } func TestTelemetryWarning(t *testing.T) { diff --git a/test/e2e/traces/mtls_about_to_expire_cert_test.go b/test/e2e/traces/mtls_about_to_expire_cert_test.go index 06a010fadc..b00d8ef2f4 100644 --- a/test/e2e/traces/mtls_about_to_expire_cert_test.go +++ b/test/e2e/traces/mtls_about_to_expire_cert_test.go @@ -20,7 +20,7 @@ import ( ) func TestMTLSAboutToExpireCert(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelTraces) + suite.RegisterTestCase(t, suite.LabelTraces, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -34,13 +34,13 @@ func TestMTLSAboutToExpireCert(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithTLS(*serverCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithMTLS(*serverCerts)) pipeline := testutils.NewTracePipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( clientCerts.CaCertPem.String(), clientCerts.ClientCertPem.String(), clientCerts.ClientKeyPem.String(), diff --git a/test/e2e/traces/mtls_cert_key_pair_dont_match_test.go b/test/e2e/traces/mtls_cert_key_pair_dont_match_test.go index 25ae108848..4616da115b 100644 --- a/test/e2e/traces/mtls_cert_key_pair_dont_match_test.go +++ b/test/e2e/traces/mtls_cert_key_pair_dont_match_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSCertKeyPairDontMatch(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelTraces) + suite.RegisterTestCase(t, suite.LabelTraces, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -32,13 +32,13 @@ func TestMTLSCertKeyPairDontMatch(t *testing.T) { _, clientCertsCreatedAgain, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithTLS(*serverCertsDefault)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithMTLS(*serverCertsDefault)) pipeline := testutils.NewTracePipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( clientCertsDefault.CaCertPem.String(), clientCertsDefault.ClientCertPem.String(), clientCertsCreatedAgain.ClientKeyPem.String(), // Use different key diff --git a/test/e2e/traces/mtls_expired_cert_test.go b/test/e2e/traces/mtls_expired_cert_test.go index 2235150033..5cb542114e 100644 --- a/test/e2e/traces/mtls_expired_cert_test.go +++ b/test/e2e/traces/mtls_expired_cert_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSExpiredCert(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelTraces) + suite.RegisterTestCase(t, suite.LabelTraces, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -31,13 +31,13 @@ func TestMTLSExpiredCert(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithTLS(*expiredServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithMTLS(*expiredServerCerts)) pipeline := testutils.NewTracePipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( expiredClientCerts.CaCertPem.String(), expiredClientCerts.ClientCertPem.String(), expiredClientCerts.ClientKeyPem.String(), diff --git a/test/e2e/traces/mtls_invalid_ca_test.go b/test/e2e/traces/mtls_invalid_ca_test.go index 7316d7d20e..859922cd6e 100644 --- a/test/e2e/traces/mtls_invalid_ca_test.go +++ b/test/e2e/traces/mtls_invalid_ca_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSInvalidCA(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelTraces) + suite.RegisterTestCase(t, suite.LabelTraces, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -31,13 +31,13 @@ func TestMTLSInvalidCA(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithTLS(*invalidServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithMTLS(*invalidServerCerts)) pipeline := testutils.NewTracePipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( invalidClientCerts.CaCertPem.String(), invalidClientCerts.ClientCertPem.String(), invalidClientCerts.ClientKeyPem.String(), diff --git a/test/e2e/traces/mtls_invalid_cert_test.go b/test/e2e/traces/mtls_invalid_cert_test.go index ed30bafb1e..1ff79c1ce5 100644 --- a/test/e2e/traces/mtls_invalid_cert_test.go +++ b/test/e2e/traces/mtls_invalid_cert_test.go @@ -18,7 +18,7 @@ import ( ) func TestMTLSInvalidCert(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelTraces) + suite.RegisterTestCase(t, suite.LabelTraces, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -31,13 +31,13 @@ func TestMTLSInvalidCert(t *testing.T) { Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithTLS(*invalidServerCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithMTLS(*invalidServerCerts)) pipeline := testutils.NewTracePipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( invalidClientCerts.CaCertPem.String(), invalidClientCerts.ClientCertPem.String(), invalidClientCerts.ClientKeyPem.String(), diff --git a/test/e2e/traces/mtls_test.go b/test/e2e/traces/mtls_test.go index a900bd6c7f..6f16093572 100644 --- a/test/e2e/traces/mtls_test.go +++ b/test/e2e/traces/mtls_test.go @@ -17,7 +17,7 @@ import ( ) func TestMTLS(t *testing.T) { - suite.RegisterTestCase(t, suite.LabelTraces) + suite.RegisterTestCase(t, suite.LabelTraces, suite.LabelMTLS) var ( uniquePrefix = unique.Prefix() @@ -29,13 +29,13 @@ func TestMTLS(t *testing.T) { serverCerts, clientCerts, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() Expect(err).ToNot(HaveOccurred()) - backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithTLS(*serverCerts)) + backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, kitbackend.WithMTLS(*serverCerts)) pipeline := testutils.NewTracePipelineBuilder(). WithName(pipelineName). WithOTLPOutput( testutils.OTLPEndpoint(backend.Endpoint()), - testutils.OTLPClientTLSFromString( + testutils.OTLPClientMTLSFromString( clientCerts.CaCertPem.String(), clientCerts.ClientCertPem.String(), clientCerts.ClientKeyPem.String(), diff --git a/test/e2e/traces/oauth2_test.go b/test/e2e/traces/oauth2_test.go new file mode 100644 index 0000000000..7ebcfdf8fd --- /dev/null +++ b/test/e2e/traces/oauth2_test.go @@ -0,0 +1,76 @@ +package traces + +import ( + "testing" + + . "github.com/onsi/gomega" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kyma-project/telemetry-manager/internal/otelcollector/ports" + testutils "github.com/kyma-project/telemetry-manager/internal/utils/test" + "github.com/kyma-project/telemetry-manager/test/testkit/assert" + kitk8s "github.com/kyma-project/telemetry-manager/test/testkit/k8s" + kitkyma "github.com/kyma-project/telemetry-manager/test/testkit/kyma" + kitbackend "github.com/kyma-project/telemetry-manager/test/testkit/mocks/backend" + "github.com/kyma-project/telemetry-manager/test/testkit/mocks/oauth2mock" + "github.com/kyma-project/telemetry-manager/test/testkit/mocks/telemetrygen" + "github.com/kyma-project/telemetry-manager/test/testkit/suite" + "github.com/kyma-project/telemetry-manager/test/testkit/unique" +) + +func TestOAuth2(t *testing.T) { + suite.RegisterTestCase(t, suite.LabelTraces, suite.LabelOAuth2) + + var ( + uniquePrefix = unique.Prefix() + pipelineName = uniquePrefix() + backendNs = uniquePrefix("backend") + genNs = uniquePrefix("gen") + ) + + oauth2server := oauth2mock.New(backendNs) + + serverCerts, _, err := testutils.NewCertBuilder(kitbackend.DefaultName, backendNs).Build() + Expect(err).ToNot(HaveOccurred()) + + backend := kitbackend.New(backendNs, kitbackend.SignalTypeTraces, + kitbackend.WithTLS(*serverCerts), + kitbackend.WithOIDCAuth(oauth2server.IssuerURL(), oauth2server.Audience()), + ) + pipeline := testutils.NewTracePipelineBuilder(). + WithName(pipelineName). + WithOTLPOutput( + testutils.OTLPEndpoint(backend.Endpoint()), + testutils.OTLPOAuth2( + testutils.OAuth2ClientID("the-mock-does-not-verify"), + testutils.OAuth2ClientSecret("the-mock-does-not-verify"), + testutils.OAuth2TokenURL(oauth2server.TokenEndpoint()), + testutils.OAuth2Params(map[string]string{"grant_type": "client_credentials"}), + ), + testutils.OTLPClientTLSFromString(serverCerts.CaCertPem.String()), + ). + Build() + + resources := []client.Object{ + kitk8s.NewNamespace(backendNs).K8sObject(), + kitk8s.NewNamespace(genNs).K8sObject(), + &pipeline, + telemetrygen.NewPod(genNs, telemetrygen.SignalTypeTraces).K8sObject(), + } + resources = append(resources, backend.K8sObjects()...) + resources = append(resources, oauth2server.K8sObjects()...) + + t.Cleanup(func() { + Expect(kitk8s.DeleteObjects(resources...)).To(Succeed()) + }) + Expect(kitk8s.CreateObjects(t, resources...)).To(Succeed()) + + assert.DeploymentReady(t, oauth2server.NamespacedName()) + assert.BackendReachable(t, backend) + assert.DeploymentReady(t, kitkyma.TraceGatewayName) + assert.TracePipelineHealthy(t, pipelineName) + assert.TracesFromNamespaceDelivered(t, backend, genNs) + + gatewayMetricsURL := suite.ProxyClient.ProxyURLForService(kitkyma.TraceGatewayMetricsService.Namespace, kitkyma.TraceGatewayMetricsService.Name, "metrics", ports.Metrics) + assert.EmitsOTelCollectorMetrics(t, gatewayMetricsURL) +} diff --git a/test/e2e/traces/reject_creation_test.go b/test/e2e/traces/reject_creation_test.go index 01172fffcc..25dc66797b 100644 --- a/test/e2e/traces/reject_creation_test.go +++ b/test/e2e/traces/reject_creation_test.go @@ -7,7 +7,6 @@ import ( . "github.com/onsi/gomega" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" telemetryv1alpha1 "github.com/kyma-project/telemetry-manager/apis/telemetry/v1alpha1" @@ -25,18 +24,16 @@ func TestRejectTracePipelineCreation(t *testing.T) { var backenEndpoint = backendHost + ":" + strconv.Itoa(backendPort) tests := []struct { + name string pipeline telemetryv1alpha1.TracePipeline errorMsg string field string causes int - label string }{ // output general { + name: "no-output", pipeline: telemetryv1alpha1.TracePipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "no-output", - }, Spec: telemetryv1alpha1.TracePipelineSpec{}, }, errorMsg: "must be of type object", @@ -44,10 +41,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { causes: 2, }, { + name: "valuefrom-accepts-only-one-option", pipeline: telemetryv1alpha1.TracePipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "valuefrom-accepts-only-one-option", - }, Spec: telemetryv1alpha1.TracePipelineSpec{ Output: telemetryv1alpha1.TracePipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -69,10 +64,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { field: "spec.output.otlp.endpoint", }, { + name: "secretkeyref-requires-key", pipeline: telemetryv1alpha1.TracePipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretkeyref-requires-key", - }, Spec: telemetryv1alpha1.TracePipelineSpec{ Output: telemetryv1alpha1.TracePipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -92,10 +85,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { field: "spec.output.otlp.endpoint.valueFrom.secretKeyRef.key", }, { + name: "secretkeyref-requires-namespace", pipeline: telemetryv1alpha1.TracePipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretkeyref-requires-namespace", - }, Spec: telemetryv1alpha1.TracePipelineSpec{ Output: telemetryv1alpha1.TracePipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -115,10 +106,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { field: "spec.output.otlp.endpoint.valueFrom.secretKeyRef.namespace", }, { + name: "secretkeyref-requires-name", pipeline: telemetryv1alpha1.TracePipeline{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretkeyref-requires-name", - }, Spec: telemetryv1alpha1.TracePipelineSpec{ Output: telemetryv1alpha1.TracePipelineOutput{ OTLP: &telemetryv1alpha1.OTLPOutput{ @@ -139,8 +128,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { }, // otlp output { + name: "otlp-output-with-default-proto-and-path", pipeline: testutils.NewTracePipelineBuilder(). - WithName("otlp-output-with-default-proto-and-path"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPEndpointPath("/v1/dummy"), @@ -150,8 +139,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { field: "spec.output.otlp", }, { + name: "otlp-output-with-grpc-proto-and-path", pipeline: testutils.NewTracePipelineBuilder(). - WithName("otlp-output-with-grpc-proto-and-path"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPEndpointPath("/v1/dummy"), @@ -162,8 +151,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { field: "spec.output.otlp", }, { + name: "otlp-output-with-non-valid-proto", pipeline: testutils.NewTracePipelineBuilder(). - WithName("otlp-output-with-non-valid-proto"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPProtocol("icke"), @@ -174,8 +163,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { field: "spec.output.otlp.protocol", }, { + name: "otlp-output-basic-auth-secretref-missing-password-key", pipeline: testutils.NewTracePipelineBuilder(). - WithName("otlp-output-basic-auth-secretref-missing-password-key"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPBasicAuthFromSecret("name", "namespace", "user", ""), @@ -185,8 +174,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { field: "spec.output.otlp.authentication.basic.password.valueFrom.secretKeyRef.key", }, { + name: "otlp-output-basic-auth-secretref-missing-user-key", pipeline: testutils.NewTracePipelineBuilder(). - WithName("otlp-output-basic-auth-secretref-missing-user-key"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPBasicAuthFromSecret("name", "namespace", "", "password"), @@ -196,8 +185,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { field: "spec.output.otlp.authentication.basic.user.valueFrom.secretKeyRef.key", }, { + name: "otlp-output-tls-missing-key", pipeline: testutils.NewTracePipelineBuilder(). - WithName("otlp-output-tls-missing-key"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPClientTLS(&telemetryv1alpha1.OTLPTLS{ @@ -210,8 +199,8 @@ func TestRejectTracePipelineCreation(t *testing.T) { field: "spec.output.otlp.tls", }, { + name: "otlp-output-tls-missing-cert", pipeline: testutils.NewTracePipelineBuilder(). - WithName("otlp-output-tls-missing-cert"). WithOTLPOutput( testutils.OTLPEndpoint(backenEndpoint), testutils.OTLPClientTLS(&telemetryv1alpha1.OTLPTLS{ @@ -223,11 +212,60 @@ func TestRejectTracePipelineCreation(t *testing.T) { errorMsg: "Can define either both 'cert' and 'key', or neither", field: "spec.output.otlp.tls", }, + { + name: "otlp-output-oauth2-invalid-token-url", + pipeline: testutils.NewTracePipelineBuilder(). + WithOTLPOutput( + testutils.OTLPEndpoint(backenEndpoint), + testutils.OTLPOAuth2( + testutils.OAuth2ClientSecret("clientsecret"), + testutils.OAuth2ClientID("clientid"), + testutils.OAuth2TokenURL("../not-a-url"), + ), + ). + Build(), + errorMsg: "Invalid value: \"object\": 'tokenURL' must be a valid URL", + field: "spec.output.otlp.authentication.oauth2.tokenURL", + }, + { + name: "otlp-output-oauth2-missing-client-id", + pipeline: testutils.NewTracePipelineBuilder(). + WithOTLPOutput( + testutils.OTLPEndpoint(backenEndpoint), + testutils.OTLPOAuth2( + testutils.OAuth2ClientSecret("clientsecret"), + testutils.OAuth2TokenURL("https://auth.example.com/token"), + ), + ). + Build(), + errorMsg: "Invalid value: \"object\": no such key: value evaluating rule: 'clientID' missing", + field: "spec.output.otlp.authentication.oauth2.clientID", + }, + { + name: "otlp-output-oauth2-insecure", + pipeline: testutils.NewTracePipelineBuilder(). + WithOTLPOutput( + testutils.OTLPEndpoint(backenEndpoint), + testutils.OTLPOAuth2( + testutils.OAuth2ClientID("clientid"), + testutils.OAuth2ClientSecret("clientsecret"), + testutils.OAuth2TokenURL("https://auth.example.com/token"), + ), + testutils.OTLPClientTLS(&telemetryv1alpha1.OTLPTLS{ + Insecure: true, + }), + ). + Build(), + errorMsg: "OAuth2 authentication requires TLS when using gRPC protocol", + field: "spec.output.otlp", + }, } for _, tc := range tests { - t.Run(suite.LabelMisc, func(t *testing.T) { + t.Run(tc.name, func(t *testing.T) { suite.RegisterTestCase(t, suite.LabelMisc) + tc.pipeline.Name = tc.name + resources := []client.Object{&tc.pipeline} t.Cleanup(func() { diff --git a/test/testkit/assert/backend.go b/test/testkit/assert/backend.go index acbdbc0d85..bcd4407771 100644 --- a/test/testkit/assert/backend.go +++ b/test/testkit/assert/backend.go @@ -82,6 +82,7 @@ func BackendReachable(t testkit.T, backend *kitbackend.Backend) { func BackendDataEventuallyMatches(t testkit.T, backend *kitbackend.Backend, httpBodyMatcher types.GomegaMatcher, assertionOptions ...BackendAssertionOption) { t.Helper() + t.Logf("Asserting that backend %s/%s data eventually matches the expected condition", backend.Namespace(), backend.Name()) assertionOptions = append(assertionOptions, WithOptionalDescription(fmt.Sprintf("Backend data did not match the expected condition. Backend: %s/%s", backend.Namespace(), backend.Name()))) queryURL := suite.ProxyClient.ProxyURLForService(backend.Namespace(), backend.Name(), kitbackend.QueryPath, kitbackend.QueryPort) diff --git a/test/testkit/images.go b/test/testkit/images.go index 2a56089b45..edbdc6a35c 100644 --- a/test/testkit/images.go +++ b/test/testkit/images.go @@ -4,6 +4,7 @@ package testkit const ( - DefaultTelemetryGenImage = "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.141.0" - DefaultOTelCollectorImage = "europe-docker.pkg.dev/kyma-project/prod/kyma-otel-collector:0.141.0-main" + DefaultTelemetryGenImage = "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.141.0" + DefaultOTelCollectorContribImage = "otel/opentelemetry-collector-contrib:0.141.0" + DefaultOTelCollectorImage = "europe-docker.pkg.dev/kyma-project/prod/kyma-otel-collector:0.141.0-main" ) diff --git a/test/testkit/mocks/backend/backend.go b/test/testkit/mocks/backend/backend.go index 4e42e4eddc..4a3c0de39f 100644 --- a/test/testkit/mocks/backend/backend.go +++ b/test/testkit/mocks/backend/backend.go @@ -44,6 +44,11 @@ const ( SignalTypeMetricsAgent = "metrics" ) +type OIDCConfig struct { + issuerURL string + audience string +} + type Backend struct { abortFaultPercentage float64 dropFromSourceLabel map[string]string @@ -52,6 +57,8 @@ type Backend struct { namespace string replicas int32 signalType SignalType + oidc *OIDCConfig + mtls bool fluentDConfigMap *fluentdConfigMapBuilder hostSecret *kitk8s.Secret @@ -149,6 +156,8 @@ func (b *Backend) buildResources() { exportedFilePath, b.signalType, b.certs, + b.oidc, + b.mtls, ) b.collectorDeployment = newCollectorDeployment( diff --git a/test/testkit/mocks/backend/collector_config_map.go b/test/testkit/mocks/backend/collector_config_map.go index 9c558572db..871dc9db02 100644 --- a/test/testkit/mocks/backend/collector_config_map.go +++ b/test/testkit/mocks/backend/collector_config_map.go @@ -1,7 +1,9 @@ package backend import ( + "bytes" "strings" + "text/template" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -15,123 +17,148 @@ type collectorConfigMapBuilder struct { exportedFilePath string signalType SignalType certs *testutils.ServerCerts + oidc *OIDCConfig + mtls bool } -func newCollectorConfigMap(name, namespace, path string, signalType SignalType, certs *testutils.ServerCerts) *collectorConfigMapBuilder { +func newCollectorConfigMap(name, namespace, path string, signalType SignalType, certs *testutils.ServerCerts, oidc *OIDCConfig, mtls bool) *collectorConfigMapBuilder { return &collectorConfigMapBuilder{ name: name, namespace: namespace, exportedFilePath: path, signalType: signalType, certs: certs, + oidc: oidc, + mtls: mtls, } } -const otelConfigTemplate = `receivers: - otlp: - protocols: - grpc: - endpoint: ${MY_POD_IP}:4317 - http: - endpoint: ${MY_POD_IP}:4318 -exporters: - file: - path: {{ FILEPATH }} -service: - telemetry: - logs: - level: "info" - pipelines: - {{ SIGNAL_TYPE }}: - receivers: - - otlp - exporters: - - file` - -const tlsConfigTemplate = `receivers: - otlp: - protocols: - grpc: - tls: - cert_pem: "{{ CERT_PEM }}" - key_pem: "{{ KEY_PEM }}" - client_ca_file: {{ CA_FILE_PATH }} - endpoint: ${MY_POD_IP}:4317 - http: - endpoint: ${MY_POD_IP}:4318 -exporters: - file: - path: {{ FILEPATH }} -service: - telemetry: - logs: - level: "info" - pipelines: - {{ SIGNAL_TYPE }}: - receivers: - - otlp - exporters: - - file` - -const logConfigTemplate = `receivers: +const unifiedConfigTemplate = ` +{{- if .OIDCEnabled }} +extensions: + oidc: + issuer_url: "{{ .IssuerURL }}" + audience: "{{ .Audience }}" +{{- end }} +receivers: + {{- if .FluentBit }} fluentforward: endpoint: localhost:8006 + {{- end }} otlp: protocols: grpc: + {{- if .UseTLS }} + tls: + cert_pem: "{{ .CertPem }}" + key_pem: "{{ .KeyPem }}" + {{- if .MTLS }} + client_ca_file: {{ .CaFilePath }} + {{- end }} + {{- end }} + {{- if .OIDCEnabled }} + auth: + authenticator: oidc + {{- end }} endpoint: ${MY_POD_IP}:4317 http: endpoint: ${MY_POD_IP}:4318 exporters: file: - path: {{ FILEPATH }} + path: {{ .FilePath }} service: telemetry: logs: level: "info" pipelines: - {{ SIGNAL_TYPE }}: + {{ .SignalType }}: receivers: - otlp + {{- if .FluentBit }} - fluentforward + {{- end }} exporters: - - file` + - file + {{- if .OIDCEnabled }} + extensions: + - oidc + {{- end }} +` func (cm *collectorConfigMapBuilder) Name() string { return cm.name } func (cm *collectorConfigMapBuilder) K8sObject() *corev1.ConfigMap { - var configTemplate string - - switch { - case cm.signalType == SignalTypeLogsFluentBit: - configTemplate = logConfigTemplate - case cm.certs != nil: - configTemplate = tlsConfigTemplate - default: - configTemplate = otelConfigTemplate - } + configTemplate := unifiedConfigTemplate - config := strings.Replace(configTemplate, "{{ FILEPATH }}", cm.exportedFilePath, 1) + // Prepare template data + signal := string(cm.signalType) if cm.signalType == SignalTypeLogsOTel { - config = strings.Replace(config, "{{ SIGNAL_TYPE }}", "logs", 1) - } else { - config = strings.Replace(config, "{{ SIGNAL_TYPE }}", string(cm.signalType), 1) + signal = "logs" + } + + isFluent := cm.signalType == SignalTypeLogsFluentBit + useTLS := cm.certs != nil && !isFluent + oidcEnabled := cm.oidc != nil + + tplData := struct { + FilePath string + SignalType string + CertPem string + KeyPem string + CaFilePath string + UseTLS bool + MTLS bool + FluentBit bool + OIDCEnabled bool + IssuerURL string + Audience string + }{ + FilePath: cm.exportedFilePath, + SignalType: signal, + CertPem: "", + KeyPem: "", + CaFilePath: "", + UseTLS: useTLS, + FluentBit: isFluent, + OIDCEnabled: false, + IssuerURL: "", + Audience: "", + MTLS: cm.mtls, + } + + if oidcEnabled { + tplData.IssuerURL = cm.oidc.issuerURL + tplData.Audience = cm.oidc.audience + tplData.OIDCEnabled = true } data := make(map[string]string) - if cm.certs != nil && cm.signalType != SignalTypeLogsFluentBit { - certPem := strings.ReplaceAll(cm.certs.ServerCertPem.String(), "\n", "\\n") - keyPem := strings.ReplaceAll(cm.certs.ServerKeyPem.String(), "\n", "\\n") - config = strings.Replace(config, "{{ CERT_PEM }}", certPem, 1) - config = strings.Replace(config, "{{ KEY_PEM }}", keyPem, 1) - config = strings.Replace(config, "{{ CA_FILE_PATH }}", "/etc/collector/ca.crt", 1) + if useTLS { + tplData.CertPem = strings.ReplaceAll(cm.certs.ServerCertPem.String(), "\n", "\\n") + tplData.KeyPem = strings.ReplaceAll(cm.certs.ServerKeyPem.String(), "\n", "\\n") + tplData.CaFilePath = "/etc/collector/ca.crt" data["ca.crt"] = cm.certs.CaCertPem.String() } + // Render template using text/template + tpl, err := template.New("collector").Parse(configTemplate) + if err != nil { + panic(err) // Template parsing should not fail + } + + var buf bytes.Buffer + + err = tpl.Execute(&buf, tplData) + if err != nil { + panic(err) // Template execution should not fail + } + + config := buf.String() + data["config.yaml"] = config return &corev1.ConfigMap{ diff --git a/test/testkit/mocks/backend/collector_deployment.go b/test/testkit/mocks/backend/collector_deployment.go index 8a87a03fd1..1372a67719 100644 --- a/test/testkit/mocks/backend/collector_deployment.go +++ b/test/testkit/mocks/backend/collector_deployment.go @@ -82,7 +82,7 @@ func (d *collectorDeploymentBuilder) containers() []corev1.Container { containers := []corev1.Container{ { Name: "otel-collector", - Image: testkit.DefaultOTelCollectorImage, + Image: testkit.DefaultOTelCollectorContribImage, Args: []string{"--config=/etc/collector/config.yaml"}, SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To[int64](101), diff --git a/test/testkit/mocks/backend/options.go b/test/testkit/mocks/backend/options.go index a5ff0a6f16..f662c53bb7 100644 --- a/test/testkit/mocks/backend/options.go +++ b/test/testkit/mocks/backend/options.go @@ -28,8 +28,25 @@ func WithReplicas(replicas int32) Option { } } -func WithTLS(certKey testutils.ServerCerts) Option { +func WithMTLS(certs testutils.ServerCerts) Option { return func(b *Backend) { - b.certs = &certKey + b.mtls = true + b.certs = &certs + } +} + +func WithTLS(certs testutils.ServerCerts) Option { + return func(b *Backend) { + b.mtls = false + b.certs = &certs + } +} + +func WithOIDCAuth(issuerURL, audience string) Option { + return func(b *Backend) { + b.oidc = &OIDCConfig{ + issuerURL: issuerURL, + audience: audience, + } } } diff --git a/test/testkit/mocks/oauth2mock/oauth2mock.go b/test/testkit/mocks/oauth2mock/oauth2mock.go new file mode 100644 index 0000000000..6fbab7a4cb --- /dev/null +++ b/test/testkit/mocks/oauth2mock/oauth2mock.go @@ -0,0 +1,102 @@ +package oauth2mock + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + kitk8s "github.com/kyma-project/telemetry-manager/test/testkit/k8s" +) + +const ( + oauth2MockImage = "europe-central2-docker.pkg.dev/sap-se-cx-kyma-goat/networking-dev-tools/oauth2-mock:latest" + + DefaultName = "oauth2-mock" + + AudienceDefault = "default" +) + +type OAuth2Authenticator struct { + name string + namespace string + authenticatorDeployment *kitk8s.Deployment + authenticatorService *kitk8s.Service +} + +func New(namespace string) *OAuth2Authenticator { + auth := &OAuth2Authenticator{ + name: DefaultName, + namespace: namespace, + } + + auth.buildResources() + + return auth +} + +func (o *OAuth2Authenticator) Name() string { + return o.name +} + +func (o *OAuth2Authenticator) Namespace() string { + return o.namespace +} + +func (o *OAuth2Authenticator) NamespacedName() types.NamespacedName { + return types.NamespacedName{Name: o.name, Namespace: o.namespace} +} + +func (o *OAuth2Authenticator) Audience() string { + return AudienceDefault +} + +func (o *OAuth2Authenticator) TokenEndpoint() string { + return fmt.Sprintf("http://%s.%s.svc.cluster.local:8080/oauth2/token", o.name, o.namespace) +} + +func (o *OAuth2Authenticator) IssuerURL() string { + return fmt.Sprintf("http://%s.%s.svc.cluster.local:8080", o.name, o.namespace) +} + +func (o *OAuth2Authenticator) K8sObjects() []client.Object { + var objects []client.Object + + objects = append(objects, o.authenticatorDeployment.K8sObject()) + objects = append(objects, o.authenticatorService.K8sObject(kitk8s.WithLabel("app", o.name))) + + return objects +} + +func (o *OAuth2Authenticator) buildResources() { + podSpec := corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "oauth2-mock", + Image: oauth2MockImage, + Ports: []corev1.ContainerPort{ + { + ContainerPort: 8080, + Name: "http", + }, + }, + Env: []corev1.EnvVar{ + { + Name: "iss", + Value: o.IssuerURL(), + }, + }, + }, + }, + } + o.authenticatorDeployment = kitk8s.NewDeployment( + o.name, + o.namespace, + ).WithReplicas(1).WithPodSpec(podSpec).WithLabel("app", o.name) + + o.authenticatorService = kitk8s.NewService( + o.name, + o.namespace, + ).WithPort("http", 8080) +} diff --git a/test/testkit/suite/suite.go b/test/testkit/suite/suite.go index 3a43b24e38..990265ae47 100644 --- a/test/testkit/suite/suite.go +++ b/test/testkit/suite/suite.go @@ -136,6 +136,11 @@ const ( // LabelUpgrade defines the label for Upgrade tests, which preserve K8s objects between test runs. LabelUpgrade = "upgrade" + + // LabelOAuth2 defines the label for OAuth2 related tests. + LabelOAuth2 = "oauth2" + // LabelMTLS defines the label for mTLS related tests. + LabelMTLS = "mtls" ) func ExpectAgent(label string) bool { // TODO(TeodorSAP): Use this for log e2e tests as well @@ -254,6 +259,10 @@ func findLabelFilterExpression() string { func toSet(labels []string) map[string]struct{} { set := make(map[string]struct{}, len(labels)) for _, label := range labels { + if label == "" { + continue + } + set[label] = struct{}{} }