Skip to content

Commit

Permalink
license: refactor license check to be agnostic of input (#2659)
Browse files Browse the repository at this point in the history
* license: refactor license check to be agnostic of input

* license: remove unused code

* cli: only check license file in enterprise version

Signed-off-by: Moritz Sanft <[email protected]>

* bazel: fix enterprise CLI build

* bazel: add keep directive

* Update internal/constellation/apply.go

Co-authored-by: Daniel Weiße <[email protected]>

* license: check for return value

---------

Signed-off-by: Moritz Sanft <[email protected]>
Co-authored-by: Daniel Weiße <[email protected]>
  • Loading branch information
msanft and daniel-weisse authored Dec 1, 2023
1 parent 381c546 commit 4d6a7fa
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 124 deletions.
6 changes: 5 additions & 1 deletion cli/internal/cmd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ go_library(
"iamdestroy.go",
"iamupgradeapply.go",
"init.go",
# keep
"license_enterprise.go",
"license_oss.go",
"log.go",
"maapatch.go",
"mini.go",
Expand Down Expand Up @@ -68,6 +71,7 @@ go_library(
"//internal/config/instancetypes",
"//internal/config/migration",
"//internal/constants",
"//internal/constellation",
"//internal/crypto",
"//internal/featureset",
"//internal/file",
Expand All @@ -77,6 +81,7 @@ go_library(
"//internal/helm",
"//internal/kms/uri",
"//internal/kubecmd",
# keep
"//internal/license",
"//internal/logger",
"//internal/maa",
Expand Down Expand Up @@ -167,7 +172,6 @@ go_test(
"//internal/helm",
"//internal/kms/uri",
"//internal/kubecmd",
"//internal/license",
"//internal/logger",
"//internal/semver",
"//internal/state",
Expand Down
25 changes: 14 additions & 11 deletions cli/internal/cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ import (
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/constellation"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
"github.com/edgelesssys/constellation/v2/internal/helm"
"github.com/edgelesssys/constellation/v2/internal/kubecmd"
"github.com/edgelesssys/constellation/v2/internal/license"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/spf13/afero"
Expand Down Expand Up @@ -244,6 +244,8 @@ func runApply(cmd *cobra.Command, _ []string) error {
)
}

applier := constellation.NewApplier(log)

apply := &applyCmd{
fileHandler: fileHandler,
flags: flags,
Expand All @@ -254,13 +256,14 @@ func runApply(cmd *cobra.Command, _ []string) error {
newDialer: newDialer,
newKubeUpgrader: newKubeUpgrader,
newInfraApplier: newInfraApplier,
applier: applier,
}

ctx, cancel := context.WithTimeout(cmd.Context(), time.Hour)
defer cancel()
cmd.SetContext(ctx)

return apply.apply(cmd, attestationconfigapi.NewFetcher(), license.NewClient(), upgradeDir)
return apply.apply(cmd, attestationconfigapi.NewFetcher(), upgradeDir)
}

type applyCmd struct {
Expand All @@ -272,12 +275,18 @@ type applyCmd struct {

merger configMerger

applier applier

newHelmClient func(kubeConfigPath string, log debugLog) (helmApplier, error)
newDialer func(validator atls.Validator) *dialer.Dialer
newKubeUpgrader func(out io.Writer, kubeConfigPath string, log debugLog) (kubernetesUpgrader, error)
newInfraApplier func(context.Context) (cloudApplier, func(), error)
}

type applier interface {
CheckLicense(ctx context.Context, csp cloudprovider.Provider, licenseID string) (int, error)
}

/*
apply updates a Constellation cluster by applying a user's config.
The control flow is as follows:
Expand Down Expand Up @@ -339,16 +348,15 @@ The control flow is as follows:
│ ───┐
┌─────────────▼────────────┐ │
Can be skipped │Upgrade NodeVersion object│ │K8s/Image
if we ran Init RP │ (Image and K8s update) │ │Phase
if we ran Init RPC │ (Image and K8s update) │ │Phase
└─────────────┬────────────┘ │
│ ───┘
┌─────────▼──────────┐
│Write success output│
└────────────────────┘
*/
func (a *applyCmd) apply(
cmd *cobra.Command, configFetcher attestationconfigapi.Fetcher,
quotaChecker license.QuotaChecker, upgradeDir string,
cmd *cobra.Command, configFetcher attestationconfigapi.Fetcher, upgradeDir string,
) error {
// Validate inputs
conf, stateFile, err := a.validateInputs(cmd, configFetcher)
Expand All @@ -357,12 +365,7 @@ func (a *applyCmd) apply(
}

// Check license
a.log.Debugf("Running license check")
checker := license.NewChecker(quotaChecker, a.fileHandler)
if err := checker.CheckLicense(cmd.Context(), conf.GetProvider(), conf.Provider, cmd.Printf); err != nil {
cmd.PrintErrf("License check failed: %s", err)
}
a.log.Debugf("Checked license")
a.checkLicenseFile(cmd, conf.GetProvider())

// Now start actually running the apply command

Expand Down
6 changes: 6 additions & 0 deletions cli/internal/cmd/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,3 +487,9 @@ func newPhases(phases ...skipPhase) skipPhases {
skipPhases.add(phases...)
return skipPhases
}

type stubConstellApplier struct{}

func (s *stubConstellApplier) CheckLicense(context.Context, cloudprovider.Provider, string) (int, error) {
return 0, nil
}
4 changes: 3 additions & 1 deletion cli/internal/cmd/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,11 @@ func TestCreate(t *testing.T) {
newInfraApplier: func(_ context.Context) (cloudApplier, func(), error) {
return tc.creator, func() {}, tc.getCreatorErr
},

applier: &stubConstellApplier{},
}

err := a.apply(cmd, stubAttestationFetcher{}, &stubLicenseClient{}, "create")
err := a.apply(cmd, stubAttestationFetcher{}, "create")

if tc.wantErr {
assert.Error(err)
Expand Down
12 changes: 2 additions & 10 deletions cli/internal/cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/grpc/testdialer"
"github.com/edgelesssys/constellation/v2/internal/helm"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/license"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/state"
Expand Down Expand Up @@ -280,9 +279,10 @@ func TestInitialize(t *testing.T) {
getClusterAttestationConfigErr: k8serrors.NewNotFound(schema.GroupResource{}, ""),
}, nil
},
applier: &stubConstellApplier{},
}

err := i.apply(cmd, stubAttestationFetcher{}, &stubLicenseClient{}, "test")
err := i.apply(cmd, stubAttestationFetcher{}, "test")

if tc.wantErr {
assert.Error(err)
Expand Down Expand Up @@ -782,14 +782,6 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
return conf
}

type stubLicenseClient struct{}

func (c *stubLicenseClient) QuotaCheck(_ context.Context, _ license.QuotaCheckRequest) (license.QuotaCheckResponse, error) {
return license.QuotaCheckResponse{
Quota: 25,
}, nil
}

type stubInitClient struct {
res io.Reader
err error
Expand Down
56 changes: 56 additions & 0 deletions cli/internal/cmd/license_enterprise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//go:build enterprise

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/

package cmd

import (
"errors"
"io/fs"

"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/license"
"github.com/spf13/cobra"
)

// checkLicenseFile reads the local license file and checks it's quota
// with the license server. If no license file is present or if errors
// occur during the check, the user is informed and the community license
// is used. It is a no-op in the open source version of Constellation.
func (a *applyCmd) checkLicenseFile(cmd *cobra.Command, csp cloudprovider.Provider) {
var licenseID string
a.log.Debugf("Running license check")

readBytes, err := a.fileHandler.Read(constants.LicenseFilename)
if errors.Is(err, fs.ErrNotExist) {
cmd.Printf("Using community license.\n")
licenseID = license.CommunityLicense
} else if err != nil {
cmd.Printf("Error: %v\nContinuing with community license.\n", err)
licenseID = license.CommunityLicense
} else {
cmd.Printf("Constellation license found!\n")
licenseID, err = license.FromBytes(readBytes)
if err != nil {
cmd.Printf("Error: %v\nContinuing with community license.\n", err)
licenseID = license.CommunityLicense
}
}

quota, err := a.applier.CheckLicense(cmd.Context(), csp, licenseID)
if err != nil {
cmd.Printf("Unable to contact license server.\n")
cmd.Printf("Please keep your vCPU quota in mind.\n")
} else if licenseID == license.CommunityLicense {
cmd.Printf("For details, see https://docs.edgeless.systems/constellation/overview/license\n")
} else {
cmd.Printf("Please keep your vCPU quota (%d) in mind.\n", quota)
}

a.log.Debugf("Checked license")
}
20 changes: 20 additions & 0 deletions cli/internal/cmd/license_oss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build !enterprise

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/

package cmd

import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/spf13/cobra"
)

// checkLicenseFile reads the local license file and checks it's quota
// with the license server. If no license file is present or if errors
// occur during the check, the user is informed and the community license
// is used. It is a no-op in the open source version of Constellation.
func (a *applyCmd) checkLicenseFile(*cobra.Command, cloudprovider.Provider) {}
3 changes: 2 additions & 1 deletion cli/internal/cmd/upgradeapply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,9 @@ func TestUpgradeApply(t *testing.T) {
newInfraApplier: func(ctx context.Context) (cloudApplier, func(), error) {
return tc.terraformUpgrader, func() {}, nil
},
applier: &stubConstellApplier{},
}
err := upgrader.apply(cmd, stubAttestationFetcher{}, &stubLicenseClient{}, "test")
err := upgrader.apply(cmd, stubAttestationFetcher{}, "test")
if tc.wantErr {
assert.Error(err)
return
Expand Down
9 changes: 8 additions & 1 deletion internal/constellation/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")

go_library(
name = "constellation",
srcs = ["constellation.go"],
srcs = [
"apply.go",
"constellation.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/constellation",
visibility = ["//:__subpackages__"],
deps = [
"//internal/cloud/cloudprovider",
"//internal/license",
],
)
47 changes: 47 additions & 0 deletions internal/constellation/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/

package constellation

import (
"context"
"fmt"

"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/license"
)

// An Applier handles applying a specific configuration to a Constellation cluster.
// In Particular, this involves Initialization and Upgrading of the cluster.
type Applier struct {
log debugLog
licenseChecker *license.Checker
}

type debugLog interface {
Debugf(format string, args ...any)
}

// NewApplier creates a new Applier.
func NewApplier(log debugLog) *Applier {
return &Applier{
log: log,
licenseChecker: license.NewChecker(license.NewClient()),
}
}

// CheckLicense checks the given Constellation license with the license server
// and returns the allowed quota for the license.
func (a *Applier) CheckLicense(ctx context.Context, csp cloudprovider.Provider, licenseID string) (int, error) {
a.log.Debugf("Contacting license server for license '%s'", licenseID)
quotaResp, err := a.licenseChecker.CheckLicense(ctx, csp, licenseID)
if err != nil {
return 0, fmt.Errorf("checking license: %w", err)
}
a.log.Debugf("Got response from license server for license '%s'", licenseID)

return quotaResp.Quota, nil
}
5 changes: 0 additions & 5 deletions internal/license/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ go_library(
visibility = ["//:__subpackages__"],
deps = [
"//internal/cloud/cloudprovider",
"//internal/config",
# keep
"//internal/constants",
"//internal/file",
],
)

Expand All @@ -30,9 +28,6 @@ go_test(
],
embed = [":license"],
deps = [
"//internal/constants",
"//internal/file",
"@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
],
Expand Down
Loading

0 comments on commit 4d6a7fa

Please sign in to comment.