Skip to content

Commit 293aa29

Browse files
vmauth: JWT support
1 parent ebb26fa commit 293aa29

File tree

8 files changed

+129
-9
lines changed

8 files changed

+129
-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: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"regexp"
88
"strings"
99

10-
v12 "k8s.io/api/networking/v1"
10+
networkingv1 "k8s.io/api/networking/v1"
1111
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1212
"k8s.io/apimachinery/pkg/labels"
1313
"k8s.io/utils/ptr"
@@ -84,6 +84,9 @@ type VMAuthSpec struct {
8484
// will be added after removal of VMUserConfigOptions
8585
// currently it has collision with inlined fields
8686
// IPFilters VMUserIPFilters `json:"ip_filters,omitempty"`
87+
// JWT represents configuration section for JWT authorization
88+
// +optional
89+
JWT []*VMAuthJWTRealm `json:"jwt,omitempty"`
8790
// will be removed at v1.0 release
8891
// +deprecated
8992
// +kubebuilder:validation:Schemaless
@@ -125,6 +128,15 @@ type VMAuthSpec struct {
125128
UseProxyProtocol bool `json:"useProxyProtocol,omitempty"`
126129
}
127130

131+
// VMAuthJWTRealm defines JWT realm parameters
132+
type VMAuthJWTRealm struct {
133+
// EnforcePrefix requires JWT token to start with "Bearer: "
134+
// +optional
135+
EnforcePrefix bool `json:"enforce_prefix,omitempty"`
136+
// OIDCEndpoint OIDC discovery endpoint
137+
OIDCEndpoint string `json:"oidc_endpoint"`
138+
}
139+
128140
// VMAuthUnauthorizedUserAccessSpec defines unauthorized_user section configuration for vmauth
129141
type VMAuthUnauthorizedUserAccessSpec struct {
130142
// URLPrefix defines prefix prefix for destination
@@ -425,7 +437,9 @@ func (cr *VMAuth) Validate() error {
425437
return fmt.Errorf("incorrect cr.spec UnauthorizedAccessConfig options: %w", err)
426438
}
427439
}
428-
440+
if cr.Spec.JWT != nil && !cr.Spec.License.IsProvided() {
441+
return fmt.Errorf("spec.jwt is only allowed in enterprise mode, but no license provided")
442+
}
429443
if cr.Spec.UnauthorizedUserAccessSpec != nil {
430444
if err := cr.Spec.UnauthorizedUserAccessSpec.Validate(); err != nil {
431445
return fmt.Errorf("incorrect cr.spec.UnauthorizedUserAccess syntax: %w", err)
@@ -461,11 +475,11 @@ type EmbeddedIngress struct {
461475
// ExtraRules - additional rules for ingress,
462476
// must be checked for correctness by user.
463477
// +optional
464-
ExtraRules []v12.IngressRule `json:"extraRules,omitempty" yaml:"extraRules,omitempty"`
478+
ExtraRules []networkingv1.IngressRule `json:"extraRules,omitempty" yaml:"extraRules,omitempty"`
465479
// ExtraTLS - additional TLS configuration for ingress
466480
// must be checked for correctness by user.
467481
// +optional
468-
ExtraTLS []v12.IngressTLS `json:"extraTls,omitempty" yaml:"extraTls,omitempty"`
482+
ExtraTLS []networkingv1.IngressTLS `json:"extraTls,omitempty" yaml:"extraTls,omitempty"`
469483
// Host defines ingress host parameter for default rule
470484
// It will be used, only if TlsHosts is empty
471485
// +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: 31 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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23651,6 +23651,28 @@ 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+
JWT represents configuration section for JWT authorization
23662+
items:
23663+
description: VMAuthJWTRealm defines JWT realm parameters
23664+
properties:
23665+
enforce_prefix:
23666+
description: 'EnforcePrefix requires JWT token to start with
23667+
"Bearer: "'
23668+
type: boolean
23669+
oidc_endpoint:
23670+
description: OIDCEndpoint OIDC discovery endpoint
23671+
type: string
23672+
required:
23673+
- oidc_endpoint
23674+
type: object
23675+
type: array
2365423676
license:
2365523677
description: |-
2365623678
License allows to configure license key to be used for enterprise features.
@@ -38811,6 +38833,9 @@ spec:
3881138833
description: BearerToken Authorization header value for accessing
3881238834
protected endpoint.
3881338835
type: string
38836+
client_id:
38837+
description: ClientID extracted from JWT token
38838+
type: string
3881438839
default_url:
3881538840
description: |-
3881638841
DefaultURLs backend url for non-matching paths filter

docs/api.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3475,6 +3475,20 @@ VMAuth is the Schema for the vmauths API
34753475
| spec<a href="#vmauth-spec" id="vmauth-spec">#</a><br/>_[VMAuthSpec](#vmauthspec)_ | _(Required)_<br/> |
34763476

34773477

3478+
#### VMAuthJWTRealm
3479+
3480+
3481+
3482+
VMAuthJWTRealm defines JWT realm parameters
3483+
3484+
Appears in: [VMAuthSpec](#vmauthspec)
3485+
3486+
| Field | Description |
3487+
| --- | --- |
3488+
| enforce_prefix<a href="#vmauthjwtrealm-enforce_prefix" id="vmauthjwtrealm-enforce_prefix">#</a><br/>_boolean_ | _(Optional)_<br/>EnforcePrefix requires JWT token to start with "Bearer: " |
3489+
| oidc_endpoint<a href="#vmauthjwtrealm-oidc_endpoint" id="vmauthjwtrealm-oidc_endpoint">#</a><br/>_string_ | _(Required)_<br/>OIDCEndpoint OIDC discovery endpoint |
3490+
3491+
34783492
#### VMAuthLoadBalancer
34793493

34803494

@@ -3587,6 +3601,7 @@ Appears in: [VMAuth](#vmauth)
35873601
| 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/ |
35883602
| 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 |
35893603
| 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) |
3604+
| jwt<a href="#vmauthspec-jwt" id="vmauthspec-jwt">#</a><br/>_[VMAuthJWTRealm](#vmauthjwtrealm) 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 />JWT represents configuration section for JWT authorization |
35903605
| 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/) |
35913606
| 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") |
35923607
| logFormat<a href="#vmauthspec-logformat" id="vmauthspec-logformat">#</a><br/>_string_ | _(Optional)_<br/>LogFormat for VMAuth to be configured with. |
@@ -4476,6 +4491,7 @@ Appears in: [VMUser](#vmuser)
44764491
| Field | Description |
44774492
| --- | --- |
44784493
| bearerToken<a href="#vmuserspec-bearertoken" id="vmuserspec-bearertoken">#</a><br/>_string_ | _(Optional)_<br/>BearerToken Authorization header value for accessing protected endpoint. |
4494+
| client_id<a href="#vmuserspec-client_id" id="vmuserspec-client_id">#</a><br/>_string_ | _(Optional)_<br/>ClientID extracted from JWT token |
44794495
| 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 |
44804496
| 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 |
44814497
| 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: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,23 @@ 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 jwtCfg []yaml.MapSlice
428+
for _, realm := range cr.Spec.JWT {
429+
if realm == nil {
430+
continue
431+
}
432+
jwtItem := yaml.MapSlice{
433+
yaml.MapItem{Key: "oidc_endpoint", Value: realm.OIDCEndpoint},
434+
}
435+
if realm.EnforcePrefix {
436+
jwtItem = append(jwtItem, yaml.MapItem{Key: "enforce_prefix", Value: realm.EnforcePrefix})
437+
}
438+
jwtCfg = append(jwtCfg, jwtItem)
439+
}
440+
if len(jwtCfg) > 0 {
441+
cfg = append(cfg, yaml.MapItem{Key: "jwt", Value: jwtCfg})
442+
}
443+
427444
if len(unAuthorizedAccessValue) > 0 {
428445
cfg = append(cfg, yaml.MapItem{Key: "unauthorized_user", Value: unAuthorizedAccessValue})
429446
}
@@ -789,7 +806,7 @@ func genUserCfg(user *vmv1beta1.VMUser, crdURLCache map[string]string, cr *vmv1b
789806
}
790807

791808
// generate user access config.
792-
var name, username, password, token string
809+
var name, username, password, token, clientID string
793810
if user.Spec.Name != nil {
794811
name = *user.Spec.Name
795812
}
@@ -807,6 +824,9 @@ func genUserCfg(user *vmv1beta1.VMUser, crdURLCache map[string]string, cr *vmv1b
807824
password = *user.Spec.Password
808825
}
809826

827+
if cr.Spec.License.IsProvided() && user.Spec.ClientID != nil {
828+
clientID = *user.Spec.ClientID
829+
}
810830
if user.Spec.BearerToken != nil {
811831
token = *user.Spec.BearerToken
812832
}
@@ -829,6 +849,15 @@ func genUserCfg(user *vmv1beta1.VMUser, crdURLCache map[string]string, cr *vmv1b
829849
})
830850
return r, nil
831851
}
852+
853+
if clientID != "" {
854+
r = append(r, yaml.MapItem{
855+
Key: "client_id",
856+
Value: clientID,
857+
})
858+
return r, nil
859+
}
860+
832861
// mutate vmuser
833862
if username == "" {
834863
username = user.Name

0 commit comments

Comments
 (0)