)]}'
{"id":"openstack%2Foslo.middleware~1002593","triplet_id":"openstack%2Foslo.middleware~master~I345847b65cc8804cf329d8f31bbd3fa3d835f246","project":"openstack/oslo.middleware","branch":"master","topic":"pbkdf2-basic-auth","hashtags":[],"change_id":"I345847b65cc8804cf329d8f31bbd3fa3d835f246","subject":"Support PBKDF2 password digests in basic auth","status":"NEW","created":"2026-08-27 08:47:56.000000000","updated":"2026-08-27 09:41:15.000000000","submit_type":"MERGE_IF_NECESSARY","mergeable":true,"submittable":false,"total_comment_count":0,"unresolved_comment_count":0,"has_review_started":true,"meta_rev_id":"14d4811e5aaf2818df9b12df600f7a50aef60190","_number":1002593,"virtual_id_number":1002593,"owner":{"_account_id":7102,"name":"Thomas Bechtold","email":"thomas.bechtold@chainguard.dev","username":"toabctl"},"actions":{},"labels":{"Verified":{"recommended":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"all":[{"tag":"autogenerated:zuul:check","value":1,"date":"2026-08-27 09:41:15.000000000","permitted_voting_range":{"min":-2,"max":2},"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]}],"values":{"-2":"Fails","-1":"Doesn\u0027t seem to work"," 0":"No score","+1":"Works for me","+2":"Verified"},"description":"","value":1,"default_value":0,"optional":true},"Code-Review":{"all":[{"value":0,"permitted_voting_range":{"min":-1,"max":1},"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]}],"values":{"-2":"Do not merge","-1":"This patch needs further work before it can be merged"," 0":"No score","+1":"Looks good to me, but someone else must approve","+2":"Looks good to me (core reviewer)"},"description":"","default_value":0,"optional":true},"Workflow":{"all":[{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]}],"values":{"-1":"Work in progress"," 0":"Ready for reviews","+1":"Approved"},"description":"","default_value":0,"optional":true},"Backport-Candidate":{"all":[{"value":0,"permitted_voting_range":{"min":-1,"max":1},"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]}],"values":{"-2":"Do Not Backport","-1":"Not A Backport Candidate"," 0":"Backport Review Needed","+1":"Proposed Backport","+2":"Should Backport"},"description":"","default_value":0,"optional":true}},"removable_reviewers":[],"reviewers":{"REVIEWER":[{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]}]},"pending_reviewers":{},"reviewer_updates":[{"updated":"2026-08-27 09:41:15.000000000","updated_by":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"reviewer":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"state":"REVIEWER"}],"messages":[{"id":"6c53219a647f26d9779c841df011f123cc82de39","tag":"autogenerated:gerrit:newPatchSet","author":{"_account_id":7102,"name":"Thomas Bechtold","email":"thomas.bechtold@chainguard.dev","username":"toabctl"},"date":"2026-08-27 08:47:56.000000000","message":"Uploaded patch set 1.","accounts_in_message":[],"_revision_number":1},{"id":"1bf5b0e1661a7fbe51103585682bff85bf4ad8bd","tag":"autogenerated:gerrit:newPatchSet","author":{"_account_id":7102,"name":"Thomas Bechtold","email":"thomas.bechtold@chainguard.dev","username":"toabctl"},"date":"2026-08-27 08:48:19.000000000","message":"Uploaded patch set 2: Commit message was updated.","accounts_in_message":[],"_revision_number":2},{"id":"14d4811e5aaf2818df9b12df600f7a50aef60190","tag":"autogenerated:zuul:check","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2026-08-27 09:41:15.000000000","message":"Patch Set 2: Verified+1\n\nBuild succeeded (check pipeline).\nhttps://zuul.opendev.org/t/openstack/buildset/0c732b81700944f4b6b0024ad68a179e\n\n- test-release-openstack https://zuul.opendev.org/t/openstack/build/73242705a4424a469670c7220576236a : SUCCESS in 2m 26s\n- requirements-check https://zuul.opendev.org/t/openstack/build/3307230eb2484d2192ce87a742d79c95 : SUCCESS in 2m 39s\n- tempest-full-py3 https://zuul.opendev.org/t/openstack/build/c8fe59ff43fa4a1dac8e38d159e9ff6e : SUCCESS in 49m 36s\n- openstack-tox-pep8 https://zuul.opendev.org/t/openstack/build/40776fdfb7a14f6ab3561b1e4a59d1fd : SUCCESS in 2m 11s\n- openstack-tox-py311 https://zuul.opendev.org/t/openstack/build/0bedee566aa04d2cbec900ced28ee44e : SUCCESS in 3m 23s\n- openstack-tox-py313 https://zuul.opendev.org/t/openstack/build/135f61a7ad714e0c90239a6a34fdf8a8 : SUCCESS in 4m 47s\n- openstack-tox-py314 https://zuul.opendev.org/t/openstack/build/37d257b39bf047379513048155163cd2 : SUCCESS in 5m 49s\n- openstack-tox-py315 https://zuul.opendev.org/t/openstack/build/37a8b2a2ea654ea6ad0dec4295d262b9 : SUCCESS in 27m 44s (non-voting)\n- openstack-tox-docs https://zuul.opendev.org/t/openstack/build/e48cebdb25a1436d8a88e4c64a977110 : SUCCESS in 2m 50s\n- build-openstack-releasenotes https://zuul.opendev.org/t/openstack/build/27d398b9853f430cae659892787963df : SUCCESS in 1m 56s","accounts_in_message":[],"_revision_number":2}],"current_revision_number":2,"current_revision":"b743f9da5889c76be2ded0d6022d3d4691e16eca","revisions":{"1037ee9d11ef320e35a16f318cbdcf4f42df8d5c":{"kind":"REWORK","_number":1,"created":"2026-08-27 08:47:56.000000000","uploader":{"_account_id":7102,"name":"Thomas Bechtold","email":"thomas.bechtold@chainguard.dev","username":"toabctl"},"ref":"refs/changes/93/1002593/1","fetch":{"anonymous http":{"url":"https://review.opendev.org/openstack/oslo.middleware","ref":"refs/changes/93/1002593/1","commands":{"Checkout":"git fetch https://review.opendev.org/openstack/oslo.middleware refs/changes/93/1002593/1 \u0026\u0026 git checkout FETCH_HEAD","Cherry Pick":"git fetch https://review.opendev.org/openstack/oslo.middleware refs/changes/93/1002593/1 \u0026\u0026 git cherry-pick FETCH_HEAD","Format Patch":"git fetch https://review.opendev.org/openstack/oslo.middleware refs/changes/93/1002593/1 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD","Pull":"git pull https://review.opendev.org/openstack/oslo.middleware refs/changes/93/1002593/1"}}},"commit":{"parents":[{"commit":"464357252b64453aafec004b8c08983907ac7c86","subject":"Merge \"Use typing objects directly\"","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/oslo.middleware/commit/464357252b64453aafec004b8c08983907ac7c86"}]}],"author":{"name":"Thomas Bechtold","email":"thomasbechtold@jpberlin.de","date":"2026-08-27 08:47:03.000000000","tz":120},"committer":{"name":"Thomas Bechtold","email":"thomasbechtold@jpberlin.de","date":"2026-08-27 08:47:03.000000000","tz":120},"subject":"Support PBKDF2 password digests in basic auth","message":"Support PBKDF2 password digests in basic auth\n\nbcrypt is not an approved algorithm under FIPS 140-3, so the basic auth\nmiddleware, which only understands bcrypt digests, cannot be used in a FIPS\ndeployment at all. Worse, it does not fail closed: the bcrypt library is a\nself-contained implementation that never calls into the OpenSSL FIPS\nprovider, so a FIPS deployment using it today authenticates with a\nnon-approved algorithm and gets no indication of that.\n\nAccept PBKDF2, which NIST SP 800-132 approves, alongside bcrypt. Both\nformats are recognised in the same auth file, so an existing deployment can\nmigrate incrementally. A bcrypt digest cannot be converted without the\nplaintext, so every password has to be re-issued either way, and nothing\nchanges for a file that stays on bcrypt.\n\nUse hashlib.pbkdf2_hmac rather than cryptography. hashlib is backed by\nCPython\u0027s _hashlib, which always links the system libcrypto, so derivation\nruns inside the system OpenSSL FIPS provider when one is active. The\ncryptography wheels published on PyPI statically bundle their own OpenSSL\nand would quietly bypass it. Using hashlib also means no new dependency.\n\nDigests are serialised in the passlib modular crypt format, so they\ninteroperate with passlib.hash.pbkdf2_sha512 and with the digests keystone\nstores for its own pbkdf2_sha512 password hash algorithm.\n\nParsing is deliberately strict. The SP 800-132 minimums, a 128 bit salt and\n1000 iterations, are enforced here rather than relying on OpenSSL to refuse,\nsince the legacy PKCS5_PBKDF2_HMAC entry point hashlib calls may waive them.\nTruncated checksums are rejected; passlib permits them but accepting one\nwould weaken the comparison. Base64 is decoded with strict_mode, because\nbinascii discards characters outside the alphabet by default, and discarding\nthem leaves the decoded length unchanged, so a corrupted or tampered entry\nwould otherwise still authenticate.\n\nApache htpasswd cannot generate PBKDF2, so add an oslo-middleware-htpasswd\ncommand that prints an auth file entry. Its strongest alternatives, -2 and\n-5, produce SHA-256 and SHA-512 crypt, which are deliberately not adopted\nhere: SHA-crypt builds on approved primitives but is not itself an approved\nkey derivation function, so it would not help a FIPS deployment, and Python\nremoved the crypt module in 3.13, so verifying it would mean taking on a\ndependency for no gain.\n\nbcrypt is now imported lazily. It was imported at module scope, and\noslo_middleware/__init__.py imports that module, so a deployment that\nremoved bcrypt could not import oslo_middleware at all, breaking every\nservice using any middleware from this library. Removing bcrypt is the only\nway to be sure it is not used, so that needs to work. bcrypt stays in\nrequirements.txt: moving it to an extra was considered and rejected, because\nthis middleware can be enabled through paste configuration alone, with\nnothing in any repository referencing it, so there is no way to know whether\nsuch a deployment relies on the transitive install. Validating the auth file\nat startup means an entry not yet converted to PBKDF2 stops the service with\nan actionable error rather than quietly continuing.\n\nThe paste filter path is covered by tests for the same reason: the entry\npoint lets an operator enable this middleware from api-paste.ini without any\ncode referencing it, so it is the configuration most likely to exist in the\nwild.\n\nWorth being explicit about cost, since the middleware re-reads the auth file\nand recomputes the digest on every request and caches nothing: at the default\n210000 rounds PBKDF2-HMAC-SHA512 takes around 50ms, against around 1.5ms for\nbcrypt at cost 5, which is what htpasswd -B produces by default. That is\ndocumented rather than worked around, along with a note that busy deployments\nwant a caching proxy in front.\n\nChange-Id: I345847b65cc8804cf329d8f31bbd3fa3d835f246\nSigned-off-by: Thomas Bechtold \u003cthomasbechtold@jpberlin.de\u003e\n","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/oslo.middleware/commit/1037ee9d11ef320e35a16f318cbdcf4f42df8d5c"}],"resolve_conflicts_web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/oslo.middleware/commit/1037ee9d11ef320e35a16f318cbdcf4f42df8d5c"}]},"branch":"refs/heads/master"},"b743f9da5889c76be2ded0d6022d3d4691e16eca":{"kind":"NO_CODE_CHANGE","_number":2,"created":"2026-08-27 08:48:19.000000000","uploader":{"_account_id":7102,"name":"Thomas Bechtold","email":"thomas.bechtold@chainguard.dev","username":"toabctl"},"ref":"refs/changes/93/1002593/2","fetch":{"anonymous http":{"url":"https://review.opendev.org/openstack/oslo.middleware","ref":"refs/changes/93/1002593/2","commands":{"Checkout":"git fetch https://review.opendev.org/openstack/oslo.middleware refs/changes/93/1002593/2 \u0026\u0026 git checkout FETCH_HEAD","Cherry Pick":"git fetch https://review.opendev.org/openstack/oslo.middleware refs/changes/93/1002593/2 \u0026\u0026 git cherry-pick FETCH_HEAD","Format Patch":"git fetch https://review.opendev.org/openstack/oslo.middleware refs/changes/93/1002593/2 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD","Pull":"git pull https://review.opendev.org/openstack/oslo.middleware refs/changes/93/1002593/2"}}},"commit":{"parents":[{"commit":"464357252b64453aafec004b8c08983907ac7c86","subject":"Merge \"Use typing objects directly\"","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/oslo.middleware/commit/464357252b64453aafec004b8c08983907ac7c86"}]}],"author":{"name":"Thomas Bechtold","email":"thomasbechtold@jpberlin.de","date":"2026-08-27 08:47:03.000000000","tz":120},"committer":{"name":"Thomas Bechtold","email":"thomasbechtold@jpberlin.de","date":"2026-08-27 08:48:16.000000000","tz":120},"subject":"Support PBKDF2 password digests in basic auth","message":"Support PBKDF2 password digests in basic auth\n\nbcrypt is not an approved algorithm under FIPS 140-3, so the basic auth\nmiddleware, which only understands bcrypt digests, cannot be used in a\nFIPS deployment at all. Worse, it does not fail closed: the bcrypt\nlibrary is a self-contained implementation that never calls into the\nOpenSSL FIPS provider, so a FIPS deployment using it today authenticates\nwith a non-approved algorithm and gets no indication of that.\n\nAccept PBKDF2, which NIST SP 800-132 approves, alongside bcrypt. Both\nformats are recognised in the same auth file, so an existing deployment\ncan migrate incrementally. A bcrypt digest cannot be converted without\nthe plaintext, so every password has to be re-issued either way, and\nnothing changes for a file that stays on bcrypt.\n\nUse hashlib.pbkdf2_hmac rather than cryptography. hashlib is backed by\nCPython\u0027s _hashlib, which always links the system libcrypto, so\nderivation runs inside the system OpenSSL FIPS provider when one is\nactive. The cryptography wheels published on PyPI statically bundle\ntheir own OpenSSL and would quietly bypass it. Using hashlib also means\nno new dependency.\n\nDigests are serialised in the passlib modular crypt format, so they\ninteroperate with passlib.hash.pbkdf2_sha512 and with the digests\nkeystone stores for its own pbkdf2_sha512 password hash algorithm.\n\nParsing is deliberately strict. The SP 800-132 minimums, a 128 bit salt\nand 1000 iterations, are enforced here rather than relying on OpenSSL to\nrefuse, since the legacy PKCS5_PBKDF2_HMAC entry point hashlib calls may\nwaive them. Truncated checksums are rejected; passlib permits them but\naccepting one would weaken the comparison. Base64 is decoded with\nstrict_mode, because binascii discards characters outside the alphabet\nby default, and discarding them leaves the decoded length unchanged, so\na corrupted or tampered entry would otherwise still authenticate.\n\nApache htpasswd cannot generate PBKDF2, so add an\noslo-middleware-htpasswd command that prints an auth file entry. Its\nstrongest alternatives, -2 and -5, produce SHA-256 and SHA-512 crypt,\nwhich are deliberately not adopted here: SHA-crypt builds on approved\nprimitives but is not itself an approved key derivation function, so it\nwould not help a FIPS deployment, and Python removed the crypt module in\n3.13, so verifying it would mean taking on a dependency for no gain.\n\nbcrypt is now imported lazily. It was imported at module scope, and\noslo_middleware/__init__.py imports that module, so a deployment that\nremoved bcrypt could not import oslo_middleware at all, breaking every\nservice using any middleware from this library. Removing bcrypt is the\nonly way to be sure it is not used, so that needs to work. bcrypt stays\nin requirements.txt: moving it to an extra was considered and rejected,\nbecause this middleware can be enabled through paste configuration\nalone, with nothing in any repository referencing it, so there is no way\nto know whether such a deployment relies on the transitive install.\nValidating the auth file at startup means an entry not yet converted to\nPBKDF2 stops the service with an actionable error rather than quietly\ncontinuing.\n\nThe paste filter path is covered by tests for the same reason: the entry\npoint lets an operator enable this middleware from api-paste.ini without\nany code referencing it, so it is the configuration most likely to exist\nin the wild.\n\nWorth being explicit about cost, since the middleware re-reads the auth\nfile and recomputes the digest on every request and caches nothing: at\nthe default 210000 rounds PBKDF2-HMAC-SHA512 takes around 50ms, against\naround 1.5ms for bcrypt at cost 5, which is what htpasswd -B produces by\ndefault. That is documented rather than worked around, along with a note\nthat busy deployments want a caching proxy in front.\n\nChange-Id: I345847b65cc8804cf329d8f31bbd3fa3d835f246\nSigned-off-by: Thomas Bechtold \u003cthomasbechtold@jpberlin.de\u003e\n","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/oslo.middleware/commit/b743f9da5889c76be2ded0d6022d3d4691e16eca"}],"resolve_conflicts_web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/oslo.middleware/commit/b743f9da5889c76be2ded0d6022d3d4691e16eca"}]},"branch":"refs/heads/master"}},"requirements":[],"submit_records":[{"rule_name":"gerrit~DefaultSubmitRule","status":"OK","labels":[{"label":"Verified","status":"MAY","applied_by":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]}},{"label":"Code-Review","status":"MAY"},{"label":"Workflow","status":"MAY"},{"label":"Backport-Candidate","status":"MAY"}]}],"submit_requirements":[{"name":"Verified","description":"Verified in gate by CI","status":"UNSATISFIED","is_legacy":false,"submittability_expression_result":{"expression":"label:Verified\u003dMAX AND -label:Verified\u003dMIN","fulfilled":false,"status":"FAIL","passing_atoms":[],"failing_atoms":["label:Verified\u003dMAX","label:Verified\u003dMIN"],"atom_explanations":{"label:Verified\u003dMAX":"","label:Verified\u003dMIN":""}}},{"name":"Backport-Candidate","description":"Backport candidate status","status":"NOT_APPLICABLE","is_legacy":false,"applicability_expression_result":{"fulfilled":false,"status":"FAIL"},"submittability_expression_result":{"expression":"is:true","fulfilled":true,"status":"NOT_EVALUATED","passing_atoms":[],"failing_atoms":[],"atom_explanations":{}}},{"name":"Code-Review","description":"Code reviewed by core reviewer","status":"UNSATISFIED","is_legacy":false,"submittability_expression_result":{"expression":"label:Code-Review\u003dMAX AND -label:Code-Review\u003dMIN","fulfilled":false,"status":"FAIL","passing_atoms":[],"failing_atoms":["label:Code-Review\u003dMAX","label:Code-Review\u003dMIN"],"atom_explanations":{"label:Code-Review\u003dMAX":"","label:Code-Review\u003dMIN":""}}},{"name":"Workflow","description":"Approved for gate by core reviewer","status":"UNSATISFIED","is_legacy":false,"submittability_expression_result":{"expression":"label:Workflow\u003dMAX AND -label:Workflow\u003dMIN","fulfilled":false,"status":"FAIL","passing_atoms":[],"failing_atoms":["label:Workflow\u003dMAX","label:Workflow\u003dMIN"],"atom_explanations":{"label:Workflow\u003dMAX":"","label:Workflow\u003dMIN":""}}}]}
