Added tests and group authorization: watcher, spender, admin, internal

This commit is contained in:
kexkey
2018-10-15 15:44:59 -04:00
parent b60cb3f1db
commit 1679ee89d1
9 changed files with 396 additions and 34 deletions

View File

@@ -12,7 +12,9 @@ COPY auth.sh /etc/nginx/conf.d
COPY default-ssl.conf /etc/nginx/conf.d/default.conf COPY default-ssl.conf /etc/nginx/conf.d/default.conf
COPY entrypoint.sh entrypoint.sh COPY entrypoint.sh entrypoint.sh
COPY keys.properties /etc/nginx/conf.d COPY keys.properties /etc/nginx/conf.d
COPY api.properties /etc/nginx/conf.d
COPY trace.sh /etc/nginx/conf.d COPY trace.sh /etc/nginx/conf.d
COPY tests.sh /etc/nginx/conf.d
RUN chmod +x /etc/nginx/conf.d/auth.sh entrypoint.sh RUN chmod +x /etc/nginx/conf.d/auth.sh entrypoint.sh

View File

@@ -23,9 +23,13 @@ dd if=/dev/urandom bs=32 count=1 2> /dev/null | xxd -ps -c 32
Put the key in keys.properties and keep it for the client. This is a secret key. keys.properties looks like this: Put the key in keys.properties and keep it for the client. This is a secret key. keys.properties looks like this:
```property ```property
#keyid=hex(key) #group.id=hex(key)
key001=2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36 watcher.001=2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36
key002=50c5e483b80964595508f214229b014aa6c013594d57d38bcb841093a39f1d83 watcher.002=50c5e483b80964595508f214229b014aa6c013594d57d38bcb841093a39f1d83
spender.001=b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af
spender.002=bb0458b705e774c0c9622efaccfe573aa30c82f62386d9435f04e9727cdc26fd
admin.001=6c009201b123e8c24c6b74590de28c0c96f3287e88cac9460a2173a53d73fb87
admin.002=19e121b698014fac638f772c4ff5775a738856bf6cbdef0dc88971059c69da4b
``` ```
You can have multiple keys, but be aware that this container has **not** been built to support thousands of API keys! **Cyphernode should be used locally**, not publicly as a service. You can have multiple keys, but be aware that this container has **not** been built to support thousands of API keys! **Cyphernode should be used locally**, not publicly as a service.

View File

@@ -0,0 +1,29 @@
# Watcher can:
action_watch=watcher
action_unwatch=watcher
action_getactivewatches=watcher
action_getbestblockhash=watcher
action_getbestblockinfo=watcher
action_getblockinfo=watcher
action_gettransaction=watcher
action_ln_getinfo=watcher
action_ln_create_invoice=watcher
# Spender can do what the watcher can do plus:
action_getbalance=spender
action_getnewaddress=spender
action_spend=spender
action_addtobatch=spender
action_batchspend=spender
action_deriveindex=spender
action_derivepubpath=spender
action_ln_pay=spender
action_ln_newaddr=spender
# Admin can do what the spender can do plus:
# Should be called from inside the Swarm:
action_conf=internal
action_executecallbacks=internal

View File

