mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
nodes: per-node default credentials seeded from the template
Add default_username/default_password as controller-only node properties (the netmiko_device_type pattern): they are not sent to the compute, persist with the project topology and can be updated or cleared per node. Creating a node from a template seeds them from the template appliance metadata, and the metadata itself is dropped there so it never leaks into the node properties.
This commit is contained in:
5 files changed
+125
No files matched your search
@@ -143,6 +143,8 @@ def test_json(node, compute):
|
||||
"custom_adapters": [],
|
||||
"console_auto_start": False,
|
||||
"netmiko_device_type": None,
|
||||
"default_username": None,
|
||||
"default_password": None,
|
||||
"ports": [
|
||||
{
|
||||
"adapter_number": 0,
|
||||
@@ -181,6 +183,8 @@ def test_json(node, compute):
|
||||
"tags": [],
|
||||
"console_auto_start": False,
|
||||
"netmiko_device_type": None,
|
||||
"default_username": None,
|
||||
"default_password": None,
|
||||
}
|
||||
|
||||
|
||||
@@ -420,6 +424,49 @@ def test_netmiko_device_type_from_template_kwargs(compute, project):
|
||||
assert node.asdict(topology_dump=True)["netmiko_device_type"] == "nokia_srl"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_default_credentials(node, compute):
|
||||
"""
|
||||
default_username/default_password are controller-only properties: updating
|
||||
them must not call the compute and must be persisted in the node json.
|
||||
"""
|
||||
|
||||
compute.put = AsyncioMagicMock()
|
||||
node._project.emit_notification = AsyncioMagicMock()
|
||||
node._project.dump = MagicMock()
|
||||
|
||||
await node.update(default_username="admin", default_password="secret")
|
||||
assert not compute.put.called
|
||||
assert node.default_username == "admin"
|
||||
assert node.default_password == "secret"
|
||||
assert node.asdict()["default_username"] == "admin"
|
||||
assert node.asdict(topology_dump=True)["default_password"] == "secret"
|
||||
|
||||
# credentials never leak into the compute properties
|
||||
assert "default_username" not in node.properties
|
||||
assert "default_password" not in node.properties
|
||||
|
||||
# both fields can be cleared with an empty string
|
||||
await node.update(default_username="", default_password="")
|
||||
assert node.default_username == ""
|
||||
assert node.default_password == ""
|
||||
|
||||
|
||||
def test_default_credentials_from_template_kwargs(compute, project):
|
||||
"""
|
||||
A node created from a template with appliance metadata inherits the
|
||||
default credentials without sending them to the compute.
|
||||
"""
|
||||
|
||||
node = Node(project, compute, "test", node_type="vpcs",
|
||||
default_username="root", default_password="cisco123")
|
||||
assert node.default_username == "root"
|
||||
assert node.default_password == "cisco123"
|
||||
assert "default_username" not in node.properties
|
||||
assert "default_password" not in node.properties
|
||||
assert node.asdict(topology_dump=True)["default_username"] == "root"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_no_changes(node, compute):
|
||||
"""
|
||||
|
||||
@@ -204,6 +204,48 @@ async def test_add_node_local(controller):
|
||||
project.emit_notification.assert_any_call("node.created", node.asdict())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_node_from_template_seeds_default_credentials(controller):
|
||||
"""
|
||||
The appliance metadata stays template level: creating a node from a
|
||||
template seeds the default credentials on the node and must not leak
|
||||
the metadata into the node properties sent to the compute.
|
||||
"""
|
||||
|
||||
compute = MagicMock()
|
||||
compute.id = "local"
|
||||
controller._computes["local"] = compute
|
||||
project = Project(controller=controller, name="Test")
|
||||
project.emit_notification = MagicMock()
|
||||
|
||||
response = MagicMock()
|
||||
response.json = {"console": 2048}
|
||||
compute.post = AsyncioMagicMock(return_value=response)
|
||||
|
||||
template = {
|
||||
"name": "VPCS_TEST",
|
||||
"template_type": "vpcs",
|
||||
"compute_id": "local",
|
||||
"default_name_format": "PC{0}",
|
||||
"properties": {"startup_script": "test.cfg"},
|
||||
"appliance_metadata": {
|
||||
"vendor_name": "Test vendor",
|
||||
"default_username": "admin",
|
||||
"default_password": "secret",
|
||||
},
|
||||
}
|
||||
|
||||
node = await project.add_node_from_template(template)
|
||||
|
||||
# credentials seeded from the appliance metadata
|
||||
assert node.default_username == "admin"
|
||||
assert node.default_password == "secret"
|
||||
# the metadata itself never reaches the node properties
|
||||
assert "appliance_metadata" not in node.properties
|
||||
assert "default_username" not in node.properties
|
||||
assert "default_password" not in node.properties
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_node_non_local(controller):
|
||||
"""
|
||||
|
||||
Reference in new issue
Block a user