|
| 1 | +#! /usr/bin/env bash |
| 2 | +# Cross-version API compatibility test: Verify that the V1 and V2 management |
| 3 | +# APIs are interoperable. Configures the server using one API version and |
| 4 | +# verifies the data is correctly accessible via the other API version. |
| 5 | +# Then runs full onboarding to confirm the server works regardless of |
| 6 | +# which API version was used for setup. |
| 7 | + |
| 8 | +set -euo pipefail |
| 9 | + |
| 10 | +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)/utils.sh" |
| 11 | + |
| 12 | +# We do NOT source mgmt-api-v1.sh or mgmt-api-v2.sh here because we need |
| 13 | +# both API versions simultaneously. Instead we define prefixed wrappers that |
| 14 | +# call the specific API endpoints directly. |
| 15 | + |
| 16 | +# ── V1 API helpers ────────────────────────────────────────────────────────── |
| 17 | + |
| 18 | +v1_set_rendezvous_info() { |
| 19 | + local manufacturer_url=$1 |
| 20 | + local rendezvous_info_json=$2 |
| 21 | + curl --fail --verbose --silent --insecure \ |
| 22 | + --request POST \ |
| 23 | + --header 'Content-Type: application/json' \ |
| 24 | + --data-raw "${rendezvous_info_json}" \ |
| 25 | + "${manufacturer_url}/api/v1/rvinfo" |
| 26 | +} |
| 27 | + |
| 28 | +v1_get_rendezvous_info() { |
| 29 | + local manufacturer_url=$1 |
| 30 | + curl --fail --verbose --silent --insecure \ |
| 31 | + --request GET \ |
| 32 | + --header 'Content-Type: text/plain' \ |
| 33 | + "${manufacturer_url}/api/v1/rvinfo" |
| 34 | +} |
| 35 | + |
| 36 | +v1_set_rvto2addr() { |
| 37 | + local owner_url=$1 |
| 38 | + local ip=$2 |
| 39 | + local dns=$3 |
| 40 | + local port=$4 |
| 41 | + local protocol=$5 |
| 42 | + local rvto2addr="[{\"ip\": \"${ip}\", \"dns\": \"${dns}\", \"port\": \"${port}\", \"protocol\": \"${protocol}\"}]" |
| 43 | + curl --fail --verbose --silent --insecure \ |
| 44 | + --request POST \ |
| 45 | + --header 'Content-Type: text/plain' \ |
| 46 | + --data-raw "${rvto2addr}" \ |
| 47 | + "${owner_url}/api/v1/owner/redirect" |
| 48 | +} |
| 49 | + |
| 50 | +v1_get_rvto2addr() { |
| 51 | + local owner_url=$1 |
| 52 | + curl --fail --verbose --silent --insecure \ |
| 53 | + --header 'Content-Type: text/plain' \ |
| 54 | + "${owner_url}/api/v1/owner/redirect" |
| 55 | +} |
| 56 | + |
| 57 | +v1_get_ov_from_manufacturer() { |
| 58 | + local manufacturer_url=$1 |
| 59 | + local guid=$2 |
| 60 | + local output=$3 |
| 61 | + curl --fail --verbose --silent --insecure \ |
| 62 | + "${manufacturer_url}/api/v1/vouchers/${guid}" -o "${output}" |
| 63 | +} |
| 64 | + |
| 65 | +v1_send_ov_to_owner() { |
| 66 | + local owner_url=$1 |
| 67 | + local output=$2 |
| 68 | + curl --fail --verbose --silent --insecure \ |
| 69 | + --request POST \ |
| 70 | + --data-binary "@${output}" \ |
| 71 | + "${owner_url}/api/v1/owner/vouchers" |
| 72 | +} |
| 73 | + |
| 74 | +v1_add_device_ca_cert() { |
| 75 | + local url=$1 |
| 76 | + local crt=$2 |
| 77 | + curl --fail --verbose --silent --insecure \ |
| 78 | + --request POST \ |
| 79 | + --header 'Content-Type: application/x-pem-file' \ |
| 80 | + --data-binary @"${crt}" \ |
| 81 | + "${url}/api/v1/device-ca" |
| 82 | +} |
| 83 | + |
| 84 | +# ── V2 API helpers ────────────────────────────────────────────────────────── |
| 85 | + |
| 86 | +v2_set_rendezvous_info() { |
| 87 | + local manufacturer_url=$1 |
| 88 | + local rendezvous_info_json=$2 |
| 89 | + curl --fail --verbose --silent --insecure \ |
| 90 | + --request PUT \ |
| 91 | + --header 'Content-Type: application/json' \ |
| 92 | + --data-raw "${rendezvous_info_json}" \ |
| 93 | + "${manufacturer_url}/api/v2/rvinfo" |
| 94 | +} |
| 95 | + |
| 96 | +v2_get_rendezvous_info() { |
| 97 | + local manufacturer_url=$1 |
| 98 | + curl --fail --verbose --silent --insecure \ |
| 99 | + --header 'Accept: application/json' \ |
| 100 | + --request GET \ |
| 101 | + "${manufacturer_url}/api/v2/rvinfo" |
| 102 | +} |
| 103 | + |
| 104 | +v2_set_rvto2addr() { |
| 105 | + local owner_url=$1 |
| 106 | + local ip=$2 |
| 107 | + local dns=$3 |
| 108 | + local port=$4 |
| 109 | + local protocol=$5 |
| 110 | + local rvto2addr="[{\"ip\": \"${ip}\", \"dns\": \"${dns}\", \"port\": ${port}, \"protocol\": \"${protocol}\"}]" |
| 111 | + curl --fail --verbose --silent --insecure \ |
| 112 | + --request PUT \ |
| 113 | + --header 'Accept: application/json' \ |
| 114 | + --header 'Content-Type: application/json' \ |
| 115 | + --data-raw "${rvto2addr}" \ |
| 116 | + "${owner_url}/api/v2/rvto2addr" |
| 117 | +} |
| 118 | + |
| 119 | +v2_get_rvto2addr() { |
| 120 | + local owner_url=$1 |
| 121 | + curl --fail --verbose --silent --insecure \ |
| 122 | + --header 'Accept: application/json' \ |
| 123 | + "${owner_url}/api/v2/rvto2addr" |
| 124 | +} |
| 125 | + |
| 126 | +v2_get_ov_from_manufacturer() { |
| 127 | + local manufacturer_url=$1 |
| 128 | + local guid=$2 |
| 129 | + local output=$3 |
| 130 | + curl --fail --verbose --silent --insecure \ |
| 131 | + --header 'Accept: application/x-pem-file' \ |
| 132 | + "${manufacturer_url}/api/v2/vouchers/${guid}" -o "${output}" |
| 133 | +} |
| 134 | + |
| 135 | +v2_send_ov_to_owner() { |
| 136 | + local owner_url=$1 |
| 137 | + local output=$2 |
| 138 | + curl --fail --verbose --silent --insecure \ |
| 139 | + --request POST \ |
| 140 | + --header 'Content-Type: application/x-pem-file' \ |
| 141 | + --data-binary "@${output}" \ |
| 142 | + "${owner_url}/api/v2/vouchers" |
| 143 | +} |
| 144 | + |
| 145 | +v2_add_device_ca_cert() { |
| 146 | + local url=$1 |
| 147 | + local crt=$2 |
| 148 | + curl --fail --verbose --silent --insecure \ |
| 149 | + --header 'Content-Type: application/x-pem-file' \ |
| 150 | + --data-binary @"${crt}" \ |
| 151 | + "${url}/api/v2/device-ca" |
| 152 | +} |
| 153 | + |
| 154 | +# ── RV info in both API formats ──────────────────────────────────────────── |
| 155 | + |
| 156 | +rv_info_v1="[{\"dns\": \"${rendezvous_dns}\", \"device_port\": \"${rendezvous_port}\", \"protocol\": \"${rendezvous_protocol}\", \"ip\": \"${rendezvous_ip}\", \"owner_port\": \"${rendezvous_port}\"}]" |
| 157 | +rv_info_v2="[[{\"dns\": \"${rendezvous_dns}\"}, {\"device_port\": ${rendezvous_port}}, {\"protocol\": \"${rendezvous_protocol}\"}, {\"ip\": \"${rendezvous_ip}\"}, {\"owner_port\": ${rendezvous_port}}]]" |
| 158 | + |
| 159 | +# ── Test helpers ──────────────────────────────────────────────────────────── |
| 160 | + |
| 161 | +send_manufacturer_ov_to_owner_v1() { |
| 162 | + local manufacturer_url=$1 |
| 163 | + local guid=$2 |
| 164 | + local owner_url=$3 |
| 165 | + local ov_dir="${base_dir}/ovs" |
| 166 | + mkdir -p "${ov_dir}" |
| 167 | + local ov_file="${ov_dir}/${guid}.ov" |
| 168 | + v1_get_ov_from_manufacturer "${manufacturer_url}" "${guid}" "${ov_file}" |
| 169 | + v1_send_ov_to_owner "${owner_url}" "${ov_file}" |
| 170 | + log_info "Waiting '${to0_wait_seconds}' seconds for TO0" |
| 171 | + sleep "${to0_wait_seconds}" |
| 172 | +} |
| 173 | + |
| 174 | +send_manufacturer_ov_to_owner_v2() { |
| 175 | + local manufacturer_url=$1 |
| 176 | + local guid=$2 |
| 177 | + local owner_url=$3 |
| 178 | + local ov_dir="${base_dir}/ovs" |
| 179 | + mkdir -p "${ov_dir}" |
| 180 | + local ov_file="${ov_dir}/${guid}.ov" |
| 181 | + v2_get_ov_from_manufacturer "${manufacturer_url}" "${guid}" "${ov_file}" |
| 182 | + v2_send_ov_to_owner "${owner_url}" "${ov_file}" |
| 183 | + log_info "Waiting '${to0_wait_seconds}' seconds for TO0" |
| 184 | + sleep "${to0_wait_seconds}" |
| 185 | +} |
| 186 | + |
| 187 | +run_test() { |
| 188 | + |
| 189 | + log_info "Setting the error trap handler" |
| 190 | + trap on_failure EXIT |
| 191 | + |
| 192 | + log_info "Environment variables" |
| 193 | + show_env |
| 194 | + |
| 195 | + log_info "Creating directories" |
| 196 | + create_directories |
| 197 | + |
| 198 | + log_info "Generating service certificates" |
| 199 | + generate_service_certs |
| 200 | + |
| 201 | + log_info "Build and install 'go-fdo-client' binary" |
| 202 | + install_client |
| 203 | + |
| 204 | + log_info "Build and install 'go-fdo-server' binary" |
| 205 | + install_server |
| 206 | + |
| 207 | + log_info "Configuring services" |
| 208 | + configure_services |
| 209 | + |
| 210 | + log_info "Configure DNS and start services" |
| 211 | + start_services |
| 212 | + |
| 213 | + log_info "Wait for the services to be ready:" |
| 214 | + wait_for_services_ready |
| 215 | + |
| 216 | + # ── Phase 1: Configure with V1 API, verify with V2 API ────────────────── |
| 217 | + |
| 218 | + log_info "=== Phase 1: Configure via V1 API, verify via V2 API ===" |
| 219 | + |
| 220 | + log_info "Setting RendezvousInfo via V1 API" |
| 221 | + v1_set_rendezvous_info "${manufacturer_url}" "${rv_info_v1}" |
| 222 | + |
| 223 | + log_info "Reading back RendezvousInfo via V2 API" |
| 224 | + v2_rv_result=$(v2_get_rendezvous_info "${manufacturer_url}") |
| 225 | + log_info "V2 API returned: ${v2_rv_result}" |
| 226 | + [ -n "${v2_rv_result}" ] || log_error "V2 API returned empty RendezvousInfo" |
| 227 | + [ "${v2_rv_result}" != "null" ] || log_error "V2 API returned null RendezvousInfo" |
| 228 | + log_success "V1->V2 RendezvousInfo cross-read succeeded" |
| 229 | + |
| 230 | + log_info "Adding Device CA certificate via V1 API to rendezvous" |
| 231 | + v1_add_device_ca_cert "${rendezvous_url}" "${device_ca_crt}" | jq -r -M . |
| 232 | + |
| 233 | + log_info "Setting RVTO2Addr via V1 API" |
| 234 | + real_owner_ip="$(get_real_ip "${owner_service_name}")" |
| 235 | + v1_set_rvto2addr "${owner_url}" "${real_owner_ip}" "${owner_dns}" "${owner_port}" "${owner_protocol}" |
| 236 | + |
| 237 | + log_info "Reading back RVTO2Addr via V2 API" |
| 238 | + v2_redirect=$(v2_get_rvto2addr "${owner_url}") |
| 239 | + log_info "V2 API returned: ${v2_redirect}" |
| 240 | + [ -n "${v2_redirect}" ] || log_error "V2 API returned empty RVTO2Addr" |
| 241 | + [ "${v2_redirect}" != "null" ] || log_error "V2 API returned null RVTO2Addr" |
| 242 | + log_success "V1->V2 RVTO2Addr cross-read succeeded" |
| 243 | + |
| 244 | + log_info "Run Device Initialization (Phase 1)" |
| 245 | + guid1=$(run_device_initialization) |
| 246 | + log_info "Device initialized with GUID: ${guid1}" |
| 247 | + |
| 248 | + log_info "Sending Ownership Voucher to Owner via V1 API" |
| 249 | + send_manufacturer_ov_to_owner_v1 "${manufacturer_url}" "${guid1}" "${owner_url}" |
| 250 | + |
| 251 | + log_info "Running FIDO Device Onboard (V1 setup)" |
| 252 | + run_fido_device_onboard "${guid1}" --debug || log_error "Onboarding after V1 setup failed!" |
| 253 | + log_success "Phase 1 (V1 setup) onboarding succeeded" |
| 254 | + |
| 255 | + # ── Phase 2: Configure with V2 API, verify with V1 API ────────────────── |
| 256 | + |
| 257 | + log_info "=== Phase 2: Configure via V2 API, verify via V1 API ===" |
| 258 | + |
| 259 | + log_info "Setting RendezvousInfo via V2 API" |
| 260 | + v2_set_rendezvous_info "${manufacturer_url}" "${rv_info_v2}" |
| 261 | + |
| 262 | + log_info "Reading back RendezvousInfo via V1 API" |
| 263 | + v1_rv_result=$(v1_get_rendezvous_info "${manufacturer_url}") |
| 264 | + log_info "V1 API returned: ${v1_rv_result}" |
| 265 | + [ -n "${v1_rv_result}" ] || log_error "V1 API returned empty RendezvousInfo" |
| 266 | + [ "${v1_rv_result}" != "null" ] || log_error "V1 API returned null RendezvousInfo" |
| 267 | + log_success "V2->V1 RendezvousInfo cross-read succeeded" |
| 268 | + |
| 269 | + log_info "Setting RVTO2Addr via V2 API" |
| 270 | + v2_set_rvto2addr "${owner_url}" "${real_owner_ip}" "${owner_dns}" "${owner_port}" "${owner_protocol}" |
| 271 | + |
| 272 | + log_info "Reading back RVTO2Addr via V1 API" |
| 273 | + v1_redirect=$(v1_get_rvto2addr "${owner_url}") |
| 274 | + log_info "V1 API returned: ${v1_redirect}" |
| 275 | + [ -n "${v1_redirect}" ] || log_error "V1 API returned empty RVTO2Addr" |
| 276 | + [ "${v1_redirect}" != "null" ] || log_error "V1 API returned null RVTO2Addr" |
| 277 | + log_success "V2->V1 RVTO2Addr cross-read succeeded" |
| 278 | + |
| 279 | + log_info "Run Device Initialization (Phase 2)" |
| 280 | + guid2=$(run_device_initialization) |
| 281 | + log_info "Device initialized with GUID: ${guid2}" |
| 282 | + |
| 283 | + log_info "Sending Ownership Voucher to Owner via V2 API" |
| 284 | + send_manufacturer_ov_to_owner_v2 "${manufacturer_url}" "${guid2}" "${owner_url}" |
| 285 | + |
| 286 | + log_info "Running FIDO Device Onboard (V2 setup)" |
| 287 | + run_fido_device_onboard "${guid2}" --debug || log_error "Onboarding after V2 setup failed!" |
| 288 | + log_success "Phase 2 (V2 setup) onboarding succeeded" |
| 289 | + |
| 290 | + # ── Phase 3: Mixed V1/V2 setup ────────────────────────────────────────── |
| 291 | + |
| 292 | + log_info "=== Phase 3: Mixed API setup (V2 rvinfo, V1 voucher) ===" |
| 293 | + |
| 294 | + log_info "Setting RendezvousInfo via V2 API" |
| 295 | + v2_set_rendezvous_info "${manufacturer_url}" "${rv_info_v2}" |
| 296 | + |
| 297 | + log_info "Run Device Initialization (Phase 3)" |
| 298 | + guid3=$(run_device_initialization) |
| 299 | + log_info "Device initialized with GUID: ${guid3}" |
| 300 | + |
| 301 | + log_info "Sending Ownership Voucher to Owner via V1 API (mixed)" |
| 302 | + send_manufacturer_ov_to_owner_v1 "${manufacturer_url}" "${guid3}" "${owner_url}" |
| 303 | + |
| 304 | + log_info "Running FIDO Device Onboard (mixed V1/V2 setup)" |
| 305 | + run_fido_device_onboard "${guid3}" --debug || log_error "Onboarding after mixed V1/V2 setup failed!" |
| 306 | + log_success "Phase 3 (mixed V1/V2) onboarding succeeded" |
| 307 | + |
| 308 | + log_info "Unsetting the error trap handler" |
| 309 | + trap - EXIT |
| 310 | + test_pass |
| 311 | +} |
| 312 | + |
| 313 | +# Allow running directly |
| 314 | +[[ "${BASH_SOURCE[0]}" != "$0" ]] || { |
| 315 | + run_test |
| 316 | + cleanup |
| 317 | +} |
0 commit comments