Gracefully handle a missing crypt key in the Query Tool connection endpoints. (#10065)

After a backend/pod restart the in-memory crypt key is gone, so
manager.connection() raises CryptKeyMissing. The new-connection
endpoints (_check_server_connection_status and get_new_connection_*)
swallowed it in a broad "except Exception", logged a full ERROR
traceback, and returned a generic error the client cannot recognise.
The standard recovery (a 503 CRYPTKEY_MISSING response that the client
uses to transparently re-establish the key and retry) therefore never
fired, leaving a spurious "Crypt key is missing" message in the Query
Tool and noisy tracebacks in the log.

Re-raise CryptKeyMissing (along with ConnectionLost /
SSHTunnelConnectionLost) before the generic handler, matching the
pattern already used by the query execution path, so these endpoints
emit the standard CRYPTKEY_MISSING response and the client recovers
gracefully.

Closes #10027
This commit is contained in:
Dave Page
2026-06-12 22:09:03 +05:30
committed by GitHub
parent 922f06c7d2
commit f6961bce49
3 changed files with 38 additions and 11 deletions
+1
View File
@@ -59,4 +59,5 @@ Bug fixes
| `Issue #9984 <https://github.com/pgadmin-org/pgadmin4/issues/9984>`_ - Fix the Docker entrypoint mishandling a quoted PGADMIN_CONFIG_CONFIG_DATABASE_URI, which caused a SQLAlchemy parse error and silently skipped PGADMIN_DEFAULT_EMAIL/PASSWORD setup.
| `Issue #9987 <https://github.com/pgadmin-org/pgadmin4/issues/9987>`_ - Fix "AttributeError: 'PgAdmin' object has no attribute 'login_manager'" crash when running setup.py user-management commands (add-user, update-user) from the CLI.
| `Issue #9988 <https://github.com/pgadmin-org/pgadmin4/issues/9988>`_ - Provide an actionable error when 'openid' is in OAUTH2_SCOPE but OAUTH2_SERVER_METADATA_URL is not set, instead of a cryptic Authlib failure.
| `Issue #10027 <https://github.com/pgadmin-org/pgadmin4/issues/10027>`_ - Fix the spurious "Crypt key is missing" error and logged traceback in the Query Tool new-connection endpoints after a backend restart, by surfacing it as the standard CRYPTKEY_MISSING response so the client recovers transparently.
| `Issue #10059 <https://github.com/pgadmin-org/pgadmin4/issues/10059>`_ - Fix the generated SQL for editing a SQL-language function/procedure whose body contains the word "return" (e.g. a RETURNING clause), which was wrongly treated as a SQL-standard body and produced a statement without the AS $BODY$ wrapper.
+10
View File
@@ -2381,6 +2381,8 @@ def _check_server_connection_status(sgid, sid=None):
}
)
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
raise
except Exception as e:
current_app.logger.exception(e)
return make_json_response(
@@ -2447,6 +2449,8 @@ def get_new_connection_data(sgid=None, sid=None):
}
)
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
raise
except Exception as e:
current_app.logger.exception(e)
return make_json_response(
@@ -2521,6 +2525,8 @@ def get_new_connection_database(sgid, sid=None):
}
}
)
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
raise
except Exception as e:
current_app.logger.exception(e)
return make_json_response(
@@ -2587,6 +2593,8 @@ def get_new_connection_user(sgid, sid=None):
}
}
)
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
raise
except Exception as e:
current_app.logger.exception(e)
return make_json_response(
@@ -2651,6 +2659,8 @@ def get_new_connection_role(sgid, sid=None):
}
}
)
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
raise
except Exception as e:
current_app.logger.exception(e)
return make_json_response(
@@ -6,11 +6,11 @@
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
from unittest.mock import patch
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.test_setup import config_data
from regression.python_test_utils import test_utils as utils
from pgadmin.utils.exception import CryptKeyMissing
class TestNewConnectionDialog(BaseTestGenerator):
@@ -19,15 +19,19 @@ class TestNewConnectionDialog(BaseTestGenerator):
('New connection dialog',
dict(
url="/sqleditor/new_connection_dialog/",
is_positive_test=True,
mocking_required=False,
is_connect_server=False,
test_data={},
mock_data={},
crypt_key_missing=False,
expected_data={
"status_code": 200
}
)),
('New connection dialog when the crypt key is missing',
dict(
url="/sqleditor/new_connection_dialog/",
crypt_key_missing=True,
expected_data={
"status_code": 503
}
)),
]
def setUp(self):
@@ -43,8 +47,20 @@ class TestNewConnectionDialog(BaseTestGenerator):
return response
def runTest(self):
if self.is_positive_test:
if self.crypt_key_missing:
# When the crypt key is missing (e.g. the backend was restarted),
# the endpoint must surface CryptKeyMissing as a 503
# CRYPTKEY_MISSING response so the client can transparently
# recover, rather than swallowing it into a generic error and
# logging a traceback. See issue #10027.
with patch('pgadmin.utils.driver.psycopg3.server_manager.'
'ServerManager.connection',
side_effect=CryptKeyMissing()):
response = self.new_connection()
self.assertEqual(response.status_code,
self.expected_data['status_code'])
self.assertIn('CRYPTKEY_MISSING', response.data.decode('utf-8'))
else:
response = self.new_connection()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)
self.assertEqual(response.status_code,
self.expected_data['status_code'])