PowerDNS-Admin: Cannot log in with current cookies after long session.

I have setup the powerdns in runtime, after a while if I don’t delete the cookies i get the following errors:

[2023-03-12 14:52:54,305] [_internal.py:224] INFO - 10.0.0.1 - - [12/Mar/2023 14:52:54] "GET / HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2091, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.9/site-packages/werkzeug/middleware/proxy_fix.py", line 187, in __call__
    return self.app(environ, start_response)
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2076, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2072, in wsgi_app
    ctx.push()
  File "/usr/local/lib/python3.9/site-packages/flask/ctx.py", line 434, in push
    self.session = session_interface.open_session(self.app, self.request)
  File "/usr/local/lib/python3.9/site-packages/flask_session/sessions.py", line 535, in open_session
    if saved_session and saved_session.expiry <= datetime.utcnow():
TypeError: '<=' not supported between instances of 'NoneType' and 'datetime.datetime'
[2023-03-12 14:52:54,576] [_internal.py:224] INFO - 10.0.0.1 - - [12/Mar/2023 14:52:54] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
[2023-03-12 14:52:54,579] [_internal.py:224] INFO - 10.0.0.1 - - [12/Mar/2023 14:52:54] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
[2023-03-12 14:52:55,591] [_internal.py:224] INFO - 10.0.0.1 - - [12/Mar/2023 14:52:55] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -
[2023-03-12 14:52:55,664] [_internal.py:224] INFO - 10.0.0.1 - - [12/Mar/2023 14:52:55] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -

This makes me unable to log in or access the DNS, the workaround i have found is either clear all cookies or accessing in incognito.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 1
  • Comments: 25 (19 by maintainers)

Commits related to this issue

Most upvoted comments

I’m happy to report that some of the discussed and long known issues involving the flask-session dependency, seem to have been solved in the latest update of 0.6.0 which I am testing in the dev branch now.

https://github.com/PowerDNS-Admin/PowerDNS-Admin/pull/1748

it seems to me that this issue has been fixed with the MR #1461

Currently used flask-session has been prone to this error since 2017. According to the project issues, there are several complaints about the same issue and overall it’s related how the application destroys session. Also some concurrency has a part in this (multiple pod race condition). The currently maintained drop-in replacement flask-session2 has merged the fix to handle session data with NULL expire value, but not the root cause of avoiding inserting NULL values in first place.

Somehow a query with insert NULL will be issued. I suspect a race condition with using session.clear() in PDA code.

INSERT INTO sessions (session_id, data, expiry) 
VALUES ('session:f933fc61-0bfc-4d13-b2ee-ca2b4ce6388e', 'U\0\0\0\0\0\0\0}_csrf_token@e9242866c9a6e8eaeacbc4258deef1f657c7e7db5d6f272bdb47d27c..s.', NULL)

I made a simple test by removing excess line from clear_session() function, by letting flask-session native logout_user() do all the session invalidation tricks. Fun fact - the sessions don’t get broken anymore. I can’t reproduce the situation any more, as all session entries in backend database have proper “not NULL” values.

diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py
--- a/powerdnsadmin/routes/index.py
+++ b/powerdnsadmin/routes/index.py
 def clear_session():
     session.pop('google_token', None)
     session.pop('authentication_type', None)
     session.pop('remote_user', None)
-    session.clear()
     logout_user()

I can’t say, what was the initial purpose of adding session.clear() to the PDA code. Maybe this breaks something in the other end, but using both, session.clear() and flask sessions logout_user(), isn’t a great combination. As in previous releases only the ‘filesystem’ session store was being used, the concurrency never triggered an issue. According to the issues of flask-session, it’s common to all centralized backends (sql, memcache, redis…).

EDIT: With this patch, I started to notice exactly the same error as in #552 (let the session time out e.g. 10 minutes and don’t close the browser tab, but just click any link in PDA). [flask_seasurf.py:344] WARNING - Forbidden (CSRF token missing or incorrect.): /login But as the 403 is resolvable with a simple reload, it’s better solution than entire applications permanent 500.

@raunz Do you have some capacity to do a PR for this change to flask-session2 by chance?

On a side note … (don’t take me seriously though, I’m by far a Flask expert) … I looked into this issue a few days ago to see if I could get Flask-Session2 switched out and it required a newer version of Flask. Flask is pinned to 2.1.3 in requirements.txt, Flask-Session2 requires 2.2.2.

I then tried to update Flask to 2.2.2 but that appeared to break quite a number more things. We may need to update to Flask 2.2.2 to use Flask-Session2.

Here is the dependency issue output log on Ubuntu 22.04… (with just the Flask-Session2 change)

Collecting Flask-Session2
  Downloading Flask_Session2-1.3.0-py3-none-any.whl (14 kB)
  Downloading Flask_Session2-1.2.0-py3-none-any.whl (11 kB)
  Downloading Flask_Session2-1.1.0-py3-none-any.whl (10 kB)
  Downloading Flask_Session2-1.0.0-py3-none-any.whl (9.8 kB)
