Skip to content

Commit 5d711fc

Browse files
authored
chore: CORs option for yarn dev server (#7630)
* chore: Yarn dev servers require CORs headers for external proxies Adds a flag to set CORs headers to `*` for yarn dev servers
1 parent 1f4f0ef commit 5d711fc

File tree

13 files changed

+80
-18
lines changed

13 files changed

+80
-18
lines changed

coderd/apidoc/docs.go

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,10 @@ func New(options *Options) *API {
393393

394394
derpHandler := derphttp.Handler(api.DERPServer)
395395
derpHandler, api.derpCloseFunc = tailnet.WithWebsocketSupport(api.DERPServer, derpHandler)
396+
cors := httpmw.Cors(options.DeploymentValues.Dangerous.AllowAllCors.Value())
396397

397398
r.Use(
399+
cors,
398400
httpmw.Recover(api.Logger),
399401
tracing.StatusWriterMiddleware,
400402
tracing.Middleware(api.TracerProvider),
@@ -799,6 +801,10 @@ func New(options *Options) *API {
799801
// Add CSP headers to all static assets and pages. CSP headers only affect
800802
// browsers, so these don't make sense on api routes.
801803
cspMW := httpmw.CSPHeaders(func() []string {
804+
if api.DeploymentValues.Dangerous.AllowAllCors {
805+
// In this mode, allow all external requests
806+
return []string{"*"}
807+
}
802808
if f := api.WorkspaceProxyHostsFn.Load(); f != nil {
803809
return (*f)()
804810
}
@@ -813,7 +819,7 @@ func New(options *Options) *API {
813819
// This is the only route we add before all the middleware.
814820
// We want to time the latency of the request, so any middleware will
815821
// interfere with that timing.
816-
rootRouter.Get("/latency-check", LatencyCheck(api.AccessURL))
822+
rootRouter.Get("/latency-check", cors(LatencyCheck(options.DeploymentValues.Dangerous.AllowAllCors.Value(), api.AccessURL)).ServeHTTP)
817823
rootRouter.Mount("/", r)
818824
api.RootHandler = rootRouter
819825

coderd/httpmw/cors.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package httpmw
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-chi/cors"
7+
)
8+
9+
//nolint:revive
10+
func Cors(allowAll bool, origins ...string) func(next http.Handler) http.Handler {
11+
if len(origins) == 0 {
12+
// The default behavior is '*', so putting the empty string defaults to
13+
// the secure behavior of blocking CORs requests.
14+
origins = []string{""}
15+
}
16+
if allowAll {
17+
origins = []string{"*"}
18+
}
19+
return cors.Handler(cors.Options{
20+
AllowedOrigins: origins,
21+
// We only need GET for latency requests
22+
AllowedMethods: []string{http.MethodOptions, http.MethodGet},
23+
AllowedHeaders: []string{"Accept", "Content-Type", "X-LATENCY-CHECK", "X-CSRF-TOKEN"},
24+
// Do not send any cookies
25+
AllowCredentials: false,
26+
})
27+
}

coderd/httpmw/csp.go

+5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ func CSPHeaders(websocketHosts func() []string) func(next http.Handler) http.Han
103103
extraConnect := websocketHosts()
104104
if len(extraConnect) > 0 {
105105
for _, extraHost := range extraConnect {
106+
if extraHost == "*" {
107+
// '*' means all
108+
cspSrcs.Append(cspDirectiveConnectSrc, "*")
109+
continue
110+
}
106111
cspSrcs.Append(cspDirectiveConnectSrc, fmt.Sprintf("wss://%[1]s ws://%[1]s", extraHost))
107112
// We also require this to make http/https requests to the workspace proxy for latency checking.
108113
cspSrcs.Append(cspDirectiveConnectSrc, fmt.Sprintf("https://%[1]s http://%[1]s", extraHost))

coderd/latencycheck.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ import (
66
"strings"
77
)
88

9-
func LatencyCheck(allowedOrigins ...*url.URL) http.HandlerFunc {
9+
// LatencyCheck is an endpoint for the web ui to measure latency with.
10+
// allowAll allows any Origin to get timing information. The allowAll should
11+
// only be set in dev modes.
12+
//
13+
//nolint:revive
14+
func LatencyCheck(allowAll bool, allowedOrigins ...*url.URL) http.HandlerFunc {
1015
allowed := make([]string, 0, len(allowedOrigins))
1116
for _, origin := range allowedOrigins {
1217
// Allow the origin without a path
1318
tmp := *origin
1419
tmp.Path = ""
1520
allowed = append(allowed, strings.TrimSuffix(origin.String(), "/"))
1621
}
22+
if allowAll {
23+
allowed = append(allowed, "*")
24+
}
1725
origins := strings.Join(allowed, ",")
1826
return func(rw http.ResponseWriter, r *http.Request) {
1927
// Allowing timing information to be shared. This allows the browser

codersdk/deployment.go

+11
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ type LoggingConfig struct {
330330
type DangerousConfig struct {
331331
AllowPathAppSharing clibase.Bool `json:"allow_path_app_sharing" typescript:",notnull"`
332332
AllowPathAppSiteOwnerAccess clibase.Bool `json:"allow_path_app_site_owner_access" typescript:",notnull"`
333+
AllowAllCors clibase.Bool `json:"allow_all_cors" typescript:",notnull"`
333334
}
334335

335336
const (
@@ -1167,6 +1168,16 @@ when required by your organization's security policy.`,
11671168
Annotations: clibase.Annotations{}.Mark(annotationExternalProxies, "true"),
11681169
},
11691170
// ☢️ Dangerous settings
1171+
{
1172+
Name: "DANGEROUS: Allow all CORs requests",
1173+
Description: "For security reasons, CORs requests are blocked. If external requests are required, setting this to true will set all cors headers as '*'. This should never be used in production.",
1174+
Flag: "dangerous-allow-cors-requests",
1175+
Env: "CODER_DANGEROUS_ALLOW_CORS_REQUESTS",
1176+
Hidden: true, // Hidden, should only be used by yarn dev server
1177+
Value: &c.Dangerous.AllowAllCors,
1178+
Group: &deploymentGroupDangerous,
1179+
Annotations: clibase.Annotations{}.Mark(annotationExternalProxies, "true"),
1180+
},
11701181
{
11711182
Name: "DANGEROUS: Allow Path App Sharing",
11721183
Description: "Allow workspace apps that are not served from subdomains to be shared. Path-based app sharing is DISABLED by default for security purposes. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. Path-based apps can be disabled entirely with --disable-path-apps for further security.",

docs/api/general.md

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
161161
"sshconfigOptions": ["string"]
162162
},
163163
"dangerous": {
164+
"allow_all_cors": true,
164165
"allow_path_app_sharing": true,
165166
"allow_path_app_site_owner_access": true
166167
},

docs/api/schemas.md

+4
Original file line numberDiff line numberDiff line change
@@ -1800,6 +1800,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
18001800

18011801
```json
18021802
{
1803+
"allow_all_cors": true,
18031804
"allow_path_app_sharing": true,
18041805
"allow_path_app_site_owner_access": true
18051806
}
@@ -1809,6 +1810,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
18091810

18101811
| Name | Type | Required | Restrictions | Description |
18111812
| ---------------------------------- | ------- | -------- | ------------ | ----------- |
1813+
| `allow_all_cors` | boolean | false | | |
18121814
| `allow_path_app_sharing` | boolean | false | | |
18131815
| `allow_path_app_site_owner_access` | boolean | false | | |
18141816

@@ -1857,6 +1859,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
18571859
"sshconfigOptions": ["string"]
18581860
},
18591861
"dangerous": {
1862+
"allow_all_cors": true,
18601863
"allow_path_app_sharing": true,
18611864
"allow_path_app_site_owner_access": true
18621865
},
@@ -2201,6 +2204,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
22012204
"sshconfigOptions": ["string"]
22022205
},
22032206
"dangerous": {
2207+
"allow_all_cors": true,
22042208
"allow_path_app_sharing": true,
22052209
"allow_path_app_site_owner_access": true
22062210
},

enterprise/cli/proxyserver.go

+1
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ func (*RootCmd) proxyServer() *clibase.Cmd {
245245
SecureAuthCookie: cfg.SecureAuthCookie.Value(),
246246
DisablePathApps: cfg.DisablePathApps.Value(),
247247
ProxySessionToken: proxySessionToken.Value(),
248+
AllowAllCors: cfg.Dangerous.AllowAllCors.Value(),
248249
})
249250
if err != nil {
250251
return xerrors.Errorf("create workspace proxy: %w", err)

enterprise/wsproxy/wsproxy.go

+6-14
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"time"
1212

1313
"github.com/go-chi/chi/v5"
14-
"github.com/go-chi/cors"
1514
"github.com/google/uuid"
1615
"github.com/prometheus/client_golang/prometheus"
1716
"go.opentelemetry.io/otel/trace"
@@ -61,6 +60,10 @@ type Options struct {
6160
DisablePathApps bool
6261

6362
ProxySessionToken string
63+
// AllowAllCors will set all CORs headers to '*'.
64+
// By default, CORs is set to accept external requests
65+
// from the dashboardURL. This should only be used in development.
66+
AllowAllCors bool
6467
}
6568

6669
func (o *Options) Validate() error {
@@ -189,18 +192,7 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
189192

190193
// The primary coderd dashboard needs to make some GET requests to
191194
// the workspace proxies to check latency.
192-
corsMW := cors.Handler(cors.Options{
193-
AllowedOrigins: []string{
194-
// Allow the dashboard to make requests to the proxy for latency
195-
// checks.
196-
opts.DashboardURL.String(),
197-
},
198-
// Only allow GET requests for latency checks.
199-
AllowedMethods: []string{http.MethodOptions, http.MethodGet},
200-
AllowedHeaders: []string{"Accept", "Content-Type", "X-LATENCY-CHECK", "X-CSRF-TOKEN"},
201-
// Do not send any cookies
202-
AllowCredentials: false,
203-
})
195+
corsMW := httpmw.Cors(opts.AllowAllCors, opts.DashboardURL.String())
204196

205197
// Routes
206198
apiRateLimiter := httpmw.RateLimit(opts.APIRateLimit, time.Minute)
@@ -266,7 +258,7 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
266258
// See coderd/coderd.go for why we need this.
267259
rootRouter := chi.NewRouter()
268260
// Make sure to add the cors middleware to the latency check route.
269-
rootRouter.Get("/latency-check", corsMW(coderd.LatencyCheck(s.DashboardURL, s.AppServer.AccessURL)).ServeHTTP)
261+
rootRouter.Get("/latency-check", corsMW(coderd.LatencyCheck(opts.AllowAllCors, s.DashboardURL, s.AppServer.AccessURL)).ServeHTTP)
270262
rootRouter.Mount("/", r)
271263
s.Handler = rootRouter
272264

scripts/develop.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ fatal() {
131131
trap 'fatal "Script encountered an error"' ERR
132132

133133
cdroot
134-
start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable --access-url "http://127.0.0.1:3000" --experiments "*" "$@"
134+
start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable --access-url "http://127.0.0.1:3000" --dangerous-allow-cors-requests=true --experiments "*" "$@"
135135

136136
echo '== Waiting for Coder to become ready'
137137
# Start the timeout in the background so interrupting this script
@@ -185,7 +185,7 @@ fatal() {
185185
# Create the proxy
186186
proxy_session_token=$("${CODER_DEV_SHIM}" wsproxy create --name=local-proxy --display-name="Local Proxy" --icon="/emojis/1f4bb.png" --only-token)
187187
# Start the proxy
188-
start_cmd PROXY "" "${CODER_DEV_SHIM}" wsproxy server --http-address=localhost:3010 --proxy-session-token="${proxy_session_token}" --primary-access-url=http://localhost:3000
188+
start_cmd PROXY "" "${CODER_DEV_SHIM}" wsproxy server --dangerous-allow-cors-requests=true --http-address=localhost:3010 --proxy-session-token="${proxy_session_token}" --primary-access-url=http://localhost:3000
189189
) || echo "Failed to create workspace proxy. No workspace proxy created."
190190
fi
191191

site/src/api/typesGenerated.ts

+1
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ export interface DERPServerConfig {
313313
export interface DangerousConfig {
314314
readonly allow_path_app_sharing: boolean
315315
readonly allow_path_app_site_owner_access: boolean
316+
readonly allow_all_cors: boolean
316317
}
317318

318319
// From codersdk/deployment.go

0 commit comments

Comments
 (0)