feat: implement JWT caching (#2928)
This commit is contained in:
@@ -22,6 +22,7 @@ jwt-aud = ""
|
||||
jwt-role-claim-key = ".\"aliased\""
|
||||
jwt-secret = ""
|
||||
jwt-secret-is-base64 = true
|
||||
jwt-cache-max-lifetime = 0
|
||||
log-level = "error"
|
||||
openapi-mode = "follow-privileges"
|
||||
openapi-security-active = false
|
||||
|
||||
@@ -22,6 +22,7 @@ jwt-aud = ""
|
||||
jwt-role-claim-key = ".\"role\""
|
||||
jwt-secret = ""
|
||||
jwt-secret-is-base64 = true
|
||||
jwt-cache-max-lifetime = 0
|
||||
log-level = "error"
|
||||
openapi-mode = "follow-privileges"
|
||||
openapi-security-active = false
|
||||
|
||||
@@ -22,6 +22,7 @@ jwt-aud = ""
|
||||
jwt-role-claim-key = ".\"role\""
|
||||
jwt-secret = ""
|
||||
jwt-secret-is-base64 = true
|
||||
jwt-cache-max-lifetime = 0
|
||||
log-level = "error"
|
||||
openapi-mode = "follow-privileges"
|
||||
openapi-security-active = false
|
||||
|
||||
@@ -22,6 +22,7 @@ jwt-aud = ""
|
||||
jwt-role-claim-key = ".\"role\""
|
||||
jwt-secret = ""
|
||||
jwt-secret-is-base64 = false
|
||||
jwt-cache-max-lifetime = 0
|
||||
log-level = "error"
|
||||
openapi-mode = "follow-privileges"
|
||||
openapi-security-active = false
|
||||
|
||||
@@ -22,6 +22,7 @@ jwt-aud = "https://otherexample.org"
|
||||
jwt-role-claim-key = ".\"other\".\"pre_config_role\""
|
||||
jwt-secret = "ODERREALLYREALLYREALLYREALLYVERYSAFE"
|
||||
jwt-secret-is-base64 = true
|
||||
jwt-cache-max-lifetime = 86400
|
||||
log-level = "info"
|
||||
openapi-mode = "disabled"
|
||||
openapi-security-active = false
|
||||
|
||||
@@ -22,6 +22,7 @@ jwt-aud = "https://example.org"
|
||||
jwt-role-claim-key = ".\"a\".\"role\""
|
||||
jwt-secret = "OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE"
|
||||
jwt-secret-is-base64 = false
|
||||
jwt-cache-max-lifetime = 86400
|
||||
log-level = "info"
|
||||
openapi-mode = "ignore-privileges"
|
||||
openapi-security-active = true
|
||||
|
||||
@@ -22,6 +22,7 @@ jwt-aud = "https://postgrest.org"
|
||||
jwt-role-claim-key = ".\"user\"[0].\"real-role\""
|
||||
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
|
||||
jwt-secret-is-base64 = true
|
||||
jwt-cache-max-lifetime = 86400
|
||||
log-level = "info"
|
||||
openapi-mode = "ignore-privileges"
|
||||
openapi-security-active = true
|
||||
|
||||
@@ -22,6 +22,7 @@ jwt-aud = ""
|
||||
jwt-role-claim-key = ".\"role\""
|
||||
jwt-secret = ""
|
||||
jwt-secret-is-base64 = false
|
||||
jwt-cache-max-lifetime = 0
|
||||
log-level = "error"
|
||||
openapi-mode = "follow-privileges"
|
||||
openapi-security-active = false
|
||||
|
||||
@@ -24,6 +24,7 @@ PGRST_JWT_AUD: 'https://postgrest.org'
|
||||
PGRST_JWT_ROLE_CLAIM_KEY: '.user[0]."real-role"'
|
||||
PGRST_JWT_SECRET: c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5
|
||||
PGRST_JWT_SECRET_IS_BASE64: true
|
||||
PGRST_JWT_CACHE_MAX_LIFETIME: 86400
|
||||
PGRST_LOG_LEVEL: info
|
||||
PGRST_OPENAPI_MODE: 'ignore-privileges'
|
||||
PGRST_OPENAPI_SECURITY_ACTIVE: true
|
||||
|
||||
@@ -22,6 +22,7 @@ jwt-aud = "https://postgrest.org"
|
||||
jwt-role-claim-key = ".user[0].\"real-role\""
|
||||
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
|
||||
jwt-secret-is-base64 = true
|
||||
jwt-cache-max-lifetime = 86400
|
||||
log-level = "info"
|
||||
openapi-mode = "ignore-privileges"
|
||||
openapi-security-active = true
|
||||
|
||||
+120
-1
@@ -1,6 +1,6 @@
|
||||
"Unit tests for Input/Ouput of PostgREST seen as a black box."
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from operator import attrgetter
|
||||
import os
|
||||
import re
|
||||
@@ -1095,3 +1095,122 @@ def test_fail_with_automatic_recovery_disabled_and_terminated_using_query(defaul
|
||||
|
||||
exitCode = wait_until_exit(postgrest)
|
||||
assert exitCode == 1
|
||||
|
||||
|
||||
def test_server_timing_jwt_should_decrease_on_subsequent_requests(defaultenv):
|
||||
"assert that server-timing duration for JWT should decrease on subsequent requests"
|
||||
|
||||
env = {
|
||||
**defaultenv,
|
||||
"PGRST_DB_PLAN_ENABLED": "true",
|
||||
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
|
||||
"PGRST_JWT_SECRET": "@/dev/stdin",
|
||||
"PGRST_DB_CONFIG": "false",
|
||||
}
|
||||
|
||||
headers = jwtauthheader(
|
||||
{
|
||||
"role": "postgrest_test_author",
|
||||
"exp": int(
|
||||
(datetime.now(timezone.utc) + timedelta(minutes=30)).timestamp()
|
||||
),
|
||||
},
|
||||
SECRET,
|
||||
)
|
||||
|
||||
with run(stdin=SECRET.encode(), env=env) as postgrest:
|
||||
first_dur_text = postgrest.session.get(
|
||||
"/authors_only", headers=headers
|
||||
).headers["Server-Timing"]
|
||||
second_dur_text = postgrest.session.get(
|
||||
"/authors_only", headers=headers
|
||||
).headers["Server-Timing"]
|
||||
|
||||
first_dur = float(first_dur_text[8:]) # skip "jwt;dur="
|
||||
second_dur = float(second_dur_text[8:])
|
||||
|
||||
# their difference should be atleast 300, implying
|
||||
# that JWT Caching is working as expected
|
||||
assert (first_dur - second_dur) > 300.0
|
||||
|
||||
|
||||
# just added to complete code coverage
|
||||
def test_jwt_caching_works_with_db_plan_disabled(defaultenv):
|
||||
"assert that JWT caching words even when Server-Timing header is not returned"
|
||||
|
||||
env = {
|
||||
**defaultenv,
|
||||
"PGRST_DB_PLAN_ENABLED": "false",
|
||||
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
|
||||
"PGRST_JWT_SECRET": "@/dev/stdin",
|
||||
"PGRST_DB_CONFIG": "false",
|
||||
}
|
||||
|
||||
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
||||
|
||||
with run(stdin=SECRET.encode(), env=env) as postgrest:
|
||||
first_request = postgrest.session.get("/authors_only", headers=headers)
|
||||
second_request = postgrest.session.get("/authors_only", headers=headers)
|
||||
|
||||
# in this case we don't get server-timing in response headers
|
||||
# so we can't compare durations, we just check if request succeeds
|
||||
assert first_request.status_code == 200 and second_request.status_code == 200
|
||||
|
||||
|
||||
def test_server_timing_jwt_should_not_decrease_when_caching_disabled(defaultenv):
|
||||
"assert than jwt duration should not decrease when disabled"
|
||||
|
||||
env = {
|
||||
**defaultenv,
|
||||
"PGRST_DB_PLAN_ENABLED": "true",
|
||||
"PGRST_JWT_CACHE_MAX_LIFETIME": "0", # cache disabled
|
||||
"PGRST_JWT_SECRET": "@/dev/stdin",
|
||||
"PGRST_DB_CONFIG": "false",
|
||||
}
|
||||
|
||||
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
||||
|
||||
with run(stdin=SECRET.encode(), env=env) as postgrest:
|
||||
warmup_req = postgrest.session.get("/authors_only", headers=headers)
|
||||
first_dur_text = postgrest.session.get(
|
||||
"/authors_only", headers=headers
|
||||
).headers["Server-Timing"]
|
||||
second_dur_text = postgrest.session.get(
|
||||
"/authors_only", headers=headers
|
||||
).headers["Server-Timing"]
|
||||
|
||||
first_dur = float(first_dur_text[8:]) # skip "jwt;dur="
|
||||
second_dur = float(second_dur_text[8:])
|
||||
|
||||
# their difference should be less than 100
|
||||
# implying that token is not cached
|
||||
assert (first_dur - second_dur) < 100.0
|
||||
|
||||
|
||||
def test_jwt_cache_with_no_exp_claim(defaultenv):
|
||||
"assert than jwt duration should decrease"
|
||||
|
||||
env = {
|
||||
**defaultenv,
|
||||
"PGRST_DB_PLAN_ENABLED": "true",
|
||||
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
|
||||
"PGRST_JWT_SECRET": "@/dev/stdin",
|
||||
"PGRST_DB_CONFIG": "false",
|
||||
}
|
||||
|
||||
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET) # no exp
|
||||
|
||||
with run(stdin=SECRET.encode(), env=env) as postgrest:
|
||||
first_dur_text = postgrest.session.get(
|
||||
"/authors_only", headers=headers
|
||||
).headers["Server-Timing"]
|
||||
second_dur_text = postgrest.session.get(
|
||||
"/authors_only", headers=headers
|
||||
).headers["Server-Timing"]
|
||||
|
||||
first_dur = float(first_dur_text[8:]) # skip "jwt;dur="
|
||||
second_dur = float(second_dur_text[8:])
|
||||
|
||||
# their difference should be less than 100
|
||||
# implying that token is not cached
|
||||
assert (first_dur - second_dur) > 300.0
|
||||
|
||||
@@ -102,7 +102,7 @@ postJsonArrayTest(){
|
||||
|
||||
echo "Running memory usage tests.."
|
||||
|
||||
jsonKeyTest "1M" "POST" "/rpc/leak?columns=blob" "16M"
|
||||
jsonKeyTest "1M" "POST" "/rpc/leak?columns=blob" "23M"
|
||||
jsonKeyTest "1M" "POST" "/leak?columns=blob" "16M"
|
||||
jsonKeyTest "1M" "PATCH" "/leak?id=eq.1&columns=blob" "16M"
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
|
||||
, configJwtRoleClaimKey = [JSPKey "role"]
|
||||
, configJwtSecret = secret
|
||||
, configJwtSecretIsBase64 = False
|
||||
, configJwtCacheMaxLifetime = 0
|
||||
, configLogLevel = LogCrit
|
||||
, configOpenApiMode = OAFollowPriv
|
||||
, configOpenApiSecurityActive = False
|
||||
|
||||
Reference in New Issue
Block a user