mirror of
https://github.com/GNS3/gns3-server
synced 2024-12-26 00:38:10 +00:00
Merge pull request #2157 from GNS3/qemu-tpm-support
Trusted Platform Module (TPM) support for Qemu VMs
This commit is contained in:
commit
8d69f7f792
@ -78,6 +78,7 @@ class QemuVM(BaseNode):
|
|||||||
self._monitor_host = server_config.get("monitor_host", "127.0.0.1")
|
self._monitor_host = server_config.get("monitor_host", "127.0.0.1")
|
||||||
self._process = None
|
self._process = None
|
||||||
self._cpulimit_process = None
|
self._cpulimit_process = None
|
||||||
|
self._swtpm_process = None
|
||||||
self._monitor = None
|
self._monitor = None
|
||||||
self._stdout_file = ""
|
self._stdout_file = ""
|
||||||
self._qemu_img_stdout_file = ""
|
self._qemu_img_stdout_file = ""
|
||||||
@ -120,6 +121,7 @@ class QemuVM(BaseNode):
|
|||||||
self._initrd = ""
|
self._initrd = ""
|
||||||
self._kernel_image = ""
|
self._kernel_image = ""
|
||||||
self._kernel_command_line = ""
|
self._kernel_command_line = ""
|
||||||
|
self._tpm = False
|
||||||
self._legacy_networking = False
|
self._legacy_networking = False
|
||||||
self._replicate_network_connection_state = True
|
self._replicate_network_connection_state = True
|
||||||
self._create_config_disk = False
|
self._create_config_disk = False
|
||||||
@ -686,7 +688,7 @@ class QemuVM(BaseNode):
|
|||||||
"""
|
"""
|
||||||
Sets whether a config disk is automatically created on HDD disk interface (secondary slave)
|
Sets whether a config disk is automatically created on HDD disk interface (secondary slave)
|
||||||
|
|
||||||
:param replicate_network_connection_state: boolean
|
:param create_config_disk: boolean
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if create_config_disk:
|
if create_config_disk:
|
||||||
@ -807,6 +809,30 @@ class QemuVM(BaseNode):
|
|||||||
log.info('QEMU VM "{name}" [{id}] has set the number of vCPUs to {cpus}'.format(name=self._name, id=self._id, cpus=cpus))
|
log.info('QEMU VM "{name}" [{id}] has set the number of vCPUs to {cpus}'.format(name=self._name, id=self._id, cpus=cpus))
|
||||||
self._cpus = cpus
|
self._cpus = cpus
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tpm(self):
|
||||||
|
"""
|
||||||
|
Returns whether TPM is activated for this QEMU VM.
|
||||||
|
|
||||||
|
:returns: boolean
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._tpm
|
||||||
|
|
||||||
|
@tpm.setter
|
||||||
|
def tpm(self, tpm):
|
||||||
|
"""
|
||||||
|
Sets whether TPM is activated for this QEMU VM.
|
||||||
|
|
||||||
|
:param tpm: boolean
|
||||||
|
"""
|
||||||
|
|
||||||
|
if tpm:
|
||||||
|
log.info('QEMU VM "{name}" [{id}] has enabled the Trusted Platform Module (TPM)'.format(name=self._name, id=self._id))
|
||||||
|
else:
|
||||||
|
log.info('QEMU VM "{name}" [{id}] has disabled the Trusted Platform Module (TPM)'.format(name=self._name, id=self._id))
|
||||||
|
self._tpm = tpm
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def options(self):
|
def options(self):
|
||||||
"""
|
"""
|
||||||
@ -984,11 +1010,8 @@ class QemuVM(BaseNode):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self._cpulimit_process and self._cpulimit_process.returncode is None:
|
if self._cpulimit_process and self._cpulimit_process.returncode is None:
|
||||||
self._cpulimit_process.kill()
|
self._cpulimit_process.terminate()
|
||||||
try:
|
self._cpulimit_process = None
|
||||||
self._process.wait(3)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
log.error("Could not kill cpulimit process {}".format(self._cpulimit_process.pid))
|
|
||||||
|
|
||||||
def _set_cpu_throttling(self):
|
def _set_cpu_throttling(self):
|
||||||
"""
|
"""
|
||||||
@ -1003,7 +1026,9 @@ class QemuVM(BaseNode):
|
|||||||
cpulimit_exec = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "cpulimit", "cpulimit.exe")
|
cpulimit_exec = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "cpulimit", "cpulimit.exe")
|
||||||
else:
|
else:
|
||||||
cpulimit_exec = "cpulimit"
|
cpulimit_exec = "cpulimit"
|
||||||
subprocess.Popen([cpulimit_exec, "--lazy", "--pid={}".format(self._process.pid), "--limit={}".format(self._cpu_throttling)], cwd=self.working_dir)
|
|
||||||
|
command = [cpulimit_exec, "--lazy", "--pid={}".format(self._process.pid), "--limit={}".format(self._cpu_throttling)]
|
||||||
|
self._cpulimit_process = subprocess.Popen(command, cwd=self.working_dir)
|
||||||
log.info("CPU throttled to {}%".format(self._cpu_throttling))
|
log.info("CPU throttled to {}%".format(self._cpu_throttling))
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise QemuError("cpulimit could not be found, please install it or deactivate CPU throttling")
|
raise QemuError("cpulimit could not be found, please install it or deactivate CPU throttling")
|
||||||
@ -1079,7 +1104,8 @@ class QemuVM(BaseNode):
|
|||||||
await self._set_process_priority()
|
await self._set_process_priority()
|
||||||
if self._cpu_throttling:
|
if self._cpu_throttling:
|
||||||
self._set_cpu_throttling()
|
self._set_cpu_throttling()
|
||||||
|
if self._tpm:
|
||||||
|
self._start_swtpm()
|
||||||
if "-enable-kvm" in command_string or "-enable-hax" in command_string:
|
if "-enable-kvm" in command_string or "-enable-hax" in command_string:
|
||||||
self._hw_virtualization = True
|
self._hw_virtualization = True
|
||||||
|
|
||||||
@ -1162,6 +1188,7 @@ class QemuVM(BaseNode):
|
|||||||
log.warning('QEMU VM "{}" PID={} is still running'.format(self._name, self._process.pid))
|
log.warning('QEMU VM "{}" PID={} is still running'.format(self._name, self._process.pid))
|
||||||
self._process = None
|
self._process = None
|
||||||
self._stop_cpulimit()
|
self._stop_cpulimit()
|
||||||
|
self._stop_swtpm()
|
||||||
if self.on_close != "save_vm_state":
|
if self.on_close != "save_vm_state":
|
||||||
await self._clear_save_vm_stated()
|
await self._clear_save_vm_stated()
|
||||||
await self._export_config()
|
await self._export_config()
|
||||||
@ -1995,6 +2022,60 @@ class QemuVM(BaseNode):
|
|||||||
|
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
def _start_swtpm(self):
|
||||||
|
"""
|
||||||
|
Start swtpm (TPM emulator)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
raise QemuError("swtpm (TPM emulator) is not supported on Windows")
|
||||||
|
tpm_dir = os.path.join(self.working_dir, "tpm")
|
||||||
|
os.makedirs(tpm_dir, exist_ok=True)
|
||||||
|
tpm_sock = os.path.join(self.temporary_directory, "swtpm.sock")
|
||||||
|
swtpm = shutil.which("swtpm")
|
||||||
|
if not swtpm:
|
||||||
|
raise QemuError("Could not find swtpm (TPM emulator)")
|
||||||
|
try:
|
||||||
|
command = [
|
||||||
|
swtpm,
|
||||||
|
"socket",
|
||||||
|
"--tpm2",
|
||||||
|
'--tpmstate', "dir={}".format(tpm_dir),
|
||||||
|
"--ctrl",
|
||||||
|
"type=unixio,path={},terminate".format(tpm_sock)
|
||||||
|
]
|
||||||
|
command_string = " ".join(shlex_quote(s) for s in command)
|
||||||
|
log.info("Starting swtpm (TPM emulator) with: {}".format(command_string))
|
||||||
|
self._swtpm_process = subprocess.Popen(command, cwd=self.working_dir)
|
||||||
|
log.info("swtpm (TPM emulator) has started")
|
||||||
|
except (OSError, subprocess.SubprocessError) as e:
|
||||||
|
raise QemuError("Could not start swtpm (TPM emulator): {}".format(e))
|
||||||
|
|
||||||
|
def _stop_swtpm(self):
|
||||||
|
"""
|
||||||
|
Stop swtpm (TPM emulator)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._swtpm_process and self._swtpm_process.returncode is None:
|
||||||
|
self._swtpm_process.terminate()
|
||||||
|
self._swtpm_process = None
|
||||||
|
|
||||||
|
def _tpm_options(self):
|
||||||
|
"""
|
||||||
|
Return the TPM options for Qemu.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tpm_sock = os.path.join(self.temporary_directory, "swtpm.sock")
|
||||||
|
options = [
|
||||||
|
"-chardev",
|
||||||
|
"socket,id=chrtpm,path={}".format(tpm_sock),
|
||||||
|
"-tpmdev",
|
||||||
|
"emulator,id=tpm0,chardev=chrtpm",
|
||||||
|
"-device",
|
||||||
|
"tpm-tis,tpmdev=tpm0"
|
||||||
|
]
|
||||||
|
return options
|
||||||
|
|
||||||
async def _network_options(self):
|
async def _network_options(self):
|
||||||
|
|
||||||
network_options = []
|
network_options = []
|
||||||
@ -2290,6 +2371,8 @@ class QemuVM(BaseNode):
|
|||||||
command.extend((await self._saved_state_option()))
|
command.extend((await self._saved_state_option()))
|
||||||
if self._console_type == "telnet":
|
if self._console_type == "telnet":
|
||||||
command.extend((await self._disable_graphics()))
|
command.extend((await self._disable_graphics()))
|
||||||
|
if self._tpm:
|
||||||
|
command.extend(self._tpm_options())
|
||||||
if additional_options:
|
if additional_options:
|
||||||
try:
|
try:
|
||||||
command.extend(shlex.split(additional_options))
|
command.extend(shlex.split(additional_options))
|
||||||
|
@ -190,6 +190,10 @@ QEMU_CREATE_SCHEMA = {
|
|||||||
"description": "Replicate the network connection state for links in Qemu",
|
"description": "Replicate the network connection state for links in Qemu",
|
||||||
"type": ["boolean", "null"],
|
"type": ["boolean", "null"],
|
||||||
},
|
},
|
||||||
|
"tpm": {
|
||||||
|
"description": "Enable the Trusted Platform Module (TPM) in Qemu",
|
||||||
|
"type": ["boolean", "null"],
|
||||||
|
},
|
||||||
"create_config_disk": {
|
"create_config_disk": {
|
||||||
"description": "Automatically create a config disk on HDD disk interface (secondary slave)",
|
"description": "Automatically create a config disk on HDD disk interface (secondary slave)",
|
||||||
"type": ["boolean", "null"],
|
"type": ["boolean", "null"],
|
||||||
@ -384,6 +388,10 @@ QEMU_UPDATE_SCHEMA = {
|
|||||||
"description": "Replicate the network connection state for links in Qemu",
|
"description": "Replicate the network connection state for links in Qemu",
|
||||||
"type": ["boolean", "null"],
|
"type": ["boolean", "null"],
|
||||||
},
|
},
|
||||||
|
"tpm": {
|
||||||
|
"description": "Enable the Trusted Platform Module (TPM) in Qemu",
|
||||||
|
"type": ["boolean", "null"],
|
||||||
|
},
|
||||||
"create_config_disk": {
|
"create_config_disk": {
|
||||||
"description": "Automatically create a config disk on HDD disk interface (secondary slave)",
|
"description": "Automatically create a config disk on HDD disk interface (secondary slave)",
|
||||||
"type": ["boolean", "null"],
|
"type": ["boolean", "null"],
|
||||||
@ -591,6 +599,10 @@ QEMU_OBJECT_SCHEMA = {
|
|||||||
"description": "Replicate the network connection state for links in Qemu",
|
"description": "Replicate the network connection state for links in Qemu",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
},
|
},
|
||||||
|
"tpm": {
|
||||||
|
"description": "Enable the Trusted Platform Module (TPM) in Qemu",
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
"create_config_disk": {
|
"create_config_disk": {
|
||||||
"description": "Automatically create a config disk on HDD disk interface (secondary slave)",
|
"description": "Automatically create a config disk on HDD disk interface (secondary slave)",
|
||||||
"type": ["boolean", "null"],
|
"type": ["boolean", "null"],
|
||||||
@ -665,6 +677,7 @@ QEMU_OBJECT_SCHEMA = {
|
|||||||
"kernel_command_line",
|
"kernel_command_line",
|
||||||
"legacy_networking",
|
"legacy_networking",
|
||||||
"replicate_network_connection_state",
|
"replicate_network_connection_state",
|
||||||
|
"tpm",
|
||||||
"create_config_disk",
|
"create_config_disk",
|
||||||
"on_close",
|
"on_close",
|
||||||
"cpu_throttling",
|
"cpu_throttling",
|
||||||
|
@ -183,6 +183,11 @@ QEMU_TEMPLATE_PROPERTIES = {
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": True
|
"default": True
|
||||||
},
|
},
|
||||||
|
"tpm": {
|
||||||
|
"description": "Enable the Trusted Platform Module (TPM) in Qemu",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": False
|
||||||
|
},
|
||||||
"create_config_disk": {
|
"create_config_disk": {
|
||||||
"description": "Automatically create a config disk on HDD disk interface (secondary slave)",
|
"description": "Automatically create a config disk on HDD disk interface (secondary slave)",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
@ -82,6 +82,7 @@ async def subprocess_check_output(*args, cwd=None, env=None, stderr=False):
|
|||||||
# and the code of VPCS, dynamips... Will detect it's not the correct binary
|
# and the code of VPCS, dynamips... Will detect it's not the correct binary
|
||||||
return output.decode("utf-8", errors="ignore")
|
return output.decode("utf-8", errors="ignore")
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_process_termination(process, timeout=10):
|
async def wait_for_process_termination(process, timeout=10):
|
||||||
"""
|
"""
|
||||||
Wait for a process terminate, and raise asyncio.TimeoutError in case of
|
Wait for a process terminate, and raise asyncio.TimeoutError in case of
|
||||||
|
@ -173,7 +173,7 @@ async def test_termination_callback(vm):
|
|||||||
await vm._termination_callback(0)
|
await vm._termination_callback(0)
|
||||||
assert vm.status == "stopped"
|
assert vm.status == "stopped"
|
||||||
|
|
||||||
await queue.get(1) # Ping
|
await queue.get(1) # Ping
|
||||||
|
|
||||||
(action, event, kwargs) = await queue.get(1)
|
(action, event, kwargs) = await queue.get(1)
|
||||||
assert action == "node.updated"
|
assert action == "node.updated"
|
||||||
@ -401,6 +401,17 @@ async def test_spice_option(vm, fake_qemu_img_binary):
|
|||||||
assert '-vga qxl' in ' '.join(options)
|
assert '-vga qxl' in ' '.join(options)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_tpm_option(vm, tmpdir, fake_qemu_img_binary):
|
||||||
|
|
||||||
|
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0")
|
||||||
|
vm._tpm = True
|
||||||
|
tpm_sock = os.path.join(vm.temporary_directory, "swtpm.sock")
|
||||||
|
options = await vm._build_command()
|
||||||
|
assert '-chardev socket,id=chrtpm,path={}'.format(tpm_sock) in ' '.join(options)
|
||||||
|
assert '-tpmdev emulator,id=tpm0,chardev=chrtpm' in ' '.join(options)
|
||||||
|
assert '-device tpm-tis,tpmdev=tpm0' in ' '.join(options)
|
||||||
|
|
||||||
|
|
||||||
async def test_disk_options_multiple_disk(vm, tmpdir, fake_qemu_img_binary):
|
async def test_disk_options_multiple_disk(vm, tmpdir, fake_qemu_img_binary):
|
||||||
|
|
||||||
vm._hda_disk_image = str(tmpdir / "test0.qcow2")
|
vm._hda_disk_image = str(tmpdir / "test0.qcow2")
|
||||||
|
Loading…
Reference in New Issue
Block a user