You've already forked netbox-proxmox-power-button
secured the plugin a bit for 0.1.1
This commit is contained in:
32
README.md
32
README.md
@@ -19,7 +19,7 @@ PLUGINS = ["proxmox_power_button"]
|
|||||||
|
|
||||||
PLUGINS_CONFIG = {
|
PLUGINS_CONFIG = {
|
||||||
"proxmox_power_button": {
|
"proxmox_power_button": {
|
||||||
"verify_ssl": False,
|
"verify_ssl": True, # set False only for self-signed lab certs
|
||||||
"stop_mode": "shutdown",
|
"stop_mode": "shutdown",
|
||||||
"reboot_mode": "reboot",
|
"reboot_mode": "reboot",
|
||||||
},
|
},
|
||||||
@@ -82,15 +82,35 @@ for both QEMU and LXC.
|
|||||||
|
|
||||||
| key | default | meaning |
|
| key | default | meaning |
|
||||||
|---------------|--------------|------------------------------------------|
|
|---------------|--------------|------------------------------------------|
|
||||||
| `verify_ssl` | `False` | verify Proxmox TLS cert |
|
| `verify_ssl` | `True` | verify Proxmox TLS cert (set `False` only for self-signed lab certs) |
|
||||||
| `stop_mode` | `"shutdown"` | `shutdown` (graceful ACPI) or `stop` (hard) |
|
| `stop_mode` | `"shutdown"` | `shutdown` (graceful ACPI) or `stop` (hard) |
|
||||||
| `reboot_mode` | `"reboot"` | `reboot` (graceful) or `reset` (hard) |
|
| `reboot_mode` | `"reboot"` | `reboot` (graceful) or `reset` (hard) |
|
||||||
|
|
||||||
## Notes / caveats
|
## Security
|
||||||
|
|
||||||
- **Token is stored in a plain-text custom field** and is visible to anyone who
|
- **Token is stored in a plain-text custom field.** It is visible to anyone who
|
||||||
can view the cluster. Use a scoped, least-privilege Proxmox API token and
|
can *view* the cluster — in the UI, in REST API responses for the cluster,
|
||||||
restrict cluster view permissions. NetBox has no "secret" custom-field type.
|
and in change-log data. NetBox has no "secret" custom-field type, so:
|
||||||
|
- restrict Cluster **view** permissions to administrators only;
|
||||||
|
- use a least-privilege Proxmox API token: only `VM.Audit` + `VM.PowerMgmt`,
|
||||||
|
scoped to the specific VMs or pool this plugin should manage — never a
|
||||||
|
`root@pam` token with datacenter-wide rights. Anyone who can read the
|
||||||
|
field can extract the token and drive the hypervisor directly.
|
||||||
|
- **Permissions.** The buttons render only for users with
|
||||||
|
`virtualization.change_virtualmachine`, and the action endpoint enforces
|
||||||
|
NetBox's **object-scoped** permissions: a user whose change permission is
|
||||||
|
constrained (e.g. to one tenant's VMs) gets a 404 on any VM outside that
|
||||||
|
scope — the same behaviour as core NetBox object views.
|
||||||
|
- **TLS verification is on by default.** The Proxmox API token crosses this
|
||||||
|
connection on every power action; leaving `verify_ssl` at `True` prevents
|
||||||
|
token theft via MitM. Only set it to `False` for self-signed lab
|
||||||
|
certificates.
|
||||||
|
- **Error detail goes to the log, not the browser.** Failed actions show a
|
||||||
|
generic message; the full Proxmox/network error (which may contain internal
|
||||||
|
hostnames, URLs, or response bodies) is written to the
|
||||||
|
`proxmox_power_button` logger.
|
||||||
|
|
||||||
|
## Notes / caveats
|
||||||
- **Requires NetBox ≥ 4.5.0.** The data migration depends on
|
- **Requires NetBox ≥ 4.5.0.** The data migration depends on
|
||||||
`extras.0134_owner` and `virtualization.0052_gfk_indexes`, which first appear
|
`extras.0134_owner` and `virtualization.0052_gfk_indexes`, which first appear
|
||||||
in 4.5.0; on 4.4 or older `migrate` fails with `NodeNotFoundError`. Bump
|
in 4.5.0; on 4.4 or older `migrate` fails with `NodeNotFoundError`. Bump
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class ProxmoxPowerButtonConfig(PluginConfig):
|
|||||||
name = "proxmox_power_button"
|
name = "proxmox_power_button"
|
||||||
verbose_name = "Proxmox Power Button"
|
verbose_name = "Proxmox Power Button"
|
||||||
description = "Start/stop/reboot Proxmox VMs from the NetBox VM detail page"
|
description = "Start/stop/reboot Proxmox VMs from the NetBox VM detail page"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
author = "Bryan Pedini"
|
author = "Bryan Pedini"
|
||||||
base_url = "proxmox-power-button"
|
base_url = "proxmox-power-button"
|
||||||
# The data migration pins extras.0134_owner / virtualization.0052_gfk_indexes,
|
# The data migration pins extras.0134_owner / virtualization.0052_gfk_indexes,
|
||||||
@@ -16,11 +16,13 @@ class ProxmoxPowerButtonConfig(PluginConfig):
|
|||||||
min_version = "4.5.0"
|
min_version = "4.5.0"
|
||||||
|
|
||||||
# Optional behaviour, overridable via PLUGINS_CONFIG["proxmox_power_button"]:
|
# Optional behaviour, overridable via PLUGINS_CONFIG["proxmox_power_button"]:
|
||||||
# verify_ssl : verify the Proxmox TLS cert (default False)
|
# verify_ssl : verify the Proxmox TLS cert (default True — set False
|
||||||
|
# only for self-signed lab certs; the API token crosses
|
||||||
|
# this connection, so disabling verification invites MitM)
|
||||||
# stop_mode : "shutdown" (graceful ACPI, default) or "stop" (hard kill)
|
# stop_mode : "shutdown" (graceful ACPI, default) or "stop" (hard kill)
|
||||||
# reboot_mode : "reboot" (graceful, default) or "reset" (hard)
|
# reboot_mode : "reboot" (graceful, default) or "reset" (hard)
|
||||||
default_settings = {
|
default_settings = {
|
||||||
"verify_ssl": False,
|
"verify_ssl": True,
|
||||||
"stop_mode": "shutdown",
|
"stop_mode": "shutdown",
|
||||||
"reboot_mode": "reboot",
|
"reboot_mode": "reboot",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{# SPDX-License-Identifier: GPL-2.0-or-later — Copyright (C) 2026 Bryan Joshua Pedini #}
|
{# SPDX-License-Identifier: GPL-2.0-or-later — Copyright (C) 2026 Bryan Joshua Pedini #}
|
||||||
|
{% if perms.virtualization.change_virtualmachine %}
|
||||||
{% if object.status == 'active' %}
|
{% if object.status == 'active' %}
|
||||||
{# Powered on: reboot (left) + stop. #}
|
{# Powered on: reboot (left) + stop. #}
|
||||||
<form class="d-inline" method="post" action="{% url 'plugins:proxmox_power_button:power' pk=object.pk action='reboot' %}">
|
<form class="d-inline" method="post" action="{% url 'plugins:proxmox_power_button:power' pk=object.pk action='reboot' %}">
|
||||||
@@ -22,3 +23,4 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|||||||
@@ -61,7 +61,12 @@ class VMPowerActionView(PermissionRequiredMixin, View):
|
|||||||
return redirect("virtualization:virtualmachine", pk=pk)
|
return redirect("virtualization:virtualmachine", pk=pk)
|
||||||
|
|
||||||
def post(self, request, pk, action):
|
def post(self, request, pk, action):
|
||||||
vm = get_object_or_404(VirtualMachine, pk=pk)
|
# Scope the lookup through NetBox's object-permission system: a user
|
||||||
|
# whose change_virtualmachine permission is constraint-scoped gets a
|
||||||
|
# 404 on VMs outside their scope, exactly like core NetBox views.
|
||||||
|
vm = get_object_or_404(
|
||||||
|
VirtualMachine.objects.restrict(request.user, "change"), pk=pk
|
||||||
|
)
|
||||||
|
|
||||||
if action not in VALID_ACTIONS:
|
if action not in VALID_ACTIONS:
|
||||||
logger.error("user=%s vm=%s: unknown power action '%s'", request.user, vm.name, action)
|
logger.error("user=%s vm=%s: unknown power action '%s'", request.user, vm.name, action)
|
||||||
@@ -72,7 +77,13 @@ class VMPowerActionView(PermissionRequiredMixin, View):
|
|||||||
result = power_action(vm, action)
|
result = power_action(vm, action)
|
||||||
except ProxmoxError as exc:
|
except ProxmoxError as exc:
|
||||||
logger.error("user=%s vm=%s: %s failed: %s", request.user, vm.name, action, exc)
|
logger.error("user=%s vm=%s: %s failed: %s", request.user, vm.name, action, exc)
|
||||||
messages.error(request, f"Proxmox action failed: {exc}")
|
# Don't echo raw Proxmox/network errors to the browser — they can
|
||||||
|
# leak internal hostnames, URLs, and response bodies. Full detail
|
||||||
|
# is in the log line above.
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
f"Proxmox {action} failed for {vm.name} — see the NetBox log for details.",
|
||||||
|
)
|
||||||
return redirect("virtualization:virtualmachine", pk=vm.pk)
|
return redirect("virtualization:virtualmachine", pk=vm.pk)
|
||||||
|
|
||||||
if action == "start":
|
if action == "start":
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
# name that goes into NetBox's PLUGINS list — stays `proxmox_power_button`,
|
# name that goes into NetBox's PLUGINS list — stays `proxmox_power_button`,
|
||||||
# matching the ecosystem convention (cf. netbox-plugin-dns / netbox_dns).
|
# matching the ecosystem convention (cf. netbox-plugin-dns / netbox_dns).
|
||||||
name = "netbox-proxmox-power-button"
|
name = "netbox-proxmox-power-button"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
description = "NetBox plugin adding Start/Stop/Reboot buttons that drive Proxmox VE VMs"
|
description = "NetBox plugin adding Start/Stop/Reboot buttons that drive Proxmox VE VMs"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "GPL-2.0-or-later"
|
license = "GPL-2.0-or-later"
|
||||||
|
|||||||
Reference in New Issue
Block a user