This commit is contained in:
deamonkai
2026-01-23 12:11:21 -06:00
commit fc94008530
16494 changed files with 2974672 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
import base64
import pytest
import responses
from certctl.ca.adcs import AdcsAdapter, AdcsConfig, _b64_to_pem, _parse_req_id
def test_parse_req_id_from_html():
body = "some html certnew.cer?ReqID=123&something"
assert _parse_req_id(body) == "123"
def test_b64_to_pem_wraps():
raw = base64.b64encode(b"dummy").decode("ascii")
pem = _b64_to_pem(raw)
assert "BEGIN CERTIFICATE" in pem
assert "END CERTIFICATE" in pem
@responses.activate
def test_adcs_submit_and_collect():
config = AdcsConfig(
base_url="https://adcs.example/certsrv",
username="user",
password="pass",
verify=False,
)
adapter = AdcsAdapter(config)
responses.add(
responses.POST,
"https://adcs.example/certsrv/certfnsh.asp",
body="certnew.cer?ReqID=456&Enc=b64",
status=200,
)
cert_b64 = base64.b64encode(b"fakecert").decode("ascii")
responses.add(
responses.GET,
"https://adcs.example/certsrv/certnew.cer",
body=cert_b64,
status=200,
)
submit = adapter.submit_csr("CSRDATA")
assert submit.request_id == "456"
cert = adapter.collect_certificate("456")
assert "BEGIN CERTIFICATE" in cert

View File

@@ -0,0 +1,28 @@
from certctl.scripts import csr_submit
def test_parse_csr_for_auto_ca(monkeypatch, tmp_path):
csr_path = tmp_path / "req.csr"
csr_path.write_text("dummy", encoding="utf-8")
def fake_run(cmd, stdout, stderr, check, text):
class Result:
def __init__(self):
self.stdout = (
"Subject: C=US, ST=AL, CN=api.rgbk.com\n"
"X509v3 Subject Alternative Name:\n"
" DNS:api.rgbk.com, DNS:example.com\n"
)
return Result()
monkeypatch.setattr(csr_submit.subprocess, "run", fake_run)
cn, san = csr_submit._parse_csr_subject_and_sans(str(csr_path))
assert cn == "api.rgbk.com"
assert "DNS:api.rgbk.com" in san
def test_choose_ca_prefers_adcs_for_rgbk():
assert csr_submit._choose_ca("svc.rgbk.com", None) == "adcs"
assert csr_submit._choose_ca("example.com", "DNS:api.rgbk.com") == "adcs"
assert csr_submit._choose_ca("example.com", "DNS:api.example.com") == "sectigo"

View File

