Mercurial > p > roundup > code
diff roundup/cgi/client.py @ 7809:be6cb2e0d471
feat: add support for rotating jwt keys
This allows jwt_secret to have multiple ',' separated secrets. The
first/leftmost should be used to sign new JWTs. All of them are used
(starting from left/newest) to try to verify a JWT.
If the first secret is < 32 chars in length JWTs are disabled. If any
of the other secrets are < 32 chars, the configuration code causes the
software to exit. This prevents insecure (too short) secrets from
being used.
Updated doc examples and tests.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Thu, 14 Mar 2024 19:04:19 -0400 |
| parents | cc4b11ab2f22 |
| children | 928c20d4344b |
line wrap: on
line diff
--- a/roundup/cgi/client.py Wed Mar 13 18:25:59 2024 -0400 +++ b/roundup/cgi/client.py Thu Mar 14 19:04:19 2024 -0400 @@ -1111,23 +1111,42 @@ self.setHeader("WWW-Authenticate", "Basic") raise LoginError('Support for jwt disabled.') - secret = self.db.config.WEB_JWT_SECRET - if len(secret) < 32: + + # If first ',' separated token is < 32, jwt is disabled. + # If second or later tokens are < 32 chars, the config system + # stops the tracker from starting so insecure tokens can not + # be used. + if len(self.db.config.WEB_JWT_SECRET[0]) < 32: # no support for jwt, this is fine. self.setHeader("WWW-Authenticate", "Basic") raise LoginError('Support for jwt disabled by admin.') - try: # handle jwt exceptions - token = jwt.decode(challenge, secret, - algorithms=['HS256'], - audience=self.db.config.TRACKER_WEB, - issuer=self.db.config.TRACKER_WEB) - except jwt.exceptions.InvalidTokenError as err: - self.setHeader("WWW-Authenticate", "Basic, Bearer") - self.make_user_anonymous() - raise LoginError(str(err)) - - return (token) + last_error = "Unknown error validating bearer token." + + for secret in self.db.config.WEB_JWT_SECRET: + try: # handle jwt exceptions + token = jwt.decode(challenge, secret, + algorithms=['HS256'], + audience=self.db.config.TRACKER_WEB, + issuer=self.db.config.TRACKER_WEB) + return (token) + + except jwt.exceptions.InvalidSignatureError as err: + # Try more signatures. + # If all signatures generate InvalidSignatureError, + # we exhaust the loop and last_error is used to + # report the final (but not only) InvalidSignatureError + last_error = str(err) # preserve for end of loop + except jwt.exceptions.InvalidTokenError as err: + self.setHeader("WWW-Authenticate", "Basic, Bearer") + self.make_user_anonymous() + raise LoginError(str(err)) + + # reach here only if no valid signature was found + self.setHeader("WWW-Authenticate", "Basic, Bearer") + self.make_user_anonymous() + raise LoginError(last_error) + def determine_user(self, is_api=False): """Determine who the user is"""
