106 lines
3.6 KiB
Python
106 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from jinja2 import Environment, StrictUndefined
|
|
import yaml
|
|
|
|
import jinjaturtle.core as core
|
|
|
|
|
|
def _render(template: str, variables: dict) -> str:
|
|
env = Environment(undefined=StrictUndefined, keep_trailing_newline=True)
|
|
return env.from_string(template).render(**variables)
|
|
|
|
|
|
def test_sshd_config_parsing_match_sections_repeated_keys_and_roundtrip(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
p = tmp_path / "sshd_config"
|
|
original = (
|
|
"# global sshd settings\n"
|
|
"Port 22\n"
|
|
"ListenAddress 0.0.0.0\n"
|
|
"ListenAddress ::\n"
|
|
"AuthenticationMethods publickey,password publickey,keyboard-interactive\n"
|
|
"Subsystem sftp /usr/lib/openssh/sftp-server # keep sftp\n"
|
|
"Match User deploy Address 10.0.0.0/8\n"
|
|
" X11Forwarding no\n"
|
|
' ForceCommand "internal-sftp -u 0002"\n'
|
|
)
|
|
p.write_text(original, encoding="utf-8")
|
|
|
|
fmt, parsed = core.parse_config(p)
|
|
assert fmt == "ssh"
|
|
|
|
flat = core.flatten_config(fmt, parsed)
|
|
assert (("Port",), "22") in flat
|
|
assert (("ListenAddress", "0"), "0.0.0.0") in flat
|
|
assert (("ListenAddress", "1"), "::") in flat
|
|
assert (
|
|
("AuthenticationMethods",),
|
|
"publickey,password publickey,keyboard-interactive",
|
|
) in flat
|
|
assert (
|
|
("Match", "user_deploy_address_10_0_0_0_8"),
|
|
"User deploy Address 10.0.0.0/8",
|
|
) in flat
|
|
assert (
|
|
("Match", "user_deploy_address_10_0_0_0_8", "ForceCommand"),
|
|
"internal-sftp -u 0002",
|
|
) in flat
|
|
|
|
ansible_yaml = core.generate_ansible_yaml("role", flat)
|
|
variables = yaml.safe_load(ansible_yaml)
|
|
template = core.generate_jinja2_template(
|
|
fmt, parsed, role_prefix="role", original_text=original
|
|
)
|
|
|
|
assert "ListenAddress {{ role_listenaddress_0 }}" in template
|
|
assert "ListenAddress {{ role_listenaddress_1 }}" in template
|
|
assert "Match {{ role_match_user_deploy_address_10_0_0_0_8 }}" in template
|
|
assert (
|
|
'ForceCommand "{{ role_match_user_deploy_address_10_0_0_0_8_forcecommand }}"'
|
|
in template
|
|
)
|
|
assert _render(template, variables) == original
|
|
|
|
|
|
def test_ssh_config_host_and_match_sections_equal_separator(tmp_path: Path) -> None:
|
|
p = tmp_path / "ssh_config"
|
|
original = (
|
|
"Include /etc/ssh/ssh_config.d/*.conf\n"
|
|
"Host bastion *.corp.example\n"
|
|
" User ops\n"
|
|
" ProxyCommand=ssh -W %h:%p jump.example\n"
|
|
'Match exec "test %p = 22"\n'
|
|
" ServerAliveInterval 30\n"
|
|
)
|
|
p.write_text(original, encoding="utf-8")
|
|
|
|
fmt, parsed = core.parse_config(p)
|
|
assert fmt == "ssh"
|
|
|
|
flat = core.flatten_config(fmt, parsed)
|
|
assert (("Host", "bastion_corp_example"), "bastion *.corp.example") in flat
|
|
assert (
|
|
("Host", "bastion_corp_example", "ProxyCommand"),
|
|
"ssh -W %h:%p jump.example",
|
|
) in flat
|
|
assert (("Match", "exec_test_p_22"), 'exec "test %p = 22"') in flat
|
|
|
|
ansible_yaml = core.generate_ansible_yaml("ssh", flat)
|
|
variables = yaml.safe_load(ansible_yaml)
|
|
template = core.generate_jinja2_template(fmt, parsed, role_prefix="ssh")
|
|
|
|
assert "ProxyCommand={{ ssh_host_bastion_corp_example_proxycommand }}" in template
|
|
assert _render(template, variables) == original
|
|
|
|
|
|
def test_ssh_config_snippet_conf_detected_by_content(tmp_path: Path) -> None:
|
|
p = tmp_path / "50-hardening.conf"
|
|
p.write_text("PasswordAuthentication no\nPermitRootLogin no\n", encoding="utf-8")
|
|
|
|
fmt, parsed = core.parse_config(p)
|
|
assert fmt == "ssh"
|
|
assert (("PasswordAuthentication",), "no") in core.flatten_config(fmt, parsed)
|