diff --git a/src/tests/Makefile b/src/tests/Makefile index f7ab1f9b400f3..8d3b06ad39352 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -115,7 +115,7 @@ SECRET = testing123 # # Build the directory for testing the server # -all: tests +all: tests tests.load clean: @rm -f test.conf dictionary *.ok *.log $(BUILD_DIR)/tests/eap @@ -136,7 +136,7 @@ test.conf: dictionary config/eap-test fi ${Q}echo "testdir =" $(TEST_PATH) >> $@ ${Q}echo 'logdir = $${testdir}' >> $@ - ${Q}echo "maindir =" $(RADDB_PATH) >> $@ + ${Q}echo "maindir =" $(RADDB_PATH:/=) >> $@ ${Q}echo 'radacctdir = $${testdir}' >> $@ ${Q}echo 'pidfile = $${testdir}/radiusd.pid' >> $@ ${Q}echo 'panic_action = "gdb -batch -x $${testdir}/panic.gdb %e %p > $${testdir}/gdb.log 2>&1; cat $${testdir}/gdb.log"' >> $@ @@ -144,12 +144,21 @@ test.conf: dictionary config/eap-test ${Q}echo ' allow_vulnerable_openssl = yes' >> $@ ${Q}echo '}' >> $@ ${Q}echo >> $@ - ${Q}echo 'modconfdir = $${maindir}mods-config' >> $@ + ${Q}echo 'modconfdir = $${maindir}/mods-config' >> $@ ${Q}echo 'certdir = $${maindir}/certs' >> $@ ${Q}echo 'cadir = $${maindir}/certs' >> $@ ${Q}echo '$$INCLUDE $${testdir}/config/' >> $@ ${Q}echo '$$INCLUDE $${maindir}/radiusd.conf' >> $@ +$(RADDB_PATH)certs/ca.pem: + ${Q}make -C $(RADDB_PATH)certs ca.pem + +$(RADDB_PATH)certs/server.pem: + ${Q}make -C $(RADDB_PATH)certs server.pem + +$(RADDB_PATH)certs/client.pem: + ${Q}make -C $(RADDB_PATH)certs client.pem + # # Rename "inner-tunnel", and ensure that it only uses the "eap-test" module. # @@ -175,7 +184,7 @@ config/eap-test: $(RADDB_PATH)mods-available/eap config/eap-test-inner-tunnel -e 's/cipher_list = "DEFAULT"/cipher_list = "DEFAULT${SECLEVEL}"/' \ < $< > $@ -radiusd.pid: test.conf +radiusd.pid: test.conf $(RADDB_PATH)certs/ca.pem $(RADDB_PATH)certs/server.pem ${Q}rm -rf $(TEST_PATH)/gdb.log $(TEST_PATH)/radius.log $(TEST_PATH)/tlscache ${Q}mkdir -p $(TEST_PATH)/tlscache ${Q}printf "Starting server... " @@ -326,4 +335,12 @@ tests.runtests: test.conf | radiusd.kill radiusd.pid ${Q}chmod a+x runtests.sh ${Q}BIN_PATH="$(BIN_PATH)" PORT="$(PORT)" ./runtests.sh $(TESTS) +# probably not the best way to do this +.PHONY: $(BUILD_PATH)/tests/tls-load +$(BUILD_PATH)/tests/tls-load: + ${Q}mkdir -p $@ + +tests.load: $(BUILD_PATH)/tests/tls-load $(RADDB_PATH)certs/ca.pem $(RADDB_PATH)certs/server.pem $(RADDB_PATH)certs/client.pem + ${Q}(cd tls-load && BUILD_DIR=${BUILD_PATH} LIB_DIR=${LIB_PATH} DICT_DIR=${top_builddir}/share RADDB_DIR=${top_builddir}/raddb ./run_test.sh -o $(BUILD_PATH)/tests/tls-load 10) + tests: tests.runtests tests.eap diff --git a/src/tests/tls-load/Dockerfile b/src/tests/tls-load/Dockerfile new file mode 100644 index 0000000000000..c173404f54af6 --- /dev/null +++ b/src/tests/tls-load/Dockerfile @@ -0,0 +1,6 @@ +ARG from=ubuntu:latest +FROM ${from} as build +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update +RUN apt-get install -y libtalloc-dev diff --git a/src/tests/tls-load/README.md b/src/tests/tls-load/README.md new file mode 100644 index 0000000000000..61205505d58a4 --- /dev/null +++ b/src/tests/tls-load/README.md @@ -0,0 +1,18 @@ +# TLS Load Testing + +Runs local radiusd load testing for TLS using docker compose. +Requires Docker. +### Usage: +`./run_test.sh -o output (number of concurrent clients/servers running)` +### Options: +- `-n`: number of messages to send per client (default is 1000) +- `-l`: log level for home/proxy servers (default is 1, 1=`radiusd -f`, 2=`radiusd -fx`, 3=`radiusd -fxx`, other=no log files generated) +- `-o`: where to put log files after running (if left empty, output will not be generated) + +### Output: +- `client_*.log`: Output of radclient for each running client +- `home_*.log`: Output from radiusd of home servers +- `proxy.log`: Output from radiusd of proxy server + +### Result: +The script will exit 0 if all clients succeed or 1 if any client fails, and will print a corresponding message. diff --git a/src/tests/tls-load/docker-compose.yml b/src/tests/tls-load/docker-compose.yml new file mode 100644 index 0000000000000..fe2164f1576f5 --- /dev/null +++ b/src/tests/tls-load/docker-compose.yml @@ -0,0 +1,78 @@ +version: "3" + +name: test-container + +networks: + test-net: + +services: + tasks: + image: radius_libraries + build: + context: . + args: + from: ${BASE_IMAGE} + entrypoint: bash -c "mkdir /test/containers ; rm /test/containers/*" + volumes: + - ./test:/test + + proxy: + depends_on: + - home + - client + image: radius_libraries + build: + context: . + args: + from: ${BASE_IMAGE} + entrypoint: /test/proxy-entrypoint.sh + environment: + - LOG_LEVEL + networks: + - test-net + volumes: + - ./test:/test + - ${JLIBTOOL_DIR}:/fbin + - ${BUILD_DIR}:/build + - ${DICT_DIR}:/share + - ${RADDB_DIR}:/raddb + + home: + depends_on: + - tasks + image: radius_libraries + build: + context: . + args: + from: ${BASE_IMAGE} + entrypoint: /test/home-entrypoint.sh + environment: + - LOG_LEVEL + networks: + - test-net + volumes: + - ./test:/test + - ${JLIBTOOL_DIR}:/fbin + - ${BUILD_DIR}:/build + - ${DICT_DIR}:/share + - ${RADDB_DIR}:/raddb + + client: + depends_on: + - tasks + image: radius_libraries + build: + context: . + args: + from: ${BASE_IMAGE} + entrypoint: /test/client-entrypoint.sh + environment: + - NUM_REQUESTS + networks: + - test-net + volumes: + - ./test:/test + - ${JLIBTOOL_DIR}:/fbin + - ${BUILD_DIR}:/build + - ${DICT_DIR}:/share + - ${RADDB_DIR}:/raddb diff --git a/src/tests/tls-load/run_test.sh b/src/tests/tls-load/run_test.sh new file mode 100755 index 0000000000000..a2a396a47db5c --- /dev/null +++ b/src/tests/tls-load/run_test.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# Other shells not tested +# Arguments in caps are exported to containers or relevant in docker-compose.yml + +export BASE_IMAGE=${BASE_IMAGE:-ubuntu:latest} +export JLIBTOOL_DIR=${JLIBTOOL_DIR:-../../../scripts/bin} +export BUILD_DIR=${BUILD_DIR:-../../../build} +export DICT_DIR=${DICT_DIR:-../../../share} +export RADDB_DIR=${RADDB_DIR:-../../../raddb} +yes | docker compose rm +if ! [ "$?" -eq 0 ]; then + echo "Docker failed" + exit 1 +fi + +### ARGUMENT PARSING ### +# Note -s and -b require the freeradius docker image to be rebuilt, meaning it must be removed, or renamed with -i. +while getopts ':n:l:d:r:o:bm' OPTION; do + case "$OPTION" in + n) + NUM_REQUESTS="$OPTARG" + ;; + l) + LOG_LEVEL="$OPTARG" + ;; + o) + output_dir="$OPTARG" + ;; + ?) + echo "Usage: $0 [-n number_of_requests_per_realm] [-l log_level (0=none, 1,2,3=radiusd -f, -fx, -fxx)] [-o output_log_dir] number_of_realms" + exit 1 + ;; + esac +done +shift "$(($OPTIND-1))" + +max_container_num=100 +num_realms="$1" +if [[ "/$num_realms" = "/" ]]; then + echo "No container num given" + exit 1 +fi +if [ "$num_realms" -gt "$max_container_num" ]; then + echo "You have tried to create more than $max_container_num docker containers, exiting load testing script without running" + exit 1 +fi +export NUM_REQUESTS=${NUM_REQUESTS:-5000} +export LOG_LEVEL=${LOG_LEVEL:-1} + +### DOCKER ### +docker compose up -d --scale home="$num_realms" --scale client="$num_realms" +if ! [ "$?" -eq 0 ]; then + echo "Docker failed" + exit 1 +fi + +# Check for when all of the client containers have exited +while docker compose ps | grep client; do + sleep 3 +done + +# We don't care about the processes now, and we need to send a SIGKILL because of zombie processes spawned by jlibtool. +docker compose kill + +# Move all the output to an external directory if specified +if [[ "/$output_dir" != "/" ]]; then + docker logs test-container-proxy-1 > "$output_dir"/proxy.log + i=1 + while [ "$i" -le $num_realms ]; do + docker logs test-container-client-"$i" > "$output_dir"/client_"$i".log& + docker logs test-container-home-"$i" > "$output_dir"/home_"$i".log& + i=$((i+1)) + done +fi + +# Check that the number of clients that were created is the same as the number of clients that were created and exited successfully +if [ $(docker compose ps --all | grep -c client) -eq $(docker compose ps --all | grep -c "client.*Exited (0)") ]; then + echo "TLS load test succeeded" + yes | docker compose rm &> /dev/null + exit 0 +else + echo "TLS load test failed" + yes | docker compose rm &> /dev/null + exit 1 +fi diff --git a/src/tests/tls-load/test/client-entrypoint.sh b/src/tests/tls-load/test/client-entrypoint.sh new file mode 100755 index 0000000000000..839b3b4babdd8 --- /dev/null +++ b/src/tests/tls-load/test/client-entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/bash +touch /test/containers/realm_"$HOSTNAME" +# Unfortunately, I don't really know a better way to synchronize the clients than a short sleep length +# and a sufficient number of requests. +# It still seems like sometimes one can start inordinately later than the others. +while ! [ -f "/test/containers/proxy-running" ]; do + sleep 0.1 +done +rm /test/containers/realm_"$HOSTNAME" +echo User-Name="bob@realm_$HOSTNAME",User-Password="bob",Message-Authenticator=0x00 | /fbin/radclient -c "$NUM_REQUESTS" test-container-proxy-1 auth testing123 +if [ "$?" -ne 0 ] ; then + echo "This container failed" + exit 1 +else + echo "This container succeeded" +fi diff --git a/src/tests/tls-load/test/containers/proxy-running b/src/tests/tls-load/test/containers/proxy-running new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tests/tls-load/test/home-entrypoint.sh b/src/tests/tls-load/test/home-entrypoint.sh new file mode 100755 index 0000000000000..24ea49971390f --- /dev/null +++ b/src/tests/tls-load/test/home-entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/bash +if [ "$LOG_LEVEL" -eq 2 ]; then + exec /fbin/radiusd -d /test/home -fx -l stdout +elif [ "$LOG_LEVEL" -eq 3 ]; then + exec /fbin/radiusd -d /test/home -fxx -l stdout +else + exec /fbin/radiusd -d /test/home -f -l stdout +fi diff --git a/src/tests/tls-load/test/home/radiusd.conf b/src/tests/tls-load/test/home/radiusd.conf new file mode 100644 index 0000000000000..ca2c6aaa9dcf8 --- /dev/null +++ b/src/tests/tls-load/test/home/radiusd.conf @@ -0,0 +1,104 @@ +# +# Minimal radiusd.conf for testing +# +raddb = /raddb +modconfdir = ${raddb}/mods-config +testdir = /test +pidfile = ${testdir}/radiusd.pid +panic_action = "gdb -batch -x ${raddb}/panic.gdb %e %p > ${testdir}/gdb-radiusd.log 2>&1; cat ${testdir}/gdb-radiusd.log" +certdir = ${raddb}/certs +cadir = ${raddb}/certs +libdir = /build/objs/src/modules + +max_requests = 1048576 + +thread pool { + start_servers = 5 + max_servers = 32 + min_spare_servers = 3 + max_spare_servers = 10 + max_requests_per_server = 0 + cleanup_delay = 5 + max_queue_size = 65536 + auto_limit_acct = no +} + +# +# Referenced by some modules for default thread pool configuration +# +modules { + $INCLUDE ${raddb}/mods-available/always +} + +clients radsec { + client all { + ipaddr = 0.0.0.0/0 + proto = tls + } +} + +listen { + type = auth + + ipaddr = * + port = 1812 + proto = tcp + + clients = radsec + + virtual_server = default + + tls { + private_key_password = whatever + private_key_file = ${certdir}/server.pem + certificate_file = ${certdir}/server.pem + ca_file = ${cadir}/ca.pem + fragment_size = 8192 + ca_path = ${cadir} + cipher_list = "DEFAULT" + tls_min_version = "1.2" + tls_max_version = "1.2" + } +} + +listen { + type = acct + + ipaddr = * + port = 1813 + proto = tcp + + clients = radsec + + virtual_server = default + + tls { + private_key_password = whatever + private_key_file = ${certdir}/server.pem + certificate_file = ${certdir}/server.pem + ca_file = ${cadir}/ca.pem + fragment_size = 8192 + ca_path = ${cadir} + cipher_list = "DEFAULT" + tls_min_version = "1.3" + tls_max_version = "1.3" + } +} + +server default { + authorize { + update control { + Auth-Type := accept + } + } + + preacct { + update control { + Response-Packet-Type := Accounting-Response + } + } + + acct { + ok + } +} diff --git a/src/tests/tls-load/test/proxy-entrypoint.sh b/src/tests/tls-load/test/proxy-entrypoint.sh new file mode 100755 index 0000000000000..427af5948855f --- /dev/null +++ b/src/tests/tls-load/test/proxy-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Unfortunately, our clients do not know their own human readable name as far as I can tell, so the proxy must know their HOSTNAME to have information in common for realms +ls /test/containers | grep realm_ > /client-hostnames.txt + +# We will generate the proxy.conf file that will be used in a separate directory +mkdir /eqx +touch /eqx/proxy.conf +cp /test/proxy/radiusd.conf /eqx/radiusd.conf + +i=1 +while read -r -u 3 c_name +do + cat /test/proxy/proxy.PART | sed -e "s/##C_NAME##/$c_name/g" -e "s/##H_IP##/test-container-home-$i/g" >> /eqx/proxy.conf + i=$((i+1)) +done 3<"/client-hostnames.txt" + +# This file is so the clients know the proxy is starting the server soon; checking at an earlier point is possible to fail if hostname resolution is very slow +touch /test/containers/proxy-running +if [ "$LOG_LEVEL" -eq 2 ]; then + exec /fbin/radiusd -d /eqx -fx -l stdout +elif [ "$LOG_LEVEL" -eq 3 ]; then + exec /fbin/radiusd -d /eqx -fxx -l stdout +else + exec /fbin/radiusd -d /eqx -f -l stdout +fi diff --git a/src/tests/tls-load/test/proxy/proxy.PART b/src/tests/tls-load/test/proxy/proxy.PART new file mode 100644 index 0000000000000..0625b9f966380 --- /dev/null +++ b/src/tests/tls-load/test/proxy/proxy.PART @@ -0,0 +1,34 @@ +home_server ##C_NAME## { + ipaddr = ##H_IP## + port = 1812 + type = auth+acct + secret = radsec + proto = tcp + status_check = none + + nonblock = yes + + revive_interval = 10 + + tls { + private_key_password = whatever + private_key_file = ${certdir}/client.pem + certificate_file = ${certdir}/client.pem + ca_file = ${cadir}/ca.pem + fragment_size = 8192 + ca_path = ${cadir} + cipher_list = "DEFAULT" + tls_min_version = "1.2" + tls_max_version = "1.2" + } +} + +home_server_pool ##C_NAME## { + type = fail-over + home_server = ##C_NAME## +} + +realm "##C_NAME##" { + pool = ##C_NAME## + nostrip +} diff --git a/src/tests/tls-load/test/proxy/radiusd.conf b/src/tests/tls-load/test/proxy/radiusd.conf new file mode 100644 index 0000000000000..56fddf67c7392 --- /dev/null +++ b/src/tests/tls-load/test/proxy/radiusd.conf @@ -0,0 +1,67 @@ +raddb = /raddb +modconfdir = ${raddb}/mods-config +testdir = /test +pidfile = ${testdir}/radiusd.pid +panic_action = "gdb -batch -x ${raddb}/panic.gdb %e %p > ${testdir}/gdb-radiusd.log 2>&1; cat ${testdir}/gdb-radiusd.log" +certdir = ${raddb}/certs +cadir = ${raddb}/certs +libdir = /build/objs/src/modules + +max_requests = 1048576 + +thread pool { + start_servers = 5 + max_servers = 32 + min_spare_servers = 3 + max_spare_servers = 10 + max_requests_per_server = 0 + cleanup_delay = 5 + max_queue_size = 65536 + auto_limit_acct = no +} + +# +# Minimum configuration for Proxy Server -> SRADIUSD +# +$INCLUDE proxy.conf + +modules { + $INCLUDE ${raddb}/mods-available/realm +} + +clients indocker { + client all { + ipaddr = 0.0.0.0/0 + secret = testing123 + proto = * + } +} + +listen { + type = auth + ipaddr = * + port = 1812 + proto = udp + clients = indocker + virtual_server = default +} + +listen { + type = acct + ipaddr = * + port = 1813 + proto = udp + clients = indocker + virtual_server = default +} + + +server default { + authorize { + suffix + } + + preacct { + suffix + } +}