From 348eb7716b887603d1370a985413a8f1be60cd55 Mon Sep 17 00:00:00 2001 From: Jeremy Yen Date: Wed, 20 Sep 2023 18:07:01 +0800 Subject: [PATCH 1/3] Add getShaInvBinding and apply --- internal/config.go | 1 + internal/http_server.go | 29 +++++++++++++++++++------- supporting-containers/nginx/nginx.conf | 2 ++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/internal/config.go b/internal/config.go index b204a68..a32be61 100644 --- a/internal/config.go +++ b/internal/config.go @@ -45,6 +45,7 @@ type Config struct { SitesToProtectedPathExceptions map[string][]string `yaml:"password_protected_path_exceptions"` SitesToPasswordHashesRoaming map[string]string `yaml:"password_hash_roaming"` SitesToPasswordCookieTtlSeconds map[string]int `yaml:"password_persite_cookie_ttl_seconds"` + SitesToUseUserAgentInShaInv map[string]bool `yaml:"use_user_agent_in_sha_inv"` ExpiringDecisionTtlSeconds int `yaml:"expiring_decision_ttl_seconds"` TooManyFailedChallengesIntervalSeconds int `yaml:"too_many_failed_challenges_interval_seconds"` TooManyFailedChallengesThreshold int `yaml:"too_many_failed_challenges_threshold"` diff --git a/internal/http_server.go b/internal/http_server.go index b77f99d..52de63a 100644 --- a/internal/http_server.go +++ b/internal/http_server.go @@ -223,8 +223,14 @@ func accessDenied(c *gin.Context, config *Config, decisionListResultString strin c.String(403, "access denied\n") } -func challenge(c *gin.Context, cookieName string, cookieTtlSeconds int, secret string, setDomainScope bool) { - newCookie := NewChallengeCookie(secret, cookieTtlSeconds, c.Request.Header.Get("X-Client-IP")) +func challenge( + c *gin.Context, + config *Config, + cookieName string, + cookieTtlSeconds int, + secret string, + setDomainScope bool) { + newCookie := NewChallengeCookie(secret, cookieTtlSeconds, getShaInvBinding(c, config)) // log.Println("Serving new cookie: ", newCookie) domainScope := "" // Provide "" to domain so that the cookie is not set for subdomains, EX: example.com if setDomainScope { @@ -235,16 +241,25 @@ func challenge(c *gin.Context, cookieName string, cookieTtlSeconds int, secret s c.Header("Cache-Control", "no-cache,no-store") } +func getShaInvBinding(c *gin.Context, config *Config) string { + // Get binding either from IP or User-Agent base on config + _, ok := config.SitesToUseUserAgentInShaInv[c.Request.Header.Get("X-Requested-Host")] + if ok { + return c.Request.Header.Get("X-Client-User-Agent") + } + return c.Request.Header.Get("X-Client-IP") +} + func passwordChallenge(c *gin.Context, config *Config, roaming bool) { cookieTtl := getPerSiteCookieTtlOrDefault(config, c.Request.Header.Get("X-Requested-Host"), config.PasswordCookieTtlSeconds) - challenge(c, "deflect_password3", cookieTtl, config.HmacSecret, roaming) + challenge(c, config, "deflect_password3", cookieTtl, config.HmacSecret, roaming) sessionCookieEndPoint(c, config) c.Data(401, "text/html", applyArgsToPasswordPage(config, config.PasswordPageBytes, roaming, cookieTtl)) c.Abort() } func shaInvChallenge(c *gin.Context, config *Config) { - challenge(c, "deflect_challenge3", config.ShaInvCookieTtlSeconds, config.HmacSecret, false) + challenge(c, config, "deflect_challenge3", config.ShaInvCookieTtlSeconds, config.HmacSecret, false) sessionCookieEndPoint(c, config) c.Data(429, "text/html", applyCookieMaxAge(config.ChallengerBytes, "deflect_challenge3", config.ShaInvCookieTtlSeconds)) c.Abort() @@ -456,7 +471,7 @@ func sendOrValidateShaChallenge( challengeCookie, err := c.Cookie("deflect_challenge3") requestedMethod := c.Request.Method if err == nil { - err := ValidateShaInvCookie(config.HmacSecret, challengeCookie, time.Now(), clientIp, 10) // XXX config + err := ValidateShaInvCookie(config.HmacSecret, challengeCookie, time.Now(), getShaInvBinding(c, config), 10) // XXX config if err != nil { // log.Println("Sha-inverse challenge failed") // log.Println(err) @@ -566,13 +581,13 @@ func sendOrValidatePassword( return sendOrValidatePasswordResult } // XXX maybe don't call this err? - err := ValidatePasswordCookie(config.HmacSecret, passwordCookie, time.Now(), clientIp, expectedHashedPassword) + err := ValidatePasswordCookie(config.HmacSecret, passwordCookie, time.Now(), getShaInvBinding(c, config), expectedHashedPassword) if err != nil { // Password fail, but provide second chance if password_hash_roaming is set expectedHashedPassword2, hasPasswordRoaming := passwordProtectedPaths.SiteToRoamingPasswordHash[requestedHost] if hasPasswordRoaming { // log.Printf("Password challenge failed, but password_hash_roaming is set for %s, checking that", requestedHost) - err := ValidatePasswordCookie(config.HmacSecret, passwordCookie, time.Now(), clientIp, expectedHashedPassword2) + err := ValidatePasswordCookie(config.HmacSecret, passwordCookie, time.Now(), getShaInvBinding(c, config), expectedHashedPassword2) if err == nil { // roaming password passed, we do not record fail specifically for roaming fail accessGranted(c, config, PasswordChallengeResultToString[PasswordChallengeRoamingPassed]) diff --git a/supporting-containers/nginx/nginx.conf b/supporting-containers/nginx/nginx.conf index 39dc3f1..494ae74 100644 --- a/supporting-containers/nginx/nginx.conf +++ b/supporting-containers/nginx/nginx.conf @@ -89,6 +89,8 @@ http { proxy_set_header X-Requested-Host $host; proxy_set_header X-Client-IP $remote_addr; proxy_set_header X-Requested-Path $request_uri; + proxy_set_header X-Client-User-Agent $http_user_agent; + proxy_pass_request_body off; proxy_pass http://127.0.0.1:8081/auth_request?; # XXX i would rather discard the path } From 288b036e046bd485191b91bb6c7e52aba901a61b Mon Sep 17 00:00:00 2001 From: Jeremy Yen Date: Wed, 20 Sep 2023 19:39:39 +0800 Subject: [PATCH 2/3] Add config --- banjax-config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/banjax-config.yaml b/banjax-config.yaml index 0b8f01d..e59fc72 100644 --- a/banjax-config.yaml +++ b/banjax-config.yaml @@ -109,3 +109,5 @@ session_cookie_hmac_secret: some_secret session_cookie_ttl_seconds: 3600 sites_to_disable_baskerville: localhost: true +use_user_agent_in_sha_inv: + localhost: true From 639167ee9cea757468c97d59f4c1f5d057496a57 Mon Sep 17 00:00:00 2001 From: Jeremy Yen Date: Wed, 27 Sep 2023 16:03:57 +0800 Subject: [PATCH 3/3] Rename to match actual use --- banjax-config.yaml | 2 +- internal/config.go | 2 +- internal/http_server.go | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/banjax-config.yaml b/banjax-config.yaml index e59fc72..76f8d69 100644 --- a/banjax-config.yaml +++ b/banjax-config.yaml @@ -109,5 +109,5 @@ session_cookie_hmac_secret: some_secret session_cookie_ttl_seconds: 3600 sites_to_disable_baskerville: localhost: true -use_user_agent_in_sha_inv: +use_user_agent_in_cookie: localhost: true diff --git a/internal/config.go b/internal/config.go index a32be61..12f5678 100644 --- a/internal/config.go +++ b/internal/config.go @@ -45,7 +45,7 @@ type Config struct { SitesToProtectedPathExceptions map[string][]string `yaml:"password_protected_path_exceptions"` SitesToPasswordHashesRoaming map[string]string `yaml:"password_hash_roaming"` SitesToPasswordCookieTtlSeconds map[string]int `yaml:"password_persite_cookie_ttl_seconds"` - SitesToUseUserAgentInShaInv map[string]bool `yaml:"use_user_agent_in_sha_inv"` + SitesToUseUserAgentInCookie map[string]bool `yaml:"use_user_agent_in_cookie"` ExpiringDecisionTtlSeconds int `yaml:"expiring_decision_ttl_seconds"` TooManyFailedChallengesIntervalSeconds int `yaml:"too_many_failed_challenges_interval_seconds"` TooManyFailedChallengesThreshold int `yaml:"too_many_failed_challenges_threshold"` diff --git a/internal/http_server.go b/internal/http_server.go index 52de63a..b016845 100644 --- a/internal/http_server.go +++ b/internal/http_server.go @@ -230,7 +230,7 @@ func challenge( cookieTtlSeconds int, secret string, setDomainScope bool) { - newCookie := NewChallengeCookie(secret, cookieTtlSeconds, getShaInvBinding(c, config)) + newCookie := NewChallengeCookie(secret, cookieTtlSeconds, getUserAgentOrIp(c, config)) // log.Println("Serving new cookie: ", newCookie) domainScope := "" // Provide "" to domain so that the cookie is not set for subdomains, EX: example.com if setDomainScope { @@ -241,9 +241,9 @@ func challenge( c.Header("Cache-Control", "no-cache,no-store") } -func getShaInvBinding(c *gin.Context, config *Config) string { +func getUserAgentOrIp(c *gin.Context, config *Config) string { // Get binding either from IP or User-Agent base on config - _, ok := config.SitesToUseUserAgentInShaInv[c.Request.Header.Get("X-Requested-Host")] + _, ok := config.SitesToUseUserAgentInCookie[c.Request.Header.Get("X-Requested-Host")] if ok { return c.Request.Header.Get("X-Client-User-Agent") } @@ -471,7 +471,7 @@ func sendOrValidateShaChallenge( challengeCookie, err := c.Cookie("deflect_challenge3") requestedMethod := c.Request.Method if err == nil { - err := ValidateShaInvCookie(config.HmacSecret, challengeCookie, time.Now(), getShaInvBinding(c, config), 10) // XXX config + err := ValidateShaInvCookie(config.HmacSecret, challengeCookie, time.Now(), getUserAgentOrIp(c, config), 10) // XXX config if err != nil { // log.Println("Sha-inverse challenge failed") // log.Println(err) @@ -581,13 +581,13 @@ func sendOrValidatePassword( return sendOrValidatePasswordResult } // XXX maybe don't call this err? - err := ValidatePasswordCookie(config.HmacSecret, passwordCookie, time.Now(), getShaInvBinding(c, config), expectedHashedPassword) + err := ValidatePasswordCookie(config.HmacSecret, passwordCookie, time.Now(), getUserAgentOrIp(c, config), expectedHashedPassword) if err != nil { // Password fail, but provide second chance if password_hash_roaming is set expectedHashedPassword2, hasPasswordRoaming := passwordProtectedPaths.SiteToRoamingPasswordHash[requestedHost] if hasPasswordRoaming { // log.Printf("Password challenge failed, but password_hash_roaming is set for %s, checking that", requestedHost) - err := ValidatePasswordCookie(config.HmacSecret, passwordCookie, time.Now(), getShaInvBinding(c, config), expectedHashedPassword2) + err := ValidatePasswordCookie(config.HmacSecret, passwordCookie, time.Now(), getUserAgentOrIp(c, config), expectedHashedPassword2) if err == nil { // roaming password passed, we do not record fail specifically for roaming fail accessGranted(c, config, PasswordChallengeResultToString[PasswordChallengeRoamingPassed])