@@ -0,0 +1,153 @@
from pathlib import Path
import pytest
from certctl.scripts import csr_create, keygen
from certctl.scripts import keycsr
def test_keygen_rsa_builds_openssl_command(monkeypatch, tmp_path):
captured = {}
def fake_run(cmd, env=None):
captured["cmd"] = cmd
if "-out" in cmd:
out_path = Path(cmd[cmd.index("-out") + 1])
out_path.write_text("dummy", encoding="utf-8")
monkeypatch.setattr(keygen, "_run", fake_run)
monkeypatch.setattr(keygen, "_require_openssl", lambda: "/usr/bin/openssl")
args = keygen.build_arg_parser().parse_args(
[
"--cn",
"example.com",
"--kind",
"rsa",
"--out",
str(tmp_path),
"--stamp",
"20260101-120000",
"--passphrase",
"secret",
]
)
keygen.run(args)
cmd = captured["cmd"]
assert "genpkey" in cmd
assert "RSA" in cmd
assert f"rsa_keygen_bits:{keygen.DEFAULT_RSA_BITS}" in cmd
assert "-aes-256-cbc" in cmd
def test_keygen_ecdsa_builds_openssl_command(monkeypatch, tmp_path):
captured = {}
def fake_run(cmd, env=None):
captured["cmd"] = cmd
if "-out" in cmd:
out_path = Path(cmd[cmd.index("-out") + 1])
out_path.write_text("dummy", encoding="utf-8")
monkeypatch.setattr(keygen, "_run", fake_run)
monkeypatch.setattr(keygen, "_require_openssl", lambda: "/usr/bin/openssl")
args = keygen.build_arg_parser().parse_args(
[
"--cn",
"example.com",
"--kind",
"ecdsa",
"--out",
str(tmp_path),
"--passphrase",
"secret",
]
)
keygen.run(args)
cmd = captured["cmd"]
assert "genpkey" in cmd
assert "EC" in cmd
assert f"ec_paramgen_curve:{keygen.DEFAULT_EC_CURVE}" in cmd
assert "-aes-256-cbc" in cmd
def test_csr_create_runs_openssl_with_config(monkeypatch, tmp_path):
captured = {}
config_path = tmp_path / "openssl.cnf"
config_path.write_text("[req]\n", encoding="utf-8")
def fake_run(cmd, env=None):
captured["cmd"] = cmd
def fake_write_config(subject, sans):
return str(config_path)
monkeypatch.setattr(csr_create, "_run", fake_run)
monkeypatch.setattr(csr_create, "_require_openssl", lambda: "/usr/bin/openssl")
monkeypatch.setattr(csr_create, "_write_openssl_config", fake_write_config)
key_path = tmp_path / "key.pem"
key_path.write_text("dummy", encoding="utf-8")
out_path = tmp_path / "req.csr"
args = csr_create.build_arg_parser().parse_args(
[
"--key-file",
str(key_path),
"--cn",
"example.com",
"--out",
str(tmp_path),
"--stamp",
"20260101-120000",
"--passphrase",
"secret",
"--san",
"DNS:www.example.com, IP:192.168.0.1",
]
)
csr_create.run(args)
cmd = captured["cmd"]
assert cmd[0] == "openssl"
assert "req" in cmd
out_arg = cmd[cmd.index("-out") + 1]
assert out_arg.startswith(str(tmp_path))
assert str(config_path) in cmd
assert not config_path.exists()
def test_keycsr_uses_shared_stamp(monkeypatch, tmp_path):
calls = {"key": None, "csr": None}
def fake_generate(kind, path, passphrase):
calls["key"] = path
def fake_csr_run(args):
calls["csr"] = args
return 0
monkeypatch.setattr(keygen, "_generate_key", fake_generate)
monkeypatch.setattr(csr_create, "run", fake_csr_run)
monkeypatch.setattr(keygen, "_get_passphrase", lambda args: "secret")
args = keycsr.build_arg_parser().parse_args(
[
"--cn",
"example.com",
"--kind",
"rsa",
"--out",
str(tmp_path),
"--stamp",
"20260101-120000",
]
)
keycsr.run(args)
assert calls["key"].name == "example.com-20260101-120000.key"
assert calls["csr"].stamp == "20260101-120000"

View File

