0%

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

RouteWie mag hem aanroepenEen weigering ziet eruit alsGelogd als
GET, HEAD /aboutiedereen405 voor een andere -
GET, HEAD /books, /books/{id}een token dat geverifieerd wordt; alleen de eigen boeken van de aanroeper401 zonder token; 404 voor een boek dat ontbreekt of van een ander isde gebruiker van het token
POST /books, PUT en DELETE /books/{id}een token waarvan de gebruiker mag schrijven; alleen de eigen boeken van de aanroeper401; 403 voor een account dat niet mag schrijven; 404 voor een boek dat niet van de aanroeper isde gebruiker van het token
POST /loginBasic-inloggegevens401; 429 met Retry-After als de naam of de hele dienst over zijn limiet isde gebruiker na een juist wachtwoord, anders -
POST /tokenseen met een gebruiker401de gebruiker van de sessie
GET /me, POST /prefsiedereengeende gebruiker van de sessie als het verzoek een levende sessie leest, anders -
POST /logoutiedereengeen-

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 de af naar standaardfout, waar de beheerder leest. De traceback is 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-control gezet op no-store.

  • Een lege body als de methode HEAD is. Dat is de regel uit with Python, die route deze 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 uit een verzoek bevatten, dus de mensen die standaardfout mogen lezen, moeten weinig zijn.

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 , de fout ontsnapt uit 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.

  1. Schrijf in tests/test_attacks.py test_attack_error_body_shows_no_traceback(). Verwijder met state = steps.keyed_state() en bearer = steps.bearer_for(state, "ana", "correct horse") de boeken met state.pop("books"), stuur GET /books met het token in de header Authorization, en controleer dat de status 500 is en dat noch b"Traceback" noch b"KeyError" in de body staat. Voer python -m pytest -q uit: deze test moet eerst falen.

  2. Zet in handle in reading_server.py de aanroep van route in een try. Roep in except Exception: traceback.print_exc() aan, bouw json_error(500, ...) met een vaste melding, zet de cache-control ervan op no-store, en maak de body leeg als de HEAD is. Houd de logregel na de try, zodat hij ook voor een 500 geschreven wordt.

  3. Voer python -m pytest -q uit tot het eindigt met 21 passed, en voer daarna python pl_sec.py other-reader en python pl_sec.py guessing uit.

  4. Zorg in SECURITY.md dat de rijen F8 en F9 elk een test noemen en closed zeggen.

Klik daarna op Check my work. Die zet een die een fout geeft op de plaats van reading_server.route en roept reading_server.handle(...) aan: het antwoord moet status 500 zijn met een vaste -body die niets van de fout laat zien, no-store, een lege body voor HEAD, een logregel met status 500, en een op standaardfout; 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 wordt beoordeeld.