From e81049c79dbc167c82e7bb76b67f560fa9541979 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 27 May 2022 09:33:20 -0500 Subject: [PATCH] fix: Open csp-images to allow external External images are required for the README parts of templates. Only allowing https right now --- coderd/coderd.go | 4 +++- coderd/coderd_test.go | 1 + coderd/csp.go | 38 ++++++++++++++++++++++++++++++++++++++ site/embed.go | 7 +++++-- 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 coderd/csp.go diff --git a/coderd/coderd.go b/coderd/coderd.go index ed8cba833ccf1..82797d452d205 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -101,7 +101,6 @@ func New(options *Options) *API { Message: "Route not found.", }) }) - r.Use( // Specific routes can specify smaller limits. httpmw.RateLimitPerMinute(options.APIRateLimit), @@ -112,6 +111,9 @@ func New(options *Options) *API { Message: "👋", }) }) + // All CSP errors will be logged + r.Post("/csp/reports", api.logReportCSPViolations) + r.Route("/buildinfo", func(r chi.Router) { r.Get("/", func(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, codersdk.BuildInfoResponse{ diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index 9b1e83a252ee6..9c3486e80b1a2 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -119,6 +119,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) { "POST:/api/v2/users/login": {NoAuthorize: true}, "POST:/api/v2/users/logout": {NoAuthorize: true}, "GET:/api/v2/users/authmethods": {NoAuthorize: true}, + "POST:/api/v2/csp/reports": {NoAuthorize: true}, // Has it's own auth "GET:/api/v2/users/oauth2/github/callback": {NoAuthorize: true}, diff --git a/coderd/csp.go b/coderd/csp.go new file mode 100644 index 0000000000000..5355a47ec505a --- /dev/null +++ b/coderd/csp.go @@ -0,0 +1,38 @@ +package coderd + +import ( + "encoding/json" + "net/http" + + "github.com/coder/coder/coderd/httpapi" + + "cdr.dev/slog" +) + +type cspViolation struct { + Report map[string]interface{} `json:"csp-report"` +} + +// logReportCSPViolations will log all reported csp violations. +func (api *API) logReportCSPViolations(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var v cspViolation + + dec := json.NewDecoder(r.Body) + err := dec.Decode(&v) + if err != nil { + api.Logger.Warn(ctx, "csp violation", slog.Error(err)) + httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ + Message: "failed to read body", + }) + return + } + + fields := make([]slog.Field, 0, len(v.Report)) + for k, v := range v.Report { + fields = append(fields, slog.F(k, v)) + } + api.Logger.Warn(ctx, "csp violation", fields...) + + httpapi.Write(rw, http.StatusOK, "ok") +} diff --git a/site/embed.go b/site/embed.go index 22e5585e25a34..b78ed3a7674c7 100644 --- a/site/embed.go +++ b/site/embed.go @@ -261,11 +261,14 @@ func secureHeaders(next http.Handler) http.Handler { CSPDirectiveManifestSrc: {"'self' blob:"}, CSPDirectiveFrameSrc: {"'self'"}, // data: for loading base64 encoded icons for generic applications. - CSPDirectiveImgSrc: {"'self' https://cdn.coder.com data:"}, + // https: allows loading images from external sources. This is not ideal + // but is required for the templates page that renders readmes. + // We should find a better solution in the future. + CSPDirectiveImgSrc: {"'self' https: https://cdn.coder.com data:"}, CSPDirectiveFormAction: {"'self'"}, CSPDirectiveMediaSrc: {"'self'"}, // Report all violations back to the server to log - CSPDirectiveReportURI: {"/api/private/csp/reports"}, + CSPDirectiveReportURI: {"/api/v2/csp/reports"}, CSPFrameAncestors: {"'none'"}, // Only scripts can manipulate the dom. This prevents someone from