Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat simplify config #635

Merged
merged 26 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
42a5c43
chore(deps): bump github.com/labstack/echo/v4 in /backend
dependabot[bot] Feb 23, 2023
08d3afe
Merge branch 'dependabot/go_modules/backend/github.com/labstack/echo/…
like-a-bause Mar 3, 2023
027535c
feat: auto-corso
like-a-bause Mar 3, 2023
d6158d7
Merge branch 'main' into feat-simplify-config
like-a-bause Mar 6, 2023
784215a
fix: fix merge conflict
like-a-bause Mar 6, 2023
fa64452
feat: simplify even further. Just use webauthn origins as cors origins
like-a-bause Mar 8, 2023
55fafdb
fix: Remove Deprecated Origin Field. Use Origins instead. BREAKING CH…
like-a-bause Mar 8, 2023
30d78b3
chore: Expose X-Auth-Token Header only on x-domain case
like-a-bause Mar 9, 2023
a820ccd
try: comment out Acces-Control-Expose-Header
like-a-bause Mar 9, 2023
500cc4e
fix: remove manual settings of allowed headers
like-a-bause Mar 9, 2023
49f4a96
docs: Remove cors and origin from config documentation. Add note that…
like-a-bause Mar 15, 2023
19c462d
doc: remove occurences of cors in docs
like-a-bause Mar 15, 2023
12f6ad3
fix:m erge conflicts
like-a-bause Mar 15, 2023
3da3189
Merge branch 'main' into feat-simplify-config
like-a-bause Mar 16, 2023
446e46c
feat: reintroduce cors.allow_origins config
FreddyDevelop Apr 4, 2023
67aeeb0
fix: fix docker compose rate limiting config
FreddyDevelop Apr 4, 2023
23fae78
docs: mention cors in the api spec
FreddyDevelop Apr 4, 2023
fed6f22
Update backend/docs/Config.md
like-a-bause Apr 11, 2023
4844067
Update backend/server/public_router.go
like-a-bause Apr 11, 2023
291c974
Update backend/server/public_router.go
like-a-bause Apr 11, 2023
332f73d
fix: fix merge conflicts. adapt to thirdparty-x-domain pr
like-a-bause Apr 12, 2023
024939d
Update backend/README.md
like-a-bause Apr 12, 2023
432bf3f
chore: remove unnecessary origin entry
FreddyDevelop Apr 12, 2023
e0a6bb5
docs: clarify note
FreddyDevelop Apr 12, 2023
3a8f171
Merge remote-tracking branch 'origin/main' into feat-simplify-config
like-a-bause Apr 12, 2023
79e5268
fix: Origins in test/config.go
like-a-bause Apr 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ To get the Hanko backend up and running you need to:
4. [Run and configure an SMTP server](#run-and-configure-an-smtp-server)
5. [Configure JSON Web Key Set generation](#configure-json-web-key-set-generation)
6. [Configure WebAuthn](#configure-webauthn)
8. [Configure CORS](#configure-cors)
9. [Start the backend](#run-the-backend)
7. [Configure CORS](#configure-cors)
8. [Start the backend](#run-the-backend)

### Run a database

Expand Down Expand Up @@ -237,7 +237,8 @@ webauthn:
relying_party:
id: "localhost"
display_name: "Hanko Authentication Service"
origin: "http://localhost"
origins:
- "http://localhost"
```

so no further configuration changes need to be made to your configuration file.
Expand All @@ -252,7 +253,8 @@ webauthn:
relying_party:
id: "example.com"
display_name: "Example Project"
origin: "https://example.com"
origins:
- "https://example.com"
```

If the login should be available at `https://login.example.com` instead, then the WebAuthn config would look like this:
Expand All @@ -262,7 +264,8 @@ webauthn:
relying_party:
id: "login.example.com"
display_name: "Example Project"
origin: "https://login.example.com"
origins:
- "https://login.example.com"
```

Given the above scenario, you still may want to bind your users WebAuthn credentials to `example.com` if you plan to
Expand All @@ -275,11 +278,10 @@ webauthn:
relying_party:
id: "example.com"
display_name: "Example Project"
origin: "https://login.example.com"
origins:
- "https://login.example.com"
```

> **Note**: Currently, only a single origin is supported. We plan to add support for a list of origins at some point.

### Configure CORS

Because the backend and your application(s) consuming backend API most likely have different origins, i.e.
Expand All @@ -290,12 +292,26 @@ Cross-Origin Resource Sharing (CORS) and specify your application(s) as allowed
server:
public:
cors:
enabled: true
allow_credentials: true
allow_origins:
- "https://example.com"
- https://example.com
```

When you include a wildcard `*` origin you need to set `unsafe_wildcard_origin_allowed: true`:

```yaml
server:
public:
cors:
allow_origins:
- "*"
unsafe_wildcard_origin_allowed: true
```

Wildcard `*` origins can lead to cross-site attacks and when you include a `*` wildcard origin,
we want to make sure, that you understand what you are doing, hence this flag.

> **Note** In most cases, the `allow_origins` list here should contain the same entries as the `webauthn.relying_party.origins` list. Only when you have an Android app you will have an extra entry (`android:apk-key-hash:...`) in the `webauthn.relying_party.origins` list.

### Start the backend

The Hanko backend consists of a public and an administrative API (currently providing user management
Expand Down
55 changes: 32 additions & 23 deletions backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
"github.com/sethvargo/go-limiter/httplimit"
"log"
"strings"
"time"
Expand Down Expand Up @@ -76,14 +75,6 @@ func DefaultConfig() *Config {
Server: Server{
Public: ServerSettings{
Address: ":8000",
Cors: Cors{
ExposeHeaders: []string{
httplimit.HeaderRateLimitLimit,
httplimit.HeaderRateLimitRemaining,
httplimit.HeaderRateLimitReset,
httplimit.HeaderRetryAfter,
},
},
},
Admin: ServerSettings{
Address: ":8001",
Expand All @@ -93,7 +84,7 @@ func DefaultConfig() *Config {
RelyingParty: RelyingParty{
Id: "localhost",
DisplayName: "Hanko Authentication Service",
Origins: []string{"http://localhost"},
Origins: []string{"http://localhost:8888"},
},
Timeout: 60000,
},
Expand Down Expand Up @@ -239,19 +230,39 @@ type ServerSettings struct {
}

type Cors struct {
Enabled bool `yaml:"enabled" json:"enabled" koanf:"enabled"`
AllowCredentials bool `yaml:"allow_credentials" json:"allow_credentials" koanf:"allow_credentials" split_words:"true"`
AllowOrigins []string `yaml:"allow_origins" json:"allow_origins" koanf:"allow_origins" split_words:"true"`
AllowMethods []string `yaml:"allow_methods" json:"allow_methods" koanf:"allow_methods" split_words:"true"`
AllowHeaders []string `yaml:"allow_headers" json:"allow_headers" koanf:"allow_headers" split_words:"true"`
ExposeHeaders []string `yaml:"expose_headers" json:"expose_headers" koanf:"expose_headers" split_words:"true"`
MaxAge int `yaml:"max_age" json:"max_age" koanf:"max_age" split_words:"true"`
// AllowOrigins determines the value of the Access-Control-Allow-Origin
// response header. This header defines a list of origins that may access the
// resource. The wildcard characters '*' and '?' are supported and are
// converted to regex fragments '.*' and '.' accordingly.
AllowOrigins []string `yaml:"allow_origins" json:"allow_origins" koanf:"allow_origins" split_words:"true"`

// UnsafeWildcardOriginWithAllowCredentials UNSAFE/INSECURE: allows wildcard '*' origin to be used with AllowCredentials
// flag. In that case we consider any origin allowed and send it back to the client with `Access-Control-Allow-Origin` header.
//
// This is INSECURE and potentially leads to [cross-origin](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties)
// attacks. See: https://github.com/labstack/echo/issues/2400 for discussion on the subject.
//
// Optional. Default value is false.
UnsafeWildcardOriginAllowed bool `yaml:"unsafe_wildcard_origin_allowed" json:"unsafe_wildcard_origin_allowed" koanf:"unsafe_wildcard_origin_allowed" split_words:"true"`
}

func (cors *Cors) Validate() error {
for _, origin := range cors.AllowOrigins {
if origin == "*" && !cors.UnsafeWildcardOriginAllowed {
return fmt.Errorf("found wildcard '*' origin in server.public.cors.allow_origins, if this is intentional set server.public.cors.unsafe_wildcard_origin_allowed to true")
}
}

return nil
}

func (s *ServerSettings) Validate() error {
if len(strings.TrimSpace(s.Address)) == 0 {
return errors.New("field Address must not be empty")
}
if err := s.Cors.Validate(); err != nil {
return err
}
return nil
}

Expand All @@ -268,12 +279,10 @@ func (r *WebauthnSettings) Validate() error {

// RelyingParty webauthn settings for your application using hanko.
type RelyingParty struct {
Id string `yaml:"id" json:"id" koanf:"id"`
DisplayName string `yaml:"display_name" json:"display_name" koanf:"display_name" split_words:"true"`
Icon string `yaml:"icon" json:"icon" koanf:"icon"`
// Deprecated: Use Origins instead
Origin string `yaml:"origin" json:"origin" koanf:"origin"`
Origins []string `yaml:"origins" json:"origins" koanf:"origins"`
Id string `yaml:"id" json:"id" koanf:"id"`
DisplayName string `yaml:"display_name" json:"display_name" koanf:"display_name" split_words:"true"`
Icon string `yaml:"icon" json:"icon" koanf:"icon"`
Origins []string `yaml:"origins" json:"origins" koanf:"origins"`
}

// SMTP Server Settings for sending passcodes
Expand Down
4 changes: 2 additions & 2 deletions backend/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ func TestEnvironmentVariables(t *testing.T) {
err := os.Setenv("PASSCODE_SMTP_HOST", "valueFromEnvVars")
require.NoError(t, err)

err = os.Setenv("SERVER_PUBLIC_CORS_ALLOW_METHODS", "GET,PUT,POST,DELETE")
err = os.Setenv("WEBAUTHN_RELYING_PARTY_ORIGINS", "https://hanko.io,https://auth.hanko.io")
require.NoError(t, err)

configPath := "./minimal-config.yaml"
cfg, err := Load(&configPath)
require.NoError(t, err)

assert.Equal(t, "valueFromEnvVars", cfg.Passcode.Smtp.Host)
assert.True(t, reflect.DeepEqual([]string{"GET", "PUT", "POST", "DELETE"}, cfg.Server.Public.Cors.AllowMethods))
assert.True(t, reflect.DeepEqual([]string{"https://hanko.io", "https://auth.hanko.io"}, cfg.Webauthn.RelyingParty.Origins))
}
42 changes: 13 additions & 29 deletions backend/docs/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ Environment variables have higher precedence than configuration via file (i.e. i
given in the file - multivalued options, like arrays, are also _not_ merged but overwritten entirely).

The schema for the configuration file is given below. To set equivalent environment variables, join keys by `_`
(underscore) and uppercase the keys, i.e. for `server.public.cors.allow_methods`
(underscore) and uppercase the keys, i.e. for `server.webauthn.relying_party.origins`
use:

```shell
export SERVER_PUBLIC_CORS_ALLOW_METHODS="GET,PUT,POST,DELETE"
export SERVER_WEBAUTHN_RELYING_PARTY_ORIGINS="https://hanko.io,android:apk-key-hash:nLSu7w..."
```


Expand All @@ -35,23 +35,19 @@ server:
# Cross Origin Resource Sharing for public endpoints.
#
cors:
## enabled ##
## allow_origins ##
#
# Sets whether cors is enabled or not.
# A list of allowed origins that may access the public endpoints.
#
# Default value: false
#
enabled: false
allow_credentials: false
allow_origins:
- "*"
allow_methods:
- ""
allow_headers:
- ""
expose_headers:
- ""
max_age: 0
- "https://hanko.io"
- "https://example.com"
## unsafe_wildcard_origin_allowed ##
#
# If allow_origins contains a wildcard '*' origin, this flag must explicitly be set to 'true'.
# Using wildcard '*' origins is insecure and potentially leads to cross-origin attacks.
#
unsafe_wildcard_origin_allowed: false
## admin ##
#
# Configuration for the admin API.
Expand Down Expand Up @@ -259,23 +255,11 @@ webauthn:
# - Acme, Inc.
#
display_name: ""
## origin ##
#
# DEPRECATED: use "origins" instead
#
# The origin for which WebAuthn credentials will be accepted by the server. Must include the protocol and can only be the effective domain,
# or a registrable domain suffix of the effective domain, as specified in the id. Except for localhost, the protocol must always be https for WebAuthn to work.
#
# Example:
# - http://localhost
# - https://example.com
# - https://subdomain.example.com
#
origin: "http://localhost"
## origins ##
#
# A list of origins for which WebAuthn credentials will be accepted by the server. Must include the protocol and can only be the effective domain,
# or a registrable domain suffix of the effective domain, as specified in the id. Except for localhost, the protocol must always be https for WebAuthn to work.
# Ip Addresses will not work.
#
# For an Android app the origin must be the base64 url encoded SHA256 fingerprint of the signing certificate.
#
Expand Down
2 changes: 1 addition & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/knadh/koanf v1.5.0
github.com/labstack/echo-contrib v0.14.0
github.com/labstack/echo-jwt/v4 v4.1.0
github.com/labstack/echo/v4 v4.10.1
github.com/labstack/echo/v4 v4.10.2
github.com/lestrrat-go/jwx/v2 v2.0.9
github.com/lib/pq v1.10.7
github.com/nicksnyder/go-i18n/v2 v2.2.1
Expand Down
4 changes: 2 additions & 2 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,8 @@ github.com/labstack/echo-contrib v0.14.0 h1:tVHJjhqOcB183bzAeNDVwKgf1GWRAM6k9PvI
github.com/labstack/echo-contrib v0.14.0/go.mod h1:0tmJZUHWLU7zGvMoxZwotRxHgUqBfW37T6bHg17SgAw=
github.com/labstack/echo-jwt/v4 v4.1.0 h1:eYGBxauPkyzBM78KJbR5OSz5uhKMDkhJZhTTIuoH6Pg=
github.com/labstack/echo-jwt/v4 v4.1.0/go.mod h1:DHSSaL6cTgczdPXjf8qrTHRbrau2flcddV7CPMs2U/Y=
github.com/labstack/echo/v4 v4.10.1 h1:rB+D8In9PWjsp1OpHaqK+t04nQv/SBD1IoIcXCg0lpY=
github.com/labstack/echo/v4 v4.10.1/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
Expand Down
1 change: 0 additions & 1 deletion backend/handler/passcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,6 @@ func (h *PasscodeHandler) Finish(c echo.Context) error {

if h.cfg.Session.EnableAuthTokenHeader {
c.Response().Header().Set("X-Auth-Token", token)
c.Response().Header().Set("Access-Control-Expose-Headers", "X-Auth-Token")
}

err = h.auditLogger.Create(c, models.AuditLogPasscodeLoginFinalSucceeded, user, nil)
Expand Down
1 change: 0 additions & 1 deletion backend/handler/password.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,6 @@ func (h *PasswordHandler) Login(c echo.Context) error {

if h.cfg.Session.EnableAuthTokenHeader {
c.Response().Header().Set("X-Auth-Token", token)
c.Response().Header().Set("Access-Control-Expose-Headers", "X-Auth-Token")
}

err = h.auditLogger.Create(c, models.AuditLogPasswordLoginSucceeded, user, nil)
Expand Down
1 change: 1 addition & 0 deletions backend/handler/thirdparty.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func (h *ThirdPartyHandler) Callback(c echo.Context) error {
}

err = h.auditLogger.Create(c, accountLinkingResult.Type, accountLinkingResult.User, nil)

if err != nil {
return h.redirectError(c, thirdparty.ErrorServer("could not create audit log").WithCause(err), h.cfg.ThirdParty.ErrorRedirectURL)
}
Expand Down
1 change: 0 additions & 1 deletion backend/handler/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ func (h TokenHandler) Validate(c echo.Context) error {

if h.cfg.Session.EnableAuthTokenHeader {
c.Response().Header().Set("X-Auth-Token", jwtToken)
c.Response().Header().Set("Access-Control-Expose-Headers", "X-Auth-Token")
}

userID = token.UserID
Expand Down
1 change: 0 additions & 1 deletion backend/handler/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ func (h *UserHandler) Create(c echo.Context) error {

if h.cfg.Session.EnableAuthTokenHeader {
c.Response().Header().Set("X-Auth-Token", token)
c.Response().Header().Set("Access-Control-Expose-Headers", "X-Auth-Token")
}
}

Expand Down
2 changes: 0 additions & 2 deletions backend/handler/webauthn.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ func NewWebauthnHandler(cfg *config.Config, persister persistence.Persister, ses
wa, err := webauthn.New(&webauthn.Config{
RPDisplayName: cfg.Webauthn.RelyingParty.DisplayName,
RPID: cfg.Webauthn.RelyingParty.Id,
RPOrigin: cfg.Webauthn.RelyingParty.Origin,
RPOrigins: cfg.Webauthn.RelyingParty.Origins,
AttestationPreference: protocol.PreferNoAttestation,
AuthenticatorSelection: protocol.AuthenticatorSelection{
Expand Down Expand Up @@ -404,7 +403,6 @@ func (h *WebauthnHandler) FinishAuthentication(c echo.Context) error {

if h.cfg.Session.EnableAuthTokenHeader {
c.Response().Header().Set("X-Auth-Token", token)
c.Response().Header().Set("Access-Control-Expose-Headers", "X-Auth-Token")
}

err = h.auditLogger.Create(c, models.AuditLogWebAuthnAuthenticationFinalSucceeded, user, nil)
Expand Down
2 changes: 1 addition & 1 deletion backend/handler/webauthn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ var defaultConfig = config.Config{
Id: "localhost",
DisplayName: "Test Relying Party",
Icon: "",
Origin: "http://localhost:8080",
Origins: []string{"http://localhost:8080"},
},
Timeout: 60000,
},
Expand Down
26 changes: 17 additions & 9 deletions backend/server/public_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/labstack/echo-contrib/prometheus"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/sethvargo/go-limiter/httplimit"
"github.com/teamhanko/hanko/backend/audit_log"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/crypto/jwk"
Expand All @@ -24,16 +25,23 @@ func NewPublicRouter(cfg *config.Config, persister persistence.Persister, promet
e.Use(middleware.RequestID())
e.Use(hankoMiddleware.GetLoggerMiddleware())

if cfg.Server.Public.Cors.Enabled {
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: cfg.Server.Public.Cors.AllowOrigins,
AllowMethods: cfg.Server.Public.Cors.AllowMethods,
AllowHeaders: cfg.Server.Public.Cors.AllowHeaders,
ExposeHeaders: cfg.Server.Public.Cors.ExposeHeaders,
AllowCredentials: cfg.Server.Public.Cors.AllowCredentials,
MaxAge: cfg.Server.Public.Cors.MaxAge,
}))
exposeHeader := []string{
httplimit.HeaderRetryAfter,
httplimit.HeaderRateLimitLimit,
httplimit.HeaderRateLimitRemaining,
httplimit.HeaderRateLimitReset,
}
if cfg.Session.EnableAuthTokenHeader {
exposeHeader = append(exposeHeader, "X-Auth-Token")
}
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
UnsafeWildcardOriginWithAllowCredentials: cfg.Server.Public.Cors.UnsafeWildcardOriginAllowed,
AllowOrigins: cfg.Server.Public.Cors.AllowOrigins,
ExposeHeaders: exposeHeader,
AllowCredentials: true,
// Based on: Chromium (starting in v76) caps at 2 hours (7200 seconds).
MaxAge: 7200,
}))

if prometheus != nil {
e.Use(prometheus.HandlerFunc)
Expand Down
Loading