Skip to content

Commit 3384e63

Browse files
vmauth: JWT support
1 parent ebb26fa commit 3384e63

File tree

8 files changed

+223
-9
lines changed

8 files changed

+223
-9
lines changed

.golangci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ linters:
4444
alias: metav1
4545
- pkg: k8s.io/api/apps/v1
4646
alias: appsv1
47-
- pkg: k8s.io/api/autoscaling/v2"
47+
- pkg: k8s.io/api/autoscaling/v2
4848
alias: autoscalingv2
4949
- pkg: k8s.io/apimachinery/pkg/api/errors
5050
alias: k8serrors
51+
- pkg: k8s.io/api/networking/v1
52+
alias: networkingv1
5153
formatters:
5254
enable:
5355
- gofmt

api/operator/v1beta1/vmauth_types.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import (
77
"regexp"
88
"strings"
99

10-
v12 "k8s.io/api/networking/v1"
10+
corev1 "k8s.io/api/core/v1"
11+
networkingv1 "k8s.io/api/networking/v1"
1112
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1213
"k8s.io/apimachinery/pkg/labels"
1314
"k8s.io/utils/ptr"
@@ -84,6 +85,9 @@ type VMAuthSpec struct {
8485
// will be added after removal of VMUserConfigOptions
8586
// currently it has collision with inlined fields
8687
// IPFilters VMUserIPFilters `json:"ip_filters,omitempty"`
88+
// OODC represents configuration section for OIDC authorization
89+
// +optional
90+
OIDC []*VMAuthOIDCRealm `json:"jwt,omitempty"`
8791
// will be removed at v1.0 release
8892
// +deprecated
8993
// +kubebuilder:validation:Schemaless
@@ -125,6 +129,28 @@ type VMAuthSpec struct {
125129
UseProxyProtocol bool `json:"useProxyProtocol,omitempty"`
126130
}
127131

132+
// VMAuthOIDCRealm defines OIDC realm parameters
133+
type VMAuthOIDCRealm struct {
134+
// EnforcePrefix requires JWT token to start with "Bearer: "
135+
// +optional
136+
EnforcePrefix bool `json:"enforce_prefix,omitempty"`
137+
// IssuerURL is OpenID Connect issuer URL
138+
// +optional
139+
IssuerURL string `json:"issuer_url,omitempty"`
140+
// JWKsURL is the OpenID Connect JWKS URL
141+
// +optional
142+
JWKsURL string `json:"jwks_url,omitempty"`
143+
// SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
144+
// +optional
145+
SkipDiscovery bool `json:"skip_discovery,omitempty"`
146+
// PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
147+
// for verifying JWT tokens
148+
PublicKeyFiles []string `json:"public_key_files,omitempty"`
149+
// PublicKeySecrets is a list of k8s Secret selectors pointing to public key files in PEM format to use
150+
// for verifying JWT tokens
151+
PublicKeySecrets []*corev1.SecretKeySelector `json:"public_key_secrets,omitempty"`
152+
}
153+
128154
// VMAuthUnauthorizedUserAccessSpec defines unauthorized_user section configuration for vmauth
129155
type VMAuthUnauthorizedUserAccessSpec struct {
130156
// URLPrefix defines prefix prefix for destination
@@ -425,7 +451,9 @@ func (cr *VMAuth) Validate() error {
425451
return fmt.Errorf("incorrect cr.spec UnauthorizedAccessConfig options: %w", err)
426452
}
427453
}
428-
454+
if len(cr.Spec.OIDC) > 0 && !cr.Spec.License.IsProvided() {
455+
return fmt.Errorf("spec.jwt is only allowed in enterprise mode, but no license provided")
456+
}
429457
if cr.Spec.UnauthorizedUserAccessSpec != nil {
430458
if err := cr.Spec.UnauthorizedUserAccessSpec.Validate(); err != nil {
431459
return fmt.Errorf("incorrect cr.spec.UnauthorizedUserAccess syntax: %w", err)
@@ -461,11 +489,11 @@ type EmbeddedIngress struct {
461489
// ExtraRules - additional rules for ingress,
462490
// must be checked for correctness by user.
463491
// +optional
464-
ExtraRules []v12.IngressRule `json:"extraRules,omitempty" yaml:"extraRules,omitempty"`
492+
ExtraRules []networkingv1.IngressRule `json:"extraRules,omitempty" yaml:"extraRules,omitempty"`
465493
// ExtraTLS - additional TLS configuration for ingress
466494
// must be checked for correctness by user.
467495
// +optional
468-
ExtraTLS []v12.IngressTLS `json:"extraTls,omitempty" yaml:"extraTls,omitempty"`
496+
ExtraTLS []networkingv1.IngressTLS `json:"extraTls,omitempty" yaml:"extraTls,omitempty"`
469497
// Host defines ingress host parameter for default rule
470498
// It will be used, only if TlsHosts is empty
471499
// +optional

api/operator/v1beta1/vmuser_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ type VMUserSpec struct {
1515
// Name of the VMUser object.
1616
// +optional
1717
Name *string `json:"name,omitempty"`
18+
// ClientID extracted from JWT token
19+
// +optional
20+
ClientID *string `json:"client_id,omitempty"`
1821
// UserName basic auth user name for accessing protected endpoint,
1922
// will be replaced with metadata.name of VMUser if omitted.
2023
// +optional

api/operator/v1beta1/zz_generated.deepcopy.go

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/overlay/crd.yaml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23651,6 +23651,69 @@ spec:
2365123651
and v1.111.0 vmauth version
2365223652
related doc https://docs.victoriametrics.com/victoriametrics/vmauth/#security
2365323653
type: string
23654+
jwt:
23655+
description: |-
23656+
IPFilters global access ip filters
23657+
supported only with enterprise version of [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/#ip-filters)
23658+
will be added after removal of VMUserConfigOptions
23659+
currently it has collision with inlined fields
23660+
IPFilters VMUserIPFilters `json:"ip_filters,omitempty"`
23661+
OODC represents configuration section for OIDC authorization
23662+
items:
23663+
description: VMAuthOIDCRealm defines OIDC realm parameters
23664+
properties:
23665+
enforce_prefix:
23666+
description: 'EnforcePrefix requires JWT token to start with
23667+
"Bearer: "'
23668+
type: boolean
23669+
issuer_url:
23670+
description: IssuerURL is OpenID Connect issuer URL
23671+
type: string
23672+
jwks_url:
23673+
description: JWKsURL is the OpenID Connect JWKS URL
23674+
type: string
23675+
public_key_files:
23676+
description: |-
23677+
PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
23678+
for verifying JWT tokens
23679+
items:
23680+
type: string
23681+
type: array
23682+
public_key_secrets:
23683+
description: |-
23684+
PublicKeySecrets is a list of k8s Secret selectors pointing to public key files in PEM format to use
23685+
for verifying JWT tokens
23686+
items:
23687+
description: SecretKeySelector selects a key of a Secret.
23688+
properties:
23689+
key:
23690+
description: The key of the secret to select from. Must
23691+
be a valid secret key.
23692+
type: string
23693+
name:
23694+
default: ""
23695+
description: |-
23696+
Name of the referent.
23697+
This field is effectively required, but due to backwards compatibility is
23698+
allowed to be empty. Instances of this type with an empty value here are
23699+
almost certainly wrong.
23700+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
23701+
type: string
23702+
optional:
23703+
description: Specify whether the Secret or its key must
23704+
be defined
23705+
type: boolean
23706+
required:
23707+
- key
23708+
type: object
23709+
x-kubernetes-map-type: atomic
23710+
type: array
23711+
skip_discovery:
23712+
description: SkipDiscovery allows to skip OIDC discovery and
23713+
use manually supplied Endpoints
23714+
type: boolean
23715+
type: object
23716+
type: array
2365423717
license:
2365523718
description: |-
2365623719
License allows to configure license key to be used for enterprise features.
@@ -38811,6 +38874,9 @@ spec:
3881138874
description: BearerToken Authorization header value for accessing
3881238875
protected endpoint.
3881338876
type: string
38877+
client_id:
38878+
description: ClientID extracted from JWT token
38879+
type: string
3881438880
default_url:
3881538881
description: |-
3881638882
DefaultURLs backend url for non-matching paths filter

docs/api.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3547,6 +3547,24 @@ Appears in: [VMAuthLoadBalancer](#vmauthloadbalancer)
35473547
| volumes<a href="#vmauthloadbalancerspec-volumes" id="vmauthloadbalancerspec-volumes">#</a><br/>_[Volume](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#volume-v1-core) array_ | _(Required)_<br/>Volumes allows configuration of additional volumes on the output Deployment/StatefulSet definition.<br />Volumes specified will be appended to other volumes that are generated.<br />/ +optional |
35483548

35493549

3550+
#### VMAuthOIDCRealm
3551+
3552+
3553+
3554+
VMAuthOIDCRealm defines OIDC realm parameters
3555+
3556+
Appears in: [VMAuthSpec](#vmauthspec)
3557+
3558+
| Field | Description |
3559+
| --- | --- |
3560+
| enforce_prefix<a href="#vmauthoidcrealm-enforce_prefix" id="vmauthoidcrealm-enforce_prefix">#</a><br/>_boolean_ | _(Optional)_<br/>EnforcePrefix requires JWT token to start with "Bearer: " |
3561+
| issuer_url<a href="#vmauthoidcrealm-issuer_url" id="vmauthoidcrealm-issuer_url">#</a><br/>_string_ | _(Optional)_<br/>IssuerURL is OpenID Connect issuer URL |
3562+
| jwks_url<a href="#vmauthoidcrealm-jwks_url" id="vmauthoidcrealm-jwks_url">#</a><br/>_string_ | _(Optional)_<br/>JWKsURL is the OpenID Connect JWKS URL |
3563+
| public_key_files<a href="#vmauthoidcrealm-public_key_files" id="vmauthoidcrealm-public_key_files">#</a><br/>_string array_ | _(Required)_<br/>PublicKeyFiles is a list of paths pointing to public key files in PEM format to use<br />for verifying JWT tokens |
3564+
| public_key_secrets<a href="#vmauthoidcrealm-public_key_secrets" id="vmauthoidcrealm-public_key_secrets">#</a><br/>_[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core) array_ | _(Required)_<br/>PublicKeySecrets is a list of k8s Secret selectors pointing to public key files in PEM format to use<br />for verifying JWT tokens |
3565+
| skip_discovery<a href="#vmauthoidcrealm-skip_discovery" id="vmauthoidcrealm-skip_discovery">#</a><br/>_boolean_ | _(Optional)_<br/>SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints |
3566+
3567+
35503568
#### VMAuthSpec
35513569

35523570

@@ -3587,6 +3605,7 @@ Appears in: [VMAuth](#vmauth)
35873605
| initContainers<a href="#vmauthspec-initcontainers" id="vmauthspec-initcontainers">#</a><br/>_[Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#container-v1-core) array_ | _(Optional)_<br/>InitContainers allows adding initContainers to the pod definition.<br />Any errors during the execution of an initContainer will lead to a restart of the Pod.<br />More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ |
35883606
| internalListenPort<a href="#vmauthspec-internallistenport" id="vmauthspec-internallistenport">#</a><br/>_string_ | _(Optional)_<br/>InternalListenPort instructs vmauth to serve internal routes at given port<br />available from v0.56.0 operator<br />and v1.111.0 vmauth version<br />related doc https://docs.victoriametrics.com/victoriametrics/vmauth/#security |
35893607
| ip_filters<a href="#vmauthspec-ip_filters" id="vmauthspec-ip_filters">#</a><br/>_[VMUserIPFilters](#vmuseripfilters)_ | _(Optional)_<br/>IPFilters defines per target src ip filters<br />supported only with enterprise version of [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/#ip-filters) |
3608+
| jwt<a href="#vmauthspec-jwt" id="vmauthspec-jwt">#</a><br/>_[VMAuthOIDCRealm](#vmauthoidcrealm) array_ | _(Optional)_<br/>IPFilters global access ip filters<br />supported only with enterprise version of [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/#ip-filters)<br />will be added after removal of VMUserConfigOptions<br />currently it has collision with inlined fields<br />IPFilters VMUserIPFilters `json:"ip_filters,omitempty"`<br />OODC represents configuration section for OIDC authorization |
35903609
| license<a href="#vmauthspec-license" id="vmauthspec-license">#</a><br/>_[License](#license)_ | _(Optional)_<br/>License allows to configure license key to be used for enterprise features.<br />Using license key is supported starting from VictoriaMetrics v1.94.0.<br />See [here](https://docs.victoriametrics.com/victoriametrics/enterprise/) |
35913610
| load_balancing_policy<a href="#vmauthspec-load_balancing_policy" id="vmauthspec-load_balancing_policy">#</a><br/>_string_ | _(Optional)_<br/>LoadBalancingPolicy defines load balancing policy to use for backend urls.<br />Supported policies: least_loaded, first_available.<br />See [here](https://docs.victoriametrics.com/victoriametrics/vmauth#load-balancing) for more details (default "least_loaded") |
35923611
| logFormat<a href="#vmauthspec-logformat" id="vmauthspec-logformat">#</a><br/>_string_ | _(Optional)_<br/>LogFormat for VMAuth to be configured with. |
@@ -4476,6 +4495,7 @@ Appears in: [VMUser](#vmuser)
44764495
| Field | Description |
44774496
| --- | --- |
44784497
| bearerToken<a href="#vmuserspec-bearertoken" id="vmuserspec-bearertoken">#</a><br/>_string_ | _(Optional)_<br/>BearerToken Authorization header value for accessing protected endpoint. |
4498+
| client_id<a href="#vmuserspec-client_id" id="vmuserspec-client_id">#</a><br/>_string_ | _(Optional)_<br/>ClientID extracted from JWT token |
44794499
| default_url<a href="#vmuserspec-default_url" id="vmuserspec-default_url">#</a><br/>_string array_ | _(Required)_<br/>DefaultURLs backend url for non-matching paths filter<br />usually used for default backend with error message |
44804500
| disable_secret_creation<a href="#vmuserspec-disable_secret_creation" id="vmuserspec-disable_secret_creation">#</a><br/>_boolean_ | _(Required)_<br/>DisableSecretCreation skips related secret creation for vmuser |
44814501
| discover_backend_ips<a href="#vmuserspec-discover_backend_ips" id="vmuserspec-discover_backend_ips">#</a><br/>_boolean_ | _(Required)_<br/>DiscoverBackendIPs instructs discovering URLPrefix backend IPs via DNS. |

internal/controller/operator/factory/reconcile/hpa.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"context"
55
"fmt"
66

7-
v2 "k8s.io/api/autoscaling/v2"
7+
autoscalingv2 "k8s.io/api/autoscaling/v2"
88
"k8s.io/apimachinery/pkg/api/equality"
99
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1010
"k8s.io/apimachinery/pkg/types"
@@ -16,9 +16,9 @@ import (
1616
)
1717

1818
// HPA creates or update horizontalPodAutoscaler object
19-
func HPA(ctx context.Context, rclient client.Client, newHPA, prevHPA *v2.HorizontalPodAutoscaler) error {
19+
func HPA(ctx context.Context, rclient client.Client, newHPA, prevHPA *autoscalingv2.HorizontalPodAutoscaler) error {
2020
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
21-
var currentHPA v2.HorizontalPodAutoscaler
21+
var currentHPA autoscalingv2.HorizontalPodAutoscaler
2222
if err := rclient.Get(ctx, types.NamespacedName{Name: newHPA.GetName(), Namespace: newHPA.GetNamespace()}, &currentHPA); err != nil {
2323
if k8serrors.IsNotFound(err) {
2424
logger.WithContext(ctx).Info(fmt.Sprintf("creating HPA %s configuration", newHPA.Name))

internal/controller/operator/factory/vmauth/vmusers_config.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,42 @@ func generateVMAuthConfig(cr *vmv1beta1.VMAuth, sus *skipableVMUsers, crdCache m
424424
return nil, fmt.Errorf("cannot build unauthorized_user config section: %w", err)
425425
}
426426

427+
var oidcCfg []yaml.MapSlice
428+
for _, realm := range cr.Spec.OIDC {
429+
if realm == nil {
430+
continue
431+
}
432+
var oidcItem yaml.MapSlice
433+
if realm.SkipDiscovery {
434+
if len(realm.JWKsURL) > 0 {
435+
oidcItem = append(oidcItem, yaml.MapItem{Key: "jwks_url", Value: realm.JWKsURL})
436+
}
437+
var publicKeyFiles []string
438+
if len(realm.PublicKeyFiles) > 0 {
439+
publicKeyFiles = append(publicKeyFiles, realm.PublicKeyFiles...)
440+
}
441+
for _, ref := range realm.PublicKeySecrets {
442+
file, err := ac.LoadPathFromSecret(build.TLSAssetsResourceKind, cr.Namespace, ref)
443+
if err != nil {
444+
return nil, fmt.Errorf("cannot build jwt config section: %w", err)
445+
}
446+
publicKeyFiles = append(publicKeyFiles, file)
447+
}
448+
if len(publicKeyFiles) > 0 {
449+
oidcItem = append(oidcItem, yaml.MapItem{Key: "public_key_files", Value: publicKeyFiles})
450+
}
451+
} else {
452+
oidcItem = append(oidcItem, yaml.MapItem{Key: "issuer_url", Value: realm.IssuerURL})
453+
}
454+
if realm.EnforcePrefix {
455+
oidcItem = append(oidcItem, yaml.MapItem{Key: "enforce_prefix", Value: realm.EnforcePrefix})
456+
}
457+
oidcCfg = append(oidcCfg, oidcItem)
458+
}
459+
if len(oidcCfg) > 0 {
460+
cfg = append(cfg, yaml.MapItem{Key: "oidc", Value: oidcCfg})
461+
}
462+
427463
if len(unAuthorizedAccessValue) > 0 {
428464
cfg = append(cfg, yaml.MapItem{Key: "unauthorized_user", Value: unAuthorizedAccessValue})
429465
}
@@ -789,7 +825,7 @@ func genUserCfg(user *vmv1beta1.VMUser, crdURLCache map[string]string, cr *vmv1b
789825
}
790826

791827
// generate user access config.
792-
var name, username, password, token string
828+
var name, username, password, token, clientID string
793829
if user.Spec.Name != nil {
794830
name = *user.Spec.Name
795831
}
@@ -807,6 +843,9 @@ func genUserCfg(user *vmv1beta1.VMUser, crdURLCache map[string]string, cr *vmv1b
807843
password = *user.Spec.Password
808844
}
809845

846+
if cr.Spec.License.IsProvided() && user.Spec.ClientID != nil {
847+
clientID = *user.Spec.ClientID
848+
}
810849
if user.Spec.BearerToken != nil {
811850
token = *user.Spec.BearerToken
812851
}
@@ -829,6 +868,15 @@ func genUserCfg(user *vmv1beta1.VMUser, crdURLCache map[string]string, cr *vmv1b
829868
})
830869
return r, nil
831870
}
871+
872+
if clientID != "" {
873+
r = append(r, yaml.MapItem{
874+
Key: "client_id",
875+
Value: clientID,
876+
})
877+
return r, nil
878+
}
879+
832880
// mutate vmuser
833881
if username == "" {
834882
username = user.Name

0 commit comments

Comments
 (0)