mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
feat: add --derp-only flag to wsproxy (#8850)
This commit is contained in:
Generated
+7
@@ -10926,6 +10926,9 @@ const docTemplate = `{
|
||||
"derp_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"derp_only": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -11654,6 +11657,10 @@ const docTemplate = `{
|
||||
"description": "DerpEnabled indicates whether the proxy should be included in the DERP\nmap or not.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"derp_only": {
|
||||
"description": "DerpOnly indicates whether the proxy should only be included in the DERP\nmap and should not be used for serving apps.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"hostname": {
|
||||
"description": "ReplicaHostname is the OS hostname of the machine that the proxy is running\non. This is only used for tracking purposes in the replicas table.",
|
||||
"type": "string"
|
||||
|
||||
Generated
+7
@@ -9918,6 +9918,9 @@
|
||||
"derp_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"derp_only": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -10634,6 +10637,10 @@
|
||||
"description": "DerpEnabled indicates whether the proxy should be included in the DERP\nmap or not.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"derp_only": {
|
||||
"description": "DerpOnly indicates whether the proxy should only be included in the DERP\nmap and should not be used for serving apps.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"hostname": {
|
||||
"description": "ReplicaHostname is the OS hostname of the machine that the proxy is running\non. This is only used for tracking purposes in the replicas table.",
|
||||
"type": "string"
|
||||
|
||||
@@ -4164,6 +4164,7 @@ func (q *FakeQuerier) InsertWorkspaceProxy(_ context.Context, arg database.Inser
|
||||
DisplayName: arg.DisplayName,
|
||||
Icon: arg.Icon,
|
||||
DerpEnabled: arg.DerpEnabled,
|
||||
DerpOnly: arg.DerpOnly,
|
||||
TokenHashedSecret: arg.TokenHashedSecret,
|
||||
RegionID: lastRegionID + 1,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
@@ -4238,6 +4239,7 @@ func (q *FakeQuerier) RegisterWorkspaceProxy(_ context.Context, arg database.Reg
|
||||
p.Url = arg.Url
|
||||
p.WildcardHostname = arg.WildcardHostname
|
||||
p.DerpEnabled = arg.DerpEnabled
|
||||
p.DerpOnly = arg.DerpOnly
|
||||
p.UpdatedAt = database.Now()
|
||||
q.workspaceProxies[i] = p
|
||||
return p, nil
|
||||
|
||||
Generated
+4
-1
@@ -878,7 +878,8 @@ CREATE TABLE workspace_proxies (
|
||||
deleted boolean NOT NULL,
|
||||
token_hashed_secret bytea NOT NULL,
|
||||
region_id integer NOT NULL,
|
||||
derp_enabled boolean DEFAULT true NOT NULL
|
||||
derp_enabled boolean DEFAULT true NOT NULL,
|
||||
derp_only boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN workspace_proxies.icon IS 'Expects an emoji character. (/emojis/1f1fa-1f1f8.png)';
|
||||
@@ -891,6 +892,8 @@ COMMENT ON COLUMN workspace_proxies.deleted IS 'Boolean indicator of a deleted w
|
||||
|
||||
COMMENT ON COLUMN workspace_proxies.token_hashed_secret IS 'Hashed secret is used to authenticate the workspace proxy using a session token.';
|
||||
|
||||
COMMENT ON COLUMN workspace_proxies.derp_only IS 'Disables app/terminal proxying for this proxy and only acts as a DERP relay.';
|
||||
|
||||
CREATE SEQUENCE workspace_proxies_region_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE workspace_proxies DROP COLUMN derp_only;
|
||||
@@ -0,0 +1,8 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE workspace_proxies
|
||||
ADD COLUMN "derp_only" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
COMMENT ON COLUMN workspace_proxies.derp_only IS 'Disables app/terminal proxying for this proxy and only acts as a DERP relay.';
|
||||
|
||||
COMMIT;
|
||||
@@ -2014,6 +2014,8 @@ type WorkspaceProxy struct {
|
||||
TokenHashedSecret []byte `db:"token_hashed_secret" json:"token_hashed_secret"`
|
||||
RegionID int32 `db:"region_id" json:"region_id"`
|
||||
DerpEnabled bool `db:"derp_enabled" json:"derp_enabled"`
|
||||
// Disables app/terminal proxying for this proxy and only acts as a DERP relay.
|
||||
DerpOnly bool `db:"derp_only" json:"derp_only"`
|
||||
}
|
||||
|
||||
type WorkspaceResource struct {
|
||||
|
||||
@@ -2848,7 +2848,7 @@ func (q *sqlQuerier) UpdateProvisionerJobWithCompleteByID(ctx context.Context, a
|
||||
|
||||
const getWorkspaceProxies = `-- name: GetWorkspaceProxies :many
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
@@ -2877,6 +2877,7 @@ func (q *sqlQuerier) GetWorkspaceProxies(ctx context.Context) ([]WorkspaceProxy,
|
||||
&i.TokenHashedSecret,
|
||||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2893,7 +2894,7 @@ func (q *sqlQuerier) GetWorkspaceProxies(ctx context.Context) ([]WorkspaceProxy,
|
||||
|
||||
const getWorkspaceProxyByHostname = `-- name: GetWorkspaceProxyByHostname :one
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
@@ -2951,13 +2952,14 @@ func (q *sqlQuerier) GetWorkspaceProxyByHostname(ctx context.Context, arg GetWor
|
||||
&i.TokenHashedSecret,
|
||||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceProxyByID = `-- name: GetWorkspaceProxyByID :one
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
@@ -2982,13 +2984,14 @@ func (q *sqlQuerier) GetWorkspaceProxyByID(ctx context.Context, id uuid.UUID) (W
|
||||
&i.TokenHashedSecret,
|
||||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceProxyByName = `-- name: GetWorkspaceProxyByName :one
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
@@ -3014,6 +3017,7 @@ func (q *sqlQuerier) GetWorkspaceProxyByName(ctx context.Context, name string) (
|
||||
&i.TokenHashedSecret,
|
||||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3028,13 +3032,14 @@ INSERT INTO
|
||||
display_name,
|
||||
icon,
|
||||
derp_enabled,
|
||||
derp_only,
|
||||
token_hashed_secret,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted
|
||||
)
|
||||
VALUES
|
||||
($1, '', '', $2, $3, $4, $5, $6, $7, $8, false) RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled
|
||||
($1, '', '', $2, $3, $4, $5, $6, $7, $8, $9, false) RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
`
|
||||
|
||||
type InsertWorkspaceProxyParams struct {
|
||||
@@ -3043,6 +3048,7 @@ type InsertWorkspaceProxyParams struct {
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
DerpEnabled bool `db:"derp_enabled" json:"derp_enabled"`
|
||||
DerpOnly bool `db:"derp_only" json:"derp_only"`
|
||||
TokenHashedSecret []byte `db:"token_hashed_secret" json:"token_hashed_secret"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
@@ -3055,6 +3061,7 @@ func (q *sqlQuerier) InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspa
|
||||
arg.DisplayName,
|
||||
arg.Icon,
|
||||
arg.DerpEnabled,
|
||||
arg.DerpOnly,
|
||||
arg.TokenHashedSecret,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
@@ -3073,6 +3080,7 @@ func (q *sqlQuerier) InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspa
|
||||
&i.TokenHashedSecret,
|
||||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3084,16 +3092,18 @@ SET
|
||||
url = $1 :: text,
|
||||
wildcard_hostname = $2 :: text,
|
||||
derp_enabled = $3 :: boolean,
|
||||
derp_only = $4 :: boolean,
|
||||
updated_at = Now()
|
||||
WHERE
|
||||
id = $4
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled
|
||||
id = $5
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
`
|
||||
|
||||
type RegisterWorkspaceProxyParams struct {
|
||||
Url string `db:"url" json:"url"`
|
||||
WildcardHostname string `db:"wildcard_hostname" json:"wildcard_hostname"`
|
||||
DerpEnabled bool `db:"derp_enabled" json:"derp_enabled"`
|
||||
DerpOnly bool `db:"derp_only" json:"derp_only"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
@@ -3102,6 +3112,7 @@ func (q *sqlQuerier) RegisterWorkspaceProxy(ctx context.Context, arg RegisterWor
|
||||
arg.Url,
|
||||
arg.WildcardHostname,
|
||||
arg.DerpEnabled,
|
||||
arg.DerpOnly,
|
||||
arg.ID,
|
||||
)
|
||||
var i WorkspaceProxy
|
||||
@@ -3118,6 +3129,7 @@ func (q *sqlQuerier) RegisterWorkspaceProxy(ctx context.Context, arg RegisterWor
|
||||
&i.TokenHashedSecret,
|
||||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3140,7 +3152,7 @@ SET
|
||||
updated_at = Now()
|
||||
WHERE
|
||||
id = $5
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
`
|
||||
|
||||
type UpdateWorkspaceProxyParams struct {
|
||||
@@ -3174,6 +3186,7 @@ func (q *sqlQuerier) UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspa
|
||||
&i.TokenHashedSecret,
|
||||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@ INSERT INTO
|
||||
display_name,
|
||||
icon,
|
||||
derp_enabled,
|
||||
derp_only,
|
||||
token_hashed_secret,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted
|
||||
)
|
||||
VALUES
|
||||
($1, '', '', $2, $3, $4, $5, $6, $7, $8, false) RETURNING *;
|
||||
($1, '', '', $2, $3, $4, $5, $6, $7, $8, $9, false) RETURNING *;
|
||||
|
||||
-- name: RegisterWorkspaceProxy :one
|
||||
UPDATE
|
||||
@@ -23,6 +24,7 @@ SET
|
||||
url = @url :: text,
|
||||
wildcard_hostname = @wildcard_hostname :: text,
|
||||
derp_enabled = @derp_enabled :: boolean,
|
||||
derp_only = @derp_only :: boolean,
|
||||
updated_at = Now()
|
||||
WHERE
|
||||
id = @id
|
||||
|
||||
@@ -49,6 +49,7 @@ type WorkspaceProxy struct {
|
||||
// Extends Region with extra information
|
||||
Region `table:"region,recursive_inline"`
|
||||
DerpEnabled bool `json:"derp_enabled" table:"derp_enabled"`
|
||||
DerpOnly bool `json:"derp_only" table:"derp_only"`
|
||||
|
||||
// Status is the latest status check of the proxy. This will be empty for deleted
|
||||
// proxies. This value can be used to determine if a workspace proxy is healthy
|
||||
|
||||
@@ -21,7 +21,7 @@ We track the following resources:
|
||||
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
|
||||
| Workspace<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deleting_at</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>locked_at</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_by_avatar_url</td><td>false</td></tr><tr><td>initiator_by_username</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |
|
||||
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>derp_only</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |
|
||||
|
||||
<!-- End generated by 'make docs/admin/audit-logs.md'. -->
|
||||
|
||||
|
||||
Generated
+5
@@ -1452,6 +1452,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceproxies \
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"derp_enabled": true,
|
||||
"derp_only": true,
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
@@ -1491,6 +1492,7 @@ Status Code **200**
|
||||
| `»» created_at` | string(date-time) | false | | |
|
||||
| `»» deleted` | boolean | false | | |
|
||||
| `»» derp_enabled` | boolean | false | | |
|
||||
| `»» derp_only` | boolean | false | | |
|
||||
| `»» display_name` | string | false | | |
|
||||
| `»» healthy` | boolean | false | | |
|
||||
| `»» icon_url` | string | false | | |
|
||||
@@ -1556,6 +1558,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceproxies \
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"derp_enabled": true,
|
||||
"derp_only": true,
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
@@ -1611,6 +1614,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy} \
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"derp_enabled": true,
|
||||
"derp_only": true,
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
@@ -1724,6 +1728,7 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy}
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"derp_enabled": true,
|
||||
"derp_only": true,
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
|
||||
Generated
+5
@@ -3820,6 +3820,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"derp_enabled": true,
|
||||
"derp_only": true,
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
@@ -6021,6 +6022,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"derp_enabled": true,
|
||||
"derp_only": true,
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
@@ -6047,6 +6049,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
| `created_at` | string | false | | |
|
||||
| `deleted` | boolean | false | | |
|
||||
| `derp_enabled` | boolean | false | | |
|
||||
| `derp_only` | boolean | false | | |
|
||||
| `display_name` | string | false | | |
|
||||
| `healthy` | boolean | false | | |
|
||||
| `icon_url` | string | false | | |
|
||||
@@ -7418,6 +7421,7 @@ _None_
|
||||
{
|
||||
"access_url": "string",
|
||||
"derp_enabled": true,
|
||||
"derp_only": true,
|
||||
"hostname": "string",
|
||||
"replica_error": "string",
|
||||
"replica_id": "string",
|
||||
@@ -7433,6 +7437,7 @@ _None_
|
||||
| ------------------------------------------------------------------------------------------------- | ------- | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `access_url` | string | false | | Access URL that hits the workspace proxy api. |
|
||||
| `derp_enabled` | boolean | false | | Derp enabled indicates whether the proxy should be included in the DERP map or not. |
|
||||
| `derp_only` | boolean | false | | Derp only indicates whether the proxy should only be included in the DERP map and should not be used for serving apps. |
|
||||
| `hostname` | string | false | | Hostname is the OS hostname of the machine that the proxy is running on. This is only used for tracking purposes in the replicas table. |
|
||||
| `replica_error` | string | false | | Replica error is the error that the replica encountered when trying to dial it's peers. This is stored in the replicas table for debugging purposes but does not affect the proxy's ability to register. |
|
||||
| This value is only stored on subsequent requests to the register endpoint, not the first request. |
|
||||
|
||||
@@ -198,6 +198,7 @@ var auditableResourcesTypes = map[any]map[string]Action{
|
||||
"deleted": ActionIgnore,
|
||||
"token_hashed_secret": ActionSecret,
|
||||
"derp_enabled": ActionTrack,
|
||||
"derp_only": ActionTrack,
|
||||
"region_id": ActionTrack,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ func (*RootCmd) proxyServer() *clibase.Cmd {
|
||||
}
|
||||
proxySessionToken clibase.String
|
||||
primaryAccessURL clibase.URL
|
||||
derpOnly clibase.Bool
|
||||
)
|
||||
opts.Add(
|
||||
// Options only for external workspace proxies
|
||||
@@ -88,6 +89,17 @@ func (*RootCmd) proxyServer() *clibase.Cmd {
|
||||
Group: &externalProxyOptionGroup,
|
||||
Hidden: false,
|
||||
},
|
||||
clibase.Option{
|
||||
Name: "DERP-only proxy",
|
||||
Description: "Run a proxy server that only supports DERP connections and does not proxy workspace app/terminal traffic.",
|
||||
Flag: "derp-only",
|
||||
Env: "CODER_PROXY_DERP_ONLY",
|
||||
YAML: "derpOnly",
|
||||
Required: false,
|
||||
Value: &derpOnly,
|
||||
Group: &externalProxyOptionGroup,
|
||||
Hidden: false,
|
||||
},
|
||||
)
|
||||
|
||||
cmd := &clibase.Cmd{
|
||||
@@ -163,6 +175,10 @@ func (*RootCmd) proxyServer() *clibase.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
if derpOnly.Value() && !cfg.DERP.Server.Enable.Value() {
|
||||
return xerrors.Errorf("cannot use --derp-only with DERP server disabled")
|
||||
}
|
||||
|
||||
// TODO: @emyrk I find this strange that we add this to the context
|
||||
// at the root here.
|
||||
ctx, httpClient, err := cli.ConfigureHTTPClient(
|
||||
@@ -236,6 +252,7 @@ func (*RootCmd) proxyServer() *clibase.Cmd {
|
||||
ProxySessionToken: proxySessionToken.Value(),
|
||||
AllowAllCors: cfg.Dangerous.AllowAllCors.Value(),
|
||||
DERPEnabled: cfg.DERP.Server.Enable.Value(),
|
||||
DERPOnly: derpOnly.Value(),
|
||||
DERPServerRelayAddress: cfg.DERP.Server.RelayURL.String(),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -32,6 +32,8 @@ type ProxyOptions struct {
|
||||
TLSCertificates []tls.Certificate
|
||||
AppHostname string
|
||||
DisablePathApps bool
|
||||
DerpDisabled bool
|
||||
DerpOnly bool
|
||||
|
||||
// ProxyURL is optional
|
||||
ProxyURL *url.URL
|
||||
@@ -91,16 +93,6 @@ func NewWorkspaceProxy(t *testing.T, coderdAPI *coderd.API, owner *codersdk.Clie
|
||||
accessURL = serverURL
|
||||
}
|
||||
|
||||
// TODO: Stun and derp stuff
|
||||
// derpPort, err := strconv.Atoi(serverURL.Port())
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// stunAddr, stunCleanup := stuntest.ServeWithPacketListener(t, nettype.Std{})
|
||||
// t.Cleanup(stunCleanup)
|
||||
//
|
||||
// derpServer := derp.NewServer(key.NewNode(), tailnet.Logger(slogtest.Make(t, nil).Named("derp").Leveled(slog.LevelDebug)))
|
||||
// derpServer.SetMeshKey("test-key")
|
||||
|
||||
var appHostnameRegex *regexp.Regexp
|
||||
if options.AppHostname != "" {
|
||||
var err error
|
||||
@@ -134,7 +126,8 @@ func NewWorkspaceProxy(t *testing.T, coderdAPI *coderd.API, owner *codersdk.Clie
|
||||
// We need a new registry to not conflict with the coderd internal
|
||||
// proxy metrics.
|
||||
PrometheusRegistry: prometheus.NewRegistry(),
|
||||
DERPEnabled: true,
|
||||
DERPEnabled: !options.DerpDisabled,
|
||||
DERPOnly: options.DerpOnly,
|
||||
DERPServerRelayAddress: accessURL.String(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -63,8 +63,8 @@ func (api *API) fetchRegions(ctx context.Context) (codersdk.RegionsResponse[code
|
||||
|
||||
regions := make([]codersdk.Region, 0, len(proxies.Regions))
|
||||
for i := range proxies.Regions {
|
||||
// Ignore deleted proxies.
|
||||
if proxies.Regions[i].Deleted {
|
||||
// Ignore deleted and DERP-only proxies.
|
||||
if proxies.Regions[i].Deleted || proxies.Regions[i].DerpOnly {
|
||||
continue
|
||||
}
|
||||
// Append the inner region data.
|
||||
@@ -353,8 +353,10 @@ func (api *API) postWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
// Enabled by default, but will be disabled on register if the proxy has
|
||||
// it disabled.
|
||||
DerpEnabled: true,
|
||||
CreatedAt: database.Now(),
|
||||
UpdatedAt: database.Now(),
|
||||
// Disabled by default, but blah blah blah.
|
||||
DerpOnly: false,
|
||||
CreatedAt: database.Now(),
|
||||
UpdatedAt: database.Now(),
|
||||
})
|
||||
if database.IsUniqueViolation(err, database.UniqueWorkspaceProxiesLowerNameIndex) {
|
||||
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
|
||||
@@ -569,6 +571,13 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if req.DerpOnly && !req.DerpEnabled {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "DerpOnly cannot be true when DerpEnabled is false.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
startingRegionID, _ := getProxyDERPStartingRegionID(api.Options.BaseDERPMap)
|
||||
regionID := int32(startingRegionID) + proxy.RegionID
|
||||
|
||||
@@ -578,6 +587,7 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request)
|
||||
ID: proxy.ID,
|
||||
Url: req.AccessURL,
|
||||
DerpEnabled: req.DerpEnabled,
|
||||
DerpOnly: req.DerpOnly,
|
||||
WildcardHostname: req.WildcardHostname,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -899,6 +909,7 @@ func convertProxy(p database.WorkspaceProxy, status proxyhealth.ProxyStatus) cod
|
||||
return codersdk.WorkspaceProxy{
|
||||
Region: convertRegion(p, status),
|
||||
DerpEnabled: p.DerpEnabled,
|
||||
DerpOnly: p.DerpOnly,
|
||||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
Deleted: p.Deleted,
|
||||
|
||||
@@ -70,6 +70,9 @@ type Options struct {
|
||||
DisablePathApps bool
|
||||
DERPEnabled bool
|
||||
DERPServerRelayAddress string
|
||||
// DERPOnly determines whether this proxy only provides DERP and does not
|
||||
// provide access to workspace apps/terminal.
|
||||
DERPOnly bool
|
||||
|
||||
ProxySessionToken string
|
||||
// AllowAllCors will set all CORs headers to '*'.
|
||||
@@ -208,6 +211,7 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
|
||||
AccessURL: opts.AccessURL.String(),
|
||||
WildcardHostname: opts.AppHostname,
|
||||
DerpEnabled: opts.DERPEnabled,
|
||||
DerpOnly: opts.DERPOnly,
|
||||
ReplicaID: replicaID,
|
||||
ReplicaHostname: osHostname,
|
||||
ReplicaError: "",
|
||||
@@ -327,18 +331,51 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
|
||||
)
|
||||
|
||||
// Attach workspace apps routes.
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(apiRateLimiter)
|
||||
s.AppServer.Attach(r)
|
||||
})
|
||||
|
||||
r.Route("/derp", func(r chi.Router) {
|
||||
r.Get("/", derpHandler.ServeHTTP)
|
||||
// This is used when UDP is blocked, and latency must be checked via HTTP(s).
|
||||
r.Get("/latency-check", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if !opts.DERPOnly {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(apiRateLimiter)
|
||||
s.AppServer.Attach(r)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
r.Group(func(r chi.Router) {
|
||||
derpOnlyHandler := func(rw http.ResponseWriter, r *http.Request) {
|
||||
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
|
||||
Title: "Head to the Dashboard",
|
||||
Status: http.StatusBadRequest,
|
||||
HideStatus: true,
|
||||
Description: "This workspace proxy is DERP-only and cannot be used for browser connections. " +
|
||||
"Please use a different region directly from the dashboard. Click to be redirected!",
|
||||
RetryEnabled: false,
|
||||
DashboardURL: opts.DashboardURL.String(),
|
||||
})
|
||||
}
|
||||
serveDerpOnlyHandler := func(r chi.Router) {
|
||||
r.HandleFunc("/*", derpOnlyHandler)
|
||||
}
|
||||
|
||||
r.Route("/%40{user}/{workspace_and_agent}/apps/{workspaceapp}", serveDerpOnlyHandler)
|
||||
r.Route("/@{user}/{workspace_and_agent}/apps/{workspaceapp}", serveDerpOnlyHandler)
|
||||
r.Get("/api/v2/workspaceagents/{workspaceagent}/pty", derpOnlyHandler)
|
||||
})
|
||||
}
|
||||
|
||||
if opts.DERPEnabled {
|
||||
r.Route("/derp", func(r chi.Router) {
|
||||
r.Get("/", derpHandler.ServeHTTP)
|
||||
// This is used when UDP is blocked, and latency must be checked via HTTP(s).
|
||||
r.Get("/latency-check", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
r.Route("/derp", func(r chi.Router) {
|
||||
r.HandleFunc("/*", func(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "DERP is disabled on this proxy.",
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
r.Get("/api/v2/buildinfo", s.buildInfo)
|
||||
r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("OK")) })
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"tailscale.com/derp/derphttp"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
@@ -29,6 +31,54 @@ import (
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
func TestDERPOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
deploymentValues := coderdtest.DeploymentValues(t)
|
||||
deploymentValues.Experiments = []string{
|
||||
string(codersdk.ExperimentMoons),
|
||||
"*",
|
||||
}
|
||||
|
||||
client, closer, api, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: deploymentValues,
|
||||
AppHostname: "*.primary.test.coder.com",
|
||||
IncludeProvisionerDaemon: true,
|
||||
RealIPConfig: &httpmw.RealIPConfig{
|
||||
TrustedOrigins: []*net.IPNet{{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Mask: net.CIDRMask(8, 32),
|
||||
}},
|
||||
TrustedHeaders: []string{
|
||||
"CF-Connecting-IP",
|
||||
},
|
||||
},
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureWorkspaceProxy: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
t.Cleanup(func() {
|
||||
_ = closer.Close()
|
||||
})
|
||||
|
||||
// Create an external proxy.
|
||||
_ = coderdenttest.NewWorkspaceProxy(t, api, client, &coderdenttest.ProxyOptions{
|
||||
Name: "best-proxy",
|
||||
DerpOnly: true,
|
||||
})
|
||||
|
||||
// Should not show up in the regions list.
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
regions, err := client.Regions(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, regions, 1)
|
||||
require.Equal(t, api.Options.AccessURL.String(), regions[0].PathAppURL)
|
||||
}
|
||||
|
||||
func TestDERP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -71,6 +121,12 @@ func TestDERP(t *testing.T) {
|
||||
Name: "worst-proxy",
|
||||
})
|
||||
|
||||
// Create a running external proxy with DERP disabled.
|
||||
proxyAPI3 := coderdenttest.NewWorkspaceProxy(t, api, client, &coderdenttest.ProxyOptions{
|
||||
Name: "no-derp-proxy",
|
||||
DerpDisabled: true,
|
||||
})
|
||||
|
||||
// Create a proxy that is never started.
|
||||
createProxyCtx := testutil.Context(t, testutil.WaitLong)
|
||||
_, err := client.CreateWorkspaceProxy(createProxyCtx, codersdk.CreateWorkspaceProxyRequest{
|
||||
@@ -90,19 +146,19 @@ func TestDERP(t *testing.T) {
|
||||
if !assert.NoError(t, err) {
|
||||
return false
|
||||
}
|
||||
if !assert.Len(t, regions, 4) {
|
||||
if !assert.Len(t, regions, 5) {
|
||||
return false
|
||||
}
|
||||
|
||||
// The first 3 regions should be healthy.
|
||||
for _, r := range regions[:3] {
|
||||
for _, r := range regions[:4] {
|
||||
if !r.Healthy {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// The last region should never be healthy.
|
||||
assert.False(t, regions[3].Healthy)
|
||||
assert.False(t, regions[4].Healthy)
|
||||
return true
|
||||
}, testutil.WaitLong, testutil.IntervalMedium)
|
||||
|
||||
@@ -170,6 +226,9 @@ resourceLoop:
|
||||
proxy2Region = r
|
||||
continue
|
||||
}
|
||||
// The no-derp-proxy shouldn't show up in the map.
|
||||
// The last region is never started, which means it's never healthy,
|
||||
// which means it's never added to the DERP map.
|
||||
|
||||
t.Fatalf("unexpected region: %+v", r)
|
||||
}
|
||||
@@ -238,6 +297,18 @@ resourceLoop:
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DERPDisabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Try to connect to the DERP server on the no-derp-proxy region.
|
||||
client, err := derphttp.NewClient(key.NewNode(), proxyAPI3.Options.AccessURL.String(), func(format string, args ...any) {})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
err = client.Connect(ctx)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDERPMapStunNodes(t *testing.T) {
|
||||
|
||||
@@ -160,6 +160,9 @@ type RegisterWorkspaceProxyRequest struct {
|
||||
// DerpEnabled indicates whether the proxy should be included in the DERP
|
||||
// map or not.
|
||||
DerpEnabled bool `json:"derp_enabled"`
|
||||
// DerpOnly indicates whether the proxy should only be included in the DERP
|
||||
// map and should not be used for serving apps.
|
||||
DerpOnly bool `json:"derp_only"`
|
||||
|
||||
// ReplicaID is a unique identifier for the replica of the proxy that is
|
||||
// registering. It should be generated by the client on startup and
|
||||
|
||||
Generated
+1
@@ -1454,6 +1454,7 @@ export interface WorkspaceOptions {
|
||||
// From codersdk/workspaceproxy.go
|
||||
export interface WorkspaceProxy extends Region {
|
||||
readonly derp_enabled: boolean
|
||||
readonly derp_only: boolean
|
||||
readonly status?: WorkspaceProxyStatus
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
|
||||
@@ -80,6 +80,7 @@ export const MockPrimaryWorkspaceProxy: TypesGen.WorkspaceProxy = {
|
||||
path_app_url: "https://coder.com",
|
||||
wildcard_hostname: "*.coder.com",
|
||||
derp_enabled: true,
|
||||
derp_only: false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
deleted: false,
|
||||
@@ -98,6 +99,7 @@ export const MockHealthyWildWorkspaceProxy: TypesGen.WorkspaceProxy = {
|
||||
path_app_url: "https://external.com",
|
||||
wildcard_hostname: "*.external.com",
|
||||
derp_enabled: true,
|
||||
derp_only: false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
deleted: false,
|
||||
@@ -116,6 +118,7 @@ export const MockUnhealthyWildWorkspaceProxy: TypesGen.WorkspaceProxy = {
|
||||
path_app_url: "https://unhealthy.coder.com",
|
||||
wildcard_hostname: "*unhealthy..coder.com",
|
||||
derp_enabled: true,
|
||||
derp_only: true,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
deleted: false,
|
||||
@@ -142,6 +145,7 @@ export const MockWorkspaceProxies: TypesGen.WorkspaceProxy[] = [
|
||||
path_app_url: "https://cowboy.coder.com",
|
||||
wildcard_hostname: "",
|
||||
derp_enabled: false,
|
||||
derp_only: false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
deleted: false,
|
||||
|
||||
Reference in New Issue
Block a user