Blog ·
Eén echte agent, één ontwikkelgateway
Vijf rondes leverden dezelfde slotzin: over de draad onbewezen. Die sluiten kostte één echte agent, een geplant wachtwoord en twee metingen die niets maten.
Door Provibr
Vijf rondes achter elkaar eindigden hun verslag met een variant op dezelfde zin: de protocolwijziging is additief, de unittests zijn groen, en niets ervan is tegen een echte agent uitgeoefend. Monitoringadapters, de verzegelde envelope, automatische agentupdates, een automatiseringsstap die op een commando wacht. Alle vijf waren ze correct voor zover iemand kon zien, en alle vijf droegen ze een voorbehoud dat de volgende ronde erfde.
Dat voorbehoud is goedkoop om op te schrijven en het stapelt. Elke ronde legt er een bij, niemand heeft ongelijk, en na vijf keer ligt er een stapel gedrag dat alleen ooit in een testopstelling heeft bestaan. Deze ronde ging over het weghalen van die stapel in plaats van er iets aan toe te voegen: geen functie, geen protocolwijziging, geen release. Tweeëntachtig metingen tegen één echte agent.
De opstelling, in één zin
Een echte agentbinary, gebouwd voor hetzelfde doel dat wij uitleveren, getekend met een ontwikkelsleutel, draaiend in een wegwerpcontainer op een machine die geen laptop van een ontwikkelaar is, pratend met een ontwikkelgateway via een omgekeerde tunnel, met zeven wegwerpdiensten op loopback om te meten en een tweede kopie van dezelfde binary ingeschreven als gehoste probe.
Alles in die zin is dragend. De binary is de uitgeleverde en geen testbuild, want de interessante fouten wonen in serialisatie en TLS en niet in logica. De gateway is bewust een ontwikkelinstantie op afwijkende poorten, want de productiegateway luistert op de voor de hand liggende, en een agent die aan de verkeerde kant daarvan aanklopt zou een zeer slechte middag opleveren. De diensten zijn wegwerpcontainers met de echte software in echte versies en geen nabootsingen, want de hele reden voor deze oefening is dat nabootsingen precies zijn wat wij al hadden.
De databasediensten kregen een canariewachtwoord, en die keuze is wat de volgende paragraaf mogelijk maakte.
| Groep | Metingen | Wat het vaststelt |
|---|---|---|
| Monitoringadapters | 44 | Zes database- en directoryprotocollen, goede en foute inloggegevens, en de regelstand die volgt |
| Automatische updates | 19 | Inplannen, toepassen, herstarten, en een gemanipuleerde build weigeren |
| Automatiseringsstap | 11 | Een commando dat binnen de wachttijd klaar is, en een dat dat niet is |
| Gehoste probe | 4 | Dezelfde binary ingeschreven als platformprobe in plaats van als klantagent |
| Bron weggevallen | 4 | Een losgekoppelde agent levert een eigen stand op, nooit een valse storing |
Een zoekopdracht met een geplante waarde
De bewering die wij het liefst wilden toetsen is een ontkenning: een monitoringgeheim reist verzegeld en wordt nooit weggeschreven naar de commandorij, het gebeurtenissenlog, de berichtenbus of een van beide proceslogs.
Ontkenningen zijn de beweringen die stil misgaan. De wachtwoorden waren dus geen willekeurige teksten waarvan de afwezigheid niets bewijst; het waren geplante waarden, zo gekozen dat één voorkomen ergens onmiskenbaar zou zijn. Daarna zijn zeven lagen doorzocht: de bewaarde commandopayloads, de gebeurtenissentabel, de bewaarde checkconfiguratie, de bewaarde resultaten, de berichtenbus in zowel tekst als gecodeerde vorm, het gatewaylog en het agentlog. Samen is dat rond een half miljoen tekens materiaal.
Nul treffers, wat het antwoord is dat wij wilden en ook het antwoord dat een kapotte zoekopdracht geeft.
Diezelfde zoekopdracht is daarom losgelaten op een bestand dat het woord zeker bevatte, en vond het. Die positieve controle is het verschil tussen "wij hebben gekeken en er was niets" en "wij hebben niet gekeken". Hij kost één regel en het is de waardevolste regel in het script.
Twee andere eisen bewaken hetzelfde falen van de andere kant: de run moet minstens een tiental echte checkcommando's en enkele tientallen busberichten waarnemen voordat hij iets mag concluderen. Allebei hebben ze tijdens de ronde rood gestaan, op runs die anders een schone veeg over helemaal niets gemeld zouden hebben. Een veeg die geen overtredingen vindt in een lege verzameling is geen bewijs, en hij ziet er precies uit als succes.
Twee manieren waarop een meting groen is zonder te meten
Allebei zijn ze in deze ronde gebeurd, in scripts geschreven door iemand die deze faalvorm kent en er geregeld over schrijft.
De eerste: een query selecteerde één kolom en de code las daarna een andere eigenschap van de resultaatrijen. In een getypeerde taal is dat een fout; hier is het undefined, voor altijd, stil, en elke vergelijking ermee faalt op een manier die eruitziet als een bewering die nooit afgaat in plaats van een bewering die altijd slaagt. De controle meldde succes omdat hij niets had om mee te vergelijken.
De tweede: een lidmaatschapstoets tegen een lege lijst. Dat is een uitspraak over niets, hij is triviaal waar te maken, en hij is de natuurlijke eindstand van elk script waarvan een eerdere stap stilletjes nul rijen opleverde. Allebei zijn ze goedkoop af te dekken: toets de vorm van wat je ophaalde, en toets dat de verzameling die je gaat onderzoeken niet leeg is.
Vijf kolomnamen in die scripts waren gegokt in plaats van opgezocht. Die faalden luidruchtig, wat de goedkope variant van dezelfde fout is, maar het ís dezelfde fout: één commando zegt hoe een kolom heet, en de versie waarin een gok in een lijst belandt die niemand uitvoert, is de versie die de review overleeft.
Een agent die weigert zich twee keer in te schrijven
Halverwege de updatetests kwam de agent niet meer terug. Van buitenaf zag het eruit als een herstartlus, en het eerste uur ging naar het updatepad.
De agent deed precies het juiste. Inschrijven schrijft een toestandsbestand, en het gegenereerde installatiescript controleert op dat bestand en weigert opnieuw in te schrijven als het er is. Een al ingeschreven host opnieuw inschrijven vervangt een identiteit, en dat per ongeluk doen is hoe een machine in één stap zijn certificaat en zijn kluis kwijtraakt. Onze toezichtlus, voor de gelegenheid geschreven, had zo'n poort niet, en elke herstartpoging liep tegen de eigen terechte weigering van de agent aan.
De les gaat niet over die poort. Hij gaat erover dat een terechte weigering en een defect van een afstand dezelfde vorm hebben, en dat wat ze scheidt het lezen is van wat de andere kant werkelijk zei in plaats van wat het symptoom suggereerde.
Uit dezelfde dagen kwamen drie andere verrassingen, alle drie ontwerpbesluiten die de werkelijkheid tegenkwamen:
Ingeplande updatetijden liggen op een raster van vijf minuten, afgedwongen door een databasebeperking. "Over één minuut", wat iemand die een test schrijft wil, bestaat niet. Wat dat werkbaar maakt is een inhaalvenster: een gemist tijdstip wordt nog een kwartier lang uitgevoerd. Dat is ook waarom een herhaalde meting binnen datzelfde kwartier een ongebruikt rastertijdstip moet kiezen, want een tijdstip wordt per agent uniek geclaimd en een tweede run tegen hetzelfde wordt terecht geweigerd.
Een TCP-check staat standaard op TLS aan. Op een kale poort gericht faalt hij met een TLS-fout, wat juist is en wat leest als een kapotte check tot je kijkt wat een standaardwaarde is.
En de auditrij van een automatiseringsstap waarvan de wachttijd verliep bevriest op de stand die de flow zag. Het commando werd zestig milliseconden later een timeout en de rij bleef zeggen wat hij zei. Dat is geen fout in de rij, het is een fout in de commentaarregel erboven, die beloofde dat je daar de uiteindelijke uitkomst kon aflezen. Die belofte is weggehaald en elders netjes beantwoord.
Een melding die groepeert, en een telling die nul zegt
De scherpste van de kleine bevindingen. Na een geslaagde update schrijft het platform een melding. De meting telde meldingen die sinds het begin van de run waren aangemaakt, en kreeg nul.
Meldingen groeperen. Een tweede voorval van hetzelfde hoogt een teller op de bestaande rij op en laat het aanmaakmoment staan, wat precies is wat je in een paneel wilt: "deze agent viel weg" met een aantal ernaast, in plaats van veertig rijen. Het is ook precies wat een telling op het aanmaakmoment niets laat melden terwijl de melding gewoon zichtbaar staat.
De meting had ongelijk, het product had gelijk, en de enige reden dat wij weten welke kant het op was is dat iemand het paneel opende in plaats van de query te geloven. Elke samenvoeging heeft een vorm, en een test die die vorm niet kent toetst zijn eigen aanname.
Wat wij zouden opschrijven
- "Over de draad onbewezen" is een schuld, en hij stapelt. Eén ronde kan hem dragen. Vijf rondes zijn een stapel gedrag dat buiten een testopstelling nooit bestaan heeft.
- Bewijs een ontkenning met een geplante waarde en een positieve controle. Nul treffers en een kapotte zoekopdracht zijn zonder die controle niet te onderscheiden.
- Geef elke veeg een ondergrens. Toets dat de invoer niet leeg is voordat je er iets over concludeert.
- Een terechte weigering ziet er van een afstand uit als een defect. Lees wat de andere kant zei voordat je je eigen kant herschrijft.
- Gok geen kolomnaam, geen standaardwaarde en geen raster. Alle drie zijn ze één commando ver, en de gok die in een lijst belandt is de gok die nooit afgaat.
- Ken de vorm van wat je telt. Gegroepeerde rijen hebben het aanmaakmoment niet waarop je filtert.
Voor de duidelijkheid: er is in deze ronde niets uitgeleverd. Geen productwijziging, geen protocolwijziging, geen agentrelease. Wat eruit kwam is dat vijf voorbehouden weg zijn en een handvol kleine defecten gerepareerd, en de opruiming is nageteld in plaats van aangenomen: geen achtergebleven rijen, geen achtergebleven containers, geen achtergebleven bestanden.
Sommige dingen bleven onbewezen. Het injectiepad van de automatisering is via zijn kern uitgeoefend en niet via het paneel, de adapters draaiden niet vanaf een gehoste probe, en er is nog steeds geen meting tegen een echte hypervisor.
Uit het platformwerk aan Provibr. Waarom het monitoringgeheim per poging verzegeld wordt staat in een geheim dat één keer reist, en hoe een getekende agentupdate een gemanipuleerde build weigert staat in hoe een getekende agentupdate zichzelf weigert.