feat(lib): Add optional restrictions for JWT based on a specific header value (#697)
* Add JWTRestrictionHeader funktionality * Add JWTRestrictionHeader to docs * Move JWT_RESTRICTION_HEADER from advanced section to normal one * Add rull request URL to Changelog * Set default value of JWT_RESTRICTION_HEADER to X-Real-IP
This commit is contained in:
parent
83503525f2
commit
ff691dfee8
5 changed files with 79 additions and 46 deletions
|
|
@ -83,6 +83,7 @@ var (
|
||||||
thothInsecure = flag.Bool("thoth-insecure", false, "if set, connect to Thoth over plain HTTP/2, don't enable this unless support told you to")
|
thothInsecure = flag.Bool("thoth-insecure", false, "if set, connect to Thoth over plain HTTP/2, don't enable this unless support told you to")
|
||||||
thothURL = flag.String("thoth-url", "", "if set, URL for Thoth, the IP reputation database for Anubis")
|
thothURL = flag.String("thoth-url", "", "if set, URL for Thoth, the IP reputation database for Anubis")
|
||||||
thothToken = flag.String("thoth-token", "", "if set, API token for Thoth, the IP reputation database for Anubis")
|
thothToken = flag.String("thoth-token", "", "if set, API token for Thoth, the IP reputation database for Anubis")
|
||||||
|
jwtRestrictionHeader = flag.String("jwt-restriction-header", "X-Real-IP", "If set, the JWT is only valid if the current value of this header matched the value when the JWT was created")
|
||||||
)
|
)
|
||||||
|
|
||||||
func keyFromHex(value string) (ed25519.PrivateKey, error) {
|
func keyFromHex(value string) (ed25519.PrivateKey, error) {
|
||||||
|
|
@ -414,6 +415,7 @@ func main() {
|
||||||
OpenGraph: policy.OpenGraph,
|
OpenGraph: policy.OpenGraph,
|
||||||
CookieSecure: *cookieSecure,
|
CookieSecure: *cookieSecure,
|
||||||
PublicUrl: *publicUrl,
|
PublicUrl: *publicUrl,
|
||||||
|
JWTRestrictionHeader: *jwtRestrictionHeader,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("can't construct libanubis.Server: %v", err)
|
log.Fatalf("can't construct libanubis.Server: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- The default `favicon` pattern in `data/common/keep-internet-working.yaml` has been updated to permit requests for png/gif/jpg/svg files as well as ico.
|
- The default `favicon` pattern in `data/common/keep-internet-working.yaml` has been updated to permit requests for png/gif/jpg/svg files as well as ico.
|
||||||
- The `--cookie-prefix` flag has been fixed so that it is fully respected.
|
- The `--cookie-prefix` flag has been fixed so that it is fully respected.
|
||||||
- The default patterns in `data/common/keep-internet-working.yaml` have been updated to appropriately escape the '.' character in the regular expression patterns.
|
- The default patterns in `data/common/keep-internet-working.yaml` have been updated to appropriately escape the '.' character in the regular expression patterns.
|
||||||
|
- Add optional restrictions for JWT based on the value of a header ([#697](https://github.com/TecharoHQ/anubis/pull/697))
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ Anubis uses these environment variables for configuration:
|
||||||
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
||||||
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. When running multiple instances on the same base domain, the key must be the same across all instances. See below for details. |
|
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. When running multiple instances on the same base domain, the key must be the same across all instances. See below for details. |
|
||||||
| `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. |
|
| `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. |
|
||||||
|
| `JWT_RESTRICTION_HEADER` | `X-Real-IP` | If set, the JWT is only valid if the current value of this header matches the value when the JWT was created. You can use it e.g. to restrict a JWT to the source IP of the user using `X-Real-IP`. |
|
||||||
| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
|
| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
|
||||||
| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
|
| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
|
||||||
| `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
| `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,13 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.opts.JWTRestrictionHeader != "" && claims["restriction"] != internal.SHA256sum(r.Header.Get(s.opts.JWTRestrictionHeader)) {
|
||||||
|
lg.Debug("JWT restriction header is invalid")
|
||||||
|
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||||
|
s.RenderIndex(w, r, cr, rule, httpStatusOnly)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
r.Header.Add("X-Anubis-Status", "PASS")
|
r.Header.Add("X-Anubis-Status", "PASS")
|
||||||
s.ServeHTTPNext(w, r)
|
s.ServeHTTPNext(w, r)
|
||||||
}
|
}
|
||||||
|
|
@ -484,12 +491,33 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate JWT cookie
|
// generate JWT cookie
|
||||||
tokenString, err := s.signJWT(jwt.MapClaims{
|
var tokenString string
|
||||||
|
|
||||||
|
// check if JWTRestrictionHeader is set and header is in request
|
||||||
|
if s.opts.JWTRestrictionHeader != "" {
|
||||||
|
if r.Header.Get(s.opts.JWTRestrictionHeader) == "" {
|
||||||
|
lg.Error("JWTRestrictionHeader is set in config but not found in request, please check your reverse proxy config.")
|
||||||
|
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||||
|
s.respondWithError(w, r, "failed to sign JWT")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
tokenString, err = s.signJWT(jwt.MapClaims{
|
||||||
|
"challenge": chall.ID,
|
||||||
|
"method": rule.Challenge.Algorithm,
|
||||||
|
"policyRule": rule.Hash(),
|
||||||
|
"action": string(cr.Rule),
|
||||||
|
"restriction": internal.SHA256sum(r.Header.Get(s.opts.JWTRestrictionHeader)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tokenString, err = s.signJWT(jwt.MapClaims{
|
||||||
"challenge": chall.ID,
|
"challenge": chall.ID,
|
||||||
"method": rule.Challenge.Algorithm,
|
"method": rule.Challenge.Algorithm,
|
||||||
"policyRule": rule.Hash(),
|
"policyRule": rule.Hash(),
|
||||||
"action": string(cr.Rule),
|
"action": string(cr.Rule),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("failed to sign JWT", "err", err)
|
lg.Error("failed to sign JWT", "err", err)
|
||||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ type Options struct {
|
||||||
CookieSecure bool
|
CookieSecure bool
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
PublicUrl string
|
PublicUrl string
|
||||||
|
JWTRestrictionHeader string
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue