aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ansible/roles/prometheus/templates/prometheus-web.yml.j23
-rw-r--r--kolla_ansible/filters.py41
-rw-r--r--kolla_ansible/tests/unit/test_filters.py20
-rw-r--r--requirements.txt3
-rw-r--r--test-requirements.txt5
-rw-r--r--zuul.d/project.yaml3
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