feat(obs): nommer chaque requete, et rendre les pages d erreur audibles
OBS-005. Un 500 dans errors.log et les six lignes de app.log qui y menent n etaient relies que par leur horodatage — ce qui n est pas une relation des que le serveur traite plus d une requete a la fois. Et un utilisateur qui dit « ca a plante quand j ai clique sur enregistrer » ne donnait a personne de quoi chercher. Chaque requete recoit un identifiant, porte par toutes les lignes de journal qu elle produit, renvoye en X-Request-Id, et affiche sur la page 500 comme reference a citer. Il est **genere**, jamais lu depuis un en-tete entrant. Accepter celui du client serait pratique pour tracer a travers nginx, et permettrait aussi a n importe qui d ecrire du texte arbitraire — retours a la ligne compris — dans le fichier de journal. C est ainsi qu un journal cesse d etre une preuve. Il n y a de toute facon aucun proxy de confiance tant qu OPS-002 est ouvert. Le test correspondant assure sur l alphabet plutot qu en envoyant un retour a la ligne : le client de test de Werkzeug refuse d emettre un tel en-tete, donc l attaque ne peut meme pas etre construite par la, ce qui ne prouverait rien sur l application. **Defaut trouve en chemin, et repare.** Les cinq gabarits d erreur remplissent le bloc `content`, qui n existait que dans la branche authentifiee de la mise en page. Un visiteur deconnecte tombant sur une erreur — donc typiquement sur la page de connexion — recevait le logo, le selecteur de langue, et **aucun message**. Le code de statut etait bon, les journaux etaient bons, la page etait vide. Le <title> disait quand meme « 404 », ce qui explique en grande partie que personne ne l ait vu. Le bloc est desormais rendu dans les deux branches via self.content(), Jinja refusant deux blocs de meme nom. Un seul cote du if s execute, donc jamais de double rendu — et c est assure, pas suppose. 550 tests.
This commit is contained in:
+29
-1
@@ -249,6 +249,27 @@ def create_app(config=None):
|
||||
# unconditionally so templates can carry nonce="" beforehand.
|
||||
g.csp_nonce = secrets.token_urlsafe(16)
|
||||
|
||||
@app.before_request
|
||||
def assign_request_id():
|
||||
"""Give this request a name, so its log lines can be found (OBS-005).
|
||||
|
||||
Every record emitted while handling it carries this id — see
|
||||
RequestIdFilter — which is what turns "an error happened around
|
||||
14:32" into the six lines that led to it. It goes back in
|
||||
X-Request-Id and onto the 500 page, so that a report of "it broke
|
||||
when I clicked save" is enough to find the trace.
|
||||
|
||||
Generated here, never taken from an inbound header: with no trusted
|
||||
proxy settled (OPS-002), an accepted header lets any caller write
|
||||
arbitrary text — newlines included — into the log file.
|
||||
"""
|
||||
g.request_id = secrets.token_hex(8)
|
||||
|
||||
@app.after_request
|
||||
def expose_request_id(response):
|
||||
response.headers['X-Request-Id'] = g.get('request_id', '-')
|
||||
return response
|
||||
|
||||
@app.url_defaults
|
||||
def version_static_urls(endpoint, values):
|
||||
"""Stamp every static URL with the file's modification time.
|
||||
@@ -533,6 +554,12 @@ def create_app(config=None):
|
||||
# Roll back any failed database session
|
||||
db.session.rollback()
|
||||
|
||||
# The id is the only thing that connects a user saying "it broke when
|
||||
# I clicked save" to the stack trace in errors.log. It identifies one
|
||||
# request and nothing else — no session, no account, nothing an
|
||||
# attacker can use — so showing it costs nothing (OBS-005).
|
||||
request_id = g.get('request_id', '-')
|
||||
|
||||
if request.path.startswith('/users/disponibilities') or request.path.startswith(
|
||||
'/users/api/'
|
||||
):
|
||||
@@ -540,9 +567,10 @@ def create_app(config=None):
|
||||
{
|
||||
'error': 'Internal server error',
|
||||
'message': 'An unexpected error occurred. Please try again later.',
|
||||
'request_id': request_id,
|
||||
}
|
||||
), 500
|
||||
return render_template('errors/500.html'), 500
|
||||
return render_template('errors/500.html', request_id=request_id), 500
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_http_exception(error):
|
||||
|
||||
Reference in New Issue
Block a user