core - Add misc tests

This commit is contained in:
Théophile Diot 2023-05-18 14:05:27 -04:00
parent 7158e7e9a1
commit 10bdf551aa
No known key found for this signature in database
GPG Key ID: E752C80DB72BB014
7 changed files with 555 additions and 1 deletions

View File

@ -0,0 +1,16 @@
FROM python:3.11.3-alpine
WORKDIR /tmp
COPY requirements.txt .
RUN MAKEFLAGS="-j $(nproc)" pip install --no-cache -r requirements.txt && \
rm -f requirements.txt
WORKDIR /opt/tests
COPY main.py .
RUN apk add --no-cache curl
ENTRYPOINT [ "python3", "main.py" ]

View File

@ -0,0 +1,27 @@
version: "3.5"
services:
tests:
build: .
environment:
PYTHONUNBUFFERED: "1"
GENERATE_SELF_SIGNED_SSL: "no"
DISABLE_DEFAULT_SERVER: "no"
REDIRECT_HTTP_TO_HTTPS: "no"
AUTO_REDIRECT_HTTP_TO_HTTPS: "yes"
ALLOWED_METHODS: "GET|POST|HEAD"
MAX_CLIENT_SIZE: "5m"
SERVE_FILES: "yes"
SSL_PROTOCOLS: "TLSv1.2 TLSv1.3"
HTTP2: "yes"
LISTEN_HTTP: "yes"
DENY_HTTP_STATUS: "403"
extra_hosts:
- "www.example.com:192.168.0.2"
networks:
bw-services:
ipv4_address: 192.168.0.3
networks:
bw-services:
external: true

View File

@ -0,0 +1,72 @@
version: "3.5"
services:
bw:
image: bunkerity/bunkerweb:1.5.0-beta
pull_policy: never
labels:
- "bunkerweb.INSTANCE"
volumes:
- ./index.html:/var/www/html/index.html
environment:
API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24 192.168.0.3"
HTTP_PORT: "80"
HTTPS_PORT: "443"
USE_BUNKERNET: "no"
USE_BLACKLIST: "no"
LOG_LEVEL: "info"
GENERATE_SELF_SIGNED_SSL: "no"
USE_MODSECURITY: "no"
# ? MISC settings
DISABLE_DEFAULT_SERVER: "no"
REDIRECT_HTTP_TO_HTTPS: "no"
AUTO_REDIRECT_HTTP_TO_HTTPS: "yes"
ALLOWED_METHODS: "GET|POST|HEAD"
MAX_CLIENT_SIZE: "5m"
SERVE_FILES: "yes"
SSL_PROTOCOLS: "TLSv1.2 TLSv1.3"
HTTP2: "yes"
LISTEN_HTTP: "yes"
DENY_HTTP_STATUS: "403"
networks:
bw-universe:
bw-services:
ipv4_address: 192.168.0.2
bw-scheduler:
image: bunkerity/bunkerweb-scheduler:1.5.0-beta
pull_policy: never
depends_on:
- bw
- bw-docker
environment:
DOCKER_HOST: "tcp://bw-docker:2375"
LOG_LEVEL: "info"
networks:
- bw-universe
- bw-docker
bw-docker:
image: tecnativa/docker-socket-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
CONTAINERS: "1"
networks:
- bw-docker
networks:
bw-universe:
name: bw-universe
ipam:
driver: default
config:
- subnet: 10.20.30.0/24
bw-services:
name: bw-services
ipam:
driver: default
config:
- subnet: 192.168.0.0/24
bw-docker:

View File

300
tests/core/misc/main.py Normal file
View File

