diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3e778a62b..2c46659f3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -131,7 +131,7 @@ jobs: strategy: fail-fast: false matrix: - kind: ['mixed', 'jwt-hs', 'jwt-hs-cache', 'jwt-hs-cache-worst', 'jwt-rsa', 'jwt-rsa-cache', 'jwt-rsa-cache-worst'] + kind: ['mixed', 'jwt', 'jwt-cache', 'jwt-cache-worst'] name: Loadtest runs-on: ubuntu-24.04 steps: diff --git a/.gitignore b/.gitignore index 5f16c95f9..eae0de7df 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ loadtest .history .docs-build gen_targets.http -gen_jwk.json +gen_jwks.json gen_private.json .pytest_cache .ruff_cache diff --git a/nix/tools/gen_rsa_materials.py b/nix/tools/gen_key_materials.py similarity index 50% rename from nix/tools/gen_rsa_materials.py rename to nix/tools/gen_key_materials.py index d961d88b5..cb26261a5 100644 --- a/nix/tools/gen_rsa_materials.py +++ b/nix/tools/gen_key_materials.py @@ -1,4 +1,4 @@ -# Generate RSA JWK/public material for loadtests. +# Generate HS & RSA JWK/public material for loadtests. import argparse import sys @@ -12,12 +12,12 @@ def main(): description="Generate RSA JWK/private key pair for loadtests" ) parser.add_argument( - "--rsa", - dest="jwk_path", - metavar="JWK_PATH", + "--jwks", + dest="jwks_path", + metavar="JWKS_PATH", type=Path, required=True, - help="Path to write the RSA JWK file", + help="Path to write the JWKS file", ) parser.add_argument( "--private-key", @@ -30,18 +30,25 @@ def main(): args = parser.parse_args() - key = jwk.JWK.generate(kty="RSA", size=4096) - private_jwk, public_jwk = key.export_private(), key.export_public() + hs = jwk.JWK.from_password("reallyreallyreallyreallyverysafe") + rsa = jwk.JWK.generate(kty="RSA", size=4096) + + jwks = jwk.JWKSet() + jwks.add(hs) + jwks.add(rsa) try: - args.jwk_path.write_text(public_jwk) - print(f"Created RSA JWK on {args.jwk_path}") + # Technically, this exports the private keys, because HS does not have the concept + # of a public key. This is not a problem for tests, though, PostgREST can verify + # tokens with the private key just as well. + args.jwks_path.write_text(jwks.export()) + print(f"Created JWKSet on {args.jwks_path}") except OSError as e: - print(f"Error writing to {args.jwk_path}:{e}", file=sys.stderr) + print(f"Error writing to {args.jwks_path}:{e}", file=sys.stderr) sys.exit(1) try: - args.private_key_path.write_text(private_jwk) + args.private_key_path.write_text(rsa.export_private()) print(f"Created private key on {args.private_key_path}") except OSError as e: print(f"Error writing to {args.private_key_path}:{e}", file=sys.stderr) diff --git a/nix/tools/generate_targets.py b/nix/tools/generate_targets.py index b8d76edc7..5f8edbd00 100644 --- a/nix/tools/generate_targets.py +++ b/nix/tools/generate_targets.py @@ -48,7 +48,7 @@ def generate_target( token = jwt.encode(payload, key, alg) return [ - f"OPTIONS {URL}/authors_only", + f"OPTIONS {URL}/authors_only?{alg}", f"Authorization: Bearer {token}", "", # blank line to separate requests ] @@ -90,7 +90,6 @@ def main(): metavar="PRIVATE_KEY_PATH", type=Path, help="Path to the RSA private key file", - default=None, ) parser.add_argument( "--worst", @@ -104,35 +103,25 @@ def main(): rsa_private_key: Optional[jwt.algorithms.RSAAlgorithm] = None - is_hs = args.private_key_path is None + nsamples = 500 # per algorithm + ntargets = 100000 - nsamples = 1000 + try: + private_key_data = args.private_key_path.read_text() + except OSError as e: + err = ( + f"Error reading RSA private key from {args.private_key_path}: " + f"{e}. Generate RSA materials first with gen_rsa_materials.py." + ) + print(err, file=sys.stderr) + sys.exit(1) - if is_hs: - ntargets = 200000 - else: - # The asymmetric targets take too long to compute so we reduce them - ntargets = 50000 - - if not is_hs: - try: - private_key_data = args.private_key_path.read_text() - except OSError as e: - err = ( - f"Error reading RSA private key from {args.private_key_path}: " - f"{e}. Generate RSA materials first with gen_rsa_materials.py." - ) - print(err, file=sys.stderr) - sys.exit(1) - - try: - rsa_private_key = jwt.algorithms.RSAAlgorithm.from_jwk(private_key_data) - except Exception as exc: # broad exception to capture parsing errors - err = ( - f"Error loading RSA private key from {args.private_key_path}: " f"{exc}" - ) - print(err, file=sys.stderr) - sys.exit(1) + try: + rsa_private_key = jwt.algorithms.RSAAlgorithm.from_jwk(private_key_data) + except Exception as exc: # broad exception to capture parsing errors + err = f"Error loading RSA private key from {args.private_key_path}: " f"{exc}" + print(err, file=sys.stderr) + sys.exit(1) print(f"Generating {ntargets} targets...") @@ -151,14 +140,19 @@ def main(): for i in range(ntargets): target = generate_target( - now, run_postgrest_time + i // 1000, rsa_private_key + now, + run_postgrest_time + i // 1000, + rsa_private_key if i % 2 == 0 else None, ) lines.extend(target) else: - targets = [generate_target(now, None, rsa_private_key) for _ in range(nsamples)] + hs_targets = [generate_target(now, None, None) for _ in range(nsamples)] + rsa_targets = [ + generate_target(now, None, rsa_private_key) for _ in range(nsamples) + ] for i in range(ntargets): - target = random.choice(targets) + target = random.choice(hs_targets if i % 2 == 0 else rsa_targets) lines.extend(target) try: diff --git a/nix/tools/loadtest.nix b/nix/tools/loadtest.nix index ea90a7392..2bd5d49ea 100644 --- a/nix/tools/loadtest.nix +++ b/nix/tools/loadtest.nix @@ -45,7 +45,7 @@ let "ARG_OPTIONAL_SINGLE([output], [o], [Filename to dump json output to], [./loadtest/result.bin])" "ARG_OPTIONAL_SINGLE([testdir], [t], [Directory to load tests and fixtures from], [./test/load])" "ARG_OPTIONAL_SINGLE([kind], [k], [Kind of loadtest], [mixed])" - "ARG_TYPE_GROUP_SET([KIND], [KIND], [kind], [mixed,jwt-hs,jwt-hs-cache,jwt-hs-cache-worst,jwt-rsa,jwt-rsa-cache,jwt-rsa-cache-worst])" + "ARG_TYPE_GROUP_SET([KIND], [KIND], [kind], [mixed,jwt,jwt-cache,jwt-cache-worst])" "ARG_OPTIONAL_SINGLE([monitor], [m], [Monitoring file], [./loadtest/result.csv])" "ARG_LEFTOVERS([additional vegeta arguments])" ]; @@ -64,43 +64,11 @@ let abs_output="$(realpath "$_arg_output")" case "$_arg_kind" in - jwt-hs) + jwt) export PGRST_JWT_CACHE_MAX_ENTRIES="0" - ${genTargets} "$_arg_testdir"/gen_targets.http - - # shellcheck disable=SC2145 - ${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \ - ${withTools.withPgrst} -m "$_arg_monitor" \ - sh -c "cd \"$_arg_testdir\" && \ - ${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\"" - ;; - - jwt-hs-cache) - ${genTargets} "$_arg_testdir"/gen_targets.http - - # shellcheck disable=SC2145 - ${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \ - ${withTools.withPgrst} -m "$_arg_monitor" \ - sh -c "cd \"$_arg_testdir\" && \ - ${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\"" - ;; - - jwt-hs-cache-worst) - ${libfaketime}/bin/faketime '2000-01-01 00:00:00' ${genTargets} --worst "$_arg_testdir"/gen_targets.http - - # shellcheck disable=SC2145 - ${withTools.withPg} -f "$_arg_testdir"/fixtures.sql \ - ${withTools.withPgrst} --faketime '2000-01-01 00:00:00' -m "$_arg_monitor" \ - sh -c "cd \"$_arg_testdir\" && \ - ${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\"" - ;; - - jwt-rsa) - export PGRST_JWT_CACHE_MAX_ENTRIES="0" - - ${genRsaMaterials} --rsa="$_arg_testdir"/gen_jwk.json --private-key="$_arg_testdir"/gen_private.json - export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwk.json" + ${genKeyMaterials} --jwks="$_arg_testdir"/gen_jwks.json --private-key="$_arg_testdir"/gen_private.json + export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwks.json" ${genTargets} --private-key="$_arg_testdir"/gen_private.json "$_arg_testdir"/gen_targets.http @@ -111,9 +79,9 @@ let ${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\"" ;; - jwt-rsa-cache) - ${genRsaMaterials} --rsa="$_arg_testdir"/gen_jwk.json --private-key="$_arg_testdir"/gen_private.json - export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwk.json" + jwt-cache) + ${genKeyMaterials} --jwks="$_arg_testdir"/gen_jwks.json --private-key="$_arg_testdir"/gen_private.json + export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwks.json" ${genTargets} --private-key="$_arg_testdir"/gen_private.json "$_arg_testdir"/gen_targets.http @@ -124,9 +92,9 @@ let ${runner} -lazy -targets gen_targets.http -output \"$abs_output\" \"''${_arg_leftovers[@]}\"" ;; - jwt-rsa-cache-worst) - ${genRsaMaterials} --rsa="$_arg_testdir"/gen_jwk.json --private-key="$_arg_testdir"/gen_private.json - export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwk.json" + jwt-cache-worst) + ${genKeyMaterials} --jwks="$_arg_testdir"/gen_jwks.json --private-key="$_arg_testdir"/gen_private.json + export PGRST_JWT_SECRET="@$_arg_testdir/gen_jwks.json" ${libfaketime}/bin/faketime '2000-01-01 00:00:00' ${genTargets} --worst --private-key="$_arg_testdir"/gen_private.json "$_arg_testdir"/gen_targets.http @@ -349,13 +317,13 @@ let } (builtins.readFile ./generate_targets.py); - genRsaMaterials = - writers.writePython3 "postgrest-gen-rsa-materials" + genKeyMaterials = + writers.writePython3 "postgrest-gen-key-materials" { libraries = [ python3Packages.jwcrypto ]; doCheck = false; # postgrest-style conflicts with this } - (builtins.readFile ./gen_rsa_materials.py); + (builtins.readFile ./gen_key_materials.py); mergeMonitorResults = writers.writePython3 "postgrest-merge-monitor-results"