From a4665ea6f0781d65da3fc0aa1e74e01cdca393e0 Mon Sep 17 00:00:00 2001 From: ajanikow <12255597+ajanikow@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:54:59 +0000 Subject: [PATCH] [Feature] [ML] Add TLS Secrets --- CHANGELOG.md | 1 + docs/api/ArangoMLExtension.V1Alpha1.md | 48 +++++++++++++++++ .../ml/v1alpha1/extension_spec_deployment.go | 10 ++++ .../v1alpha1/extension_spec_deployment_tls.go | 29 ++++++++++ .../v1alpha1/extension_status_arangodb_ref.go | 4 +- pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go | 36 +++++++++++++ pkg/apis/shared/v1/object.go | 14 +++++ .../crds/ml-extension.schema.generated.yaml | 12 +++++ pkg/deployment/resources/certificates_tls.go | 5 +- pkg/util/context.go | 35 +++++++++++- pkg/util/k8sutil/kerrors/errors.go | 53 ++++++++++++++++++- pkg/util/k8sutil/secrets.go | 9 ++-- 12 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 pkg/apis/ml/v1alpha1/extension_spec_deployment_tls.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b89f6737..8eebbddb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - (Bugfix) Ensure PDB is created - (Bugfix) Fix Schema Apply Checksum - (Bugfix) Use MD5 instead of SHA256 for CRD Checksums +- (Feature) (ML) Add TLS Secrets ## [1.2.40](https://github.com/arangodb/kube-arangodb/tree/1.2.40) (2024-04-10) - (Feature) Add Core fields to the Scheduler Container Spec diff --git a/docs/api/ArangoMLExtension.V1Alpha1.md b/docs/api/ArangoMLExtension.V1Alpha1.md index 0de026b0f..5333d9866 100644 --- a/docs/api/ArangoMLExtension.V1Alpha1.md +++ b/docs/api/ArangoMLExtension.V1Alpha1.md @@ -567,6 +567,22 @@ Default Value: `false` *** +### .spec.deployment.tls.altNames + +Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.40/pkg/apis/ml/v1alpha1/extension_spec_deployment_tls.go#L28) + +AltNames define TLS AltNames used when TLS on the ArangoDB is enabled + +*** + +### .spec.deployment.tls.enabled + +Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.40/pkg/apis/ml/v1alpha1/extension_spec_deployment_tls.go#L25) + +Enabled define if TLS Should be enabled. If is not set then default is taken from ArangoDeployment settings + +*** + ### .spec.deployment.tolerations Type: `[]core.Toleration` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.40/pkg/apis/scheduler/v1alpha1/pod/resources/scheduling.go#L49) @@ -3317,6 +3333,38 @@ UID keeps the information about object UID *** +### .status.arangoDB.tls.checksum + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.40/pkg/apis/shared/v1/object.go#L61) + +UID keeps the information about object Checksum + +*** + +### .status.arangoDB.tls.name + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.40/pkg/apis/shared/v1/object.go#L52) + +Name of the object + +*** + +### .status.arangoDB.tls.namespace + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.40/pkg/apis/shared/v1/object.go#L55) + +Namespace of the object. Should default to the namespace of the parent object + +*** + +### .status.arangoDB.tls.uid + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.40/pkg/apis/shared/v1/object.go#L58) + +UID keeps the information about object UID + +*** + ### .status.conditions Type: `api.Conditions` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.40/pkg/apis/ml/v1alpha1/extension_status.go#L31) diff --git a/pkg/apis/ml/v1alpha1/extension_spec_deployment.go b/pkg/apis/ml/v1alpha1/extension_spec_deployment.go index fc6d7543f..4fa08fb80 100644 --- a/pkg/apis/ml/v1alpha1/extension_spec_deployment.go +++ b/pkg/apis/ml/v1alpha1/extension_spec_deployment.go @@ -58,6 +58,9 @@ type ArangoMLExtensionSpecDeployment struct { // Service defines how components will be exposed Service *ArangoMLExtensionSpecDeploymentService `json:"service,omitempty"` + // TLS defined TLS Settings for extension + TLS *ArangoMLExtensionSpecDeploymentTLS `json:"tls,omitempty"` + // Pod defines base template for pods *schedulerPodApi.Pod @@ -136,6 +139,13 @@ func (s *ArangoMLExtensionSpecDeployment) GetService() *ArangoMLExtensionSpecDep return s.Service } +func (s *ArangoMLExtensionSpecDeployment) GetTLS() *ArangoMLExtensionSpecDeploymentTLS { + if s == nil { + return nil + } + return s.TLS +} + func (s *ArangoMLExtensionSpecDeployment) Validate() error { if s == nil { return nil diff --git a/pkg/apis/ml/v1alpha1/extension_spec_deployment_tls.go b/pkg/apis/ml/v1alpha1/extension_spec_deployment_tls.go new file mode 100644 index 000000000..242b14c1f --- /dev/null +++ b/pkg/apis/ml/v1alpha1/extension_spec_deployment_tls.go @@ -0,0 +1,29 @@ +// +// 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 + +type ArangoMLExtensionSpecDeploymentTLS struct { + // Enabled define if TLS Should be enabled. If is not set then default is taken from ArangoDeployment settings + Enabled *bool `json:"enabled,omitempty"` + + // AltNames define TLS AltNames used when TLS on the ArangoDB is enabled + AltNames []string `json:"altNames,omitempty"` +} diff --git a/pkg/apis/ml/v1alpha1/extension_status_arangodb_ref.go b/pkg/apis/ml/v1alpha1/extension_status_arangodb_ref.go index 9ce7b78b9..b51e5a127 100644 --- a/pkg/apis/ml/v1alpha1/extension_status_arangodb_ref.go +++ b/pkg/apis/ml/v1alpha1/extension_status_arangodb_ref.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// Copyright 2023-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. @@ -25,6 +25,8 @@ import sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1" type ArangoMLExtensionStatusArangoDBRef struct { // Secret keeps the information about ArangoDB deployment Secret *sharedApi.Object `json:"secret,omitempty"` + // TLS keeps information about TLS Secret rendered from ArangoDB deployment + TLS *sharedApi.Object `json:"tls,omitempty"` // JWTTokenSecret keeps the JWT for ArangoDB authentication (only when ArangoDeployment has JWT enabled) JWTTokenSecret *sharedApi.Object `json:"jwtTokenSecret,omitempty"` } diff --git a/pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go index ef431a0ce..c9f71c96b 100644 --- a/pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go @@ -380,6 +380,11 @@ func (in *ArangoMLExtensionSpecDeployment) DeepCopyInto(out *ArangoMLExtensionSp *out = new(ArangoMLExtensionSpecDeploymentService) (*in).DeepCopyInto(*out) } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(ArangoMLExtensionSpecDeploymentTLS) + (*in).DeepCopyInto(*out) + } if in.Pod != nil { in, out := &in.Pod, &out.Pod *out = new(pod.Pod) @@ -465,6 +470,32 @@ func (in *ArangoMLExtensionSpecDeploymentService) DeepCopy() *ArangoMLExtensionS return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArangoMLExtensionSpecDeploymentTLS) DeepCopyInto(out *ArangoMLExtensionSpecDeploymentTLS) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } + if in.AltNames != nil { + in, out := &in.AltNames, &out.AltNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoMLExtensionSpecDeploymentTLS. +func (in *ArangoMLExtensionSpecDeploymentTLS) DeepCopy() *ArangoMLExtensionSpecDeploymentTLS { + if in == nil { + return nil + } + out := new(ArangoMLExtensionSpecDeploymentTLS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArangoMLExtensionSpecMetadataService) DeepCopyInto(out *ArangoMLExtensionSpecMetadataService) { *out = *in @@ -563,6 +594,11 @@ func (in *ArangoMLExtensionStatusArangoDBRef) DeepCopyInto(out *ArangoMLExtensio *out = new(sharedv1.Object) (*in).DeepCopyInto(*out) } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(sharedv1.Object) + (*in).DeepCopyInto(*out) + } if in.JWTTokenSecret != nil { in, out := &in.JWTTokenSecret, &out.JWTTokenSecret *out = new(sharedv1.Object) diff --git a/pkg/apis/shared/v1/object.go b/pkg/apis/shared/v1/object.go index fd1ec59a1..e9cc5adf0 100644 --- a/pkg/apis/shared/v1/object.go +++ b/pkg/apis/shared/v1/object.go @@ -94,6 +94,20 @@ func (o *Object) GetUID() types.UID { return "" } +func (o *Object) AsUIDPrecondition() *meta.Preconditions { + if o == nil || o.UID == nil { + return nil + } + + uid := o.GetUID() + + if uid == "" { + return nil + } + + return meta.NewUIDPreconditions(string(uid)) +} + func (o *Object) GetChecksum() string { if o != nil { if n := o.Checksum; n != nil { diff --git a/pkg/crd/crds/ml-extension.schema.generated.yaml b/pkg/crd/crds/ml-extension.schema.generated.yaml index 35ea28a4a..2b04e092a 100644 --- a/pkg/crd/crds/ml-extension.schema.generated.yaml +++ b/pkg/crd/crds/ml-extension.schema.generated.yaml @@ -1441,6 +1441,18 @@ v1alpha1: type: string shareProcessNamespace: type: boolean + tls: + description: TLS defined TLS Settings for extension + properties: + altNames: + description: AltNames define TLS AltNames used when TLS on the ArangoDB is enabled + items: + type: string + type: array + enabled: + description: Enabled define if TLS Should be enabled. If is not set then default is taken from ArangoDeployment settings + type: boolean + type: object tolerations: items: properties: diff --git a/pkg/deployment/resources/certificates_tls.go b/pkg/deployment/resources/certificates_tls.go index 0465e53e1..d7ad24b4d 100644 --- a/pkg/deployment/resources/certificates_tls.go +++ b/pkg/deployment/resources/certificates_tls.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. @@ -113,7 +113,8 @@ func createTLSServerCertificate(ctx context.Context, log logging.Logger, cachedS strings.TrimSpace(priv) err = globals.GetGlobalTimeouts().Kubernetes().RunWithTimeout(ctx, func(ctxChild context.Context) error { - return k8sutil.CreateTLSKeyfileSecret(ctxChild, secrets, secretName, keyfile, ownerRef) + _, err := k8sutil.CreateTLSKeyfileSecret(ctxChild, secrets, secretName, keyfile, ownerRef) + return err }) if err != nil { if kerrors.IsAlreadyExists(err) { diff --git a/pkg/util/context.go b/pkg/util/context.go index 380d1dca4..16bb027cf 100644 --- a/pkg/util/context.go +++ b/pkg/util/context.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// Copyright 2023-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. @@ -64,6 +64,39 @@ func WithContextTimeoutP2A2[P1, P2, A1, A2 interface{}](ctx context.Context, tim return f(nCtx, a1, a2) } +func WithKubernetesContextTimeoutP1A4[P1, A1, A2, A3, A4 interface{}](ctx context.Context, f func(context.Context, A1, A2, A3, A4) P1, a1 A1, a2 A2, a3 A3, a4 A4) P1 { + return WithContextTimeoutP1A4(ctx, globals.GetGlobals().Timeouts().Kubernetes().Get(), f, a1, a2, a3, a4) +} + +func WithContextTimeoutP1A4[P1, A1, A2, A3, A4 interface{}](ctx context.Context, timeout time.Duration, f func(context.Context, A1, A2, A3, A4) P1, a1 A1, a2 A2, a3 A3, a4 A4) P1 { + nCtx, c := context.WithTimeout(ctx, timeout) + defer c() + + return f(nCtx, a1, a2, a3, a4) +} + +func WithKubernetesContextTimeoutP2A4[P1, P2, A1, A2, A3, A4 interface{}](ctx context.Context, f func(context.Context, A1, A2, A3, A4) (P1, P2), a1 A1, a2 A2, a3 A3, a4 A4) (P1, P2) { + return WithContextTimeoutP2A4(ctx, globals.GetGlobals().Timeouts().Kubernetes().Get(), f, a1, a2, a3, a4) +} + +func WithContextTimeoutP2A4[P1, P2, A1, A2, A3, A4 interface{}](ctx context.Context, timeout time.Duration, f func(context.Context, A1, A2, A3, A4) (P1, P2), a1 A1, a2 A2, a3 A3, a4 A4) (P1, P2) { + nCtx, c := context.WithTimeout(ctx, timeout) + defer c() + + return f(nCtx, a1, a2, a3, a4) +} + +func WithKubernetesContextTimeoutP4A3[P1, P2, P3, P4, A1, A2, A3 interface{}](ctx context.Context, f func(context.Context, A1, A2, A3) (P1, P2, P3, P4), a1 A1, a2 A2, a3 A3) (P1, P2, P3, P4) { + return WithContextTimeoutP4A3(ctx, globals.GetGlobals().Timeouts().Kubernetes().Get(), f, a1, a2, a3) +} + +func WithContextTimeoutP4A3[P1, P2, P3, P4, A1, A2, A3 interface{}](ctx context.Context, timeout time.Duration, f func(context.Context, A1, A2, A3) (P1, P2, P3, P4), a1 A1, a2 A2, a3 A3) (P1, P2, P3, P4) { + nCtx, c := context.WithTimeout(ctx, timeout) + defer c() + + return f(nCtx, a1, a2, a3) +} + type PatchInterface[P1 meta.Object] interface { Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts meta.PatchOptions, subresources ...string) (P1, error) } diff --git a/pkg/util/k8sutil/kerrors/errors.go b/pkg/util/k8sutil/kerrors/errors.go index d599dc92a..98b76fe43 100644 --- a/pkg/util/k8sutil/kerrors/errors.go +++ b/pkg/util/k8sutil/kerrors/errors.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. @@ -26,6 +26,47 @@ import ( "github.com/arangodb/kube-arangodb/pkg/util/errors" ) +func Is(err error, codes ...KErrors) bool { + if err == nil { + return false + } + + for _, code := range codes { + if code.Is(err) { + return true + } + } + + return false +} + +type KErrors int + +const ( + AlreadyExists KErrors = iota + Conflict + Invalid + NotFound + Forbidden +) + +func (k KErrors) Is(err error) bool { + switch k { + case AlreadyExists: + return IsAlreadyExists(err) + case Conflict: + return IsConflict(err) + case Invalid: + return IsInvalid(err) + case NotFound: + return IsNotFound(err) + case Forbidden: + return IsForbidden(err) + default: + return false + } +} + func isError(err error, precondition func(err error) bool) bool { if err == nil { return false @@ -62,6 +103,16 @@ func isConflictC(err error) bool { return apierrors.IsConflict(err) } +// IsForbidden returns true if the given error is or is caused by a +// kubernetes ForbiddenError, +func IsForbidden(err error) bool { + return isError(err, isConflictC) +} + +func IsForbiddenC(err error) bool { + return apierrors.IsForbidden(err) +} + // IsNotFound returns true if the given error is or is caused by a // kubernetes NotFoundError, func IsNotFound(err error) bool { diff --git a/pkg/util/k8sutil/secrets.go b/pkg/util/k8sutil/secrets.go index 6dfb68146..a17a5fa74 100644 --- a/pkg/util/k8sutil/secrets.go +++ b/pkg/util/k8sutil/secrets.go @@ -216,7 +216,7 @@ func GetTLSKeyfileFromSecret(s *core.Secret) (string, error) { // CreateTLSKeyfileSecret creates a secret used to store a PEM encoded keyfile // in the format ArangoDB accepts it for its `--ssl.keyfile` option. func CreateTLSKeyfileSecret(ctx context.Context, secrets secretv1.ModInterface, secretName string, keyfile string, - ownerRef *meta.OwnerReference) error { + ownerRef *meta.OwnerReference) (*core.Secret, error) { // Create secret secret := &core.Secret{ ObjectMeta: meta.ObjectMeta{ @@ -228,11 +228,12 @@ func CreateTLSKeyfileSecret(ctx context.Context, secrets secretv1.ModInterface, } // Attach secret to owner AddOwnerRefToObject(secret, ownerRef) - if _, err := secrets.Create(ctx, secret, meta.CreateOptions{}); err != nil { + if s, err := secrets.Create(ctx, secret, meta.CreateOptions{}); err != nil { // Failed to create secret - return kerrors.NewResourceError(err, secret) + return nil, kerrors.NewResourceError(err, secret) + } else { + return s, nil } - return nil } // ValidateTokenSecret checks that a secret with given name in given namespace