Wie wat mag, en hoe vaak · eindproject
Eindproject: Project: jouw lijst en van niemand anders
Eigendom, een loginlimiet en een log werken elk, en elk heeft een test. Dit project formuleert ze als één beleid, en dicht het laatste gat erin: wat er gebeurt als je eigen code faalt.
Het beleid
| Route | Wie mag hem aanroepen | Een weigering ziet eruit als | Gelogd als |
|---|---|---|---|
GET, HEAD /about | iedereen | 405 voor een andere | - |
GET, HEAD /books, /books/{id} | een token dat geverifieerd wordt; alleen de eigen boeken van de aanroeper | 401 zonder token; 404 voor een boek dat ontbreekt of van een ander is | de gebruiker van het token |
POST /books, PUT en DELETE /books/{id} | een token waarvan de gebruiker mag schrijven; alleen de eigen boeken van de aanroeper | 401; 403 voor een account dat niet mag schrijven; 404 voor een boek dat niet van de aanroeper is | de gebruiker van het token |
POST /login | Basic-inloggegevens | 401; 429 met Retry-After als de naam of de hele dienst over zijn limiet is | de gebruiker na een juist wachtwoord, anders - |
POST /tokens | een | 401 | de gebruiker van de sessie |
GET /me, POST /prefs | iedereen | geen | de gebruiker van de sessie als het verzoek een levende sessie leest, anders - |
POST /logout | iedereen | geen | - |
Elk verzoek dat handle() bereikt, wordt één keer opgeschreven, in vijf velden en zonder inloggegeven. De boeklezingen antwoorden private, no-cache, en de vijf sessieroutes antwoorden no-store.
Als je eigen code faalt
De tabel zegt wat de server doet met verzoeken die hij begrijpt. Eén geval ontbreekt: een fout in de server zelf. Vandaag ontsnapt een fout in route() uit handle(). De client krijgt geen antwoord van je code, en de log krijgt geen regel.
handle() is de enige plek waar elk antwoord dat je routes bouwen doorheen gaat, dus daar hoort de bewaking. (De 411, 413 en 400 die read_body antwoordt voordat een route draait, gaan er niet doorheen, en worden niet gelogd.) Zet de aanroep van route in een try. Bouw in except Exception: het antwoord:
traceback.print_exc()drukt deaf naar standaardfout, waar de beheerder leest. De tracebackis nieuw, het project importeert hem al, en deze ene aanroep is alles wat je ervan nodig hebt.json_error(500, ...)met een vaste melding naar keuze.cache-controlgezet opno-store.Een lege body als de methode
HEADis. Dat is de regel uitwith Python, die routedeze keer niet kon toepassen.
Houd de logregel na de try, zodat het mislukte verzoek ook gelogd wordt, als een 500. Les 5 schreef log_line zo dat hij niet kan falen, dus niets na de try kan een fout geven.
Waarom Exception, en geen kale except? Op Ctrl+C drukken geeft KeyboardInterrupt, en zo stop je een server vanaf het toetsenbord. KeyboardInterrupt is geen Exception, dus deze bewaking laat hem door, en de server stopt nog steeds.
Python in Practice leerde je nauw af te vangen, en dat geldt overal elders nog steeds. Dit is de enige plek waar een brede vangst de juiste grens is, en het is de buitenste: wat er ook misging in route(), de client krijgt een antwoord en de beheerder krijgt de details.
De stream van de beheerder is de juiste plek voor een traceback. Het is geen veilige plek. Een traceback kan een
Waarom vangt de bewaking Exception af en niet elke mogelijke exception?
Mijlpalen
1. De aanvalstest. Een client kan je server al een fout laten geven. Stuur GET /books/ gevolgd door 5.000 cijfers: int() weigert een zo lang getal met een handle(), en de client krijgt helemaal geen antwoord. Een bewaking is voor die fout en voor de fouten die nog niemand gevonden heeft. De test gebruikt een fout die sneller op te schrijven is: hij maakt de state met de hand kapot. Met steps.keyed_state() en een token voor ana verwijdert hij de boeken met state.pop("books") en stuurt hij GET /books. Hij controleert status 500, en dat noch b"Traceback" noch b"KeyError" in de body staat. Voer python -m pytest -q uit: 1 failed, 20 passed. De KeyError ontsnapt uit handle() en laat de test falen.
2. De bewaking. Schrijf hem in handle, zoals hierboven beschreven.
3. Alles samen. python -m pytest -q eindigt met 21 passed. Voer daarna beide scenario’s uit:
python pl_sec.py other-reader
python pl_sec.py guessing
Elk eindigt met de log van je server. In de eerste kun je de poging van ben lezen:
1000000 DELETE /books/1 404 ben
4. De bevindingen. In SECURITY.md noemen de rijen F8 en F9 elk hun test en zeggen ze closed.
De geweigerde verwijdering van ben staat in de log als ... DELETE /books/1 404 ben. Wat geeft die regel een beheerder wat de 404 alleen niet gaf?
Wat nog open staat
F14 uit hoofdstuk 5 staat nog open: een token dat lekt, werkt tot het verloopt.
En kijk nog eens naar de boeken. Een titel kan elke tekst zijn die een client stuurt, tot aan de bodylimiet. Je server slaat hem op en geeft hem terug zonder er nog eens naar te kijken. Het volgende hoofdstuk geeft een boek drie velden meer, en vraagt wat er gebeurt als tekst uit een verzoek gelezen wordt als iets meer dan tekst.
Opdracht
Dicht het laatste gat: een fout in je eigen code moet een vaste 500 worden.
Schrijf in
tests/test_attacks.pytest_attack_error_body_shows_no_traceback(). Verwijder metstate = steps.keyed_state()enbearer = steps.bearer_for(state, "ana", "correct horse")de boeken metstate.pop("books"), stuurGET /booksmet het token in de headerAuthorization, en controleer dat de status 500 is en dat nochb"Traceback"nochb"KeyError"in de body staat. Voerpython -m pytest -quit: deze test moet eerst falen.Zet in
handleinreading_server.pyde aanroep vanroutein eentry. Roep inexcept Exception:traceback.print_exc()aan, bouwjson_error(500, ...)met een vaste melding, zet decache-controlervan opno-store, en maak de body leeg als deHEADis. Houd de logregel na detry, zodat hij ook voor een 500 geschreven wordt.Voer
python -m pytest -quit tot het eindigt met21 passed, en voer daarnapython pl_sec.py other-readerenpython pl_sec.py guessinguit.Zorg in
SECURITY.mddat de rijen F8 en F9 elk een test noemen enclosedzeggen.
Klik daarna op Check my work. Die zet een reading_server.route en roept reading_server.handle(...) aan: het antwoord moet status 500 zijn met een vaste no-store, een lege body voor HEAD, een logregel met status 500, en een KeyboardInterrupt moet erdoorheen gaan. Hij herhaalt de controles van les 2 tot en met 5 met nieuwe gebruikers, boeken en tijden. Hij voert de vier tests van dit hoofdstuk uit, op naam geselecteerd, tegen servers die hij op de plaats van reading_server.handle en reading_server.sample_state zet: alle vier moeten slagen tegen een correcte, elk moet falen tegen een server met zijn eigen fout, en alle moeten falen tegen een server die elk verzoek weigert. Hij leest de rijen F8 en F9 van SECURITY.md. Niets uit je