Skip to content

Commit

Permalink
feat: support for reverse proxy trusted header authentication (resolve
Browse files Browse the repository at this point in the history
  • Loading branch information
muety committed Oct 15, 2023
1 parent fc07c06 commit cee76b1
Show file tree
Hide file tree
Showing 7 changed files with 1,624 additions and 1,453 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ You can specify configuration options either via a config file (default: `config
| `security.allow_signup` /<br> `WAKAPI_ALLOW_SIGNUP` | `true` | Whether to enable user registration |
| `security.disable_frontpage` /<br> `WAKAPI_DISABLE_FRONTPAGE` | `false` | Whether to disable landing page (useful for personal instances) |
| `security.expose_metrics` /<br> `WAKAPI_EXPOSE_METRICS` | `false` | Whether to expose Prometheus metrics under `/api/metrics` |
| `security.trusted_header_auth` /<br> `WAKAPI_TRUSTED_HEADER_AUTH` | `false` | Whether to enable trusted header authentication for reverse proxies (see [#534](https://github.com/muety/wakapi/issues/534)). **Use with caution!** |
| `security.trusted_header_auth_key` /<br> `WAKAPI_TRUSTED_HEADER_AUTH_KEY` | `Remote-User` | Header field for trusted header authentication. **Caution:** proxy must be configured to strip this header from client requests! |
| `security.trust_reverse_proxy_ips` /<br> `WAKAPI_TRUST_REVERSE_PROXY_IPS` | - | Comma-separated list IPv4 or IPv6 addresses of reverse proxies to trust to handle authentication. |
| `db.host` /<br> `WAKAPI_DB_HOST` | - | Database host |
| `db.port` /<br> `WAKAPI_DB_PORT` | - | Database port |
| `db.socket` /<br> `WAKAPI_DB_SOCKET` | - | Database UNIX socket (alternative to `host`) (for MySQL only) |
Expand Down Expand Up @@ -200,6 +203,17 @@ Wakapi uses [GORM](https://gorm.io) as an ORM. As a consequence, a set of differ
* [Postgres](https://hub.docker.com/_/postgres) (_open-source as well_)
* [CockroachDB](https://www.cockroachlabs.com/docs/stable/install-cockroachdb-linux.html) (_cloud-native, distributed, Postgres-compatible API_)

## 🔐 Authentication
Wakapi supports different types of user authentication.

* **Cookie:** This method is used in the browser. Users authenticate by sending along an encrypted, secure, HTTP-only cookie (`wakapi_auth`) that was set in the server's response upon login.
* **API key:**
* **Via header:** This method is inspired by [WakaTime's auth. mechanism](https://wakatime.com/developers/#authentication) and is the common way to authenticate against API endpoints. Users set the `Authorization` header to `Basic <BASE64_TOKEN>`, where the latter part corresponds to your base64-hashed API key.
* **Vis query param:** Alternatively, users can also pass their plain API key as a query parameter (e.g. `?api_key=86648d74-19c5-452b-ba01-fb3ec70d4c2f`) in the URL with every request.
* **Trusted header:** This mechanism allows to delegate authentication to a **reverse proxy** (e.g. for SSO), that Wakapi will then trust blindly. See [#534](https://github.com/muety/wakapi/issues/534) for details.
* Must be enabled via `trusted_header_auth` and configuring `trust_reverse_proxy_ip` in the config
* Warning: This type of authentication is quite prone to misconfiguration. Make sure that your reverse proxy properly strips relevant headers from client requests.

## 🔧 API endpoints

See our [Swagger API Documentation](https://wakapi.dev/swagger-ui).
Expand Down
9 changes: 6 additions & 3 deletions config.default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,16 @@ db:
automigrate_fail_silently: false # whether to ignore schema auto-migration failures when starting up

security:
password_salt: # change this
insecure_cookies: true # should be set to 'false', except when not running with HTTPS (e.g. on localhost)
password_salt: # change this
insecure_cookies: true # should be set to 'false', except when not running with HTTPS (e.g. on localhost)
cookie_max_age: 172800
allow_signup: true
disable_frontpage: false
expose_metrics: false
enable_proxy: false # only intended for production instance at wakapi.dev
enable_proxy: false # only intended for production instance at wakapi.dev
trusted_header_auth: false # whether to enable trusted header auth for reverse proxies, use with caution!! (https://github.com/muety/wakapi/issues/534)
trusted_header_auth_key: Remote-User # header field for trusted header auth (warning: your proxy must correctly strip this header from client requests!!)
trust_reverse_proxy_ips: # single ip address of the reverse proxy which you trust to pass headers for authentication

sentry:
dsn: # leave blank to disable sentry integration
Expand Down
46 changes: 41 additions & 5 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"regexp"
Expand Down Expand Up @@ -93,11 +94,15 @@ type securityConfig struct {
EnableProxy bool `yaml:"enable_proxy" default:"false" env:"WAKAPI_ENABLE_PROXY"` // only intended for production instance at wakapi.dev
DisableFrontpage bool `yaml:"disable_frontpage" default:"false" env:"WAKAPI_DISABLE_FRONTPAGE"`
// this is actually a pepper (https://en.wikipedia.org/wiki/Pepper_(cryptography))
PasswordSalt string `yaml:"password_salt" default:"" env:"WAKAPI_PASSWORD_SALT"`
InsecureCookies bool `yaml:"insecure_cookies" default:"false" env:"WAKAPI_INSECURE_COOKIES"`
CookieMaxAgeSec int `yaml:"cookie_max_age" default:"172800" env:"WAKAPI_COOKIE_MAX_AGE"`
SecureCookie *securecookie.SecureCookie `yaml:"-"`
SessionKey []byte `yaml:"-"`
PasswordSalt string `yaml:"password_salt" default:"" env:"WAKAPI_PASSWORD_SALT"`
InsecureCookies bool `yaml:"insecure_cookies" default:"false" env:"WAKAPI_INSECURE_COOKIES"`
CookieMaxAgeSec int `yaml:"cookie_max_age" default:"172800" env:"WAKAPI_COOKIE_MAX_AGE"`
TrustedHeaderAuth bool `yaml:"trusted_header_auth" default:"false" env:"WAKAPI_TRUSTED_HEADER_AUTH"`
TrustedHeaderAuthKey string `yaml:"trusted_header_auth_key" default:"Remote-User" env:"WAKAPI_TRUSTED_HEADER_AUTH_KEY"`
TrustReverseProxyIps string `yaml:"trust_reverse_proxy_ips" default:"" env:"WAKAPI_TRUST_REVERSE_PROXY_IPS"` // comma-separated list of trusted reverse proxy ips
SecureCookie *securecookie.SecureCookie `yaml:"-"`
SessionKey []byte `yaml:"-"`
trustReverseProxyIpParsed []net.IP
}

type dbConfig struct {
Expand Down Expand Up @@ -310,6 +315,21 @@ func (c *appConfig) HeartbeatsMaxAge() time.Duration {
return d
}

func (c *securityConfig) ParseTrustReverseProxyIPs() {
c.trustReverseProxyIpParsed = make([]net.IP, 0)
for _, ip := range strings.Split(c.TrustReverseProxyIps, ",") {
if parsedIp := net.ParseIP(strings.TrimSpace(ip)); parsedIp == nil {
logbuch.Warn("failed to parse reverse proxy ip")
} else {
c.trustReverseProxyIpParsed = append(c.trustReverseProxyIpParsed, parsedIp)
}
}
}

func (c *securityConfig) TrustReverseProxyIPs() []net.IP {
return c.trustReverseProxyIpParsed
}

func (c *dbConfig) IsSQLite() bool {
return c.Dialect == "sqlite3"
}
Expand Down Expand Up @@ -409,6 +429,7 @@ func Load(configFlag string, version string) *Config {

config.Security.SecureCookie = securecookie.New(hashKey, blockKey)
config.Security.SessionKey = sessionKey
config.Security.ParseTrustReverseProxyIPs()

config.Server.BasePath = strings.TrimSuffix(config.Server.BasePath, "/")

Expand Down Expand Up @@ -450,6 +471,9 @@ func Load(configFlag string, version string) *Config {
if _, err := time.ParseDuration(config.App.HeartbeatMaxAge); err != nil {
logbuch.Fatal("invalid duration set for heartbeat_max_age")
}
if config.Security.TrustedHeaderAuth && len(config.Security.trustReverseProxyIpParsed) == 0 {
config.Security.TrustedHeaderAuth = false
}

cronParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)

Expand Down Expand Up @@ -479,3 +503,15 @@ func Load(configFlag string, version string) *Config {
Set(config)
return Get()
}

func Empty() *Config {
return &Config{
App: appConfig{},
Security: securityConfig{},
Db: dbConfig{},
Server: serverConfig{},
Subscriptions: subscriptionsConfig{},
Sentry: sentryConfig{},
Mail: mailConfig{},
}
}
Loading

0 comments on commit cee76b1

Please sign in to comment.