mirror of
https://github.com/Katakate/k7.git
synced 2026-09-22 01:53:19 +00:00
13fafe0997
Per-key node pins, k7d-fc pause/resume/exec, and HA-soak fixes. Playbook pins k7d 0.7.0. GitHub .deb, Launchpad PPA, and PyPI k7-sdk are 0.4.0.
242 lines
7.7 KiB
Python
242 lines
7.7 KiB
Python
"""Unit tests for ``k7 install`` TLS flags and the TLS manifest renderer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from typer.testing import CliRunner
|
|
|
|
from k7.cli.k7 import app
|
|
from k7.core.models import OperationResult
|
|
from k7.deploy.k7_api_tls import CADDY_IMAGE, CADDY_PIN, render_caddyfile, render_deployment, render_service
|
|
|
|
runner = CliRunner()
|
|
|
|
_PLAYBOOK = (Path(__file__).resolve().parents[2] / "src/k7/deploy/k7-install-node.yaml").read_text()
|
|
_HTTP_DEPLOYMENT = (Path(__file__).resolve().parents[2] / "src/k7/deploy/manifests/k7-api/deployment.yaml").read_text()
|
|
_HTTP_SERVICE = (Path(__file__).resolve().parents[2] / "src/k7/deploy/manifests/k7-api/service.yaml").read_text()
|
|
_CLUSTERROLE = (Path(__file__).resolve().parents[2] / "src/k7/deploy/manifests/k7-api/clusterrole.yaml").read_text()
|
|
|
|
|
|
def _install(args: list[str]):
|
|
if "--backend" not in args and "-b" not in args:
|
|
args = ["--backend", "kql", *args]
|
|
with patch("k7.cli.k7.K7Core") as core_cls:
|
|
install = MagicMock(return_value=OperationResult(success=True, message="ok"))
|
|
core_cls.return_value.install_node = install
|
|
result = runner.invoke(app, ["--core", "install", *args])
|
|
return result, install
|
|
|
|
|
|
def _extra_vars(install: MagicMock) -> dict:
|
|
return install.call_args.kwargs["extra_vars"]
|
|
|
|
|
|
def test_default_install_is_https():
|
|
result, install = _install([])
|
|
assert result.exit_code == 0, result.output
|
|
ev = _extra_vars(install)
|
|
assert ev["k7_api_insecure_http"] == "false"
|
|
assert ev["k7_api_hostname"] == ""
|
|
assert ev["k7_api_tls_cert_src"] == ""
|
|
assert ev["k7_api_tls_key_src"] == ""
|
|
|
|
|
|
def test_insecure_http_is_forwarded():
|
|
result, install = _install(["--api-insecure-http"])
|
|
assert result.exit_code == 0, result.output
|
|
assert _extra_vars(install)["k7_api_insecure_http"] == "true"
|
|
|
|
|
|
def test_insecure_http_with_allowlist_warns():
|
|
result, install = _install(["--api-insecure-http", "--api-allow-cidr", "203.0.113.4/32"])
|
|
assert result.exit_code == 0, result.output
|
|
assert "keys still travel in cleartext" in result.output
|
|
ev = _extra_vars(install)
|
|
assert ev["k7_api_insecure_http"] == "true"
|
|
assert ev["k7_api_allow_cidrs"] == "203.0.113.4/32"
|
|
|
|
|
|
def test_hostname_and_insecure_http_fail_before_ansible():
|
|
result, install = _install(["--api-insecure-http", "--api-hostname", "api.example.com"])
|
|
assert result.exit_code != 0
|
|
assert "--api-insecure-http cannot be combined" in result.output
|
|
install.assert_not_called()
|
|
|
|
|
|
def test_hostname_and_operator_cert_fail_before_ansible(tmp_path: Path):
|
|
cert = tmp_path / "server.crt"
|
|
key = tmp_path / "server.key"
|
|
cert.write_text("x")
|
|
key.write_text("y")
|
|
result, install = _install(
|
|
["--api-hostname", "api.example.com", "--api-tls-cert", str(cert), "--api-tls-key", str(key)]
|
|
)
|
|
assert result.exit_code != 0
|
|
assert "--api-hostname cannot be combined" in result.output
|
|
install.assert_not_called()
|
|
|
|
|
|
def test_hostname_must_be_a_dns_name():
|
|
result, install = _install(["--api-hostname", "203.0.113.10"])
|
|
assert result.exit_code != 0
|
|
assert "must be a DNS name" in result.output
|
|
install.assert_not_called()
|
|
|
|
|
|
def test_cert_without_key_fails():
|
|
result, install = _install(["--api-tls-cert", "/tmp/server.crt"])
|
|
assert result.exit_code != 0
|
|
assert "must be passed together" in result.output
|
|
install.assert_not_called()
|
|
|
|
|
|
def test_mismatched_cert_key_fails_before_ansible(tmp_path: Path):
|
|
cert = tmp_path / "a.crt"
|
|
key = tmp_path / "b.key"
|
|
subprocess.run(
|
|
[
|
|
"openssl",
|
|
"req",
|
|
"-x509",
|
|
"-newkey",
|
|
"rsa:2048",
|
|
"-nodes",
|
|
"-days",
|
|
"1",
|
|
"-subj",
|
|
"/CN=a",
|
|
"-keyout",
|
|
str(tmp_path / "a.key"),
|
|
"-out",
|
|
str(cert),
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
)
|
|
subprocess.run(
|
|
[
|
|
"openssl",
|
|
"req",
|
|
"-x509",
|
|
"-newkey",
|
|
"rsa:2048",
|
|
"-nodes",
|
|
"-days",
|
|
"1",
|
|
"-subj",
|
|
"/CN=b",
|
|
"-keyout",
|
|
str(key),
|
|
"-out",
|
|
str(tmp_path / "b.crt"),
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
)
|
|
result, install = _install(["--api-tls-cert", str(cert), "--api-tls-key", str(key)])
|
|
assert result.exit_code != 0
|
|
assert "not a matching pair" in result.output
|
|
install.assert_not_called()
|
|
|
|
|
|
def test_matching_cert_key_is_forwarded(tmp_path: Path):
|
|
cert = tmp_path / "server.crt"
|
|
key = tmp_path / "server.key"
|
|
subprocess.run(
|
|
[
|
|
"openssl",
|
|
"req",
|
|
"-x509",
|
|
"-newkey",
|
|
"rsa:2048",
|
|
"-nodes",
|
|
"-days",
|
|
"1",
|
|
"-subj",
|
|
"/CN=k7-api",
|
|
"-keyout",
|
|
str(key),
|
|
"-out",
|
|
str(cert),
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
)
|
|
result, install = _install(["--api-tls-cert", str(cert), "--api-tls-key", str(key)])
|
|
assert result.exit_code == 0, result.output
|
|
ev = _extra_vars(install)
|
|
assert ev["k7_api_tls_cert_src"] == str(cert.resolve())
|
|
assert ev["k7_api_tls_key_src"] == str(key.resolve())
|
|
|
|
|
|
def test_acme_staging_requires_hostname():
|
|
result, install = _install(["--api-acme-staging"])
|
|
assert result.exit_code != 0
|
|
assert "--api-acme-staging requires --api-hostname" in result.output
|
|
install.assert_not_called()
|
|
|
|
|
|
def test_renderer_adds_caddy_and_hides_ca_key():
|
|
out = render_deployment(_HTTP_DEPLOYMENT, acme=False)
|
|
assert "name: caddy" in out
|
|
assert CADDY_IMAGE in out
|
|
assert "secretName: k7-api-tls" in out
|
|
assert "mountPath: /etc/k7/tls" in out
|
|
assert "name: tls-hide" in out
|
|
assert "path: /health" in out
|
|
assert "port: 8000" in out
|
|
assert "ca.key" not in out
|
|
assert "hostPort: 80" not in out
|
|
|
|
|
|
def test_renderer_acme_exposes_host_port_80_and_no_tls_secret():
|
|
out = render_deployment(_HTTP_DEPLOYMENT, acme=True)
|
|
assert "hostPort: 80" in out
|
|
assert "secretName: k7-api-tls" not in out
|
|
|
|
|
|
def test_renderer_service_targets_8443():
|
|
assert "targetPort: 8443" in render_service(_HTTP_SERVICE)
|
|
assert "nodePort: 31007" in render_service(_HTTP_SERVICE)
|
|
|
|
|
|
def test_caddyfile_files_mode_disables_auto_https():
|
|
text = render_caddyfile(acme=False)
|
|
assert "auto_https off" in text
|
|
assert "tls /certs/tls.crt /certs/tls.key" in text
|
|
assert "reverse_proxy 127.0.0.1:8000" in text
|
|
|
|
|
|
def test_caddyfile_acme_uses_hostname_and_optional_staging():
|
|
text = render_caddyfile(acme=True, hostname="api.example.com", staging=True)
|
|
assert "api.example.com {" in text
|
|
assert "https_port 8443" in text
|
|
assert "http_port 80" in text
|
|
assert "acme-staging-v02" in text
|
|
with pytest.raises(ValueError, match="hostname"):
|
|
render_caddyfile(acme=True, hostname="")
|
|
|
|
|
|
def test_playbook_caddy_pin_matches_the_renderer():
|
|
assert f'k7_caddy_image: "{CADDY_PIN}"' in _PLAYBOOK
|
|
assert CADDY_IMAGE in render_deployment(_HTTP_DEPLOYMENT, acme=False)
|
|
|
|
|
|
def test_playbook_keeps_the_20d_reserved_entities():
|
|
assert '- fromEntities: ["host", "remote-node", "health", "kube-apiserver"]' in _PLAYBOOK
|
|
|
|
|
|
def test_playbook_does_not_enable_a_host_firewall():
|
|
assert "enable-host-firewall" not in _PLAYBOOK
|
|
assert "CiliumHostFirewall" not in _PLAYBOOK
|
|
|
|
|
|
def test_clusterrole_can_manage_expose_services():
|
|
# Delete always removes `{name}-expose`. Without this verb the API
|
|
# gets 403 (not 404) and every sandbox delete through k7-api is 400.
|
|
assert '"services"' in _CLUSTERROLE
|