@@ -16,61 +16,99 @@
. ./trace.sh . ./trace.sh
verify() verify_sign()
{ {
local returncode
local header64=$(echo ${1} | cut -sd '.' -f1) local header64=$(echo ${1} | cut -sd '.' -f1)
local payload64=$(echo ${1} | cut -sd '.' -f2) local payload64=$(echo ${1} | cut -sd '.' -f2)
local signature=$(echo ${1} | cut -sd '.' -f3) local signature=$(echo ${1} | cut -sd '.' -f3)
trace "[verify] header64=${header64}" trace "[verify_sign] header64=${header64}"
trace "[verify] payload64=${payload64}" trace "[verify_sign] payload64=${payload64}"
trace "[verify] signature=${signature}" trace "[verify_sign] signature=${signature}"
local payload=$(echo ${payload64} | base64 -d) local payload=$(echo ${payload64} | base64 -d)
local exp=$(echo ${payload} | jq ".exp") local exp=$(echo ${payload} | jq ".exp")
local current=$(date +"%s") local current=$(date +"%s")
trace "[verify] payload=${payload}" trace "[verify_sign] payload=${payload}"
trace "[verify] exp=${exp}" trace "[verify_sign] exp=${exp}"
trace "[verify] current=${current}" trace "[verify_sign] current=${current}"
if [ ${exp} -gt ${current} ]; then if [ ${exp} -gt ${current} ]; then
trace "[verify] Not expired, let's validate signature" trace "[verify_sign] Not expired, let's validate signature"
local id=$(echo ${payload} | jq ".id" | tr -d '"') local id=$(echo ${payload} | jq ".id" | tr -d '"')
trace "[verify] id=${id}" trace "[verify_sign] id=${id}"
# It is so much faster to include the keys here instead of grep'ing the file for key. # It is so much faster to include the keys here instead of grep'ing the file for key.
. ./keys.properties . ./keys.properties
local key local key
eval key='$key'$id eval key='$ukey_'$id
trace "[verify] key=${key}" trace "[verify_sign] key=${key}"
local comp_sign=$(echo "${header64}.${payload64}" | openssl dgst -hmac "${key}" -sha256 -r | cut -sd ' ' -f1) local comp_sign=$(echo "${header64}.${payload64}" | openssl dgst -hmac "${key}" -sha256 -r | cut -sd ' ' -f1)
trace "[verify] comp_sign=${comp_sign}" trace "[verify_sign] comp_sign=${comp_sign}"
if [ "${comp_sign}" = "${signature}" ]; then if [ "${comp_sign}" = "${signature}" ]; then
trace "[verify] Valid signature!" trace "[verify_sign] Valid signature!"
echo -en "Status: 200 OK\r\n\r\n"
return verify_group ${id}
fi returncode=$?
trace "[verify] Invalid signature!"
return 1 if [ "${returncode}" -eq 0 ]; then
echo -en "Status: 200 OK\r\n\r\n"
return
fi
trace "[verify_sign] Invalid group!"
return 1
fi
trace "[verify_sign] Invalid signature!"
return 1
fi fi
trace "[verify] Expired!" trace "[verify_sign] Expired!"
return 1 return 1
} }
verify_group()
{
trace "[verify_group] Verifying group..."
local id=${1}
local action=${REQUEST_URI:1}
# It is so much faster to include the keys here instead of grep'ing the file for key.
. ./api.properties
local needed_group
local ugroups
eval needed_group='$action_'${action}
trace "[verify_group] needed_group=${needed_group}"
eval ugroups='$ugroups_'$id
trace "[verify_group] user groups=${ugroups}"
case "${ugroups}" in
*${needed_group}*) trace "[verify_group] Access granted"; return 0 ;;
esac
trace "[verify_group] Access NOT granted"
return 1
}
# $HTTP_AUTHORIZATION = Bearer <token> # $HTTP_AUTHORIZATION = Bearer <token>
# If this is not found in header, we leave
trace "[auth.sh] HTTP_AUTHORIZATION=${HTTP_AUTHORIZATION}" trace "[auth.sh] HTTP_AUTHORIZATION=${HTTP_AUTHORIZATION}"
if [ "${HTTP_AUTHORIZATION:0:6}" = "Bearer" ]; then if [ "${HTTP_AUTHORIZATION:0:6}" = "Bearer" ]; then
token="${HTTP_AUTHORIZATION:6}" token="${HTTP_AUTHORIZATION:6}"
if [ -n "$token" ]; then if [ -n "$token" ]; then
trace "[auth.sh] Valid format for authorization header" trace "[auth.sh] Valid format for authorization header"
verify "${token}" verify_sign "${token}"
[ "$?" -eq "0" ] && return [ "$?" -eq "0" ] && return
fi fi
fi fi

View File

@@ -0,0 +1 @@
TRACING=1

View File

@@ -1,3 +1,7 @@
#keyid=hex(key) #kappiid="id";kapi_key="key";kapi_groups="group1,group2";leave the rest intact
key001=2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36 kapi_id="001";kapi_key="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";kapi_groups="watcher";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
key002=50c5e483b80964595508f214229b014aa6c013594d57d38bcb841093a39f1d83 kapi_id="002";kapi_key="50c5e483b80964595508f214229b014aa6c013594d57d38bcb841093a39f1d83";kapi_groups="watcher";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="003";kapi_key="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";kapi_groups="watcher,spender";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="004";kapi_key="bb0458b705e774c0c9622efaccfe573aa30c82f62386d9435f04e9727cdc26fd";kapi_groups="watcher,spender";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="005";kapi_key="6c009201b123e8c24c6b74590de28c0c96f3287e88cac9460a2173a53d73fb87";kapi_groups="watcher,spender,admin";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="006";kapi_key="19e121b698014fac638f772c4ff5775a738856bf6cbdef0dc88971059c69da4b";kapi_groups="watcher,spender,admin";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}

