)]}'
{"id":"openstack%2Foctavia~993690","triplet_id":"openstack%2Foctavia~master~I40a0bbab862632b659547eae221b9794789f2a19","project":"openstack/octavia","branch":"master","hashtags":["pqc-migration"],"change_id":"I40a0bbab862632b659547eae221b9794789f2a19","subject":"Implement PQC check mode (spec 2026.2/pqc-strict-mode)","status":"NEW","created":"2026-06-16 20:39:27.000000000","updated":"2026-08-13 16:12:59.000000000","submit_type":"MERGE_IF_NECESSARY","mergeable":true,"submittable":false,"total_comment_count":1,"unresolved_comment_count":0,"work_in_progress":true,"has_review_started":true,"meta_rev_id":"b2b29e010f11e9a47650b66af930bc357ca34691","_number":993690,"virtual_id_number":993690,"owner":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"actions":{},"labels":{"Verified":{"recommended":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"all":[{"tag":"autogenerated:zuul:check","value":1,"date":"2026-06-17 14:39:23.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":[{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]}]},"reviewer_updates":[{"updated":"2026-06-16 23:15:32.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":"f56e0a398b53df71beffad3d5cc9e63862f4e681","tag":"autogenerated:gerrit:newPatchSet","author":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"date":"2026-06-16 20:39:27.000000000","message":"Uploaded patch set 1.","accounts_in_message":[],"_revision_number":1},{"id":"410b286354fb11aa0f8ae9a7ea7598f8658e0f6d","tag":"autogenerated:gerrit:setWorkInProgress","author":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"date":"2026-06-16 20:40:24.000000000","message":"Set Work In Progress","accounts_in_message":[],"_revision_number":1},{"id":"7c94d2156bbf6bf44e783568378c049d86d1eb9a","tag":"autogenerated:gerrit:newWipPatchSet","author":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"date":"2026-06-16 20:42:06.000000000","message":"Patch Set 2: Commit message was updated.","accounts_in_message":[],"_revision_number":2},{"id":"3a57a7ffd5f7895d40e5a28bd002753a5c6a9dfb","tag":"autogenerated:claude-review","author":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"date":"2026-06-16 21:07:39.000000000","message":"Patch Set 2:\n\n(1 comment)\n\n*Reviewed by claude-sonnet-4-6*\n\n# Code Review: openstack/octavia - Change #993690\n\n**Gerrit URL**: https://review.opendev.org/c/openstack/octavia/+/993690\n**Patchset**: 2\n**Reviewed**: 2026-06-16 22:03:48\n**Reviewer**: claude-sonnet-4-6\n**First Review**\n\n---\n\n## Change Summary\n\nThis change implements a three-part Post-Quantum Cryptography (PQC) check mode for Octavia, allowing operators to configure compliance checking for both control-plane (amphora mTLS) and data-plane (listener TLS, client-auth CA, pool backend) certificates. It introduces algorithm-agnostic key generation for amphora certificates, a new `pqc_utils.py` compliance-checking module, per-plane configurable check modes (DISABLED/PERMISSIVE/STRICT), and a startup validation hook. All new modes default to DISABLED, preserving existing behaviour for current deployments.\n\n**Files Modified**: 15\n**Lines Added**: +1,694\n**Lines Removed**: -67\n\n### Commit Message\n```\nDNM/WIP: Implement PQC check mode (spec 2026.2/pqc-strict-mode)\n\nImplements the three-part PQC check mode spec:\n\nPart 1 - Algorithm-agnostic key generation:\n* Add key_algorithm StrOpt to [certificates] config group\n  (RSA-2048, RSA-4096, ECDSA-P256, ECDSA-P384; default RSA-2048)\n* Refactor LocalCertGenerator._generate_private_key() to dispatch\n  on key_algorithm instead of hardcoding rsa.generate_private_key()\n* Raise ConfigInvalidError at generation time for unsupported values\n* Fix _generate_csr() KeyUsage extension per RFC 5280: RSA gets\n  key_encipherment\u003dTrue; ECDSA gets key_agreement\u003dTrue; PQC\n  (future) gets digital_signature only\n\nPart 2 - PQC compliance checker utility:\n* New module octavia/common/tls_utils/pqc_utils.py exposing:\n  - check_algorithm_compliance(cert_or_key, plane): reads per-plane\n    mode from config; DISABLED returns immediately; PERMISSIVE emits\n    LOG.warning; STRICT raises CertificateValidationException\n  - validate_pqc_config(): startup check that (a) warns when\n    pqc_allowed_algorithms differs from PQC_SAFE_ALGORITHMS and\n    (b) raises ConfigInvalidError if control plane is STRICT but\n    key_algorithm is not in the allowlist\n* Replace .public_numbers() comparison in cert_parser.validate_cert()\n  with algorithm-agnostic .public_bytes(DER) comparison so future\n  ML-DSA/ML-KEM keys (which have no public_numbers()) work correctly\n\nPart 3 - Per-plane PQC check modes and startup validation:\n* Add PQC_SAFE_ALGORITHMS constant to octavia/common/constants.py\n  (ML-DSA-44/65/87, ML-KEM-512/768/1024, SLH-DSA-SHAKE variants)\n* Add PQC_DISABLED/PERMISSIVE/STRICT string constants and\n  PQC_CHECK_MODES list\n* Add ConfigInvalidError and CertificateValidationException to\n  octavia/common/exceptions.py\n* Add pqc_control_plane_check_mode, pqc_data_plane_check_mode,\n  pqc_allowed_algorithms StrOpt/ListOpt to certgen_opts in\n  octavia/certificates/common/local.py (all default to DISABLED /\n  PQC_SAFE_ALGORITHMS to preserve existing behaviour)\n* Wire check_algorithm_compliance() at all four certificate load\n  points: barbican.py get_cert() [data], base.py client-CA\n  validation [data], cert_parser.py load_certificates_data() [data],\n  local.py generate_cert_key_pair() [control]\n* Call validate_pqc_config() from common/service.py prepare_service()\n  which is the shared startup path for all four Octavia services\n\nBoth check modes default to DISABLED. All defaults preserve existing\nbehaviour; no deployment is affected unless an operator explicitly\nopts in by setting a non-DISABLED check mode.\n\nUnit tests:\n* New test_pqc_utils.py: 26 tests covering all three modes for both\n  planes, algorithm name extraction (RSA/ECDSA/unknown), startup\n  validation (list diff warnings, ConfigInvalidError for STRICT\n  control with non-compliant key_algorithm)\n* test_local.py: 7 new tests for algorithm dispatch and RFC 5280\n  KeyUsage correctness (RSA vs ECDSA)\n* test_cert_parser.py: 3 new tests for algorithm-agnostic key\n  comparison using .public_bytes() for RSA and EC keys\n* local_csr.py: updated base tests to use oslo config fixture;\n  bit_length parameter is now ignored in favour of key_algorithm\n\nFunctional tests:\n* New test_pqc_check_mode.py: 6 tests verifying DISABLED (listener\n  creates), PERMISSIVE (creates, check called), STRICT (HTTP 400\n  with algorithm name), PUT STRICT (400), plain HTTP unaffected,\n  and default-is-disabled behaviour\n\nCloses-Bug: #2149791\n\nCo-authored-by: OpenCode \u003copencode@anomaly.co\u003e\nCo-authored-by: Claude Sonnet 4.6 \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Richard Cruise \u003crcruise@redhat.com\u003e\n\nChange-Id: I40a0bbab862632b659547eae221b9794789f2a19\nBug refs: #2149791\n```\n\n### Purpose\nAddresses Bug #2149791 (Octavia PQC vulnerabilities): hardcoded RSA-2048 key generation for amphora mTLS, no compliance checking on loaded certificates, and no migration path for quantum-safe algorithm enforcement.\n\n### Scope\nAffects: certificate generator (`local.py`), certificate manager (`barbican.py`), API controller base (`base.py`), TLS utilities (`cert_parser.py`), common constants/exceptions/service, plus a new `pqc_utils.py` module. Adds a spec (`specs/2026.2/pqc-strict-mode.rst`) and 14 modified/new source and test files.\n\n---\n\n## Test Results\n\n_No test commands configured._\n\n---\n\n## Code Analysis\n\n### Overall Assessment\nThe overall design is sound and well-structured: a new opt-in compliance module, conservative defaults, and a clean three-mode architecture (DISABLED/PERMISSIVE/STRICT). However, there is one **critical correctness bug** in the Barbican path that would silently swallow STRICT-mode PQC violations, and a **major inconsistency** between the newly correct CSR KeyUsage and the still-incorrect KeyUsage set by `sign_cert()` in the final signed certificate. Both must be fixed before merge.\n\n### Strengths\n- Conservative defaults (all modes default to DISABLED) guarantee zero behavioural change for existing deployments.\n- Good separation of concerns: `pqc_utils.py` is self-contained, testable, and well-documented with docstrings.\n- `validate_pqc_config()` startup fast-fail for STRICT+incompatible `key_algorithm` is a solid safety net.\n- The `public_bytes(DER)` replacement for `public_numbers()` in `validate_cert()` is a forward-looking correctness improvement.\n- Test coverage is substantial: 26 unit tests for `pqc_utils`, 7 for `test_local`, and 6 functional tests.\n- The spec document is thorough and provides good rationale.\n- Explicit `except CertificateValidationException: raise` guard in `base.py` and `cert_parser.py` is the correct pattern.\n\n### Issues Found\n\n#### Critical Issues 🔴\n\n**`barbican.py`: `CertificateValidationException` silently swallowed in STRICT mode**\n\n`pqc_utils.check_algorithm_compliance()` raises `CertificateValidationException` (an `APIException` subclass) in STRICT mode. In `BarbicanCertManager.get_cert()`, only `UnreadablePKCS12` is explicitly re-raised before the broad `except Exception as e:` handler. A STRICT-mode PQC violation will therefore be caught by the generic handler, logged as a generic warning, and then the code falls through to the legacy Barbican driver — completely defeating the security enforcement that STRICT mode is supposed to provide.\n\nCompare with the correct pattern used in `base.py` and `cert_parser.py` (both explicitly re-raise `CertificateValidationException` before their generic handlers).\n\n#### Major Issues 🟡\n\n**`sign_cert()` KeyUsage not updated — RFC 5280 fix is incomplete**\n\nThe commit message claims to \"Fix _generate_csr() KeyUsage extension per RFC 5280: RSA gets key_encipherment\u003dTrue; ECDSA gets key_agreement\u003dTrue.\" The fix is correctly applied to `_generate_csr()`. However, `sign_cert()` — which is what produces the certificate that Octavia actually stores and uses — still hardcodes the old, overly broad `KeyUsage` with `data_encipherment\u003dTrue` and `key_agreement\u003dTrue` set for *all* key types regardless of algorithm. Since `sign_cert()` does not copy the CSR\u0027s requested extensions (which is standard CA practice) and instead applies its own unconditional extension, ECDSA-P256 amphora certs will still receive an RFC 5280-violating KeyUsage that includes `key_encipherment\u003dTrue`. The CSR-level fix is functionally irrelevant; the fix must be applied at the signing step to have any real effect.\n\n**`_generate_private_key()` silently ignores the `bit_length` parameter**\n\nThe public API of `_generate_private_key(cls, bit_length\u003d2048, passphrase\u003dNone)` now ignores `bit_length` entirely, reading `CONF.certificates.key_algorithm` instead. Callers that pass `bit_length\u003d4096` will silently get whatever `key_algorithm` config specifies, which may be RSA-2048 or ECDSA. The `local_csr.py` note in the commit message (\"bit_length parameter is now ignored\") confirms this is intentional, but:\n\n1. There is no deprecation warning emitted when `bit_length` differs from the algorithm-derived size.\n2. External callers (e.g., drivers) relying on the parameter will break silently.\n3. The parameter should at minimum log a warning if a non-default value is passed, or be deprecated formally.\n\n**`key_algorithm` config option lacks startup validation**\n\nThe `key_algorithm` StrOpt in `certgen_opts` has no `choices\u003d` constraint. Invalid values (e.g., `key_algorithm \u003d FOOBAR`) will not be caught at config load or by `validate_pqc_config()` — they will only surface when `_generate_private_key()` raises `ConfigInvalidError` during actual certificate generation. Adding `choices\u003d[\u0027RSA-2048\u0027, \u0027RSA-4096\u0027, \u0027ECDSA-P256\u0027, \u0027ECDSA-P384\u0027]` to the StrOpt definition would provide immediate startup feedback, consistent with how `pqc_control_plane_check_mode` uses `choices\u003dconstants.PQC_CHECK_MODES`.\n\n#### Minor Issues / Suggestions 🔵\n\n**ML-KEM in `PQC_SAFE_ALGORITHMS` is conceptually mismatched**\n\nML-KEM (CRYSTALS-Kyber) is a Key Encapsulation Mechanism (KEM), not a digital signature scheme. X.509 certificates are signed with signature algorithms (ML-DSA / SLH-DSA), not KEMs. Including `ML-KEM-512/768/1024` in `PQC_SAFE_ALGORITHMS` — which is used as an allowlist for checking certificate public key algorithms — is unlikely to ever match any real certificate\u0027s public key type. This may confuse operators reading the config documentation. If ML-KEM support is genuinely planned for a future use case (e.g., hybrid KEMs in TLS session setup), a comment explaining this should be added.\n\n**SLH-DSA SHA2 variants absent from `PQC_SAFE_ALGORITHMS`**\n\n`PQC_SAFE_ALGORITHMS` includes three SLH-DSA-SHAKE variants but omits the SLH-DSA-SHA2 family (`SLH-DSA-SHA2-128s`, `SLH-DSA-SHA2-128f`, `SLH-DSA-SHA2-192s`, etc.), which are equally standardised in FIPS 205. Operators deploying SLH-DSA-SHA2 certificates will see unexpected warnings/failures when PQC checking is enabled unless they manually extend the allowlist — contrary to the \"batteries included\" intent of the default list.\n\n**`generate_cert_key_pair()`: exception propagation from STRICT mode not documented for callers**\n\nWhen `pqc_control_plane_check_mode` is STRICT and a non-compliant algorithm is generated, `check_algorithm_compliance()` raises `CertificateValidationException` (an HTTP 400 `APIException`) from inside `generate_cert_key_pair()`. The callers of this method in the amphora provisioning flow are internal taskflow tasks that may not handle `APIException` gracefully. The exception type may need to be caught and converted to an internal exception at this boundary, or at minimum, the behaviour should be documented.\n\n**No release notes**\n\nThere is no `releasenotes/notes/` entry for this change. A new config group (`key_algorithm`, `pqc_*` options), new exception types, and new behaviour in the startup path all warrant a reno entry per OpenStack convention.\n\n**Commit subject prefixed with `DNM/WIP`**\n\nThe commit message starts with `DNM/WIP:` indicating this is explicitly not ready for merge. This review is noted accordingly.\n\n#### Nits 🟢\n\n---\n*This review was generated by an AI and may contain errors.*","accounts_in_message":[],"_revision_number":2},{"id":"0273964ab5c0664043c92b2b860e6edf09df24e7","tag":"autogenerated:zuul:check","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2026-06-16 23:15:32.000000000","message":"Patch Set 2: Verified-1\n\nBuild failed (check pipeline).  For information on how to proceed, see\nhttps://docs.opendev.org/opendev/infra-manual/latest/developers.html#automated-testing\nand https://docs.openstack.org/project-team-guide/testing.html#how-to-handle-test-failures\n\nhttps://zuul.opendev.org/t/openstack/buildset/f541b31fdc044ed0bf803fd4c828ed5b\n\n- openstack-tox-cover https://zuul.opendev.org/t/openstack/build/15149a83688a4d71a4c0d8c0ebf8f2c6 : SUCCESS in 9m 42s\n- openstack-tox-pep8 https://zuul.opendev.org/t/openstack/build/30348609ad7a461b8ecbd880cf5e8414 : FAILURE in 2m 56s\n- openstack-tox-py311 https://zuul.opendev.org/t/openstack/build/bdf0b2475b464d77994850fefa6a861f : SUCCESS in 5m 03s\n- openstack-tox-py313 https://zuul.opendev.org/t/openstack/build/656566dac8b7483ea4b764ede74b98b3 : SUCCESS in 5m 02s\n- openstack-tox-py314 https://zuul.opendev.org/t/openstack/build/b14ac8b0f4014df7a92fb1a33938b08b : SUCCESS in 7m 24s (non-voting)\n- openstack-tox-docs https://zuul.opendev.org/t/openstack/build/a5132ac056344af1945c080cb6778880 : FAILURE in 4m 51s\n- build-openstack-releasenotes https://zuul.opendev.org/t/openstack/build/ccc7ebf831ad47bbab2277832cd78df9 : FAILURE in 8m 06s\n- octavia-tox-py313-tips https://zuul.opendev.org/t/openstack/build/fcdefe568baa44a5bf45c0da46d33e6e : SUCCESS in 3m 38s\n- octavia-tox-functional-py313-tips https://zuul.opendev.org/t/openstack/build/89a5eb6d073a492d93c8c02dcf44fecf : SUCCESS in 4m 09s\n- openstack-tox-pip-check-reqs https://zuul.opendev.org/t/openstack/build/6129c5fac75545f1bf89a7f1689c50cb : SUCCESS in 3m 37s\n- openstack-tox-functional-py313 https://zuul.opendev.org/t/openstack/build/c760f0555c684593b3e54f0adf776a5a : SUCCESS in 5m 44s\n- octavia-v2-dsvm-noop-api https://zuul.opendev.org/t/openstack/build/cbd922634b7b426980f608b9abd90441 : SUCCESS in 32m 50s\n- octavia-v2-dsvm-scenario-traffic-ops https://zuul.opendev.org/t/openstack/build/56e960d2813c4d7ba8ba7643e8dc2c8d : SUCCESS in 1h 33m 24s\n- octavia-v2-dsvm-scenario-non-traffic-ops https://zuul.opendev.org/t/openstack/build/5613d1c3d4854a849721cf4aeff21d60 : SUCCESS in 1h 11m 00s\n- octavia-v2-dsvm-scenario-traffic-ops-jobboard https://zuul.opendev.org/t/openstack/build/0eaeb424fa814941ac72a087a6348a72 : SUCCESS in 1h 37m 22s\n- octavia-v2-dsvm-scenario-non-traffic-ops-jobboard https://zuul.opendev.org/t/openstack/build/553abb90ca3743b4b397c63aa043be64 : SUCCESS in 1h 03m 19s\n- octavia-v2-dsvm-tls-barbican https://zuul.opendev.org/t/openstack/build/51c644ba42b7454ba8ba9659ece4a6e5 : SUCCESS in 40m 34s\n- octavia-grenade https://zuul.opendev.org/t/openstack/build/4930f5a455e14e88bdb49d43addb69d2 : SUCCESS in 49m 56s\n- octavia-grenade-skip-level https://zuul.opendev.org/t/openstack/build/b50835d22ed84e73948d572464a51cbc : SUCCESS in 44m 53s\n- octavia-v2-act-stdby-dsvm-scenario https://zuul.opendev.org/t/openstack/build/bbce3fea9f7549e3a893a5ebf1ea3068 : SUCCESS in 36m 33s (non-voting)\n- octavia-v2-dsvm-cinder-amphora https://zuul.opendev.org/t/openstack/build/8cf6e6c5d1e742cba49b2a2ecd4fcaa1 : SUCCESS in 39m 15s (non-voting)\n- octavia-v2-dsvm-scenario-two-node https://zuul.opendev.org/t/openstack/build/2519caf296934a15a819fb0587a13252 : SUCCESS in 2h 31m 53s (non-voting)\n- octavia-v2-dsvm-scenario-ipv6-only https://zuul.opendev.org/t/openstack/build/f3041feb84df4ffdbc3ff9131bd0aa0c : SUCCESS in 1h 29m 05s (non-voting)\n- octavia-v2-dsvm-scenario-centos-10-stream-traffic-ops https://zuul.opendev.org/t/openstack/build/1c514cc65a02467d9156b2d9db561f2a : SUCCESS in 1h 26m 03s (non-voting)\n- octavia-v2-dsvm-scenario-centos-10-stream-non-traffic-ops https://zuul.opendev.org/t/openstack/build/28e06e345973430a971fe2b6f177e2ab : SUCCESS in 1h 05m 53s (non-voting)","accounts_in_message":[],"_revision_number":2},{"id":"4f28f848318389809f89a95244c8b7b3374a20ad","tag":"autogenerated:gerrit:newWipPatchSet","author":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"date":"2026-06-17 11:45:41.000000000","message":"Uploaded patch set 3.\n\nOutdated Votes:\n* Verified-1 (copy condition: \"NEVER\")\n","accounts_in_message":[],"_revision_number":3},{"id":"417d7b8db46913ec32a16a573b10484aeec9b900","tag":"autogenerated:gerrit:newWipPatchSet","author":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"date":"2026-06-17 11:50:24.000000000","message":"Uploaded patch set 4.","accounts_in_message":[],"_revision_number":4},{"id":"ccdf0271129a621edf781701acceb74ce60017fe","tag":"autogenerated:zuul:check","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2026-06-17 14:39:23.000000000","message":"Patch Set 4: Verified+1\n\nBuild succeeded (check pipeline).\nhttps://zuul.opendev.org/t/openstack/buildset/97e65a2ce27c47d2ba688e8113ed37ac\n\n- openstack-tox-cover https://zuul.opendev.org/t/openstack/build/6e112049b29c415e98e003fa74133d82 : SUCCESS in 10m 31s\n- openstack-tox-pep8 https://zuul.opendev.org/t/openstack/build/63c0b7da6d124aa9bf190e6e7167f72c : SUCCESS in 5m 49s\n- openstack-tox-py311 https://zuul.opendev.org/t/openstack/build/da12093f9bc345a8b51a18ba59da3ce7 : SUCCESS in 3m 55s\n- openstack-tox-py313 https://zuul.opendev.org/t/openstack/build/2bf9d5e48dde47c39e1a1f4ddac92490 : SUCCESS in 4m 34s\n- openstack-tox-py314 https://zuul.opendev.org/t/openstack/build/f435c73be0964067a4762835ac12e4c3 : SUCCESS in 5m 52s (non-voting)\n- openstack-tox-docs https://zuul.opendev.org/t/openstack/build/216f15a5700d461091a54b7b2d616ca1 : SUCCESS in 8m 33s\n- build-openstack-releasenotes https://zuul.opendev.org/t/openstack/build/81b84af2b4914de7ad56189af89c4dee : SUCCESS in 10m 05s\n- octavia-tox-py313-tips https://zuul.opendev.org/t/openstack/build/9a91affeb18b4bf4b872d73bb5ae34ab : SUCCESS in 5m 40s\n- octavia-tox-functional-py313-tips https://zuul.opendev.org/t/openstack/build/fef601861c4d40d5aff06b3c21734981 : SUCCESS in 5m 39s\n- openstack-tox-pip-check-reqs https://zuul.opendev.org/t/openstack/build/a18605d4bc104515beffb3a63b2142f9 : SUCCESS in 5m 02s\n- openstack-tox-functional-py313 https://zuul.opendev.org/t/openstack/build/cc7d4d6367004f3c99c2ebe748882fb5 : SUCCESS in 6m 35s\n- octavia-v2-dsvm-noop-api https://zuul.opendev.org/t/openstack/build/b28a2a4bba604a6d8a8d14d98746f4e3 : SUCCESS in 1h 12m 54s\n- octavia-v2-dsvm-scenario-traffic-ops https://zuul.opendev.org/t/openstack/build/5dc939afb1964c1daba2f86a7bce7652 : SUCCESS in 1h 41m 17s\n- octavia-v2-dsvm-scenario-non-traffic-ops https://zuul.opendev.org/t/openstack/build/74ffaf62997846deaafd713efb6a4252 : SUCCESS in 1h 06m 04s\n- octavia-v2-dsvm-scenario-traffic-ops-jobboard https://zuul.opendev.org/t/openstack/build/aaab9dcff8304bf293ea4f72e5f2eb8a : SUCCESS in 1h 53m 37s\n- octavia-v2-dsvm-scenario-non-traffic-ops-jobboard https://zuul.opendev.org/t/openstack/build/83ba421f245340abb1caa91a72747de4 : SUCCESS in 1h 13m 37s\n- octavia-v2-dsvm-tls-barbican https://zuul.opendev.org/t/openstack/build/59b1b8d00c1c492d9b121e9b4878eaf6 : SUCCESS in 39m 34s\n- octavia-grenade https://zuul.opendev.org/t/openstack/build/c02c4ae96ed84ba7b34fcba1f20f9c65 : SUCCESS in 51m 53s\n- octavia-grenade-skip-level https://zuul.opendev.org/t/openstack/build/f2f41021ad65469c96bd1ef82a59e2ea : SUCCESS in 47m 34s\n- octavia-v2-act-stdby-dsvm-scenario https://zuul.opendev.org/t/openstack/build/ec4e7279a0834abca27e72d0d392421c : SUCCESS in 41m 28s (non-voting)\n- octavia-v2-dsvm-cinder-amphora https://zuul.opendev.org/t/openstack/build/d4dff05d84834fa1becab37c9d38fbb2 : SUCCESS in 39m 27s (non-voting)\n- octavia-v2-dsvm-scenario-two-node https://zuul.opendev.org/t/openstack/build/fc95b5886605475391ac5be6a21132c8 : SUCCESS in 2h 47m 27s (non-voting)\n- octavia-v2-dsvm-scenario-ipv6-only https://zuul.opendev.org/t/openstack/build/c547c56358484a26b9c6fbdc9555d6d1 : SUCCESS in 1h 33m 45s (non-voting)\n- octavia-v2-dsvm-scenario-centos-10-stream-traffic-ops https://zuul.opendev.org/t/openstack/build/237a4f52ae614cd18cffbaf2bd16f556 : SUCCESS in 1h 34m 50s (non-voting)\n- octavia-v2-dsvm-scenario-centos-10-stream-non-traffic-ops https://zuul.opendev.org/t/openstack/build/79a1d4743f5b4918b43fc4bc1067aaee : SUCCESS in 1h 05m 23s (non-voting)","accounts_in_message":[],"_revision_number":4},{"id":"b2b29e010f11e9a47650b66af930bc357ca34691","tag":"autogenerated:gerrit:setHashtag","author":{"_account_id":35125,"name":"Mauricio Harley","email":"mharley@redhat.com","username":"mharley-rh"},"date":"2026-08-13 16:12:59.000000000","message":"Hashtag added: pqc-migration","accounts_in_message":[],"_revision_number":4}],"current_revision_number":4,"current_revision":"1ea6976c37a91e083a765b21fb92d011d072e142","revisions":{"8268505352fa083da718c5b6f4f4a844ab97b385":{"kind":"REWORK","_number":1,"created":"2026-06-16 20:39:27.000000000","uploader":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"ref":"refs/changes/90/993690/1","fetch":{"anonymous http":{"url":"https://review.opendev.org/openstack/octavia","ref":"refs/changes/90/993690/1","commands":{"Checkout":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/1 \u0026\u0026 git checkout FETCH_HEAD","Cherry Pick":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/1 \u0026\u0026 git cherry-pick FETCH_HEAD","Format Patch":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/1 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD","Pull":"git pull https://review.opendev.org/openstack/octavia refs/changes/90/993690/1"}}},"commit":{"parents":[{"commit":"9ff4683c8212e4c043af69f1b5ebadc21651dc58","subject":"Merge \"Drop Python 3.10\"","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/9ff4683c8212e4c043af69f1b5ebadc21651dc58"}]}],"author":{"name":"Richard Cruise","email":"rcruise@redhat.com","date":"2026-06-10 10:16:51.000000000","tz":60},"committer":{"name":"Richard Cruise","email":"rcruise@redhat.com","date":"2026-06-16 20:39:12.000000000","tz":60},"subject":"Implement PQC check mode (spec 2026.2/pqc-strict-mode)","message":"Implement PQC check mode (spec 2026.2/pqc-strict-mode)\n\nImplements the three-part PQC check mode spec:\n\nPart 1 - Algorithm-agnostic key generation:\n* Add key_algorithm StrOpt to [certificates] config group\n  (RSA-2048, RSA-4096, ECDSA-P256, ECDSA-P384; default RSA-2048)\n* Refactor LocalCertGenerator._generate_private_key() to dispatch\n  on key_algorithm instead of hardcoding rsa.generate_private_key()\n* Raise ConfigInvalidError at generation time for unsupported values\n* Fix _generate_csr() KeyUsage extension per RFC 5280: RSA gets\n  key_encipherment\u003dTrue; ECDSA gets key_agreement\u003dTrue; PQC\n  (future) gets digital_signature only\n\nPart 2 - PQC compliance checker utility:\n* New module octavia/common/tls_utils/pqc_utils.py exposing:\n  - check_algorithm_compliance(cert_or_key, plane): reads per-plane\n    mode from config; DISABLED returns immediately; PERMISSIVE emits\n    LOG.warning; STRICT raises CertificateValidationException\n  - validate_pqc_config(): startup check that (a) warns when\n    pqc_allowed_algorithms differs from PQC_SAFE_ALGORITHMS and\n    (b) raises ConfigInvalidError if control plane is STRICT but\n    key_algorithm is not in the allowlist\n* Replace .public_numbers() comparison in cert_parser.validate_cert()\n  with algorithm-agnostic .public_bytes(DER) comparison so future\n  ML-DSA/ML-KEM keys (which have no public_numbers()) work correctly\n\nPart 3 - Per-plane PQC check modes and startup validation:\n* Add PQC_SAFE_ALGORITHMS constant to octavia/common/constants.py\n  (ML-DSA-44/65/87, ML-KEM-512/768/1024, SLH-DSA-SHAKE variants)\n* Add PQC_DISABLED/PERMISSIVE/STRICT string constants and\n  PQC_CHECK_MODES list\n* Add ConfigInvalidError and CertificateValidationException to\n  octavia/common/exceptions.py\n* Add pqc_control_plane_check_mode, pqc_data_plane_check_mode,\n  pqc_allowed_algorithms StrOpt/ListOpt to certgen_opts in\n  octavia/certificates/common/local.py (all default to DISABLED /\n  PQC_SAFE_ALGORITHMS to preserve existing behaviour)\n* Wire check_algorithm_compliance() at all four certificate load\n  points: barbican.py get_cert() [data], base.py client-CA\n  validation [data], cert_parser.py load_certificates_data() [data],\n  local.py generate_cert_key_pair() [control]\n* Call validate_pqc_config() from common/service.py prepare_service()\n  which is the shared startup path for all four Octavia services\n\nBoth check modes default to DISABLED. All defaults preserve existing\nbehaviour; no deployment is affected unless an operator explicitly\nopts in by setting a non-DISABLED check mode.\n\nUnit tests:\n* New test_pqc_utils.py: 26 tests covering all three modes for both\n  planes, algorithm name extraction (RSA/ECDSA/unknown), startup\n  validation (list diff warnings, ConfigInvalidError for STRICT\n  control with non-compliant key_algorithm)\n* test_local.py: 7 new tests for algorithm dispatch and RFC 5280\n  KeyUsage correctness (RSA vs ECDSA)\n* test_cert_parser.py: 3 new tests for algorithm-agnostic key\n  comparison using .public_bytes() for RSA and EC keys\n* local_csr.py: updated base tests to use oslo config fixture;\n  bit_length parameter is now ignored in favour of key_algorithm\n\nFunctional tests:\n* New test_pqc_check_mode.py: 6 tests verifying DISABLED (listener\n  creates), PERMISSIVE (creates, check called), STRICT (HTTP 400\n  with algorithm name), PUT STRICT (400), plain HTTP unaffected,\n  and default-is-disabled behaviour\n\nCloses-Bug: #2149791\n\nCo-authored-by: OpenCode \u003copencode@anomaly.co\u003e\nCo-authored-by: Claude Sonnet 4.6 \u003cnoreply@anthropic.com\u003e\nChange-Id: I40a0bbab862632b659547eae221b9794789f2a19\nSigned-off-by: Richard Cruise \u003crcruise@redhat.com\u003e\n","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/8268505352fa083da718c5b6f4f4a844ab97b385"}],"resolve_conflicts_web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/8268505352fa083da718c5b6f4f4a844ab97b385"}]},"branch":"refs/heads/master"},"c0c5d10500e8f6ef27e33db2461ea2e3052dc38d":{"kind":"NO_CODE_CHANGE","_number":2,"created":"2026-06-16 20:42:06.000000000","uploader":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"ref":"refs/changes/90/993690/2","fetch":{"anonymous http":{"url":"https://review.opendev.org/openstack/octavia","ref":"refs/changes/90/993690/2","commands":{"Checkout":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/2 \u0026\u0026 git checkout FETCH_HEAD","Cherry Pick":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/2 \u0026\u0026 git cherry-pick FETCH_HEAD","Format Patch":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/2 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD","Pull":"git pull https://review.opendev.org/openstack/octavia refs/changes/90/993690/2"}}},"commit":{"parents":[{"commit":"9ff4683c8212e4c043af69f1b5ebadc21651dc58","subject":"Merge \"Drop Python 3.10\"","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/9ff4683c8212e4c043af69f1b5ebadc21651dc58"}]}],"author":{"name":"Richard Cruise","email":"rcruise@redhat.com","date":"2026-06-10 10:16:51.000000000","tz":60},"committer":{"name":"Richard Cruise","email":"rcruise@redhat.com","date":"2026-06-16 20:42:06.000000000","tz":0},"subject":"DNM/WIP: Implement PQC check mode (spec 2026.2/pqc-strict-mode)","message":"DNM/WIP: Implement PQC check mode (spec 2026.2/pqc-strict-mode)\n\nImplements the three-part PQC check mode spec:\n\nPart 1 - Algorithm-agnostic key generation:\n* Add key_algorithm StrOpt to [certificates] config group\n  (RSA-2048, RSA-4096, ECDSA-P256, ECDSA-P384; default RSA-2048)\n* Refactor LocalCertGenerator._generate_private_key() to dispatch\n  on key_algorithm instead of hardcoding rsa.generate_private_key()\n* Raise ConfigInvalidError at generation time for unsupported values\n* Fix _generate_csr() KeyUsage extension per RFC 5280: RSA gets\n  key_encipherment\u003dTrue; ECDSA gets key_agreement\u003dTrue; PQC\n  (future) gets digital_signature only\n\nPart 2 - PQC compliance checker utility:\n* New module octavia/common/tls_utils/pqc_utils.py exposing:\n  - check_algorithm_compliance(cert_or_key, plane): reads per-plane\n    mode from config; DISABLED returns immediately; PERMISSIVE emits\n    LOG.warning; STRICT raises CertificateValidationException\n  - validate_pqc_config(): startup check that (a) warns when\n    pqc_allowed_algorithms differs from PQC_SAFE_ALGORITHMS and\n    (b) raises ConfigInvalidError if control plane is STRICT but\n    key_algorithm is not in the allowlist\n* Replace .public_numbers() comparison in cert_parser.validate_cert()\n  with algorithm-agnostic .public_bytes(DER) comparison so future\n  ML-DSA/ML-KEM keys (which have no public_numbers()) work correctly\n\nPart 3 - Per-plane PQC check modes and startup validation:\n* Add PQC_SAFE_ALGORITHMS constant to octavia/common/constants.py\n  (ML-DSA-44/65/87, ML-KEM-512/768/1024, SLH-DSA-SHAKE variants)\n* Add PQC_DISABLED/PERMISSIVE/STRICT string constants and\n  PQC_CHECK_MODES list\n* Add ConfigInvalidError and CertificateValidationException to\n  octavia/common/exceptions.py\n* Add pqc_control_plane_check_mode, pqc_data_plane_check_mode,\n  pqc_allowed_algorithms StrOpt/ListOpt to certgen_opts in\n  octavia/certificates/common/local.py (all default to DISABLED /\n  PQC_SAFE_ALGORITHMS to preserve existing behaviour)\n* Wire check_algorithm_compliance() at all four certificate load\n  points: barbican.py get_cert() [data], base.py client-CA\n  validation [data], cert_parser.py load_certificates_data() [data],\n  local.py generate_cert_key_pair() [control]\n* Call validate_pqc_config() from common/service.py prepare_service()\n  which is the shared startup path for all four Octavia services\n\nBoth check modes default to DISABLED. All defaults preserve existing\nbehaviour; no deployment is affected unless an operator explicitly\nopts in by setting a non-DISABLED check mode.\n\nUnit tests:\n* New test_pqc_utils.py: 26 tests covering all three modes for both\n  planes, algorithm name extraction (RSA/ECDSA/unknown), startup\n  validation (list diff warnings, ConfigInvalidError for STRICT\n  control with non-compliant key_algorithm)\n* test_local.py: 7 new tests for algorithm dispatch and RFC 5280\n  KeyUsage correctness (RSA vs ECDSA)\n* test_cert_parser.py: 3 new tests for algorithm-agnostic key\n  comparison using .public_bytes() for RSA and EC keys\n* local_csr.py: updated base tests to use oslo config fixture;\n  bit_length parameter is now ignored in favour of key_algorithm\n\nFunctional tests:\n* New test_pqc_check_mode.py: 6 tests verifying DISABLED (listener\n  creates), PERMISSIVE (creates, check called), STRICT (HTTP 400\n  with algorithm name), PUT STRICT (400), plain HTTP unaffected,\n  and default-is-disabled behaviour\n\nCloses-Bug: #2149791\n\nCo-authored-by: OpenCode \u003copencode@anomaly.co\u003e\nCo-authored-by: Claude Sonnet 4.6 \u003cnoreply@anthropic.com\u003e\nChange-Id: I40a0bbab862632b659547eae221b9794789f2a19\nSigned-off-by: Richard Cruise \u003crcruise@redhat.com\u003e\n","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/c0c5d10500e8f6ef27e33db2461ea2e3052dc38d"}],"resolve_conflicts_web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/c0c5d10500e8f6ef27e33db2461ea2e3052dc38d"}]},"branch":"refs/heads/master","description":"Edit commit message"},"03dc8ddcb3a0b4650370f4dbbf70d185a9c3ae16":{"kind":"REWORK","_number":3,"created":"2026-06-17 11:45:41.000000000","uploader":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"ref":"refs/changes/90/993690/3","fetch":{"anonymous http":{"url":"https://review.opendev.org/openstack/octavia","ref":"refs/changes/90/993690/3","commands":{"Checkout":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/3 \u0026\u0026 git checkout FETCH_HEAD","Cherry Pick":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/3 \u0026\u0026 git cherry-pick FETCH_HEAD","Format Patch":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/3 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD","Pull":"git pull https://review.opendev.org/openstack/octavia refs/changes/90/993690/3"}}},"commit":{"parents":[{"commit":"20a4d449d4b8d38516e450a7021a84540225ea57","subject":"Octavia Post Quantum Cryptography (PQC) check mode spec","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/20a4d449d4b8d38516e450a7021a84540225ea57"}]}],"author":{"name":"Richard Cruise","email":"rcruise@redhat.com","date":"2026-06-10 10:16:51.000000000","tz":60},"committer":{"name":"Richard Cruise","email":"rcruise@redhat.com","date":"2026-06-17 11:45:08.000000000","tz":60},"subject":"Implement PQC check mode (spec 2026.2/pqc-strict-mode)","message":"Implement PQC check mode (spec 2026.2/pqc-strict-mode)\n\nImplements the three-part PQC check mode spec:\n\nPart 1 - Algorithm-agnostic key generation:\n* Add key_algorithm StrOpt to [certificates] config group\n  (RSA-2048, RSA-4096, ECDSA-P256, ECDSA-P384; default RSA-2048)\n* Refactor LocalCertGenerator._generate_private_key() to dispatch\n  on key_algorithm instead of hardcoding rsa.generate_private_key()\n* Raise ConfigInvalidError at generation time for unsupported values\n* Fix _generate_csr() KeyUsage extension per RFC 5280: RSA gets\n  key_encipherment\u003dTrue; ECDSA gets key_agreement\u003dTrue; PQC\n  (future) gets digital_signature only\n\nPart 2 - PQC compliance checker utility:\n* New module octavia/common/tls_utils/pqc_utils.py exposing:\n  - check_algorithm_compliance(cert_or_key, plane): reads per-plane\n    mode from config; DISABLED returns immediately; PERMISSIVE emits\n    LOG.warning; STRICT raises CertificateValidationException\n  - validate_pqc_config(): startup check that (a) warns when\n    pqc_allowed_algorithms differs from PQC_SAFE_ALGORITHMS and\n    (b) raises ConfigInvalidError if control plane is STRICT but\n    key_algorithm is not in the allowlist\n* Replace .public_numbers() comparison in cert_parser.validate_cert()\n  with algorithm-agnostic .public_bytes(DER) comparison so future\n  ML-DSA/ML-KEM keys (which have no public_numbers()) work correctly\n\nPart 3 - Per-plane PQC check modes and startup validation:\n* Add PQC_SAFE_ALGORITHMS constant to octavia/common/constants.py\n  (ML-DSA-44/65/87, ML-KEM-512/768/1024, SLH-DSA-SHAKE variants)\n* Add PQC_DISABLED/PERMISSIVE/STRICT string constants and\n  PQC_CHECK_MODES list\n* Add ConfigInvalidError and CertificateValidationException to\n  octavia/common/exceptions.py\n* Add pqc_control_plane_check_mode, pqc_data_plane_check_mode,\n  pqc_allowed_algorithms StrOpt/ListOpt to certgen_opts in\n  octavia/certificates/common/local.py (all default to DISABLED /\n  PQC_SAFE_ALGORITHMS to preserve existing behaviour)\n* Wire check_algorithm_compliance() at all four certificate load\n  points: barbican.py get_cert() [data], base.py client-CA\n  validation [data], cert_parser.py load_certificates_data() [data],\n  local.py generate_cert_key_pair() [control]\n* Call validate_pqc_config() from common/service.py prepare_service()\n  which is the shared startup path for all four Octavia services\n\nBoth check modes default to DISABLED. All defaults preserve existing\nbehaviour; no deployment is affected unless an operator explicitly\nopts in by setting a non-DISABLED check mode.\n\nUnit tests:\n* New test_pqc_utils.py: 26 tests covering all three modes for both\n  planes, algorithm name extraction (RSA/ECDSA/unknown), startup\n  validation (list diff warnings, ConfigInvalidError for STRICT\n  control with non-compliant key_algorithm)\n* test_local.py: 7 new tests for algorithm dispatch and RFC 5280\n  KeyUsage correctness (RSA vs ECDSA)\n* test_cert_parser.py: 3 new tests for algorithm-agnostic key\n  comparison using .public_bytes() for RSA and EC keys\n* local_csr.py: updated base tests to use oslo config fixture;\n  bit_length parameter is now ignored in favour of key_algorithm\n\nFunctional tests:\n* New test_pqc_check_mode.py: 6 tests verifying DISABLED (listener\n  creates), PERMISSIVE (creates, check called), STRICT (HTTP 400\n  with algorithm name), PUT STRICT (400), plain HTTP unaffected,\n  and default-is-disabled behaviour\n\nCloses-Bug: #2149791\n\nCo-authored-by: OpenCode \u003copencode@anomaly.co\u003e\nCo-authored-by: Claude Sonnet 4.6 \u003cnoreply@anthropic.com\u003e\nChange-Id: I40a0bbab862632b659547eae221b9794789f2a19\nSigned-off-by: Richard Cruise \u003crcruise@redhat.com\u003e\n","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/03dc8ddcb3a0b4650370f4dbbf70d185a9c3ae16"}],"resolve_conflicts_web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/03dc8ddcb3a0b4650370f4dbbf70d185a9c3ae16"}]},"branch":"refs/heads/master"},"1ea6976c37a91e083a765b21fb92d011d072e142":{"kind":"REWORK","_number":4,"created":"2026-06-17 11:50:24.000000000","uploader":{"_account_id":38562,"name":"Richard Cruise","email":"rcruise@redhat.com","username":"rcruise"},"ref":"refs/changes/90/993690/4","fetch":{"anonymous http":{"url":"https://review.opendev.org/openstack/octavia","ref":"refs/changes/90/993690/4","commands":{"Checkout":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/4 \u0026\u0026 git checkout FETCH_HEAD","Cherry Pick":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/4 \u0026\u0026 git cherry-pick FETCH_HEAD","Format Patch":"git fetch https://review.opendev.org/openstack/octavia refs/changes/90/993690/4 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD","Pull":"git pull https://review.opendev.org/openstack/octavia refs/changes/90/993690/4"}}},"commit":{"parents":[{"commit":"20a4d449d4b8d38516e450a7021a84540225ea57","subject":"Octavia Post Quantum Cryptography (PQC) check mode spec","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/20a4d449d4b8d38516e450a7021a84540225ea57"}]}],"author":{"name":"Richard Cruise","email":"rcruise@redhat.com","date":"2026-06-10 10:16:51.000000000","tz":60},"committer":{"name":"Richard Cruise","email":"rcruise@redhat.com","date":"2026-06-17 11:50:08.000000000","tz":60},"subject":"Implement PQC check mode (spec 2026.2/pqc-strict-mode)","message":"Implement PQC check mode (spec 2026.2/pqc-strict-mode)\n\nImplements the three-part PQC check mode spec:\n\nPart 1 - Algorithm-agnostic key generation:\n* Add key_algorithm StrOpt to [certificates] config group\n  (RSA-2048, RSA-4096, ECDSA-P256, ECDSA-P384; default RSA-2048)\n* Refactor LocalCertGenerator._generate_private_key() to dispatch\n  on key_algorithm instead of hardcoding rsa.generate_private_key()\n* Raise ConfigInvalidError at generation time for unsupported values\n* Fix _generate_csr() KeyUsage extension per RFC 5280: RSA gets\n  key_encipherment\u003dTrue; ECDSA gets key_agreement\u003dTrue; PQC\n  (future) gets digital_signature only\n\nPart 2 - PQC compliance checker utility:\n* New module octavia/common/tls_utils/pqc_utils.py exposing:\n  - check_algorithm_compliance(cert_or_key, plane): reads per-plane\n    mode from config; DISABLED returns immediately; PERMISSIVE emits\n    LOG.warning; STRICT raises CertificateValidationException\n  - validate_pqc_config(): startup check that (a) warns when\n    pqc_allowed_algorithms differs from PQC_SAFE_ALGORITHMS and\n    (b) raises ConfigInvalidError if control plane is STRICT but\n    key_algorithm is not in the allowlist\n* Replace .public_numbers() comparison in cert_parser.validate_cert()\n  with algorithm-agnostic .public_bytes(DER) comparison so future\n  ML-DSA/ML-KEM keys (which have no public_numbers()) work correctly\n\nPart 3 - Per-plane PQC check modes and startup validation:\n* Add PQC_SAFE_ALGORITHMS constant to octavia/common/constants.py\n  (ML-DSA-44/65/87, ML-KEM-512/768/1024, SLH-DSA-SHAKE variants)\n* Add PQC_DISABLED/PERMISSIVE/STRICT string constants and\n  PQC_CHECK_MODES list\n* Add ConfigInvalidError and CertificateValidationException to\n  octavia/common/exceptions.py\n* Add pqc_control_plane_check_mode, pqc_data_plane_check_mode,\n  pqc_allowed_algorithms StrOpt/ListOpt to certgen_opts in\n  octavia/certificates/common/local.py (all default to DISABLED /\n  PQC_SAFE_ALGORITHMS to preserve existing behaviour)\n* Wire check_algorithm_compliance() at all four certificate load\n  points: barbican.py get_cert() [data], base.py client-CA\n  validation [data], cert_parser.py load_certificates_data() [data],\n  local.py generate_cert_key_pair() [control]\n* Call validate_pqc_config() from common/service.py prepare_service()\n  which is the shared startup path for all four Octavia services\n\nBoth check modes default to DISABLED. All defaults preserve existing\nbehaviour; no deployment is affected unless an operator explicitly\nopts in by setting a non-DISABLED check mode.\n\nUnit tests:\n* New test_pqc_utils.py: 26 tests covering all three modes for both\n  planes, algorithm name extraction (RSA/ECDSA/unknown), startup\n  validation (list diff warnings, ConfigInvalidError for STRICT\n  control with non-compliant key_algorithm)\n* test_local.py: 7 new tests for algorithm dispatch and RFC 5280\n  KeyUsage correctness (RSA vs ECDSA)\n* test_cert_parser.py: 3 new tests for algorithm-agnostic key\n  comparison using .public_bytes() for RSA and EC keys\n* local_csr.py: updated base tests to use oslo config fixture;\n  bit_length parameter is now ignored in favour of key_algorithm\n\nFunctional tests:\n* New test_pqc_check_mode.py: 6 tests verifying DISABLED (listener\n  creates), PERMISSIVE (creates, check called), STRICT (HTTP 400\n  with algorithm name), PUT STRICT (400), plain HTTP unaffected,\n  and default-is-disabled behaviour\n\nCloses-Bug: #2149791\n\nCo-authored-by: OpenCode \u003copencode@anomaly.co\u003e\nCo-authored-by: Claude Sonnet 4.6 \u003cnoreply@anthropic.com\u003e\nChange-Id: I40a0bbab862632b659547eae221b9794789f2a19\nSigned-off-by: Richard Cruise \u003crcruise@redhat.com\u003e\n","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/1ea6976c37a91e083a765b21fb92d011d072e142"}],"resolve_conflicts_web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/octavia/commit/1ea6976c37a91e083a765b21fb92d011d072e142"}]},"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":""}}}]}
