Skip to content

Commit 1bda8a0

Browse files
authored
feat: add deployment_id to the ui and licenses (#13096)
* feat: expose `deployment_id` in the user dropdown * feat: add license deployment_id verification * Ignore wireguard.com from mlc config
1 parent 0e3dc2a commit 1bda8a0

File tree

15 files changed

+115
-26
lines changed

15 files changed

+115
-26
lines changed

.github/workflows/mlc_config.json

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
},
1818
{
1919
"pattern": "tailscale.com"
20+
},
21+
{
22+
"pattern": "wireguard.com"
2023
}
2124
],
2225
"aliveStatusCodes": [200, 0]

coderd/apidoc/docs.go

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ func New(options *Options) *API {
735735
// All CSP errors will be logged
736736
r.Post("/csp/reports", api.logReportCSPViolations)
737737

738-
r.Get("/buildinfo", buildInfo(api.AccessURL, api.DeploymentValues.CLIUpgradeMessage.String()))
738+
r.Get("/buildinfo", buildInfo(api.AccessURL, api.DeploymentValues.CLIUpgradeMessage.String(), api.DeploymentID))
739739
// /regions is overridden in the enterprise version
740740
r.Group(func(r chi.Router) {
741741
r.Use(apiKeyMiddleware)

coderd/deployment.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func (api *API) deploymentStats(rw http.ResponseWriter, r *http.Request) {
6868
// @Tags General
6969
// @Success 200 {object} codersdk.BuildInfoResponse
7070
// @Router /buildinfo [get]
71-
func buildInfo(accessURL *url.URL, upgradeMessage string) http.HandlerFunc {
71+
func buildInfo(accessURL *url.URL, upgradeMessage, deploymentID string) http.HandlerFunc {
7272
return func(rw http.ResponseWriter, r *http.Request) {
7373
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.BuildInfoResponse{
7474
ExternalURL: buildinfo.ExternalURL(),
@@ -77,6 +77,7 @@ func buildInfo(accessURL *url.URL, upgradeMessage string) http.HandlerFunc {
7777
DashboardURL: accessURL.String(),
7878
WorkspaceProxy: false,
7979
UpgradeMessage: upgradeMessage,
80+
DeploymentID: deploymentID,
8081
})
8182
}
8283
}

codersdk/deployment.go

+3
Original file line numberDiff line numberDiff line change
@@ -2151,6 +2151,9 @@ type BuildInfoResponse struct {
21512151
// UpgradeMessage is the message displayed to users when an outdated client
21522152
// is detected.
21532153
UpgradeMessage string `json:"upgrade_message"`
2154+
2155+
// DeploymentID is the unique identifier for this deployment.
2156+
DeploymentID string `json:"deployment_id"`
21542157
}
21552158

21562159
type WorkspaceProxyBuildInfo struct {

docs/api/general.md

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/api/schemas.md

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enterprise/coderd/coderdenttest/coderdenttest.go

+9-7
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

+10-8
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

+10
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

+17
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})

site/src/api/typesGenerated.ts

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.tsx

+47-9
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import LaunchIcon from "@mui/icons-material/LaunchOutlined";
1212
import DocsIcon from "@mui/icons-material/MenuBook";
1313
import Divider from "@mui/material/Divider";
1414
import MenuItem from "@mui/material/MenuItem";
15+
import Tooltip from "@mui/material/Tooltip";
1516
import type { FC } from "react";
1617
import { Link } from "react-router-dom";
1718
import type * as TypesGen from "api/typesGenerated";
19+
import { CopyButton } from "components/CopyButton/CopyButton";
1820
import { ExternalImage } from "components/ExternalImage/ExternalImage";
1921
import { usePopover } from "components/Popover/Popover";
2022
import { Stack } from "components/Stack/Stack";
@@ -161,15 +163,51 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
161163
<Divider css={{ marginBottom: "0 !important" }} />
162164

163165
<Stack css={styles.info} spacing={0}>
164-
<a
165-
title="Browse Source Code"
166-
css={[styles.footerText, styles.buildInfo]}
167-
href={buildInfo?.external_url}
168-
target="_blank"
169-
rel="noreferrer"
170-
>
171-
{buildInfo?.version} <LaunchIcon />
172-
</a>
166+
<Tooltip title="Coder Version">
167+
<a
168+
title="Browse Source Code"
169+
css={[styles.footerText, styles.buildInfo]}
170+
href={buildInfo?.external_url}
171+
target="_blank"
172+
rel="noreferrer"
173+
>
174+
{buildInfo?.version} <LaunchIcon />
175+
</a>
176+
</Tooltip>
177+
178+
{Boolean(buildInfo?.deployment_id) && (
179+
<div
180+
css={css`
181+
font-size: 12px;
182+
display: flex;
183+
align-items: center;
184+
`}
185+
>
186+
<Tooltip title="Deployment Identifier">
187+
<div
188+
css={css`
189+
white-space: nowrap;
190+
overflow: hidden;
191+
text-overflow: ellipsis;
192+
`}
193+
>
194+
{buildInfo?.deployment_id}
195+
</div>
196+
</Tooltip>
197+
<CopyButton
198+
text={buildInfo!.deployment_id}
199+
buttonStyles={css`
200+
width: 16px;
201+
height: 16px;
202+
203+
svg {
204+
width: 16px;
205+
height: 16px;
206+
}
207+
`}
208+
/>
209+
</div>
210+
)}
173211

174212
<div css={styles.footerText}>{Language.copyrightText}</div>
175213
</Stack>

site/src/testHelpers/entities.ts

+1
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ export const MockBuildInfo: TypesGen.BuildInfoResponse = {
201201
dashboard_url: "https:///mock-url",
202202
workspace_proxy: false,
203203
upgrade_message: "My custom upgrade message",
204+
deployment_id: "510d407f-e521-4180-b559-eab4a6d802b8",
204205
};
205206

206207
export const MockSupportLinks: TypesGen.LinkConfig[] = [

0 commit comments

Comments
 (0)