mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: Add workspace proxy enterprise cli commands (#7176)
* feat: Add workspace proxy enterprise cli commands * chore: Handle custom workspace proxy options. Remove excess * chore: Add endpoint to register workspace proxies
This commit is contained in:
@@ -32,8 +32,7 @@ type Options struct {
|
||||
|
||||
// DashboardURL is the URL of the primary coderd instance.
|
||||
DashboardURL *url.URL
|
||||
// AccessURL is the URL of the WorkspaceProxy. This is the url to communicate
|
||||
// with this server.
|
||||
// AccessURL is the URL of the WorkspaceProxy.
|
||||
AccessURL *url.URL
|
||||
|
||||
// TODO: @emyrk We use these two fields in many places with this comment.
|
||||
@@ -49,9 +48,6 @@ type Options struct {
|
||||
AppHostnameRegex *regexp.Regexp
|
||||
|
||||
RealIPConfig *httpmw.RealIPConfig
|
||||
// TODO: @emyrk this key needs to be provided via a file or something?
|
||||
// Maybe we should curl it from the primary over some secure connection?
|
||||
AppSecurityKey workspaceapps.SecurityKey
|
||||
|
||||
Tracing trace.TracerProvider
|
||||
PrometheusRegistry *prometheus.Registry
|
||||
@@ -72,7 +68,6 @@ func (o *Options) Validate() error {
|
||||
errs.Required("RealIPConfig", o.RealIPConfig)
|
||||
errs.Required("PrometheusRegistry", o.PrometheusRegistry)
|
||||
errs.NotEmpty("ProxySessionToken", o.ProxySessionToken)
|
||||
errs.NotEmpty("AppSecurityKey", o.AppSecurityKey)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
@@ -107,7 +102,10 @@ type Server struct {
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func New(opts *Options) (*Server, error) {
|
||||
// New creates a new workspace proxy server. This requires a primary coderd
|
||||
// instance to be reachable and the correct authorization access token to be
|
||||
// provided. If the proxy cannot authenticate with the primary, this will fail.
|
||||
func New(ctx context.Context, opts *Options) (*Server, error) {
|
||||
if opts.PrometheusRegistry == nil {
|
||||
opts.PrometheusRegistry = prometheus.NewRegistry()
|
||||
}
|
||||
@@ -116,13 +114,34 @@ func New(opts *Options) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: implement some ping and registration logic
|
||||
client := wsproxysdk.New(opts.DashboardURL)
|
||||
err := client.SetSessionToken(opts.ProxySessionToken)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("set client token: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Probably do some version checking here
|
||||
info, err := client.SDKClient.BuildInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to fetch build info from %q: %w", opts.DashboardURL, err)
|
||||
}
|
||||
if info.WorkspaceProxy {
|
||||
return nil, xerrors.Errorf("%q is a workspace proxy, not a primary coderd instance", opts.DashboardURL)
|
||||
}
|
||||
|
||||
regResp, err := client.RegisterWorkspaceProxy(ctx, wsproxysdk.RegisterWorkspaceProxyRequest{
|
||||
AccessURL: opts.AccessURL.String(),
|
||||
WildcardHostname: opts.AppHostname,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("register proxy: %w", err)
|
||||
}
|
||||
|
||||
secKey, err := workspaceapps.KeyFromString(regResp.AppSecurityKey)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse app security key: %w", err)
|
||||
}
|
||||
|
||||
r := chi.NewRouter()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
s := &Server{
|
||||
@@ -149,11 +168,11 @@ func New(opts *Options) (*Server, error) {
|
||||
AccessURL: opts.AccessURL,
|
||||
AppHostname: opts.AppHostname,
|
||||
Client: client,
|
||||
SecurityKey: s.Options.AppSecurityKey,
|
||||
SecurityKey: secKey,
|
||||
Logger: s.Logger.Named("proxy_token_provider"),
|
||||
},
|
||||
WorkspaceConnCache: wsconncache.New(s.DialWorkspaceAgent, 0),
|
||||
AppSecurityKey: opts.AppSecurityKey,
|
||||
AppSecurityKey: secKey,
|
||||
|
||||
DisablePathApps: opts.DisablePathApps,
|
||||
SecureAuthCookie: opts.SecureAuthCookie,
|
||||
@@ -220,9 +239,10 @@ func (s *Server) DialWorkspaceAgent(id uuid.UUID) (*codersdk.WorkspaceAgentConn,
|
||||
|
||||
func (s *Server) buildInfo(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.BuildInfoResponse{
|
||||
ExternalURL: buildinfo.ExternalURL(),
|
||||
Version: buildinfo.Version(),
|
||||
DashboardURL: s.DashboardURL.String(),
|
||||
ExternalURL: buildinfo.ExternalURL(),
|
||||
Version: buildinfo.Version(),
|
||||
DashboardURL: s.DashboardURL.String(),
|
||||
WorkspaceProxy: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -142,3 +142,31 @@ func (c *Client) IssueSignedAppTokenHTML(ctx context.Context, rw http.ResponseWr
|
||||
}
|
||||
return res, true
|
||||
}
|
||||
|
||||
type RegisterWorkspaceProxyRequest struct {
|
||||
// AccessURL that hits the workspace proxy api.
|
||||
AccessURL string `json:"access_url"`
|
||||
// WildcardHostname that the workspace proxy api is serving for subdomain apps.
|
||||
WildcardHostname string `json:"wildcard_hostname"`
|
||||
}
|
||||
|
||||
type RegisterWorkspaceProxyResponse struct {
|
||||
AppSecurityKey string `json:"app_security_key"`
|
||||
}
|
||||
|
||||
func (c *Client) RegisterWorkspaceProxy(ctx context.Context, req RegisterWorkspaceProxyRequest) (RegisterWorkspaceProxyResponse, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost,
|
||||
"/api/v2/workspaceproxies/me/register",
|
||||
req,
|
||||
)
|
||||
if err != nil {
|
||||
return RegisterWorkspaceProxyResponse{}, xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return RegisterWorkspaceProxyResponse{}, codersdk.ReadBodyAsError(res)
|
||||
}
|
||||
var resp RegisterWorkspaceProxyResponse
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user