From d16723ac7300bfa545f42278663e83f900ab734d Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Sat, 7 Nov 2026 14:13:16 -1000 Subject: Run the auth checks against the real libraries The unit checks always took the harness stand-ins and the cgit-level checks needed a module to be missing, so the real luaossl and luaposix were never exercised anywhere. A misuse of their interfaces, or a login through cgit that never worked, passed every run. Where an interpreter has both libraries and its crypt(3) checks a SHA-512 hash from openssl passwd, the unit checks run again against them, adding the secret's mode. Where cgit's own Lua does, a login is posted through cgit and the cookie it grants opens the repository. Both skip where crypt checks no SHA-512 hash or the libraries are missing. --- tests/extensions/test-auth.lua | 42 ++++++++++++------- tests/t0505-auth.sh | 94 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 115 insertions(+), 21 deletions(-) diff --git a/tests/extensions/test-auth.lua b/tests/extensions/test-auth.lua index 8306aaf..3470741 100644 --- a/tests/extensions/test-auth.lua +++ b/tests/extensions/test-auth.lua @@ -1,16 +1,18 @@ -- Unit checks for the two auth filters, run under a standalone Lua by -- t0505-auth.sh with the script path as the first argument, the variant, -- inline or file, as the second and a directory of the run's own as the --- third, which takes the fixtures. The scripts take luaossl and luaposix at --- load, so the harness meets both with deterministic stand-ins, which keeps --- every check about this script's own logic, the cookie layout, the expiry --- and field rules, the redirect vetting and the action flows, rather than --- about OpenSSL. The signing secret is made in the run's directory, and --- the accounts and access lists are filled through each variant's own --- channel, its commented-out examples switched on for the inline script and --- fixture files reached through a redirected io.open for the file one. Both --- variants carry the same data so the flow checks read identically for the --- two. +-- third, which takes the fixtures and the signing secret. The scripts need +-- luaossl and luaposix, which the harness meets with deterministic +-- stand-ins, so the checks are about the script's own logic, the cookie +-- layout, the expiry and field rules, the redirect vetting and the action +-- flows, rather than about OpenSSL. Given a fourth argument, a hash the +-- system's crypt(3) makes of the password below, the same checks run +-- against the real libraries instead, which is where a misuse of their +-- interfaces would show. The accounts and access lists are filled through +-- each variant's own channel, its commented-out examples switched on for +-- the inline script and fixture files reached through a redirected io.open +-- for the file one. Both variants carry the same data so the flow checks +-- read identically for the two. local test_directory = arg[0]:match("^(.*)/") or "." local h = dofile(test_directory .. "/harness.lua") @@ -18,13 +20,17 @@ local h = dofile(test_directory .. "/harness.lua") local script = arg[1] local variant = arg[2] local directory = assert(arg[3], "test-auth.lua needs a directory for its fixtures") +local real_hash = arg[4] -h.stub_auth_modules() +if real_hash == nil then + h.stub_auth_modules() +end --- The password behind every test account, stored as what the harness crypt --- stand-in returns for it so a login with it verifies and any other fails. +-- The password behind every test account. With the stand-ins it is stored +-- as what their crypt returns for it, so a login with it verifies and any +-- other fails. local password = "open sesame" -local stored_hash = "$6$rounds=300000$testsalt$" .. password +local stored_hash = real_hash or "$6$rounds=300000$testsalt$" .. password if variant == "file" then local users_file = io.open(directory .. "/auth-users", "w") @@ -81,8 +87,8 @@ load_script() -- Served over https at the root, the session cookie takes the __Host- name. local session_name = "__Host-cgitauth" --- crypt is watched through its stand-in module, so a check can see whether --- a login spent it and on which hash. +-- crypt is watched through the module the script requires, so a check can +-- see whether a login spent it and on which hash. local unistd = require("posix.unistd") local real_crypt = unistd.crypt local crypt_settings = {} @@ -134,6 +140,10 @@ local _, made = pcall(get_secret) h.check("a new secret is 64 hex characters", type(made) == "string" and #made == 64 and not made:find("%X"), tostring(made)) h.equals("and is what its file holds", read_file(directory .. "/auth-secret"), made) +if real_hash then + local stat = sysstat.stat(directory .. "/auth-secret") + h.equals("which only its owner can read", stat and stat.st_mode % 512, 384) +end h.equals("it is written under a mask that keeps everyone else out", umasks[1] and umasks[1].mask, 63) h.check("and the mask before it goes back after", #umasks == 2 and umasks[2].mask == umasks[1].old) diff --git a/tests/t0505-auth.sh b/tests/t0505-auth.sh index 35bd591..81666d5 100755 --- a/tests/t0505-auth.sh +++ b/tests/t0505-auth.sh @@ -2,17 +2,25 @@ # Checks auth-file.lua and auth-inline.lua, the shipped auth filters, which # differ only in where accounts live. The unit checks meet luaossl and -# luaposix with deterministic stand-ins from the harness, proving tampered -# and expired cookies turned away, header injection stripped, unsafe -# redirects refused and the login flows answering as documented. Through cgit -# itself an unedited copy protects nothing, with or without the modules, and -# when the binary's own Lua lacks them a protected repository stays closed. +# luaposix with deterministic stand-ins from the harness, and once more the +# real libraries wherever they are installed, proving tampered and expired +# cookies turned away, header injection stripped, unsafe redirects refused +# and the login flows answering as documented. Through cgit itself an +# unedited copy protects nothing, with or without the modules. When the +# binary's own Lua lacks them a protected repository stays closed, and when +# it has them a login opens one. A unit run is one test of some two hundred +# checks, and the names of those that failed show only under -v. test_description='Check the shipped auth extensions' CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh . "$TEST_OUTPUT_DIRECTORY/extensions/lib.sh" +# The unit checks run once more against the real luaossl and luaposix +# wherever an interpreter has them and its crypt(3) checks a SHA-512 hash. +auth_hash=$(openssl passwd -6 -salt testsalt "open sesame" 2>/dev/null) || + auth_hash= + for lua in $interpreters do for variant in inline file @@ -23,6 +31,24 @@ do "$EXTENSIONS_DIRECTORY/auth-$variant.lua" $variant "$PWD/unit-$lua-$variant" ' done + + auth_libs=AUTH_LIBS_$(echo "$lua" | tr a-z. A-Z_) + if test -n "$auth_hash" && + "$lua" -e " + require('openssl.hmac') require('openssl.rand') require('posix.sys.stat') + assert(require('posix.unistd').crypt('open sesame', '$auth_hash') == '$auth_hash') + " >/dev/null 2>&1 + then + test_set_prereq $auth_libs + fi + for variant in inline file + do + test_expect_success $auth_libs "auth-$variant checks against the real libraries under $lua" ' + mkdir "real-$lua-$variant" && + "$lua" "$EXT_TEST_DIRECTORY/test-auth.lua" \ + "$EXTENSIONS_DIRECTORY/auth-$variant.lua" $variant "$PWD/real-$lua-$variant" "$auth_hash" + ' + done done test_expect_success 'create a repository with one commit' ' @@ -40,6 +66,25 @@ if test "$CGIT_HAS_LUA" -eq 1 && then test_set_prereq CGIT_LUA_NOAUTH say 'cgit lua lacks luaossl or luaposix, expecting protected pages refused' +elif test "$CGIT_HAS_LUA" -eq 1 && test -n "$auth_hash" +then + # A login through cgit also needs the crypt(3) behind cgit's own Lua to + # check the hash. + cat >crypt-probe.lua <<-EOF + function filter_open(...) end + function filter_write(str) end + function filter_close() + if require("posix.unistd").crypt("open sesame", "$auth_hash") == "$auth_hash" then + html("CRYPT_PROBE_YES") + end + return 0 + end + EOF + cgitrc_repo crypt-probe-rc authy "repo.commit-filter=lua:$PWD/crypt-probe.lua" + if cgit_rc crypt-probe-rc "url=authy/commit/" | grep CRYPT_PROBE_YES >/dev/null + then + test_set_prereq CGIT_LUA_AUTH + fi fi test_expect_success CGIT_LUA 'point cgit at the unedited auth filter' ' @@ -77,6 +122,45 @@ test_expect_success CGIT_LUA_NOAUTH 'without the modules a protected repository grep "^\[cgit\] " err ' +# A login through cgit itself, from the form to the protected repository's +# pages, opened with the cookie it grants and closed again without it. +test_expect_success CGIT_LUA_AUTH 'a login through cgit opens a protected repository' ' + cat >grant.awk <<-EOF && + /^local secret_filename = / { print "local secret_filename = \"$PWD/login-secret\""; next } + { print } + /^local protected_repos = [{]\$/ { print "\t[\"authy\"] = { alice = true }," } + /^local users = [{]\$/ { print "\talice = \"$auth_hash\"," } + EOF + awk -f grant.awk "$EXTENSIONS_DIRECTORY/auth-inline.lua" >granted.lua && + grep "login-secret" granted.lua && + grep "testsalt" granted.lua && + grep "\"authy\"" granted.lua && + cgitrc_repo grantrc authy "auth-filter=lua:$PWD/granted.lua" && + (export HTTPS=on && cgit_ok grantrc "url=authy/log/") >form && + grep "
post && + ( + export CGIT_CONFIG="$PWD/grantrc" QUERY_STRING=p=login REQUEST_METHOD=POST \ + HTTPS=on CONTENT_LENGTH=${#body} CONTENT_TYPE=application/x-www-form-urlencoded && + cgit answer 2>err + ) && + test_must_be_empty err && + grep "^Status: 302" answer && + grep "^Location: /authy/log/\$" answer && + cookie=$(sed -n "s/^Set-Cookie: \(__Host-cgitauth=[^;]*\);.*/\1/p" answer) && + test -n "$cookie" && + (export HTTPS=on HTTP_COOKIE="$cookie" && cgit_ok grantrc "url=authy/log/") >tmp && + grep ">guarded commit" tmp && + (export HTTPS=on && cgit_ok grantrc "url=authy/log/") >tmp && + grep "