@@ -0,0 +1,259 @@
import json
from pathlib import Path
import responses
from certctl.scripts.nsconsole_certpoll import build_arg_parser, run
def _args(args):
parser = build_arg_parser()
return parser.parse_args(args)
def _add_login(resps, base):
resps.add(
responses.POST,
f"{base}/nitro/v2/config/login",
json={"login": [{"sessionid": "token"}]},
status=200,
)
def _add_inventory(resps, base):
resps.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"action": "inventory"})],
json={"errorcode": 0, "message": "Done"},
status=200,
)
def _add_certkey_get(resps, base, items):
resps.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"errorcode": 0, "ns_ssl_certkey": items},
status=200,
)
@responses.activate
def test_certpoll_filters_in_use(monkeypatch, tmp_path):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
_add_certkey_get(
responses,
base,
[
{
"certkeypair_name": "active-bound",
"no_of_bound_entities": 2,
"days_to_expiry": 10,
"entity_binding_arr": [
{"entity_name": "lb1", "entity_type": "sslvserver", "display_name": "adc-1"}
],
},
{"certkeypair_name": "active-unbound", "certkey_status": "ACTIVE", "days_to_expiry": 15},
{"certkeypair_name": "inactive", "certkey_status": "INACTIVE", "days_to_expiry": 20},
],
)
out_path = tmp_path / "report.json"
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--format",
"json",
"--out",
str(out_path),
]
)
assert run(args) == 0
payload = json.loads(out_path.read_text(encoding="utf-8"))
assert payload["count"] == 2
names = {item["certkeypair_name"] for item in payload["items"]}
assert names == {"active-bound", "active-unbound"}
bound = next(item for item in payload["items"] if item["certkeypair_name"] == "active-bound")
assert bound["binding_count"] == 1
assert bound["binding_entities"] == "lb1"
assert bound["binding_types"] == "sslvserver"
assert bound["binding_devices"] == "adc-1"
@responses.activate
def test_certpoll_includes_cert_store_mapping(monkeypatch, tmp_path):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
_add_certkey_get(
responses,
base,
[
{
"certkeypair_name": "mapped",
"certkey_status": "ACTIVE",
"cert_store_id": "cert-1",
}
],
)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/cert_store_mapping",
json={
"errorcode": 0,
"cert_store_mapping": [
{
"cert_id": "cert-1",
"entity_name": "svc-1",
"entity_type": "service",
"instance_display_name": "adc-2",
"instance_ip": "10.0.0.1",
}
],
},
status=200,
)
out_path = tmp_path / "report.json"
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--format",
"json",
"--out",
str(out_path),
"--include-mappings",
]
)
assert run(args) == 0
payload = json.loads(out_path.read_text(encoding="utf-8"))
item = payload["items"][0]
assert item["mapping_count"] == 1
assert item["mapping_entities"] == "svc-1"
assert item["mapping_entity_types"] == "service"
assert item["mapping_instances"] == "adc-2"
assert item["mapping_instance_ips"] == "10.0.0.1"
@responses.activate
def test_certpoll_filters_by_expiry(monkeypatch, tmp_path):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
_add_certkey_get(
responses,
base,
[
{"certkeypair_name": "soon", "certkey_status": "ACTIVE", "days_to_expiry": 10},
{"certkeypair_name": "later", "certkey_status": "ACTIVE", "days_to_expiry": 45},
],
)
out_path = tmp_path / "report.json"
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--format",
"json",
"--out",
str(out_path),
"--expires-within",
"30",
]
)
assert run(args) == 0
payload = json.loads(out_path.read_text(encoding="utf-8"))
names = {item["certkeypair_name"] for item in payload["items"]}
assert names == {"soon"}
@responses.activate
def test_certpoll_inventory_and_all(monkeypatch, capsys):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
_add_inventory(responses, base)
_add_certkey_get(
responses,
base,
[
{"certkeypair_name": "active", "certkey_status": "ACTIVE", "days_to_expiry": 10},
{"certkeypair_name": "inactive", "certkey_status": "INACTIVE", "days_to_expiry": 20},
],
)
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--inventory",
"--all",
]
)
assert run(args) == 0
stdout = capsys.readouterr().out
assert "certkeypair_name" in stdout
assert "active" in stdout
assert "inactive" in stdout
assert any("action=inventory" in call.request.url for call in responses.calls)
@responses.activate
def test_certpoll_uses_config_profile(monkeypatch, tmp_path):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
config_path = tmp_path / "certctl.json"
config_path.write_text(
json.dumps(
{
"defaults": {"format": "json", "inventory": True},
"consoles": {
"test": {
"url": base,
"user": "nsroot",
"insecure": True,
}
},
}
),
encoding="utf-8",
)
_add_login(responses, base)
_add_inventory(responses, base)
_add_certkey_get(
responses,
base,
[{"certkeypair_name": "active", "certkey_status": "ACTIVE", "days_to_expiry": 10}],
)
out_path = tmp_path / "report.json"
args = _args(["--config", str(config_path), "--profile", "test", "--out", str(out_path)])
assert run(args) == 0
payload = json.loads(out_path.read_text(encoding="utf-8"))
assert payload["count"] == 1
assert payload["items"][0]["certkeypair_name"] == "active"