@ -0,0 +1,300 @@
from os import getenv
from subprocess import run
from requests import ConnectionError, head, options, post
from socket import create_connection
from ssl import CERT_NONE, create_default_context
from time import sleep
from traceback import format_exc
try:
ssl_generated = getenv("GENERATE_SELF_SIGNED_SSL", "no") == "yes"
disabled_default_server = getenv("DISABLE_DEFAULT_SERVER", "no") == "yes"
deny_http_status = getenv("DENY_HTTP_STATUS", "403")
listen_http = getenv("LISTEN_HTTP", "no") == "yes"
error = False
print(
" Sending a HEAD request to http://192.168.0.2 (default server) to test DISABLE_DEFAULT_SERVER",
flush=True,
)
try:
response = head("http://192.168.0.2")
if response.status_code != 403 and disabled_default_server:
print(
"❌ Request didn't get rejected, even if default server is disabled, exiting ...",
flush=True,
)
exit(1)
elif response.status_code == 403:
if not disabled_default_server:
print(
"❌ Request got rejected, even if the default server is enabled, exiting ...",
flush=True,
)
exit(1)
if deny_http_status != "403":
print(
f"❌ Request got rejected, but the status code shouldn't be 403 as DENY_HTTP_STATUS is set to {deny_http_status}, exiting ...",
flush=True,
)
exit(1)
print("✅ Request got rejected, as expected", flush=True)
else:
if not listen_http:
print(
"❌ Request didn't get rejected, even if the server is not listening on HTTP, exiting ...",
flush=True,
)
exit(1)
if response.status_code not in (404, 301):
response.raise_for_status()
print("✅ Request didn't get rejected, as expected", flush=True)
except ConnectionError as e:
if listen_http:
if deny_http_status == "403" or not disabled_default_server:
raise e
print(
"✅ Request got rejected with the expected deny_http_status", flush=True
)
exit(0)
else:
print(
"✅ Request got rejected because the server is not listening on HTTP, as expected",
flush=True,
)
if ssl_generated:
sleep(1)
ssl_protocols = getenv("SSL_PROTOCOLS", "TLSv1.2 TLSv1.3")
print(
f" Creating a socket and wrapping it with SSL an SSL context to test SSL_PROTOCOLS",
flush=True,
)
sock = create_connection(("www.example.com", 443))
ssl_context = create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = CERT_NONE
ssl_sock = ssl_context.wrap_socket(sock, server_hostname="www.example.com")
if ssl_sock.version() not in ssl_protocols.split(" "):
print(
f"❌ SSL_PROTOCOLS is set to {ssl_protocols}, but the socket is using {ssl_sock.version()}, exiting ...",
flush=True,
)
exit(1)
print("✅ Socket is using the expected SSL protocol", flush=True)
if not listen_http:
exit(0)
else:
print(
f" Skipping SSL_PROTOCOLS test as SSL is disabled",
flush=True,
)
sleep(1)
redirect_http_to_https = getenv("REDIRECT_HTTP_TO_HTTPS", "no") == "yes"
auto_redirect_http_to_https = getenv("AUTO_REDIRECT_HTTP_TO_HTTPS", "no") == "yes"
print(
f" Sending a HEAD request to http://www.example.com to test {'auto ' if auto_redirect_http_to_https else ''}redirect_http_to_https",
flush=True,
)
response = head("http://www.example.com", headers={"Host": "www.example.com"})
if response.status_code == 403:
print(
"✅ Request got rejected, as expected because the server is not listening on HTTP",
flush=True,
)
else:
if response.status_code not in (404, 301):
response.raise_for_status()
if (
redirect_http_to_https or (auto_redirect_http_to_https and ssl_generated)
) and response.status_code != 301:
print(
f"❌ Request didn't get redirected, even if {'auto ' if auto_redirect_http_to_https else ''}redirect_http_to_https is enabled, exiting ...",
flush=True,
)
exit(1)
print("✅ Request got redirected to https, as expected", flush=True)
sleep(1)
allowed_methods = getenv("ALLOWED_METHODS", "GET|POST|HEAD")
print(
f" Sending a OPTIONS request to http{'s' if ssl_generated else ''}://www.example.com to test ALLOWED_METHODS",
flush=True,
)
response = options(
f"http{'s' if ssl_generated else ''}://www.example.com",
headers={"Host": "www.example.com"},
)
if response.status_code == 405:
if "OPTIONS" in allowed_methods:
print(
"❌ Request got rejected, even if OPTIONS is in allowed methods, exiting ...",
flush=True,
)
exit(1)
print("✅ Request got rejected, as expected", flush=True)
else:
if response.status_code != 404:
response.raise_for_status()
if "OPTIONS" not in allowed_methods:
print(
"❌ Request didn't get rejected, even if OPTIONS is not in allowed methods, exiting ...",
flush=True,
)
exit(1)
print("✅ Request didn't get rejected, as expected", flush=True)
sleep(1)
max_client_size = getenv("MAX_CLIENT_SIZE", "5m")
print(
f" Sending a POST request to http{'s' if ssl_generated else ''}://www.example.com with a 5+MB body to test MAX_CLIENT_SIZE",
flush=True,
)
response = post(
f"http{'s' if ssl_generated else ''}://www.example.com",
headers={"Host": "www.example.com"},
data="a" * 5242881,
verify=not ssl_generated,
)
if response.status_code in (413, 400):
if max_client_size != "5m":
print(
f"❌ Request got rejected, but the status code shouldn't be 400 or 413 as MAX_CLIENT_SIZE is set to {max_client_size}, exiting ...",
flush=True,
)
exit(1)
print("✅ Request got rejected, as expected", flush=True)
else:
if response.status_code != 404:
response.raise_for_status()
if max_client_size == "5m":
print(
f"❌ Request didn't get rejected, even if MAX_CLIENT_SIZE is set to {max_client_size}, exiting ...",
flush=True,
)
exit(1)
print("✅ Request didn't get rejected, as expected", flush=True)
sleep(1)
serve_files = getenv("SERVE_FILES", "no") == "yes"
print(
f" Sending a HEAD request to http{'s' if ssl_generated else ''}://www.example.com/index.html to test the serve_files option",
flush=True,
)
response = head(
f"http{'s' if ssl_generated else ''}://www.example.com/index.html",
headers={"Host": "www.example.com"},
verify=not ssl_generated,
)
if response.status_code != 404 and not serve_files:
print(
"❌ Request didn't get rejected, even if serve_files is disabled, exiting ...",
flush=True,
)
exit(1)
elif response.status_code == 404:
if serve_files:
print(
"❌ Request got rejected, even if serve_files is enabled, exiting ...",
flush=True,
)
exit(1)
print("✅ Request got rejected, as expected", flush=True)
else:
response.raise_for_status()
print("✅ Request didn't get rejected, as expected", flush=True)
sleep(1)
http2 = getenv("HTTP2", "no") == "yes"
print(
f" Sending a GET request to http{'s' if ssl_generated else ''}://www.example.com with HTTP/2 to test HTTP2",
flush=True,
)
proc = run(
[
"curl",
"--insecure",
"--http2",
"-I",
"-H",
'"Host: www.example.com"',
f"http{'s' if ssl_generated else ''}://www.example.com",
"-w '%{response_code} %{http_version}'",
],
capture_output=True,
text=True,
check=True,
)
status_code, http_version = (
proc.stdout.splitlines()[-1].replace("'", "").strip().split(" ")
)
if status_code not in ("200", "404"):
print(
f"❌ Request didn't get accepted, exiting ...",
flush=True,
)
exit(1)
elif ssl_generated and http2 and http_version != "2":
print(
f"❌ Request didn't get accepted with HTTP/2, exiting ...",
flush=True,
)
exit(1)
elif (not ssl_generated or not http2) and http_version != "1.1":
print(
f"❌ Request got accepted with HTTP/2, it shouldn't have, exiting ...",
flush=True,
)
exit(1)
print(f"✅ Request got accepted with HTTP/{http_version}", flush=True)
except SystemExit as e:
exit(e.code)
except:
print(f"❌ Something went wrong, exiting ...\n{format_exc()}", flush=True)
exit(1)

