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

[Feature] [Gateway] ArangoDB AuthIntegration (#1715)

This commit is contained in:
Adam Janikowski 2024-09-03 17:50:59 +02:00 committed by GitHub
parent 81dfd87566
commit fb2ac883ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 792 additions and 47 deletions

View file

@ -23,6 +23,7 @@
- (Feature) Integration Service TLS
- (Feature) (Gateway) SNI and Authz support
- (Maintenance) Bump Examples to ArangoDB 3.12
- (Feature) (Gateway) ArangoDB JWT Auth Integration
## [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

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-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.
@ -21,8 +21,6 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/arangodb/kube-arangodb/pkg/version"
@ -38,6 +36,5 @@ var cmdVersion = &cobra.Command{
}
func versionRun(cmd *cobra.Command, args []string) {
v := version.GetVersionV1()
println(fmt.Sprintf("Version: %s %s, Build: %s, Go: %s, Build Date: %s", v.Edition.Title(), v.Version, v.Build, v.GoVersion, v.BuildDate))
println(version.GetVersionV1().String())
}

View file

@ -16,6 +16,12 @@ Deployment specifies the ArangoDeployment object name
***
### .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#L28)</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>
@ -131,6 +137,12 @@ UID keeps the information about object UID
***
### .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>
***
### .status.target.destinations\[int\].host
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target_destination.go#L38)</sup>
@ -145,7 +157,7 @@ Type: `integer` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.
### .status.target.path
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L37)</sup>
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L40)</sup>
Path specifies request path override

View file

@ -0,0 +1,46 @@
//
// 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"
pbEnvoyAuthV3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
)
type AuthResponse struct {
Username string
}
type AuthRequestFunc func(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest, current *AuthResponse) (*AuthResponse, error)
func MergeAuthRequest(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest, requests ...AuthRequestFunc) (*AuthResponse, error) {
var resp *AuthResponse
for _, r := range requests {
if v, err := r(ctx, request, resp); err != nil {
return nil, err
} else {
resp = v
}
}
return resp, nil
}

View file

@ -0,0 +1,60 @@
//
// 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"
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"
)
func (i *impl) checkADBJWT(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest, current *AuthResponse) (*AuthResponse, error) {
if current != nil {
// Already authenticated
return current, nil
}
if auth, ok := request.GetAttributes().GetRequest().GetHttp().GetHeaders()["authorization"]; ok {
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],
})
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 nil, nil
}

View file

@ -22,4 +22,18 @@ package v3
const (
Name = "envoy.auth.v3"
AuthConfigKeywordTrue = "true"
AuthConfigKeywordFalse = "false"
AuthConfigNamespace = "platform.arangodb.com"
AuthConfigAuthNamespace = "auth." + AuthConfigNamespace
AuthConfigTypeKey = AuthConfigNamespace + "/type"
AuthConfigTypeValue = "ArangoDBPlatform"
AuthConfigAuthRequiredKey = AuthConfigAuthNamespace + "/required"
AuthUsernameHeader = "arangodb-platform-user"
AuthAuthenticatedHeader = "arangodb-platform-authenticated"
)

View file