View File

@@ -0,0 +1,226 @@
import json
import responses
from certctl.scripts.nsconsole_certpoll_all import build_arg_parser, run
def _args(args):
parser = build_arg_parser()
return parser.parse_args(args)
def _add_login(resps, base):
resps.add(
responses.POST,
f"{base}/nitro/v2/config/login",
json={"login": [{"sessionid": "token"}]},
status=200,
)
def _add_certkey_get(resps, base, items):
resps.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"errorcode": 0, "ns_ssl_certkey": items},
status=200,
)
@responses.activate
def test_poll_all_from_config(tmp_path, monkeypatch):
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
config_path = tmp_path / "certctl.json"
config_path.write_text(
json.dumps(
{
"defaults": {"format": "json"},
"consoles": {
"test": {"url": "https://console-test.example", "user": "nsroot", "insecure": True},
"prod": {"url": "https://console-prod.example", "user": "nsroot", "insecure": True},
},
}
),
encoding="utf-8",
)
_add_login(responses, "https://console-test.example")
_add_certkey_get(
responses,
"https://console-test.example",
[{"certkeypair_name": "test-cert", "certkey_status": "ACTIVE"}],
)
_add_login(responses, "https://console-prod.example")
_add_certkey_get(
responses,
"https://console-prod.example",
[{"certkeypair_name": "prod-cert", "certkey_status": "ACTIVE"}],
)
out_dir = tmp_path / "reports"
args = _args(["--config", str(config_path), "--format", "json", "--out-dir", str(out_dir)])
assert run(args) == 0
test_payload = json.loads((out_dir / "test.json").read_text(encoding="utf-8"))
prod_payload = json.loads((out_dir / "prod.json").read_text(encoding="utf-8"))
assert test_payload["count"] == 1
assert prod_payload["count"] == 1
@responses.activate
def test_poll_all_merge_json(tmp_path, monkeypatch):
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
config_path = tmp_path / "certctl.json"
config_path.write_text(
json.dumps(
{
"defaults": {"format": "json"},
"consoles": {
"test": {"url": "https://console-test.example", "user": "nsroot", "insecure": True},
"prod": {"url": "https://console-prod.example", "user": "nsroot", "insecure": True},
},
}
),
encoding="utf-8",
)
_add_login(responses, "https://console-test.example")
_add_certkey_get(
responses,
"https://console-test.example",
[{"certkeypair_name": "test-cert", "certkey_status": "ACTIVE"}],
)
_add_login(responses, "https://console-prod.example")
_add_certkey_get(
responses,
"https://console-prod.example",
[{"certkeypair_name": "prod-cert", "certkey_status": "ACTIVE"}],
)
out_dir = tmp_path / "reports"
args = _args(
[
"--config",
str(config_path),
"--format",
"json",
"--out-dir",
str(out_dir),
"--merge",
]
)
assert run(args) == 0
merged = json.loads((out_dir / "all.json").read_text(encoding="utf-8"))
assert merged["count"] == 2
profiles = {item["profile"] for item in merged["items"]}
assert profiles == {"test", "prod"}
@responses.activate
def test_poll_all_merge_csv(tmp_path, monkeypatch):
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
config_path = tmp_path / "certctl.json"
config_path.write_text(
json.dumps(
{
"defaults": {"format": "json"},
"consoles": {
"test": {"url": "https://console-test.example", "user": "nsroot", "insecure": True},
"prod": {"url": "https://console-prod.example", "user": "nsroot", "insecure": True},
},
}
),
encoding="utf-8",
)
_add_login(responses, "https://console-test.example")
_add_certkey_get(
responses,
"https://console-test.example",
[{"certkeypair_name": "test-cert", "certkey_status": "ACTIVE"}],
)
_add_login(responses, "https://console-prod.example")
_add_certkey_get(
responses,
"https://console-prod.example",
[{"certkeypair_name": "prod-cert", "certkey_status": "ACTIVE"}],
)
out_dir = tmp_path / "reports"
args = _args(
[
"--config",
str(config_path),
"--format",
"csv",
"--out-dir",
str(out_dir),
"--merge",
]
)
assert run(args) == 0
csv_path = out_dir / "all.csv"
lines = csv_path.read_text(encoding="utf-8").strip().splitlines()
assert lines
assert "profile" in lines[0]
assert any("test" in line for line in lines[1:])
assert any("prod" in line for line in lines[1:])
@responses.activate
def test_poll_all_rollup_json(tmp_path, monkeypatch):
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
config_path = tmp_path / "certctl.json"
config_path.write_text(
json.dumps(
{
"defaults": {"format": "json"},
"consoles": {
"test": {"url": "https://console-test.example", "user": "nsroot", "insecure": True},
"prod": {"url": "https://console-prod.example", "user": "nsroot", "insecure": True},
},
}
),
encoding="utf-8",
)
_add_login(responses, "https://console-test.example")
_add_certkey_get(
responses,
"https://console-test.example",
[{"certkeypair_name": "test-cert", "certkey_status": "ACTIVE", "subject": "CN=example.com"}],
)
_add_login(responses, "https://console-prod.example")
_add_certkey_get(
responses,
"https://console-prod.example",
[{"certkeypair_name": "prod-cert", "certkey_status": "ACTIVE", "subject": "CN=example.com"}],
)
out_dir = tmp_path / "reports"
args = _args(
[
"--config",
str(config_path),
"--format",
"json",
"--out-dir",
str(out_dir),
"--rollup",
]
)
assert run(args) == 0
rollup = json.loads((out_dir / "rollup_subjects.json").read_text(encoding="utf-8"))
assert rollup["count_subjects"] == 1
subject = rollup["subjects"][0]
assert subject["subject"] == "CN=example.com"
assert set(subject["profiles"]) == {"test", "prod"}

