Files
BartelLuis 7b979e6243
CI / javascript-check (push) Successful in 14s
CI / container-policy (push) Successful in 4s
CI / python-tests (push) Successful in 1m48s
CI / container-verify (push) Skipped
CI / container-publish (push) Successful in 49s
feat(ui): replace configuration editors with graphical forms
2026-09-14 21:52:39 +02:00

77 lines
3.8 KiB
Python

"""Root passwords entered through the UI become encrypted installer hashes."""
import json
from passlib.hash import sha512_crypt
import pytest
from provisioner.security import Security
from test_acceptance import PASSWORD, environment, login, post
def test_root_password_secret_is_hashed_encrypted_and_never_returned(environment):
app, client, csrf = environment
password = " Installer-password-2026! "
created = post(client, "/api/v1/secrets", {
"name": "Installer root password", "kind": "root_password", "value": password,
}, csrf)
assert set(created) == {"id", "name"}
with app.state.db.connection() as connection:
row = connection.execute("SELECT * FROM secrets WHERE id=?", (created["id"],)).fetchone()
audit_data = json.dumps([dict(entry) for entry in connection.execute("SELECT * FROM audit")])
stored = app.state.security.decrypt(row["ciphertext"])
assert stored.startswith("$6$rounds=656000$")
assert sha512_crypt.verify(password, stored)
assert not sha512_crypt.verify(password.strip(), stored)
assert password not in row["ciphertext"] and stored not in row["ciphertext"]
public_data = json.dumps(created) + client.get("/api/v1/secrets").text + audit_data
assert password not in public_data and stored not in public_data
@pytest.mark.parametrize("password", ["short", "x" * 1025, "Valid-password\x00invalid"])
def test_root_password_secret_rejects_invalid_password_without_echo(environment, password):
app, client, csrf = environment
response = client.post("/api/v1/secrets", json={
"name": "Invalid root password", "kind": "root_password", "value": password,
}, headers=csrf)
assert response.status_code == 422
assert password not in response.text
with app.state.db.connection() as connection:
assert connection.execute("SELECT COUNT(*) FROM secrets").fetchone()[0] == 0
def test_raw_secret_default_preserves_existing_behavior(environment):
app, client, csrf = environment
created = post(client, "/api/v1/secrets", {"name": "Raw value", "value": " unchanged-value "}, csrf)
with app.state.db.connection() as connection:
row = connection.execute("SELECT ciphertext FROM secrets WHERE id=?", (created["id"],)).fetchone()
assert app.state.security.decrypt(row["ciphertext"]) == "unchanged-value"
assert client.post("/api/v1/secrets", json={"name": "Empty raw value", "value": " "}, headers=csrf).status_code == 422
@pytest.mark.parametrize("role, metadata_status", [("author", 200), ("operator", 200), ("reader", 403)])
def test_secret_metadata_roles_cannot_create_or_read_secret_values(environment, role, metadata_status):
app, client, csrf = environment
created = post(client, "/api/v1/secrets", {"name": "Private root reference", "value": "private-value"}, csrf)
post(client, "/api/v1/users", {"username": role, "password": PASSWORD, "role": role}, csrf)
user_csrf = login(client, username=role)
response = client.get("/api/v1/secrets")
assert response.status_code == metadata_status
if metadata_status == 200:
assert response.json()[0]["id"] == created["id"]
assert set(response.json()[0]) == {"id", "name", "created_at"}
assert "private-value" not in response.text
denied = client.post("/api/v1/secrets", json={
"name": "Denied password", "kind": "root_password", "value": "Forbidden-password-2026!",
}, headers=user_csrf)
assert denied.status_code == 403
assert "Forbidden-password" not in denied.text
def test_installer_password_hash_uses_random_salt():
password = "Installer-password-2026!"
first = Security.hash_installer_password(password)
second = Security.hash_installer_password(password)
assert first != second
assert sha512_crypt.verify(password, first)
assert sha512_crypt.verify(password, second)