mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
GT-525 License Manager for ML Deployment (#1501)
This commit is contained in:
parent
91e7312fcd
commit
cbf5e65e8b
9 changed files with 150 additions and 28 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -7,10 +7,13 @@ vendor/
|
|||
.idea/
|
||||
deps/
|
||||
.vscode/
|
||||
|
||||
**/*.enterprise.go
|
||||
**/*.enterprise_test.go
|
||||
**/enterprise/**
|
||||
enterprise.mk
|
||||
license-header.enterprise.txt
|
||||
|
||||
local/
|
||||
kustomize_test/
|
||||
tools/codegen/boilerplate.go.txt
|
|
@ -22,6 +22,7 @@
|
|||
- (Feature) (ML) Introduce basic Conditions
|
||||
- (Improvement) Raise memory requests for init containers to 50mi
|
||||
- (Feature) (ML) Metadata Service Implementation
|
||||
- (Feature) License Manager for ML Deployment
|
||||
|
||||
## [1.2.35](https://github.com/arangodb/kube-arangodb/tree/1.2.35) (2023-11-06)
|
||||
- (Maintenance) Update go-driver to v1.6.0, update IsNotFound() checks
|
||||
|
|
|
@ -23,7 +23,7 @@ package v1alpha1
|
|||
import api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||
|
||||
const (
|
||||
ExtensionDeploymentFoundCondition api.ConditionType = "DeploymentFound"
|
||||
|
||||
ExtensionDeploymentFoundCondition api.ConditionType = "DeploymentFound"
|
||||
ExtensionMetadataServiceValidCondition api.ConditionType = "MetadataServiceValid"
|
||||
LicenseValidCondition api.ConditionType = "LicenseValid"
|
||||
)
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
// limitations under the License.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
|
||||
//go:build !enterprise
|
||||
|
||||
package license
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ const (
|
|||
// NotLicensed
|
||||
StatusFeatureExpired
|
||||
|
||||
// StatusValid define state when Operator should continue execution
|
||||
// StatusValid define state when Operator should continue execution
|
||||
// Licensed
|
||||
StatusValid
|
||||
)
|
||||
|
@ -70,6 +70,29 @@ func (s Status) Validate(feature Feature, subFeatures ...Feature) Status {
|
|||
|
||||
type Feature string
|
||||
|
||||
const (
|
||||
// FeatureAll define feature name for all features
|
||||
FeatureAll Feature = "*"
|
||||
|
||||
// FeatureArangoDB define feature name for ArangoDB
|
||||
FeatureArangoDB Feature = "ArangoDB"
|
||||
|
||||
// FeatureArangoSearch define feature name for ArangoSearch
|
||||
FeatureArangoSearch Feature = "ArangoSearch"
|
||||
|
||||
// FeatureArangoML define feature name for ArangoML
|
||||
FeatureArangoML Feature = "ArangoML"
|
||||
)
|
||||
|
||||
func (f Feature) In(features []Feature) bool {
|
||||
for _, v := range features {
|
||||
if v == f {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type License interface {
|
||||
// Validate validates the license scope. In case of:
|
||||
// - if feature is '*' - checks if:
|
||||
|
@ -84,5 +107,6 @@ type License interface {
|
|||
// --- checks if subFeature or '*' is in the list of License Feature enabled SubFeatures
|
||||
Validate(feature Feature, subFeatures ...Feature) Status
|
||||
|
||||
// Refresh refreshes the license from the source (Secret) and verifies the signature
|
||||
Refresh(ctx context.Context) error
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ package license
|
|||
import "context"
|
||||
|
||||
type Loader interface {
|
||||
// Refresh reloads license in specified manner, returns license, found, error
|
||||
// Refresh reloads license in a specified manner.
|
||||
// It returns license (base64 encoded), found, error
|
||||
Refresh(ctx context.Context) (string, bool, error)
|
||||
}
|
||||
|
|
|
@ -26,48 +26,34 @@ import (
|
|||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/constants"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
|
||||
)
|
||||
|
||||
func NewArengoDeploymentLicenseLoader(factory kclient.Factory, namespace, name string) Loader {
|
||||
func NewArangoDeploymentLicenseLoader(client kubernetes.Interface, deployment *api.ArangoDeployment) Loader {
|
||||
return arangoDeploymentLicenseLoader{
|
||||
factory: factory,
|
||||
namespace: namespace,
|
||||
name: name,
|
||||
client: client,
|
||||
deployment: deployment,
|
||||
}
|
||||
}
|
||||
|
||||
type arangoDeploymentLicenseLoader struct {
|
||||
factory kclient.Factory
|
||||
client kubernetes.Interface
|
||||
|
||||
namespace, name string
|
||||
deployment *api.ArangoDeployment
|
||||
}
|
||||
|
||||
func (a arangoDeploymentLicenseLoader) Refresh(ctx context.Context) (string, bool, error) {
|
||||
client, ok := a.factory.Client()
|
||||
if !ok {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
deployment, err := client.Arango().DatabaseV1().ArangoDeployments(a.namespace).Get(ctx, a.name, meta.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
spec := deployment.GetAcceptedSpec()
|
||||
spec := a.deployment.GetAcceptedSpec()
|
||||
|
||||
if !spec.License.HasSecretName() {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
secret, err := client.Kubernetes().CoreV1().Secrets(deployment.GetNamespace()).Get(ctx, spec.License.GetSecretName(), meta.GetOptions{})
|
||||
secret, err := a.client.CoreV1().Secrets(a.deployment.GetNamespace()).Get(ctx, spec.License.GetSecretName(), meta.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return "", false, nil
|
||||
|
|
35
pkg/license/loader_test.go
Normal file
35
pkg/license/loader_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2023 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 license
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockLoader struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockLoader) Refresh(ctx context.Context) (string, bool, error) {
|
||||
args := m.Called(ctx)
|
||||
return args.String(0), args.Bool(1), args.Error(2)
|
||||
}
|
71
pkg/util/cert/signer.go
Normal file
71
pkg/util/cert/signer.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2023 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 cert
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
)
|
||||
|
||||
type Signer struct {
|
||||
privateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
// NewSigner creates a new Signer with a generated private key.
|
||||
func NewSigner() (*Signer, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Signer{privateKey: privateKey}, nil
|
||||
}
|
||||
|
||||
// Sign signs the content with the private key and returns:
|
||||
// base64 encoded signature, base64 encoded content and error.
|
||||
func (s *Signer) Sign(content string) (string, string, error) {
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(content))
|
||||
signature, err := rsa.SignPKCS1v15(rand.Reader, s.privateKey, crypto.SHA256, hash.Sum(nil))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(signature), base64.StdEncoding.EncodeToString([]byte(content)), nil
|
||||
}
|
||||
|
||||
// PublicKey returns the public key in PKIX format.
|
||||
func (s *Signer) PublicKey() (string, error) {
|
||||
publicKey := &s.privateKey.PublicKey
|
||||
publicKeyDer, err := x509.MarshalPKIXPublicKey(publicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
publicKeyBlock := pem.Block{
|
||||
Type: "RSA PUBLIC KEY",
|
||||
Bytes: publicKeyDer,
|
||||
}
|
||||
return string(pem.EncodeToMemory(&publicKeyBlock)), nil
|
||||
}
|
Loading…
Reference in a new issue