First commit

This commit is contained in:
southerntofu 2020-09-29 12:37:55 +02:00
commit e81a3b4b25
11 changed files with 261 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.*.sw*

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# Webhook endpoints
This repository contains the specification and tests for the forge webhook endpoints.
# Running tests
Running tests requires the bats framework (`apt install bats`). You can run the `test.sh` script to start the tests. If you are not running from the implementation's folder, you may pass it as first argument the path to the program starting the local server (for tests).
```
$ ./test.sh ~/endpoints.php/server
```

29
test.sh Executable file
View File

@ -0,0 +1,29 @@
#! /bin/bash
SCRIPTDIR="$(dirname "$0")"
# Call me with the path to the script/program running the server
# Otherwise we try looking in the parent folder (if the tests are a submodule)
if [ -z "$1" ]; then
potential_server="$(readlink -m "$SCRIPTDIR"/server)"
echo $potential_server
if [ -x "$potential_server" ]; then
export FORGEHOOKENDPOINT="$potential_server"
elif [ -x "$(readlink -m "$potential_server"/../../server)" ]; then
export FORGEHOOKENDPOINT="$(readlink -m "$potential_server"/../../server)"
else
echo "test.sh SERVER"
exit 1
fi
else
[ ! -x "$1" ] && echo "Cannot execute "$1"" && exit 2
export FORGEHOOKENDPOINT="$(readlink -m $1)"
fi
ORIGDIR="$(pwd)"
cd "$SCRIPTDIR"
export FORGEHOOK="$(pwd)/tests/mock-forgehook.sh"
bats tests/*.bats
cd "$ORIGDIR"

46
tests/gitea.bats Normal file
View File

@ -0,0 +1,46 @@
#! /bin/bash
function setup {
# Load helper functions
load helper
# Which forgehook implementation to use?
if [ -z "$FORGEHOOK" ]; then FORGEHOOK="forgehook"; fi
port=$(find_free_port)
[ ! -z "$FORGEHOOKENDPOINT" ]
TMPFILE=$(mktemp)
# Need 3>&- so bats doesn't hang because of background task
$FORGEHOOKENDPOINT $port 3>&- &
export FORGEHOOKPID="$!"
[[ $? = 0 ]]
export FORGEHOOKSRV="http://localhost:$port"
}
function teardown {
# If setup fails, $FORGEHOOKPID will be empty so nothing to clean
if [ ! -z "$FORGEHOOKPID" ]; then
# Also kill the PID's children processes
kill $(ps -o pid= --ppid $FORGEHOOKPID)
fi
if [ -f $TMPFILE ]; then rm $TMPFILE; fi
}
@test "correct signature works" {
repo="https://tildegit.org/forge/hook.sh"
webhook="$(gen_webhook tests/gitea.json "$repo")"
sig="$(hash_hmac sha256 "$webhook" "$($FORGEHOOK secret $repo)")"
run send_webhook "${FORGEHOOKSRV}?action=gitea" "$webhook" "$sig" "X-Gitea-Signature"
[ $status -eq 0 ]
[[ "$output" -eq "200" ]]
}
@test "incorrect signature fails" {
repo="https://tildegit.org/forge/hook.sh"
webhook="$(gen_webhook tests/gitea.json "$repo")"
# Calculate wrong signature
sig="$(hash_hmac sha256 "EXTRA$webhook" "$($FORGEHOOK secret $repo)")"
run send_webhook "${FORGEHOOKSRV}?action=gitea" "$webhook" "$sig" "X-Gitea-Signature"
[ "$status" -eq 2 ]
[[ "$output" = "403" ]]
}

5
tests/gitea.json Normal file
View File

@ -0,0 +1,5 @@
{
"repository": {
"html_url": "$repo_url"
}
}

46
tests/github.bats Normal file
View File

@ -0,0 +1,46 @@
#! /bin/bash
function setup {
# Load helper functions
load helper
# Which forgehook implementation to use?
if [ -z "$FORGEHOOK" ]; then FORGEHOOK="forgehook"; fi
port=$(find_free_port)
[ ! -z "$FORGEHOOKENDPOINT" ]
TMPFILE=$(mktemp)
# Need 3>&- so bats doesn't hang because of background task
$FORGEHOOKENDPOINT $port 3>&- &
export FORGEHOOKPID="$!"
[[ $? = 0 ]]
export FORGEHOOKSRV="http://localhost:$port"
}
function teardown {
# If setup fails, $FORGEHOOKPID will be empty so nothing to clean
if [ ! -z "$FORGEHOOKPID" ]; then
# Also kill the PID's children processes
kill $(ps -o pid= --ppid $FORGEHOOKPID)
fi
if [ -f $TMPFILE ]; then rm $TMPFILE; fi
}
@test "correct signature works" {
repo="https://tildegit.org/forge/hook.sh"
webhook="$(gen_webhook tests/github.json "$repo")"
sig="$(hash_hmac sha256 "$webhook" "$($FORGEHOOK secret $repo)")"
run send_webhook "${FORGEHOOKSRV}?action=github" "$webhook" "$sig" "X-Hub-Signature"
[ $status -eq 0 ]
[[ "$output" -eq "200" ]]
}
@test "incorrect signature fails" {
repo="https://tildegit.org/forge/hook.sh"
webhook="$(gen_webhook tests/gitea.json "$repo")"
# Calculate wrong signature
sig="$(hash_hmac sha256 "EXTRA$webhook" "$($FORGEHOOK secret $repo)")"
run send_webhook "${FORGEHOOKSRV}?action=github" "$webhook" "$sig" "X-Hub-Signature"
[ "$status" -eq 2 ]
[[ "$output" = "403" ]]
}

5
tests/github.json Normal file
View File

@ -0,0 +1,5 @@
{
"repository": {
"html_url": "$repo_url"
}
}

44
tests/gitlab.bats Normal file
View File

@ -0,0 +1,44 @@
#! /bin/bash
function setup {
# Load helper functions
load helper
# Which forgehook implementation to use?
if [ -z "$FORGEHOOK" ]; then FORGEHOOK="forgehook"; fi
port=$(find_free_port)
[ ! -z "$FORGEHOOKENDPOINT" ]
TMPFILE=$(mktemp)
# Need 3>&- so bats doesn't hang because of background task
$FORGEHOOKENDPOINT $port 3>&- &
export FORGEHOOKPID="$!"
[[ $? = 0 ]]
export FORGEHOOKSRV="http://localhost:$port"
}
function teardown {
# If setup fails, $FORGEHOOKPID will be empty so nothing to clean
if [ ! -z "$FORGEHOOKPID" ]; then
# Also kill the PID's children processes
kill $(ps -o pid= --ppid $FORGEHOOKPID)
fi
if [ -f $TMPFILE ]; then rm $TMPFILE; fi
}
@test "correct token works" {
repo="https://tildegit.org/forge/hook.sh"
webhook="$(gen_webhook tests/gitlab.json "$repo")"
run send_webhook "${FORGEHOOKSRV}?action=gitlab" "$webhook" "$($FORGEHOOK secret $repo)" "X-Gitlab-Token"
[ $status -eq 0 ]
[[ "$output" = "200" ]]
}
@test "incorrect token fails" {
repo="https://tildegit.org/forge/hook.sh"
webhook="$(gen_webhook tests/gitlab.json "$repo")"
# Send FAKE token
run send_webhook "${FORGEHOOKSRV}?action=gitlab" "$webhook" "FAKE" "X-Gitlab-Token"
[ "$status" -eq 2 ]
[[ "$output" = "403" ]]
}

5
tests/gitlab.json Normal file
View File

@ -0,0 +1,5 @@
{
"project": {
"git_http_url": "$repo_url"
}
}

59
tests/helper.bash Executable file
View File

@ -0,0 +1,59 @@
#! /bin/bash
# https://unix.stackexchange.com/a/423052
function find_free_port {
comm -23 <(seq 49152 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1
}
# gen_webhook "REPO_URL"
#function gen_webhook {
# echo "{ \"project\": { \"git_http_url\": \"$1\" } }"
#}
function gen_webhook() {
export repo_url="$2"
envsubst < "$1"
}
# send_webhook "ENDPOINT" "PAYLOAD" "SECRET" "HEADER"
# ENDPOINT: where to send the request
# PAYLOAD: POST body
# SECRET: the secret for this transaction
# HEADER: where to store the secret
function send_webhook {
echo "$2" > $TMPFILE
# We can make a few attempts, just in case the webserver hasn't started yet
n=0
while [[ "$status" != "0" ]]; do
if [ $n -eq 3 ]; then
# Failed to reach server after 3 attempts
return 1;
fi
# --data-binary so that newlines aren't broken
# (otherwise, signature won't match)
run curl --header "Content-Type: application/json" \
--header ""$4": "$3"" \
--request POST \
--data-binary @$TMPFILE \
-s -w "%{http_code}" \
"$1"
# Requested succeeded, break out of loop
if [ $status -eq 0 ]; then
echo "$output"
if [[ ! "$output" = 200 ]]; then return 2; fi
return 0;
fi
((n++))
done
}
# https://stackoverflow.com/a/7385197
function hash_hmac {
digest="$1"
data="$2"
key="$3"
shift 3
# Don't print (stdin)= ...
echo -n "$data" | openssl dgst "-$digest" -hmac "$key" | awk '{print $2}'
}

10
tests/mock-forgehook.sh Executable file
View File

@ -0,0 +1,10 @@
#! /bin/bash
# This script mocks forgehook for use by webhook endpoints, only for testing purposes
# It returns a secret being simply the hashed URL of the repo
[[ "$#" != 2 ]] && echo "WRONG ARGUMENTS ($#): $@" && exit 1
[[ "$1" != "secret" ]] && echo "UNIMPLEMENTED" && exit 2
echo -n "$2" | sha256sum | cut -d ' ' -f 1