Skip to content

Commit 896fe39

Browse files
author
Simon Emms
committed
Extract the evaluator type and create Gitpod and Replicated licensors
1 parent 2971061 commit 896fe39

File tree

6 files changed

+441
-137
lines changed

6 files changed

+441
-137
lines changed

components/licensor/ee/cmd/validate.go

+20-9
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,28 @@ var validateCmd = &cobra.Command{
2222
Short: "Validates a license - reads from stdin if no argument is provided",
2323
Args: cobra.MaximumNArgs(1),
2424
RunE: func(cmd *cobra.Command, args []string) (err error) {
25-
var lic []byte
26-
if len(args) == 0 {
27-
lic, err = io.ReadAll(os.Stdin)
28-
if err != nil {
29-
return err
25+
domain, _ := cmd.Flags().GetString("domain")
26+
licensorType, _ := cmd.Flags().GetString("licensor")
27+
28+
var e licensor.Evaluator
29+
switch licensorType {
30+
case string(licensor.LicenseTypeReplicated):
31+
e = licensor.NewReplicatedEvaluator(domain)
32+
break
33+
default:
34+
var lic []byte
35+
if len(args) == 0 {
36+
lic, err = io.ReadAll(os.Stdin)
37+
if err != nil {
38+
return err
39+
}
40+
} else {
41+
lic = []byte(args[0])
3042
}
31-
} else {
32-
lic = []byte(args[0])
43+
44+
e = licensor.NewGitpodEvaluator(lic, domain)
3345
}
3446

35-
domain, _ := cmd.Flags().GetString("domain")
36-
e := licensor.NewEvaluator(lic, domain)
3747
if msg, valid := e.Validate(); !valid {
3848
return xerrors.Errorf(msg)
3949
}
@@ -47,4 +57,5 @@ var validateCmd = &cobra.Command{
4757
func init() {
4858
rootCmd.AddCommand(validateCmd)
4959
validateCmd.Flags().String("domain", "", "domain to evaluate the license against")
60+
validateCmd.Flags().String("licensor", "gitpod", "licensor to use")
5061
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the Gitpod Enterprise Source Code License,
3+
// See License.enterprise.txt in the project root folder.
4+
5+
package licensor
6+
7+
import (
8+
"crypto"
9+
"crypto/rsa"
10+
"crypto/sha256"
11+
"encoding/base64"
12+
"encoding/json"
13+
"fmt"
14+
"time"
15+
)
16+
17+
// GitpodEvaluator determines what a license allows for
18+
type GitpodEvaluator struct {
19+
invalid string
20+
lic LicensePayload
21+
}
22+
23+
// Validate returns false if the license isn't valid and a message explaining why that is.
24+
func (e *GitpodEvaluator) Validate() (msg string, valid bool) {
25+
if e.invalid == "" {
26+
return "", true
27+
}
28+
29+
return e.invalid, false
30+
}
31+
32+
// Enabled determines if a feature is enabled by the license
33+
func (e *GitpodEvaluator) Enabled(feature Feature) bool {
34+
if e.invalid != "" {
35+
return false
36+
}
37+
38+
_, ok := e.lic.Level.allowance().Features[feature]
39+
return ok
40+
}
41+
42+
// HasEnoughSeats returns true if the license supports at least the give amount of seats
43+
func (e *GitpodEvaluator) HasEnoughSeats(seats int) bool {
44+
if e.invalid != "" {
45+
return false
46+
}
47+
48+
return e.lic.Seats == 0 || seats <= e.lic.Seats
49+
}
50+
51+
// Inspect returns the license information this evaluator holds.
52+
// This function is intended for transparency/debugging purposes only and must
53+
// never be used to determine feature eligibility under a license. All code making
54+
// those kinds of decisions must be part of the Evaluator.
55+
func (e *GitpodEvaluator) Inspect() LicensePayload {
56+
return e.lic
57+
}
58+
59+
// NewGitpodEvaluator produces a new license evaluator from a license key
60+
func NewGitpodEvaluator(key []byte, domain string) (res *GitpodEvaluator) {
61+
if len(key) == 0 {
62+
// fallback to the default license
63+
return &GitpodEvaluator{
64+
lic: defaultLicense,
65+
}
66+
}
67+
68+
deckey := make([]byte, base64.StdEncoding.DecodedLen(len(key)))
69+
n, err := base64.StdEncoding.Decode(deckey, key)
70+
if err != nil {
71+
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot decode key: %q", err)}
72+
}
73+
deckey = deckey[:n]
74+
75+
var lic licensePayload
76+
err = json.Unmarshal(deckey, &lic)
77+
if err != nil {
78+
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot unmarshal key: %q", err)}
79+
}
80+
81+
keyWoSig, err := json.Marshal(lic.LicensePayload)
82+
if err != nil {
83+
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot remarshal key: %q", err)}
84+
}
85+
hashed := sha256.Sum256(keyWoSig)
86+
87+
for _, k := range publicKeys {
88+
err = rsa.VerifyPKCS1v15(k, crypto.SHA256, hashed[:], lic.Signature)
89+
if err == nil {
90+
break
91+
}
92+
}
93+
if err != nil {
94+
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot verify key: %q", err)}
95+
}
96+
97+
if !matchesDomain(lic.Domain, domain) {
98+
return &GitpodEvaluator{invalid: "wrong domain"}
99+
}
100+
101+
if lic.ValidUntil.Before(time.Now()) {
102+
return &GitpodEvaluator{invalid: "not valid anymore"}
103+
}
104+
105+
return &GitpodEvaluator{
106+
lic: lic.LicensePayload,
107+
}
108+
}

components/licensor/ee/pkg/licensor/licensor.go

+12-91
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ import (
1717
"time"
1818
)
1919

20+
type LicenseType string
21+
22+
const (
23+
LicenseTypeGitpod LicenseType = "gitpod"
24+
LicenseTypeReplicated LicenseType = "replicated"
25+
)
26+
2027
// LicensePayload is the actual license content
2128
type LicensePayload struct {
2229
ID string `json:"id"`
@@ -115,57 +122,6 @@ var defaultLicense = LicensePayload{
115122
// Domain, ValidUntil are free for all
116123
}
117124

118-
// NewEvaluator produces a new license evaluator from a license key
119-
func NewEvaluator(key []byte, domain string) (res *Evaluator) {
120-
if len(key) == 0 {
121-
// fallback to the default license
122-
return &Evaluator{
123-
lic: defaultLicense,
124-
}
125-
}
126-
127-
deckey := make([]byte, base64.StdEncoding.DecodedLen(len(key)))
128-
n, err := base64.StdEncoding.Decode(deckey, key)
129-
if err != nil {
130-
return &Evaluator{invalid: fmt.Sprintf("cannot decode key: %q", err)}
131-
}
132-
deckey = deckey[:n]
133-
134-
var lic licensePayload
135-
err = json.Unmarshal(deckey, &lic)
136-
if err != nil {
137-
return &Evaluator{invalid: fmt.Sprintf("cannot unmarshal key: %q", err)}
138-
}
139-
140-
keyWoSig, err := json.Marshal(lic.LicensePayload)
141-
if err != nil {
142-
return &Evaluator{invalid: fmt.Sprintf("cannot remarshal key: %q", err)}
143-
}
144-
hashed := sha256.Sum256(keyWoSig)
145-
146-
for _, k := range publicKeys {
147-
err = rsa.VerifyPKCS1v15(k, crypto.SHA256, hashed[:], lic.Signature)
148-
if err == nil {
149-
break
150-
}
151-
}
152-
if err != nil {
153-
return &Evaluator{invalid: fmt.Sprintf("cannot verify key: %q", err)}
154-
}
155-
156-
if !matchesDomain(lic.Domain, domain) {
157-
return &Evaluator{invalid: "wrong domain"}
158-
}
159-
160-
if lic.ValidUntil.Before(time.Now()) {
161-
return &Evaluator{invalid: "not valid anymore"}
162-
}
163-
164-
return &Evaluator{
165-
lic: lic.LicensePayload,
166-
}
167-
}
168-
169125
func matchesDomain(pattern, domain string) bool {
170126
if pattern == "" {
171127
return true
@@ -184,46 +140,11 @@ func matchesDomain(pattern, domain string) bool {
184140
return false
185141
}
186142

187-
// Evaluator determines what a license allows for
188-
type Evaluator struct {
189-
invalid string
190-
lic LicensePayload
191-
}
192-
193-
// Validate returns false if the license isn't valid and a message explaining why that is.
194-
func (e *Evaluator) Validate() (msg string, valid bool) {
195-
if e.invalid == "" {
196-
return "", true
197-
}
198-
199-
return e.invalid, false
200-
}
201-
202-
// Enabled determines if a feature is enabled by the license
203-
func (e *Evaluator) Enabled(feature Feature) bool {
204-
if e.invalid != "" {
205-
return false
206-
}
207-
208-
_, ok := e.lic.Level.allowance().Features[feature]
209-
return ok
210-
}
211-
212-
// HasEnoughSeats returns true if the license supports at least the give amount of seats
213-
func (e *Evaluator) HasEnoughSeats(seats int) bool {
214-
if e.invalid != "" {
215-
return false
216-
}
217-
218-
return e.lic.Seats == 0 || seats <= e.lic.Seats
219-
}
220-
221-
// Inspect returns the license information this evaluator holds.
222-
// This function is intended for transparency/debugging purposes only and must
223-
// never be used to determine feature eligibility under a license. All code making
224-
// those kinds of decisions must be part of the Evaluator.
225-
func (e *Evaluator) Inspect() LicensePayload {
226-
return e.lic
143+
type Evaluator interface {
144+
Enabled(feature Feature) bool
145+
HasEnoughSeats(seats int) bool
146+
Inspect() LicensePayload
147+
Validate() (msg string, valid bool)
227148
}
228149

229150
// Sign signs a license so that it can be used with the evaluator

0 commit comments

Comments
 (0)