From fb2ac883ae7d826612e6a0cde4b26b38d94050ca Mon Sep 17 00:00:00 2001 From: Adam Janikowski <12255597+ajanikow@users.noreply.github.com> Date: Tue, 3 Sep 2024 17:50:59 +0200 Subject: [PATCH] [Feature] [Gateway] ArangoDB AuthIntegration (#1715) --- CHANGELOG.md | 1 + cmd/version.go | 7 +- docs/api/ArangoRoute.V1Alpha1.md | 14 ++- integrations/envoy/auth/v3/check.go | 46 ++++++++++ integrations/envoy/auth/v3/check_adb_jwt.go | 60 ++++++++++++ integrations/envoy/auth/v3/consts.go | 14 +++ integrations/envoy/auth/v3/impl.go | 92 ++++++++++++++++++- integrations/envoy/auth/v3/response.go | 85 +++++++++++++++++ integrations/envoy/auth/v3/service_test.go | 27 +++++- .../v1alpha1/route_spec_authentication.go | 62 +++++++++++++ .../v1alpha1/route_spec_destination.go | 12 +++ .../route_spec_destination_authentication.go | 47 ++++++++++ .../v1alpha1/route_status_target.go | 5 +- .../route_status_target_authentication.go | 34 +++++++ .../v1alpha1/zz_generated.deepcopy.go | 47 +++++++++- .../networking-route.schema.generated.yaml | 6 ++ .../resources/config_map_gateway.go | 7 ++ .../gateway/gateway_authz_extension.go | 67 ++++++++++++++ .../gateway/gateway_config_destination.go | 16 +++- .../gateway/gateway_filter_extension.go | 58 ++++++++++++ .../resources/pod_creator_gateway_pod.go | 10 +- .../networking/route/handler_destination.go | 4 + pkg/integrations/authentication_v1.go | 6 +- pkg/integrations/authorization_v0.go | 6 +- pkg/integrations/config_v1.go | 6 +- pkg/integrations/envoy_auth_v3.go | 22 ++++- pkg/integrations/integration.go | 2 +- pkg/integrations/register.go | 5 +- pkg/integrations/scheduler_v1.go | 6 +- pkg/integrations/shutdown_v1.go | 6 +- .../sidecar/integration.authentication.v1.go | 17 ++-- pkg/integrations/storage_v1.go | 6 +- pkg/integrations/tls_test.go | 4 +- pkg/util/dict.go | 9 ++ pkg/util/errors/panics/recovery.go | 16 +++- pkg/version/version.go | 7 +- 36 files changed, 792 insertions(+), 47 deletions(-) create mode 100644 integrations/envoy/auth/v3/check.go create mode 100644 integrations/envoy/auth/v3/check_adb_jwt.go create mode 100644 integrations/envoy/auth/v3/response.go create mode 100644 pkg/apis/networking/v1alpha1/route_spec_authentication.go create mode 100644 pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go create mode 100644 pkg/apis/networking/v1alpha1/route_status_target_authentication.go create mode 100644 pkg/deployment/resources/gateway/gateway_authz_extension.go create mode 100644 pkg/deployment/resources/gateway/gateway_filter_extension.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b1a550f0..3c076e232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/cmd/version.go b/cmd/version.go index fb4c9d920..f489ec786 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -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()) } diff --git a/docs/api/ArangoRoute.V1Alpha1.md b/docs/api/ArangoRoute.V1Alpha1.md index e69bf4c81..b083aa436 100644 --- a/docs/api/ArangoRoute.V1Alpha1.md +++ b/docs/api/ArangoRoute.V1Alpha1.md @@ -16,6 +16,12 @@ Deployment specifies the ArangoDeployment object name *** +### .spec.destination.authentication.type + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go#L28) + +*** + ### .spec.destination.path Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination.go#L36) @@ -131,6 +137,12 @@ UID keeps the information about object UID *** +### .status.target.authentication.type + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target_authentication.go#L26) + +*** + ### .status.target.destinations\[int\].host Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target_destination.go#L38) @@ -145,7 +157,7 @@ Type: `integer` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1. ### .status.target.path -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L37) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L40) Path specifies request path override diff --git a/integrations/envoy/auth/v3/check.go b/integrations/envoy/auth/v3/check.go new file mode 100644 index 000000000..c462276fe --- /dev/null +++ b/integrations/envoy/auth/v3/check.go @@ -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 +} diff --git a/integrations/envoy/auth/v3/check_adb_jwt.go b/integrations/envoy/auth/v3/check_adb_jwt.go new file mode 100644 index 000000000..fc96d9ba4 --- /dev/null +++ b/integrations/envoy/auth/v3/check_adb_jwt.go @@ -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 +} diff --git a/integrations/envoy/auth/v3/consts.go b/integrations/envoy/auth/v3/consts.go index 7cd7c3413..ae414cd3e 100644 --- a/integrations/envoy/auth/v3/consts.go +++ b/integrations/envoy/auth/v3/consts.go @@ -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" ) diff --git a/integrations/envoy/auth/v3/impl.go b/integrations/envoy/auth/v3/impl.go index c3930b08b..7c6a96bf7 100644 --- a/integrations/envoy/auth/v3/impl.go +++ b/integrations/envoy/auth/v3/impl.go @@ -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 } diff --git a/integrations/envoy/auth/v3/response.go b/integrations/envoy/auth/v3/response.go new file mode 100644 index 000000000..15cc2499b --- /dev/null +++ b/integrations/envoy/auth/v3/response.go @@ -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 +} diff --git a/integrations/envoy/auth/v3/service_test.go b/integrations/envoy/auth/v3/service_test.go index a1eeb34c3..18e25c454 100644 --- a/integrations/envoy/auth/v3/service_test.go +++ b/integrations/envoy/auth/v3/service_test.go @@ -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) } diff --git a/pkg/apis/networking/v1alpha1/route_spec_authentication.go b/pkg/apis/networking/v1alpha1/route_spec_authentication.go new file mode 100644 index 000000000..249302f0e --- /dev/null +++ b/pkg/apis/networking/v1alpha1/route_spec_authentication.go @@ -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)) +} diff --git a/pkg/apis/networking/v1alpha1/route_spec_destination.go b/pkg/apis/networking/v1alpha1/route_spec_destination.go index ff1c58018..055fd6cbf 100644 --- a/pkg/apis/networking/v1alpha1/route_spec_destination.go +++ b/pkg/apis/networking/v1alpha1/route_spec_destination.go @@ -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 diff --git a/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go b/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go new file mode 100644 index 000000000..47225cab4 --- /dev/null +++ b/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go @@ -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), + ) +} diff --git a/pkg/apis/networking/v1alpha1/route_status_target.go b/pkg/apis/networking/v1alpha1/route_status_target.go index 89f1b7a0d..26d67b1d3 100644 --- a/pkg/apis/networking/v1alpha1/route_status_target.go +++ b/pkg/apis/networking/v1alpha1/route_status_target.go @@ -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()) } diff --git a/pkg/apis/networking/v1alpha1/route_status_target_authentication.go b/pkg/apis/networking/v1alpha1/route_status_target_authentication.go new file mode 100644 index 000000000..e24014189 --- /dev/null +++ b/pkg/apis/networking/v1alpha1/route_status_target_authentication.go @@ -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()) +} diff --git a/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go index ba179a20b..b1cf33eed 100644 --- a/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go @@ -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 diff --git a/pkg/crd/crds/networking-route.schema.generated.yaml b/pkg/crd/crds/networking-route.schema.generated.yaml index 49d93b356..5751f46b1 100644 --- a/pkg/crd/crds/networking-route.schema.generated.yaml +++ b/pkg/crd/crds/networking-route.schema.generated.yaml @@ -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 diff --git a/pkg/deployment/resources/config_map_gateway.go b/pkg/deployment/resources/config_map_gateway.go index 739474f8e..9285e38f8 100644 --- a/pkg/deployment/resources/config_map_gateway.go +++ b/pkg/deployment/resources/config_map_gateway.go @@ -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 } diff --git a/pkg/deployment/resources/gateway/gateway_authz_extension.go b/pkg/deployment/resources/gateway/gateway_authz_extension.go new file mode 100644 index 000000000..b7e6edf0c --- /dev/null +++ b/pkg/deployment/resources/gateway/gateway_authz_extension.go @@ -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 +} diff --git a/pkg/deployment/resources/gateway/gateway_config_destination.go b/pkg/deployment/resources/gateway/gateway_config_destination.go index f8ee81c63..3139b3f31 100644 --- a/pkg/deployment/resources/gateway/gateway_config_destination.go +++ b/pkg/deployment/resources/gateway/gateway_config_destination.go @@ -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 } diff --git a/pkg/deployment/resources/gateway/gateway_filter_extension.go b/pkg/deployment/resources/gateway/gateway_filter_extension.go new file mode 100644 index 000000000..f711e952e --- /dev/null +++ b/pkg/deployment/resources/gateway/gateway_filter_extension.go @@ -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 +} diff --git a/pkg/deployment/resources/pod_creator_gateway_pod.go b/pkg/deployment/resources/pod_creator_gateway_pod.go index 5f8fc16bf..16d14c89d 100644 --- a/pkg/deployment/resources/pod_creator_gateway_pod.go +++ b/pkg/deployment/resources/pod_creator_gateway_pod.go @@ -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 diff --git a/pkg/handlers/networking/route/handler_destination.go b/pkg/handlers/networking/route/handler_destination.go index 80806d135..7a618ffed 100644 --- a/pkg/handlers/networking/route/handler_destination.go +++ b/pkg/handlers/networking/route/handler_destination.go @@ -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()), diff --git a/pkg/integrations/authentication_v1.go b/pkg/integrations/authentication_v1.go index 3de449d76..60bc77afa 100644 --- a/pkg/integrations/authentication_v1.go +++ b/pkg/integrations/authentication_v1.go @@ -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 +} diff --git a/pkg/integrations/authorization_v0.go b/pkg/integrations/authorization_v0.go index d63ab33d5..fd001d687 100644 --- a/pkg/integrations/authorization_v0.go +++ b/pkg/integrations/authorization_v0.go @@ -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 +} diff --git a/pkg/integrations/config_v1.go b/pkg/integrations/config_v1.go index 582605a03..d0ff5faeb 100644 --- a/pkg/integrations/config_v1.go +++ b/pkg/integrations/config_v1.go @@ -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 +} diff --git a/pkg/integrations/envoy_auth_v3.go b/pkg/integrations/envoy_auth_v3.go index aadfabacc..c8cb55808 100644 --- a/pkg/integrations/envoy_auth_v3.go +++ b/pkg/integrations/envoy_auth_v3.go @@ -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 } diff --git a/pkg/integrations/integration.go b/pkg/integrations/integration.go index 0b6543177..d896c7f00 100644 --- a/pkg/integrations/integration.go +++ b/pkg/integrations/integration.go @@ -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 { diff --git a/pkg/integrations/register.go b/pkg/integrations/register.go index 8799452dc..d20a8b0d8 100644 --- a/pkg/integrations/register.go +++ b/pkg/integrations/register.go @@ -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 { diff --git a/pkg/integrations/scheduler_v1.go b/pkg/integrations/scheduler_v1.go index e6d906797..ad84aec7f 100644 --- a/pkg/integrations/scheduler_v1.go +++ b/pkg/integrations/scheduler_v1.go @@ -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 +} diff --git a/pkg/integrations/shutdown_v1.go b/pkg/integrations/shutdown_v1.go index 504f1931f..93c4c4a9c 100644 --- a/pkg/integrations/shutdown_v1.go +++ b/pkg/integrations/shutdown_v1.go @@ -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 +} diff --git a/pkg/integrations/sidecar/integration.authentication.v1.go b/pkg/integrations/sidecar/integration.authentication.v1.go index a51df0c78..9504877df 100644 --- a/pkg/integrations/sidecar/integration.authentication.v1.go +++ b/pkg/integrations/sidecar/integration.authentication.v1.go @@ -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), }, }, }, diff --git a/pkg/integrations/storage_v1.go b/pkg/integrations/storage_v1.go index ffa351f4e..9ca2a1f00 100644 --- a/pkg/integrations/storage_v1.go +++ b/pkg/integrations/storage_v1.go @@ -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 +} diff --git a/pkg/integrations/tls_test.go b/pkg/integrations/tls_test.go index 79aa38f6d..0bb1616da 100644 --- a/pkg/integrations/tls_test.go +++ b/pkg/integrations/tls_test.go @@ -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) }) }) diff --git a/pkg/util/dict.go b/pkg/util/dict.go index edcd37fc4..38af1a980 100644 --- a/pkg/util/dict.go +++ b/pkg/util/dict.go @@ -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 +} diff --git a/pkg/util/errors/panics/recovery.go b/pkg/util/errors/panics/recovery.go index 3a54e4f05..9556da94f 100644 --- a/pkg/util/errors/panics/recovery.go +++ b/pkg/util/errors/panics/recovery.go @@ -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) +} diff --git a/pkg/version/version.go b/pkg/version/version.go index 0115a34ea..94e81316c 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -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),