@ -22,15 +22,23 @@ package v3
import (
"context"
"net/http"
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"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/errors/panics"
"github.com/arangodb/kube-arangodb/pkg/util/svc"
)
func New() svc.Handler {
return &impl{}
func New(authClient pbAuthenticationV1.AuthenticationV1Client) svc.Handler {
return &impl{
authClient: authClient,
}
}
var _ pbEnvoyAuthV3.AuthorizationServer = &impl{}
@ -38,6 +46,8 @@ var _ svc.Handler = &impl{}
type impl struct {
pbEnvoyAuthV3.UnimplementedAuthorizationServer
authClient pbAuthenticationV1.AuthenticationV1Client
}
func (i *impl) Name() string {
@ -53,10 +63,84 @@ func (i *impl) Register(registrar *grpc.Server) {
}
func (i *impl) Check(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest) (*pbEnvoyAuthV3.CheckResponse, error) {
logger.Info("Request Received")
resp, err := panics.RecoverO1(func() (*pbEnvoyAuthV3.CheckResponse, error) {
return i.check(ctx, request)
})
if err != nil {
var v DeniedResponse
if errors.As(err, &v) {
return v.GetCheckResponse()
}
return nil, err
}
return resp, nil
}
func (i *impl) check(ctx context.Context, request *pbEnvoyAuthV3.CheckRequest) (*pbEnvoyAuthV3.CheckResponse, error) {
ext := request.GetAttributes().GetContextExtensions()
if v, ok := ext[AuthConfigTypeKey]; !ok || v != AuthConfigTypeValue {
return nil, DeniedResponse{
Code: http.StatusBadRequest,
Message: &DeniedMessage{
Message: "Auth plugin is not enabled for this request",
},
}
}
authenticated, err := MergeAuthRequest(ctx, request, i.checkADBJWT)
if err != nil {
return nil, err
}
if util.Optional(ext, AuthConfigAuthRequiredKey, AuthConfigKeywordFalse) == AuthConfigKeywordTrue && authenticated == nil {
return nil, DeniedResponse{
Code: http.StatusUnauthorized,
Message: &DeniedMessage{
Message: "Unauthorized",
},
}
}
if authenticated != nil {
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,
},
},
},
},
}, nil
}
return &pbEnvoyAuthV3.CheckResponse{
HttpResponse: &pbEnvoyAuthV3.CheckResponse_OkResponse{
OkResponse: &pbEnvoyAuthV3.OkHttpResponse{},
OkResponse: &pbEnvoyAuthV3.OkHttpResponse{
Headers: []*corev3.HeaderValueOption{
{
Header: &corev3.HeaderValue{
Key: AuthAuthenticatedHeader,
Value: "false",
},
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
},
},
},
},
}, nil
}

View file

@ -0,0 +1,85 @@
//
// 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 (
"encoding/json"
"fmt"
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
pbEnvoyAuthV3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
status "google.golang.org/genproto/googleapis/rpc/status"
)
type DeniedMessage struct {
Message string `json:"message,omitempty"`
}
type DeniedResponse struct {
Code int32
Headers map[string]string
Message *DeniedMessage
}
func (d DeniedResponse) Error() string {
return fmt.Sprintf("Request denied with code: %d", d.Code)
}
func (d DeniedResponse) GetCheckResponse() (*pbEnvoyAuthV3.CheckResponse, error) {
var resp pbEnvoyAuthV3.DeniedHttpResponse
for k, v := range d.Headers {
resp.Headers = append(resp.Headers, &corev3.HeaderValueOption{
Header: &corev3.HeaderValue{
Key: k,
Value: v,
},
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
})
}
if data := d.Message; data != nil {
z, err := json.Marshal(data)
if err != nil {
return nil, err
}
resp.Body = string(z)
resp.Headers = append(resp.Headers, &corev3.HeaderValueOption{
Header: &corev3.HeaderValue{
Key: "content/type",
Value: "application/json",
},
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
})
}
resp.Status = &typev3.HttpStatus{
Code: typev3.StatusCode(d.Code),
}
return &pbEnvoyAuthV3.CheckResponse{
HttpResponse: &pbEnvoyAuthV3.CheckResponse_DeniedResponse{DeniedResponse: &resp},
Status: &status.Status{
Code: d.Code,
}}, nil
}

View file

