Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Replicated as a Gitpod license evaluator #8211

Merged
merged 3 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions components/licensor/ee/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,28 @@ var validateCmd = &cobra.Command{
Short: "Validates a license - reads from stdin if no argument is provided",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
var lic []byte
if len(args) == 0 {
lic, err = io.ReadAll(os.Stdin)
if err != nil {
return err
domain, _ := cmd.Flags().GetString("domain")
licensorType, _ := cmd.Flags().GetString("licensor")

var e licensor.Evaluator
switch licensorType {
case string(licensor.LicenseTypeReplicated):
e = licensor.NewReplicatedEvaluator(domain)
break
default:
var lic []byte
if len(args) == 0 {
lic, err = io.ReadAll(os.Stdin)
if err != nil {
return err
}
} else {
lic = []byte(args[0])
}
} else {
lic = []byte(args[0])

e = licensor.NewGitpodEvaluator(lic, domain)
}

domain, _ := cmd.Flags().GetString("domain")
e := licensor.NewEvaluator(lic, domain)
if msg, valid := e.Validate(); !valid {
return xerrors.Errorf(msg)
}
Expand All @@ -47,4 +57,5 @@ var validateCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(validateCmd)
validateCmd.Flags().String("domain", "", "domain to evaluate the license against")
validateCmd.Flags().String("licensor", "gitpod", "licensor to use")
}
108 changes: 108 additions & 0 deletions components/licensor/ee/pkg/licensor/gitpod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the Gitpod Enterprise Source Code License,
// See License.enterprise.txt in the project root folder.

package licensor

import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"time"
)

// GitpodEvaluator determines what a license allows for
type GitpodEvaluator struct {
invalid string
lic LicensePayload
}

// Validate returns false if the license isn't valid and a message explaining why that is.
func (e *GitpodEvaluator) Validate() (msg string, valid bool) {
if e.invalid == "" {
return "", true
}

return e.invalid, false
}

// Enabled determines if a feature is enabled by the license
func (e *GitpodEvaluator) Enabled(feature Feature) bool {
if e.invalid != "" {
return false
}

_, ok := e.lic.Level.allowance().Features[feature]
return ok
}

// HasEnoughSeats returns true if the license supports at least the give amount of seats
func (e *GitpodEvaluator) HasEnoughSeats(seats int) bool {
if e.invalid != "" {
return false
}

return e.lic.Seats == 0 || seats <= e.lic.Seats
}

// Inspect returns the license information this evaluator holds.
// This function is intended for transparency/debugging purposes only and must
// never be used to determine feature eligibility under a license. All code making
// those kinds of decisions must be part of the Evaluator.
func (e *GitpodEvaluator) Inspect() LicensePayload {
return e.lic
}

// NewGitpodEvaluator produces a new license evaluator from a license key
func NewGitpodEvaluator(key []byte, domain string) (res *GitpodEvaluator) {
if len(key) == 0 {
// fallback to the default license
return &GitpodEvaluator{
lic: defaultLicense,
}
}

deckey := make([]byte, base64.StdEncoding.DecodedLen(len(key)))
n, err := base64.StdEncoding.Decode(deckey, key)
if err != nil {
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot decode key: %q", err)}
}
deckey = deckey[:n]

var lic licensePayload
err = json.Unmarshal(deckey, &lic)
if err != nil {
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot unmarshal key: %q", err)}
}

keyWoSig, err := json.Marshal(lic.LicensePayload)
if err != nil {
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot remarshal key: %q", err)}
}
hashed := sha256.Sum256(keyWoSig)

for _, k := range publicKeys {
err = rsa.VerifyPKCS1v15(k, crypto.SHA256, hashed[:], lic.Signature)
if err == nil {
break
}
}
if err != nil {
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot verify key: %q", err)}
}

if !matchesDomain(lic.Domain, domain) {
return &GitpodEvaluator{invalid: "wrong domain"}
}

if lic.ValidUntil.Before(time.Now()) {
return &GitpodEvaluator{invalid: "not valid anymore"}
}

return &GitpodEvaluator{
lic: lic.LicensePayload,
}
}
103 changes: 12 additions & 91 deletions components/licensor/ee/pkg/licensor/licensor.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ import (
"time"
)

type LicenseType string

const (
LicenseTypeGitpod LicenseType = "gitpod"
LicenseTypeReplicated LicenseType = "replicated"
)

// LicensePayload is the actual license content
type LicensePayload struct {
ID string `json:"id"`
Expand Down Expand Up @@ -115,57 +122,6 @@ var defaultLicense = LicensePayload{
// Domain, ValidUntil are free for all
}

// NewEvaluator produces a new license evaluator from a license key
func NewEvaluator(key []byte, domain string) (res *Evaluator) {
if len(key) == 0 {
// fallback to the default license
return &Evaluator{
lic: defaultLicense,
}
}

deckey := make([]byte, base64.StdEncoding.DecodedLen(len(key)))
n, err := base64.StdEncoding.Decode(deckey, key)
if err != nil {
return &Evaluator{invalid: fmt.Sprintf("cannot decode key: %q", err)}
}
deckey = deckey[:n]

var lic licensePayload
err = json.Unmarshal(deckey, &lic)
if err != nil {
return &Evaluator{invalid: fmt.Sprintf("cannot unmarshal key: %q", err)}
}

keyWoSig, err := json.Marshal(lic.LicensePayload)
if err != nil {
return &Evaluator{invalid: fmt.Sprintf("cannot remarshal key: %q", err)}
}
hashed := sha256.Sum256(keyWoSig)

for _, k := range publicKeys {
err = rsa.VerifyPKCS1v15(k, crypto.SHA256, hashed[:], lic.Signature)
if err == nil {
break
}
}
if err != nil {
return &Evaluator{invalid: fmt.Sprintf("cannot verify key: %q", err)}
}

if !matchesDomain(lic.Domain, domain) {
return &Evaluator{invalid: "wrong domain"}
}

if lic.ValidUntil.Before(time.Now()) {
return &Evaluator{invalid: "not valid anymore"}
}

return &Evaluator{
lic: lic.LicensePayload,
}
}

func matchesDomain(pattern, domain string) bool {
if pattern == "" {
return true
Expand All @@ -184,46 +140,11 @@ func matchesDomain(pattern, domain string) bool {
return false
}

// Evaluator determines what a license allows for
type Evaluator struct {
invalid string
lic LicensePayload
}

// Validate returns false if the license isn't valid and a message explaining why that is.
func (e *Evaluator) Validate() (msg string, valid bool) {
if e.invalid == "" {
return "", true
}

return e.invalid, false
}

// Enabled determines if a feature is enabled by the license
func (e *Evaluator) Enabled(feature Feature) bool {
if e.invalid != "" {
return false
}

_, ok := e.lic.Level.allowance().Features[feature]
return ok
}

// HasEnoughSeats returns true if the license supports at least the give amount of seats
func (e *Evaluator) HasEnoughSeats(seats int) bool {
if e.invalid != "" {
return false
}

return e.lic.Seats == 0 || seats <= e.lic.Seats
}

// Inspect returns the license information this evaluator holds.
// This function is intended for transparency/debugging purposes only and must
// never be used to determine feature eligibility under a license. All code making
// those kinds of decisions must be part of the Evaluator.
func (e *Evaluator) Inspect() LicensePayload {
return e.lic
type Evaluator interface {
Enabled(feature Feature) bool
HasEnoughSeats(seats int) bool
Inspect() LicensePayload
Validate() (msg string, valid bool)
}

// Sign signs a license so that it can be used with the evaluator
Expand Down
Loading