Skip to content

Commit 1fa4a6b

Browse files
authored
[Feature] [Platform] Auto User Creation (#1896)
1 parent 5535997 commit 1fa4a6b

File tree

13 files changed

+261
-29
lines changed

13 files changed

+261
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- (Feature) (Platform) Envoy Cache Introduction
1111
- (Feature) (Platform) OpenID Integration - API Extension
1212
- (Feature) Windows Platform CLI
13+
- (Feature) (Platform) Auth User Creation
1314

1415
## [1.2.48](https://github.com/arangodb/kube-arangodb/tree/1.2.48) (2025-05-08)
1516
- (Maintenance) Extend Documentation

docs/cli/arangodb_operator_integration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ Flags:
4444
--integration.config.v1.internal Defones if Internal access to service config.v1 is enabled (Env: INTEGRATION_CONFIG_V1_INTERNAL) (default true)
4545
--integration.config.v1.module strings Module in the reference <name>=<abs path> (Env: INTEGRATION_CONFIG_V1_MODULE)
4646
--integration.envoy.auth.v3 Enable EnvoyAuthV3 Integration Service (Env: INTEGRATION_ENVOY_AUTH_V3)
47+
--integration.envoy.auth.v3.database.endpoint string Endpoint of ArangoDB (Env: INTEGRATION_ENVOY_AUTH_V3_DATABASE_ENDPOINT)
48+
--integration.envoy.auth.v3.database.port int Port of ArangoDB (Env: INTEGRATION_ENVOY_AUTH_V3_DATABASE_PORT) (default 8529)
49+
--integration.envoy.auth.v3.database.proto string Proto of the ArangoDB endpoint (Env: INTEGRATION_ENVOY_AUTH_V3_DATABASE_PROTO) (default "http")
4750
--integration.envoy.auth.v3.extensions.cookie.jwt Defines if Cookie JWT extension is enabled (Env: INTEGRATION_ENVOY_AUTH_V3_EXTENSIONS_COOKIE_JWT) (default true)
4851
--integration.envoy.auth.v3.extensions.jwt Defines if JWT extension is enabled (Env: INTEGRATION_ENVOY_AUTH_V3_EXTENSIONS_JWT) (default true)
52+
--integration.envoy.auth.v3.extensions.users.create Defines if UserCreation extension is enabled (Env: INTEGRATION_ENVOY_AUTH_V3_EXTENSIONS_USERS_CREATE)
4953
--integration.envoy.auth.v3.external Defones if External access to service envoy.auth.v3 is enabled (Env: INTEGRATION_ENVOY_AUTH_V3_EXTERNAL)
5054
--integration.envoy.auth.v3.internal Defones if Internal access to service envoy.auth.v3 is enabled (Env: INTEGRATION_ENVOY_AUTH_V3_INTERNAL) (default true)
5155
--integration.scheduler.v1 SchedulerV1 Integration (Env: INTEGRATION_SCHEDULER_V1)

integrations/envoy/auth/v3/impl/auth_bearer/impl.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,15 @@ func New(configuration pbImplEnvoyAuthV3Shared.Configuration) (pbImplEnvoyAuthV3
4141
var z impl
4242

4343
z.configuration = configuration
44+
z.authClient = cache.NewObject[pbAuthenticationV1.AuthenticationV1Client](configuration.GetAuthClientFetcher)
45+
4446
z.cache = cache.NewCache[pbImplEnvoyAuthV3Shared.Token, pbImplEnvoyAuthV3Shared.ResponseAuth](func(ctx context.Context, in pbImplEnvoyAuthV3Shared.Token) (pbImplEnvoyAuthV3Shared.ResponseAuth, error) {
45-
resp, err := z.configuration.AuthClient.Validate(ctx, &pbAuthenticationV1.ValidateRequest{
47+
client, err := z.authClient.Get(ctx)
48+
if err != nil {
49+
return pbImplEnvoyAuthV3Shared.ResponseAuth{}, err
50+
}
51+
52+
resp, err := client.Validate(ctx, &pbAuthenticationV1.ValidateRequest{
4653
Token: string(in),
4754
})
4855
if err != nil {
@@ -70,6 +77,8 @@ type impl struct {
7077
configuration pbImplEnvoyAuthV3Shared.Configuration
7178

7279
cache cache.Cache[pbImplEnvoyAuthV3Shared.Token, pbImplEnvoyAuthV3Shared.ResponseAuth]
80+
81+
authClient cache.Object[pbAuthenticationV1.AuthenticationV1Client]
7382
}
7483

7584
func (p impl) Handle(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest, current *pbImplEnvoyAuthV3Shared.Response) error {

integrations/envoy/auth/v3/impl/auth_cookie/impl.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,21 @@ import (
3636
const JWTAuthorizationCookieName = "X-ArangoDB-Token-JWT"
3737

3838
func New(configuration pbImplEnvoyAuthV3Shared.Configuration) (pbImplEnvoyAuthV3Shared.AuthHandler, bool) {
39-
if !configuration.Extensions.JWT {
39+
if !configuration.Extensions.CookieJWT {
4040
return nil, false
4141
}
4242

4343
var z impl
4444

4545
z.configuration = configuration
46+
z.authClient = cache.NewObject[pbAuthenticationV1.AuthenticationV1Client](configuration.GetAuthClientFetcher)
4647
z.cache = cache.NewCache[pbImplEnvoyAuthV3Shared.Token, pbImplEnvoyAuthV3Shared.ResponseAuth](func(ctx context.Context, in pbImplEnvoyAuthV3Shared.Token) (pbImplEnvoyAuthV3Shared.ResponseAuth, error) {
47-
resp, err := z.configuration.AuthClient.Validate(ctx, &pbAuthenticationV1.ValidateRequest{
48+
client, err := z.authClient.Get(ctx)
49+
if err != nil {
50+
return pbImplEnvoyAuthV3Shared.ResponseAuth{}, err
51+
}
52+
53+
resp, err := client.Validate(ctx, &pbAuthenticationV1.ValidateRequest{
4854
Token: string(in),
4955
})
5056
if err != nil {
@@ -72,6 +78,8 @@ type impl struct {
7278
configuration pbImplEnvoyAuthV3Shared.Configuration
7379

7480
cache cache.Cache[pbImplEnvoyAuthV3Shared.Token, pbImplEnvoyAuthV3Shared.ResponseAuth]
81+
82+
authClient cache.Object[pbAuthenticationV1.AuthenticationV1Client]
7583
}
7684

7785
func (p impl) Handle(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest, current *pbImplEnvoyAuthV3Shared.Response) error {

integrations/envoy/auth/v3/impl/impl.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3/impl/auth_required"
2727
"github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3/impl/pass_mode"
2828
"github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3/impl/required"
29+
"github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3/impl/users"
2930
pbImplEnvoyAuthV3Shared "github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3/shared"
3031
)
3132

@@ -36,5 +37,6 @@ func Factory() pbImplEnvoyAuthV3Shared.Factory {
3637
auth_cookie.New,
3738
auth_required.New,
3839
pass_mode.New,
40+
users.New,
3941
)
4042
}

integrations/envoy/auth/v3/impl/pass_mode/impl.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func New(configuration pbImplEnvoyAuthV3Shared.Configuration) (pbImplEnvoyAuthV3
4242
var z impl
4343

4444
z.configuration = configuration
45+
z.authClient = cache.NewObject(configuration.GetAuthClientFetcher)
4546
z.cache = cache.NewHashCache[*pbImplEnvoyAuthV3Shared.ResponseAuth, pbImplEnvoyAuthV3Shared.Token](z.Token, pbImplEnvoyAuthV3Shared.DefaultTTL)
4647

4748
return z, true
@@ -51,6 +52,8 @@ type impl struct {
5152
configuration pbImplEnvoyAuthV3Shared.Configuration
5253

5354
cache cache.HashCache[*pbImplEnvoyAuthV3Shared.ResponseAuth, pbImplEnvoyAuthV3Shared.Token]
55+
56+
authClient cache.Object[pbAuthenticationV1.AuthenticationV1Client]
5457
}
5558

5659
func (p impl) Handle(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest, current *pbImplEnvoyAuthV3Shared.Response) error {
@@ -141,7 +144,13 @@ func (p impl) Token(ctx context.Context, in *pbImplEnvoyAuthV3Shared.ResponseAut
141144
if in == nil {
142145
return "", errors.Errorf("Nil is not allowed")
143146
}
144-
resp, err := p.configuration.AuthClient.CreateToken(ctx, &pbAuthenticationV1.CreateTokenRequest{
147+
148+
client, err := p.authClient.Get(ctx)
149+
if err != nil {
150+
return "", err
151+
}
152+
153+
resp, err := client.CreateToken(ctx, &pbAuthenticationV1.CreateTokenRequest{
145154
Lifetime: durationpb.New(pbImplEnvoyAuthV3Shared.DefaultLifetime),
146155
User: util.NewType(in.User),
147156
Roles: in.Roles,
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2025 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
21+
package users
22+
23+
import (
24+
"context"
25+
"fmt"
26+
"time"
27+
28+
pbEnvoyAuthV3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
29+
"k8s.io/apimachinery/pkg/util/uuid"
30+
31+
"github.com/arangodb/go-driver/v2/arangodb"
32+
"github.com/arangodb/go-driver/v2/arangodb/shared"
33+
"github.com/arangodb/go-driver/v2/connection"
34+
35+
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
36+
pbImplEnvoyAuthV3Shared "github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3/shared"
37+
"github.com/arangodb/kube-arangodb/pkg/util"
38+
"github.com/arangodb/kube-arangodb/pkg/util/cache"
39+
operatorHTTP "github.com/arangodb/kube-arangodb/pkg/util/http"
40+
)
41+
42+
func New(configuration pbImplEnvoyAuthV3Shared.Configuration) (pbImplEnvoyAuthV3Shared.AuthHandler, bool) {
43+
if !configuration.Extensions.UsersCreate {
44+
return nil, false
45+
}
46+
47+
i := &impl{
48+
authClient: cache.NewObject[pbAuthenticationV1.AuthenticationV1Client](configuration.GetAuthClientFetcher),
49+
}
50+
51+
i.userClient = cache.NewObject(func(ctx context.Context) (arangodb.ClientUsers, time.Duration, error) {
52+
ac, err := i.authClient.Get(ctx)
53+
if err != nil {
54+
return nil, 0, err
55+
}
56+
57+
client := arangodb.NewClient(connection.NewHttpConnection(connection.HttpConfiguration{
58+
Authentication: pbAuthenticationV1.NewRootRequestModifier(ac),
59+
Endpoint: connection.NewRoundRobinEndpoints([]string{
60+
fmt.Sprintf("%s://%s:%d", configuration.Database.Proto, configuration.Database.Endpoint, configuration.Database.Port),
61+
}),
62+
ContentType: connection.ApplicationJSON,
63+
ArangoDBConfig: connection.ArangoDBConfiguration{},
64+
Transport: operatorHTTP.RoundTripperWithShortTransport(operatorHTTP.WithTransportTLS(operatorHTTP.Insecure)),
65+
}))
66+
67+
return client, 24 * time.Hour, nil
68+
})
69+
70+
i.users = cache.NewCache[string, arangodb.User](func(ctx context.Context, in string) (arangodb.User, error) {
71+
client, err := i.userClient.Get(ctx)
72+
if err != nil {
73+
return nil, err
74+
}
75+
76+
if user, err := client.User(ctx, in); err == nil {
77+
return user, nil
78+
} else {
79+
if !shared.IsNotFound(err) {
80+
return nil, err
81+
}
82+
}
83+
84+
if user, err := client.CreateUser(ctx, in, &arangodb.UserOptions{
85+
Password: string(uuid.NewUUID()),
86+
Active: util.NewType(true),
87+
}); err != nil {
88+
return user, nil
89+
} else {
90+
if !shared.IsConflict(err) {
91+
return nil, err
92+
}
93+
}
94+
95+
return client.User(ctx, in)
96+
}, 24*time.Hour)
97+
98+
return i, true
99+
}
100+
101+
type impl struct {
102+
authClient cache.Object[pbAuthenticationV1.AuthenticationV1Client]
103+
userClient cache.Object[arangodb.ClientUsers]
104+
105+
users cache.Cache[string, arangodb.User]
106+
}
107+
108+
func (i *impl) Handle(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest, current *pbImplEnvoyAuthV3Shared.Response) error {
109+
if !current.Authenticated() {
110+
return nil
111+
}
112+
113+
_, err := i.users.Get(ctx, current.User.User)
114+
115+
return err
116+
}

integrations/envoy/auth/v3/shared/configuration.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,38 @@
2121
package shared
2222

2323
import (
24-
"github.com/arangodb/go-driver/v2/arangodb"
24+
"context"
25+
"time"
2526

2627
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
28+
ugrpc "github.com/arangodb/kube-arangodb/pkg/util/grpc"
2729
)
2830

2931
type Configuration struct {
30-
AuthClient pbAuthenticationV1.AuthenticationV1Client
32+
Address string
3133

32-
ArangoDBClient arangodb.Client
34+
Database ConfigurationDatabase
3335

3436
Extensions ConfigurationExtensions
3537
}
3638

39+
type ConfigurationDatabase struct {
40+
Proto string
41+
Endpoint string
42+
Port int
43+
}
44+
3745
type ConfigurationExtensions struct {
38-
JWT bool
39-
CookieJWT bool
46+
JWT bool
47+
CookieJWT bool
48+
UsersCreate bool
49+
}
50+
51+
func (c Configuration) GetAuthClientFetcher(ctx context.Context) (pbAuthenticationV1.AuthenticationV1Client, time.Duration, error) {
52+
client, _, err := ugrpc.NewGRPCClient(ctx, pbAuthenticationV1.NewAuthenticationV1Client, c.Address)
53+
if err != nil {
54+
return nil, 0, err
55+
}
56+
57+
return client, time.Hour, nil
4058
}

pkg/integrations/envoy_auth_v3.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@ import (
2525

2626
"github.com/spf13/cobra"
2727

28-
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
2928
pbImplEnvoyAuthV3 "github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3"
3029
pbImplEnvoyAuthV3Shared "github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3/shared"
3130
"github.com/arangodb/kube-arangodb/pkg/util/errors"
32-
ugrpc "github.com/arangodb/kube-arangodb/pkg/util/grpc"
3331
"github.com/arangodb/kube-arangodb/pkg/util/svc"
3432
)
3533

@@ -55,6 +53,10 @@ func (a *envoyAuthV3) Register(cmd *cobra.Command, fs FlagEnvHandler) error {
5553
return errors.Errors(
5654
fs.BoolVar(&a.config.Extensions.JWT, "extensions.jwt", true, "Defines if JWT extension is enabled"),
5755
fs.BoolVar(&a.config.Extensions.CookieJWT, "extensions.cookie.jwt", true, "Defines if Cookie JWT extension is enabled"),
56+
fs.BoolVar(&a.config.Extensions.UsersCreate, "extensions.users.create", false, "Defines if UserCreation extension is enabled"),
57+
fs.StringVar(&a.config.Database.Endpoint, "database.endpoint", "", "Endpoint of ArangoDB"),
58+
fs.StringVar(&a.config.Database.Proto, "database.proto", "http", "Proto of the ArangoDB endpoint"),
59+
fs.IntVar(&a.config.Database.Port, "database.port", 8529, "Port of ArangoDB"),
5860
)
5961
}
6062

@@ -66,14 +68,7 @@ func (a *envoyAuthV3) Handler(ctx context.Context, cmd *cobra.Command) (svc.Hand
6668
return nil, err
6769
}
6870

69-
c, _, err := ugrpc.NewGRPCClient(ctx, pbAuthenticationV1.NewAuthenticationV1Client, v)
70-
if err != nil {
71-
return nil, err
72-
}
73-
74-
cfg := a.config
75-
76-
cfg.AuthClient = c
71+
a.config.Address = v
7772

78-
return pbImplEnvoyAuthV3.New(cfg), nil
73+
return pbImplEnvoyAuthV3.New(a.config), nil
7974
}

pkg/integrations/flags.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ type FlagEnvHandler interface {
5757
Uint16Var(p *uint16, name string, value uint16, usage string) error
5858
Uint16(name string, value uint16, usage string) error
5959

60+
IntVar(p *int, name string, value int, usage string) error
61+
Int(name string, value int, usage string) error
62+
6063
DurationVar(p *time.Duration, name string, value time.Duration, usage string) error
6164
Duration(name string, value time.Duration, usage string) error
6265
}
@@ -257,6 +260,44 @@ func (f flagEnvHandler) Uint16(name string, value uint16, usage string) error {
257260
return nil
258261
}
259262

263+
func (f flagEnvHandler) IntVar(p *int, name string, value int, usage string) error {
264+
v, err := parseEnvToInt(f.getEnv(name), value)
265+
if err != nil {
266+
return err
267+
}
268+
269+
fname := f.name(name)
270+
271+
f.fs.IntVar(p, fname, v, f.varDesc(name, usage))
272+
273+
if !f.visible {
274+
if err := f.fs.MarkHidden(fname); err != nil {
275+
return err
276+
}
277+
}
278+
279+
return nil
280+
}
281+
282+
func (f flagEnvHandler) Int(name string, value int, usage string) error {
283+
v, err := parseEnvToInt(f.getEnv(name), value)
284+
if err != nil {
285+
return err
286+
}
287+
288+
fname := f.name(name)
289+
290+
f.fs.Int(fname, v, f.varDesc(name, usage))
291+
292+
if !f.visible {
293+
if err := f.fs.MarkHidden(fname); err != nil {
294+
return err
295+
}
296+
}
297+
298+
return nil
299+
}
300+
260301
func (f flagEnvHandler) varDesc(name string, dest string) string {
261302
return fmt.Sprintf("%s (Env: %s)", dest, f.getEnv(name))
262303
}
@@ -306,6 +347,13 @@ func parseEnvToUint16(env string, def uint16) (uint16, error) {
306347
})
307348
}
308349

350+
func parseEnvToInt(env string, def int) (int, error) {
351+
return parseEnvToType(env, def, func(in string) (int, error) {
352+
v, err := strconv.ParseInt(in, 10, 64)
353+
return int(v), err
354+
})
355+
}
356+
309357
func parseEnvToBool(env string, def bool) (bool, error) {
310358
return parseEnvToType(env, def, strconv.ParseBool)
311359
}

0 commit comments

Comments
 (0)