Webex legt een gele banner over de bovenkant van het venster. Offline - No internet connection. De telefoondiensten staan op losgekoppeld, niets synchroniseert, en je komt de vergadering niet in die negentig seconden geleden begon.
Dat is geen ongemak als het ding je werktelefoon is. Via Webex komen mijn gesprekken binnen, en mijn VoIP-lijn loopt erdoorheen, dus een client die zich niet aanmeldt is een bureautelefoon die niet overgaat, een vergadering waar ik niet ben, en een collega die de voicemail krijgt. Het hield me van mijn werk. Niet trager, niet beperkt. Gestopt.
En niets ervan was aan mij om te veroorzaken of te voorkomen. Een werkdag ging scheef door kwaliteitscontrole die niet is toegepast, bij een leverancier die betaald wordt, op een product dat met een ondersteuningscontract wordt verkocht, tegen een platform dat de eigen eisenpagina ondersteund noemt. Ik heb niets verkeerd ingesteld. Ik installeerde het pakket van de leverancier, uit de repository van de leverancier, op een platform dat de leverancier noemt, en het kon geen TLS-verbinding openen. Daarna heb ik een avond van mijn eigen tijd besteed aan uitzoeken waarom, en dat is tijd die de mensen die het pakket ondertekenden niet hebben besteed.
De machine is niet offline. De browser ernaast laadt pagina’s. Je mail komt binnen, je terminal haalt op van een remote, en als je het besturingssysteem vraagt of het het internet bereikt, zegt het ja. Webex is het daar zelf mee eens, in zijn eigen log, elf seconden voordat het je het tegendeel vertelt.
Wat er werkelijk is gebeurd, is dat de kopie van OpenSSL die Cisco in Webex meelevert geen enkele certificaatautoriteit kan vinden, omdat hij is gecompileerd om ze te zoeken in /workspace/.conan2/p/b/cisco8ee8b59cf93de/p/ssl. Dat is een map op een Cisco-buildcontainer. Die heeft nooit op jouw computer bestaan en zal dat nooit doen. Elke TLS-verbinding van de applicatie sneuvelt bij de certificaatcontrole, het aanmeldtoken kan niet worden ververst, een timer van vijftien seconden loopt af, en de interface grijpt naar de enige verklaring waarvoor hij een tekst heeft.
De banner is dus op een heel specifieke en weinig behulpzame manier fout. Hij wijst jouw netwerk aan. De fout is een pad in hun build.
Dit is versie 46.8.0.35631, op Fedora 44, kernel 7.2.4. Het is een ondersteund platform. Cisco publiceert Linux-systeemeisen en levert een ondertekende .rpm, webex-46.8.0.35631-1.x86_641. Wat volgt is hoe je het in een minuut of tien bewijst, waarom geen van de voor de hand liggende oplossingen werkt, de ene die dat wel doet, en dan het deel dat meer telt dan dat alles: dit is geen subtiele bug. Dit is een build die nooit iemand heeft gedraaid op een machine die hem niet had gebouwd.
Wat de banner je eigenlijk vertelt
Webex stuurt zijn verbindingsweergave aan met een samengestelde toestandsmachine, zeven deelmachines met elk eigen timers. Bij het starten initialiseren ze zo:
ConnectivityStateMachine::ConnectivityBanner - Initializing with state: Connected
ConnectivityStateMachine::Network - Initializing with state: NoNetwork
ConnectivityStateMachine::Services - Initializing with state: Connected
ConnectivityStateMachine::Mercury - Initializing with state: Disconnected
ConnectivityStateMachine::Authentication - Initializing with state: UserNotAuthenticated
ConnectivityStateMachine::Syncing - Initializing with state: Synced
ConnectivityStateMachine::Survivability - Initializing with state: SurvivabilityHide
Veertig milliseconden later meldt het besturingssysteem terug, en de applicatie schrijft het op:
NetworkManagerPowerNetworkWatcher.cpp:93 onConnectivityCheckSuccess::
The host is connected to a network, that appears to be able to reach the full Internet.
Die regel staat in hetzelfde bestand, in dezelfde sessie, als de banner die beweert dat er geen internetverbinding is. De applicatie wist het. Ze had het antwoord om 08:25:12.131 in handen en liet om 08:25:27.132 het tegendeel zien.
Tussen die twee momenten dit:
| Tijd | Wat er gebeurde |
|---|---|
| 08:25:12.131 | Besturingssysteem bevestigt volledige bereikbaarheid van het internet |
| 08:25:12.218 | Proxydetectie: geen ingesteld, directe verbinding |
| 08:25:12.241 | Eerste HTTPS-aanvraag faalt, errorCode: 167772294 Error in SSL handshake |
| 08:25:12.569 | Verversen van het CloudApps-toegangstoken faalt, dezelfde code |
| 08:25:12.571 | Verversen van het Kms-toegangstoken faalt, dezelfde code |
| 08:25:15.684 | Nieuwe poging, beide falen |
| 08:25:21.745 | Nieuwe poging, beide falen |
| 08:25:27.132 | Timer van vijftien seconden loopt af, banner schakelt naar NoInternet |
| 08:26:12.091 | Timers van zestig seconden lopen af, diensten zakken naar DisconnectedShortTerm |
De deelmachine voor authenticatie verlaat UserNotAuthenticated nooit, dus leidt Services zichzelf af als losgekoppeld, dus gaat de banner aan. Elke stap daarvan is correct gedrag gegeven de invoer. De invoer is fout, en de invoer is een getal: 167772294, bij elke mislukte aanvraag, van de eerste tot de laatste.
Dat getal is het hele stuk. Hou het vast.
Drie paden, waarvan er geen één bestaat
Webex gebruikt niet de OpenSSL van het systeem. Het brengt zijn eigen mee, samen met zijn eigen libcurl, en die libcurl linkt tegen de meegeleverde en niet tegen die van jou:
$ ldd /opt/Webex/bin/libcurl.so | grep -E 'ssl|crypto'
libssl.so.3 => /opt/Webex/bin/../lib/libssl.so.3
libcrypto.so.3 => /opt/Webex/bin/../lib/libcrypto.so.3
Tot zover prima. Een eigen TLS-bibliotheek meeleveren is een verdedigbare keuze en genoeg leveranciers doen het. Waar het om gaat is wat erin gebakken is, en OpenSSL vertelt het je als je het vraagt:
$ strings /opt/Webex/lib/libcrypto.so.3 | grep -E 'OPENSSLDIR|ENGINESDIR|MODULESDIR'
OPENSSLDIR: "/workspace/.conan2/p/b/cisco8ee8b59cf93de/p/ssl"
ENGINESDIR: "/workspace/.conan2/p/b/cisco8ee8b59cf93de/p/lib/engines-3"
MODULESDIR: "/workspace/.conan2/p/b/cisco8ee8b59cf93de/p/lib/ossl-modules"
Drie stuks. Niet één verschoven instelling. Het complete installatievoorvoegsel, meegesleept van de machine die het compileerde, in een bibliotheek die de klant bereikt als ondertekend pakket op een ondersteund platform.
OPENSSLDIR wordt één keer gezet, tijdens het configureren, met --openssldir, en OpenSSL’s eigen bouwdocumentatie is duidelijk waar het voor is: “Directory for OpenSSL configuration files, and also the default certificate and key store.”2 Alles wat onder vertrouwen hangt, hangt eraan. Het standaard CA-bestand is cert.pem erin en de standaard CA-map is certs erin3. /workspace is een Conan-buildcache. Conan is de C++-pakketbeheerder waarmee Cisco bouwt, en hij bewaart elk pakket onder een hash van zijn bouwinvoer4. De hash cisco8ee8b59cf93de is een feit over een container die waarschijnlijk minuten na afloop van de build is verwijderd.
Vraag de bibliotheek wat ze is, en het is niet eens standaard-OpenSSL:
VERSION CiscoSSL 3.5.5.8.5.4 27 Jan 2026
BUILT_ON built on: Wed Feb 25 06:29:18 2026 UTC
PLATFORM platform: conan-Release-Linux-x86_64-gcc-13
DIR OPENSSLDIR: "/workspace/.conan2/p/b/cisco8ee8b59cf93de/p/ssl"
Een onderhouden eigen fork, met een eigen versieschema, gebouwd in februari, uitgeleverd in augustus, en nog steeds met de werkmap erin waarin hij is gemaakt.
Neem strings niet als antwoord. Vraag het de bibliotheek
strings vindt tekst in een bestand. Het bewijst niet dat de bibliotheek die gebruikt. Laad dus de meegeleverde bibliotheek en vraag het haar rechtstreeks, wat ongeveer twaalf regels Python kost en geen root:
import ctypes
c = ctypes.CDLL("/opt/Webex/lib/libcrypto.so.3")
for f in ("X509_get_default_cert_file", "X509_get_default_cert_dir",
"X509_get_default_cert_file_env", "X509_get_default_cert_dir_env"):
getattr(c, f).restype = ctypes.c_char_p
print(f"{f:34} {getattr(c, f)().decode()}")
X509_get_default_cert_file /workspace/.conan2/p/b/cisco8ee8b59cf93de/p/ssl/cert.pem
X509_get_default_cert_dir /workspace/.conan2/p/b/cisco8ee8b59cf93de/p/ssl/certs
X509_get_default_cert_file_env SSL_CERT_FILE
X509_get_default_cert_dir_env SSL_CERT_DIR
Daar staat het uit de mond van de bibliotheek zelf. Als iets in Webex deze kopie van OpenSSL om de standaard vertrouwensopslag vraagt, krijgt het een bestand en een map aangereikt die niet bestaan. Dat is wat SSL_CTX_set_default_verify_paths doet, en dat is wat vrijwel elke client doet tenzij hem iets anders is verteld.
De laatste twee regels zijn het noteren waard, want dat is de nooduitgang: de bibliotheek laat SSL_CERT_FILE en SSL_CERT_DIR beide overschrijven5. Onthou dat ook. Het wordt belangrijk, en niet op de manier die je zou verwachten.
De exacte foutcode reproduceren
Bewijs door inspectie is geen bewijs. Neem de meegeleverde libssl.so.3 en libcrypto.so.3, doe een echte handshake naar een echte host met niets dan de standaardwaarden van de bibliotheek, en kijk wat er terugkomt.
De interessante run is die waarin de standaardwaarden nergens op wijzen. SSL_CERT_FILE en SSL_CERT_DIR overschrijven precies de twee waarden die een ontbrekende OPENSSLDIR in de lucht laat hangen, dus ze op een pad richten dat niet bestaat reproduceert de uitgeleverde toestand exact:
$ SSL_CERT_FILE=/nonexistent/cert.pem SSL_CERT_DIR=/nonexistent/certs python3 tls.py
set_default_verify_paths -> 1
set_fd -> 1
SNI -> 1
SSL_connect -> -1 SSL_get_error -> 1
verify result 20: unable to get local issuer certificate
err: 0xa000086 error:0A000086:SSL routines::certificate verify failed
0x0A000086 is in decimalen 167772294.
Dat is het getal in elke mislukte regel van het Webex-log, en het kwam niet van Webex. Het kwam uit Cisco’s eigen TLS-bibliotheek, draaiend buiten hun applicatie, falend om precies één reden: ze had geen vertrouwensankers om de keten tegen te controleren. Dezelfde bibliotheek, dezelfde fout, geen applicatie ertussen.
Richt dezelfde twee variabelen op de echte Fedora-bundel en hetzelfde codepad loopt door:
$ SSL_CERT_FILE=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem python3 tls.py
SSL_connect -> 1 SSL_get_error -> 0
verify result 0: ok
Aan het netwerk veranderde tussen die twee runs niets. Aan één bestandspad wel.
Waarom niets je waarschuwde
Twee dingen spannen samen om dit stil te houden, en maar één ervan is Cisco’s schuld.
| Wat nooit een woord zegt | Wiens ontwerp | Waarom het stil blijft |
|---|---|---|
| Een vertrouwensopslag laden die er niet is | Dat van OpenSSL, met opzet | SSL_CTX_set_default_verify_paths geeft 1 terug of de paden nu echt zijn of niet. “A missing default location is still treated as a success”3 |
| Niets hebben om op terug te vallen | Dat van Cisco, en juist | Certificaten gecontroleerd, zelfondertekende geweigerd, SSL-herhaling uit, dus er bestaat geen afgeknepen modus die een fout kan maskeren |
Het eerste is redelijk. Een programma dat zijn eigen ankers apart meelevert zou zich daar niet om hoeven bekommeren, dus de aanroep slaagt, de opslag is leeg, en nergens in de stapel zegt iets ik heb nul certificaatautoriteiten geladen. Het eerste dat het merkt is een controlefout een halve seconde later. Ik heb die aanroep tegen de meegeleverde bibliotheek gedraaid met de paden op /nonexistent en hij gaf 1 terug. Het staat in de uitvoer hierboven.
Het tweede is beleid dat van de dienst komt, en het log legt het vast:
Wdm.cpp:1162 parseDeviceJson: Adding policy << allowSelfSignedCertificate with value: false
NetworkManager.cpp:1738 onConfigReady: ...httpRequestSSLRetryEnabled: 0
HttpRequestManager.cpp:1883 rawHttpRequest: {"validateCertificates":"true","useClientCertificate":"false"}
Die drie vlaggen zijn de enige reden dat deze fout een storing is en niet iets veel ergers, en daar is het de moeite waard bij stil te staan in plaats van eroverheen te haasten.
Werk het uit. De meegeleverde bibliotheek laadt nul vertrouwensankers, en niets onder de beleidslaag zou dat ooit hebben gemerkt, want de fout is helemaal naar beneden toe met opzet stil. Wat er een banner van maakte waren drie vlaggen. Zet er één om zoals genoeg clients hem uitleveren en dezelfde build faalt helemaal niet. Hij verbindt, met alles dat welk certificaat dan ook ophoudt, want hij heeft niets om er een tegen te controleren.
Hoe het aan de oppervlakte komt, dat is het probleem. De gebruiker krijgt “Offline - No internet connection”. Het log krijgt “Error in SSL handshake” en een decimaal getal. Het woord certificaat komt nergens voor waar een gebruiker of een eerstelijnsmedewerker ooit zal kijken, en het enige diagnostische kruimeltje is een getal dat je eerst naar hex moet omrekenen voordat het iets betekent.
De oplossingen die niet werkten
De twee voor de hand liggende falen allebei, en de redenen verschillen en zijn allebei het weten waard.
De meegeleverde openssl.cnf aanpassen
Webex levert een configuratiebestand mee op /opt/Webex/lib/openssl.cnf, en zoals het wordt uitgeleverd activeert het één provider:
[provider_sect]
fips = fips_sect
Alleen FIPS. Niet de default-provider, waar de gewone TLS-algoritmen wonen6. Die terugzetten is een aanpassing van één regel en het verandert helemaal niets, want de meegeleverde OpenSSL leest dat bestand nooit. Hij zoekt openssl.cnf binnen OPENSSLDIR7, en OPENSSLDIR is het pad dat niet bestaat. Het bestand ligt gezaghebbend in de installatiemap. Niets leest het.
Het is een tweede defect dat zich achter het eerste verbergt. Zelfs als het padprobleem morgen werd opgelost door OPENSSLDIR op /opt/Webex/lib te richten, zou deze configuratie dan laden en alleen FIPS activeren. En de FIPS-module zelf wordt geladen uit MODULESDIR, het derde dode pad uit dezelfde boom, dus de fips.so die in /opt/Webex/lib wordt meegeleverd is ook niet te vinden. Drie paden, één verkeerd voorvoegsel, en elk ervan zo kapot dat het het volgende verbergt.
De omgevingsvariabelen zetten
De bibliotheek eerbiedigt SSL_CERT_FILE en SSL_CERT_DIR. Dat heb ik hierboven bewezen. De geslaagde run staat er gewoon. De voor de hand liggende zet is dus om ze in de bureaubladvermelding te zetten, en dat is geprobeerd:
Exec=env OPENSSL_CONF=/opt/Webex/lib/openssl.cnf \
SSL_CERT_FILE=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem \
SSL_CERT_DIR=/etc/pki/tls/certs/ /opt/Webex/bin/CiscoCollabHost %U
Geen verandering. En de reden is niet dat Webex de variabelen negeert. De reden is dat het proces ze nooit heeft gekregen:
$ tr '\0' '\n' < /proc/20293/environ | grep -E 'SSL|OPENSSL|CURL'
$ tr '\0' '\n' < /proc/20293/environ | wc -l
99
Negenennegentig variabelen in het draaiende Webex-proces, en geen van alle de drie die waren gezet. Want er zijn twee bureaubladvermeldingen met dezelfde naam op deze machine:
| Bestand | Wat de Exec-regel start | Geschreven door |
|---|---|---|
/usr/share/applications/webex.desktop | de env-regel met drie variabelen hierboven, volledig | de .rpm, daarna met de hand aangepast |
~/.local/share/applications/webex.desktop | /opt/Webex/bin/CiscoCollabHost %U, en helemaal geen omgeving | Webex’ eigen starter |
En de specificatie is niet dubbelzinnig over welke wint: “The base directory defined by $XDG_DATA_HOME is considered more important than any of the base directories defined by $XDG_DATA_DIRS.”8 $XDG_DATA_HOME is ~/.local/share. De gebruikerskopie overschaduwt de verpakte, elke keer, op elk bureaublad dat zich aan de specificatie houdt9.
Webex installeert dus een tweede kopie van zijn eigen starter in je thuismap, en die kopie is degene die je bureaublad start. Pas het verpakte bestand aan zoveel je wilt. Je bewerkt een document dat niets leest.
De oplossing die wel werkt
Als de bibliotheek op een pad staat, geef haar dat pad. Maak de map die ze is gecompileerd om te willen en vul hem met symlinks naar het echte werk:
#!/bin/bash
# Point the bundled CiscoSSL at the system trust store by building the
# directory it was compiled to look for. Tested: Fedora 44, Webex 46.8.0.35631.
set -euo pipefail
OPENSSLDIR=$(strings /opt/Webex/lib/libcrypto.so.3 \
| grep -oP '(?<=OPENSSLDIR: ")[^"]+')
[ -n "$OPENSSLDIR" ] || { echo "no OPENSSLDIR found in the shipped library"; exit 1; }
for p in /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem \
/etc/ssl/certs/ca-certificates.crt \
/etc/pki/tls/certs/ca-bundle.crt \
/etc/ssl/cert.pem; do
[ -f "$p" ] && { CA_BUNDLE="$p"; break; }
done
[ -n "${CA_BUNDLE:-}" ] || { echo "no system CA bundle found"; exit 1; }
echo "OPENSSLDIR: $OPENSSLDIR"
echo "CA bundle: $CA_BUNDLE"
sudo mkdir -p "$OPENSSLDIR"
sudo ln -sf "$CA_BUNDLE" "$OPENSSLDIR/cert.pem"
sudo ln -sf "$(dirname "$CA_BUNDLE")" "$OPENSSLDIR/certs"
sudo tee "$OPENSSLDIR/openssl.cnf" > /dev/null <<'CONF'
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
[provider_sect]
default = default_sect
fips = fips_sect
[default_sect]
activate = 1
CONF
echo "done. now restart Webex"
Start het opnieuw, en dezelfde opstartvolgorde levert de tegenovergestelde uitkomst op. Hetzelfde binaire bestand. Dezelfde logregels. Het verversen van het token, dat eerder binnen zestig milliseconden faalde, is nu na driehonderddertig klaar:
AuthTokenRequester.cpp:579 Managed to fetch a new Kms access token.
AuthTokenRequester.cpp:579 Managed to fetch a new CloudApps access token.
AuthTokenSupervisor.cpp:310 Auth tokens refreshed. Expires in [64799 secs].
AuthenticationManager.cpp:1860 onUserAuthenticated: User authenticated.
ConnectivityStateMachine::Authentication - UserNotAuthenticated -> UserAuthenticated
Na ongeveer 750 ms geauthenticeerd, dus de timer van vijftien seconden gaat nooit af en de banner verschijnt nooit. De telefoondiensten gaan van Disconnected naar Connecting, een toestand die de kapotte sessie in een minuut proberen nooit bereikte.
| Voor | Na | |
|---|---|---|
| Netwerkcontrole | slaagt | slaagt |
| Eerste HTTPS-aanvraag | 167772294 Error in SSL handshake | HTTP 200 |
| CloudApps-token | mislukt | opgehaald |
| Kms-token | mislukt | opgehaald |
| Authenticatie | blijft hangen op UserNotAuthenticated | UserAuthenticated |
| Banner na 15 s | “Offline - No internet connection” | geen |
| Telefoondiensten | nooit geprobeerd | verbindend |
| Tijd tot authenticatie | nooit | ~750 ms |
Let op wat de oplossing met je bestandssysteem doet, want dat hoort je te storen. Ze maakt op je machine een map /workspace op het hoogste niveau, een naam waar de Filesystem Hierarchy Standard geen plek voor heeft10, met daarin een Conan-cachepad en een buildhash die toebehoren aan een bedrijf waar je software van hebt gekocht. Dat is de vorm van de remedie die Cisco je heeft nagelaten: koppel iemand anders’ bouwomgeving aan de wortel van je eigen.
Ze gaat ook stuk. Op drie manieren:
| Wanneer het stukgaat | Waarom | Wat je doet |
|---|---|---|
| Een Webex-update | cisco8ee8b59cf93de is afgeleid van de bouwinvoer, dus een herbouwde afhankelijkheid betekent een nieuwe map | Het script opnieuw draaien; het leest het pad uit het nieuwe binaire bestand in plaats van het oude aan te nemen |
| Een verandering in de distributie | Het doel van de symlink is een beslissing van de distributie, geen standaard11 | Opnieuw richten; het script probeert vier bekende locaties |
| Een herinstallatie | /workspace wordt door niets geback-upt, verpakt of bezeten | Draai het opnieuw, elke keer, voor altijd |
Niets daarvan is onderhoud. Het is jij, die voor een stap in iemand anders’ buildpijplijn invalt, voor onbepaalde tijd, onbetaald. Een defect tijdens het draaien oplappen, op elke machine die je bezit, omdat de leverancier het niet één keer bij het bouwen wilde oplappen.
Ze kenden de regel en pasten hem op de helft van de build toe
Dit is wat het van een bugrapport in een argument verandert.
Lees de dynamische sectie van de uitgeleverde binaire bestanden:
$ readelf -d /opt/Webex/bin/libcurl.so | grep RUNPATH
0x1d (RUNPATH) Library runpath: [$ORIGIN:$ORIGIN/../lib]
$ readelf -d /opt/Webex/bin/CiscoCollabHost | grep RUNPATH
0x1d (RUNPATH) Library runpath: [$ORIGIN/../lib]
$ORIGIN wordt bij het laden uitgebreid naar de map waarin het object zelf staat12. Het is het juiste gereedschap voor een verplaatsbare bundel en ze hebben het correct gebruikt. Ze moesten wel: Webex levert dezelfde boom twee keer uit, één keer naar /opt/Webex en één keer naar ~/.local/share/WebexLauncher/46.8.0.35631_9e6196c9-…/, en een starter kiest bij het opstarten tussen beide. Twee voorvoegsels, één build, en de linker vindt zijn bibliotheken in allebei.
De mensen die dit pakket maakten begrepen het probleem dus precies. Absolute paden overleven het uitleveren niet. Voor de code hebben ze het opgelost.
Daarna lieten ze de datapaden staan als absolute teksten die de container noemen die ze compileerde. Dezelfde build. Dezelfde middag.
Dit kan dus niet worden weggeschreven onder ze wisten het niet. In $ORIGIN struikel je niet per ongeluk. Je grijpt ernaar omdat je hebt begrepen dat een absoluut pad dat in een uitgeleverd artefact is gebakken een defect is, en het goed genoeg hebt begrepen om het in de linker te gaan repareren. Daarna schrijft dezelfde build drie absolute paden in dezelfde bibliotheken, en levert ze uit.
De regel kennen en hem op de helft van de build toepassen is erger dan hem niet kennen. Niet weten is een opleidingsprobleem en opleiding heeft een oplossing. Dit is een pakket waar het juiste idee in zat, zwart op wit, in de ELF-header waar iedereen het kon lezen, en het ging toch kapot de deur uit. Wat je vertelt dat niets stroomafwaarts van de compiler naar het resultaat keek. Niets deed dat.
De container was er al
Waar /workspace vandaan komt is geen raadsel, en gokken hoef je ook niet. Het staat in de pakketheader:
$ rpm -qi webex | grep -E 'Build Host|Build Date|Vendor'
Build Date : Sat 08 Aug 2026 20:47:43 BST
Build Host : c964ea9239ae
Vendor : Cisco
c964ea9239ae is geen hostnaam die iemand heeft getypt. Het zijn twaalf hexadecimale tekens, en dat is wat een container als hostnaam meldt als niets er een instelt. Het pakket is dus gebouwd in een container, door een bedrijf dat duidelijk de images, het register en de orkestratie heeft om dat te doen, en drie losse artefacten op deze machine zeggen dat onafhankelijk van elkaar:
| Bewijs, afgelezen van het geïnstalleerde pakket | Waarde | Wat het bewijst |
|---|---|---|
Build Host in de RPM-header | c964ea9239ae | een container-ID, geen buildmachine |
OPENSSLDIR in libcrypto.so.3 | /workspace/.conan2/… | een pad dat alleen in die container bestaat |
PLATFORM in dezelfde bibliotheek | conan-Release-Linux-x86_64-gcc-13 | een Conan-toolchain in een container |
Requires in de RPM | glibc >= 2.28 | een bewust gekozen, zeer oude ABI-ondergrens |
Daarna hebben ze het ondertekend. De build was om 20:47:43 klaar en de handtekening is gedateerd op 21:02:07 diezelfde avond, sleutel-ID 9995e5bbb5ccde3c. Vijftien minuten. Er is dus een uitgiftepoort, iemand of iets bedient hem, en wat hij bevestigt is wie het pakket heeft gemaakt, niet of het pakket werkt. Een handtekening is een uitspraak over herkomst. Ze is nooit een uitspraak over geschiktheid geweest, en een proces dat het ene wel heeft en het andere niet heeft zijn prioriteiten in de verkeerde volgorde.
Want de ontbrekende stap is de goedkope. De container zit al in de pijplijn. Neem het artefact dat er net uit kwam, start een schone image van elke distributie waarvan je zegt hem te ondersteunen, installeer het, start het, en lees de eerste honderd regels van het log:
docker run --rm fedora:44 sh -c '
dnf -y install ./webex-46.8.0.35631-1.x86_64.rpm &&
timeout 25 /opt/Webex/bin/CiscoCollabHost &
sleep 20
grep -c "Error in SSL handshake" ~/.local/share/Webex/current_log.txt'
Niet nul, elke keer, op deze build. De installatie is 1,1 GB, dus reken op een minuut per doel bij een warme cache. Zes distributies is zes minuten van een machine die toch al draait, op hardware die Cisco toch al bezit, in een pijplijn die er toch al is. Het is niet gedaan. Geen enkele keer.
En dat is het antwoord op wat mensen nog steeds over Linux zeggen, dat meerdere distributies ondersteunen moeilijk is. Dat hield op moeilijk te zijn op de dag dat dit gereedschap kwam, en het gereedschap is hetzelfde gereedschap waarmee ze compileren. Eén basisimage per doel. Hetzelfde artefact in elk ervan. De matrix is een lus.
Een pijplijn die in een container bouwt en het resultaat nooit in een container draait is geen pijplijn. Het is een compiler met een cronjob ervoor en een ondertekensleutel erachter, en wat er ook uit komt is een gok.
Waarom wordt een release uit 2026 tegen een libc uit 2018 gebouwd?
Het pakket verklaart wat het nodig heeft, en de interessante regel is de eerste:
$ rpm -q --requires webex | grep glibc
glibc >= 2.28
glibc 2.28 verscheen op 1 augustus 201813. Het is de versie in Red Hat Enterprise Linux 814, een uitgave waarvan de volledige ondersteuning in 2024 eindigde. Dit is een product uit 2026, in 2026 gecompileerd, dat mikt op de C-bibliotheek van 2018. En dat vervolgens zijn eigen kopie van 2,5 MB van libstdc++.so.6 in de thuismap van de gebruiker zet, omdat de C++-runtime die bij zo’n oude basis hoort de code niet kan dragen.
Waarom zou iemand dat nog doen? Omdat ze niet statisch willen linken.
Dat is het hele verhaal. Op het moment dat je dynamisch tegen de C-bibliotheek van de host linkt, wordt de oudste distributie die je wilt ondersteunen een beperking op de machine waarop je compileert. Je kunt geen symbool gebruiken waar het oudste doel nooit van heeft gehoord, dus spijker je de build vast op een stokoude basisimage en daar blijf je. Elk jaar wordt het gat groter. Elke nieuwe taal- of bibliotheekfunctie komt met een discussie of de ondergrens omhoog mag. Lever je eigen libstdc++ mee om het ergste te verbloemen, en je onderhoudt er nu ook nog een eigen runtime bij.
Die ruil was zinnig toen een buildhost een fysieke machine in een rek was die iemand opnieuw moest installeren. Al een decennium is hij dat niet meer. Als je tegen de libc van de host moet linken, geeft een container per doel je een echte build op een echte versie van elk platform, en beperkt geen enkele de andere.
Maar kijk wat die discipline met een oud doel hier werkelijk heeft opgeleverd. Het is ABI-behoudendheid tegen aanzienlijke kosten: een acht jaar oude ondergrens, een meegeleverde C++-runtime, een ondersteuningsmatrix die eromheen is bevroren. En de client start nog steeds niet. Want wat er stukging was een bestandspad, en geen enkele zorgvuldigheid rond symboolversies beschermt een bestandspad. Ze hebben de compatibiliteitsbelasting en de bundelbelasting betaald, en de ene controle overgeslagen die niets kost. Beide rekeningen. Geen product.
Ze betaalden voor een zelfstandige bundel en kregen er geen
De beproefde manier om commerciële software op Linux uit te leveren is van zo weinig mogelijk van de host af te hangen. Statisch waar het kan, een zelfstandige boom waar het niet kan, en geen aannames over de distributie eronder. Het is niet elegant en niemand beweert dat. Het bestaat vanwege het alternatief. Een binair bestand dat een bepaalde versie van een bepaalde bibliotheek op een bepaalde plek nodig heeft, maakt van de machine van elke klant een supportzaak.
Cisco koos de tweede route en bundelde. Hier is de rekening:
| Wat de bundel bevat | Grootte of aantal |
|---|---|
Gedeelde objecten in /opt/Webex/lib | 150 |
| Hun eigen libcurl, CiscoSSL, zlib-ng, ICU, Kerberos, CUPS-client, hunspell en inferentiemotor | allemaal |
Geïnstalleerd onder /opt/Webex | 1,1 GB |
Een tweede kopie onder ~/.local/share/WebexLauncher, per gebruiker | 1,1 GB |
| Op schijf voor een chat- en belclient | 2,2 GB |
Twee gigabyte aan afhankelijkheden. Dat is de volle prijs van bundelen: de download, de schijf, de verdubbeling, de veiligheidslast van de enige partij te zijn die er iets van kan patchen, de hele mikmak. Betaal dat, en wat je koopt is een programma dat zich niets aantrekt van wat de host geïnstalleerd heeft, dat zich op Fedora en Debian en Arch en op wat een klant dit jaar ook standaardiseert hetzelfde gedraagt, en dat niet kapot kan door een distributie-upgrade waar het nooit van heeft gehoord.
Alleen doet het dat wel. Het trekt zich enorm veel aan van één map, en dat is een map op een buildserver.
Ze leverden hun eigen Kerberos en hun eigen ICU en hun eigen spellingcontrole mee, en een werkend pad naar de certificaten konden ze niet meeleveren. Het hele doel van de bundel is zelfstandig te zijn, en in het ene opzicht dat hem laat werken is hij niet zelfstandig. Elk van die 1,1 GB wordt via $ORIGIN correct gevonden. De ruim veertig bytes die het meest tellen niet.
En dit is het stuk dat mij pakt. Bundelen is de dure optie. Ze namen de kosten, ze deden het zware ingenieurswerk, ze kregen de verplaatsbaarheid goed voor honderdvijftig bibliotheken over twee installatievoorvoegsels. En richtten toen het ene deel dat bepaalt of iets ervan werkt op een machine die nooit een klant heeft bezeten.
Niemand heeft het ook gedocumenteerd
Naast de eerste tekortkoming staat een tweede, en dat is degene die de eerste zou hebben gevangen.
Vraag het pakket welke documentatie het meelevert:
$ rpm -qd webex | wc -l
0
$ rpm -qc webex | wc -l
0
Geen documentatiebestanden. En geen configuratiebestanden. Nul vermeldingen met %config in een pakket dat een openssl.cnf meelevert. Dat is niet cosmetisch. Een RPM markeert een bestand als %config zodat de pakketbeheerder bewaart wat de beheerder heeft veranderd, en een .rpmsave opslaat in plaats van het te overschrijven1516. /opt/Webex/lib/openssl.cnf wordt als gewoon bestand uitgeleverd, dus de volgende update overschrijft elke aanpassing die je erin hebt gemaakt en zegt je niets. Het ene bestand dat een klant terecht zou moeten kunnen aanpassen is het bestand dat de verpakking als wegwerpartikel behandelt.
Niets gepubliceerds zegt welke vertrouwensopslag de client gebruikt, welke omgevingsvariabelen hij eerbiedigt, of waar hij zijn TLS-configuratie leest. Er is geen pagina om na te slaan. Er is er geen geschreven. De enige manier waarop ik iets hiervan heb vastgesteld waren strings, readelf, ldd en ctypes tegen de uitgeleverde binaire bestanden. Een ondersteund product reverse-engineeren om een vraag te beantwoorden die de documentatie in één zin had moeten beantwoorden.
En dit is waarom dat meer telt dan het klinkt. Die documentatie schrijven is zelf een test. Zet wie dan ook bij de leverancier voor een leeg vel met het opschrift waar Webex voor Linux zijn CA-certificaten leest, en het eerste wat ze moeten doen is gaan kijken. Op het moment dat ze kijken vinden ze /workspace/.conan2/p/b/cisco8ee8b59cf93de/p/ssl, en het volgende dat eruit komt is een vraag. Gedocumenteerde configuratiepaden zijn geen papierwerk ten gunste van de klant. Ze zijn de goedkoopste audit die een leverancier op zijn eigen build kan uitvoeren, en ze overslaan is hoe zo’n pad tot in een release overleeft.
Niets daarvan is veel gevraagd. Zeg waar je configuratie staat. Zeg welke omgevingsvariabelen je eerbiedigt. Markeer je configuratiebestanden als configuratie zodat een update ze niet opeet. Dan heeft de klant die op een storing stuit ergens om te kijken dat geen hex-editor is.
Niemand heeft het gedraaid
De ontbrekende stap is hierboven vaak genoeg genoemd. Wat het vragen waard is, is waarom hij op dit platform ontbrak en op de andere niet.
Op Fedora in elk geval niet. Op macOS en het Fisher-Price OS (Windows) kan deze klasse fouten niet op dezelfde manier opduiken, omdat die platformen een TLS-stapel van het systeem hebben met een vertrouwensopslag die het besturingssysteem beheert. Linux heeft zoiets niet. OpenSSL is de vertrouwensopslag, en dus is wie hem meelevert eigenaar van waar hij kijkt. Het ene platform waar de meegeleverde bibliotheek dragend is, is dus het platform dat ongetest is uitgeleverd, wat een beslissing is over welke klanten een rooktest waard zijn.
Het hele onderzoek kostte een avond: het log lezen, opmerken dat de netwerkdetectie slaagde voordat de banner het tegendeel beweerde, het gecompileerde pad uit het binaire bestand trekken, de exacte foutcode tegen de meegeleverde bibliotheek reproduceren. Dat alles met een AI-agent die de logcorrelatie en het ctypes-tuig deed terwijl ik bedacht wat ik hem moest vragen. Ik noem dat om één reden: de diagnose die een leverancier nooit stelde voordat hij dit pakket ondertekende en in zijn eigen repository legde, ligt nu binnen een avond bereik van elke klant met het geduld om te kijken. De ingenieurs van Cisco hebben net als iedereen Claude tot hun beschikking, en het had dit fatsoenlijk gebouwd. Je kunt het niet vragen /workspace/.conan2 in een uit te leveren artefact te bakken zonder dat het je vertelt wat er gebeurt als het artefact de workspace verlaat. Het gereedschap om dit te vangen is niet schaars meer, en de kennis ook niet. Wat ontbreekt is iemand bij de leverancier wiens taak het was om te kijken.
Ondertussen is het supportpad voor de persoon die hier tegenaan loopt een banner die zegt dat hun internet eruit ligt. Ze zullen de router herstarten. Ze zullen hun provider bellen. Ze zullen een ticket aanmaken dat nergens heen gaat, omdat het symptoom dat Cisco koos om te tonen van Cisco weg wijst.
Dat is de volledige lijst van wat er misging. Het is het zeggen waard hoe goed eruit had gezien, want elk punt erop heeft een uitgemaakt antwoord dat ouder is dan dit product.
Hoe het gebouwd had moeten worden
Genoeg over wat er misging. Hier is de norm, en niets ervan is nieuw. Het is wat een binair bestand uitleveren naar de computer van iemand anders al twintig jaar van je vraagt.
Begin bij de beslissing die Cisco in beginsel goed en in uitvoering fout nam: hoeveel van de host je bereid bent aan te nemen. Statisch linken is het sterkste antwoord, en het is de moeite waard daar concreet over te zijn, want “link het gewoon statisch” wordt rondgezwaaid door mensen die het nooit hebben gehoeven en weggewuifd door mensen die het nooit hebben geprobeerd.
| Wat het wegneemt | Wat het kost |
|---|---|
RUNPATH, en elke manier om het fout te doen, want er is geen zoektocht bij het laden | glibc linkt niet netjes statisch: naam- en gebruikersopzoeking gaan via dlopen, dus het binaire bestand grijpt nog steeds naar de NSS-modules van de host17 |
| De glibc-ondergrens, zodat de oudste distributie niet meer bepaalt waarop je mag compileren | LGPL-delen brengen een herlinkverplichting mee, dus die blijven dynamisch of je levert mee wat nodig is om te herlinken |
Breuk door een distributie-upgrade, een hernoemde bibliotheek of een verouderde ldconfig-cache | Je bezit elke patch: geen beveiligingsupdate van een distributie bereikt je klanten |
De dlopen van een geversioneerde .so uit een map die er misschien niet is | Een grotere download, en geen delen van geheugenpagina’s tussen processen |
En één regel in de linkerkolom die de rest alleen maar ondersteunt: het artefact dat je pijplijn heeft geproduceerd is het artefact dat de klant draait, byte voor byte. Test het en je hebt getest wat je hebt uitgeleverd. Dat is de eigenschap over wiens afwezigheid dit hele stuk gaat.
Over de rechterkolom hoort eerlijkheid. Echte kosten, en de reden dat mensen naar musl grijpen of een mengvorm accepteren. Maar kijk naar de derde regel. Elke patch bezitten geldt precies zo voor wat Cisco al deed: een gebundelde boom van 150 bibliotheken is dezelfde verplichting zonder enige van de garanties bij het laden. Ze tekenden hoe dan ook voor het bezitten van elke patch en kregen er niets voor terug.
De regel is dus eenvoudig, en het is de regel die ze braken: wat je er niet in kunt linken, moet je vinden via een pad relatief aan het binaire bestand. $ORIGIN voor de code, en dezelfde discipline, bewust, voor elk datapad waar de bibliotheek naar gaat zoeken. In dit geval zijn er vier en elk ervan heeft een gedocumenteerd handvat:
| Waar de bibliotheek naar zoekt | Wat er is uitgeleverd | Wat het had moeten zijn |
|---|---|---|
| Vertrouwensankers | OPENSSLDIR/cert.pem, vastgezet bij het bouwen | meegeleverd in de boom, of SSL_CERT_FILE gezet bij het starten5 |
| Configuratie | OPENSSLDIR/openssl.cnf, hetzelfde pad, nooit gevonden | OPENSSL_CONF, gericht op de kopie in het pakket5 |
| Providers, inclusief FIPS | MODULESDIR, absoluut, dus fips.so onbereikbaar | OPENSSL_MODULES, “the directory from which cryptographic providers are loaded”5 |
| Engines | ENGINESDIR, absoluut | OPENSSL_ENGINES, of niets, aangezien OpenSSL 4.0 de ondersteuning voor engines helemaal heeft verwijderd5 |
Vier paden, vier omgevingsvariabelen, allemaal in één handleidingpagina die hun eigen bibliotheek meelevert. Als een tekst in je artefact met een / begint en bij het bouwen is vastgelegd, is het een defect dat wacht tot een klant het vindt. Er is geen derde mogelijkheid waarin een absoluut buildpad in orde is.
De oplossingen voor deze, en de controle die hem vangt
Van het beginsel naar dit concrete defect. Zes wijzigingen, geen enkele daarvan onderzoek:
| Oplossing | Inspanning | Waarom het klopt |
|---|---|---|
--openssldir=/opt/Webex/lib/ssl zetten en de boom meeleveren | één configuratievlag | Het pad bestaat dan in het pakket, en daar is de vlag voor2 |
SSL_CERT_FILE/SSL_CERT_DIR bij het starten in CiscoSSLUtils zetten en de bekende distributiepaden aftasten | een stuk of twaalf regels | Standaard, gedocumenteerd, al ondersteund door hun eigen bibliotheek5 |
| De CA-bundel zelf in het pakket meeleveren | alleen verpakking | Volledige controle over het vertrouwen, tegen de prijs van de versheid ervan bewaken |
De openssl.cnf corrigeren zodat de default-provider wordt geactiveerd | één regel | Hoe dan ook nodig, en nu gemaskeerd door de padfout6 |
openssl.cnf als %config markeren | één regel in de spec | Voorkomt dat een update de wijziging van een beheerder stilletjes opeet15 |
| De paden en de geëerbiedigde variabelen publiceren | één pagina | De goedkoopste audit die er is, en hij vindt deze fout terwijl hij wordt geschreven |
De eerste is de oplossing met één vlag en ze had het product werkend uitgeleverd. Het is één waarde in een buildscript, één keer gezet, die ze fout hadden omdat er stroomafwaarts nooit iets controleerde.
De controle is kleiner dan de oplossing:
# in CI, on the packaged artefact, in a clean container
test -d "$(strings lib/libcrypto.so.3 | grep -oP '(?<=OPENSSLDIR: ")[^"]+')" \
|| { echo "shipping a trust store path that does not exist"; exit 1; }
Eén regel. Hij had deze release luid laten falen, in februari, op de machine die hem maakte, in de container die hem maakte, voordat het pakket werd ondertekend. Dat hij er niet is, komt niet door moeilijkheid en niet door kosten. Het komt doordat niemand gevraagd is hem te schrijven, en daarmee was de vraag of het verpakte artefact zich als software gedroeg niemands taak.
Wat een slordig buildproces alle anderen kost
Alles hierboven is het buildproces van één product van binnenuit gezien, en ik loop het niet nog eens met je door. De vraag die het waard is, is of dat niveau van zorg waarschijnlijk bij één team ophoudt.
Zet er dus het beeld van buiten naast. CISA houdt een catalogus bij van kwetsbaarheden waarvan bekend is dat ze in het wild worden misbruikt. Niet theoretisch, niet gescoord. Waargenomen terwijl ze tegen mensen werden ingezet. Per 14 september 2026 telt hij 1.710 vermeldingen18:
| Leverancier | Vermeldingen in de KEV-catalogus |
|---|---|
| Microsoft | 388 |
| Cisco | 98 |
| Apple | 94 |
| Adobe | 81 |
| 74 | |
| Oracle | 46 |
| Fortinet | 30 |
| VMware | 26 |
Tweede, achter een monopolie op besturingssystemen en voor alle anderen. De meest recente toevoeging van Cisco ging erin op 14 september 2026, de dag voordat dit werd geschreven.
Ik telde hetzelfde bestand drie weken eerder voor /nl/random/is-your-msp-lying-to-you-part2/: versie 2026.08.27, 1.685 vermeldingen, Cisco op 96. Twee erbij sindsdien, in eenentwintig dagen.
Wees eerlijk over wat die tabel op zichzelf wel en niet bewijst. Een grote geïnstalleerde basis op plekken met hoge waarde trekt aandacht, en aandacht vindt bugs, dus elke leverancier van die omvang draagt een lange lijst. Het getal is een vermoeden, geen oordeel.
De samenstelling is lastiger weg te wuiven dan het totaal. Vijfentwintig van Cisco’s achtennegentig zitten op de beveiligingslijnen: de firewalls, de appliances, de VPN-concentrators, de mail- en webgateways, de identiteitsdiensten. En de vier meest recente vermeldingen tegen hen zijn, op een rij, Secure Firewall Management Center twee keer, Secure Firewall ASA, en Secure Email Gateway, die laatste op 14 september 202618. Niet de switches. Niet de samenwerkingsapparatuur. De producten die specifiek worden verkocht om datgene te zijn wat iedereen anders veilig houdt.
Wat de zin is waar dit hele stuk naartoe liep, dus hier is hij in gewone taal. Dit is een bedrijf waarvan het vak het verkopen van beveiligingsapparatuur is, en het krijgt het niet voor elkaar een bureaubladclient een certificaat te laten controleren. Geen moeilijke zaak. Geen nieuwe aanval. De meest alledaagse beveiligingshandeling in de informatica, uitgevoerd door elke browser bij elke pagina, in een bibliotheek die ze zelf hebben geforkt, en ze leverden hem uit gericht op een map die nooit op een klantmachine heeft bestaan. En ondertekenden hem toen.
Wat dit stuk toevoegt is een steekproef van het proces erachter. Geen kwetsbaarheid. Een eenvoudig verpakkingsdefect, de minst subtiele klasse fouten die er is, op een ondersteund platform, te vangen door elke rooktest die iemand had willen draaien, en toch uitgeleverd. Als een buildpijplijn dat voor een betalende klant zet, is het niet duidelijk wat ze wel zou tegenhouden. Niets deed dat.
Mag ik vragen waarom dit is ondertekend?
Mag ik vragen waarom een pakket dat geen TLS-handshake kan afronden is ondertekend en gepubliceerd tegen een platform dat jullie eigen eisenpagina als ondersteund noemt? Niet wie het heeft ondertekend. In een naam heb ik geen belang en daar gaat het niet om. Wat het toestond, want iets deed dat, vijftien minuten nadat de build klaar was, en wat het ook is, het draait vandaag nog.
Nu de botte versie. Dit is geen project dat ik ergens vandaan heb geplukt en op goed geluk heb geprobeerd. Er is voor betaald. Erachter zitten een contract, een supportlijn, een eisenpagina die een bewering over Linux doet, en een ondertekensleutel die stelt dat het pakket van Cisco komt en geschikt is om te installeren. Elk daarvan is een uitspraak aan een klant, en bij deze release was elk ervan niets waard, omdat het resultaat nooit is geopend.
En de norm die hier wordt gemist is niet die van mij. Het is die van hen. De vier variabelen die die paden hadden kunnen dragen staan gedocumenteerd in een handleidingpagina die jullie in de bundel meeleveren. Jullie eigen pakketformaat heeft een %config-markering die jullie niet hebben gebruikt en een documentatiesectie die jullie leeg hebben gelaten. Jullie eigen build draaide in een container die jullie nooit hebben gebruikt om de uitvoer te draaien. Ik vraag een netwerk- en samenwerkingsbedrijf niet om iets uit te vinden. Ik vraag waarom het zijn eigen handleidingen niet heeft gelezen.
Het excuus dat ik dan ook niet accepteer is dat dit moeilijk zou zijn. Het is voor niemand moeilijk, en zeker niet voor een bedrijf dat dit dagelijks doet, op deze schaal, voor dit geld. Vakmensen die dit elke dag doen hebben geen excuus, en groot zijn is er geen.
En mag ik er nog één stellen, want dat is degene die er echt toe doet. Jullie verkopen firewalls. Jullie verkopen een e-mailgateway, een VPN-concentrator, een identiteitsdienst, een beheercentrum voor dat alles, en het verhaal bij alle is dat jullie dit beter begrijpen dan jullie klant. Hoe levert een bedrijf dat zich neerzet als autoriteit op netwerkbeveiliging dan een client uit die een certificaat niet kan valideren? Niet: hem niet slim genoeg valideert. Er geen kan opzoeken, omdat de map er nooit was.
Er bestaat geen versie van dat antwoord die ik wil horen die begint met dat het bureaubladteam los zou staan van het apparatuurteam. Het is dezelfde handtekening, dezelfde pijplijn, dezelfde gepubliceerde bewering over een ondersteund platform, en dezelfde ontbrekende stap aan het eind, namelijk de doos openmaken. Als certificaatvalidatie voor het uitleveren niet wordt gecontroleerd in het product waar breuk luid en onschadelijk is, heb ik geen reden te geloven dat ze wordt gecontroleerd in het product waar breuk stil en duur is.
En het eerlijke deel, gewoon gezegd. Niets hiervan verraste me. Ik ben dit niveau van deze leverancier gaan verwachten, en daarom koop ik, waar de keuze aan mij is, hun apparatuur niet en bouw ik er niet op. Dat is geen voorkeur voor logo’s. Het is hetzelfde oordeel dat ik over elke leverancier zou vellen: ik heb hun werk gemeten, meer dan eens, en het komt steeds hetzelfde terug. De catalogus hierboven is één meting. Wat de eerste helft van dit stuk kostte is een tweede.
De reden dat ik überhaupt op Webex zat is dat de keuze niet aan mij was, en dat is het benoemen waard, want dat is de positie waarin de meesten die dit lezen zitten. Je komt zelden onder een leverancier uit wiens kwaliteit je al hebt gemeten. Iemand anders tekent het contract, de spullen komen binnen, en de eerste die erachter komt wat er in de build is overgeslagen ben jij, aan je bureau, met een vergadering die begint.
Iets uitleveren dat je nooit hebt gedraaid
Er zal een interne verklaring komen. Een drukke sprint, een pijplijn die van eigenaar wisselde, een platform zonder eigenaar. Geen ervan is het horen waard, want ze beschrijven allemaal hetzelfde: het werk is niet gedaan en niets in het proces eiste dat het wel gebeurde.
Er bestaat in de ambachten een oude norm die het nooit tot de software heeft gebracht: je verlaat de klus niet voordat je het ding hebt laten draaien. Je vult de installatie en controleert elke koppeling. Je zet spanning op de kast en test elke groep. Niet omdat je aan je werk twijfelt. Omdat de klant het gaat gebruiken, en erachter komen waar hij bij staat is geen professionele uitkomst. Niemand kijkt toe terwijl je het doet. Je doet het toch. Dat is de hele betekenis van het woord.
Software voert al dertig jaar aan dat het anders is, dat de build het op te leveren product is en de installatie het probleem van iemand anders, dat een groene pijplijn hetzelfde is als een werkend product, en dat is niet zo. Een build die nooit buiten de container is uitgevoerd die hem maakte is niet afgemaakt, hij is achtergelaten op het punt waar afmaken saai wordt. Alles wat erop volgt is een bewering over werk dat niet is gedaan.
De oplossing is één configuratievlag. De controle is één regel shell. De kosten van geen van beide zijn het interessante; het interessante getal is hoeveel mensen hun inloggegevens in een Webex-venster typten, keken hoe het zei dat hun internet eruit lag, en het geloofden, omdat Cisco het ze vertelde en Cisco een netwerkbedrijf is. Dat is wat de ontbrekende test werkelijk kocht: geen bug, maar een leugen die het product met overtuiging vertelt, bij elke start, aan mensen die geen manier hebben om beter te weten.
En dat is de streep die het waard is te trekken voordat je het tabblad sluit, want de twee helften van dit stuk zijn geen twee onderwerpen. Een vertrouwenspad dat naar een buildcontainer wijst en een authenticatieomzeiling op een randapparaat zijn dezelfde fout met andere inzet. Beide zijn een waarde die niemand controleerde, in een artefact dat niemand draaide, ondertekend door een proces dat herkomst bevestigt en geen geschiktheid. Die voor mijn neus was de onschadelijke soort. Hij ging luid stuk, op mijn eigen bureau, en ik wist het als eerste. De andere soort doet dat niet voor je.
Niemand bij Cisco besloot een misbruikbare firewall uit te leveren, net zomin als iemand besloot een client uit te leveren die het internet niet kan bereiken. Dat is geen verdediging. Het is de aanklacht. Geen van beide hoeft te worden besloten, en dat is precies het probleem: beide zijn wat er aan de andere kant uit komt als een pijplijn compileert, ondertekent en publiceert zonder dat iemand verantwoordelijk is gemaakt voor het openen van het resultaat. Een proces dat een map die niet bestaat niet vangt, ging een lengte die niet wordt gecontroleerd nooit vangen, en een leverancier die je het apparaat verkoopt dat je grens bewaakt heeft geen recht op dat proces.
Dus als de naam van een leverancier steeds op die lijst blijft opduiken, weersta dan de comfortabele verklaring dat ze simpelweg groot zijn en zwaar in de schijnwerpers staan. Schaal verklaart het aantal. Het verklaart de soort niet. Kijk in plaats daarvan naar wat hun buildproces met de saaie dingen doet, want de saaie dingen zijn van buitenaf meetbaar, door jou, vandaag, op spullen die je al hebt.
Controleer je eigen bundels. strings en readelf en twintig minuten vertellen je welke van je leveranciers een pad uitleveren naar een machine die je nooit zult zien. Wat je in werkelijkheid meet is niet het pad. Het is of daar iemand zat te kijken, en als het antwoord nee is bij iets dat zo goedkoop te vangen is, weet je al wat het is bij de dingen die dat niet zijn.
Cisco — Webex App system requirements — de lijst met ondersteunde platformen, Linux inbegrepen. ↩︎
OpenSSL — INSTALL.md,
--openssldir— “Directory for OpenSSL configuration files, and also the default certificate and key store.” ↩︎ ↩︎OpenSSL — SSL_CTX_load_verify_locations(3) — het standaard CA-bestand is
cert.pemen de standaard CA-mapcerts, beide binnen de standaard OpenSSL-map; en over retourwaarden: “A missing default location is still treated as a success.” ↩︎ ↩︎Conan 2 —
conan cache— pakketbinaries staan onder een gehasht pad in de lokale cache, en daar komt/workspace/.conan2/p/b/<hash>/pvandaan. ↩︎OpenSSL — openssl-env(7) —
SSL_CERT_DIRenSSL_CERT_FILE“specify the default directory or file containing CA certificates”. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎OpenSSL — provider(7) — de standaardprovider en wat een configuratie die hem weglaat onbeschikbaar maakt. ↩︎ ↩︎
OpenSSL — config(5) — het configuratiebestand dat OpenSSL bij de initialisatie laadt, en waar ernaar wordt gezocht. ↩︎
freedesktop.org — XDG Base Directory Specification — “The base directory defined by
$XDG_DATA_HOMEis considered more important than any of the base directories defined by$XDG_DATA_DIRS.” ↩︎freedesktop.org — Desktop Entry Specification — waar naar
.desktop-bestanden wordt gezocht en hoe het ene het andere met dezelfde naam overschaduwt. ↩︎Filesystem Hierarchy Standard 3.0 — de mappen die een hoofdbestandssysteem geacht wordt te bevatten,
/workspaceniet daaronder. ↩︎update-ca-trust(8) — hoe de samengevoegde bundel onder
/etc/pki/ca-trust/extractedop Fedora en verwanten wordt gemaakt. ↩︎ld.so(8) —
$ORIGINwordt uitgebreid naar de map die het programma of gedeelde object bevat, en dat is wat een gebundelde boom verplaatsbaar maakt. ↩︎glibc timeline — “2018-08-01 GLIBC 2.28 — The GNU C Library version 2.28 is now available”. ↩︎
glibc — Release wiki — de tabel van versie naar distributie; Red Hat Enterprise Linux 8 is glibc 2.28. ↩︎
RPM — spec file reference —
%configen wat de pakketbeheerder doet met een bestand dat als configuratie is gemarkeerd. ↩︎ ↩︎Fedora packaging guidelines — configuration files — wanneer een meegeleverd bestand als
%configmoet worden gemarkeerd. ↩︎glibc FAQ — waarom een statisch gelinkt glibc-programma tijdens het draaien alsnog de NSS-modules van de host nodig heeft. ↩︎
CISA — Known Exploited Vulnerabilities Catalog — geteld uit de gepubliceerde JSON-feed, catalogusversie 2026.09.14, 1.710 vermeldingen. ↩︎ ↩︎