Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
Move CSRF implementation into session
Browse files Browse the repository at this point in the history
This drops the need for two separate cookies to maintain CSRF and removes a few layers of indirection for CSRF handling. CSRF values are now also tied to session duration.

This intentionally leaves the configuration and secret in place until the _next_ release to provide a seamless transition.
  • Loading branch information
sethvargo committed Mar 30, 2021
1 parent db5d1cc commit a61c434
Show file tree
Hide file tree
Showing 19 changed files with 232 additions and 95 deletions.
2 changes: 1 addition & 1 deletion assets/server/codes/issue.html
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ <h1>{{t $.locale "codes.issue.header"}}</h1>
contentType: 'application/json',
data: JSON.stringify(data),
headers: {
'X-CSRF-Token': '{{.csrfToken}}',
'X-CSRF-Token': getCSRFToken(),
},
success: function(result) {
if(result.error && result.error != "") {
Expand Down
2 changes: 1 addition & 1 deletion assets/server/login/_loginscripts.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
data: {
idToken: idToken,
},
headers: { 'X-CSRF-Token': '{{.csrfToken}}' },
headers: { 'X-CSRF-Token': getCSRFToken() },
contentType: 'application/x-www-form-urlencoded',
{{if not .currentUser}}
success: function(returnData) {
Expand Down
2 changes: 1 addition & 1 deletion assets/server/login/register-phone.html
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@
idToken: idToken,
factorCount: factorCount,
},
headers: { 'X-CSRF-Token': '{{.csrfToken}}' },
headers: { 'X-CSRF-Token': getCSRFToken() },
contentType: 'application/x-www-form-urlencoded',
success: function(returnData) {
flash.clear();
Expand Down
6 changes: 5 additions & 1 deletion assets/server/static/js/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ $(function() {
}
}

let csrfToken = $("meta[name=csrf-token]").attr("content");
let csrfToken = getCSRFToken();
let $csrfField = $("<input>")
.attr("type", "hidden")
.attr("name", "gorilla.csrf.Token")
Expand Down Expand Up @@ -238,6 +238,10 @@ $(function() {
window.flash = flash;
});

function getCSRFToken() {
return document.querySelector('meta[name="csrf-token"]').content;
}

function setCookie(cname, cvalue, exdays) {
let d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
Expand Down
2 changes: 1 addition & 1 deletion assets/server/users/import.html
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ <h1>Import users</h1>
"users": data,
"sendInvites": sendInvites,
}),
headers: { 'X-CSRF-Token': '{{.csrfToken}}' },
headers: { 'X-CSRF-Token': getCSRFToken() },
contentType: 'application/json',
success: function(result) {
totalUsersAdded += result.newUsers.length;
Expand Down
6 changes: 0 additions & 6 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,6 @@ represent best practices.
# Disable local observability.
export OBSERVABILITY_EXPORTER="NOOP"
# Configure CSRF for preventing request forgery. Create your own with:
#
# openssl rand -base64 32
#
export CSRF_AUTH_KEY="RcCNhTkS9tSDMSGcl4UCa1FUg9GmctkJpdI+eqZ+3v4="
# Configure cookie encryption, the first is 64 bytes, the second is 32.
# Create your own values with:
#
Expand Down
16 changes: 0 additions & 16 deletions docs/production.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,22 +184,6 @@ provide its _reference_ in the environment. If you are not using a secret
manager, provide this value directly in the environment.


### Cross-site request forgery (CSRF) keys

**Recommended frequency:** 90 days, on breach

To rotate the key, generate a new 32-byte key. You can use `openssl` or similar:

```sh
openssl rand -base64 32 | tr -d "\n"
```

Update the `CSRF_AUTH_KEY` environment variable and re-deploy. The system [only
supports a single key for CSRF](https://github.com/gorilla/csrf/issues/65). When
you deploy the new key, any existing open HTML forms will fail to submit as an
invalid request.


### Database encryption keys

**Recommend frequency:** 30 days, on breach
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ require (
github.com/google/exposure-notifications-server v0.24.1-0.20210322233555-ecf9dee504c1
github.com/google/go-cmp v0.5.5
github.com/google/uuid v1.2.0
github.com/gorilla/csrf v1.7.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,6 @@ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/csrf v1.7.0 h1:mMPjV5/3Zd460xCavIkppUdvnl5fPXMpv2uz2Zyg7/Y=
github.com/gorilla/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
Expand Down
3 changes: 1 addition & 2 deletions internal/envstest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,7 @@ func NewServerConfig(tb testing.TB, testDatabaseInstance *database.TestInstance)
EnableAuthenticatedSMS: true,
},

CookieKeys: config.Base64ByteSlice{randomBytes(tb, 64), randomBytes(tb, 32)},
CSRFAuthKey: randomBytes(tb, 32),
CookieKeys: config.Base64ByteSlice{randomBytes(tb, 64), randomBytes(tb, 32)},

CertificateSigning: config.CertificateSigningConfig{
Keys: *harness.KeyManagerConfig,
Expand Down
8 changes: 4 additions & 4 deletions internal/routes/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,6 @@ func Server(
processDebug := middleware.ProcessDebug()
r.Use(processDebug)

// Install the CSRF protection middleware.
configureCSRF := middleware.ConfigureCSRF(cfg, h)
r.Use(configureCSRF)

// Sessions
requireSession := middleware.RequireSession(sessions, h)
r.Use(requireSession)
Expand All @@ -137,6 +133,10 @@ func Server(
currentPath := middleware.InjectCurrentPath()
r.Use(currentPath)

// Install the CSRF protection middleware.
handleCSRF := middleware.HandleCSRF(h)
r.Use(handleCSRF)

// Create common middleware
requireAuth := middleware.RequireAuth(cacher, authProvider, db, h, cfg.SessionIdleTimeout, cfg.SessionDuration)
checkIdleNoAuth := middleware.CheckSessionIdleNoAuth(h, cfg.SessionIdleTimeout)
Expand Down
9 changes: 0 additions & 9 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,6 @@ func (p *Padding) UnmarshalJSON(b []byte) error {
return nil
}

// CSRFResponse is the return type when requesting an AJAX CSRF token.
type CSRFResponse struct {
Padding Padding `json:"padding"`

CSRFToken string `json:"csrftoken"`
Error string `json:"error"`
ErrorCode string `json:"errorCode"`
}

// UserBatchRequest is a request for bulk creation of users.
// This is called by the Web frontend.
// API is served at /users/import/userbatch
Expand Down
12 changes: 8 additions & 4 deletions pkg/config/server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package config
import (
"context"
"fmt"
"os"
"strings"
"time"

Expand All @@ -27,6 +28,7 @@ import (
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday/v2"

"github.com/google/exposure-notifications-server/pkg/logging"
"github.com/google/exposure-notifications-server/pkg/observability"

firebase "firebase.google.com/go"
Expand Down Expand Up @@ -85,10 +87,6 @@ type ServerConfig struct {
// CookieDomain is the domain for which cookie should be valid.
CookieDomain string `env:"COOKIE_DOMAIN"`

// CSRFAuthKey is the authentication key. It must be 32-bytes and can be
// generated with tools/gen-secret. The value's should be base64 encoded.
CSRFAuthKey envconfig.Base64Bytes `env:"CSRF_AUTH_KEY,required"`

// Application Config
ServerName string `env:"SERVER_NAME,default=Diagnosis Verification Server"`

Expand All @@ -113,6 +111,12 @@ func NewServerConfig(ctx context.Context) (*ServerConfig, error) {
return nil, err
}

// Deprecations
logger := logging.FromContext(ctx).Named("config.ServerConfig")
if v := os.Getenv("CSRF_AUTH_KEY"); v != "" {
logger.Warnw("CSRF_AUTH_KEY is no longer used, please remove it from your configuration")
}

// Parse system notice - do this once since it's displayed on every page.
if v := project.TrimSpace(config.SystemNotice); v != "" {
raw := blackfriday.Run([]byte(v))
Expand Down
Loading

0 comments on commit a61c434

Please sign in to comment.