mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
chore: Dynamic CSP connect-src to support terminals connecting to workspace proxies (#7352)
* chore: Expose proxy hostnames to csp header
This commit is contained in:
@ -250,6 +250,10 @@ func New(ctx context.Context, options *Options) (*API, error) {
|
||||
// Force the initial loading of the cache. Do this in a go routine in case
|
||||
// the calls to the workspace proxies hang and this takes some time.
|
||||
go api.forceWorkspaceProxyHealthUpdate(ctx)
|
||||
|
||||
// Use proxy health to return the healthy workspace proxy hostnames.
|
||||
f := api.ProxyHealth.ProxyHosts
|
||||
api.AGPL.WorkspaceProxyHostsFn.Store(&f)
|
||||
}
|
||||
|
||||
err = api.updateEntitlements(ctx)
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@ -59,7 +60,9 @@ type ProxyHealth struct {
|
||||
logger slog.Logger
|
||||
client *http.Client
|
||||
|
||||
cache *atomic.Pointer[map[uuid.UUID]ProxyStatus]
|
||||
// Cached values for quick access to the health of proxies.
|
||||
cache *atomic.Pointer[map[uuid.UUID]ProxyStatus]
|
||||
proxyHosts *atomic.Pointer[[]string]
|
||||
|
||||
// PromMetrics
|
||||
healthCheckDuration prometheus.Histogram
|
||||
@ -112,6 +115,7 @@ func New(opts *Options) (*ProxyHealth, error) {
|
||||
logger: opts.Logger,
|
||||
client: client,
|
||||
cache: &atomic.Pointer[map[uuid.UUID]ProxyStatus]{},
|
||||
proxyHosts: &atomic.Pointer[[]string]{},
|
||||
healthCheckDuration: healthCheckDuration,
|
||||
healthCheckResults: healthCheckResults,
|
||||
}, nil
|
||||
@ -133,12 +137,24 @@ func (p *ProxyHealth) Run(ctx context.Context) {
|
||||
p.logger.Error(ctx, "proxy health check failed", slog.Error(err))
|
||||
continue
|
||||
}
|
||||
// Store the statuses in the cache.
|
||||
p.cache.Store(&statuses)
|
||||
p.storeProxyHealth(statuses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProxyHealth) storeProxyHealth(statuses map[uuid.UUID]ProxyStatus) {
|
||||
var proxyHosts []string
|
||||
for _, s := range statuses {
|
||||
if s.ProxyHost != "" {
|
||||
proxyHosts = append(proxyHosts, s.ProxyHost)
|
||||
}
|
||||
}
|
||||
|
||||
// Store the statuses in the cache before any other quick values.
|
||||
p.cache.Store(&statuses)
|
||||
p.proxyHosts.Store(&proxyHosts)
|
||||
}
|
||||
|
||||
// ForceUpdate runs a single health check and updates the cache. If the health
|
||||
// check fails, the cache is not updated and an error is returned. This is useful
|
||||
// to trigger an update when a proxy is created or deleted.
|
||||
@ -148,8 +164,7 @@ func (p *ProxyHealth) ForceUpdate(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the statuses in the cache.
|
||||
p.cache.Store(&statuses)
|
||||
p.storeProxyHealth(statuses)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -168,12 +183,28 @@ type ProxyStatus struct {
|
||||
// useful to know as it helps determine if the proxy checked has different values
|
||||
// then the proxy in hand. AKA if the proxy was updated, and the status was for
|
||||
// an older proxy.
|
||||
Proxy database.WorkspaceProxy
|
||||
Proxy database.WorkspaceProxy
|
||||
// ProxyHost is the host:port of the proxy url. This is included in the status
|
||||
// to make sure the proxy url is a valid URL. It also makes it easier to
|
||||
// escalate errors if the url.Parse errors (should never happen).
|
||||
ProxyHost string
|
||||
Status Status
|
||||
Report codersdk.ProxyHealthReport
|
||||
CheckedAt time.Time
|
||||
}
|
||||
|
||||
// ProxyHosts returns the host:port of all healthy proxies.
|
||||
// This can be computed from HealthStatus, but is cached to avoid the
|
||||
// caller needing to loop over all proxies to compute this on all
|
||||
// static web requests.
|
||||
func (p *ProxyHealth) ProxyHosts() []string {
|
||||
ptr := p.proxyHosts.Load()
|
||||
if ptr == nil {
|
||||
return []string{}
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// runOnce runs the health check for all workspace proxies. If there is an
|
||||
// unexpected error, an error is returned. Expected errors will mark a proxy as
|
||||
// unreachable.
|
||||
@ -248,6 +279,7 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID
|
||||
status.Status = Unhealthy
|
||||
break
|
||||
}
|
||||
|
||||
status.Status = Healthy
|
||||
case err == nil && resp.StatusCode != http.StatusOK:
|
||||
// Unhealthy as we did reach the proxy but it got an unexpected response.
|
||||
@ -262,6 +294,15 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID
|
||||
status.Status = Unknown
|
||||
}
|
||||
|
||||
u, err := url.Parse(proxy.Url)
|
||||
if err != nil {
|
||||
// This should never happen. This would mean the proxy sent
|
||||
// us an invalid url?
|
||||
status.Report.Errors = append(status.Report.Errors, fmt.Sprintf("failed to parse proxy url: %s", err.Error()))
|
||||
status.Status = Unhealthy
|
||||
}
|
||||
status.ProxyHost = u.Host
|
||||
|
||||
// Set the prometheus metric correctly.
|
||||
switch status.Status {
|
||||
case Healthy:
|
||||
|
Reference in New Issue
Block a user