Skip to content

Commit cbf5e65

Browse files
authored
GT-525 License Manager for ML Deployment (#1501)
1 parent 91e7312 commit cbf5e65

File tree

9 files changed

+150
-28
lines changed

9 files changed

+150
-28
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ vendor/
77
.idea/
88
deps/
99
.vscode/
10+
1011
**/*.enterprise.go
12+
**/*.enterprise_test.go
1113
**/enterprise/**
1214
enterprise.mk
1315
license-header.enterprise.txt
16+
1417
local/
1518
kustomize_test/
1619
tools/codegen/boilerplate.go.txt

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- (Feature) (ML) Introduce basic Conditions
2323
- (Improvement) Raise memory requests for init containers to 50mi
2424
- (Feature) (ML) Metadata Service Implementation
25+
- (Feature) License Manager for ML Deployment
2526

2627
## [1.2.35](https://github.com/arangodb/kube-arangodb/tree/1.2.35) (2023-11-06)
2728
- (Maintenance) Update go-driver to v1.6.0, update IsNotFound() checks

pkg/apis/ml/v1alpha1/extension_conditions.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ package v1alpha1
2323
import api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
2424

2525
const (
26-
ExtensionDeploymentFoundCondition api.ConditionType = "DeploymentFound"
27-
26+
ExtensionDeploymentFoundCondition api.ConditionType = "DeploymentFound"
2827
ExtensionMetadataServiceValidCondition api.ConditionType = "MetadataServiceValid"
28+
LicenseValidCondition api.ConditionType = "LicenseValid"
2929
)

pkg/license/license.community.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
// limitations under the License.
1717
//
1818
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19-
//
19+
20+
//go:build !enterprise
2021

2122
package license
2223

pkg/license/license.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const (
5555
// NotLicensed
5656
StatusFeatureExpired
5757

58-
// StatusValid define state when Operator should continue execution
58+
// StatusValid define state when Operator should continue execution
5959
// Licensed
6060
StatusValid
6161
)
@@ -70,6 +70,29 @@ func (s Status) Validate(feature Feature, subFeatures ...Feature) Status {
7070

7171
type Feature string
7272

73+
const (
74+
// FeatureAll define feature name for all features
75+
FeatureAll Feature = "*"
76+
77+
// FeatureArangoDB define feature name for ArangoDB
78+
FeatureArangoDB Feature = "ArangoDB"
79+
80+
// FeatureArangoSearch define feature name for ArangoSearch
81+
FeatureArangoSearch Feature = "ArangoSearch"
82+
83+
// FeatureArangoML define feature name for ArangoML
84+
FeatureArangoML Feature = "ArangoML"
85+
)
86+
87+
func (f Feature) In(features []Feature) bool {
88+
for _, v := range features {
89+
if v == f {
90+
return true
91+
}
92+
}
93+
return false
94+
}
95+
7396
type License interface {
7497
// Validate validates the license scope. In case of:
7598
// - if feature is '*' - checks if:
@@ -84,5 +107,6 @@ type License interface {
84107
// --- checks if subFeature or '*' is in the list of License Feature enabled SubFeatures
85108
Validate(feature Feature, subFeatures ...Feature) Status
86109

110+
// Refresh refreshes the license from the source (Secret) and verifies the signature
87111
Refresh(ctx context.Context) error
88112
}

pkg/license/loader.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ package license
2323
import "context"
2424

2525
type Loader interface {
26-
// Refresh reloads license in specified manner, returns license, found, error
26+
// Refresh reloads license in a specified manner.
27+
// It returns license (base64 encoded), found, error
2728
Refresh(ctx context.Context) (string, bool, error)
2829
}

pkg/license/loader_arangodeployment.go

+9-23
Original file line numberDiff line numberDiff line change
@@ -26,48 +26,34 @@ import (
2626

2727
"k8s.io/apimachinery/pkg/api/errors"
2828
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/client-go/kubernetes"
2930

31+
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
3032
"github.com/arangodb/kube-arangodb/pkg/util/constants"
3133
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
32-
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
3334
)
3435

35-
func NewArengoDeploymentLicenseLoader(factory kclient.Factory, namespace, name string) Loader {
36+
func NewArangoDeploymentLicenseLoader(client kubernetes.Interface, deployment *api.ArangoDeployment) Loader {
3637
return arangoDeploymentLicenseLoader{
37-
factory: factory,
38-
namespace: namespace,
39-
name: name,
38+
client: client,
39+
deployment: deployment,
4040
}
4141
}
4242

4343
type arangoDeploymentLicenseLoader struct {
44-
factory kclient.Factory
44+
client kubernetes.Interface
4545

46-
namespace, name string
46+
deployment *api.ArangoDeployment
4747
}
4848

4949
func (a arangoDeploymentLicenseLoader) Refresh(ctx context.Context) (string, bool, error) {
50-
client, ok := a.factory.Client()
51-
if !ok {
52-
return "", false, nil
53-
}
54-
55-
deployment, err := client.Arango().DatabaseV1().ArangoDeployments(a.namespace).Get(ctx, a.name, meta.GetOptions{})
56-
if err != nil {
57-
if errors.IsNotFound(err) {
58-
return "", false, nil
59-
}
60-
61-
return "", false, err
62-
}
63-
64-
spec := deployment.GetAcceptedSpec()
50+
spec := a.deployment.GetAcceptedSpec()
6551

6652
if !spec.License.HasSecretName() {
6753
return "", false, nil
6854
}
6955

70-
secret, err := client.Kubernetes().CoreV1().Secrets(deployment.GetNamespace()).Get(ctx, spec.License.GetSecretName(), meta.GetOptions{})
56+
secret, err := a.client.CoreV1().Secrets(a.deployment.GetNamespace()).Get(ctx, spec.License.GetSecretName(), meta.GetOptions{})
7157
if err != nil {
7258
if errors.IsNotFound(err) {
7359
return "", false, nil

pkg/license/loader_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2023 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
20+
package license
21+
22+
import (
23+
"context"
24+
25+
"github.com/stretchr/testify/mock"
26+
)
27+
28+
type MockLoader struct {
29+
mock.Mock
30+
}
31+
32+
func (m *MockLoader) Refresh(ctx context.Context) (string, bool, error) {
33+
args := m.Called(ctx)
34+
return args.String(0), args.Bool(1), args.Error(2)
35+
}

pkg/util/cert/signer.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2023 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
21+
package cert
22+
23+
import (
24+
"crypto"
25+
"crypto/rand"
26+
"crypto/rsa"
27+
"crypto/sha256"
28+
"crypto/x509"
29+
"encoding/base64"
30+
"encoding/pem"
31+
)
32+
33+
type Signer struct {
34+
privateKey *rsa.PrivateKey
35+
}
36+
37+
// NewSigner creates a new Signer with a generated private key.
38+
func NewSigner() (*Signer, error) {
39+
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
40+
if err != nil {
41+
return nil, err
42+
}
43+
return &Signer{privateKey: privateKey}, nil
44+
}
45+
46+
// Sign signs the content with the private key and returns:
47+
// base64 encoded signature, base64 encoded content and error.
48+
func (s *Signer) Sign(content string) (string, string, error) {
49+
hash := sha256.New()
50+
hash.Write([]byte(content))
51+
signature, err := rsa.SignPKCS1v15(rand.Reader, s.privateKey, crypto.SHA256, hash.Sum(nil))
52+
if err != nil {
53+
return "", "", err
54+
}
55+
return base64.StdEncoding.EncodeToString(signature), base64.StdEncoding.EncodeToString([]byte(content)), nil
56+
}
57+
58+
// PublicKey returns the public key in PKIX format.
59+
func (s *Signer) PublicKey() (string, error) {
60+
publicKey := &s.privateKey.PublicKey
61+
publicKeyDer, err := x509.MarshalPKIXPublicKey(publicKey)
62+
if err != nil {
63+
return "", err
64+
}
65+
66+
publicKeyBlock := pem.Block{
67+
Type: "RSA PUBLIC KEY",
68+
Bytes: publicKeyDer,
69+
}
70+
return string(pem.EncodeToMemory(&publicKeyBlock)), nil
71+
}

0 commit comments

Comments
 (0)