Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow login in maintenance mode #506

Closed
jakubkrysl opened this issue May 28, 2021 · 1 comment · Fixed by #743
Closed

Allow login in maintenance mode #506

jakubkrysl opened this issue May 28, 2021 · 1 comment · Fixed by #743
Labels
type: bug Something isn't working as expected

Comments

@jakubkrysl
Copy link

Environment

  • Python version: 3.6.8
  • Netbox version: 2.9.9

Note: I'm in middle of migration from Netbox v2.9.9 so I cannot test this on Nautobot yet, but I have not found any change that would fix this already, so I believe this bug is in Nautobot too. If it's already fixed, please disregard.

Steps to Reproduce

  1. (probably) Have LOGIN_TIMEOUT set to something small and have user with expired login
  2. set MAINTENACE_MODE=True with LDAP
  3. Set PostgreSQL DB to read-only
  4. Restart
  5. Try to login with the user

Expected Behavior

Being able to login as MAINTENANCE_MODE=True should allow for login without updating DB (https://nautobot.readthedocs.io/en/latest/configuration/optional-settings/#maintenance_mode)

Observed Behavior

Unable to login, app tries UPDATE on read-only DB. Following traceback appears in logs:

Click to expand traceback

[2021-05-28 16:20:29,122] ERROR: Internal Server Error: /login/
Traceback (most recent call last):
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django_prometheus/db/common.py", line 68, in execute
    return super(CursorWrapper, self).execute(*args, **kwargs)
psycopg2.errors.ReadOnlySqlTransaction: cannot execute UPDATE in a read-only transaction
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/views/generic/base.py", line 73, in view
    return self.dispatch(request, *args, **kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/views/decorators/debug.py", line 89, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/opt/netbox/netbox/users/views.py", line 36, in dispatch
    return super().dispatch(*args, **kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/views/generic/base.py", line 101, in dispatch
    return handler(request, *args, **kwargs)
  File "/opt/netbox/netbox/users/views.py", line 53, in post
    if form.is_valid():
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/forms/forms.py", line 177, in is_valid
    return self.is_bound and not self.errors
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/forms/forms.py", line 172, in errors
    self.full_clean()
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/forms/forms.py", line 375, in full_clean
    self._clean_form()
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/forms/forms.py", line 402, in _clean_form
    cleaned_data = self.clean()
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/contrib/auth/forms.py", line 215, in clean
    self.user_cache = authenticate(self.request, username=username, password=password)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/contrib/auth/__init__.py", line 73, in authenticate
    user = backend.authenticate(request, **credentials)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django_auth_ldap/backend.py", line 153, in authenticate
    user = self.authenticate_ldap_user(ldap_user, password)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django_auth_ldap/backend.py", line 211, in authenticate_ldap_user
    return ldap_user.authenticate(password)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django_auth_ldap/backend.py", line 355, in authenticate
    self._get_or_create_user()
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django_auth_ldap/backend.py", line 632, in _get_or_create_user
    self._user.save()
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/contrib/auth/base_user.py", line 67, in save
    super().save(*args, **kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/base.py", line 751, in save
    force_update=force_update, update_fields=update_fields)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/base.py", line 789, in save_base
    force_update, using, update_fields,
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/base.py", line 870, in _save_table
    forced_update)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/base.py", line 923, in _do_update
    return filtered._update(values) > 0
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/query.py", line 803, in _update
    return query.get_compiler(self.db).execute_sql(CURSOR)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/sql/compiler.py", line 1515, in execute_sql
    cursor = super().execute_sql(result_type)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/sql/compiler.py", line 1154, in execute_sql
    cursor.execute(sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/cacheops/transaction.py", line 93, in execute
    result = self._no_monkey.execute(self, sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django_prometheus/db/common.py", line 68, in execute
    return super(CursorWrapper, self).execute(*args, **kwargs)
django.db.utils.InternalError: cannot execute UPDATE in a read-only transaction

Note: If I set AUTH_LDAP_ALWAYS_UPDATE_USER = False, I get this:

Click to expand traceback

[2021-05-28 17:18:41,977] ERROR: Internal Server Error: /login/
Traceback (most recent call last):
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django_prometheus/db/common.py", line 68, in execute
    return super(CursorWrapper, self).execute(*args, **kwargs)
psycopg2.errors.ReadOnlySqlTransaction: cannot execute INSERT in a read-only transaction
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/views/generic/base.py", line 73, in view
    return self.dispatch(request, *args, **kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/views/decorators/debug.py", line 89, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/opt/netbox/netbox/users/views.py", line 36, in dispatch
    return super().dispatch(*args, **kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/views/generic/base.py", line 101, in dispatch
    return handler(request, *args, **kwargs)
  File "/opt/netbox/netbox/users/views.py", line 63, in post
    auth_login(request, form.get_user())
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/contrib/auth/__init__.py", line 108, in login
    request.session.cycle_key()
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/contrib/sessions/backends/base.py", line 326, in cycle_key
    self.create()
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/contrib/sessions/backends/db.py", line 55, in create
    self.save(must_create=True)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/contrib/sessions/backends/db.py", line 87, in save
    obj.save(force_insert=must_create, force_update=not must_create, using=using)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/base.py", line 751, in save
    force_update=force_update, update_fields=update_fields)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/base.py", line 789, in save_base
    force_update, using, update_fields,
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/base.py", line 892, in _save_table
    results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/base.py", line 932, in _do_insert
    using=using, raw=raw,
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/query.py", line 1249, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/models/sql/compiler.py", line 1395, in execute_sql
    cursor.execute(sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/cacheops/transaction.py", line 93, in execute
    result = self._no_monkey.execute(self, sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/netbox/venv/lib64/python3.6/site-packages/django_prometheus/db/common.py", line 68, in execute
    return super(CursorWrapper, self).execute(*args, **kwargs)
django.db.utils.InternalError: cannot execute INSERT in a read-only transaction

Workaround

I found a way to be able to log in without updating the DB (e.g. the DB stayed read-only with the expired user). I used django-no-last-login, so the following steps were made during the deployment of app to maintenance mode (source: https://stackoverflow.com/a/49172299):

  1. add django-no-last-login==0.1.0 to requirements.txt
  2. Add this to settings.py after INSTALLED_APPS:
SESSION_ENGINE = 'django.contrib.sessions.backends.file'

INSTALLED_APPS += [
    'nolastlogin',
]
NO_UPDATE_LAST_LOGIN = True

So having this applied if settings.MAINTENANCE_MODE == True should allow for logins against read-only DB.

@itdependsnetworks has more information regarding my environment

@jathanism
Copy link
Contributor

jathanism commented Jun 4, 2021

This is an unfortunate side effect of database-backed sessions and for this and many other reasons we have discussed just moving to Redis-backed sessions as the default, which would be even simpler than having to change the sessions to file-based (which would not work w/ multi-front-end deployment).

Thanks for this suggestion!

@jathanism jathanism added status: under review type: feature Introduction of new or enhanced functionality to the application labels Jun 4, 2021
@jedelman8 jedelman8 added type: bug Something isn't working as expected and removed status: under review type: feature Introduction of new or enhanced functionality to the application labels Jun 17, 2021
glennmatthews added a commit that referenced this issue Aug 3, 2021
hellerve added a commit to hellerve/nautobot that referenced this issue Aug 5, 2021
* develop: (110 commits)
  Add release-note for nautobot#506
  Adding docs and checks for MAINTENANCE_MODE (nautobot#743)
  Add release-note for nautobot#744
  Add Celery DjangoFixup explicitly. Fixes nautobot#744 (nautobot#763)
  Added release-notes for nautobot#759
  Correct Power Feed and Power Panel Navigation (nautobot#760)
  Added release-notes for nautobot#738
  .Fix issue nautobot#738 protect *.env files (nautobot#741)
  Added release-notes for nautobot#681
  Adding AWS S3 Example (nautobot#681)
  Nautobot Application Stack (nautobot#713)
  Add release-note for nautobot#727
  Bump version to 1.1.1-beta.1
  Fix GraphQLQuery run API endpoint. Fixes nautobot#727 (nautobot#730)
  Implement `FileProxy` and `FileAttachment` objects (nautobot#712)
  Update computed field GraphQL schema generator to use correct arg name (nautobot#721)
  Added changelog for nautobot#372
  Add support for custom fields in tables (nautobot#469)
  Bump version for v1.1.0 and prep release notes.
  Added changelog for nautobot#717
  ...
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: bug Something isn't working as expected
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants