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:
parent
713d92702e
commit
3d46436c59
15 changed files with 284 additions and 35 deletions
|
@ -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
|
||||
|
|
|
@ -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
4
go.mod
|
@ -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
|
||||
|
|
120
integrations/envoy/auth/v3/adb_helper.go
Normal file
120
integrations/envoy/auth/v3/adb_helper.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ const (
|
|||
AuthConfigTypeValue = "ArangoDBPlatform"
|
||||
|
||||
AuthConfigAuthRequiredKey = AuthConfigAuthNamespace + "/required"
|
||||
AuthConfigAuthPassModeKey = AuthConfigAuthNamespace + "/pass_mode"
|
||||
|
||||
AuthUsernameHeader = "arangodb-platform-user"
|
||||
AuthAuthenticatedHeader = "arangodb-platform-authenticated"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -12,6 +12,8 @@ v1alpha1:
|
|||
authentication:
|
||||
description: Authentication defines auth methods
|
||||
properties:
|
||||
passMode:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
Loading…
Reference in a new issue