From a07377ce96e9524430262d55bb8d2004d7ecd87a Mon Sep 17 00:00:00 2001 From: Sarita Mahajan Date: Wed, 19 Nov 2025 23:27:18 +0000 Subject: [PATCH] test: add e2e tests for RV bypass functionality Add comprehensive e2e test for RV bypass (skip TO1) with CI support. Signed-off-by: Sarita Mahajan --- .github/workflows/ci.yml | 43 ++++++++ .github/workflows/containers.yml | 44 +++++++++ .../compose/client/fdo-client-rv-bypass.yaml | 20 ++++ .../compose/server/fdo-rv-bypass-servers.yaml | 66 +++++++++++++ scripts/fdo-utils.sh | 6 +- test/ci/test-rv-bypass.sh | 99 +++++++++++++++++++ test/ci/utils.sh | 22 ++++- test/container/test-rv-bypass.sh | 12 +++ test/container/utils.sh | 4 +- 9 files changed, 309 insertions(+), 7 deletions(-) create mode 100644 deployments/compose/client/fdo-client-rv-bypass.yaml create mode 100644 deployments/compose/server/fdo-rv-bypass-servers.yaml create mode 100755 test/ci/test-rv-bypass.sh create mode 100755 test/container/test-rv-bypass.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d90468e..a980bdb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -264,3 +264,46 @@ jobs: run: | source test/ci/test-onboarding-config.sh cleanup + + test-rv-bypass: + name: Test RV bypass (skip TO1) + runs-on: ubuntu-latest + steps: + - name: Install golang + uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - name: Check out server repository code + uses: actions/checkout@v4 + with: + path: go-fdo-server + + # TODO: Update to upstream once RV bypass PR is merged in go-fdo-client + # Tracking: https://github.com/fido-device-onboard/go-fdo-client/pull/38 + - name: Check out client repository code with RV bypass support + uses: actions/checkout@v4 + with: + repository: sarmahaj/go-fdo-client + ref: pr20-rv-bypass-fixes + path: go-fdo-client + + - name: Test RV bypass functionality + run: | + cd go-fdo-server + source test/ci/test-rv-bypass.sh + run_test + + - name: Get manufacturer and owner server logs + if: always() + run: | + cd go-fdo-server + source test/ci/test-rv-bypass.sh + get_logs + + - name: Cleanup the environment + if: always() + run: | + cd go-fdo-server + source test/ci/test-rv-bypass.sh + cleanup diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml index 94ee743d..0f1094e8 100644 --- a/.github/workflows/containers.yml +++ b/.github/workflows/containers.yml @@ -250,3 +250,47 @@ jobs: run: | source test/container/test-onboarding-config.sh cleanup + + test-container-rv-bypass: + name: Test RV bypass (skip TO1) + runs-on: ubuntu-latest + steps: + + - name: Install golang + uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - name: Check out server repository code + uses: actions/checkout@v4 + with: + path: go-fdo-server + + # TODO: Update to upstream once RV bypass PR is merged in go-fdo-client + # Tracking: https://github.com/fido-device-onboard/go-fdo-client/pull/38 + - name: Check out client repository code with RV bypass support + uses: actions/checkout@v4 + with: + repository: sarmahaj/go-fdo-client + ref: pr20-rv-bypass-fixes + path: go-fdo-client + + - name: Test RV bypass functionality + run: | + cd go-fdo-server + source test/container/test-rv-bypass.sh + run_test + + - name: Get manufacturer and owner server logs + if: always() + run: | + cd go-fdo-server + source test/container/test-rv-bypass.sh + get_logs + + - name: Cleanup the environment + if: always() + run: | + cd go-fdo-server + source test/container/test-rv-bypass.sh + cleanup diff --git a/deployments/compose/client/fdo-client-rv-bypass.yaml b/deployments/compose/client/fdo-client-rv-bypass.yaml new file mode 100644 index 00000000..39ec47f5 --- /dev/null +++ b/deployments/compose/client/fdo-client-rv-bypass.yaml @@ -0,0 +1,20 @@ +services: + go-fdo-client: + container_name: go-fdo-client + hostname: go-fdo-client + image: go-fdo-client + # Build from local go-fdo-client directory with RV bypass support + # Path is relative to this compose file: deployments/compose/client/ + # ../../../ goes to project root, then ../go-fdo-client to sibling dir + build: ../../../../go-fdo-client + working_dir: ${container_working_dir:-/workdir}/device-credentials + user: "${container_user}" + networks: + - fdo + volumes: + - ${base_dir:-./test/workdir}:${container_working_dir:-/workdir} + restart: no +networks: + fdo: + name: fdo + external: true diff --git a/deployments/compose/server/fdo-rv-bypass-servers.yaml b/deployments/compose/server/fdo-rv-bypass-servers.yaml new file mode 100644 index 00000000..960cb09f --- /dev/null +++ b/deployments/compose/server/fdo-rv-bypass-servers.yaml @@ -0,0 +1,66 @@ +include: + - fdo-networks.yaml +services: + manufacturer: + container_name: manufacturer + hostname: manufacturer + image: manufacturer + build: ../../.. + environment: + TZ: Europe/Madrid + user: ${container_user} + command: + - --db-type=sqlite + - --db-dsn=file:${container_working_dir:-/workdir}/manufacturer.db + - --log-level=debug + - manufacturing + - manufacturer:8038 + - --manufacturing-key=${container_working_dir:-/workdir}/certs/manufacturer.key + - --owner-cert=${container_working_dir:-/workdir}/certs/owner.crt + - --device-ca-cert=${container_working_dir:-/workdir}/certs/device_ca.crt + - --device-ca-key=${container_working_dir:-/workdir}/certs/device_ca.key + working_dir: ${container_working_dir:-/workdir} + volumes: + - ${base_dir:-./test/workdir}:${container_working_dir:-/workdir} + networks: + - fdo + ports: + - 8038:8038 + restart: unless-stopped + healthcheck: + test: [ "CMD", "curl --silent --output /dev/null --fail http://manufacturer:8038/health" ] + interval: 5s + timeout: 30s + retries: 5 + start_period: 5s + + owner: + container_name: owner + hostname: owner + image: owner + build: ../../.. + environment: + TZ: Europe/Madrid + user: ${container_user} + command: + - --db-type=sqlite + - --db-dsn=file:${container_working_dir:-/workdir}/owner.db + - --log-level=debug + - owner + - owner:8043 + - --owner-key=${container_working_dir:-/workdir}/certs/owner.key + - --device-ca-cert=${container_working_dir:-/workdir}/certs/device_ca.crt + working_dir: ${container_working_dir:-/workdir} + volumes: + - ${base_dir:-./test/workdir}:${container_working_dir:-/workdir} + networks: + - fdo + ports: + - 8043:8043 + restart: unless-stopped + healthcheck: + test: [ "CMD", "curl --silent --output /dev/null --fail http://owner:8043/health" ] + interval: 5s + timeout: 30s + retries: 5 + start_period: 5s diff --git a/scripts/fdo-utils.sh b/scripts/fdo-utils.sh index 6c2f4934..fabe307b 100644 --- a/scripts/fdo-utils.sh +++ b/scripts/fdo-utils.sh @@ -14,7 +14,8 @@ set_rendezvous_info () { local rendezvous_ip=$3 local rendezvous_port=$4 local rendezvous_protocol=$5 - local rendezvous_info="[{\"dns\": \"${rendezvous_dns}\", \"device_port\": \"${rendezvous_port}\", \"protocol\": \"${rendezvous_protocol}\", \"ip\": \"${rendezvous_ip}\", \"owner_port\": \"${rendezvous_port}\"}]" + local rv_bypass=$([[ "${6}" == "true" ]] && echo true || echo false) + local rendezvous_info="[{\"dns\": \"${rendezvous_dns}\", \"device_port\": \"${rendezvous_port}\", \"protocol\": \"${rendezvous_protocol}\", \"ip\": \"${rendezvous_ip}\", \"owner_port\": \"${rendezvous_port}\", \"rv_bypass\": ${rv_bypass}}]" curl --fail --verbose --silent --insecure \ --request POST \ --header 'Content-Type: text/plain' \ @@ -28,7 +29,8 @@ update_rendezvous_info () { local rendezvous_ip=$3 local rendezvous_port=$4 local rendezvous_protocol=$5 - local rendezvous_info="[{\"dns\": \"${rendezvous_dns}\", \"device_port\": \"${rendezvous_port}\", \"protocol\": \"${rendezvous_protocol}\", \"ip\": \"${rendezvous_ip}\", \"owner_port\": \"${rendezvous_port}\"}]" + local rv_bypass=$([[ "${6}" == "true" ]] && echo true || echo false) + local rendezvous_info="[{\"dns\": \"${rendezvous_dns}\", \"device_port\": \"${rendezvous_port}\", \"protocol\": \"${rendezvous_protocol}\", \"ip\": \"${rendezvous_ip}\", \"owner_port\": \"${rendezvous_port}\", \"rv_bypass\": ${rv_bypass}}]" curl --fail --verbose --silent --insecure \ --request PUT \ --header 'Content-Type: text/plain' \ diff --git a/test/ci/test-rv-bypass.sh b/test/ci/test-rv-bypass.sh new file mode 100755 index 00000000..3ccdd08f --- /dev/null +++ b/test/ci/test-rv-bypass.sh @@ -0,0 +1,99 @@ +#! /usr/bin/env bash +# RV bypass test: Device skips TO1 by getting Owner address directly from voucher (TO0 not needed) + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)/utils.sh" + +verify_rv_bypass_behavior() { + local guid="$1" + local device_log="${logs_dir}/onboarding-device-${guid}.log" + + echo " 🔍 Checking device onboard log for bypass indicators..." + + # Device onboarding completed successfully + if grep -q "FIDO Device Onboard Complete" "${device_log}" || grep -q "Credential Reuse Protocol" "${device_log}"; then + echo " ✅ Device onboarding completed" + else + echo " ❌ Device onboarding did not complete" + cat "${device_log}" + return 1 + fi + + # Rendezvous service was NOT contacted (device skips TO1, no TO0 needed) + if [ -f "${rendezvous_log}" ]; then + echo " ❌ Rendezvous log exists but should not with RV bypass!" + return 1 + fi + echo " ✅ Rendezvous service was not used (device skipped TO1)" + + # Owner received TO2 directly (msg/60) + find_in_log_or_fail "${owner_log}" 'msg/60' + echo " ✅ Owner received TO2 protocol messages (direct connection)" + + # Owner completed TO2 (msg/70) + find_in_log_or_fail "${owner_log}" 'msg/70' + echo " ✅ Owner completed TO2 protocol" + + echo " 🎉 RV bypass behavior verified successfully!" +} + +run_test() { + + echo "⭐ Creating directories" + create_directories + + echo "⭐ Cleaning up any old log files" + rm -f "${logs_dir}"/*.log + + echo "⭐ Generating service certificates" + generate_service_certs + + echo "⭐ Build and install 'go-fdo-client' binary" + install_client + + echo "⭐ Build and install 'go-fdo-server' binary" + install_server + + echo "⭐ Adding hostnames to /etc/hosts" + set_hostnames + + echo "⭐ Start Manufacturing and Owner services (NO Rendezvous with bypass!)" + start_service "${manufacturer_service_name}" + start_service "${owner_service_name}" + + echo "⭐ Wait for the services to be ready:" + wait_for_service_ready "${manufacturer_service_name}" + wait_for_service_ready "${owner_service_name}" + + echo "⭐ Setting Rendezvous Info with RV BYPASS flag" + set_or_update_rendezvous_info "${manufacturer_url}" "${owner_service_name}" "${owner_dns}" "${owner_port}" "http" "true" + + echo "⭐ Run Device Initialization" + run_device_initialization + + guid=$(get_device_guid) + echo "⭐ Device initialized with GUID: ${guid}" + + echo "⭐ Sending Ownership Voucher to the Owner" + send_manufacturer_ov_to_owner "${manufacturer_url}" "${guid}" "${owner_url}" + + echo "⭐ Setting or updating Owner Redirect Info (RVTO2Addr)" + set_or_update_owner_redirect_info "${owner_url}" "${owner_service_name}" "${owner_dns}" "${owner_port}" + + echo "⭐ SKIPPING TO0 (no need - device will skip TO1 via RV bypass)" + + echo "⭐ Running FIDO Device Onboard with RV bypass" + run_fido_device_onboard --debug + + echo "⭐ Saving container logs (no-op for binary tests)" + save_logs + + echo "⭐ Verifying RV bypass behavior in logs" + verify_rv_bypass_behavior "${guid}" + + echo "⭐ Success! ✅" +} + +# Allow running directly +[[ "${BASH_SOURCE[0]}" != "$0" ]] || { run_test; cleanup; } diff --git a/test/ci/utils.sh b/test/ci/utils.sh index 91fd3f5c..f14a5919 100644 --- a/test/ci/utils.sh +++ b/test/ci/utils.sh @@ -395,7 +395,16 @@ stop_services() { } install_client() { - go install github.com/fido-device-onboard/go-fdo-client@latest + # Build from local clone with RV bypass support (pr20-rv-bypass-fixes branch) + # TODO: Change to @latest once RV bypass is merged to upstream main + # Tracking: https://github.com/fido-device-onboard/go-fdo-client/pull/38 + local client_dir="../go-fdo-client" + if [ -d "${client_dir}" ]; then + (cd "${client_dir}" && git checkout pr20-rv-bypass-fixes && go build -o "$(go env GOPATH)/bin/go-fdo-client") + else + echo "WARNING: Local go-fdo-client not found, falling back to @latest (RV bypass not supported)" >&2 + go install github.com/fido-device-onboard/go-fdo-client@latest + fi } uninstall_client() { @@ -446,16 +455,17 @@ set_or_update_rendezvous_info() { local rendezvous_dns=$3 local rendezvous_port=$4 local rendezvous_protocol=${5:-http} + local rv_bypass=${6:-false} local real_rendezvous_ip real_rendezvous_ip="$(get_real_ip "${rendezvous_service_name}")" log_info "Checking if 'RendezvousInfo' is configured on manufacturer side (${manufacturer_url})" if [ -z "$(get_rendezvous_info "${manufacturer_url}")" ]; then log_warn "'RendezvousInfo' not found, creating it" - set_rendezvous_info "${manufacturer_url}" "${rendezvous_dns}" "${real_rendezvous_ip}" "${rendezvous_port}" "${rendezvous_protocol}" + set_rendezvous_info "${manufacturer_url}" "${rendezvous_dns}" "${real_rendezvous_ip}" "${rendezvous_port}" "${rendezvous_protocol}" "${rv_bypass}" else log_info "'RendezvousInfo' found, updating it" - update_rendezvous_info "${manufacturer_url}" "${rendezvous_dns}" "${real_rendezvous_ip}" "${rendezvous_port}" "${rendezvous_protocol}" + update_rendezvous_info "${manufacturer_url}" "${rendezvous_dns}" "${real_rendezvous_ip}" "${rendezvous_port}" "${rendezvous_protocol}" "${rv_bypass}" fi echo } @@ -541,6 +551,12 @@ remove_files() { rm -vrf "${base_dir:?}"/* } +save_logs() { + # No-op in binary tests (logs already go to files) + # Overridden in container tests to save Docker logs to files + : +} + cleanup() { stop_services unset_hostnames diff --git a/test/container/test-rv-bypass.sh b/test/container/test-rv-bypass.sh new file mode 100755 index 00000000..63c3ae38 --- /dev/null +++ b/test/container/test-rv-bypass.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)/../ci/test-rv-bypass.sh" +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)/utils.sh" + +client_compose_file="deployments/compose/client/fdo-client-rv-bypass.yaml" +servers_compose_file="deployments/compose/server/fdo-rv-bypass-servers.yaml" + +# Allow running directly +[[ "${BASH_SOURCE[0]}" != "$0" ]] || { run_test; cleanup; } diff --git a/test/container/utils.sh b/test/container/utils.sh index c7e3fe68..9179ffaa 100644 --- a/test/container/utils.sh +++ b/test/container/utils.sh @@ -25,7 +25,7 @@ get_real_ip() { set_hostnames() { for service_name in $(docker compose -f ${servers_compose_file} config --services | grep -v db); do - service_dns="$(docker inspect "${service_name}" --format='{{index .NetworkSettings.Networks.fdo.Aliases 0}}')" + service_dns="$(docker inspect "${service_name}" --format='{{index .NetworkSettings.Networks.fdo.Aliases 0}}' 2>/dev/null || true)" [ -n "${service_dns}" ] || service_dns="${service_name}" set_hostname "$service_dns" "127.0.0.1" done @@ -34,7 +34,7 @@ set_hostnames() { unset_hostnames() { log_info "Removing hostnames from '/etc/hosts'" for service_name in $(docker compose -f ${servers_compose_file} config --services "{{.Name}}" | grep -v db); do - service_dns="$(docker inspect "${service_name}" --format='{{index .NetworkSettings.Networks.fdo.Aliases 0}}')" + service_dns="$(docker inspect "${service_name}" --format='{{index .NetworkSettings.Networks.fdo.Aliases 0}}' 2>/dev/null || true)" [ -n "${service_dns}" ] || service_dns="${service_name}" unset_hostname "$service_dns" "127.0.0.1" done