- Notifications
You must be signed in to change notification settings - Fork1.1k
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
Changes fromall commits
6de5ce2341c6888cff11beafee6b8195b934ef174ab509099File 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 | ||
Contributor 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. MemberAuthor 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.