INFO: pip is looking at multiple versions of psycopg2 to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of zxcvbn to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of rcssmin to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of zipp to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of werkzeug to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of webcolors to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of rjsmin to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of requests to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of qrcode to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of pytz to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of pytimeparse to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of python3-saml to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of python-ldap to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of pytest to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of pyotp to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of pyasn1 to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of passlib to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of mysqlclient to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of lima to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of itsdangerous to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of gunicorn to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of flask-session-captcha to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of cssmin to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of cryptography to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of configobj to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of cffi to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of certifi to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of bravado-core to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of bcrypt to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of sqlalchemy to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of pyyaml to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of jinja2 to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of flask to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of flask-seasurf to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of flask-sslify to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of flask-sqlalchemy to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of flask-migrate to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of flask-mail to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of <Python from Requires-Python> to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of flask-login to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of flask-assets to determine which version is compatible with other requirements. This could take a while.
INFO: pip is looking at multiple versions of authlib to determine which version is compatible with other requirements. This could take a while.
ERROR: Cannot install -r requirements.txt (line 2), -r requirements.txt (line 23), -r requirements.txt (line 3), -r requirements.txt (line 4), -r requirements.txt (line 5), -r requirements.txt (line 6), -r requirements.txt (line 7), -r requirements.txt (line 8), -r requirements.txt (line 9) and Flask==2.1.3 because these package versions have conflicting dependencies.

The conflict is caused by:
    The user requested Flask==2.1.3
    flask-assets 2.0 depends on Flask>=0.8
    flask-login 0.6.2 depends on Flask>=1.0.4
    flask-mail 0.9.1 depends on Flask
    flask-migrate 2.5.3 depends on Flask>=0.9
    flask-sqlalchemy 2.5.1 depends on Flask>=0.10
    flask-sslify 0.1.5 depends on Flask
    flask-seasurf 1.1.1 depends on Flask
    flask-session-captcha 1.3.0 depends on Flask
    flask-session2 1.3.1 depends on Flask<3.0.0 and >=2.2.2
    The user requested Flask==2.1.3
    flask-assets 2.0 depends on Flask>=0.8
    flask-login 0.6.2 depends on Flask>=1.0.4
    flask-mail 0.9.1 depends on Flask
    flask-migrate 2.5.3 depends on Flask>=0.9
    flask-sqlalchemy 2.5.1 depends on Flask>=0.10
    flask-sslify 0.1.5 depends on Flask
    flask-seasurf 1.1.1 depends on Flask
    flask-session-captcha 1.3.0 depends on Flask
    flask-session2 1.3.0 depends on Flask<3.0.0 and >=2.2.2
    The user requested Flask==2.1.3
    flask-assets 2.0 depends on Flask>=0.8
    flask-login 0.6.2 depends on Flask>=1.0.4
    flask-mail 0.9.1 depends on Flask
    flask-migrate 2.5.3 depends on Flask>=0.9
    flask-sqlalchemy 2.5.1 depends on Flask>=0.10
    flask-sslify 0.1.5 depends on Flask
    flask-seasurf 1.1.1 depends on Flask
    flask-session-captcha 1.3.0 depends on Flask
    flask-session2 1.2.0 depends on Flask<3.0.0 and >=2.2.2
    The user requested Flask==2.1.3
    flask-assets 2.0 depends on Flask>=0.8
    flask-login 0.6.2 depends on Flask>=1.0.4
    flask-mail 0.9.1 depends on Flask
    flask-migrate 2.5.3 depends on Flask>=0.9
    flask-sqlalchemy 2.5.1 depends on Flask>=0.10
    flask-sslify 0.1.5 depends on Flask
    flask-seasurf 1.1.1 depends on Flask
    flask-session-captcha 1.3.0 depends on Flask
    flask-session2 1.1.0 depends on Flask<3.0.0 and >=2.2.2
    The user requested Flask==2.1.3
    flask-assets 2.0 depends on Flask>=0.8
    flask-login 0.6.2 depends on Flask>=1.0.4
    flask-mail 0.9.1 depends on Flask
    flask-migrate 2.5.3 depends on Flask>=0.9
    flask-sqlalchemy 2.5.1 depends on Flask>=0.10
    flask-sslify 0.1.5 depends on Flask
    flask-seasurf 1.1.1 depends on Flask
    flask-session-captcha 1.3.0 depends on Flask
    flask-session2 1.0.0 depends on Flask<3.0.0 and >=2.2.2

To fix this you could try to:
1. loosen the range of package versions you've specified
2. remove package versions to allow pip attempt to solve the dependency conflict

ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/topics/dependency-resolution/#dealing-with-dependency-conflicts

@tbe

I have experienced this issue with expired session. So, i would assume that here may be more then one issue with the same result

Did You clear the backend from stale “NULL” expiry sessions? The change to remove session.clear() doesn’t patch the TypeError occuring if the database contents are already bad. I cleared the database with the following and restarted PDA with patched powerdnsadmin/routes/index.py code.

 delete from sessions where expiry is null; 
 or 
 truncate sessions;

This is a bug in Flask-Session: https://github.com/fengsp/flask-session/issues/151

But there is hope, https://github.com/christopherpickering/flask-session2 has not the same issue, as it checks the session object before using it, and it should be a simple drop in replacement @AzorianMatt

@MrGeneration I haven’t replicated the problem yet myself, but it’s starting to sound like it’s an issue directly tied to the changeover for session storage, which would make complete sense as it changed with this new release.

Ergo, after upgrading, all existing sessions would become invalid and likely lead to this kind of exception even though it should be more properly handled in the library.

Just wanted to say that I encountered the same issue in the following two scenarious:

  • the session timed out without proper logging out (above error appears)
  • logged in with valid session, updated to 0.4 and refreshed the page (above error appears)

Every time this issue appears removing the site’s cookies is enough. The issue does not seem to occur when you’re logging out cleanly.

This was a migrated installation via docker with MariaDB backend.