View File

@ -0,0 +1 @@
requests==2.30.0

140
tests/core/misc/test.sh Normal file → Executable file
View File

@ -1 +1,139 @@
# TODO
#!/bin/bash
echo "🗃️ Building misc stack ..."
# Starting stack
docker compose pull bw-docker
if [ $? -ne 0 ] ; then
echo "🗃️ Pull failed ❌"
exit 1
fi
docker compose -f docker-compose.test.yml build
if [ $? -ne 0 ] ; then
echo "🗃️ Build failed ❌"
exit 1
fi
manual=0
end=0
cleanup_stack () {
exit_code=$?
if [[ $end -eq 1 || $exit_code = 1 ]] || [[ $end -eq 0 && $exit_code = 0 ]] && [ $manual = 0 ] ; then
find . -type f -name 'docker-compose.*' -exec sed -i 's@GENERATE_SELF_SIGNED_SSL: "yes"@GENERATE_SELF_SIGNED_SSL: "no"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@DISABLE_DEFAULT_SERVER: "yes"@DISABLE_DEFAULT_SERVER: "no"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@ALLOWED_METHODS: ".*"$@ALLOWED_METHODS: "GET|POST|HEAD"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@MAX_CLIENT_SIZE: "10m"@MAX_CLIENT_SIZE: "5m"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@SERVE_FILES: "no"@SERVE_FILES: "yes"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@SSL_PROTOCOLS: "TLSv1.2"@SSL_PROTOCOLS: "TLSv1.2 TLSv1.3"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@HTTP2: "no"@HTTP2: "yes"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@LISTEN_HTTP: "no"@LISTEN_HTTP: "yes"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@DENY_HTTP_STATUS: "444"@DENY_HTTP_STATUS: "403"@' {} \;
if [[ $end -eq 1 && $exit_code = 0 ]] ; then
return
fi
fi
echo "🗃️ Cleaning up current stack ..."
docker compose down -v --remove-orphans 2>/dev/null
if [ $? -ne 0 ] ; then
echo "🗃️ Down failed ❌"
exit 1
fi
echo "🗃️ Cleaning up current stack done ✅"
}
# Cleanup stack on exit
trap cleanup_stack EXIT
for test in "default" "ssl_generated" "tweaked" "deny_status_444" "TLSv1.2"
do
if [ "$test" = "default" ] ; then
echo "🗃️ Running tests when misc settings have default values except MAX_CLIENT_SIZE which have the value \"5m\" ..."
elif [ "$test" = "ssl_generated" ] ; then
echo "🗃️ Running tests when misc settings have default values and the ssl is generated in self signed ..."
find . -type f -name 'docker-compose.*' -exec sed -i 's@GENERATE_SELF_SIGNED_SSL: "no"@GENERATE_SELF_SIGNED_SSL: "yes"@' {} \;
elif [ "$test" = "tweaked" ] ; then
echo "🗃️ Running tests when misc settings have tweaked values ..."
echo " Keeping the ssl generated in self signed ..."
find . -type f -name 'docker-compose.*' -exec sed -i 's@DISABLE_DEFAULT_SERVER: "no"@DISABLE_DEFAULT_SERVER: "yes"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@ALLOWED_METHODS: ".*"$@ALLOWED_METHODS: "GET|POST|HEAD|OPTIONS"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@MAX_CLIENT_SIZE: "5m"@MAX_CLIENT_SIZE: "10m"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@SERVE_FILES: "yes"@SERVE_FILES: "no"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@HTTP2: "yes"@HTTP2: "no"@' {} \;
elif [ "$test" = "deny_status_444" ] ; then
echo "🗃️ Running tests when the server's deny status is set to 444 ..."
echo " Keeping the ssl generated in self signed ..."
find . -type f -name 'docker-compose.*' -exec sed -i 's@DENY_HTTP_STATUS: "403"@DENY_HTTP_STATUS: "444"@' {} \;
elif [ "$test" = "TLSv1.2" ] ; then
echo "🗃️ Running tests with only TLSv1.2 enabled and when the server is not listening on http ..."
echo " Keeping the ssl generated in self signed ..."
find . -type f -name 'docker-compose.*' -exec sed -i 's@DISABLE_DEFAULT_SERVER: "yes"@DISABLE_DEFAULT_SERVER: "no"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@SSL_PROTOCOLS: "TLSv1.2 TLSv1.3"@SSL_PROTOCOLS: "TLSv1.2"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@LISTEN_HTTP: "yes"@LISTEN_HTTP: "no"@' {} \;
fi
echo "🗃️ Starting stack ..."
docker compose up -d 2>/dev/null
if [ $? -ne 0 ] ; then
echo "🗃️ Up failed, retrying ... ⚠️"
manual=1
cleanup_stack
manual=0
docker compose up -d 2>/dev/null
if [ $? -ne 0 ] ; then
echo "🗃️ Up failed ❌"
exit 1
fi
fi
# Check if stack is healthy
echo "🗃️ Waiting for stack to be healthy ..."
i=0
while [ $i -lt 120 ] ; do
containers=("misc-bw-1" "misc-bw-scheduler-1")
healthy="true"
for container in "${containers[@]}" ; do
check="$(docker inspect --format "{{json .State.Health }}" $container | grep "healthy")"
if [ "$check" = "" ] ; then
healthy="false"
break
fi
done
if [ "$healthy" = "true" ] ; then
echo "🗃️ Docker stack is healthy ✅"
break
fi
sleep 1
i=$((i+1))
done
if [ $i -ge 120 ] ; then
docker compose logs
echo "🗃️ Docker stack is not healthy ❌"
exit 1
fi
# Start tests
docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from tests 2>/dev/null
if [ $? -ne 0 ] ; then
echo "🗃️ Test \"$test\" failed ❌"
echo "🛡️ Showing BunkerWeb and BunkerWeb Scheduler logs ..."
docker compose logs bw bw-scheduler
exit 1
else
echo "🗃️ Test \"$test\" succeeded ✅"
fi
manual=1
cleanup_stack
manual=0
echo " "
done
end=1
echo "🗃️ Tests are done ! ✅"