mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
feat: add debug server for tailnet coordinators (#5861)
Implements a Tailscale-like debug server for our in-memory coordinator. This should provide some visibility into why connections could be failing. Resolves: https://github.com/coder/coder/issues/5845 
This commit is contained in:
22
coderd/apidoc/docs.go
generated
22
coderd/apidoc/docs.go
generated
@ -362,6 +362,28 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/coordinator": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"text/html"
|
||||
],
|
||||
"tags": [
|
||||
"Debug"
|
||||
],
|
||||
"summary": "Debug Info Wireguard Coordinator",
|
||||
"operationId": "debug-info-wireguard-coordinator",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/entitlements": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
18
coderd/apidoc/swagger.json
generated
18
coderd/apidoc/swagger.json
generated
@ -308,6 +308,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/coordinator": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["text/html"],
|
||||
"tags": ["Debug"],
|
||||
"summary": "Debug Info Wireguard Coordinator",
|
||||
"operationId": "debug-info-wireguard-coordinator",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/entitlements": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
@ -613,6 +613,25 @@ func New(options *Options) *API {
|
||||
r.Get("/", api.workspaceApplicationAuth)
|
||||
})
|
||||
})
|
||||
|
||||
r.Route("/debug", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
// Ensure only owners can access debug endpoints.
|
||||
func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceDebugInfo) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
r.Get("/coordinator", api.debugCoordinator)
|
||||
})
|
||||
})
|
||||
|
||||
if options.SwaggerEndpoint {
|
||||
|
@ -272,6 +272,11 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
|
||||
AssertAction: rbac.ActionRead,
|
||||
AssertObject: rbac.ResourceTemplate,
|
||||
},
|
||||
|
||||
"GET:/api/v2/debug/coordinator": {
|
||||
AssertAction: rbac.ActionRead,
|
||||
AssertObject: rbac.ResourceDebugInfo,
|
||||
},
|
||||
}
|
||||
|
||||
// Routes like proxy routes support all HTTP methods. A helper func to expand
|
||||
|
@ -327,7 +327,7 @@ func assertAccept(t *testing.T, comment SwaggerComment) {
|
||||
}
|
||||
}
|
||||
|
||||
var allowedProduceTypes = []string{"json", "text/event-stream"}
|
||||
var allowedProduceTypes = []string{"json", "text/event-stream", "text/html"}
|
||||
|
||||
func assertProduce(t *testing.T, comment SwaggerComment) {
|
||||
var hasResponseModel bool
|
||||
@ -344,7 +344,8 @@ func assertProduce(t *testing.T, comment SwaggerComment) {
|
||||
} else {
|
||||
if (comment.router == "/workspaceagents/me/app-health" && comment.method == "post") ||
|
||||
(comment.router == "/workspaceagents/me/version" && comment.method == "post") ||
|
||||
(comment.router == "/licenses/{id}" && comment.method == "delete") {
|
||||
(comment.router == "/licenses/{id}" && comment.method == "delete") ||
|
||||
(comment.router == "/debug/coordinator" && comment.method == "get") {
|
||||
return // Exception: HTTP 200 is returned without response entity
|
||||
}
|
||||
|
||||
|
14
coderd/debug.go
Normal file
14
coderd/debug.go
Normal file
@ -0,0 +1,14 @@
|
||||
package coderd
|
||||
|
||||
import "net/http"
|
||||
|
||||
// @Summary Debug Info Wireguard Coordinator
|
||||
// @ID debug-info-wireguard-coordinator
|
||||
// @Security CoderSessionToken
|
||||
// @Produce text/html
|
||||
// @Tags Debug
|
||||
// @Success 200
|
||||
// @Router /debug/coordinator [get]
|
||||
func (api *API) debugCoordinator(rw http.ResponseWriter, r *http.Request) {
|
||||
(*api.TailnetCoordinator.Load()).ServeHTTPDebug(rw, r)
|
||||
}
|
@ -150,6 +150,11 @@ var (
|
||||
ResourceReplicas = Object{
|
||||
Type: "replicas",
|
||||
}
|
||||
|
||||
// ResourceDebugInfo controls access to the debug routes `/api/v2/debug/*`.
|
||||
ResourceDebugInfo = Object{
|
||||
Type: "debug_info",
|
||||
}
|
||||
)
|
||||
|
||||
// Object is used to create objects for authz checks when you have none in
|
||||
|
@ -521,6 +521,16 @@ func (api *API) workspaceAgentCoordinate(rw http.ResponseWriter, r *http.Request
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
workspace, err := api.Database.GetWorkspaceByID(ctx, build.WorkspaceID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Internal error fetching workspace.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the resource is still valid!
|
||||
// We only accept agents for resources on the latest build.
|
||||
ensureLatestBuild := func() error {
|
||||
@ -618,7 +628,7 @@ func (api *API) workspaceAgentCoordinate(rw http.ResponseWriter, r *http.Request
|
||||
closeChan := make(chan struct{})
|
||||
go func() {
|
||||
defer close(closeChan)
|
||||
err := (*api.TailnetCoordinator.Load()).ServeAgent(wsNetConn, workspaceAgent.ID)
|
||||
err := (*api.TailnetCoordinator.Load()).ServeAgent(wsNetConn, workspaceAgent.ID, fmt.Sprintf("%s-%s", workspace.Name, workspaceAgent.Name))
|
||||
if err != nil {
|
||||
api.Logger.Warn(ctx, "tailnet coordinator agent error", slog.Error(err))
|
||||
_ = conn.Close(websocket.StatusInternalError, err.Error())
|
||||
|
@ -207,7 +207,7 @@ func (c *client) ListenWorkspaceAgent(_ context.Context) (net.Conn, error) {
|
||||
<-closed
|
||||
})
|
||||
go func() {
|
||||
_ = c.coordinator.ServeAgent(serverConn, c.agentID)
|
||||
_ = c.coordinator.ServeAgent(serverConn, c.agentID, "")
|
||||
close(closed)
|
||||
}()
|
||||
return clientConn, nil
|
||||
|
Reference in New Issue
Block a user