Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d6c404c

Browse files
committed
feat: Add HSTS and secure cookie options
1 parent 6ab1a68 commit d6c404c

File tree

5 files changed

+90
-1
lines changed

5 files changed

+90
-1
lines changed

cli/start.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ func start() *cobra.Command {
5656
tlsMinVersion string
5757
useTunnel bool
5858
traceDatadog bool
59+
hsts bool
60+
secureCookie bool
5961
)
6062
root := &cobra.Command{
6163
Use: "start",
@@ -132,6 +134,8 @@ func start() *cobra.Command {
132134
Database: databasefake.New(),
133135
Pubsub: database.NewPubsubInMemory(),
134136
GoogleTokenValidator: validator,
137+
HSTS: hsts,
138+
SecureCookie: secureCookie,
135139
}
136140

137141
if !dev {
@@ -334,6 +338,8 @@ func start() *cobra.Command {
334338
cliflag.BoolVarP(root.Flags(), &useTunnel, "tunnel", "", "CODER_DEV_TUNNEL", true, "Serve dev mode through a Cloudflare Tunnel for easy setup")
335339
_ = root.Flags().MarkHidden("tunnel")
336340
cliflag.BoolVarP(root.Flags(), &traceDatadog, "trace-datadog", "", "CODER_TRACE_DATADOG", false, "Send tracing data to a datadog agent")
341+
cliflag.BoolVarP(root.Flags(), &hsts, "hsts", "", "CODER_HSTS", false, "Set the 'strict-transport-security' header on http responses")
342+
cliflag.BoolVarP(root.Flags(), &secureCookie, "secure-cookie", "", "CODER_SECURE_COOKIE", false, "Set the 'Secure' property on browser session cookies")
337343

338344
return root
339345
}

coderd/coderd.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ type Options struct {
2929

3030
AWSCertificates awsidentity.Certificates
3131
GoogleTokenValidator *idtoken.Validator
32+
33+
HSTS bool
34+
SecureCookie bool
3235
}
3336

3437
// New constructs the Coder API into an HTTP handler.
@@ -45,7 +48,10 @@ func New(options *Options) (http.Handler, func()) {
4548

4649
r := chi.NewRouter()
4750
r.Route("/api/v2", func(r chi.Router) {
48-
r.Use(chitrace.Middleware())
51+
r.Use(
52+
chitrace.Middleware(),
53+
httpmw.HSTS(api.HSTS),
54+
)
4955
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
5056
httpapi.Write(w, http.StatusOK, httpapi.Response{
5157
Message: "👋",

coderd/httpmw/hsts.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package httpmw
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"time"
7+
)
8+
9+
const (
10+
HSTSHeader = "Strict-Transport-Security"
11+
HSTSMaxAge = time.Hour * 24 * 365 // 1 year
12+
)
13+
14+
// HSTS will add the strict-transport-security header if enabled.
15+
// This header forces a browser to always use https for the domain after it loads https
16+
// once.
17+
// Meaning: On first load of product.coder.com, they are redirected to https.
18+
// On all subsequent loads, the client's local browser forces https. This prevents man in the middle.
19+
//
20+
// This header only makes sense if the app is using tls.
21+
// Full header example
22+
// Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
23+
func HSTS(hsts bool) func(next http.Handler) http.Handler {
24+
return func(next http.Handler) http.Handler {
25+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
26+
if hsts {
27+
w.Header().Set(HSTSHeader, fmt.Sprintf("max-age=%d", int64(HSTSMaxAge)))
28+
}
29+
30+
next.ServeHTTP(w, r)
31+
})
32+
}
33+
}

coderd/httpmw/hsts_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package httpmw_test
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/coder/coder/coderd/httpmw"
10+
"github.com/go-chi/chi/v5"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestHSTS(t *testing.T) {
15+
t.Parallel()
16+
setup := func(hsts bool) *http.Response {
17+
rw := httptest.NewRecorder()
18+
r := httptest.NewRequest("GET", "/", nil)
19+
20+
rtr := chi.NewRouter()
21+
rtr.Use(httpmw.HSTS(hsts))
22+
rtr.Get("/", func(w http.ResponseWriter, r *http.Request) {
23+
_, _ = w.Write([]byte("hello!"))
24+
})
25+
rtr.ServeHTTP(rw, r)
26+
res := rw.Result()
27+
defer res.Body.Close()
28+
return res
29+
}
30+
31+
t.Run("True", func(t *testing.T) {
32+
t.Parallel()
33+
34+
res := setup(true)
35+
require.Contains(t, res.Header.Get(httpmw.HSTSHeader), fmt.Sprintf("max-age=%d", int64(httpmw.HSTSMaxAge)))
36+
})
37+
t.Run("False", func(t *testing.T) {
38+
t.Parallel()
39+
40+
res := setup(false)
41+
require.NotContains(t, res.Header.Get(httpmw.HSTSHeader), fmt.Sprintf("max-age=%d", int64(httpmw.HSTSMaxAge)))
42+
})
43+
}

coderd/users.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ func (api *api) postLogin(rw http.ResponseWriter, r *http.Request) {
417417
Path: "/",
418418
HttpOnly: true,
419419
SameSite: http.SameSiteLaxMode,
420+
Secure: api.SecureCookie,
420421
})
421422

422423
render.Status(r, http.StatusCreated)

0 commit comments

Comments
 (0)