Skip to content

Commit 3d46436

Browse files
authored
[Feature] [Gateway] Add Auth Token (#1717)
1 parent 713d927 commit 3d46436

File tree

15 files changed

+284
-35
lines changed

15 files changed

+284
-35
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
- (Maintenance) Bump Examples to ArangoDB 3.12
2626
- (Feature) (Gateway) ArangoDB JWT Auth Integration
2727
- (Feature) Scheduler Handler
28+
- (Feature) (Gateway) ArangoDB Auth Token
2829

2930
## [1.2.42](https://github.com/arangodb/kube-arangodb/tree/1.2.42) (2024-07-23)
3031
- (Maintenance) Go 1.22.4 & Kubernetes 1.29.6 libraries

docs/api/ArangoRoute.V1Alpha1.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@ Deployment specifies the ArangoDeployment object name
1616

1717
***
1818

19-
### .spec.destination.authentication.type
19+
### .spec.destination.authentication.passMode
2020

2121
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go#L28)</sup>
2222

2323
***
2424

25+
### .spec.destination.authentication.type
26+
27+
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go#L29)</sup>
28+
29+
***
30+
2531
### .spec.destination.path
2632

2733
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination.go#L36)</sup>
@@ -137,6 +143,12 @@ UID keeps the information about object UID
137143

138144
***
139145

146+
### .status.target.authentication.passMode
147+
148+
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target_authentication.go#L27)</sup>
149+
150+
***
151+
140152
### .status.target.authentication.type
141153

142154
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target_authentication.go#L26)</sup>

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ require (
6868
k8s.io/client-go v0.29.6
6969
k8s.io/kube-openapi v0.0.0-20231129212854-f0671cc7e66a
7070
sigs.k8s.io/yaml v1.4.0
71+
github.com/golang/protobuf v1.5.4
72+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917
7173
)
7274

7375
require (
@@ -96,7 +98,6 @@ require (
9698
github.com/goccy/go-json v0.10.2 // indirect
9799
github.com/gogo/protobuf v1.3.2 // indirect
98100
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
99-
github.com/golang/protobuf v1.5.4 // indirect
100101
github.com/google/gnostic-models v0.6.8 // indirect
101102
github.com/google/go-cmp v0.6.0 // indirect
102103
github.com/google/gofuzz v1.2.0 // indirect
@@ -132,7 +133,6 @@ require (
132133
golang.org/x/tools v0.17.0 // indirect
133134
google.golang.org/appengine v1.6.8 // indirect
134135
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
135-
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
136136
gopkg.in/inf.v0 v0.9.1 // indirect
137137
gopkg.in/yaml.v2 v2.4.0 // indirect
138138
k8s.io/klog/v2 v2.110.1 // indirect
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2024 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 v3
22+
23+
import (
24+
"context"
25+
"sync"
26+
"time"
27+
28+
"google.golang.org/protobuf/types/known/durationpb"
29+
30+
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
31+
"github.com/arangodb/kube-arangodb/pkg/util"
32+
)
33+
34+
const (
35+
DefaultLifetime = time.Minute * 5
36+
DefaultTTL = time.Minute
37+
)
38+
39+
func NewADBHelper(client pbAuthenticationV1.AuthenticationV1Client) ADBHelper {
40+
return &adbHelper{
41+
client: client,
42+
cache: map[string]adbHelperToken{},
43+
}
44+
}
45+
46+
type ADBHelper interface {
47+
Validate(ctx context.Context, token string) (*AuthResponse, error)
48+
Token(ctx context.Context, resp *AuthResponse) (string, bool, error)
49+
}
50+
51+
type adbHelperToken struct {
52+
TTL time.Time
53+
Token string
54+
}
55+
56+
type adbHelper struct {
57+
lock sync.Mutex
58+
cache map[string]adbHelperToken
59+
60+
client pbAuthenticationV1.AuthenticationV1Client
61+
}
62+
63+
func (a *adbHelper) Token(ctx context.Context, resp *AuthResponse) (string, bool, error) {
64+
a.lock.Lock()
65+
defer a.lock.Unlock()
66+
67+
if resp == nil {
68+
// Token cannot be fetch if authentication is not valid
69+
return "", false, nil
70+
}
71+
72+
v, ok := a.cache[resp.Username]
73+
if ok {
74+
// We received token
75+
if time.Now().Before(v.TTL) {
76+
return v.Token, true, nil
77+
}
78+
// Token has been expired
79+
delete(a.cache, resp.Username)
80+
}
81+
82+
// We did not receive token, create one
83+
auth, err := a.client.CreateToken(ctx, &pbAuthenticationV1.CreateTokenRequest{
84+
Lifetime: durationpb.New(DefaultLifetime),
85+
User: util.NewType(resp.Username),
86+
})
87+
if err != nil {
88+
return "", false, err
89+
}
90+
91+
a.cache[resp.Username] = adbHelperToken{
92+
TTL: time.Now().Add(DefaultTTL),
93+
Token: auth.GetToken(),
94+
}
95+
96+
return auth.GetToken(), true, nil
97+
}
98+
99+
func (a *adbHelper) Validate(ctx context.Context, token string) (*AuthResponse, error) {
100+
a.lock.Lock()
101+
defer a.lock.Unlock()
102+
103+
resp, err := a.client.Validate(ctx, &pbAuthenticationV1.ValidateRequest{
104+
Token: token,
105+
})
106+
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
if resp.GetIsValid() {
112+
if det := resp.GetDetails(); det != nil {
113+
return &AuthResponse{
114+
Username: det.GetUser(),
115+
}, nil
116+
}
117+
}
118+
119+
return nil, nil
120+
}

integrations/envoy/auth/v3/check_adb_jwt.go

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

2626
pbEnvoyAuthV3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
2727

28-
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
2928
"github.com/arangodb/kube-arangodb/pkg/util/strings"
3029
)
3130

@@ -38,20 +37,13 @@ func (i *impl) checkADBJWT(ctx context.Context, request *pbEnvoyAuthV3.CheckRequ
3837
parts := strings.SplitN(auth, " ", 2)
3938
if len(parts) == 2 {
4039
if strings.ToLower(parts[0]) == "bearer" {
41-
resp, err := i.authClient.Validate(ctx, &pbAuthenticationV1.ValidateRequest{
42-
Token: parts[1],
43-
})
40+
resp, err := i.helper.Validate(ctx, parts[1])
4441
if err != nil {
4542
logger.Err(err).Warn("Auth failure")
4643
return nil, nil
4744
}
4845

49-
if err == nil && resp.GetIsValid() {
50-
// All went fine!
51-
return &AuthResponse{
52-
Username: resp.GetDetails().GetUser(),
53-
}, nil
54-
}
46+
return resp, nil
5547
}
5648
}
5749
}

integrations/envoy/auth/v3/consts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const (
3333
AuthConfigTypeValue = "ArangoDBPlatform"
3434

3535
AuthConfigAuthRequiredKey = AuthConfigAuthNamespace + "/required"
36+
AuthConfigAuthPassModeKey = AuthConfigAuthNamespace + "/pass_mode"
3637

3738
AuthUsernameHeader = "arangodb-platform-user"
3839
AuthAuthenticatedHeader = "arangodb-platform-authenticated"

integrations/envoy/auth/v3/impl.go

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ package v3
2222

2323
import (
2424
"context"
25+
"fmt"
2526
"net/http"
27+
"strings"
2628

2729
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
2830
pbEnvoyAuthV3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
2931
"google.golang.org/grpc"
3032

3133
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
34+
networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1"
3235
"github.com/arangodb/kube-arangodb/pkg/util"
3336
"github.com/arangodb/kube-arangodb/pkg/util/errors"
3437
"github.com/arangodb/kube-arangodb/pkg/util/errors/panics"
@@ -37,7 +40,7 @@ import (
3740

3841
func New(authClient pbAuthenticationV1.AuthenticationV1Client) svc.Handler {
3942
return &impl{
40-
authClient: authClient,
43+
helper: NewADBHelper(authClient),
4144
}
4245
}
4346

@@ -47,7 +50,7 @@ var _ svc.Handler = &impl{}
4750
type impl struct {
4851
pbEnvoyAuthV3.UnimplementedAuthorizationServer
4952

50-
authClient pbAuthenticationV1.AuthenticationV1Client
53+
helper ADBHelper
5154
}
5255

5356
func (i *impl) Name() string {
@@ -104,25 +107,62 @@ func (i *impl) check(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest) (
104107
}
105108

106109
if authenticated != nil {
110+
var headers = []*corev3.HeaderValueOption{
111+
{
112+
Header: &corev3.HeaderValue{
113+
Key: AuthUsernameHeader,
114+
Value: authenticated.Username,
115+
},
116+
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
117+
},
118+
{
119+
Header: &corev3.HeaderValue{
120+
Key: AuthAuthenticatedHeader,
121+
Value: "true",
122+
},
123+
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
124+
},
125+
}
126+
127+
switch networkingApi.ArangoRouteSpecAuthenticationPassMode(strings.ToLower(util.Optional(ext, AuthConfigAuthPassModeKey, ""))) {
128+
case networkingApi.ArangoRouteSpecAuthenticationPassModeOverride:
129+
token, ok, err := i.helper.Token(ctx, authenticated)
130+
if err != nil {
131+
return nil, err
132+
}
133+
134+
if !ok {
135+
return nil, DeniedResponse{
136+
Code: http.StatusUnauthorized,
137+
Message: &DeniedMessage{
138+
Message: "Unable to render token",
139+
},
140+
}
141+
}
142+
143+
headers = append(headers, &corev3.HeaderValueOption{
144+
Header: &corev3.HeaderValue{
145+
Key: "authorization",
146+
Value: fmt.Sprintf("bearer %s", token),
147+
},
148+
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
149+
},
150+
)
151+
case networkingApi.ArangoRouteSpecAuthenticationPassModeRemove:
152+
headers = append(headers, &corev3.HeaderValueOption{
153+
Header: &corev3.HeaderValue{
154+
Key: "authorization",
155+
},
156+
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
157+
KeepEmptyValue: false,
158+
},
159+
)
160+
}
161+
107162
return &pbEnvoyAuthV3.CheckResponse{
108163
HttpResponse: &pbEnvoyAuthV3.CheckResponse_OkResponse{
109164
OkResponse: &pbEnvoyAuthV3.OkHttpResponse{
110-
Headers: []*corev3.HeaderValueOption{
111-
{
112-
Header: &corev3.HeaderValue{
113-
Key: AuthUsernameHeader,
114-
Value: authenticated.Username,
115-
},
116-
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
117-
},
118-
{
119-
Header: &corev3.HeaderValue{
120-
Key: AuthAuthenticatedHeader,
121-
Value: "true",
122-
},
123-
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
124-
},
125-
},
165+
Headers: headers,
126166
},
127167
},
128168
}, nil

0 commit comments

Comments
 (0)