mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Decomplicate the eauth tests
Signed-off-by: Pedro Algarvio <palgarvio@vmware.com>
This commit is contained in:
parent
5f63d785e9
commit
8b762be8ad
1 changed files with 101 additions and 132 deletions
|
@ -1,6 +1,5 @@
|
|||
import json
|
||||
import pathlib
|
||||
import tempfile
|
||||
import logging
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
@ -9,17 +8,18 @@ from tests.conftest import CODE_DIR
|
|||
|
||||
docker = pytest.importorskip("docker")
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.core_test,
|
||||
pytest.mark.timeout_unless_on_windows(120),
|
||||
pytest.mark.timeout_unless_on_windows(600, func_only=True),
|
||||
]
|
||||
|
||||
|
||||
def json_output_to_dict(output):
|
||||
"""
|
||||
r"""
|
||||
Convert ``salt ... --out=json`` Syndic return to a dictionary. Since the
|
||||
--out=json will return several JSON outputs, e.g. {...}\\n{...}, we have to
|
||||
--out=json will return several JSON outputs, e.g. {...}\n{...}, we have to
|
||||
parse that output individually.
|
||||
"""
|
||||
output = output or ""
|
||||
|
@ -36,20 +36,6 @@ def json_output_to_dict(output):
|
|||
return results
|
||||
|
||||
|
||||
def accept_keys(container, required_minions):
|
||||
failure_time = time.time() + 20
|
||||
while time.time() < failure_time:
|
||||
container.run("salt-key -Ay")
|
||||
res = container.run("salt-key -L --out=json")
|
||||
if (
|
||||
isinstance(res.data, dict)
|
||||
and set(res.data.get("minions")) == required_minions
|
||||
):
|
||||
break
|
||||
else:
|
||||
pytest.skip(f"{container} unable to accept keys for {required_minions}")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def syndic_network():
|
||||
try:
|
||||
|
@ -73,56 +59,49 @@ def syndic_network():
|
|||
network.remove()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def source_path():
|
||||
return str(CODE_DIR / "salt")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def container_image_name():
|
||||
return "ghcr.io/saltstack/salt-ci-containers/salt:3005"
|
||||
return "ghcr.io/saltstack/salt-ci-containers/salt:3006"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def container_python_version():
|
||||
return "3.7"
|
||||
return "3.10"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def config(source_path):
|
||||
# 3.10>= will allow the below line
|
||||
# with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp_path:
|
||||
with tempfile.TemporaryDirectory() as tmp_path:
|
||||
tmp_path = pathlib.Path(tmp_path)
|
||||
master_dir = tmp_path / "master"
|
||||
minion_dir = tmp_path / "minion"
|
||||
syndic_a_dir = tmp_path / "syndic_a"
|
||||
syndic_b_dir = tmp_path / "syndic_b"
|
||||
minion_a1_dir = tmp_path / "minion_a1"
|
||||
minion_a2_dir = tmp_path / "minion_a2"
|
||||
minion_b1_dir = tmp_path / "minion_b1"
|
||||
minion_b2_dir = tmp_path / "minion_b2"
|
||||
def config(tmp_path_factory):
|
||||
tmp_path = tmp_path_factory.mktemp("eauth")
|
||||
master_dir = tmp_path / "master"
|
||||
minion_dir = tmp_path / "minion"
|
||||
syndic_a_dir = tmp_path / "syndic_a"
|
||||
syndic_b_dir = tmp_path / "syndic_b"
|
||||
minion_a1_dir = tmp_path / "minion_a1"
|
||||
minion_a2_dir = tmp_path / "minion_a2"
|
||||
minion_b1_dir = tmp_path / "minion_b1"
|
||||
minion_b2_dir = tmp_path / "minion_b2"
|
||||
|
||||
for dir_ in (
|
||||
master_dir,
|
||||
minion_dir,
|
||||
syndic_a_dir,
|
||||
syndic_b_dir,
|
||||
minion_a1_dir,
|
||||
minion_a2_dir,
|
||||
minion_b1_dir,
|
||||
minion_b2_dir,
|
||||
):
|
||||
dir_.mkdir(parents=True, exist_ok=True)
|
||||
(dir_ / "master.d").mkdir(exist_ok=True)
|
||||
# minion.d is probably needed to prevent errors on tempdir cleanup
|
||||
(dir_ / "minion.d").mkdir(exist_ok=True)
|
||||
(dir_ / "pki").mkdir(exist_ok=True)
|
||||
(master_dir / "master.d").mkdir(exist_ok=True)
|
||||
for dir_ in (
|
||||
master_dir,
|
||||
minion_dir,
|
||||
syndic_a_dir,
|
||||
syndic_b_dir,
|
||||
minion_a1_dir,
|
||||
minion_a2_dir,
|
||||
minion_b1_dir,
|
||||
minion_b2_dir,
|
||||
):
|
||||
dir_.mkdir(parents=True, exist_ok=True)
|
||||
(dir_ / "master.d").mkdir(exist_ok=True)
|
||||
# minion.d is probably needed to prevent errors on tempdir cleanup
|
||||
(dir_ / "minion.d").mkdir(exist_ok=True)
|
||||
(dir_ / "pki").mkdir(exist_ok=True)
|
||||
(master_dir / "master.d").mkdir(exist_ok=True)
|
||||
|
||||
master_config_path = master_dir / "master"
|
||||
master_config_path.write_text(
|
||||
"""
|
||||
master_config_path = master_dir / "master"
|
||||
master_config_path.write_text(
|
||||
"""
|
||||
open_mode: True
|
||||
auth.pam.python: /usr/local/bin/python3
|
||||
order_masters: True
|
||||
|
||||
|
@ -143,17 +122,20 @@ nodegroups:
|
|||
second_string: "minion_*2"
|
||||
b_string: "minion_b*"
|
||||
|
||||
"""
|
||||
)
|
||||
|
||||
minion_config_path = minion_dir / "minion"
|
||||
minion_config_path.write_text("id: minion\nmaster: master\nopen_mode: True")
|
||||
|
||||
syndic_a_minion_config_path = syndic_a_dir / "minion"
|
||||
syndic_a_minion_config_path.write_text(
|
||||
"id: syndic_a\nmaster: master\nopen_mode: True"
|
||||
)
|
||||
syndic_a_master_config_path = syndic_a_dir / "master"
|
||||
syndic_a_master_config_path.write_text(
|
||||
"""
|
||||
)
|
||||
|
||||
minion_config_path = minion_dir / "minion"
|
||||
minion_config_path.write_text("id: minion\nmaster: master")
|
||||
|
||||
syndic_a_minion_config_path = syndic_a_dir / "minion"
|
||||
syndic_a_minion_config_path.write_text("id: syndic_a\nmaster: master")
|
||||
syndic_a_master_config_path = syndic_a_dir / "master"
|
||||
syndic_a_master_config_path.write_text(
|
||||
"""
|
||||
open_mode: True
|
||||
auth.pam.python: /usr/local/bin/python3
|
||||
syndic_master: master
|
||||
publisher_acl:
|
||||
|
@ -168,34 +150,36 @@ external_auth:
|
|||
- '*1':
|
||||
- test.*
|
||||
- file.touch
|
||||
"""
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
minion_a1_config_path = minion_a1_dir / "minion"
|
||||
minion_a1_config_path.write_text("id: minion_a1\nmaster: syndic_a")
|
||||
minion_a2_config_path = minion_a2_dir / "minion"
|
||||
minion_a2_config_path.write_text("id: minion_a2\nmaster: syndic_a")
|
||||
minion_a1_config_path = minion_a1_dir / "minion"
|
||||
minion_a1_config_path.write_text("id: minion_a1\nmaster: syndic_a\nopen_mode: True")
|
||||
minion_a2_config_path = minion_a2_dir / "minion"
|
||||
minion_a2_config_path.write_text("id: minion_a2\nmaster: syndic_a\nopen_mode: True")
|
||||
|
||||
syndic_b_minion_config_path = syndic_b_dir / "minion"
|
||||
syndic_b_minion_config_path.write_text("id: syndic_b\nmaster: master")
|
||||
syndic_b_master_config_path = syndic_b_dir / "master"
|
||||
syndic_b_master_config_path.write_text("syndic_master: master")
|
||||
syndic_b_minion_config_path = syndic_b_dir / "minion"
|
||||
syndic_b_minion_config_path.write_text(
|
||||
"id: syndic_b\nmaster: master\nopen_mode: True"
|
||||
)
|
||||
syndic_b_master_config_path = syndic_b_dir / "master"
|
||||
syndic_b_master_config_path.write_text("syndic_master: master\nopen_mode: True")
|
||||
|
||||
minion_b1_config_path = minion_b1_dir / "minion"
|
||||
minion_b1_config_path.write_text("id: minion_b1\nmaster: syndic_b")
|
||||
minion_b2_config_path = minion_b2_dir / "minion"
|
||||
minion_b2_config_path.write_text("id: minion_b2\nmaster: syndic_b")
|
||||
minion_b1_config_path = minion_b1_dir / "minion"
|
||||
minion_b1_config_path.write_text("id: minion_b1\nmaster: syndic_b\nopen_mode: True")
|
||||
minion_b2_config_path = minion_b2_dir / "minion"
|
||||
minion_b2_config_path.write_text("id: minion_b2\nmaster: syndic_b\nopen_mode: True")
|
||||
|
||||
yield {
|
||||
"minion_dir": minion_dir,
|
||||
"master_dir": master_dir,
|
||||
"syndic_a_dir": syndic_a_dir,
|
||||
"syndic_b_dir": syndic_b_dir,
|
||||
"minion_a1_dir": minion_a1_dir,
|
||||
"minion_a2_dir": minion_a2_dir,
|
||||
"minion_b1_dir": minion_b1_dir,
|
||||
"minion_b2_dir": minion_b2_dir,
|
||||
}
|
||||
return {
|
||||
"minion_dir": minion_dir,
|
||||
"master_dir": master_dir,
|
||||
"syndic_a_dir": syndic_a_dir,
|
||||
"syndic_b_dir": syndic_b_dir,
|
||||
"minion_a1_dir": minion_a1_dir,
|
||||
"minion_a2_dir": minion_a2_dir,
|
||||
"minion_b1_dir": minion_b1_dir,
|
||||
"minion_b2_dir": minion_b2_dir,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
@ -203,7 +187,6 @@ def docker_master(
|
|||
salt_factories,
|
||||
syndic_network,
|
||||
config,
|
||||
source_path,
|
||||
container_image_name,
|
||||
container_python_version,
|
||||
):
|
||||
|
@ -217,7 +200,7 @@ def docker_master(
|
|||
"network": syndic_network,
|
||||
"volumes": {
|
||||
config_dir: {"bind": "/etc/salt", "mode": "z"},
|
||||
source_path: {
|
||||
str(CODE_DIR / "salt"): {
|
||||
"bind": f"/usr/local/lib/python{container_python_version}/site-packages/salt/",
|
||||
"mode": "z",
|
||||
},
|
||||
|
@ -242,7 +225,6 @@ def docker_minion(
|
|||
salt_factories,
|
||||
syndic_network,
|
||||
config,
|
||||
source_path,
|
||||
container_image_name,
|
||||
container_python_version,
|
||||
):
|
||||
|
@ -256,7 +238,7 @@ def docker_minion(
|
|||
"network": syndic_network,
|
||||
"volumes": {
|
||||
config_dir: {"bind": "/etc/salt", "mode": "z"},
|
||||
source_path: {
|
||||
str(CODE_DIR / "salt"): {
|
||||
"bind": f"/usr/local/lib/python{container_python_version}/site-packages/salt/",
|
||||
"mode": "z",
|
||||
},
|
||||
|
@ -276,7 +258,6 @@ def docker_syndic_a(
|
|||
salt_factories,
|
||||
config,
|
||||
syndic_network,
|
||||
source_path,
|
||||
container_image_name,
|
||||
container_python_version,
|
||||
):
|
||||
|
@ -290,7 +271,7 @@ def docker_syndic_a(
|
|||
"network": syndic_network,
|
||||
"volumes": {
|
||||
config_dir: {"bind": "/etc/salt", "mode": "z"},
|
||||
source_path: {
|
||||
str(CODE_DIR / "salt"): {
|
||||
"bind": f"/usr/local/lib/python{container_python_version}/site-packages/salt/",
|
||||
"mode": "z",
|
||||
},
|
||||
|
@ -310,7 +291,6 @@ def docker_syndic_b(
|
|||
salt_factories,
|
||||
config,
|
||||
syndic_network,
|
||||
source_path,
|
||||
container_image_name,
|
||||
container_python_version,
|
||||
):
|
||||
|
@ -324,7 +304,7 @@ def docker_syndic_b(
|
|||
"network": syndic_network,
|
||||
"volumes": {
|
||||
config_dir: {"bind": "/etc/salt", "mode": "z"},
|
||||
source_path: {
|
||||
str(CODE_DIR / "salt"): {
|
||||
"bind": f"/usr/local/lib/python{container_python_version}/site-packages/salt/",
|
||||
"mode": "z",
|
||||
},
|
||||
|
@ -344,7 +324,6 @@ def docker_minion_a1(
|
|||
salt_factories,
|
||||
config,
|
||||
syndic_network,
|
||||
source_path,
|
||||
container_image_name,
|
||||
container_python_version,
|
||||
):
|
||||
|
@ -358,7 +337,7 @@ def docker_minion_a1(
|
|||
"entrypoint": "python -m http.server",
|
||||
"volumes": {
|
||||
config_dir: {"bind": "/etc/salt", "mode": "z"},
|
||||
source_path: {
|
||||
str(CODE_DIR / "salt"): {
|
||||
"bind": f"/usr/local/lib/python{container_python_version}/site-packages/salt/",
|
||||
"mode": "z",
|
||||
},
|
||||
|
@ -378,7 +357,6 @@ def docker_minion_a2(
|
|||
salt_factories,
|
||||
config,
|
||||
syndic_network,
|
||||
source_path,
|
||||
container_image_name,
|
||||
container_python_version,
|
||||
):
|
||||
|
@ -392,7 +370,7 @@ def docker_minion_a2(
|
|||
"entrypoint": "python -m http.server",
|
||||
"volumes": {
|
||||
config_dir: {"bind": "/etc/salt", "mode": "z"},
|
||||
source_path: {
|
||||
str(CODE_DIR / "salt"): {
|
||||
"bind": f"/usr/local/lib/python{container_python_version}/site-packages/salt/",
|
||||
"mode": "z",
|
||||
},
|
||||
|
@ -412,7 +390,6 @@ def docker_minion_b1(
|
|||
salt_factories,
|
||||
config,
|
||||
syndic_network,
|
||||
source_path,
|
||||
container_image_name,
|
||||
container_python_version,
|
||||
):
|
||||
|
@ -426,7 +403,7 @@ def docker_minion_b1(
|
|||
"entrypoint": "python -m http.server",
|
||||
"volumes": {
|
||||
config_dir: {"bind": "/etc/salt", "mode": "z"},
|
||||
source_path: {
|
||||
str(CODE_DIR / "salt"): {
|
||||
"bind": f"/usr/local/lib/python{container_python_version}/site-packages/salt/",
|
||||
"mode": "z",
|
||||
},
|
||||
|
@ -446,7 +423,6 @@ def docker_minion_b2(
|
|||
salt_factories,
|
||||
config,
|
||||
syndic_network,
|
||||
source_path,
|
||||
container_image_name,
|
||||
container_python_version,
|
||||
):
|
||||
|
@ -460,7 +436,7 @@ def docker_minion_b2(
|
|||
"entrypoint": "python -m http.server",
|
||||
"volumes": {
|
||||
config_dir: {"bind": "/etc/salt", "mode": "z"},
|
||||
source_path: {
|
||||
str(CODE_DIR / "salt"): {
|
||||
"bind": f"/usr/local/lib/python{container_python_version}/site-packages/salt/",
|
||||
"mode": "z",
|
||||
},
|
||||
|
@ -512,23 +488,16 @@ def all_the_docker(
|
|||
# END WORKAROUND
|
||||
for s in (docker_syndic_a, docker_syndic_b):
|
||||
s.run("salt-syndic -d")
|
||||
failure_time = time.time() + 20
|
||||
accept_keys(
|
||||
container=docker_master, required_minions={"minion", "syndic_a", "syndic_b"}
|
||||
)
|
||||
accept_keys(
|
||||
container=docker_syndic_a, required_minions={"minion_a1", "minion_a2"}
|
||||
)
|
||||
accept_keys(
|
||||
container=docker_syndic_b, required_minions={"minion_b1", "minion_b2"}
|
||||
)
|
||||
for tries in range(30):
|
||||
res = docker_master.run(r"salt \* test.ping -t20 --out=json")
|
||||
failure_time = time.time() + (5 * 60)
|
||||
results = None
|
||||
while time.time() < failure_time:
|
||||
res = docker_master.run(r"salt \* test.ping -t10 --out=json")
|
||||
results = json_output_to_dict(res.stdout)
|
||||
if set(results).issuperset(
|
||||
["minion", "minion_a1", "minion_a2", "minion_b1", "minion_b2"]
|
||||
):
|
||||
break
|
||||
time.sleep(5)
|
||||
else:
|
||||
pytest.skip(f"Missing some minions: {sorted(results)}")
|
||||
|
||||
|
@ -551,10 +520,10 @@ def all_the_docker(
|
|||
# res = container.run('rm -rfv /etc/salt/')
|
||||
# print(container)
|
||||
# print(res.stdout)
|
||||
except docker.errors.APIError as e:
|
||||
except docker.errors.APIError as exc:
|
||||
# if the container isn't running, there's not thing we can do
|
||||
# at this point.
|
||||
print(f"Docker failed removing /etc/salt: {e}")
|
||||
log.info(f"Docker failed removing /etc/salt: %s", exc)
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
|
@ -783,12 +752,12 @@ def test_root_user_should_be_able_to_call_any_and_all_minions_with_any_and_all_c
|
|||
):
|
||||
target, expected_minions = all_the_minions
|
||||
res = docker_master.run(
|
||||
f"salt {target} {all_the_commands} -t 20 --out=json",
|
||||
f"salt {target} {all_the_commands} -t 10 --out=json",
|
||||
)
|
||||
if "jid does not exist" in (res.stderr or ""):
|
||||
# might be flaky, let's retry
|
||||
res = docker_master.run(
|
||||
f"salt {target} {all_the_commands} -t 20 --out=json",
|
||||
f"salt {target} {all_the_commands} -t 10 --out=json",
|
||||
)
|
||||
results = json_output_to_dict(res.stdout)
|
||||
assert sorted(results) == expected_minions, res.stdout
|
||||
|
@ -799,7 +768,7 @@ def test_eauth_user_should_be_able_to_target_valid_minions_with_valid_command(
|
|||
):
|
||||
target, expected_minions = eauth_valid_minions
|
||||
res = docker_master.run(
|
||||
f"salt -a pam --username bob --password '' {target} {eauth_valid_commands} {eauth_valid_arguments} -t 20 --out=json",
|
||||
f"salt -a pam --username bob --password '' {target} {eauth_valid_commands} {eauth_valid_arguments} -t 10 --out=json",
|
||||
)
|
||||
results = json_output_to_dict(res.stdout)
|
||||
assert sorted(results) == expected_minions, res.stdout
|
||||
|
@ -809,7 +778,7 @@ def test_eauth_user_should_not_be_able_to_target_invalid_minions(
|
|||
eauth_blocked_minions, docker_master, docker_minions
|
||||
):
|
||||
res = docker_master.run(
|
||||
f"salt -a pam --username bob --password '' {eauth_blocked_minions} file.touch /tmp/bad_bad_file.txt -t 20 --out=json",
|
||||
f"salt -a pam --username bob --password '' {eauth_blocked_minions} file.touch /tmp/bad_bad_file.txt -t 10 --out=json",
|
||||
)
|
||||
assert "Authorization error occurred." == res.data or res.data is None
|
||||
for minion in docker_minions:
|
||||
|
@ -824,7 +793,7 @@ def test_eauth_user_should_not_be_able_to_target_valid_minions_with_invalid_comm
|
|||
):
|
||||
tgt, _ = eauth_valid_minions
|
||||
res = docker_master.run(
|
||||
f"salt -a pam --username bob --password '' {tgt} {eauth_invalid_commands} -t 20 --out=json",
|
||||
f"salt -a pam --username bob --password '' {tgt} {eauth_invalid_commands} -t 10 --out=json",
|
||||
)
|
||||
results = json_output_to_dict(res.stdout)
|
||||
assert "Authorization error occurred" in res.stdout
|
||||
|
@ -837,7 +806,7 @@ def test_eauth_user_should_not_be_able_to_target_valid_minions_with_valid_comman
|
|||
):
|
||||
tgt, _ = eauth_valid_minions
|
||||
res = docker_master.run(
|
||||
f"salt -a pam --username bob --password '' -C '{tgt}' {eauth_valid_commands} {eauth_invalid_arguments} -t 20 --out=json",
|
||||
f"salt -a pam --username bob --password '' -C '{tgt}' {eauth_valid_commands} {eauth_invalid_arguments} -t 10 --out=json",
|
||||
)
|
||||
results = json_output_to_dict(res.stdout)
|
||||
assert "Authorization error occurred" in res.stdout
|
||||
|
@ -850,7 +819,7 @@ def test_invalid_eauth_user_should_not_be_able_to_do_anything(
|
|||
# TODO: Do we really need to run all of these tests for the invalid user? Maybe not! -W. Werner, 2022-12-01
|
||||
tgt, _ = eauth_valid_minions
|
||||
res = docker_master.run(
|
||||
f"salt -a pam --username badguy --password '' -C '{tgt}' {eauth_valid_commands} {eauth_valid_arguments} -t 20 --out=json",
|
||||
f"salt -a pam --username badguy --password '' -C '{tgt}' {eauth_valid_commands} {eauth_valid_arguments} -t 10 --out=json",
|
||||
)
|
||||
results = json_output_to_dict(res.stdout)
|
||||
assert sorted(results) == []
|
||||
|
@ -861,7 +830,7 @@ def test_root_should_be_able_to_use_comprehensive_targeting(
|
|||
):
|
||||
tgt, expected_minions = comprehensive_minion_targeting
|
||||
res = docker_master.run(
|
||||
f"salt -C '{tgt}' test.version -t 20 --out=json",
|
||||
f"salt -C '{tgt}' test.version -t 10 --out=json",
|
||||
)
|
||||
results = json_output_to_dict(res.stdout)
|
||||
assert sorted(results) == expected_minions
|
||||
|
@ -872,7 +841,7 @@ def test_eauth_user_should_be_able_to_target_valid_minions_with_valid_commands_c
|
|||
):
|
||||
tgt, expected_minions = valid_eauth_comprehensive_minion_targeting
|
||||
res = docker_master.run(
|
||||
f"salt -a pam --username bob --password '' -C '{tgt}' test.version -t 20 --out=json",
|
||||
f"salt -a pam --username bob --password '' -C '{tgt}' test.version -t 10 --out=json",
|
||||
)
|
||||
results = json_output_to_dict(res.stdout)
|
||||
assert sorted(results) == expected_minions
|
||||
|
@ -882,7 +851,7 @@ def test_eauth_user_with_invalid_comprehensive_targeting_should_auth_failure(
|
|||
invalid_comprehensive_minion_targeting, docker_master
|
||||
):
|
||||
res = docker_master.run(
|
||||
f"salt -a pam --username fnord --password '' -C '{invalid_comprehensive_minion_targeting}' test.version -t 20 --out=json",
|
||||
f"salt -a pam --username fnord --password '' -C '{invalid_comprehensive_minion_targeting}' test.version -t 10 --out=json",
|
||||
)
|
||||
results = json_output_to_dict(res.stdout)
|
||||
assert "Authorization error occurred" in res.stdout
|
||||
|
|
Loading…
Add table
Reference in a new issue