View File

@@ -0,0 +1,490 @@
import responses
from certctl.scripts import nsconsole_deploy
def _args(args):
return nsconsole_deploy.build_arg_parser().parse_args(args)
def _add_login(resps, base):
resps.add(
responses.POST,
f"{base}/nitro/v2/config/login",
json={"login": [{"sessionid": "token"}]},
status=200,
)
def _add_certkey(resps, base, name, binding):
resps.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": f"certkeypair_name:{name}"})],
json={
"errorcode": 0,
"ns_ssl_certkey": [{"certkeypair_name": name, "id": "cert-id", "entity_binding_arr": [binding]}],
},
status=200,
)
@responses.activate
def test_deploy_binds_and_links(tmp_path, monkeypatch):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
_add_certkey(responses, base, "target", {"certkeypair_name": "old", "ns_ip_address": "10.0.0.1", "id": "123"})
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"errorcode": 0, "message": "Done"},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certlink",
json={"errorcode": 0, "message": "Done"},
status=200,
)
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--certkeypair",
"target",
"--ca-certkey",
"root_ca",
]
)
assert nsconsole_deploy.run(args) == 0
assert any("action=bind_cert" in call.request.url for call in responses.calls)
assert any("action=link" in call.request.url for call in responses.calls)
@responses.activate
def test_deploy_links_from_chain(tmp_path, monkeypatch):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": "certkeypair_name:source"})],
json={
"errorcode": 0,
"ns_ssl_certkey": [
{
"certkeypair_name": "source",
"entity_binding_arr": [{"ns_ip_address": "10.0.0.1", "id": "123"}],
"certkeychain": [{"cert_name": "root_ca"}],
}
],
},
status=200,
)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": "certkeypair_name:target"})],
json={"errorcode": 0, "ns_ssl_certkey": [{"certkeypair_name": "target", "id": "target-id"}]},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"errorcode": 0, "message": "Done"},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certlink",
json={"errorcode": 0, "message": "Done"},
status=200,
)
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--certkeypair",
"target",
"--source-certkeypair",
"source",
]
)
assert nsconsole_deploy.run(args) == 0
assert any("action=link" in call.request.url for call in responses.calls)
@responses.activate
def test_deploy_falls_back_to_modify(monkeypatch):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
_add_certkey(responses, base, "target", {"certkeypair_name": "old", "ns_ip_address": "10.0.0.1", "id": "123"})
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"message": "API not supported in this deployment"},
status=405,
)
responses.add(
responses.PUT,
f"{base}/nitro/v2/config/ns_ssl_certkey/cert-id",
json={"errorcode": 0, "message": "Done"},
status=200,
)
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--certkeypair",
"target",
"--no-link-ca",
]
)
assert nsconsole_deploy.run(args) == 0
assert any(call.request.method == "PUT" for call in responses.calls)
@responses.activate
def test_deploy_resolves_cn(monkeypatch):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": "certkeypair_name:test.molloyhome.net"})],
json={"errorcode": 0, "ns_ssl_certkey": []},
status=200,
)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"pagesize": "200"})],
json={
"errorcode": 0,
"ns_ssl_certkey": [
{
"certkeypair_name": "test_molloyhome_net",
"id": "cert-id",
"subject": "CN=test.molloyhome.net, O=Example",
"entity_binding_arr": [{"certkeypair_name": "old", "ns_ip_address": "10.0.0.1", "id": "123"}],
}
],
},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"message": "API not supported in this deployment"},
status=405,
)
responses.add(
responses.PUT,
f"{base}/nitro/v2/config/ns_ssl_certkey/cert-id",
json={"errorcode": 0, "message": "Done"},
status=200,
)
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--certkeypair",
"test.molloyhome.net",
"--no-link-ca",
]
)
assert nsconsole_deploy.run(args) == 0
@responses.activate
def test_deploy_syncs_inventory(monkeypatch):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": "certkeypair_name:target"})],
json={"errorcode": 0, "ns_ssl_certkey": []},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"action": "inventory"})],
json={"errorcode": 0, "message": "Done"},
status=200,
)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": "certkeypair_name:target"})],
json={
"errorcode": 0,
"ns_ssl_certkey": [
{
"certkeypair_name": "target",
"id": "cert-id",
"entity_binding_arr": [{"certkeypair_name": "old", "ns_ip_address": "10.0.0.1", "id": "123"}],
}
],
},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"message": "API not supported in this deployment"},
status=405,
)
responses.add(
responses.PUT,
f"{base}/nitro/v2/config/ns_ssl_certkey/cert-id",
json={"errorcode": 0, "message": "Done"},
status=200,
)
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--certkeypair",
"target",
"--sync",
"--no-link-ca",
]
)
assert nsconsole_deploy.run(args) == 0
@responses.activate
def test_deploy_imports_missing_from_adc(monkeypatch):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": "certkeypair_name:test.molloyhome.net"})],
json={"errorcode": 0, "ns_ssl_certkey": []},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"action": "list_entity_cert"})],
json={
"errorcode": 0,
"ns_ssl_certkey": [
{
"certkeypair_name": "test_molloyhome_net",
"subject": "CN=test.molloyhome.net, O=Example",
}
],
},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"errorcode": 0, "message": "Done"},
status=200,
)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": "certkeypair_name:test.molloyhome.net"})],
json={
"errorcode": 0,
"ns_ssl_certkey": [
{
"certkeypair_name": "test.molloyhome.net",
"id": "cert-id",
"entity_binding_arr": [{"certkeypair_name": "old", "ns_ip_address": "10.0.0.1", "id": "123"}],
}
],
},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"message": "API not supported in this deployment"},
status=405,
)
responses.add(
responses.PUT,
f"{base}/nitro/v2/config/ns_ssl_certkey/cert-id",
json={"errorcode": 0, "message": "Done"},
status=200,
)
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--certkeypair",
"test.molloyhome.net",
"--import-missing",
"--adc-ip",
"10.0.0.1",
"--no-link-ca",
]
)
assert nsconsole_deploy.run(args) == 0
@responses.activate
def test_deploy_imports_missing_with_guess_on_unsupported_list(monkeypatch):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": "certkeypair_name:test.molloyhome.net"})],
json={"errorcode": 0, "ns_ssl_certkey": []},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"action": "list_entity_cert"})],
json={"message": "API not supported in this deployment"},
status=405,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"errorcode": 0, "message": "Done"},
status=200,
)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/ns_ssl_certkey",
match=[responses.matchers.query_param_matcher({"filter": "certkeypair_name:test.molloyhome.net"})],
json={
"errorcode": 0,
"ns_ssl_certkey": [
{
"certkeypair_name": "test.molloyhome.net",
"id": "cert-id",
"entity_binding_arr": [{"certkeypair_name": "old", "ns_ip_address": "10.0.0.1", "id": "123"}],
}
],
},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"message": "API not supported in this deployment"},
status=405,
)
responses.add(
responses.PUT,
f"{base}/nitro/v2/config/ns_ssl_certkey/cert-id",
json={"errorcode": 0, "message": "Done"},
status=200,
)
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--certkeypair",
"test.molloyhome.net",
"--import-missing",
"--adc-ip",
"10.0.0.1",
"--no-link-ca",
]
)
assert nsconsole_deploy.run(args) == 0
@responses.activate
def test_deploy_polls_adcs(monkeypatch):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
_add_login(responses, base)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/managed_device",
json={
"errorcode": 0,
"managed_device": [
{
"id": "dev-1",
"display_name": "adc-1",
"ip_address": "10.0.0.1",
"ha_master_state": "Primary",
}
],
},
status=200,
)
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey_policy",
match=[responses.matchers.query_param_matcher({"action": "do_poll"})],
json={"errorcode": 0, "message": "Done"},
status=200,
)
_add_certkey(responses, base, "target", {"certkeypair_name": "old", "ns_ip_address": "10.0.0.1", "id": "123"})
responses.add(
responses.POST,
f"{base}/nitro/v2/config/ns_ssl_certkey",
json={"errorcode": 0, "message": "Done"},
status=200,
)
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--certkeypair",
"target",
"--list-adc",
"menu",
"--poll-adc",
"--no-link-ca",
]
)
monkeypatch.setattr("builtins.input", lambda _: "1")
assert nsconsole_deploy.run(args) == 0

