- Notifications
You must be signed in to change notification settings - Fork914
feat: add WorkspaceUpdates tailnet RPC#14847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
6de5ce2
341c688
8cff11b
eafee6b
8195b93
4ef174a
b509099
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -33,6 +33,7 @@ import ( | ||
"github.com/coder/coder/v2/coderd/httpapi" | ||
"github.com/coder/coder/v2/coderd/httpmw" | ||
"github.com/coder/coder/v2/coderd/jwtutils" | ||
"github.com/coder/coder/v2/coderd/rbac" | ||
"github.com/coder/coder/v2/coderd/rbac/policy" | ||
"github.com/coder/coder/v2/coderd/wspubsub" | ||
"github.com/coder/coder/v2/codersdk" | ||
@@ -844,31 +845,10 @@ func (api *API) workspaceAgentClientCoordinate(rw http.ResponseWriter, r *http.R | ||
return | ||
} | ||
peerID, err := api.handleResumeToken(ctx, rw, r) | ||
if err != nil { | ||
// handleResumeToken has already written the response. | ||
return | ||
} | ||
api.WebsocketWaitMutex.Lock() | ||
@@ -891,13 +871,47 @@ func (api *API) workspaceAgentClientCoordinate(rw http.ResponseWriter, r *http.R | ||
go httpapi.Heartbeat(ctx, conn) | ||
defer conn.Close(websocket.StatusNormalClosure, "") | ||
err = api.TailnetClientService.ServeClient(ctx, version, wsNetConn, tailnet.StreamID{ | ||
Name: "client", | ||
ID: peerID, | ||
Auth: tailnet.ClientCoordinateeAuth{ | ||
AgentID: workspaceAgent.ID, | ||
}, | ||
}) | ||
if err != nil && !xerrors.Is(err, io.EOF) && !xerrors.Is(err, context.Canceled) { | ||
_ = conn.Close(websocket.StatusInternalError, err.Error()) | ||
return | ||
} | ||
} | ||
// handleResumeToken accepts a resume_token query parameter to use the same peer ID | ||
func (api *API) handleResumeToken(ctx context.Context, rw http.ResponseWriter, r *http.Request) (peerID uuid.UUID, err error) { | ||
peerID = uuid.New() | ||
resumeToken := r.URL.Query().Get("resume_token") | ||
if resumeToken != "" { | ||
peerID, err = api.Options.CoordinatorResumeTokenProvider.VerifyResumeToken(ctx, resumeToken) | ||
// If the token is missing the key ID, it's probably an old token in which | ||
// case we just want to generate a new peer ID. | ||
if xerrors.Is(err, jwtutils.ErrMissingKeyID) { | ||
peerID = uuid.New() | ||
err = nil | ||
} else if err != nil { | ||
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ | ||
Message: workspacesdk.CoordinateAPIInvalidResumeToken, | ||
Detail: err.Error(), | ||
Validations: []codersdk.ValidationError{ | ||
{Field: "resume_token", Detail: workspacesdk.CoordinateAPIInvalidResumeToken}, | ||
}, | ||
}) | ||
return peerID, err | ||
} else { | ||
api.Logger.Debug(ctx, "accepted coordinate resume token for peer", | ||
slog.F("peer_id", peerID.String())) | ||
} | ||
} | ||
return peerID, err | ||
} | ||
// @Summary Post workspace agent log source | ||
// @ID post-workspace-agent-log-source | ||
// @Security CoderSessionToken | ||
@@ -1469,6 +1483,80 @@ func (api *API) workspaceAgentsExternalAuthListen(ctx context.Context, rw http.R | ||
} | ||
} | ||
// @Summary User-scoped tailnet RPC connection | ||
// @ID user-scoped-tailnet-rpc-connection | ||
// @Security CoderSessionToken | ||
// @Tags Agents | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. drop this tag --- it's not specifically related to agents, unlike the existing RPC which targets a specific agent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. The swagger parser requires a tag, so it'd be between | ||
// @Success 101 | ||
// @Router /tailnet [get] | ||
func (api *API) tailnetRPCConn(rw http.ResponseWriter, r *http.Request) { | ||
ctx := r.Context() | ||
version := "2.0" | ||
qv := r.URL.Query().Get("version") | ||
if qv != "" { | ||
version = qv | ||
} | ||
if err := proto.CurrentVersion.Validate(version); err != nil { | ||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ | ||
Message: "Unknown or unsupported API version", | ||
Validations: []codersdk.ValidationError{ | ||
{Field: "version", Detail: err.Error()}, | ||
}, | ||
}) | ||
return | ||
} | ||
peerID, err := api.handleResumeToken(ctx, rw, r) | ||
ethanndickson marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
if err != nil { | ||
// handleResumeToken has already written the response. | ||
return | ||
} | ||
// Used to authorize tunnel request | ||
sshPrep, err := api.HTTPAuth.AuthorizeSQLFilter(r, policy.ActionSSH, rbac.ResourceWorkspace.Type) | ||
if err != nil { | ||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ | ||
Message: "Internal error preparing sql filter.", | ||
Detail: err.Error(), | ||
}) | ||
return | ||
} | ||
api.WebsocketWaitMutex.Lock() | ||
api.WebsocketWaitGroup.Add(1) | ||
api.WebsocketWaitMutex.Unlock() | ||
defer api.WebsocketWaitGroup.Done() | ||
conn, err := websocket.Accept(rw, r, nil) | ||
if err != nil { | ||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ | ||
Message: "Failed to accept websocket.", | ||
Detail: err.Error(), | ||
}) | ||
return | ||
} | ||
ctx, wsNetConn := codersdk.WebsocketNetConn(ctx, conn, websocket.MessageBinary) | ||
defer wsNetConn.Close() | ||
defer conn.Close(websocket.StatusNormalClosure, "") | ||
go httpapi.Heartbeat(ctx, conn) | ||
err = api.TailnetClientService.ServeClient(ctx, version, wsNetConn, tailnet.StreamID{ | ||
Name: "client", | ||
ID: peerID, | ||
Auth: tailnet.ClientUserCoordinateeAuth{ | ||
Auth: &rbacAuthorizer{ | ||
sshPrep: sshPrep, | ||
db: api.Database, | ||
}, | ||
}, | ||
}) | ||
if err != nil && !xerrors.Is(err, io.EOF) && !xerrors.Is(err, context.Canceled) { | ||
_ = conn.Close(websocket.StatusInternalError, err.Error()) | ||
return | ||
} | ||
} | ||
// createExternalAuthResponse creates an ExternalAuthResponse based on the | ||
// provider type. This is to support legacy `/workspaceagents/me/gitauth` | ||
// which uses `Username` and `Password`. | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.