Skip to content

Commit 437f8b3

Browse files
committed
feat: add license deployment_id verification
1 parent 3d7e678 commit 437f8b3

File tree

4 files changed

+46
-15
lines changed

4 files changed

+46
-15
lines changed

enterprise/coderd/coderdenttest/coderdenttest.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,14 @@ func NewWithAPI(t *testing.T, options *Options) (
147147
}
148148

149149
type LicenseOptions struct {
150-
AccountType string
151-
AccountID string
152-
Trial bool
153-
AllFeatures bool
154-
GraceAt time.Time
155-
ExpiresAt time.Time
156-
Features license.Features
150+
AccountType string
151+
AccountID string
152+
DeploymentIDs []string
153+
Trial bool
154+
AllFeatures bool
155+
GraceAt time.Time
156+
ExpiresAt time.Time
157+
Features license.Features
157158
}
158159

159160
// AddFullLicense generates a license with all features enabled.
@@ -190,6 +191,7 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
190191
LicenseExpires: jwt.NewNumericDate(options.GraceAt),
191192
AccountType: options.AccountType,
192193
AccountID: options.AccountID,
194+
DeploymentIDs: options.DeploymentIDs,
193195
Trial: options.Trial,
194196
Version: license.CurrentVersion,
195197
AllFeatures: options.AllFeatures,

enterprise/coderd/license/license.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,16 @@ type Claims struct {
257257
// the end of the grace period (identical to LicenseExpires if there is no grace period).
258258
// The reason we use the standard claim for the end of the grace period is that we want JWT
259259
// processing libraries to consider the token "valid" until then.
260-
LicenseExpires *jwt.NumericDate `json:"license_expires,omitempty"`
261-
AccountType string `json:"account_type,omitempty"`
262-
AccountID string `json:"account_id,omitempty"`
263-
Trial bool `json:"trial"`
264-
AllFeatures bool `json:"all_features"`
265-
Version uint64 `json:"version"`
266-
Features Features `json:"features"`
267-
RequireTelemetry bool `json:"require_telemetry,omitempty"`
260+
LicenseExpires *jwt.NumericDate `json:"license_expires,omitempty"`
261+
AccountType string `json:"account_type,omitempty"`
262+
AccountID string `json:"account_id,omitempty"`
263+
// DeploymentIDs enforces the license can only be used on a set of deployments.
264+
DeploymentIDs []string `json:"deployment_ids,omitempty"`
265+
Trial bool `json:"trial"`
266+
AllFeatures bool `json:"all_features"`
267+
Version uint64 `json:"version"`
268+
Features Features `json:"features"`
269+
RequireTelemetry bool `json:"require_telemetry,omitempty"`
268270
}
269271

270272
// ParseRaw consumes a license and returns the claims.

enterprise/coderd/licenses.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"encoding/json"
1111
"fmt"
1212
"net/http"
13+
"slices"
1314
"strconv"
1415
"strings"
1516
"time"
@@ -120,6 +121,15 @@ func (api *API) postLicense(rw http.ResponseWriter, r *http.Request) {
120121
// old licenses with a uuid.
121122
id = uuid.New()
122123
}
124+
if len(claims.DeploymentIDs) > 0 && !slices.Contains(claims.DeploymentIDs, api.AGPL.DeploymentID) {
125+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
126+
Message: "License cannot be used on this deployment!",
127+
Detail: fmt.Sprintf("The provided license is locked to the following deployments: %q. "+
128+
"Your deployment identifier is %q. Please contact sales.", claims.DeploymentIDs, api.AGPL.DeploymentID),
129+
})
130+
return
131+
}
132+
123133
dl, err := api.Database.InsertLicense(ctx, database.InsertLicenseParams{
124134
UploadedAt: dbtime.Now(),
125135
JWT: addLicense.License,

enterprise/coderd/licenses_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66
"testing"
77

8+
"github.com/google/uuid"
89
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/require"
1011
"golang.org/x/xerrors"
@@ -36,6 +37,22 @@ func TestPostLicense(t *testing.T) {
3637
assert.EqualValues(t, 1, features[codersdk.FeatureAuditLog])
3738
})
3839

40+
t.Run("InvalidDeploymentID", func(t *testing.T) {
41+
t.Parallel()
42+
// The generated deployment will start out with a different deployment ID.
43+
client, _ := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true})
44+
license := coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
45+
DeploymentIDs: []string{uuid.NewString()},
46+
})
47+
_, err := client.AddLicense(context.Background(), codersdk.AddLicenseRequest{
48+
License: license,
49+
})
50+
errResp := &codersdk.Error{}
51+
require.ErrorAs(t, err, &errResp)
52+
require.Equal(t, http.StatusBadRequest, errResp.StatusCode())
53+
require.Contains(t, errResp.Message, "License cannot be used on this deployment!")
54+
})
55+
3956
t.Run("Unauthorized", func(t *testing.T) {
4057
t.Parallel()
4158
client, _ := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true})

0 commit comments

Comments
 (0)