diff options
| author | Michal Nasiadka <mnasiadka@gmail.com> | 2025-12-05 08:08:47 +0100 |
|---|---|---|
| committer | Michal Nasiadka <mnasiadka@gmail.com> | 2026-03-03 12:10:33 +0000 |
| commit | ad59c9e8c22cb4ec631e060303922bfbe6def4af (patch) | |
| tree | 824568e431ca004cf068bf8aed103b14916e92e0 | |
| parent | b9ab5fe911f99f8a0c267edd2b31f02e65c84ff4 (diff) | |
| download | kolla-ansible-ad59c9e8c22cb4ec631e060303922bfbe6def4af.tar.gz kolla-ansible-ad59c9e8c22cb4ec631e060303922bfbe6def4af.zip | |
Switch from passlib to internal bcrypt filter
Change-Id: I1c0312aa7bdfb78fedef6c98463f25e6460f4587
Co-Authored-By: Pierre Riteau <pierre@stackhpc.com>
Signed-off-by: Michal Nasiadka <mnasiadka@gmail.com>
| -rw-r--r-- | ansible/roles/prometheus/templates/prometheus-web.yml.j2 | 3 | ||||
| -rw-r--r-- | kolla_ansible/filters.py | 41 | ||||
| -rw-r--r-- | kolla_ansible/tests/unit/test_filters.py | 20 | ||||
| -rw-r--r-- | requirements.txt | 3 | ||||
| -rw-r--r-- | test-requirements.txt | 5 | ||||
| -rw-r--r-- | zuul.d/project.yaml | 3 |
6 files changed, 65 insertions, 10 deletions
diff --git a/ansible/roles/prometheus/templates/prometheus-web.yml.j2 b/ansible/roles/prometheus/templates/prometheus-web.yml.j2 index 67e055428..1499e2c1b 100644 --- a/ansible/roles/prometheus/templates/prometheus-web.yml.j2 +++ b/ansible/roles/prometheus/templates/prometheus-web.yml.j2 @@ -1,4 +1,5 @@ basic_auth_users: {% for user in prometheus_basic_auth_users | selectattr('enabled') | list %} - {{ user.username }}: {{ user.password | password_hash('bcrypt', salt=prometheus_bcrypt_salt) }} +{# TODO(mnasiadka): Migrate me to libcrypt/libxcrypt in Ansible once it's available #} + {{ user.username }}: {{ user.password | bcrypt_hash(salt=prometheus_bcrypt_salt) }} {% endfor %} diff --git a/kolla_ansible/filters.py b/kolla_ansible/filters.py index 88dcff5e9..1ee5445c1 100644 --- a/kolla_ansible/filters.py +++ b/kolla_ansible/filters.py @@ -12,12 +12,30 @@ # License for the specific language governing permissions and limitations # under the License. +import base64 +import hashlib +import string + +import bcrypt import jinja2 from kolla_ansible import exception from kolla_ansible.helpers import _call_bool_filter +# bcrypt uses a non-standard base64 encoding +BASE64_ENCODING = (string.ascii_uppercase + string.ascii_lowercase + + string.digits + '+/') +BCRYPT_ENCODING = ('./' + string.ascii_uppercase + string.ascii_lowercase + + string.digits) + + +def _bcrypt_b64encode(s): + b64_s = base64.b64encode(s) + return b64_s.translate(bytes.maketrans(BASE64_ENCODING.encode('utf-8'), + BCRYPT_ENCODING.encode('utf-8'))) + + @jinja2.pass_context def service_enabled(context, service): """Return whether a service is enabled. @@ -109,8 +127,31 @@ def select_services_enabled_and_mapped_to_host(context, services): if service_enabled_and_mapped_to_host(context, service)} +@jinja2.pass_context +def bcrypt_hash(context, password, salt=None): + """Provide bcrypt hash. + + :param context: Jinja2 Context object. + :param password: Cleartext password to encode + :param salt: Salt, optional. + :returns: A byte string containing password hash + """ + if salt: + # Generate a 16-byte salt from your string using SHA256 + salt_bytes = hashlib.sha256(salt.encode('utf-8')).digest()[:16] + + # bcrypt requires salt in a specific format, so encode it properly + # bcrypt expects special-encoded base64 characters + cost factor prefix + salt_encoded = b'$2b$12$' + _bcrypt_b64encode(salt_bytes) + + return bcrypt.hashpw(password.encode(), salt_encoded) + else: + return bcrypt.hashpw(password.encode(), bcrypt.gensalt()) + + def get_filters(): return { + "bcrypt_hash": bcrypt_hash, "extract_haproxy_services": extract_haproxy_services, "service_enabled": service_enabled, "service_mapped_to_host": service_mapped_to_host, diff --git a/kolla_ansible/tests/unit/test_filters.py b/kolla_ansible/tests/unit/test_filters.py index 6ea3c51b6..92aa148a3 100644 --- a/kolla_ansible/tests/unit/test_filters.py +++ b/kolla_ansible/tests/unit/test_filters.py @@ -37,6 +37,26 @@ class TestFilters(unittest.TestCase): return self.env.context_class( self.env, parent=parent, name='dummy', blocks={}) + def test_bcrypt_without_salt(self): + password = 'test' # nosec B105 + result = filters.bcrypt_hash(self.context, password) + self.assertIsInstance(result, bytes) + + def test_bcrypt_with_salt(self): + password = 'test' # nosec B105 + salt = '$2a$12$w40nlebw3XyoZ5Cqke14M.' + result = filters.bcrypt_hash(self.context, password, salt) + self.assertIsInstance(result, bytes) + + def test_bcrypt_with_salt_idempotent(self): + password = 'test' # nosec B105 + salt = '$2a$12$w40nlebw3XyoZ5Cqke14M.' + first = filters.bcrypt_hash(self.context, password, salt) + second = filters.bcrypt_hash(self.context, password, salt) + self.assertIsInstance(first, bytes) + self.assertIsInstance(second, bytes) + self.assertEqual(first, second) + def test_service_enabled_true(self): service = { 'enabled': True diff --git a/requirements.txt b/requirements.txt index 32c6c8536..de5aeb51b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,8 +20,5 @@ oslo.config>=5.2.0 # Apache-2.0 # password generation oslo.utils>=3.33.0 # Apache-2.0 -# Password hashing -passlib[bcrypt]>=1.0.0 # BSD - # CLI cliff>=4.7.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 85a3dc193..cfcbfa775 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,5 +1,5 @@ # Password hashing -bcrypt>=3.0.0 # Apache-2.0 +bcrypt>=4.3.0 # Apache-2.0 # password generation cryptography>=2.1 # BSD/Apache-2.0 @@ -20,9 +20,6 @@ oslo.config>=5.2.0 # Apache-2.0 # password generation oslo.utils>=3.33.0 # Apache-2.0 -# Password hashing -passlib[bcrypt]>=1.0.0 # BSD - # CLI cliff>=4.7.0 # Apache-2.0 diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index 777520d80..0758a8067 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -3,8 +3,7 @@ queue: kolla templates: - ansible-role-jobs - # TODO(mnasiadka): Fix ansible/passlib/bcrypt 5.0.0 - #- check-requirements + - check-requirements - kolla-ansible-scenario-aio - kolla-ansible-scenario-bifrost - kolla-ansible-scenario-cells |