282
api_auth_docker/tests.sh Normal file
View File

@@ -0,0 +1,282 @@
#!/bin/sh
# We just want to test the authentication/authorization, not the actual called function
# Replace
# proxy_pass http://cyphernode:8888;
# by
# proxy_pass http://tests:8888;
# in /etc/nginx/conf.d/default.conf to run the tests
test_expiration()
{
# Let's test expiration: 1 second in payload, request 2 seconds later
local id=${1}
# echo "id=${id}"
local k
eval k='$ukey_'$id
local p64=$(echo "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+1))}" | base64)
local s=$(echo "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1)
local token="$h64.$p64.$s"
echo " Sleeping 2 seconds... "
sleep 2
local rc
echo -n " Testing expired request... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/getblockinfo)
[ "${rc}" -ne "403" ] && return 10
return 0
}
test_authentication()
{
# Let's test authentication/signature
local id=${1}
# echo "id=${id}"
local k
eval k='$ukey_'$id
local p64=$(echo "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64)
local s=$(echo "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1)
local token="$h64.$p64.$s"
local rc
echo -n " Testing good signature... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/getblockinfo)
[ "${rc}" -eq "403" ] && return 20
token="$h64.$p64.a$s"
echo -n " Testing bad signature... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/getblockinfo)
[ "${rc}" -ne "403" ] && return 30
return 0
}
test_authorization_watcher()
{
# Let's test autorization
local id=${1}
# echo "id=${id}"
local k
eval k='$ukey_'$id
local p64=$(echo "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64)
local s=$(echo "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1)
local token="$h64.$p64.$s"
local rc
# Watcher can:
# watch
echo -n " Testing watch... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/watch)
[ "${rc}" -eq "403" ] && return 40
# unwatch
echo -n " Testing unwatch... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/unwatch)
[ "${rc}" -eq "403" ] && return 50
# getactivewatches
echo -n " Testing getactivewatches... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/getactivewatches)
[ "${rc}" -eq "403" ] && return 60
# getbestblockhash
echo -n " Testing getbestblockhash... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/getbestblockhash)
[ "${rc}" -eq "403" ] && return 70
# getbestblockinfo
echo -n " Testing getbestblockinfo... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/getbestblockinfo)
[ "${rc}" -eq "403" ] && return 80
# getblockinfo
echo -n " Testing getblockinfo... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/getblockinfo)
[ "${rc}" -eq "403" ] && return 90
# gettransaction
echo -n " Testing gettransaction... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/gettransaction)
[ "${rc}" -eq "403" ] && return 100
# ln_getinfo
echo -n " Testing ln_getinfo... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/ln_getinfo)
[ "${rc}" -eq "403" ] && return 110
# ln_create_invoice
echo -n " Testing ln_create_invoice... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/ln_create_invoice)
[ "${rc}" -eq "403" ] && return 120
return 0
}
test_authorization_spender()
{
# Let's test autorization
local id=${1}
# echo "id=${id}"
local is_spender=${2}
# echo "is_spender=${is_spender}"
local k
eval k='$ukey_'$id
local p64=$(echo "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64)
local s=$(echo "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1)
local token="$h64.$p64.$s"
local rc
# Spender can do what the watcher can do, plus:
# getbalance
echo -n " Testing getbalance... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/getbalance)
[ ${is_spender} = true ] && [ "${rc}" -eq "403" ] && return 130
[ ${is_spender} = false ] && [ "${rc}" -ne "403" ] && return 135
# getnewaddress
echo -n " Testing getnewaddress... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/getnewaddress)
[ ${is_spender} = true ] && [ "${rc}" -eq "403" ] && return 140
[ ${is_spender} = false ] && [ "${rc}" -ne "403" ] && return 145
# spend
echo -n " Testing spend... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/spend)
[ ${is_spender} = true ] && [ "${rc}" -eq "403" ] && return 150
[ ${is_spender} = false ] && [ "${rc}" -ne "403" ] && return 155
# addtobatch
echo -n " Testing addtobatch... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/addtobatch)
[ ${is_spender} = true ] && [ "${rc}" -eq "403" ] && return 160
[ ${is_spender} = false ] && [ "${rc}" -ne "403" ] && return 165
# batchspend
echo -n " Testing batchspend... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/batchspend)
[ ${is_spender} = true ] && [ "${rc}" -eq "403" ] && return 170
[ ${is_spender} = false ] && [ "${rc}" -ne "403" ] && return 175
# deriveindex
echo -n " Testing deriveindex... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/deriveindex)
[ ${is_spender} = true ] && [ "${rc}" -eq "403" ] && return 180
[ ${is_spender} = false ] && [ "${rc}" -ne "403" ] && return 185
# derivepubpath
echo -n " Testing derivepubpath... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/derivepubpath)
[ ${is_spender} = true ] && [ "${rc}" -eq "403" ] && return 190
[ ${is_spender} = false ] && [ "${rc}" -ne "403" ] && return 195
# ln_pay
echo -n " Testing ln_pay... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/ln_pay)
[ ${is_spender} = true ] && [ "${rc}" -eq "403" ] && return 200
[ ${is_spender} = false ] && [ "${rc}" -ne "403" ] && return 205
# ln_newaddr
echo -n " Testing ln_newaddr... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/ln_newaddr)
[ ${is_spender} = true ] && [ "${rc}" -eq "403" ] && return 210
[ ${is_spender} = false ] && [ "${rc}" -ne "403" ] && return 215
return 0
}
test_authorization_internal()
{
# Let's test autorization
local id=${1}
# echo "id=${id}"
local k
eval k='$ukey_'$id
local p64=$(echo "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64)
local s=$(echo "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1)
local token="$h64.$p64.$s"
local rc
# Should be called from inside the Swarm:
# conf
echo -n " Testing conf... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/conf)
[ "${rc}" -ne "403" ] && return 220
# executecallbacks
echo -n " Testing executecallbacks... "
rc=$(time -f "%E" curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -k https://localhost/executecallbacks)
[ "${rc}" -ne "403" ] && return 230
return 0
}
kapi_id="001";kapi_key="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";kapi_groups="watcher";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="003";kapi_key="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";kapi_groups="watcher,spender";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="005";kapi_key="6c009201b123e8c24c6b74590de28c0c96f3287e88cac9460a2173a53d73fb87";kapi_groups="watcher,spender,admin";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
h64=$(echo "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64)
# Let's test expiration: 1 second in payload, request 2 seconds later
echo 'test_expiration "001"'
test_expiration "001" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_expiration "003"'
test_expiration "003" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_expiration "005"'
test_expiration "005" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
# Let's test authentication/signature
echo 'test_authentication "001"'
test_authentication "001" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_authentication "003"'
test_authentication "003" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_authentication "005"'
test_authentication "005" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
# Let's test autorization for watcher actions
echo 'test_authorization_watcher "001"'
test_authorization_watcher "001" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_authorization_watcher "003"'
test_authorization_watcher "003" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_authorization_watcher "005"'
test_authorization_watcher "005" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
# Let's test autorization for spender actions
echo 'test_authorization_spender "001" false'
test_authorization_spender "001" false ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_authorization_spender "003" true'
test_authorization_spender "003" true ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_authorization_spender "005" true'
test_authorization_spender "005" true ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
# Let's test autorization for admin actions
#test_authorization_admin "001"
#test_authorization_admin "003"
#test_authorization_admin "005"
# Let's test autorization for internal actions
echo 'test_authorization_internal "001"'
test_authorization_internal "001" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_authorization_internal "003"'
test_authorization_internal "003" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc
echo 'test_authorization_internal "005"'
test_authorization_internal "005" ; rc=$? ; [ $rc -ne 0 ] && echo $rc && return $rc

View File

@@ -3,13 +3,13 @@
trace() trace()
{ {
if [ -n "${TRACING}" ]; then if [ -n "${TRACING}" ]; then
echo "[$(date +%Y-%m-%dT%H:%M:%S%z)] ${1}" > /dev/stderr echo "[$(date +%Y-%m-%dT%H:%M:%S%z)] ${1}" 1>&2
fi fi
} }
trace_rc() trace_rc()
{ {
if [ -n "${TRACING}" ]; then if [ -n "${TRACING}" ]; then
echo "[$(date +%Y-%m-%dT%H:%M:%S%z)] Last return code: ${1}" > /dev/stderr echo "[$(date +%Y-%m-%dT%H:%M:%S%z)] Last return code: ${1}" 1>&2
fi fi
} }

View File

@@ -3,6 +3,8 @@ version: "3"
services: services:
authapi: authapi:
# HTTP authentication API gate # HTTP authentication API gate
env_file:
- api_auth_docker/env.properties
image: authapi image: authapi
ports: ports:
- "80:80" - "80:80"