View File

@@ -0,0 +1,67 @@
import json
import responses
from certctl.scripts import nsconsole_deploy
def _args(args):
return nsconsole_deploy.build_arg_parser().parse_args(args)
@responses.activate
def test_list_adc_json(tmp_path, monkeypatch):
base = "https://console.example"
monkeypatch.setenv("CERTCTL_CONSOLE_PASSWORD", "secret")
responses.add(
responses.POST,
f"{base}/nitro/v2/config/login",
json={"login": [{"sessionid": "token"}]},
status=200,
)
responses.add(
responses.GET,
f"{base}/nitro/v2/config/managed_device",
json={
"errorcode": 0,
"managed_device": [
{
"id": "1",
"display_name": "adc-1",
"ip_address": "10.0.0.1",
"device_family": "NetScaler",
"ha_master_state": "Primary",
},
{
"id": "2",
"display_name": "adc-2",
"ip_address": "10.0.0.2",
"device_family": "NetScaler",
"ha_master_state": "Secondary",
},
],
},
status=200,
)
out_path = tmp_path / "devices.json"
args = _args(
[
"--console",
base,
"--user",
"nsroot",
"--certkeypair",
"ignored",
"--list-adc",
"json",
"--list-adc-out",
str(out_path),
]
)
assert nsconsole_deploy.run(args) == 0
data = json.loads(out_path.read_text(encoding="utf-8"))
assert data[0]["ip_address"] == "10.0.0.1"
assert all(row["ha_master_state"] != "Secondary" for row in data)