mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat: add csp headers for embedded apps (#18374)
I modified the proxy host cache we already had and were using for websocket csp headers to also include the wildcard app host, then used those for frame-src policies. I did not add frame-ancestors, since if I understand correctly, those would go on the app, and this middleware does not come into play there. Maybe we will want to add it on workspace apps like we do with cors, if we find apps are setting it to `none` or something. Closes https://github.com/coder/internal/issues/684
This commit is contained in:
@ -21,6 +21,8 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/prometheusmetrics"
|
||||
agplproxyhealth "github.com/coder/coder/v2/coderd/proxyhealth"
|
||||
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
@ -63,7 +65,7 @@ type ProxyHealth struct {
|
||||
|
||||
// Cached values for quick access to the health of proxies.
|
||||
cache *atomic.Pointer[map[uuid.UUID]ProxyStatus]
|
||||
proxyHosts *atomic.Pointer[[]string]
|
||||
proxyHosts *atomic.Pointer[[]*agplproxyhealth.ProxyHost]
|
||||
|
||||
// PromMetrics
|
||||
healthCheckDuration prometheus.Histogram
|
||||
@ -116,7 +118,7 @@ func New(opts *Options) (*ProxyHealth, error) {
|
||||
logger: opts.Logger,
|
||||
client: client,
|
||||
cache: &atomic.Pointer[map[uuid.UUID]ProxyStatus]{},
|
||||
proxyHosts: &atomic.Pointer[[]string]{},
|
||||
proxyHosts: &atomic.Pointer[[]*agplproxyhealth.ProxyHost]{},
|
||||
healthCheckDuration: healthCheckDuration,
|
||||
healthCheckResults: healthCheckResults,
|
||||
}, nil
|
||||
@ -144,9 +146,9 @@ func (p *ProxyHealth) Run(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (p *ProxyHealth) storeProxyHealth(statuses map[uuid.UUID]ProxyStatus) {
|
||||
var proxyHosts []string
|
||||
var proxyHosts []*agplproxyhealth.ProxyHost
|
||||
for _, s := range statuses {
|
||||
if s.ProxyHost != "" {
|
||||
if s.ProxyHost != nil {
|
||||
proxyHosts = append(proxyHosts, s.ProxyHost)
|
||||
}
|
||||
}
|
||||
@ -190,23 +192,22 @@ type ProxyStatus struct {
|
||||
// then the proxy in hand. AKA if the proxy was updated, and the status was for
|
||||
// an older proxy.
|
||||
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
|
||||
// ProxyHost is the base host:port and app host of the proxy. 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 *agplproxyhealth.ProxyHost
|
||||
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 {
|
||||
// ProxyHosts returns the host:port and wildcard host 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() []*agplproxyhealth.ProxyHost {
|
||||
ptr := p.proxyHosts.Load()
|
||||
if ptr == nil {
|
||||
return []string{}
|
||||
return []*agplproxyhealth.ProxyHost{}
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
@ -350,7 +351,10 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID
|
||||
status.Report.Errors = append(status.Report.Errors, fmt.Sprintf("failed to parse proxy url: %s", err.Error()))
|
||||
status.Status = Unhealthy
|
||||
}
|
||||
status.ProxyHost = u.Host
|
||||
status.ProxyHost = &agplproxyhealth.ProxyHost{
|
||||
Host: u.Host,
|
||||
AppHost: appurl.ConvertAppHostForCSP(u.Host, proxy.WildcardHostname),
|
||||
}
|
||||
|
||||
// Set the prometheus metric correctly.
|
||||
switch status.Status {
|
||||
|
@ -965,12 +965,8 @@ func convertRegion(proxy database.WorkspaceProxy, status proxyhealth.ProxyStatus
|
||||
func convertProxy(p database.WorkspaceProxy, status proxyhealth.ProxyStatus) codersdk.WorkspaceProxy {
|
||||
now := dbtime.Now()
|
||||
if p.IsPrimary() {
|
||||
// Primary is always healthy since the primary serves the api that this
|
||||
// is returned from.
|
||||
u, _ := url.Parse(p.Url)
|
||||
status = proxyhealth.ProxyStatus{
|
||||
Proxy: p,
|
||||
ProxyHost: u.Host,
|
||||
Status: proxyhealth.Healthy,
|
||||
Report: codersdk.ProxyHealthReport{},
|
||||
CheckedAt: now,
|
||||
|
Reference in New Issue
Block a user