summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2025-09-25 15:22:34 +0100
committerGitHub <noreply@github.com>2025-09-25 15:22:34 +0100
commit51b2d4dc9c08b15c5c404b5f9575080935c85d4d (patch)
treeb8bb695c136299d07720e103d1270e1e64628c9d /src
parent6902480472250fcb590091a53ef7fba3863ab6b8 (diff)
parent36d9d7068cc0c437f17f85c8fbd05c79f7d99627 (diff)
downloadvyos-1x-51b2d4dc9c08b15c5c404b5f9575080935c85d4d.tar.gz
vyos-1x-51b2d4dc9c08b15c5c404b5f9575080935c85d4d.zip
Merge pull request #4669 from sarthurdev/tpm_fixes
tpm: T7713: T7717: Multiple TPM fixes
Diffstat (limited to 'src')
-rwxr-xr-xsrc/helpers/vyos-config-encrypt.py188
-rwxr-xr-xsrc/op_mode/image_installer.py57
2 files changed, 166 insertions, 79 deletions
diff --git a/src/helpers/vyos-config-encrypt.py b/src/helpers/vyos-config-encrypt.py
index 86c98d07a..c983e6065 100755
--- a/src/helpers/vyos-config-encrypt.py
+++ b/src/helpers/vyos-config-encrypt.py
@@ -20,15 +20,12 @@ import sys
from argparse import ArgumentParser
from cryptography.fernet import Fernet
-from tempfile import NamedTemporaryFile
-from tempfile import TemporaryDirectory
+from tempfile import NamedTemporaryFile, TemporaryDirectory
-from vyos.system.image import is_live_boot
-from vyos.tpm import clear_tpm_key
-from vyos.tpm import read_tpm_key
-from vyos.tpm import write_tpm_key
+from vyos.system.image import is_live_boot, get_running_image
+from vyos.tpm import clear_tpm_key, read_tpm_key, write_tpm_key
from vyos.utils.io import ask_input, ask_yes_no
-from vyos.utils.process import cmd
+from vyos.utils.process import cmd, run
from vyos.defaults import directories
persistpath_cmd = '/opt/vyatta/sbin/vyos-persistpath'
@@ -37,30 +34,18 @@ mount_path = directories['config']
mount_path_old = f'{mount_path}.old'
dm_device = '/dev/mapper/vyos_config'
+
def is_opened():
return os.path.exists(dm_device)
-def get_current_image():
- with open('/proc/cmdline', 'r') as f:
- args = f.read().split(" ")
- for arg in args:
- if 'vyos-union' in arg:
- k, v = arg.split("=")
- path_split = v.split("/")
- return path_split[-1]
- return None
-
def load_config(key):
if not key:
return
persist_path = cmd(persistpath_cmd).strip()
- image_name = get_current_image()
+ image_name = get_running_image()
image_path = os.path.join(persist_path, 'luks', image_name)
- if not os.path.exists(image_path):
- raise Exception("Encrypted config volume doesn't exist")
-
if is_opened():
print('Encrypted config volume is already mounted')
return
@@ -71,6 +56,7 @@ def load_config(key):
cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
+ run(f'umount -l {mount_path}')
cmd(f'mount /dev/mapper/vyos_config {mount_path}')
cmd(f'chgrp -R vyattacfg {mount_path}')
@@ -79,9 +65,6 @@ def load_config(key):
return True
def encrypt_config(key, recovery_key=None, is_tpm=True):
- if is_opened():
- raise Exception('An encrypted config volume is already mapped')
-
# Clear and write key to TPM
if is_tpm:
try:
@@ -98,38 +81,49 @@ def encrypt_config(key, recovery_key=None, is_tpm=True):
if not os.path.isdir(luks_folder):
os.mkdir(luks_folder)
- image_name = get_current_image()
+ image_name = get_running_image()
image_path = os.path.join(luks_folder, image_name)
- # Create file for encrypted config
- cmd(f'fallocate -l {size}M {image_path}')
+ try:
+ # Create file for encrypted config
+ cmd(f'fallocate -l {size}M {image_path}')
- # Write TPM key for slot #1
- with NamedTemporaryFile(dir='/dev/shm', delete=False) as f:
- f.write(key)
- key_file = f.name
+ # Write TPM key for slot #1
+ with NamedTemporaryFile(dir='/dev/shm', delete=False) as f:
+ f.write(key)
+ key_file = f.name
- # Format and add main key to volume
- cmd(f'cryptsetup -q luksFormat {image_path} {key_file}')
+ # Format and add main key to volume
+ cmd(f'cryptsetup -q luksFormat {image_path} {key_file}')
- if recovery_key:
- # Write recovery key for slot 2
- with NamedTemporaryFile(dir='/dev/shm', delete=False) as f:
- f.write(recovery_key)
- recovery_key_file = f.name
+ if recovery_key:
+ # Write recovery key for slot 2
+ with NamedTemporaryFile(dir='/dev/shm', delete=False) as f:
+ f.write(recovery_key)
+ recovery_key_file = f.name
- cmd(f'cryptsetup -q luksAddKey {image_path} {recovery_key_file} --key-file={key_file}')
+ cmd(f'cryptsetup -q luksAddKey {image_path} {recovery_key_file} --key-file={key_file}')
- # Open encrypted volume and format with ext4
- cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
- cmd('mkfs.ext4 /dev/mapper/vyos_config')
+ # Open encrypted volume and format with ext4
+ cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
+ cmd('mkfs.ext4 /dev/mapper/vyos_config')
+ except Exception as e:
+ print('An error occurred while creating the encrypted config volume, aborting.')
+
+ if os.path.exists('/dev/mapper/vyos_config'):
+ run('cryptsetup -q close vyos_config')
+
+ if os.path.exists(image_path):
+ os.unlink(image_path)
+
+ raise e
with TemporaryDirectory() as d:
cmd(f'mount /dev/mapper/vyos_config {d}')
# Move mount_path to encrypted volume
shutil.copytree(mount_path, d, copy_function=shutil.move, dirs_exist_ok=True)
-
+ cmd(f'chgrp -R vyattacfg {d}')
cmd(f'umount {d}')
os.unlink(key_file)
@@ -137,21 +131,53 @@ def encrypt_config(key, recovery_key=None, is_tpm=True):
if recovery_key:
os.unlink(recovery_key_file)
+ run(f'umount -l {mount_path}')
cmd(f'mount /dev/mapper/vyos_config {mount_path}')
cmd(f'chgrp vyattacfg {mount_path}')
return True
-def decrypt_config(key):
+def config_backup_folder(base):
+ # Get next available backup folder
+ if not os.path.exists(base):
+ return base
+
+ idx = 1
+ while os.path.exists(f'{base}.{idx}'):
+ idx += 1
+ return f'{base}.{idx}'
+
+def test_decrypt(key):
if not key:
return
persist_path = cmd(persistpath_cmd).strip()
- image_name = get_current_image()
+ image_name = get_running_image()
image_path = os.path.join(persist_path, 'luks', image_name)
- if not os.path.exists(image_path):
- raise Exception("Encrypted config volume doesn't exist")
+ key_file = None
+
+ if not is_opened():
+ with NamedTemporaryFile(dir='/dev/shm', delete=False) as f:
+ f.write(key)
+ key_file = f.name
+
+ try:
+ cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
+ os.unlink(key_file)
+ return True
+ except:
+ return False
+ return False
+
+def decrypt_config(key):
+ if not key:
+ return
+
+ persist_path = cmd(persistpath_cmd).strip()
+ image_name = get_running_image()
+ image_path = os.path.join(persist_path, 'luks', image_name)
+ original_config_path = os.path.join(persist_path, 'boot', image_name, 'rw', 'opt', 'vyatta', 'etc', 'config')
key_file = None
@@ -162,17 +188,21 @@ def decrypt_config(key):
cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
- # unmount encrypted volume mount point
- if os.path.ismount(mount_path):
- cmd(f'umount {mount_path}')
+ # unmount encrypted volume mount points
+ run(f'umount -Alq /dev/mapper/vyos_config')
# If /opt/vyatta/etc/config is populated, move to /opt/vyatta/etc/config.old
if len(os.listdir(mount_path)) > 0:
- print(f'Moving existing {mount_path} folder to {mount_path_old}')
- shutil.move(mount_path, mount_path_old)
+ backup_path = config_backup_folder(mount_path_old)
+ print(f'Moving existing {mount_path} folder to {backup_path}')
+ shutil.move(mount_path, backup_path)
+
+ # Mount original persistence config path
+ if not os.path.exists(mount_path):
+ os.mkdir(mount_path)
+ cmd(f'mount --bind {original_config_path} {mount_path}')
- # Temporarily mount encrypted volume and migrate files to
- # /opt/vyatta/etc/config on rootfs
+ # Temporarily mount encrypted volume and migrate files to /config on rootfs
with TemporaryDirectory() as d:
cmd(f'mount /dev/mapper/vyos_config {d}')
@@ -191,7 +221,8 @@ def decrypt_config(key):
os.unlink(image_path)
try:
- clear_tpm_key()
+ if ask_yes_no('Do you want to clear the TPM? This will cause issues if other system images use the key'):
+ clear_tpm_key()
except:
pass
@@ -212,6 +243,18 @@ if __name__ == '__main__':
parser.add_argument('--load', help='Load encrypted config volume', action="store_true")
args = parser.parse_args()
+ if args.disable or args.load:
+ persist_path = cmd(persistpath_cmd).strip()
+ image_name = get_running_image()
+ image_path = os.path.join(persist_path, 'luks', image_name)
+
+ if not os.path.exists(image_path):
+ print('Encrypted config volume does not exist, aborting.')
+ sys.exit(0)
+ elif args.enable and is_opened():
+ print('An encrypted config volume is already mapped, aborting.')
+ sys.exit(0)
+
tpm_exists = os.path.exists('/sys/class/tpm/tpm0')
key = None
@@ -220,18 +263,31 @@ if __name__ == '__main__':
question_key_str = 'recovery key' if tpm_exists else 'key'
- if tpm_exists:
- if args.enable:
- key = Fernet.generate_key()
- elif args.disable or args.load:
+ if not is_opened():
+ if tpm_exists:
+ existing_key = None
+
try:
- key = read_tpm_key()
- need_recovery = False
- except:
- print('Failed to read key from TPM, recovery key required')
- need_recovery = True
- else:
- need_recovery = True
+ existing_key = read_tpm_key()
+ except: pass
+
+ if args.enable:
+ if existing_key:
+ print('WARNING: An encryption key already exists in the TPM.')
+ print('If you choose not to use the existing key, any system image')
+ print('using the old key will need the recovery key.')
+ if existing_key and ask_yes_no('Do you want to use the existing TPM key?'):
+ key = existing_key
+ else:
+ key = Fernet.generate_key()
+ elif args.disable or args.load:
+ if existing_key and test_decrypt(existing_key):
+ need_recovery = False
+ else:
+ print('TPM key invalid or not found, recovery key required')
+ need_recovery = True
+ else:
+ need_recovery = True
if args.enable and not tpm_exists:
print('WARNING: VyOS will boot into a default config when encrypted without a TPM')
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index 6dc2dc490..a5afd26f4 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -26,6 +26,7 @@ from os import environ
from os import readlink
from os import getpid
from os import getppid
+from os import sync
from json import loads
from json import dumps
from typing import Union
@@ -1086,30 +1087,60 @@ def add_image(image_path: str, vrf: str = None, username: str = '',
# find target directory
root_dir: str = disk.find_persistence()
+ cmdline_options = []
+
# a config dir. It is the deepest one, so the comand will
# create all the rest in a single step
target_config_dir: str = f'{root_dir}/boot/{image_name}/rw{DIR_CONFIG}/'
# copy config
if no_prompt or migrate_config():
- print('Copying configuration directory')
- # copytree preserves perms but not ownership:
- Path(target_config_dir).mkdir(parents=True)
- chown(target_config_dir, group='vyattacfg')
- chmod_2775(target_config_dir)
- copytree(f'{DIR_CONFIG}/', target_config_dir, symlinks=True,
- copy_function=copy_preserve_owner, dirs_exist_ok=True)
-
- # Record information from which image we upgraded to the new one.
- # This can be used for a future automatic rollback into the old image.
- tmp = {'previous_image' : image.get_running_image()}
- write_file(f'{target_config_dir}/first_boot', dumps(tmp))
-
+ if Path('/dev/mapper/vyos_config').exists():
+ print('Copying encrypted configuration volume')
+
+ # Record information from which image we upgraded to the new one.
+ # This can be used for a future automatic rollback into the old image.
+ #
+ # For encrypted config, we need to copy, sync filesystems and remove from current image
+ tmp = {'previous_image' : image.get_running_image()}
+ write_file('/opt/vyatta/etc/config/first_boot', dumps(tmp))
+ sync()
+
+ # Copy encrypteed volumes
+ current_name = image.get_running_image()
+ current_config_path = f'{root_dir}/luks/{current_name}'
+ target_config_path = f'{root_dir}/luks/{image_name}'
+ copy(current_config_path, target_config_path)
+
+ # Now remove from current image
+ Path('/opt/vyatta/etc/config/first_boot').unlink()
+
+ cmdline_options = get_cli_kernel_options(
+ f'/opt/vyatta/etc/config/config.boot')
+ else:
+ print('Copying configuration directory')
+ # copytree preserves perms but not ownership:
+ Path(target_config_dir).mkdir(parents=True)
+ chown(target_config_dir, group='vyattacfg')
+ chmod_2775(target_config_dir)
+ copytree(f'{DIR_CONFIG}/', target_config_dir, symlinks=True,
+ copy_function=copy_preserve_owner, dirs_exist_ok=True)
+
+ # Record information from which image we upgraded to the new one.
+ # This can be used for a future automatic rollback into the old image.
+ tmp = {'previous_image' : image.get_running_image()}
+ write_file(f'{target_config_dir}/first_boot', dumps(tmp))
+
+ cmdline_options = get_cli_kernel_options(
+ f'{target_config_dir}/config.boot')
else:
Path(target_config_dir).mkdir(parents=True)
chown(target_config_dir, group='vyattacfg')
chmod_2775(target_config_dir)
Path(f'{target_config_dir}/.vyatta_config').touch()
+ cmdline_options = get_cli_kernel_options(
+ f'{target_config_dir}/config.boot')
+
target_ssh_dir: str = f'{root_dir}/boot/{image_name}/rw/etc/ssh/'
if no_prompt or copy_ssh_host_keys():
print('Copying SSH host keys')