diff --git a/banjax-config.yaml b/banjax-config.yaml index c5a93db..8f968cd 100644 --- a/banjax-config.yaml +++ b/banjax-config.yaml @@ -34,6 +34,8 @@ password_hashes: "localhost": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" password_hash_roaming: sub.example.com: example.com +password_persite_cookie_ttl_seconds: + example.com: 3600 per_site_decision_lists: example.com: allow: diff --git a/banjax_base_test.go b/banjax_base_test.go index fe6c5a4..48d6ce2 100644 --- a/banjax_base_test.go +++ b/banjax_base_test.go @@ -119,7 +119,7 @@ func httpCheck(client *http.Client, resource_ptr *TestResource, t *testing.T) { } } -type CookieMap map[string]string +type CookieMap map[string]*http.Cookie func httpTesterWithCookie(t *testing.T, resources []TestResource) { client := &http.Client{} @@ -128,6 +128,11 @@ func httpTesterWithCookie(t *testing.T, resources []TestResource) { t.Run(test_name, func(t *testing.T) { cookies := httpCheckWithCookie(client, &resource, t) assert.Contains(t, cookies, resource.contains[0]) + if len(resource.contains) > 1 { + log.Print(cookies[resource.contains[0]]) + expectedMaxAge, _ := strconv.Atoi(resource.contains[1]) + assert.Equal(t, cookies[resource.contains[0]].MaxAge, expectedMaxAge) + } }) } } @@ -140,9 +145,8 @@ func httpCheckWithCookie(client *http.Client, resource_ptr *TestResource, t *tes cookieMap = make(CookieMap) if len(resp.Cookies()) > 0 { - log.Print(resp.Cookies()) for _, cookie := range resp.Cookies() { - cookieMap[cookie.Name] = cookie.Value + cookieMap[cookie.Name] = cookie } } diff --git a/banjax_integration_test.go b/banjax_integration_test.go index 11c566d..6c4ff94 100644 --- a/banjax_integration_test.go +++ b/banjax_integration_test.go @@ -21,8 +21,15 @@ func TestPathWithChallengeCookies(t *testing.T) { prefix := "/auth_request?path=" httpTesterWithCookie(t, []TestResource{ {"GET", prefix + "/wp-admin", 401, nil, []string{"deflect_password3"}}, + {"GET", prefix + "/wp-admin", 401, nil, []string{"deflect_password3", "3600"}}, // testing max-age {"GET", prefix + "/global_mask_64_ban", 429, ClientIP("192.168.1.64"), []string{"deflect_challenge3"}}, }) + + // reload without per site max age, test if default value 14400 present + reloadConfig(fixtureConfigTestReloadCIDR, 1) + httpTesterWithCookie(t, []TestResource{ + {"GET", prefix + "/wp-admin", 401, nil, []string{"deflect_password3", "14400"}}, // testing max-age + }) } func TestGlobalPerSiteDecisionListsMask(t *testing.T) { diff --git a/fixtures/banjax-config-test.yaml b/fixtures/banjax-config-test.yaml index 85d996c..7af5c27 100644 --- a/fixtures/banjax-config-test.yaml +++ b/fixtures/banjax-config-test.yaml @@ -45,6 +45,8 @@ password_protected_path_exceptions: password_hashes: "localhost:8081": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" "localhost": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" +password_persite_cookie_ttl_seconds: + "localhost:8081": 3600 per_site_decision_lists: example.com: allow: diff --git a/internal/config.go b/internal/config.go index 9a43c7c..c4faa55 100644 --- a/internal/config.go +++ b/internal/config.go @@ -44,6 +44,7 @@ type Config struct { SitesToProtectedPaths map[string][]string `yaml:"password_protected_paths"` 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"` 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 f32b304..15a567a 100644 --- a/internal/http_server.go +++ b/internal/http_server.go @@ -234,9 +234,10 @@ func challenge(c *gin.Context, cookieName string, cookieTtlSeconds int, secret s } func passwordChallenge(c *gin.Context, config *Config, roaming bool) { - challenge(c, "deflect_password3", config.PasswordCookieTtlSeconds, config.HmacSecret, roaming) + cookieTtl := getPerSiteCookieTtlOrDefault(config, c.Request.Header.Get("X-Requested-Host"), config.PasswordCookieTtlSeconds) + challenge(c, "deflect_password3", cookieTtl, config.HmacSecret, roaming) // custom status code, not defined in RFC - c.Data(401, "text/html", applyArgsToPasswordPage(config, config.PasswordPageBytes, roaming)) + c.Data(401, "text/html", applyArgsToPasswordPage(config, config.PasswordPageBytes, roaming, cookieTtl)) c.Abort() } @@ -247,6 +248,14 @@ func shaInvChallenge(c *gin.Context, config *Config) { c.Abort() } +func getPerSiteCookieTtlOrDefault(config *Config, domain string, defaultTtl int) (cookieTtl int) { + cookieTtl, ok := config.SitesToPasswordCookieTtlSeconds[domain] + if ok { + return + } + return defaultTtl +} + func modifyHTMLContent(pageBytes []byte, targetStr string, toReplace string) (modifiedPageBytes []byte) { return bytes.Replace(pageBytes, []byte(targetStr), []byte(toReplace), 1) } @@ -275,9 +284,9 @@ func applyCookieDomain(pageBytes []byte, cookieName string) (modifiedPageBytes [ ) } -func applyArgsToPasswordPage(config *Config, pageBytes []byte, roaming bool) (modifiedPageBytes []byte) { +func applyArgsToPasswordPage(config *Config, pageBytes []byte, roaming bool, cookieTtl int) (modifiedPageBytes []byte) { // apply default or site specific expire time - modifiedPageBytes = applyCookieMaxAge(pageBytes, "deflect_password3", config.PasswordCookieTtlSeconds) + modifiedPageBytes = applyCookieMaxAge(pageBytes, "deflect_password3", cookieTtl) if !roaming { return