feat: allow bypassing current CORS magic based on template config (#18706)

Solves https://github.com/coder/coder/issues/15096

This is a slight rework/refactor of the earlier PRs from @dannykopping
and @Emyrk:
- https://github.com/coder/coder/pull/15669
- https://github.com/coder/coder/pull/15684
- https://github.com/coder/coder/pull/17596

Rather than having a per-app CORS behaviour setting and additionally a
template level setting for ports, this PR adds a single template level
CORS behaviour setting that is then used by all apps/ports for
workspaces created from that template.

The main changes are in `proxy.go` and `request.go` to:
a) get the CORS behaviour setting from the template
b) have `HandleSubdomain` bypass the CORS middleware handler if the
selected behaviour is `passthru`
c) in `proxyWorkspaceApp`, do not modify the response if the selected
behaviour is `passthru`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added support for configuring CORS behavior ("simple" or "passthru")
at the template level for all shared ports.
* Introduced a new "CORS Behavior" setting in the template creation and
settings forms.
* API endpoints and responses now include the optional `cors_behavior`
property for templates.
* Workspace apps and proxy now honor the specified CORS behavior,
enabling conditional CORS middleware application.
* Enhanced workspace app tests with comprehensive scenarios covering
CORS behaviors and authentication states.

* **Bug Fixes**
  * None.

* **Documentation**
* Updated API and admin documentation to describe the new
`cors_behavior` property and its usage.
* Added examples and schema references for CORS behavior in relevant API
docs.

* **Tests**
* Extended automated tests to cover different CORS behavior scenarios
for templates and workspace apps.

* **Chores**
* Updated audit logging to track changes to the `cors_behavior` field on
templates.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Callum Styan <callumstyan@gmail.com>
This commit is contained in:
Callum Styan
2025-07-30 13:42:39 -07:00
committed by GitHub
parent 96e32d60a2
commit ffbfaf2a6f
36 changed files with 1149 additions and 108 deletions
+61 -1
View File
@@ -559,6 +559,64 @@ func AllConnectionTypeValues() []ConnectionType {
}
}
type CorsBehavior string
const (
CorsBehaviorSimple CorsBehavior = "simple"
CorsBehaviorPassthru CorsBehavior = "passthru"
)
func (e *CorsBehavior) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = CorsBehavior(s)
case string:
*e = CorsBehavior(s)
default:
return fmt.Errorf("unsupported scan type for CorsBehavior: %T", src)
}
return nil
}
type NullCorsBehavior struct {
CorsBehavior CorsBehavior `json:"cors_behavior"`
Valid bool `json:"valid"` // Valid is true if CorsBehavior is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullCorsBehavior) Scan(value interface{}) error {
if value == nil {
ns.CorsBehavior, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.CorsBehavior.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullCorsBehavior) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.CorsBehavior), nil
}
func (e CorsBehavior) Valid() bool {
switch e {
case CorsBehaviorSimple,
CorsBehaviorPassthru:
return true
}
return false
}
func AllCorsBehaviorValues() []CorsBehavior {
return []CorsBehavior{
CorsBehaviorSimple,
CorsBehaviorPassthru,
}
}
type CryptoKeyFeature string
const (
@@ -3474,6 +3532,7 @@ type Template struct {
ActivityBump int64 `db:"activity_bump" json:"activity_bump"`
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"`
CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"`
CreatedByUsername string `db:"created_by_username" json:"created_by_username"`
CreatedByName string `db:"created_by_name" json:"created_by_name"`
@@ -3521,7 +3580,8 @@ type TemplateTable struct {
ActivityBump int64 `db:"activity_bump" json:"activity_bump"`
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
// Determines whether to default to the dynamic parameter creation flow for this template or continue using the legacy classic parameter creation flow.This is a template wide setting, the template admin can revert to the classic flow if there are any issues. An escape hatch is required, as workspace creation is a core workflow and cannot break. This column will be removed when the dynamic parameter creation flow is stable.
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"`
}
// Records aggregated usage statistics for templates/users. All usage is rounded up to the nearest minute.