From 48d7d333a7efbd84a0010b9177938891d0b1797d Mon Sep 17 00:00:00 2001 From: Andrii Chubatiuk Date: Sat, 12 Jul 2025 15:31:48 +0300 Subject: [PATCH] vmauth: JWT support --- .golangci.yml | 2 +- api/operator/v1beta1/vmauth_types.go | 25 +++++- api/operator/v1beta1/vmuser_types.go | 10 +++ api/operator/v1beta1/zz_generated.deepcopy.go | 76 ++++++++++++++++++ config/crd/overlay/crd.yaml | 71 +++++++++++++++++ docs/api.md | 62 +++++++++++---- .../operator/factory/reconcile/hpa.go | 6 +- .../operator/factory/vmauth/vmusers_config.go | 77 ++++++++++++------- .../factory/vmauth/vmusers_config_test.go | 27 +++++++ 9 files changed, 307 insertions(+), 49 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1b46338dc..ba9f8be36 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -44,7 +44,7 @@ linters: alias: metav1 - pkg: k8s.io/api/apps/v1 alias: appsv1 - - pkg: k8s.io/api/autoscaling/v2" + - pkg: k8s.io/api/autoscaling/v2 alias: autoscalingv2 - pkg: k8s.io/apimachinery/pkg/api/errors alias: k8serrors diff --git a/api/operator/v1beta1/vmauth_types.go b/api/operator/v1beta1/vmauth_types.go index 3d6ee9bd9..9dec240b8 100644 --- a/api/operator/v1beta1/vmauth_types.go +++ b/api/operator/v1beta1/vmauth_types.go @@ -8,6 +8,7 @@ import ( "strings" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -98,6 +99,9 @@ type VMAuthSpec struct { // currently it has collision with inlined fields // IPFilters VMUserIPFilters `json:"ip_filters,omitempty"` + // JWTIssuers represents configuration section for JWT issuers + // +optional + JWTIssuers []*VMAuthJWTIssuer `json:"jwt_issuers,omitempty"` // License allows to configure license key to be used for enterprise features. // Using license key is supported starting from VictoriaMetrics v1.94.0. // See [here](https://docs.victoriametrics.com/victoriametrics/enterprise/) @@ -147,6 +151,26 @@ type VMAuthSpec struct { HPA *EmbeddedHPA `json:"hpa,omitempty"` } +// VMAuthJWTIssuer defines JWT issuer parameters +type VMAuthJWTIssuer struct { + // Match defines map of claims to match issuer against + Match map[string]string `json:"match,omitempty"` + // DiscoveryURL is OpenID Connect discovery URL + // +optional + DiscoveryURL string `json:"discovery_url,omitempty"` + // JWKsURL is the OpenID Connect JWKS URL + // +optional + JWKsURL string `json:"jwks_url,omitempty"` + // PublicKeyFiles is a list of paths pointing to public key files in PEM format to use + // for verifying JWT tokens + PublicKeyFiles []string `json:"public_key_files,omitempty"` + // PublicKeySecrets is a list of k8s Secret selectors pointing to public key files in PEM format to use + // for verifying JWT tokens + PublicKeySecrets []*corev1.SecretKeySelector `json:"public_key_secrets,omitempty"` + // SyncPeriod defines how frequently JWT issuer keys are synchronized + SyncPeriod string `json:"sync_period,omitempty"` +} + // VMAuthUnauthorizedUserAccessSpec defines unauthorized_user section configuration for vmauth type VMAuthUnauthorizedUserAccessSpec struct { // URLPrefix defines prefix prefix for destination @@ -447,7 +471,6 @@ func (cr *VMAuth) Validate() error { return fmt.Errorf("incorrect cr.spec UnauthorizedAccessConfig options: %w", err) } } - if cr.Spec.UnauthorizedUserAccessSpec != nil { if err := cr.Spec.UnauthorizedUserAccessSpec.Validate(); err != nil { return fmt.Errorf("incorrect cr.spec.UnauthorizedUserAccess syntax: %w", err) diff --git a/api/operator/v1beta1/vmuser_types.go b/api/operator/v1beta1/vmuser_types.go index 5284e743d..04d0e78fb 100644 --- a/api/operator/v1beta1/vmuser_types.go +++ b/api/operator/v1beta1/vmuser_types.go @@ -16,6 +16,8 @@ type VMUserSpec struct { // Name of the VMUser object. // +optional Name *string `json:"name,omitempty"` + // JWTToken defines JWT auth section for user + JWTToken *VMUserJWTToken `json:"jwt_token,omitempty"` // Username basic auth user name for accessing protected endpoint, // will be replaced with metadata.name of VMUser if omitted. // +optional @@ -53,6 +55,14 @@ type VMUserSpec struct { ManagedMetadata *ManagedObjectsMetadata `json:"managedMetadata,omitempty"` } +// VMUserJWTToken describes JWT auth for user +type VMUserJWTToken struct { + // Match defines claim match map + Match map[string]string `json:"match,omitempty" yaml:"match,omitempty"` + // AllowUnhealthy defines if unhealthy JWT issuer status is ignored + AllowUnhealthy bool `json:"allow_unhealthy,omitempty" yaml:"allow_unhealthy,omitempty"` +} + // TargetRef describes target for user traffic forwarding. // one of target types can be chosen: // crd or static per targetRef. diff --git a/api/operator/v1beta1/zz_generated.deepcopy.go b/api/operator/v1beta1/zz_generated.deepcopy.go index da990529b..1dcf95cc3 100644 --- a/api/operator/v1beta1/zz_generated.deepcopy.go +++ b/api/operator/v1beta1/zz_generated.deepcopy.go @@ -5093,6 +5093,44 @@ func (in *VMAuth) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VMAuthJWTIssuer) DeepCopyInto(out *VMAuthJWTIssuer) { + *out = *in + if in.Match != nil { + in, out := &in.Match, &out.Match + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.PublicKeyFiles != nil { + in, out := &in.PublicKeyFiles, &out.PublicKeyFiles + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PublicKeySecrets != nil { + in, out := &in.PublicKeySecrets, &out.PublicKeySecrets + *out = make([]*v1.SecretKeySelector, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMAuthJWTIssuer. +func (in *VMAuthJWTIssuer) DeepCopy() *VMAuthJWTIssuer { + if in == nil { + return nil + } + out := new(VMAuthJWTIssuer) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VMAuthList) DeepCopyInto(out *VMAuthList) { *out = *in @@ -5259,6 +5297,17 @@ func (in *VMAuthSpec) DeepCopyInto(out *VMAuthSpec) { *out = new(VMAuthUnauthorizedUserAccessSpec) (*in).DeepCopyInto(*out) } + if in.JWTIssuers != nil { + in, out := &in.JWTIssuers, &out.JWTIssuers + *out = make([]*VMAuthJWTIssuer, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(VMAuthJWTIssuer) + (*in).DeepCopyInto(*out) + } + } + } if in.License != nil { in, out := &in.License, &out.License *out = new(License) @@ -6947,6 +6996,28 @@ func (in *VMUserIPFilters) DeepCopy() *VMUserIPFilters { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VMUserJWTToken) DeepCopyInto(out *VMUserJWTToken) { + *out = *in + if in.Match != nil { + in, out := &in.Match, &out.Match + *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 VMUserJWTToken. +func (in *VMUserJWTToken) DeepCopy() *VMUserJWTToken { + if in == nil { + return nil + } + out := new(VMUserJWTToken) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VMUserList) DeepCopyInto(out *VMUserList) { *out = *in @@ -6987,6 +7058,11 @@ func (in *VMUserSpec) DeepCopyInto(out *VMUserSpec) { *out = new(string) **out = **in } + if in.JWTToken != nil { + in, out := &in.JWTToken, &out.JWTToken + *out = new(VMUserJWTToken) + (*in).DeepCopyInto(*out) + } if in.Username != nil { in, out := &in.Username, &out.Username *out = new(string) diff --git a/config/crd/overlay/crd.yaml b/config/crd/overlay/crd.yaml index 8e9d45804..8b2fb50ea 100644 --- a/config/crd/overlay/crd.yaml +++ b/config/crd/overlay/crd.yaml @@ -25978,6 +25978,64 @@ spec: and v1.111.0 vmauth version related doc https://docs.victoriametrics.com/victoriametrics/vmauth/#security type: string + jwt_issuers: + description: JWTIssuers represents configuration section for JWT issuers + items: + description: VMAuthJWTIssuer defines JWT issuer parameters + properties: + discovery_url: + description: DiscoveryURL is OpenID Connect discovery URL + type: string + jwks_url: + description: JWKsURL is the OpenID Connect JWKS URL + type: string + match: + additionalProperties: + type: string + description: Match defines map of claims to match issuer against + type: object + public_key_files: + description: |- + PublicKeyFiles is a list of paths pointing to public key files in PEM format to use + for verifying JWT tokens + items: + type: string + type: array + public_key_secrets: + description: |- + PublicKeySecrets is a list of k8s Secret selectors pointing to public key files in PEM format to use + for verifying JWT tokens + items: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array + sync_period: + description: SyncPeriod defines how frequently JWT issuer keys + are synchronized + type: string + type: object + type: array license: description: |- License allows to configure license key to be used for enterprise features. @@ -41988,6 +42046,19 @@ spec: type: string type: array type: object + jwt_token: + description: JWTToken defines JWT auth section for user + properties: + allow_unhealthy: + description: AllowUnhealthy defines if unhealthy JWT issuer status + is ignored + type: boolean + match: + additionalProperties: + type: string + description: Match defines claim match map + type: object + type: object load_balancing_policy: description: |- LoadBalancingPolicy defines load balancing policy to use for backend urls. diff --git a/docs/api.md b/docs/api.md index 52eadc134..60250635d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -671,7 +671,6 @@ Appears in: [VMAnomalyMonitoringSpec](#vmanomalymonitoringspec) VMAnomalyMonitoringPushSpec defines metrics push configuration - VMAnomaly uses prometheus text exposition format Appears in: [VMAnomalyMonitoringSpec](#vmanomalymonitoringspec) @@ -1609,6 +1608,7 @@ Appears in: [VMAlertmanagerSpec](#vmalertmanagerspec) | Field | Description | | --- | --- | | key#
_string_ | _(Required)_
The ConfigMap key to refer to. | +| name#
_string_ | _(Optional)_
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names | #### ConsulSDConfig @@ -1817,7 +1817,6 @@ Appears in: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLStorage](#vlstorage EmbeddedHTTPRoute describes httproute configuration options. - Requires gateway-controller CRD installed and VM_GATEWAY_API_ENABLED=true env var See https://gateway-api.sigs.k8s.io/guides/#installing-a-gateway-controller @@ -1899,7 +1898,7 @@ Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlse | maxUnavailable#
_[IntOrString](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#intorstring-intstr-util)_ | _(Optional)_
An eviction is allowed if at most "maxUnavailable" pods selected by
"selector" are unavailable after the eviction, i.e. even in absence of
the evicted pod. For example, one can prevent all voluntary evictions
by specifying 0. This is a mutually exclusive setting with "minAvailable". | | minAvailable#
_[IntOrString](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#intorstring-intstr-util)_ | _(Optional)_
An eviction is allowed if at least "minAvailable" pods selected by
"selector" will still be available after the eviction, i.e. even in the
absence of the evicted pod. So for example you can prevent all voluntary
evictions by specifying "100%". | | selectorLabels#
_object (keys:string, values:string)_ | _(Optional)_
replaces default labels selector generated by operator
it's useful when you need to create custom budget | -| unhealthyPodEvictionPolicy#
_string_ | _(Optional)_
UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods

Valid policies are IfHealthyBudget and AlwaysAllow.
If no policy is specified, the default behavior will be used,
which corresponds to the IfHealthyBudget policy.
Available from operator v0.64.0 | +| unhealthyPodEvictionPolicy#
_string_ | _(Optional)_
UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods
Valid policies are IfHealthyBudget and AlwaysAllow.
If no policy is specified, the default behavior will be used,
which corresponds to the IfHealthyBudget policy.
Available from operator v0.64.0 | #### EmbeddedProbes @@ -2051,7 +2050,6 @@ The private IP address is used by default, but may be changed to the public IP address with relabeling. See [here](https://docs.victoriametrics.com/victoriametrics/sd_configs/#gce_sd_configs) - The GCE service discovery will load the Google Cloud credentials from the file specified by the GOOGLE_APPLICATION_CREDENTIALS environment variable. See https://cloud.google.com/kubernetes-engine/docs/tutorials/authenticating-to-cloud-platform @@ -2838,7 +2836,7 @@ Appears in: [CommonScrapeParams](#commonscrapeparams), [VMAgentSpec](#vmagentspe | basicAuth#
_[BasicAuth](#basicauth)_ | _(Optional)_
BasicAuth allow an endpoint to authenticate over basic authentication | | bearerTokenFile#
_string_ | _(Optional)_
File to read bearer token for scraping targets. | | bearerTokenSecret#
_[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core)_ | _(Optional)_
Secret to mount to read bearer token for scraping targets. The secret
needs to be in the same namespace as the scrape object and accessible by
the victoria-metrics operator. | -| default#
_boolean_ | _(Optional)_
default defines that the scrape applies to all scrape objects that
don't configure an explicit scrape class name.

Only one scrape class can be set as the default. | +| default#
_boolean_ | _(Optional)_
default defines that the scrape applies to all scrape objects that
don't configure an explicit scrape class name.
Only one scrape class can be set as the default. | | metricRelabelConfigs#
_[RelabelConfig](#relabelconfig) array_ | _(Optional)_
MetricRelabelConfigs to apply to samples after scrapping. | | name#
_string_ | _(Required)_
name of the scrape class. | | oauth2#
_[OAuth2](#oauth2)_ | _(Optional)_
OAuth2 defines auth configuration | @@ -3114,9 +3112,9 @@ Appears in: [StreamAggrConfig](#streamaggrconfig) | Field | Description | | --- | --- | -| by#
_string array_ | _(Optional)_
By is an optional list of labels for grouping input series.

See also Without.

If neither By nor Without are set, then the Outputs are calculated
individually per each input time series. | +| by#
_string array_ | _(Optional)_
By is an optional list of labels for grouping input series.
See also Without.
If neither By nor Without are set, then the Outputs are calculated
individually per each input time series. | | dedup_interval#
_string_ | _(Optional)_
DedupInterval is an optional interval for deduplication. | -| drop_input_labels#
_string_ | _(Optional)_
DropInputLabels is an optional list with labels, which must be dropped before further processing of input samples.

Labels are dropped before de-duplication and aggregation. | +| drop_input_labels#
_string_ | _(Optional)_
DropInputLabels is an optional list with labels, which must be dropped before further processing of input samples.
Labels are dropped before de-duplication and aggregation. | | enable_windows#
_boolean_ | _(Optional)_
EnableWindows enables aggregating data in separate windows | | flush_on_shutdown#
_boolean_ | _(Optional)_
FlushOnShutdown defines whether to flush the aggregation state on process termination
or config reload. Is `false` by default.
It is not recommended changing this setting, unless unfinished aggregations states
are preferred to missing data points. | | ignoreFirstSampleInterval#
_string_ | _(Required)_
IgnoreFirstSampleInterval sets interval for total and prometheus_total during which first samples will be ignored | @@ -3125,12 +3123,12 @@ Appears in: [StreamAggrConfig](#streamaggrconfig) | input_relabel_configs#
_[RelabelConfig](#relabelconfig) array_ | _(Optional)_
InputRelabelConfigs is an optional relabeling rules, which are applied on the input
before aggregation. | | interval#
_string_ | _(Required)_
Interval is the interval between aggregations. | | keep_metric_names#
_boolean_ | _(Optional)_
KeepMetricNames instructs to leave metric names as is for the output time series without adding any suffix. | -| match#
_[StringOrArray](#stringorarray)_ | _(Optional)_
Match is a label selector (or list of label selectors) for filtering time series for the given selector.

If the match isn't set, then all the input time series are processed. | +| match#
_[StringOrArray](#stringorarray)_ | _(Optional)_
Match is a label selector (or list of label selectors) for filtering time series for the given selector.
If the match isn't set, then all the input time series are processed. | | no_align_flush_to_interval#
_boolean_ | _(Optional)_
NoAlignFlushToInterval disables aligning of flushes to multiples of Interval.
By default flushes are aligned to Interval. | | output_relabel_configs#
_[RelabelConfig](#relabelconfig) array_ | _(Optional)_
OutputRelabelConfigs is an optional relabeling rules, which are applied
on the aggregated output before being sent to remote storage. | -| outputs#
_string array_ | _(Required)_
Outputs is a list of output aggregate functions to produce.

The following names are allowed:

- total - aggregates input counters
- increase - counts the increase over input counters
- count_series - counts the input series
- count_samples - counts the input samples
- sum_samples - sums the input samples
- last - the last biggest sample value
- min - the minimum sample value
- max - the maximum sample value
- avg - the average value across all the samples
- stddev - standard deviation across all the samples
- stdvar - standard variance across all the samples
- histogram_bucket - creates VictoriaMetrics histogram for input samples
- quantiles(phi1, ..., phiN) - quantiles' estimation for phi in the range [0..1]

The output time series will have the following names:

input_name:aggr__ | +| outputs#
_string array_ | _(Required)_
Outputs is a list of output aggregate functions to produce.
The following names are allowed:
- total - aggregates input counters
- increase - counts the increase over input counters
- count_series - counts the input series
- count_samples - counts the input samples
- sum_samples - sums the input samples
- last - the last biggest sample value
- min - the minimum sample value
- max - the maximum sample value
- avg - the average value across all the samples
- stddev - standard deviation across all the samples
- stdvar - standard variance across all the samples
- histogram_bucket - creates VictoriaMetrics histogram for input samples
- quantiles(phi1, ..., phiN) - quantiles' estimation for phi in the range [0..1]
The output time series will have the following names:
input_name:aggr__ | | staleness_interval#
_string_ | _(Optional)_
Staleness interval is interval after which the series state will be reset if no samples have been sent during it.
The parameter is only relevant for outputs: total, total_prometheus, increase, increase_prometheus and histogram_bucket. | -| without#
_string array_ | _(Optional)_
Without is an optional list of labels, which must be excluded when grouping input series.

See also By.

If neither By nor Without are set, then the Outputs are calculated
individually per each input time series. | +| without#
_string array_ | _(Optional)_
Without is an optional list of labels, which must be excluded when grouping input series.
See also By.
If neither By nor Without are set, then the Outputs are calculated
individually per each input time series. | #### StringOrArray @@ -3990,6 +3988,24 @@ VMAuth is the Schema for the vmauths API | spec#
_[VMAuthSpec](#vmauthspec)_ | _(Required)_
| +#### VMAuthJWTIssuer + + + +VMAuthJWTIssuer defines JWT issuer parameters + +Appears in: [VMAuthSpec](#vmauthspec) + +| Field | Description | +| --- | --- | +| discovery_url#
_string_ | _(Optional)_
DiscoveryURL is OpenID Connect discovery URL | +| jwks_url#
_string_ | _(Optional)_
JWKsURL is the OpenID Connect JWKS URL | +| match#
_object (keys:string, values:string)_ | _(Required)_
Match defines map of claims to match issuer against | +| public_key_files#
_string array_ | _(Required)_
PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
for verifying JWT tokens | +| public_key_secrets#
_[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core) array_ | _(Required)_
PublicKeySecrets is a list of k8s Secret selectors pointing to public key files in PEM format to use
for verifying JWT tokens | +| sync_period#
_string_ | _(Required)_
SyncPeriod defines how frequently JWT issuer keys are synchronized | + + #### VMAuthLoadBalancer @@ -4090,7 +4106,7 @@ Appears in: [VMAuth](#vmauth) | dnsConfig#
_[PodDNSConfig](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#poddnsconfig-v1-core)_ | _(Optional)_
Specifies the DNS parameters of a pod.
Parameters specified here will be merged to the generated DNS
configuration based on DNSPolicy. | | dnsPolicy#
_[DNSPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#dnspolicy-v1-core)_ | _(Optional)_
DNSPolicy sets DNS policy for the pod | | drop_src_path_prefix_parts#
_integer_ | _(Optional)_
DropSrcPathPrefixParts is the number of `/`-delimited request path prefix parts to drop before proxying the request to backend.
See [here](https://docs.victoriametrics.com/victoriametrics/vmauth/#dropping-request-path-prefix) for more details. | -| dump_request_on_errors#
_boolean_ | _(Optional)_
DumpRequestOnErrors instructs vmauth to return detailed request params to the client
if routing rules don't allow to forward request to the backends.
Useful for debugging `src_hosts` and `src_headers` based routing rules

available since v1.107.0 vmauth version | +| dump_request_on_errors#
_boolean_ | _(Optional)_
DumpRequestOnErrors instructs vmauth to return detailed request params to the client
if routing rules don't allow to forward request to the backends.
Useful for debugging `src_hosts` and `src_headers` based routing rules
available since v1.107.0 vmauth version | | externalConfig#
_[ExternalConfig](#externalconfig)_ | _(Optional)_
ExternalConfig defines a source of external VMAuth configuration.
If it's defined, configuration for vmauth becomes unmanaged and operator'll not create any related secrets/config-reloaders | | extraArgs#
_object (keys:string, values:string)_ | _(Optional)_
ExtraArgs that will be passed to the application container
for example remoteWrite.tmpDataPath: /tmp | | extraEnvs#
_[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core) array_ | _(Optional)_
ExtraEnvs that will be passed to the application container | @@ -4107,6 +4123,7 @@ Appears in: [VMAuth](#vmauth) | initContainers#
_[Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#container-v1-core) array_ | _(Optional)_
InitContainers allows adding initContainers to the pod definition.
Any errors during the execution of an initContainer will lead to a restart of the Pod.
More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ | | internalListenPort#
_string_ | _(Optional)_
InternalListenPort instructs vmauth to serve internal routes at given port
available from v0.56.0 operator
and v1.111.0 vmauth version
related doc https://docs.victoriametrics.com/victoriametrics/vmauth/#security | | ip_filters#
_[VMUserIPFilters](#vmuseripfilters)_ | _(Optional)_
IPFilters defines per target src ip filters
supported only with enterprise version of [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/#ip-filters) | +| jwt_issuers#
_[VMAuthJWTIssuer](#vmauthjwtissuer) array_ | _(Optional)_
JWTIssuers represents configuration section for JWT issuers | | license#
_[License](#license)_ | _(Optional)_
License allows to configure license key to be used for enterprise features.
Using license key is supported starting from VictoriaMetrics v1.94.0.
See [here](https://docs.victoriametrics.com/victoriametrics/enterprise/) | | load_balancing_policy#
_string_ | _(Optional)_
LoadBalancingPolicy defines load balancing policy to use for backend urls.
Supported policies: least_loaded, first_available.
See [here](https://docs.victoriametrics.com/victoriametrics/vmauth/#load-balancing) for more details (default "least_loaded") | | logFormat#
_string_ | _(Optional)_
LogFormat for VMAuth to be configured with. | @@ -4139,7 +4156,7 @@ Appears in: [VMAuth](#vmauth) | tlsConfig#
_[TLSConfig](#tlsconfig)_ | _(Optional)_
TLSConfig defines tls configuration for the backend connection | | tolerations#
_[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#toleration-v1-core) array_ | _(Optional)_
Tolerations If specified, the pod's tolerations. | | topologySpreadConstraints#
_[TopologySpreadConstraint](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#topologyspreadconstraint-v1-core) array_ | _(Optional)_
TopologySpreadConstraints embedded kubernetes pod configuration option,
controls how pods are spread across your cluster among failure-domains
such as regions, zones, nodes, and other user-defined topology domains
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ | -| unauthorizedAccessConfig#
_[UnauthorizedAccessConfigURLMap](#unauthorizedaccessconfigurlmap) array_ | _(Required)_
UnauthorizedAccessConfig configures access for un authorized users

Deprecated: use unauthorizedUserAccessSpec instead
will be removed at v1.0 release | +| unauthorizedAccessConfig#
_[UnauthorizedAccessConfigURLMap](#unauthorizedaccessconfigurlmap) array_ | _(Required)_
UnauthorizedAccessConfig configures access for un authorized users
Deprecated: use unauthorizedUserAccessSpec instead
will be removed at v1.0 release | | unauthorizedUserAccessSpec#
_[VMAuthUnauthorizedUserAccessSpec](#vmauthunauthorizeduseraccessspec)_ | _(Optional)_
UnauthorizedUserAccessSpec defines unauthorized_user config section of vmauth config | | updateStrategy#
_[DeploymentStrategyType](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#deploymentstrategytype-v1-apps)_ | _(Optional)_
UpdateStrategy - overrides default update strategy.
Available from operator v0.64.0 | | useDefaultResources#
_boolean_ | _(Optional)_
UseDefaultResources controls resource settings
By default, operator sets built-in resource requirements | @@ -4167,7 +4184,7 @@ Appears in: [VMAuthSpec](#vmauthspec) | default_url#
_string array_ | _(Required)_
DefaultURLs backend url for non-matching paths filter
usually used for default backend with error message | | discover_backend_ips#
_boolean_ | _(Required)_
DiscoverBackendIPs instructs discovering URLPrefix backend IPs via DNS. | | drop_src_path_prefix_parts#
_integer_ | _(Optional)_
DropSrcPathPrefixParts is the number of `/`-delimited request path prefix parts to drop before proxying the request to backend.
See [here](https://docs.victoriametrics.com/victoriametrics/vmauth/#dropping-request-path-prefix) for more details. | -| dump_request_on_errors#
_boolean_ | _(Optional)_
DumpRequestOnErrors instructs vmauth to return detailed request params to the client
if routing rules don't allow to forward request to the backends.
Useful for debugging `src_hosts` and `src_headers` based routing rules

available since v1.107.0 vmauth version | +| dump_request_on_errors#
_boolean_ | _(Optional)_
DumpRequestOnErrors instructs vmauth to return detailed request params to the client
if routing rules don't allow to forward request to the backends.
Useful for debugging `src_hosts` and `src_headers` based routing rules
available since v1.107.0 vmauth version | | headers#
_string array_ | _(Optional)_
Headers represent additional http headers, that vmauth uses
in form of ["header_key: header_value"]
multiple values for header key:
["header_key: value1,value2"]
it's available since 1.68.0 version of vmauth | | ip_filters#
_[VMUserIPFilters](#vmuseripfilters)_ | _(Optional)_
IPFilters defines per target src ip filters
supported only with enterprise version of [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/#ip-filters) | | load_balancing_policy#
_string_ | _(Optional)_
LoadBalancingPolicy defines load balancing policy to use for backend urls.
Supported policies: least_loaded, first_available.
See [here](https://docs.victoriametrics.com/victoriametrics/vmauth/#load-balancing) for more details (default "least_loaded") | @@ -4968,7 +4985,7 @@ Appears in: [VMAuthSpec](#vmauthspec), [VMAuthUnauthorizedUserAccessSpec](#vmaut | default_url#
_string array_ | _(Required)_
DefaultURLs backend url for non-matching paths filter
usually used for default backend with error message | | discover_backend_ips#
_boolean_ | _(Required)_
DiscoverBackendIPs instructs discovering URLPrefix backend IPs via DNS. | | drop_src_path_prefix_parts#
_integer_ | _(Optional)_
DropSrcPathPrefixParts is the number of `/`-delimited request path prefix parts to drop before proxying the request to backend.
See [here](https://docs.victoriametrics.com/victoriametrics/vmauth/#dropping-request-path-prefix) for more details. | -| dump_request_on_errors#
_boolean_ | _(Optional)_
DumpRequestOnErrors instructs vmauth to return detailed request params to the client
if routing rules don't allow to forward request to the backends.
Useful for debugging `src_hosts` and `src_headers` based routing rules

available since v1.107.0 vmauth version | +| dump_request_on_errors#
_boolean_ | _(Optional)_
DumpRequestOnErrors instructs vmauth to return detailed request params to the client
if routing rules don't allow to forward request to the backends.
Useful for debugging `src_hosts` and `src_headers` based routing rules
available since v1.107.0 vmauth version | | headers#
_string array_ | _(Optional)_
Headers represent additional http headers, that vmauth uses
in form of ["header_key: header_value"]
multiple values for header key:
["header_key: value1,value2"]
it's available since 1.68.0 version of vmauth | | ip_filters#
_[VMUserIPFilters](#vmuseripfilters)_ | _(Optional)_
IPFilters defines per target src ip filters
supported only with enterprise version of [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/#ip-filters) | | load_balancing_policy#
_string_ | _(Optional)_
LoadBalancingPolicy defines load balancing policy to use for backend urls.
Supported policies: least_loaded, first_available.
See [here](https://docs.victoriametrics.com/victoriametrics/vmauth/#load-balancing) for more details (default "least_loaded") | @@ -4993,6 +5010,20 @@ Appears in: [VMAuthSpec](#vmauthspec), [VMAuthUnauthorizedUserAccessSpec](#vmaut | deny_list#
_string array_ | _(Required)_
| +#### VMUserJWTToken + + + +VMUserJWTToken describes JWT auth for user + +Appears in: [VMUserSpec](#vmuserspec) + +| Field | Description | +| --- | --- | +| allow_unhealthy#
_boolean_ | _(Required)_
AllowUnhealthy defines if unhealthy JWT issuer status is ignored | +| match#
_object (keys:string, values:string)_ | _(Required)_
Match defines claim match map | + + #### VMUserSpec @@ -5008,10 +5039,11 @@ Appears in: [VMUser](#vmuser) | disable_secret_creation#
_boolean_ | _(Required)_
DisableSecretCreation skips related secret creation for vmuser | | discover_backend_ips#
_boolean_ | _(Required)_
DiscoverBackendIPs instructs discovering URLPrefix backend IPs via DNS. | | drop_src_path_prefix_parts#
_integer_ | _(Optional)_
DropSrcPathPrefixParts is the number of `/`-delimited request path prefix parts to drop before proxying the request to backend.
See [here](https://docs.victoriametrics.com/victoriametrics/vmauth/#dropping-request-path-prefix) for more details. | -| dump_request_on_errors#
_boolean_ | _(Optional)_
DumpRequestOnErrors instructs vmauth to return detailed request params to the client
if routing rules don't allow to forward request to the backends.
Useful for debugging `src_hosts` and `src_headers` based routing rules

available since v1.107.0 vmauth version | +| dump_request_on_errors#
_boolean_ | _(Optional)_
DumpRequestOnErrors instructs vmauth to return detailed request params to the client
if routing rules don't allow to forward request to the backends.
Useful for debugging `src_hosts` and `src_headers` based routing rules
available since v1.107.0 vmauth version | | generatePassword#
_boolean_ | _(Optional)_
GeneratePassword instructs operator to generate password for user
if spec.password if empty. | | headers#
_string array_ | _(Optional)_
Headers represent additional http headers, that vmauth uses
in form of ["header_key: header_value"]
multiple values for header key:
["header_key: value1,value2"]
it's available since 1.68.0 version of vmauth | | ip_filters#
_[VMUserIPFilters](#vmuseripfilters)_ | _(Optional)_
IPFilters defines per target src ip filters
supported only with enterprise version of [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/#ip-filters) | +| jwt_token#
_[VMUserJWTToken](#vmuserjwttoken)_ | _(Required)_
JWTToken defines JWT auth section for user | | load_balancing_policy#
_string_ | _(Optional)_
LoadBalancingPolicy defines load balancing policy to use for backend urls.
Supported policies: least_loaded, first_available.
See [here](https://docs.victoriametrics.com/victoriametrics/vmauth/#load-balancing) for more details (default "least_loaded") | | managedMetadata#
_[ManagedObjectsMetadata](#managedobjectsmetadata)_ | _(Required)_
ManagedMetadata defines metadata that will be added to the all objects
created by operator for the given CustomResource | | max_concurrent_requests#
_integer_ | _(Optional)_
MaxConcurrentRequests defines max concurrent requests per user
300 is default value for vmauth | diff --git a/internal/controller/operator/factory/reconcile/hpa.go b/internal/controller/operator/factory/reconcile/hpa.go index c55522e7d..508ea1d3d 100644 --- a/internal/controller/operator/factory/reconcile/hpa.go +++ b/internal/controller/operator/factory/reconcile/hpa.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - v2 "k8s.io/api/autoscaling/v2" + autoscalingv2 "k8s.io/api/autoscaling/v2" "k8s.io/apimachinery/pkg/api/equality" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" @@ -15,9 +15,9 @@ import ( ) // HPA creates or update horizontalPodAutoscaler object -func HPA(ctx context.Context, rclient client.Client, newHPA, prevHPA *v2.HorizontalPodAutoscaler) error { +func HPA(ctx context.Context, rclient client.Client, newHPA, prevHPA *autoscalingv2.HorizontalPodAutoscaler) error { return retryOnConflict(func() error { - var currentHPA v2.HorizontalPodAutoscaler + var currentHPA autoscalingv2.HorizontalPodAutoscaler if err := rclient.Get(ctx, types.NamespacedName{Name: newHPA.GetName(), Namespace: newHPA.GetNamespace()}, ¤tHPA); err != nil { if k8serrors.IsNotFound(err) { logger.WithContext(ctx).Info(fmt.Sprintf("creating HPA %s configuration", newHPA.Name)) diff --git a/internal/controller/operator/factory/vmauth/vmusers_config.go b/internal/controller/operator/factory/vmauth/vmusers_config.go index a0daa3428..fc0fc50e6 100644 --- a/internal/controller/operator/factory/vmauth/vmusers_config.go +++ b/internal/controller/operator/factory/vmauth/vmusers_config.go @@ -363,6 +363,38 @@ func (pos *parsedObjects) generateVMAuthConfig(cr *vmv1beta1.VMAuth, crdCache ma return nil, fmt.Errorf("cannot build unauthorized_user config section: %w", err) } + var issuerCfg []yaml.MapSlice + for _, issuer := range cr.Spec.JWTIssuers { + if issuer == nil { + continue + } + var issuerItem yaml.MapSlice + if len(issuer.DiscoveryURL) > 0 { + issuerItem = append(issuerItem, yaml.MapItem{Key: "discovery_url", Value: issuer.DiscoveryURL}) + } + if len(issuer.JWKsURL) > 0 { + issuerItem = append(issuerItem, yaml.MapItem{Key: "jwks_url", Value: issuer.JWKsURL}) + } + var publicKeyFiles []string + if len(issuer.PublicKeyFiles) > 0 { + publicKeyFiles = append(publicKeyFiles, issuer.PublicKeyFiles...) + } + for _, ref := range issuer.PublicKeySecrets { + file, err := ac.LoadPathFromSecret(build.TLSAssetsResourceKind, cr.Namespace, ref) + if err != nil { + return nil, fmt.Errorf("cannot build jwt config section: %w", err) + } + publicKeyFiles = append(publicKeyFiles, file) + } + if len(publicKeyFiles) > 0 { + issuerItem = append(issuerItem, yaml.MapItem{Key: "public_key_files", Value: publicKeyFiles}) + } + issuerCfg = append(issuerCfg, issuerItem) + } + if len(issuerCfg) > 0 { + cfg = append(cfg, yaml.MapItem{Key: "jwt_issuers", Value: issuerCfg}) + } + if len(unAuthorizedAccessValue) > 0 { cfg = append(cfg, yaml.MapItem{Key: "unauthorized_user", Value: unAuthorizedAccessValue}) } @@ -755,27 +787,12 @@ func genUserCfg(user *vmv1beta1.VMUser, crdURLCache map[string]string, cr *vmv1b } // generate user access config. - var name, username, password, token string - if user.Spec.Name != nil { - name = *user.Spec.Name - } - if name != "" { + if user.Spec.Name != nil && len(*user.Spec.Name) > 0 { r = append(r, yaml.MapItem{ Key: "name", - Value: name, + Value: *user.Spec.Name, }) } - - if user.Spec.Username != nil { - username = *user.Spec.Username - } - if user.Spec.Password != nil { - password = *user.Spec.Password - } - - if user.Spec.BearerToken != nil { - token = *user.Spec.BearerToken - } r, err = addUserConfigOptionToYaml(r, user.Spec.VMUserConfigOptions, cr, ac) if err != nil { return nil, err @@ -786,32 +803,34 @@ func genUserCfg(user *vmv1beta1.VMUser, crdURLCache map[string]string, cr *vmv1b Value: user.Spec.MetricLabels, }) } - - // fast path. - if token != "" { + if user.Spec.BearerToken != nil && len(*user.Spec.BearerToken) > 0 { r = append(r, yaml.MapItem{ Key: "bearer_token", - Value: token, + Value: *user.Spec.BearerToken, }) return r, nil } - // mutate vmuser - if username == "" { - username = user.Name - user.Spec.Username = ptr.To(username) + if user.Spec.JWTToken != nil { + r = append(r, yaml.MapItem{ + Key: "jwt_token", + Value: user.Spec.JWTToken, + }) + return r, nil + } + username := user.Name + if user.Spec.Username != nil && len(*user.Spec.Username) > 0 { + username = *user.Spec.Username } - r = append(r, yaml.MapItem{ Key: "username", Value: username, }) - if password != "" { + if user.Spec.Password != nil && len(*user.Spec.Password) > 0 { r = append(r, yaml.MapItem{ Key: "password", - Value: password, + Value: *user.Spec.Password, }) } - return r, nil } diff --git a/internal/controller/operator/factory/vmauth/vmusers_config_test.go b/internal/controller/operator/factory/vmauth/vmusers_config_test.go index 9d4b1633e..0e9baa99e 100644 --- a/internal/controller/operator/factory/vmauth/vmusers_config_test.go +++ b/internal/controller/operator/factory/vmauth/vmusers_config_test.go @@ -1097,6 +1097,9 @@ func Test_buildConfig(t *testing.T) { }, Spec: vmv1beta1.VMAuthSpec{ SelectAllByDefault: true, + JWTIssuers: []*vmv1beta1.VMAuthJWTIssuer{ + {}, + }, }, }, predefinedObjects: []runtime.Object{ @@ -1153,6 +1156,25 @@ func Test_buildConfig(t *testing.T) { }, }, }, + &vmv1beta1.VMUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: "user-jwt", + Namespace: "default", + }, + Spec: vmv1beta1.VMUserSpec{ + JWTToken: &vmv1beta1.VMUserJWTToken{}, + TargetRefs: []vmv1beta1.TargetRef{ + { + CRD: &vmv1beta1.CRDRef{ + Kind: "VMAgent", + Name: "test", + Namespace: "default", + }, + Paths: []string{"/"}, + }, + }, + }, + }, &vmv1beta1.VMAgent{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -1168,6 +1190,11 @@ func Test_buildConfig(t *testing.T) { - url_prefix: - http://vmagent-test.default.svc:8429 bearer_token: bearer-token-2 +- url_prefix: + - http://vmagent-test.default.svc:8429 + jwt_token: {} +jwt_issuers: +- {} `, })