mirror of
https://github.com/coder/coder.git
synced 2025-07-06 15:41:45 +00:00
chore: add workspace proxies to the backend (#7032)
Co-authored-by: Dean Sheather <dean@deansheather.com>
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package coderd
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@ -12,7 +13,10 @@ import (
|
||||
"github.com/coder/coder/coderd/audit"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/coderd/workspaceapps"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/cryptorand"
|
||||
"github.com/coder/coder/enterprise/wsproxy/wsproxysdk"
|
||||
)
|
||||
|
||||
// @Summary Create workspace proxy
|
||||
@ -20,7 +24,7 @@ import (
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Templates
|
||||
// @Tags Enterprise
|
||||
// @Param request body codersdk.CreateWorkspaceProxyRequest true "Create workspace proxy request"
|
||||
// @Success 201 {object} codersdk.WorkspaceProxy
|
||||
// @Router /workspaceproxies [post]
|
||||
@ -50,23 +54,35 @@ func (api *API) postWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := httpapi.CompileHostnamePattern(req.WildcardHostname); err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Wildcard URL is invalid.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
if req.WildcardHostname != "" {
|
||||
if _, err := httpapi.CompileHostnamePattern(req.WildcardHostname); err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Wildcard URL is invalid.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
id := uuid.New()
|
||||
secret, err := cryptorand.HexString(64)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
hashedSecret := sha256.Sum256([]byte(secret))
|
||||
fullToken := fmt.Sprintf("%s:%s", id, secret)
|
||||
|
||||
proxy, err := api.Database.InsertWorkspaceProxy(ctx, database.InsertWorkspaceProxyParams{
|
||||
ID: uuid.New(),
|
||||
Name: req.Name,
|
||||
DisplayName: req.DisplayName,
|
||||
Icon: req.Icon,
|
||||
Url: req.URL,
|
||||
WildcardHostname: req.WildcardHostname,
|
||||
CreatedAt: database.Now(),
|
||||
UpdatedAt: database.Now(),
|
||||
ID: id,
|
||||
Name: req.Name,
|
||||
DisplayName: req.DisplayName,
|
||||
Icon: req.Icon,
|
||||
Url: req.URL,
|
||||
WildcardHostname: req.WildcardHostname,
|
||||
TokenHashedSecret: hashedSecret[:],
|
||||
CreatedAt: database.Now(),
|
||||
UpdatedAt: database.Now(),
|
||||
})
|
||||
if database.IsUniqueViolation(err) {
|
||||
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
|
||||
@ -80,7 +96,10 @@ func (api *API) postWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
aReq.New = proxy
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, convertProxy(proxy))
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateWorkspaceProxyResponse{
|
||||
Proxy: convertProxy(proxy),
|
||||
ProxyToken: fullToken,
|
||||
})
|
||||
}
|
||||
|
||||
// nolint:revive
|
||||
@ -137,3 +156,55 @@ func convertProxy(p database.WorkspaceProxy) codersdk.WorkspaceProxy {
|
||||
Deleted: p.Deleted,
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Issue signed workspace app token
|
||||
// @ID issue-signed-workspace-app-token
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Param request body workspaceapps.IssueTokenRequest true "Issue signed app token request"
|
||||
// @Success 201 {object} wsproxysdk.IssueSignedAppTokenResponse
|
||||
// @Router /workspaceproxies/me/issue-signed-app-token [post]
|
||||
// @x-apidocgen {"skip": true}
|
||||
func (api *API) workspaceProxyIssueSignedAppToken(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
// NOTE: this endpoint will return JSON on success, but will (usually)
|
||||
// return a self-contained HTML error page on failure. The external proxy
|
||||
// should forward any non-201 response to the client.
|
||||
|
||||
var req workspaceapps.IssueTokenRequest
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
// userReq is a http request from the user on the other side of the proxy.
|
||||
// Although the workspace proxy is making this call, we want to use the user's
|
||||
// authorization context to create the token.
|
||||
//
|
||||
// We can use the existing request context for all tracing/logging purposes.
|
||||
// Any workspace proxy auth uses different context keys so we don't need to
|
||||
// worry about that.
|
||||
userReq, err := http.NewRequestWithContext(ctx, "GET", req.AppRequest.BasePath, nil)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
httpapi.InternalServerError(rw, xerrors.Errorf("[DEV ERROR] new request: %w", err))
|
||||
return
|
||||
}
|
||||
userReq.Header.Set(codersdk.SessionTokenHeader, req.SessionToken)
|
||||
|
||||
// Exchange the token.
|
||||
token, tokenStr, ok := api.AGPL.WorkspaceAppsProvider.Issue(ctx, rw, userReq, req)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if token == nil {
|
||||
httpapi.InternalServerError(rw, xerrors.New("nil token after calling token provider"))
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, wsproxysdk.IssueSignedAppTokenResponse{
|
||||
SignedTokenStr: tokenStr,
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user