Files
cyphernode/api_auth_docker/tests/test-gatekeeper.sh
2021-11-17 01:07:28 -05:00

613 lines
20 KiB
Bash
Executable File

#!/bin/bash
. ./colors.sh
# We just want to test the authentication/authorization, not the actual called function
# You need jq installed for these tests to run correctly
# This will test this structure of access (api.properties):
# # Watcher can do stuff
# # Spender can do what the watcher can do plus more stuff
# # Admin can do what the spender can do plus even more stuff
# # Stats can:
# action_helloworld=stats
# action_getblockchaininfo=stats
# action_installation_info=stats
# action_getmempoolinfo=stats
# action_getblockhash=stats
#
# # Watcher can do what the stats can do, plus:
# action_watch=watcher
# action_unwatch=watcher
# action_watchxpub=watcher
# action_unwatchxpubbyxpub=watcher
# action_unwatchxpubbylabel=watcher
# action_getactivewatchesbyxpub=watcher
# action_getactivewatchesbylabel=watcher
# action_getactivexpubwatches=watcher
# action_get_txns_by_watchlabel=watcher
# action_get_unused_addresses_by_watchlabel=watcher
# action_watchtxid=watcher
# action_unwatchtxid=watcher
# action_getactivewatches=watcher
# action_getbestblockhash=watcher
# action_getbestblockinfo=watcher
# action_getblockinfo=watcher
# action_gettransaction=watcher
# action_ots_verify=watcher
# action_ots_info=watcher
# action_ln_getinfo=watcher
# action_ln_create_invoice=watcher
# action_ln_getconnectionstring=watcher
# action_ln_decodebolt11=watcher
# action_ln_listpeers=watcher
# action_ln_getroute=watcher
# action_ln_listpays=watcher
# action_ln_paystatus=watcher
# action_bitcoin_estimatesmartfee=watcher
#
# # Spender can do what the watcher can do, plus:
# action_get_txns_spending=spender
# action_getbalance=spender
# action_getbalances=spender
# action_getbalancebyxpub=spender
# action_getbalancebyxpublabel=spender
# action_getnewaddress=spender
# action_spend=spender
# action_bumpfee=spender
# action_addtobatch=spender
# action_batchspend=spender
# action_deriveindex=spender
# action_derivepubpath=spender
# action_deriveindex_bitcoind=spender
# action_derivepubpath_bitcoind=spender
# action_ln_pay=spender
# action_ln_newaddr=spender
# action_ots_stamp=spender
# action_ots_getfile=spender
# action_ln_getinvoice=spender
# action_ln_delinvoice=spender
# action_ln_connectfund=spender
# action_ln_listfunds=spender
# action_ln_withdraw=spender
# action_createbatcher=spender
# action_updatebatcher=spender
# action_removefrombatch=spender
# action_listbatchers=spender
# action_getbatcher=spender
# action_getbatchdetails=spender
#
# # Admin can do what the spender can do, plus:
#
#
# # Should be called from inside the Docker network only:
# action_conf=internal
# action_newblock=internal
# action_executecallbacks=internal
# action_ots_backoffice=internal
#
#
trace() {
if [ "${1}" -le "${TRACING}" ]; then
echo -en "$(date -u +%FT%TZ) ${2}" 1>&2
fi
}
trace_following() {
if [ "${1}" -le "${TRACING}" ]; then
echo -en "${2}" 1>&2
fi
}
start_test_container() {
trace 1 "[stop_test_container] ${BCyan}Starting test container...${Color_Off}\n"
docker run -d --rm -t --name tests-gatekeeper --network=cyphernodenet alpine
sleep 2
}
stop_test_container() {
trace 1 "[stop_test_container] ${BCyan}Stopping existing containers if they are running...${Color_Off}\n"
local containers=$(docker ps -q -f "name=tests-gatekeeper")
if [ -n "${containers}" ]; then
docker stop ${containers}
fi
}
exec_in_test_container() {
local response
local returncode
response=$(docker exec -it tests-gatekeeper "$@")
returncode=${?}
echo -n "${response}" | tr -d '\r\n'
return ${returncode}
}
exec_in_test_container_leave_lf() {
local response
local returncode
response=$(docker exec -it tests-gatekeeper "$@")
returncode=${?}
echo -n "${response}"
return ${returncode}
}
test_auth() {
local fn=${1}
local token=${2}
local has_access=${3}
local httperrorcode=${4}
local returncode
local httpcode
trace 2 "[test_auth] ${BCyan}action=${fn}...${Color_Off}"
httpcode=$(exec_in_test_container curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /cert.pem https://gatekeeper:2009/v0/${fn})
returncode=${?}
if ${has_access}; then
trace_following 2 " has access... "
else
trace_following 2 " has NOT access... "
fi
if [ "${returncode}" -ne "0" ] || ${has_access} && [ "${httpcode}" -eq "${httperrorcode}" ] || ! ${has_access} && [ "${httpcode}" -ne "${httperrorcode}" ]; then
trace_following 1 "${On_Red}${BBlack} curl code=${returncode}, HTTP code=${httpcode}!${Color_Off}\n"
return 10
else
trace_following 2 "${On_IGreen}${BBlack} OK ${Color_Off}\n"
fi
}
test_authorization() {
test_auth ${1} ${2} ${3} 403
return $?
}
test_authentication() {
test_auth ${1} ${2} ${3} 401
return $?
}
generate_token() {
local id=${1}
local expired=${2:-false}
local key
local groups
key=$(exec_in_test_container sh -c 'source /keys.properties ; echo $ukey_'${id})
groups=$(exec_in_test_container sh -c 'source /keys.properties ; echo $ugroups_'${id})
trace 3 "[generate_token] id=${id}\n"
trace 3 "[generate_token] key=${key}\n"
trace 3 "[generate_token] groups=${groups}\n"
local h64broken=$(exec_in_test_container sh -c "echo -n '{\"alg\":\"HS256\",\"typ\":\"JWT\"}' | base64")
trace 3 "[generate_token] h64broken=${h64broken}\n"
local h64=$(exec_in_test_container sh -c "echo -n '{\"alg\":\"HS256\",\"typ\":\"JWT\"}' | basenc --base64url | tr -d '='")
trace 3 "[generate_token] h64=${h64}\n"
local d=$(exec_in_test_container date +"%s")
if ! ${expired}; then
d=$((d+100000))
fi
trace 3 "[generate_token] d=${d}\n"
local p64=$(exec_in_test_container sh -c "echo -n '{\"id\":\"${id}\",\"exp\":${d}}' | basenc --base64url | tr -d '='")
trace 3 "[generate_token] p64=${p64}\n"
# local s=$(echo -n "${h64}.${p64}" | openssl dgst -hmac "${key}" -sha256 -r | cut -sd ' ' -f1)
local sig=$(exec_in_test_container sh -c "echo -n \"${h64}.${p64}\" | openssl dgst -hmac \"${key}\" -sha256 -r -binary | basenc --base64url | tr -d '='")
trace 3 "[generate_token] sig=${sig}\n"
local token="${h64}.${p64}.${sig}"
trace 3 "[generate_token] token=${token}\n\n"
trace 1 "[test_gatekeeper] ${BCyan}Current user ${id} with groups ${groups}${Color_Off}\n"
echo -n "${token}"
}
generate_broken_token() {
local id=${1}
local key
local groups
key=$(exec_in_test_container sh -c 'source /keys.properties ; echo $ukey_'${id})
groups=$(exec_in_test_container sh -c 'source /keys.properties ; echo $ugroups_'${id})
trace 3 "[generate_token] id=${id}\n"
trace 3 "[generate_token] key=${key}\n"
trace 3 "[generate_token] groups=${groups}\n"
local h64broken=$(exec_in_test_container sh -c "echo -n '{\"alg\":\"HS256\",\"typ\":\"JWT\"}' | base64")
trace 3 "[generate_token] h64broken=${h64broken}\n"
local d=$(exec_in_test_container date +"%s")
trace 3 "[generate_token] d=${d}\n"
local p64broken=$(exec_in_test_container sh -c "echo -n '{\"id\":\"${id}\",\"exp\":$((d+100000))}' | base64")
trace 3 "[generate_token] p64broken=${p64broken}\n"
local sigbroken=$(exec_in_test_container sh -c "echo -n \"${h64}.${p64}\" | openssl dgst -hmac \"${key}\" -sha256 -r | cut -sd ' ' -f1")
trace 3 "[generate_token] sigbroken=${sigbroken}\n"
local token="${h64broken}.${p64broken}.${sigbroken}"
trace 3 "[generate_token] token=${token}\n\n"
trace 1 "[test_gatekeeper] ${BCyan}Current user ${id} with groups ${groups}${Color_Off}\n"
echo -n "${token}"
}
test_functions_with_broken_token() {
trace 1 "\n\n[test_functions_with_broken_token] ${BCyan}Let's test functions with broken token...${Color_Off}\n\n"
local id=${1}
local token=$(generate_broken_token ${id})
local has_access=${2}
# Stats can:
# action_helloworld=stats
test_authorization "helloworld" "${token}" ${has_access} || return 10
# action_getblockchaininfo=stats
test_authorization "getblockchaininfo" "${token}" ${has_access} || return 20
# action_installation_info=stats
test_authorization "installation_info" "${token}" ${has_access} || return 30
# action_getmempoolinfo=stats
test_authorization "getmempoolinfo" "${token}" ${has_access} || return 40
# action_getblockhash=stats
test_authorization "getblockhash" "${token}" ${has_access} || return 50
trace 1 "\n\n[test_functions_with_broken_token] ${On_IGreen}${BBlack} SUCCESS with user ${id}! ${Color_Off}\n"
}
test_functions_with_wrong_token() {
trace 1 "\n\n[test_functions_with_wrong_token] ${BCyan}Let's test functions with wrong token (we will receive HTTP 401 Unauthorized here)...${Color_Off}\n\n"
local id=${1}
local token=aaa$(generate_token ${id})aaa
local has_access=false
# Stats can:
# action_helloworld=stats
test_authentication "helloworld" "${token}" ${has_access} || return 10
# action_getblockchaininfo=stats
test_authentication "getblockchaininfo" "${token}" ${has_access} || return 20
# action_installation_info=stats
test_authentication "installation_info" "${token}" ${has_access} || return 30
# action_getmempoolinfo=stats
test_authentication "getmempoolinfo" "${token}" ${has_access} || return 40
# action_getblockhash=stats
test_authentication "getblockhash" "${token}" ${has_access} || return 50
trace 1 "\n\n[test_functions_with_wrong_token] ${On_IGreen}${BBlack} SUCCESS with user ${id}! ${Color_Off}\n"
}
test_stats_functions() {
trace 1 "\n\n[test_gatekeeper] ${BCyan}Let's test stats functions...${Color_Off}\n\n"
local id=${1}
local token=$(generate_token ${id})
local has_access=${2}
# Stats can:
# action_helloworld=stats
test_authorization "helloworld" "${token}" ${has_access} || return 10
# action_getblockchaininfo=stats
test_authorization "getblockchaininfo" "${token}" ${has_access} || return 20
# action_installation_info=stats
test_authorization "installation_info" "${token}" ${has_access} || return 30
# action_getmempoolinfo=stats
test_authorization "getmempoolinfo" "${token}" ${has_access} || return 40
# action_getblockhash=stats
test_authorization "getblockhash" "${token}" ${has_access} || return 50
trace 1 "\n\n[test_stats_functions] ${On_IGreen}${BBlack} SUCCESS with user ${id}! ${Color_Off}\n"
}
test_watcher_functions() {
trace 1 "\n\n[test_watcher_functions] ${BCyan}Let's test watcher functions...${Color_Off}\n\n"
local id=${1}
local token=$(generate_token ${id})
local has_access=${2}
# Watcher can:
# action_watch=watcher
test_authorization "watch" "${token}" ${has_access} || return 10
# action_unwatch=watcher
test_authorization "unwatch" "${token}" ${has_access} || return 20
# action_watchxpub=watcher
test_authorization "watchxpub" "${token}" ${has_access} || return 30
# action_unwatchxpubbyxpub=watcher
test_authorization "unwatchxpubbyxpub" "${token}" ${has_access} || return 40
# action_unwatchxpubbylabel=watcher
test_authorization "unwatchxpubbylabel" "${token}" ${has_access} || return 50
# action_getactivewatchesbyxpub=watcher
test_authorization "getactivewatchesbyxpub" "${token}" ${has_access} || return 60
# action_getactivewatchesbylabel=watcher
test_authorization "getactivewatchesbylabel" "${token}" ${has_access} || return 70
# action_getactivexpubwatches=watcher
test_authorization "getactivexpubwatches" "${token}" ${has_access} || return 80
# action_get_txns_by_watchlabel=watcher
test_authorization "get_txns_by_watchlabel" "${token}" ${has_access} || return 90
# action_get_unused_addresses_by_watchlabel=watcher
test_authorization "get_unused_addresses_by_watchlabel" "${token}" ${has_access} || return 100
# action_watchtxid=watcher
test_authorization "watchtxid" "${token}" ${has_access} || return 110
# action_unwatchtxid=watcher
test_authorization "unwatchtxid" "${token}" ${has_access} || return 120
# action_getactivewatches=watcher
test_authorization "getactivewatches" "${token}" ${has_access} || return 130
# action_getbestblockhash=watcher
test_authorization "getbestblockhash" "${token}" ${has_access} || return 140
# action_getbestblockinfo=watcher
test_authorization "getbestblockinfo" "${token}" ${has_access} || return 150
# action_getblockinfo=watcher
test_authorization "getblockinfo" "${token}" ${has_access} || return 160
# action_gettransaction=watcher
test_authorization "gettransaction" "${token}" ${has_access} || return 170
# action_ots_verify=watcher
test_authorization "ots_verify" "${token}" ${has_access} || return 180
# action_ots_info=watcher
test_authorization "ots_info" "${token}" ${has_access} || return 190
# action_ln_getinfo=watcher
test_authorization "ln_getinfo" "${token}" ${has_access} || return 200
# action_ln_create_invoice=watcher
test_authorization "ln_create_invoice" "${token}" ${has_access} || return 210
# action_ln_getconnectionstring=watcher
test_authorization "ln_getconnectionstring" "${token}" ${has_access} || return 220
# action_ln_decodebolt11=watcher
test_authorization "ln_decodebolt11" "${token}" ${has_access} || return 230
# action_ln_listpeers=watcher
test_authorization "ln_listpeers" "${token}" ${has_access} || return 240
# action_ln_getroute=watcher
test_authorization "ln_getroute" "${token}" ${has_access} || return 250
# action_ln_listpays=watcher
test_authorization "ln_listpays" "${token}" ${has_access} || return 260
# action_ln_paystatus=watcher
test_authorization "ln_paystatus" "${token}" ${has_access} || return 270
# action_bitcoin_estimatesmartfee=watcher
test_authorization "bitcoin_estimatesmartfee" "${token}" ${has_access} || return 280
trace 1 "\n\n[test_watcher_functions] ${On_IGreen}${BBlack} SUCCESS with user ${id}! ${Color_Off}\n"
}
test_spender_functions() {
trace 1 "\n\n[test_spender_functions] ${BCyan}Let's test spender functions...${Color_Off}\n"
local id=${1}
local token=$(generate_token ${id})
local has_access=${2}
# Spender can do what the watcher can do, plus:
# action_get_txns_spending=spender
test_authorization "get_txns_spending" "${token}" ${has_access} || return 10
# action_getbalance=spender
test_authorization "getbalance" "${token}" ${has_access} || return 10
# action_getbalances=spender
test_authorization "getbalances" "${token}" ${has_access} || return 10
# action_getbalancebyxpub=spender
test_authorization "getbalancebyxpub" "${token}" ${has_access} || return 10
# action_getbalancebyxpublabel=spender
test_authorization "getbalancebyxpublabel" "${token}" ${has_access} || return 10
# action_getnewaddress=spender
test_authorization "getnewaddress" "${token}" ${has_access} || return 10
# action_spend=spender
test_authorization "spend" "${token}" ${has_access} || return 10
# action_bumpfee=spender
test_authorization "bumpfee" "${token}" ${has_access} || return 10
# action_addtobatch=spender
test_authorization "addtobatch" "${token}" ${has_access} || return 10
# action_batchspend=spender
test_authorization "batchspend" "${token}" ${has_access} || return 10
# action_deriveindex=spender
test_authorization "deriveindex" "${token}" ${has_access} || return 10
# action_derivepubpath=spender
test_authorization "derivepubpath" "${token}" ${has_access} || return 10
# action_deriveindex_bitcoind=spender
test_authorization "deriveindex_bitcoind" "${token}" ${has_access} || return 10
# action_derivepubpath_bitcoind=spender
test_authorization "derivepubpath_bitcoind" "${token}" ${has_access} || return 10
# action_ln_pay=spender
test_authorization "ln_pay" "${token}" ${has_access} || return 10
# action_ln_newaddr=spender
test_authorization "ln_newaddr" "${token}" ${has_access} || return 10
# action_ots_stamp=spender
test_authorization "ots_stamp" "${token}" ${has_access} || return 10
# action_ots_getfile=spender
test_authorization "ots_getfile" "${token}" ${has_access} || return 10
# action_ln_getinvoice=spender
test_authorization "ln_getinvoice" "${token}" ${has_access} || return 10
# action_ln_delinvoice=spender
test_authorization "ln_delinvoice" "${token}" ${has_access} || return 10
# action_ln_connectfund=spender
test_authorization "ln_connectfund" "${token}" ${has_access} || return 10
# action_ln_listfunds=spender
test_authorization "ln_listfunds" "${token}" ${has_access} || return 10
# action_ln_withdraw=spender
test_authorization "ln_withdraw" "${token}" ${has_access} || return 10
# action_createbatcher=spender
test_authorization "createbatcher" "${token}" ${has_access} || return 10
# action_updatebatcher=spender
test_authorization "updatebatcher" "${token}" ${has_access} || return 10
# action_removefrombatch=spender
test_authorization "removefrombatch" "${token}" ${has_access} || return 10
# action_listbatchers=spender
test_authorization "listbatchers" "${token}" ${has_access} || return 10
# action_getbatcher=spender
test_authorization "getbatcher" "${token}" ${has_access} || return 10
# action_getbatchdetails=spender
test_authorization "getbatchdetails" "${token}" ${has_access} || return 10
trace 1 "\n\n[test_spender_functions] ${On_IGreen}${BBlack} SUCCESS with user ${id}! ${Color_Off}\n"
}
test_admin_functions() {
trace 1 "\n\n[test_admin_functions] ${BCyan}Let's test admin functions...${Color_Off}\n"
# Admin can do what the spender can do, plus:
trace 1 "\n\n[test_admin_functions] ${On_IGreen}${BBlack} SUCCESS with user ${id}! ${Color_Off}\n"
}
test_internal_functions() {
trace 1 "\n\n[test_internal_functions] ${BCyan}Let's test internal functions...${Color_Off}\n"
local id=${1}
local token=$(generate_token ${id})
local has_access=${2}
# Should be called from inside the Docker network only:
# action_conf=internal
test_authorization "conf" "${token}" ${has_access} || return 10
# action_newblock=internal
test_authorization "newblock" "${token}" ${has_access} || return 10
# action_executecallbacks=internal
test_authorization "executecallbacks" "${token}" ${has_access} || return 10
# action_ots_backoffice=internal
test_authorization "ots_backoffice" "${token}" ${has_access} || return 10
trace 1 "\n\n[test_internal_functions] ${On_IGreen}${BBlack} SUCCESS with user ${id}! ${Color_Off}\n"
}
test_expired() {
trace 1 "\n\n[test_expired] ${BCyan}Let's test calling a function with an expired token (we will receive HTTP 403 Forbidden here)...${Color_Off}\n\n"
local id=${1}
local token=$(generate_token ${id} true)
local has_access=false
# Stats can:
# action_helloworld=stats
test_authorization "helloworld" "${token}" ${has_access} || return 10
trace 1 "\n\n[test_expired] ${On_IGreen}${BBlack} SUCCESS with user ${id}! ${Color_Off}\n"
}
TRACING=3
stop_test_container
start_test_container
trace 1 "\n\n[test_gatekeeper] ${BCyan}Installing needed packages...${Color_Off}\n"
exec_in_test_container_leave_lf apk add --update curl coreutils openssl
# Copy keys to test container
trace 1 "\n\n[test_gatekeeper] ${BCyan}Copying keys and certs to test container...${Color_Off}\n"
gatekeeperid=$(docker ps -q -f "name=cyphernode_gatekeeper")
testid=$(docker ps -q -f "name=tests-gatekeeper")
docker cp ${gatekeeperid}:/etc/nginx/conf.d/keys.properties - | docker cp - ${testid}:/
docker cp ${gatekeeperid}:/etc/ssl/certs/cert.pem - | docker cp - ${testid}:/
# Test with an expired token
# Test functions with broken legacy token
# Test functions with wrong token
# Stats functions with a stats
# Stats functions with a watcher
# Stats functions with a spender
# Stats functions with a admin
# Watcher functions with a stats
# Watcher functions with a watcher
# Watcher functions with a spender
# Watcher functions with a admin
# Spending functions with a stats
# Spending functions with a watcher
# Spending functions with a spender
# Spending functions with a admin
# Internal functions with a stats
# Internal functions with a watcher
# Internal functions with a spender
# Internal functions with a admin
test_expired 003 \
&& test_functions_with_broken_token 003 true \
&& test_functions_with_wrong_token 003 true \
&& test_stats_functions 000 true \
&& test_stats_functions 001 true \
&& test_stats_functions 002 true \
&& test_stats_functions 003 true \
&& test_watcher_functions 000 false \
&& test_watcher_functions 001 true \
&& test_watcher_functions 002 true \
&& test_watcher_functions 003 true \
&& test_spender_functions 000 false \
&& test_spender_functions 001 false \
&& test_spender_functions 002 true \
&& test_spender_functions 003 true \
&& test_internal_functions 000 false \
&& test_internal_functions 001 false \
&& test_internal_functions 002 false \
&& test_internal_functions 003 false \
|| trace 1 "\n\n[test_gatekeeper] ${On_Red}${BBlack} test_watcher_functions error: ${?} ${Color_Off}\n"
trace 1 "\n\n[test_gatekeeper] ${BCyan}Tearing down...${Color_Off}\n"
wait
stop_test_container
trace 1 "\n\n[test_gatekeeper] ${BCyan}See ya!${Color_Off}\n"