@ -22,6 +22,7 @@ package v3
import (
"context"
"net/http"
"testing"
pbEnvoyAuthV3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
@ -35,7 +36,7 @@ import (
func Client(t *testing.T, ctx context.Context) pbEnvoyAuthV3.AuthorizationClient {
local := svc.NewService(svc.Configuration{
Address: "127.0.0.1:0",
}, New())
}, New(nil))
start := local.Start(ctx)
@ -44,7 +45,7 @@ func Client(t *testing.T, ctx context.Context) pbEnvoyAuthV3.AuthorizationClient
return client
}
func Test_AllowAll(t *testing.T) {
func Test_DenyHeader(t *testing.T) {
ctx, c := context.WithCancel(context.Background())
defer c()
@ -53,8 +54,28 @@ func Test_AllowAll(t *testing.T) {
resp, err := client.Check(ctx, &pbEnvoyAuthV3.CheckRequest{})
require.NoError(t, err)
require.NoError(t, resp.Validate())
require.NotNil(t, resp.Status)
require.NotNil(t, resp.HttpResponse)
require.NotNil(t, tests.CastAs[*pbEnvoyAuthV3.CheckResponse_DeniedResponse](t, resp.GetHttpResponse()).DeniedResponse)
require.EqualValues(t, http.StatusBadRequest, tests.CastAs[*pbEnvoyAuthV3.CheckResponse_DeniedResponse](t, resp.GetHttpResponse()).DeniedResponse.GetStatus().GetCode())
}
func Test_AllowAll(t *testing.T) {
ctx, c := context.WithCancel(context.Background())
defer c()
client := Client(t, ctx)
resp, err := client.Check(ctx, &pbEnvoyAuthV3.CheckRequest{
Attributes: &pbEnvoyAuthV3.AttributeContext{
ContextExtensions: map[string]string{
AuthConfigTypeKey: AuthConfigTypeValue,
},
},
})
require.NoError(t, err)
require.NoError(t, resp.Validate())
require.Nil(t, resp.Status)
require.NotNil(t, resp.HttpResponse)
require.NotNil(t, tests.CastAs[*pbEnvoyAuthV3.CheckResponse_OkResponse](t, resp.GetHttpResponse()).OkResponse)
require.Nil(t, resp.DynamicMetadata)
}

View file

@ -0,0 +1,62 @@
//
// 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 ArangoRouteSpecAuthenticationType string
const (
ArangoRouteSpecAuthenticationTypeRequired ArangoRouteSpecAuthenticationType = "required"
ArangoRouteSpecAuthenticationTypeOptional ArangoRouteSpecAuthenticationType = "optional"
)
func (a *ArangoRouteSpecAuthenticationType) Get() ArangoRouteSpecAuthenticationType {
if a == nil {
return ArangoRouteSpecAuthenticationTypeOptional
}
switch v := *a; v {
case ArangoRouteSpecAuthenticationTypeOptional, ArangoRouteSpecAuthenticationTypeRequired:
return v
}
return ""
}
func (a *ArangoRouteSpecAuthenticationType) Validate() error {
switch v := a.Get(); v {
case ArangoRouteSpecAuthenticationTypeOptional, ArangoRouteSpecAuthenticationTypeRequired:
return nil
default:
return errors.Errorf("Invalid AuthType: %s", v)
}
}
func (a *ArangoRouteSpecAuthenticationType) Hash() string {
if a == nil {
return ""
}
return util.SHA256FromString(string(*a))
}

View file

@ -34,6 +34,9 @@ type ArangoRouteSpecDestination struct {
// Path defines service path used for overrides
Path *string `json:"path,omitempty"`
// Authentication defines auth methods
Authentication *ArangoRouteSpecDestinationAuthentication `json:"authentication,omitempty"`
}
func (a *ArangoRouteSpecDestination) GetService() *ArangoRouteSpecDestinationService {
@ -68,6 +71,14 @@ func (a *ArangoRouteSpecDestination) GetTLS() *ArangoRouteSpecDestinationTLS {
return a.TLS
}
func (a *ArangoRouteSpecDestination) GetAuthentication() *ArangoRouteSpecDestinationAuthentication {
if a == nil || a.Authentication == nil {
return nil
}
return a.Authentication
}
func (a *ArangoRouteSpecDestination) Validate() error {
if a == nil {
a = &ArangoRouteSpecDestination{}
@ -77,6 +88,7 @@ func (a *ArangoRouteSpecDestination) Validate() error {
shared.ValidateOptionalInterfacePath("service", a.Service),
shared.ValidateOptionalInterfacePath("schema", a.Schema),
shared.ValidateOptionalInterfacePath("tls", a.TLS),
shared.ValidateOptionalInterfacePath("authentication", a.Authentication),
shared.PrefixResourceError("path", shared.ValidateAPIPath(a.GetPath())),
); err != nil {
return err

View file

@ -0,0 +1,47 @@
//
// 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 (
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
)
type ArangoRouteSpecDestinationAuthentication struct {
Type *ArangoRouteSpecAuthenticationType `json:"type,omitempty"`
}
func (a *ArangoRouteSpecDestinationAuthentication) GetType() ArangoRouteSpecAuthenticationType {
if a == nil {
return ArangoRouteSpecAuthenticationTypeOptional
}
return a.Type.Get()
}
func (a *ArangoRouteSpecDestinationAuthentication) Validate() error {
if a == nil {
return nil
}
return shared.WithErrors(
shared.ValidateOptionalInterfacePath("type", a.Type),
)
}

View file

@ -33,6 +33,9 @@ type ArangoRouteStatusTarget struct {
// TLS Keeps target TLS Settings (if not nil, TLS is enabled)
TLS *ArangoRouteStatusTargetTLS `json:"TLS,omitempty"`
// Authentication specifies the authentication details
Authentication ArangoRouteStatusTargetAuthentication `json:"authentication,omitempty"`
// Path specifies request path override
Path string `json:"path,omitempty"`
}
@ -61,5 +64,5 @@ func (a *ArangoRouteStatusTarget) Hash() string {
if a == nil {
return ""
}
return util.SHA256FromStringArray(a.Destinations.Hash(), a.TLS.Hash(), a.Path)
return util.SHA256FromStringArray(a.Destinations.Hash(), a.TLS.Hash(), a.Path, a.Authentication.Hash())
}

View file

@ -0,0 +1,34 @@
//
// 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"
type ArangoRouteStatusTargetAuthentication struct {
Type ArangoRouteSpecAuthenticationType `json:"type,omitempty"`
}
func (a *ArangoRouteStatusTargetAuthentication) Hash() string {
if a == nil {
return ""
}
return util.SHA256FromStringArray(a.Type.Hash())
}

View file

@ -26,7 +26,7 @@
package v1alpha1
import (
v2alpha1 "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
deploymentv1 "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
v1 "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
@ -147,6 +147,11 @@ func (in *ArangoRouteSpecDestination) DeepCopyInto(out *ArangoRouteSpecDestinati
*out = new(string)
**out = **in
}
if in.Authentication != nil {
in, out := &in.Authentication, &out.Authentication
*out = new(ArangoRouteSpecDestinationAuthentication)
(*in).DeepCopyInto(*out)
}
return
}
@ -160,6 +165,27 @@ func (in *ArangoRouteSpecDestination) DeepCopy() *ArangoRouteSpecDestination {
return out
}
// 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.Type != nil {
in, out := &in.Type, &out.Type
*out = new(ArangoRouteSpecAuthenticationType)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoRouteSpecDestinationAuthentication.
func (in *ArangoRouteSpecDestinationAuthentication) DeepCopy() *ArangoRouteSpecDestinationAuthentication {
if in == nil {
return nil
}
out := new(ArangoRouteSpecDestinationAuthentication)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArangoRouteSpecDestinationService) DeepCopyInto(out *ArangoRouteSpecDestinationService) {
*out = *in
@ -233,7 +259,7 @@ func (in *ArangoRouteStatus) DeepCopyInto(out *ArangoRouteStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make(v2alpha1.ConditionList, len(*in))
*out = make(deploymentv1.ConditionList, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@ -274,6 +300,7 @@ func (in *ArangoRouteStatusTarget) DeepCopyInto(out *ArangoRouteStatusTarget) {
*out = new(ArangoRouteStatusTargetTLS)
(*in).DeepCopyInto(*out)
}
out.Authentication = in.Authentication
return
}
@ -287,6 +314,22 @@ func (in *ArangoRouteStatusTarget) DeepCopy() *ArangoRouteStatusTarget {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArangoRouteStatusTargetAuthentication) DeepCopyInto(out *ArangoRouteStatusTargetAuthentication) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoRouteStatusTargetAuthentication.
func (in *ArangoRouteStatusTargetAuthentication) DeepCopy() *ArangoRouteStatusTargetAuthentication {
if in == nil {
return nil
}
out := new(ArangoRouteStatusTargetAuthentication)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArangoRouteStatusTargetDestination) DeepCopyInto(out *ArangoRouteStatusTargetDestination) {
*out = *in

View file

@ -9,6 +9,12 @@ v1alpha1:
destination:
description: Destination defines the route destination
properties:
authentication:
description: Authentication defines auth methods
properties:
type:
type: string
type: object
path:
description: Path defines service path used for overrides
type: string

View file

@ -30,6 +30,7 @@ import (
kerrors "k8s.io/apimachinery/pkg/api/errors"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
pbImplEnvoyAuthV3 "github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3"
networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources/gateway"
@ -131,6 +132,7 @@ func (r *Resources) renderGatewayConfig(cachedStatus inspectorInterface.Inspecto
Port: shared.ArangoPort,
},
},
AuthExtension: &gateway.ConfigAuthZExtension{},
}
if spec.TLS.IsSecure() {
@ -193,6 +195,11 @@ func (r *Resources) renderGatewayConfig(cachedStatus inspectorInterface.Inspecto
dest.Type = util.NewType(gateway.ConfigDestinationTypeHTTPS)
}
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),
},
}
cfg.Destinations[at.Spec.GetRoute().GetPath()] = dest
}

View file

@ -0,0 +1,67 @@
//
// 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 gateway
import (
httpFilterAuthzApi "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
"google.golang.org/protobuf/types/known/anypb"
pbImplEnvoyAuthV3 "github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3"
"github.com/arangodb/kube-arangodb/pkg/util"
)
type ConfigAuthZExtension struct {
AuthZExtension map[string]string `json:"authZExtension,omitempty"`
}
func (c *ConfigAuthZExtension) RenderTypedFilterConfig() (util.KV[string, *anypb.Any], error) {
if c == nil {
return util.KV[string, *anypb.Any]{}, nil
}
var data = map[string]string{}
for k, v := range c.AuthZExtension {
data[k] = v
}
data[pbImplEnvoyAuthV3.AuthConfigTypeKey] = pbImplEnvoyAuthV3.AuthConfigTypeValue
q, err := anypb.New(&httpFilterAuthzApi.ExtAuthzPerRoute{
Override: &httpFilterAuthzApi.ExtAuthzPerRoute_CheckSettings{
CheckSettings: &httpFilterAuthzApi.CheckSettings{
ContextExtensions: data,
},
},
})
if err != nil {
return util.KV[string, *anypb.Any]{}, err
}
return util.KV[string, *anypb.Any]{
K: IntegrationSidecarFilterName,
V: q,
}, nil
}
func (c *ConfigAuthZExtension) Validate() error {
return nil
}

View file

@ -26,7 +26,6 @@ import (
clusterAPI "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
endpointAPI "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
routeAPI "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
anypb "github.com/golang/protobuf/ptypes/any"
"google.golang.org/protobuf/types/known/durationpb"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
@ -62,6 +61,8 @@ type ConfigDestination struct {
Type *ConfigDestinationType `json:"type,omitempty"`
Path *string `json:"path,omitempty"`
AuthExtension *ConfigAuthZExtension `json:"authExtension,omitempty"`
}
func (c *ConfigDestination) Validate() error {
@ -72,6 +73,7 @@ func (c *ConfigDestination) Validate() error {
shared.PrefixResourceError("targets", c.Targets.Validate()),
shared.PrefixResourceError("type", c.Type.Validate()),
shared.PrefixResourceError("path", shared.ValidateAPIPath(c.GetPath())),
shared.PrefixResourceError("authExtension", c.AuthExtension.Validate()),
)
}
@ -84,6 +86,16 @@ func (c *ConfigDestination) GetPath() string {
}
func (c *ConfigDestination) RenderRoute(name, prefix string) (*routeAPI.Route, error) {
var tcg []TypedFilterConfigGen
if c != nil && c.AuthExtension != nil {
tcg = append(tcg, c.AuthExtension)
}
tc, err := NewTypedFilterConfig(tcg...)
if err != nil {
return nil, err
}
return &routeAPI.Route{
Match: &routeAPI.RouteMatch{
PathSpecifier: &routeAPI.RouteMatch_Prefix{
@ -98,7 +110,7 @@ func (c *ConfigDestination) RenderRoute(name, prefix string) (*routeAPI.Route, e
PrefixRewrite: c.GetPath(),
},
},
TypedPerFilterConfig: map[string]*anypb.Any{},
TypedPerFilterConfig: tc,
}, nil
}

View file

@ -0,0 +1,58 @@
//
// 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 gateway
import (
"google.golang.org/protobuf/types/known/anypb"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
type TypedFilterConfigGen interface {
RenderTypedFilterConfig() (util.KV[string, *anypb.Any], error)
}
func NewTypedFilterConfig(gens ...TypedFilterConfigGen) (map[string]*anypb.Any, error) {
generated := map[string]*anypb.Any{}
for _, g := range gens {
if k, err := g.RenderTypedFilterConfig(); err != nil {
return nil, err
} else {
if _, ok := generated[k.K]; ok {
return nil, errors.Errorf("Duplicated key: %s", k.K)
}
if k.V == nil {
continue
}
generated[k.K] = k.V
}
}
if len(generated) == 0 {
return nil, nil
}
return generated, nil
}

View file

@ -238,9 +238,13 @@ func (m *MemberGatewayPod) Labels() map[string]string {
func (m *MemberGatewayPod) Profiles() (schedulerApi.ProfileTemplates, error) {
integration, err := sidecar.NewIntegration(&schedulerContainerResourcesApi.Image{
Image: util.NewType(m.resources.context.GetOperatorImage()),
}, m.spec.Gateway.GetSidecar(), []string{shared.ServerContainerName}, sidecar.IntegrationEnvoyV3{
Spec: m.spec,
})
}, m.spec.Gateway.GetSidecar(), []string{shared.ServerContainerName},
sidecar.IntegrationEnvoyV3{
Spec: m.spec,
}, sidecar.IntegrationAuthenticationV1{
DeploymentName: m.context.GetName(),
Spec: m.spec,
})
if err != nil {
return nil, err

View file

@ -121,6 +121,10 @@ func (h *handler) HandleArangoDestination(ctx context.Context, item operation.It
target.Path = dest.GetPath()
// Render Auth Settings
target.Authentication.Type = dest.GetAuthentication().GetType()
if dest.Schema.Get() == networkingApi.ArangoRouteSpecDestinationSchemaHTTPS {
target.TLS = &networkingApi.ArangoRouteStatusTargetTLS{
Insecure: util.NewType(extension.Spec.Destination.GetTLS().GetInsecure()),

View file

@ -56,7 +56,7 @@ func (a *authenticationV1) Register(cmd *cobra.Command, arg ArgGen) error {
return nil
}
func (a *authenticationV1) Handler(ctx context.Context) (svc.Handler, error) {
func (a *authenticationV1) Handler(ctx context.Context, cmd *cobra.Command) (svc.Handler, error) {
return pbImplAuthenticationV1.New(ctx, a.config)
}
@ -67,3 +67,7 @@ func (a *authenticationV1) Name() string {
func (a *authenticationV1) Description() string {
return "Enable AuthenticationV1 Integration Service"
}
func (*authenticationV1) Init(ctx context.Context, cmd *cobra.Command) error {
return nil
}

View file

@ -51,6 +51,10 @@ func (a authorizationV0) Register(cmd *cobra.Command, arg ArgGen) error {
return nil
}
func (a authorizationV0) Handler(ctx context.Context) (svc.Handler, error) {
func (a authorizationV0) Handler(ctx context.Context, cmd *cobra.Command) (svc.Handler, error) {
return pbImplAuthorizationV0.New(), nil
}
func (a authorizationV0) Init(ctx context.Context, cmd *cobra.Command) error {
return nil
}

View file

@ -49,7 +49,7 @@ func (a *configV1) Register(cmd *cobra.Command, arg ArgGen) error {
return nil
}
func (a *configV1) Handler(ctx context.Context) (svc.Handler, error) {
func (a *configV1) Handler(ctx context.Context, cmd *cobra.Command) (svc.Handler, error) {
var cfg pbImplConfigV1.Config
cfg.Modules = map[string]pbImplConfigV1.ModuleDefinition{}
@ -75,3 +75,7 @@ func (a *configV1) Name() string {
func (a *configV1) Description() string {
return "Enable ConfigV1 Integration Service"
}
func (*configV1) Init(ctx context.Context, cmd *cobra.Command) error {
return nil
}

View file

@ -25,7 +25,9 @@ import (
"github.com/spf13/cobra"
pbAuthenticationV1 "github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition"
pbImplEnvoyAuthV3 "github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/svc"
)
@ -42,14 +44,26 @@ func (a envoyAuthV3) Name() string {
return pbImplEnvoyAuthV3.Name
}
func (a envoyAuthV3) Description() string {
func (a *envoyAuthV3) Description() string {
return "Enable EnvoyAuthV3 Integration Service"
}
func (a envoyAuthV3) Register(cmd *cobra.Command, arg ArgGen) error {
func (a *envoyAuthV3) Register(cmd *cobra.Command, arg ArgGen) error {
return nil
}
func (a envoyAuthV3) Handler(ctx context.Context) (svc.Handler, error) {
return pbImplEnvoyAuthV3.New(), nil
func (a *envoyAuthV3) Handler(ctx context.Context, cmd *cobra.Command) (svc.Handler, error) {
f := cmd.Flags()
v, err := f.GetString("services.address")
if err != nil {
return nil, err
}
c, _, err := util.NewGRPCClient(ctx, pbAuthenticationV1.NewAuthenticationV1Client, v)
if err != nil {
return nil, err
}
return pbImplEnvoyAuthV3.New(c), nil
}

View file

@ -38,7 +38,7 @@ type Integration interface {
Register(cmd *cobra.Command, arg ArgGen) error
Handler(ctx context.Context) (svc.Handler, error)
Handler(ctx context.Context, cmd *cobra.Command) (svc.Handler, error)
}
type IntegrationEnablement interface {

View file

@ -36,6 +36,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/shutdown"
"github.com/arangodb/kube-arangodb/pkg/util/svc"
"github.com/arangodb/kube-arangodb/pkg/version"
)
var registerer = util.NewRegisterer[string, Factory]()
@ -171,6 +172,8 @@ func (c *configuration) run(cmd *cobra.Command, args []string) error {
}
func (c *configuration) runWithContext(ctx context.Context, cancel context.CancelFunc, cmd *cobra.Command) error {
println(version.GetVersionV1().String())
healthConfig, err := c.health.Config()
if err != nil {
return errors.Wrapf(err, "Unable to parse health config")
@ -214,7 +217,7 @@ func (c *configuration) runWithContext(ctx context.Context, cancel context.Cance
Info("Service discovered")
if ok && (internalEnabled || externalEnabled) {
if svc, err := handler.Handler(ctx); err != nil {
if svc, err := handler.Handler(ctx, cmd); err != nil {
return err
} else {
if internalEnabled {

View file

@ -59,7 +59,7 @@ func (b *schedulerV1) Register(cmd *cobra.Command, arg ArgGen) error {
return nil
}
func (b *schedulerV1) Handler(ctx context.Context) (svc.Handler, error) {
func (b *schedulerV1) Handler(ctx context.Context, cmd *cobra.Command) (svc.Handler, error) {
client, ok := kclient.GetDefaultFactory().Client()
if !ok {
return nil, errors.Errorf("Unable to create Kubernetes Client")
@ -67,3 +67,7 @@ func (b *schedulerV1) Handler(ctx context.Context) (svc.Handler, error) {
return pbImplSchedulerV1.New(ctx, client, b.Configuration)
}
func (*schedulerV1) Init(ctx context.Context, cmd *cobra.Command) error {
return nil
}

View file

@ -40,7 +40,7 @@ func init() {
type shutdownV1 struct {
}
func (s *shutdownV1) Handler(ctx context.Context) (svc.Handler, error) {
func (s *shutdownV1) Handler(ctx context.Context, cmd *cobra.Command) (svc.Handler, error) {
return pbImplShutdownV1.New(shutdown.Stop), nil
}
@ -55,3 +55,7 @@ func (s *shutdownV1) Description() string {
func (s *shutdownV1) Register(cmd *cobra.Command, arg ArgGen) error {
return nil
}
func (*shutdownV1) Init(ctx context.Context, cmd *cobra.Command) error {
return nil
}

View file

@ -26,15 +26,16 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)
var _ IntegrationVolumes = IntegrationAuthenticationV1{}
type IntegrationAuthenticationV1 struct {
Core *Core
Deployment *api.ArangoDeployment
Core *Core
DeploymentName string
Spec api.DeploymentSpec
}
func (i IntegrationAuthenticationV1) Name() []string {
@ -42,10 +43,6 @@ func (i IntegrationAuthenticationV1) Name() []string {
}
func (i IntegrationAuthenticationV1) Validate() error {
if i.Deployment == nil {
return errors.Errorf("Deployment is nil")
}
return nil
}
@ -53,7 +50,7 @@ func (i IntegrationAuthenticationV1) Args() (k8sutil.OptionPairs, error) {
options := k8sutil.CreateOptionPairs()
options.Add("--integration.authentication.v1", true)
options.Add("--integration.authentication.v1.enabled", i.Deployment.GetAcceptedSpec().IsAuthenticated())
options.Add("--integration.authentication.v1.enabled", i.Spec.IsAuthenticated())
options.Add("--integration.authentication.v1.path", shared.ClusterJWTSecretVolumeMountDir)
options.Merge(i.Core.Args(i))
@ -62,13 +59,13 @@ func (i IntegrationAuthenticationV1) Args() (k8sutil.OptionPairs, error) {
}
func (i IntegrationAuthenticationV1) Volumes() ([]core.Volume, []core.VolumeMount, error) {
if i.Deployment.GetAcceptedSpec().IsAuthenticated() {
if i.Spec.IsAuthenticated() {
return []core.Volume{
{
Name: shared.ClusterJWTSecretVolumeName,
VolumeSource: core.VolumeSource{
Secret: &core.SecretVolumeSource{
SecretName: pod.JWTSecretFolder(i.Deployment.GetName()),
SecretName: pod.JWTSecretFolder(i.DeploymentName),
},
},
},

View file

@ -64,6 +64,10 @@ func (b *storageV1) Register(cmd *cobra.Command, arg ArgGen) error {
return nil
}
func (b *storageV1) Handler(ctx context.Context) (svc.Handler, error) {
func (b *storageV1) Handler(ctx context.Context, cmd *cobra.Command) (svc.Handler, error) {
return storage.NewService(ctx, b.Configuration)
}
func (*storageV1) Init(ctx context.Context, cmd *cobra.Command) error {
return nil
}

View file

@ -109,7 +109,7 @@ func Test_TLSCases(t *testing.T) {
"--tls.enabled=false",
"client",
"health",
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"error reading server preface: EOF\"")
"v1")).Code(t, codes.Unavailable)
})
t.Run("external", func(t *testing.T) {
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
@ -117,7 +117,7 @@ func Test_TLSCases(t *testing.T) {
"--tls.enabled=false",
"client",
"health",
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"error reading server preface: EOF\"")
"v1")).Code(t, codes.Unavailable)
})
})

View file

@ -115,3 +115,12 @@ func IterateSorted[V any](m map[string]V, cb func(string, V)) {
cb(k, m[k])
}
}
func Optional[K comparable, V any](m map[K]V, key K, def V) V {
v, ok := m[key]
if ok {
return v
}
return def
}

View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-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.
@ -30,6 +30,20 @@ func recoverPanic(skipFrames int, in func() error) (err error) {
return in()
}
func recoverPanicO1[O1 any](skipFrames int, in func() (O1, error)) (o1 O1, err error) {
defer func() {
if r := recover(); r != nil {
err = newPanicError(r, GetStack(skipFrames))
}
}()
return in()
}
func Recover(in func() error) (err error) {
return recoverPanic(4, in)
}
func RecoverO1[O1 any](in func() (O1, error)) (O1, error) {
return recoverPanicO1(4, in)
}

View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-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.
@ -21,6 +21,7 @@
package version
import (
"fmt"
"runtime"
"github.com/arangodb/go-driver"
@ -58,6 +59,10 @@ func (i InfoV1) IsEnterprise() bool {
return i.Edition == EnterpriseEdition
}
func (i InfoV1) String() string {
return fmt.Sprintf("Version: %s %s, Build: %s, Go: %s, Build Date: %s", i.Edition.Title(), i.Version, i.Build, i.GoVersion, i.BuildDate)
}
func GetVersionV1() InfoV1 {
return InfoV1{
Version: driver.Version(version),