1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Feature] [Gateway] Add Auth Token (#1717)

This commit is contained in:
Adam Janikowski 2024-09-06 15:36:51 +02:00 committed by GitHub
parent 713d92702e
commit 3d46436c59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 284 additions and 35 deletions

View file

@ -25,6 +25,7 @@
- (Maintenance) Bump Examples to ArangoDB 3.12
- (Feature) (Gateway) ArangoDB JWT Auth Integration
- (Feature) Scheduler Handler
- (Feature) (Gateway) ArangoDB Auth Token
## [1.2.42](https://github.com/arangodb/kube-arangodb/tree/1.2.42) (2024-07-23)
- (Maintenance) Go 1.22.4 & Kubernetes 1.29.6 libraries

View file

@ -16,12 +16,18 @@ Deployment specifies the ArangoDeployment object name
***
### .spec.destination.authentication.type
### .spec.destination.authentication.passMode
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>
***
### .spec.destination.authentication.type
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>
***
### .spec.destination.path
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
***
### .status.target.authentication.passMode
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>
***
### .status.target.authentication.type
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>

4
go.mod
View file

@ -68,6 +68,8 @@ require (
k8s.io/client-go v0.29.6
k8s.io/kube-openapi v0.0.0-20231129212854-f0671cc7e66a
sigs.k8s.io/yaml v1.4.0
github.com/golang/protobuf v1.5.4
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917
)
require (
@ -96,7 +98,6 @@ require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
@ -132,7 +133,6 @@ require (
golang.org/x/tools v0.17.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.110.1 // indirect

View file

@ -0,0 +1,120 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v3
import (
"context"
"sync"
"time"
"google.golang.org/protobuf/types/known/durationpb"
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
"github.com/arangodb/kube-arangodb/pkg/util"
)
const (
DefaultLifetime = time.Minute * 5
DefaultTTL = time.Minute
)
func NewADBHelper(client pbAuthenticationV1.AuthenticationV1Client) ADBHelper {
return &adbHelper{
client: client,
cache: map[string]adbHelperToken{},
}
}
type ADBHelper interface {
Validate(ctx context.Context, token string) (*AuthResponse, error)
Token(ctx context.Context, resp *AuthResponse) (string, bool, error)
}
type adbHelperToken struct {
TTL time.Time
Token string
}
type adbHelper struct {
lock sync.Mutex
cache map[string]adbHelperToken
client pbAuthenticationV1.AuthenticationV1Client
}
func (a *adbHelper) Token(ctx context.Context, resp *AuthResponse) (string, bool, error) {
a.lock.Lock()
defer a.lock.Unlock()
if resp == nil {
// Token cannot be fetch if authentication is not valid
return "", false, nil
}
v, ok := a.cache[resp.Username]
if ok {
// We received token
if time.Now().Before(v.TTL) {
return v.Token, true, nil
}
// Token has been expired
delete(a.cache, resp.Username)
}
// We did not receive token, create one
auth, err := a.client.CreateToken(ctx, &pbAuthenticationV1.CreateTokenRequest{
Lifetime: durationpb.New(DefaultLifetime),
User: util.NewType(resp.Username),
})
if err != nil {
return "", false, err
}
a.cache[resp.Username] = adbHelperToken{
TTL: time.Now().Add(DefaultTTL),
Token: auth.GetToken(),
}
return auth.GetToken(), true, nil
}
func (a *adbHelper) Validate(ctx context.Context, token string) (*AuthResponse, error) {
a.lock.Lock()
defer a.lock.Unlock()
resp, err := a.client.Validate(ctx, &pbAuthenticationV1.ValidateRequest{
Token: token,
})
if err != nil {
return nil, err
}
if resp.GetIsValid() {
if det := resp.GetDetails(); det != nil {
return &AuthResponse{
Username: det.GetUser(),
}, nil
}
}
return nil, nil
}

View file

@ -25,7 +25,6 @@ import (
pbEnvoyAuthV3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
"github.com/arangodb/kube-arangodb/pkg/util/strings"
)
@ -38,20 +37,13 @@ func (i *impl) checkADBJWT(ctx context.Context, request *pbEnvoyAuthV3.CheckRequ
parts := strings.SplitN(auth, " ", 2)
if len(parts) == 2 {
if strings.ToLower(parts[0]) == "bearer" {
resp, err := i.authClient.Validate(ctx, &pbAuthenticationV1.ValidateRequest{
Token: parts[1],
})
resp, err := i.helper.Validate(ctx, parts[1])
if err != nil {
logger.Err(err).Warn("Auth failure")
return nil, nil
}
if err == nil && resp.GetIsValid() {
// All went fine!
return &AuthResponse{
Username: resp.GetDetails().GetUser(),
}, nil
}
return resp, nil
}
}
}

View file

@ -33,6 +33,7 @@ const (
AuthConfigTypeValue = "ArangoDBPlatform"
AuthConfigAuthRequiredKey = AuthConfigAuthNamespace + "/required"
AuthConfigAuthPassModeKey = AuthConfigAuthNamespace + "/pass_mode"
AuthUsernameHeader = "arangodb-platform-user"
AuthAuthenticatedHeader = "arangodb-platform-authenticated"

View file

@ -22,13 +22,16 @@ package v3
import (
"context"
"fmt"
"net/http"
"strings"
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
pbEnvoyAuthV3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"google.golang.org/grpc"
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/errors/panics"
@ -37,7 +40,7 @@ import (
func New(authClient pbAuthenticationV1.AuthenticationV1Client) svc.Handler {
return &impl{
authClient: authClient,
helper: NewADBHelper(authClient),
}
}
@ -47,7 +50,7 @@ var _ svc.Handler = &impl{}
type impl struct {
pbEnvoyAuthV3.UnimplementedAuthorizationServer
authClient pbAuthenticationV1.AuthenticationV1Client
helper ADBHelper
}
func (i *impl) Name() string {
@ -104,25 +107,62 @@ func (i *impl) check(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest) (
}
if authenticated != nil {
var headers = []*corev3.HeaderValueOption{
{
Header: &corev3.HeaderValue{
Key: AuthUsernameHeader,
Value: authenticated.Username,
},
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
},
{
Header: &corev3.HeaderValue{
Key: AuthAuthenticatedHeader,
Value: "true",
},
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
},
}
switch networkingApi.ArangoRouteSpecAuthenticationPassMode(strings.ToLower(util.Optional(ext, AuthConfigAuthPassModeKey, ""))) {
case networkingApi.ArangoRouteSpecAuthenticationPassModeOverride:
token, ok, err := i.helper.Token(ctx, authenticated)
if err != nil {
return nil, err
}
if !ok {
return nil, DeniedResponse{
Code: http.StatusUnauthorized,
Message: &DeniedMessage{
Message: "Unable to render token",
},
}
}
headers = append(headers, &corev3.HeaderValueOption{
Header: &corev3.HeaderValue{
Key: "authorization",
Value: fmt.Sprintf("bearer %s", token),
},
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
},
)
case networkingApi.ArangoRouteSpecAuthenticationPassModeRemove:
headers = append(headers, &corev3.HeaderValueOption{
Header: &corev3.HeaderValue{
Key: "authorization",
},
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
KeepEmptyValue: false,
},
)
}
return &pbEnvoyAuthV3.CheckResponse{
HttpResponse: &pbEnvoyAuthV3.CheckResponse_OkResponse{
OkResponse: &pbEnvoyAuthV3.OkHttpResponse{
Headers: []*corev3.HeaderValueOption{
{
Header: &corev3.HeaderValue{
Key: AuthUsernameHeader,
Value: authenticated.Username,
},
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
},
{
Header: &corev3.HeaderValue{
Key: AuthAuthenticatedHeader,
Value: "true",
},
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
},
},
Headers: headers,
},
},
}, nil

View file

@ -0,0 +1,63 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v1alpha1
import (
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
type ArangoRouteSpecAuthenticationPassMode string
const (
ArangoRouteSpecAuthenticationPassModePass ArangoRouteSpecAuthenticationPassMode = "pass"
ArangoRouteSpecAuthenticationPassModeOverride ArangoRouteSpecAuthenticationPassMode = "override"
ArangoRouteSpecAuthenticationPassModeRemove ArangoRouteSpecAuthenticationPassMode = "remove"
)
func (a *ArangoRouteSpecAuthenticationPassMode) Get() ArangoRouteSpecAuthenticationPassMode {
if a == nil {
return ArangoRouteSpecAuthenticationPassModeOverride
}
switch v := *a; v {
case ArangoRouteSpecAuthenticationPassModePass, ArangoRouteSpecAuthenticationPassModeOverride, ArangoRouteSpecAuthenticationPassModeRemove:
return v
}
return ""
}
func (a *ArangoRouteSpecAuthenticationPassMode) Validate() error {
switch v := a.Get(); v {
case ArangoRouteSpecAuthenticationPassModePass, ArangoRouteSpecAuthenticationPassModeOverride, ArangoRouteSpecAuthenticationPassModeRemove:
return nil
default:
return errors.Errorf("Invalid AuthPassMode: %s", v)
}
}
func (a *ArangoRouteSpecAuthenticationPassMode) Hash() string {
if a == nil {
return ""
}
return util.SHA256FromString(string(*a))
}

View file

@ -25,7 +25,8 @@ import (
)
type ArangoRouteSpecDestinationAuthentication struct {
Type *ArangoRouteSpecAuthenticationType `json:"type,omitempty"`
PassMode *ArangoRouteSpecAuthenticationPassMode `json:"passMode,omitempty"`
Type *ArangoRouteSpecAuthenticationType `json:"type,omitempty"`
}
func (a *ArangoRouteSpecDestinationAuthentication) GetType() ArangoRouteSpecAuthenticationType {
@ -36,6 +37,14 @@ func (a *ArangoRouteSpecDestinationAuthentication) GetType() ArangoRouteSpecAuth
return a.Type.Get()
}
func (a *ArangoRouteSpecDestinationAuthentication) GetPassMode() ArangoRouteSpecAuthenticationPassMode {
if a == nil {
return ArangoRouteSpecAuthenticationPassModeOverride
}
return a.PassMode.Get()
}
func (a *ArangoRouteSpecDestinationAuthentication) Validate() error {
if a == nil {
return nil
@ -43,5 +52,6 @@ func (a *ArangoRouteSpecDestinationAuthentication) Validate() error {
return shared.WithErrors(
shared.ValidateOptionalInterfacePath("type", a.Type),
shared.ValidateOptionalInterfacePath("passMode", a.PassMode),
)
}

View file

@ -23,12 +23,13 @@ package v1alpha1
import "github.com/arangodb/kube-arangodb/pkg/util"
type ArangoRouteStatusTargetAuthentication struct {
Type ArangoRouteSpecAuthenticationType `json:"type,omitempty"`
Type ArangoRouteSpecAuthenticationType `json:"type,omitempty"`
PassMode ArangoRouteSpecAuthenticationPassMode `json:"passMode,omitempty"`
}
func (a *ArangoRouteStatusTargetAuthentication) Hash() string {
if a == nil {
return ""
}
return util.SHA256FromStringArray(a.Type.Hash())
return util.SHA256FromStringArray(a.Type.Hash(), a.PassMode.Hash())
}

View file

@ -168,6 +168,11 @@ func (in *ArangoRouteSpecDestination) DeepCopy() *ArangoRouteSpecDestination {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArangoRouteSpecDestinationAuthentication) DeepCopyInto(out *ArangoRouteSpecDestinationAuthentication) {
*out = *in
if in.PassMode != nil {
in, out := &in.PassMode, &out.PassMode
*out = new(ArangoRouteSpecAuthenticationPassMode)
**out = **in
}
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(ArangoRouteSpecAuthenticationType)

View file

@ -12,6 +12,8 @@ v1alpha1:
authentication:
description: Authentication defines auth methods
properties:
passMode:
type: string
type:
type: string
type: object

View file

@ -197,7 +197,8 @@ func (r *Resources) renderGatewayConfig(cachedStatus inspectorInterface.Inspecto
dest.Path = util.NewType(target.Path)
dest.AuthExtension = &gateway.ConfigAuthZExtension{
AuthZExtension: map[string]string{
pbImplEnvoyAuthV3.AuthConfigAuthRequiredKey: util.BoolSwitch(target.Authentication.Type.Get() == networkingApi.ArangoRouteSpecAuthenticationTypeRequired, pbImplEnvoyAuthV3.AuthConfigKeywordTrue, pbImplEnvoyAuthV3.AuthConfigKeywordFalse),
pbImplEnvoyAuthV3.AuthConfigAuthRequiredKey: util.BoolSwitch[string](target.Authentication.Type.Get() == networkingApi.ArangoRouteSpecAuthenticationTypeRequired, pbImplEnvoyAuthV3.AuthConfigKeywordTrue, pbImplEnvoyAuthV3.AuthConfigKeywordFalse),
pbImplEnvoyAuthV3.AuthConfigAuthPassModeKey: string(target.Authentication.PassMode),
},
}
cfg.Destinations[at.Spec.GetRoute().GetPath()] = dest

View file

@ -124,6 +124,7 @@ func (h *handler) HandleArangoDestination(ctx context.Context, item operation.It
// Render Auth Settings
target.Authentication.Type = dest.GetAuthentication().GetType()
target.Authentication.PassMode = dest.GetAuthentication().GetPassMode()
if dest.Schema.Get() == networkingApi.ArangoRouteSpecDestinationSchemaHTTPS {
target.TLS = &networkingApi.ArangoRouteStatusTargetTLS{