pgadmin4 FIPS compatibility
Currently running: Oracle Linux 8, FIPS mode enabled, pgadmin4 8.11-1, which includes ItsDangerous 2.2.0.
I am unable to start pgadmin4 on a FIPS-enabled RHEL-based Linux host because a dependency of pgadmin4 called ItsDangerous is using the default hashlib.sha1, which is blocked by FIPS.
If I make the following change, I can run pgadmin4 on a FIPS-enabled Linux host.
/usr/pgadmin4/venv/lib/python3.9/site-packages/itsdangerous/signer.py
- self.digest_method: t.Any = digest_method
+ self.digest_method: t.Any = hashlib.sha256
There are two issues with this approach.
- I do not know what other functions of pgadmin4 this may affect (possibly adversely).
- When the pgadmin4 package is patched through regular updates, the changes are lost and must be repeated to restore FIPS compatibility.
Will you please consider releasing a FIPS compatible version of pgadmin4?
This issue upstream sounds related: https://github.com/pallets/itsdangerous/issues/375
For devs - need to override the digest_method function in ManagedSessionInterface and provide a config variable to customise it with default value same as Flask. Check flask/sessions.py file for more.
@rojehy What sort of error message are you seeing?
@benjaminjb There is no error message. On startup, the application shows a grey window with spinning cursor (seemingly forever) instead of the splash screen following by the application launching.
@rojehy can you access the pgadmin logs? There might be errors there.
(Or in the developer tools in the browser.)
@adityatoshniwal I've got a question around your suggestion:
I'm pip installing pgadmin4 in a container running Red Hat Python 3.11.
Flask 3.0.3 and itsdangerous 2.2.0 have _lazy_sha1 as a default digest method rather than a direct call to hashlib.sha1; but on a FIPS environment, I was getting the error,
Unsupported digestmod <function _lazy_sha1 ...
(I noticed that error I had was also reported here, and I didn't see it resolved there.)
Seems like my env was having trouble unwrapping that _lazy_sha1 func (or put another way, OpenSSL wasn't recognizing that as a digest func). So I overrode the default_digest_method in a config_X.py file for a func that would run in this env:
from itsdangerous import signer
import hashlib
signer.Signer.default_digest_method = staticmethod(hashlib.sha1)
There's no pgAdmin native way to do that is there? I.e., there's no PGADMIN_<ENV VAR> I could set to change the digest method used by itsdangerous/Flask? Or is there another way to hook in here to change that through pgAdmin itself rather than through itsdangerous/Flask?
There's no pgAdmin native way to do that is there? I.e., there's no PGADMIN_ I could set to change the digest method used by itsdangerous/Flask? Or is there another way to hook in here to change that through pgAdmin itself rather than through itsdangerous/Flask?
Yes the idea to provide a config PGADMIN_ to allow changing the hashlib algo used. Exactly like you did. Currently, it is not possible.
There's no pgAdmin native way to do that is there? I.e., there's no PGADMIN_ I could set to change the digest method used by itsdangerous/Flask? Or is there another way to hook in here to change that through pgAdmin itself rather than through itsdangerous/Flask?
Yes the idea to provide a config
PGADMIN_to allow changing the hashlib algo used. Exactly like you did. Currently, it is not possible.
Do you think it's worth me looking into making a PR around that?
@adityatoshniwal (and apologies for pinging you all the time)
Would something like this work: https://github.com/pgadmin-org/pgadmin4/compare/master...benjaminjb:pgadmin4:allow_digest_method_setting?expand=1 ?
(Tried to do a minimal adjustment.)
I'm also curious because the error I was getting was, at its heart, from itsdangerous.
Hi @benjaminjb,
SessionInterface is already extended in ManagedSessionInterface, so code changes would go in ManagedSessionInterface. Secondly, it can be more inline with how flask/sessions.py is doing.
Also, config name could be SESSION_DIGEST_METHOD. You can raise a PR.
Hi @adityatoshniwal just to make sure I understand, when you say to make this more inline with how flask/sessions.py is doing things, do you mean something like this _lazy_sha1 func: https://github.com/pallets/flask/blob/main/src/flask/sessions.py#L290-L295 ?
def _lazy_sha1(string: bytes = b"") -> t.Any:
"""Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include
SHA-1, in which case the import and use as a default would fail before the
developer can configure something else.
"""
return hashlib.sha1(string)
And then override the ManagedSessionInterface.default_digest_method func?
@benjaminjb We could create a function lazy_digest_method similar to lazy_sha1 which will return based on pgAdmin config. And then set digest_method = staticmethod(lazy_digest_method)
ItsDangerous maintainer here, sorry for the confusion. FIPS is quite annoying, and the lazy function was my best answer to keeping the completely safe SHA-1 where possible, while allowing overrides. As you've identified, you can either write your own lazy function to configure it further, or just outright assign SHA-256 or something. The reason I didn't change the default is that it would invalidate all existing cookies across all Flask apps, and SHA-256 produces much longer hashes than SHA-1 for no benefit given how it's used. I don't know how pgadmin uses cookies, but just setting a newer hash may be acceptable vs the complexity of adding config.
I'd also suggest reaching out to Oracle and RedHat, since they're the one implementing FIPS (and going beyond by disabling SHA-1). They can patch their install of pgadmin, just as they maintain patches for the rest of the software they distribute.
ItsDangerous maintainer here, sorry for the confusion. FIPS is quite annoying, and the lazy function was my best answer to keeping the completely safe SHA-1 where possible, while allowing overrides. As you've identified, you can either write your own lazy function to configure it further, or just outright assign SHA-256 or something. The reason I didn't change the default is that it would invalidate all existing cookies across all Flask apps, and SHA-256 produces much longer hashes than SHA-1 for no benefit given how it's used. I don't know how pgadmin uses cookies, but just setting a newer hash may be acceptable vs the complexity of adding config.
pgAdmin is supported on many platforms, we do not wish to change the default hash method. And that is the reason, we're allowing it to be configurable. Having lazy function can help for hash libs which may not be supported by the OS, just like for SHA1
@davidism @adityatoshniwal It appears resolving this issue will first require the pallets-eco/flask-wtf package to provide a configurable digest_method argument to URLSafeTimedSerializer.
I'm not familiar enough with python to know if it's possible for pgadmin4 to override the Signer.default_digest_method. I've made the following change to get around this issue when running pgadmin4 on a ubi9/httpd-24 image.
flask_wtf/csrf.py lines 48-49 (this change resolves the issue for pgadmin4)
if field_name not in g:
- s = URLSafeTimedSerializer(secret_key, salt="wtf-csrf-token")
+ s = URLSafeTimedSerializer(secret_key, salt="wtf-csrf-token", signer_kwargs={'digest_method': hashlib.sha256})
A PR to resolve this issue for flask-wtf should provide a configurable method to replace hashlib.sha1. Within csrf.py these lines appear to rely on hashlib.sha1 as well: line 104, should pass in signer_kwargs that specify desired digeset_method.
s = URLSafeTimedSerializer(secret_key, salt="wtf-csrf-token")
line 52 & 57
session[field_name] = hashlib.sha1(os.urandom(64)).hexdigest()
/var/log/pgadmin/pgadmin4.log
ERROR pgadmin: Unsupported digestmod <function _lazy_sha1 at 0x7fce7d73c3a0>
Traceback (most recent call last):
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/flask/app.py", line 917, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/flask/app.py", line 902, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/flask_security/decorators.py", line 489, in decorated
return current_app.ensure_sync(fn)(*args, **kwargs)
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/flask_security/views.py", line 238, in login
return _security.render_template(
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/flask_security/utils.py", line 1172, in default_render_template
return render_template(*args, **kwargs)
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/flask/templating.py", line 150, in render_template
return _render(app, template, context)
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/flask/templating.py", line 131, in _render
rv = template.render(context)
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/jinja2/environment.py", line 1295, in render
self.environment.handle_exception()
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/jinja2/environment.py", line 942, in handle_exception
raise rewrite_traceback_stack(source=source)
File "/usr/pgadmin4/web/pgadmin/templates/security/login_user.html", line 16, in top-level template code
{% set page_props = {
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/flask_wtf/csrf.py", line 56, in generate_csrf
token = s.dumps(session[field_name])
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/itsdangerous/serializer.py", line 317, in dumps
rv = self.make_signer(salt).sign(payload)
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/itsdangerous/timed.py", line 51, in sign
return value + sep + self.get_signature(value)
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/itsdangerous/signer.py", line 219, in get_signature
sig = self.algorithm.get_signature(key, value)
File "/usr/pgadmin4/venv/lib64/python3.9/site-packages/itsdangerous/signer.py", line 63, in get_signature
mac = hmac.new(key, msg=value, digestmod=self.digest_method)
File "/usr/lib64/python3.9/hmac.py", line 189, in new
return HMAC(key, msg, digestmod)
File "/usr/lib64/python3.9/hmac.py", line 60, in __init__
self._init_hmac(key, msg, digestmod)
File "/usr/lib64/python3.9/hmac.py", line 69, in _init_hmac
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
_hashlib.UnsupportedDigestmodError: Unsupported digestmod <function _lazy_sha1 at 0x7fce7d73c3a0>
I created an PR for this: https://github.com/pgadmin-org/pgadmin4/pull/8939
Hi All,
The issue has been fixed. Could someone please test the nightly build and let us know if the fix works as expected?
Hi All,
The issue has been fixed. Could someone please test the nightly build and let us know if the fix works as expected?
I tested the nightly build and it ran successfully on a FIPs-enabled system without any modification. Thank you. Tested: https://www.postgresql.org/ftp/pgadmin/pgadmin4/snapshots/2025-07-15/yum/