Skip to content

Commit

Permalink
config: T4919: Encryption without a TPM
Browse files Browse the repository at this point in the history
Allows encryption without a TPM being present, requires KVM/serial to manually load the encrypted config.

Boot behaviour will load a default VyOS config. User then logs in with vyos/vyos, mounts the encrypted volume and loads the now decrypted config.
  • Loading branch information
sarthurdev committed Jan 23, 2023
1 parent 403d96e commit a462c50
Showing 1 changed file with 61 additions and 38 deletions.
99 changes: 61 additions & 38 deletions src/helpers/vyos-config-encrypt.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ def get_current_image():
return path_split[-1]
return None

def load_config(key, recovery_key):
if not key and not recovery_key:
def load_config(key):
if not key:
return

persist_path = cmd(persistpath_cmd).strip()
Expand All @@ -63,19 +63,15 @@ def load_config(key, recovery_key):
print('Encrypted config volume is already mounted')
return

# If /config is populated, move to /config.old
if len(os.listdir('/config')) > 0:
print('Moving existing /config folder to /config.old')
shutil.move('/config', '/config.old')

with NamedTemporaryFile(dir='/dev/shm', delete=False) as f:
f.write(key or recovery_key)
f.write(key)
key_file = f.name

cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')

for path in mount_paths:
cmd(f'mount /dev/mapper/vyos_config {path}')
cmd(f'chgrp vyattacfg {path}')

os.unlink(key_file)

Expand Down Expand Up @@ -111,14 +107,16 @@ def encrypt_config(key, recovery_key):
f.write(key)
key_file = f.name

# Write recovery key for slot 2
with NamedTemporaryFile(dir='/dev/shm', delete=False) as f:
f.write(recovery_key)
recovery_key_file = f.name

# Format and add keys to volume
# Format and add main key to volume
cmd(f'cryptsetup -q luksFormat {image_path} {key_file}')
cmd(f'cryptsetup -q luksAddKey {image_path} {recovery_key_file} --key-file={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

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}')
Expand All @@ -133,15 +131,18 @@ def encrypt_config(key, recovery_key):
cmd(f'umount {d}')

os.unlink(key_file)
os.unlink(recovery_key_file)

cmd('mount /dev/mapper/vyos_config /config')
cmd('mount /dev/mapper/vyos_config /opt/vyatta/etc/config')
if recovery_key:
os.unlink(recovery_key_file)

for path in mount_paths:
cmd(f'mount /dev/mapper/vyos_config {path}')
cmd(f'chgrp vyattacfg {path}')

return True

def decrypt_config(key, recovery_key):
if not key and not recovery_key:
def decrypt_config(key):
if not key:
return

persist_path = cmd(persistpath_cmd).strip()
Expand All @@ -155,7 +156,7 @@ def decrypt_config(key, recovery_key):

if not is_opened():
with NamedTemporaryFile(dir='/dev/shm', delete=False) as f:
f.write(key or recovery_key)
f.write(key)
key_file = f.name

cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
Expand All @@ -176,6 +177,7 @@ def decrypt_config(key, recovery_key):

# Move encrypted volume to /config
shutil.copytree(d, '/config', copy_function=shutil.move, dirs_exist_ok=True)
cmd(f'chgrp vyattacfg /config')

cmd(f'umount {d}')

Expand Down Expand Up @@ -203,23 +205,38 @@ def decrypt_config(key, recovery_key):
enable = '--enable' in sys.argv
load = '--load' in sys.argv

tpm_exists = os.path.exists('/sys/class/tpm/tpm0')

key = None
recovery_key = None
need_recovery = False

if enable:
key = Fernet.generate_key()
elif disable or load:
try:
key = read_tpm_key()
need_recovery = False
except:
print('Failed to read key from TPM, recovery key required')
need_recovery = True

if need_recovery or (not disable and not ask_yes_no('Automatically generate a recovery key?', default=True)):
question_key_str = 'recovery key' if tpm_exists else 'key'

if tpm_exists:
if enable:
key = Fernet.generate_key()
elif disable or load:
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

if enable and not tpm_exists:
print('WARNING: VyOS will boot into a default config when encrypted without a TPM')
print('You will need to manually login with default credentials and use "encryption load"')
print('to mount the encrypted volume and use "load /config/config.boot"')

if not ask_yes_no('Are you sure you want to proceed?'):
sys.exit(0)

if need_recovery or (enable and not ask_yes_no(f'Automatically generate a {question_key_str}?', default=True)):
while True:
recovery_key = ask_input('Enter recovery key:', default=None).encode()
recovery_key = ask_input(f'Enter {question_key_str}:', default=None).encode()

if len(recovery_key) >= 32:
break
Expand All @@ -230,21 +247,27 @@ def decrypt_config(key, recovery_key):

try:
if disable:
decrypt_config(key, recovery_key)
decrypt_config(key or recovery_key)

print('Encrypted config volume has been disabled')
print('Contents have been migrated to /config on rootfs')
elif load:
load_config(key, recovery_key)
load_config(key or recovery_key)

print('Encrypted config volume has been mounted')
print('Use "load /config/config.boot" to load configuration')
elif enable:
elif enable and tpm_exists:
encrypt_config(key, recovery_key)

print('Encrypted config volume has been enabled')
print('Encrypted config volume has been enabled with TPM')
print('Backup the recovery key in a safe place!')
print('Recovery key: ' + recovery_key.decode())
elif enable:
encrypt_config(recovery_key)

print('Encrypted config volume has been enabled without TPM')
print('Backup the key in a safe place!')
print('Key: ' + recovery_key.decode())
except Exception as e:
word = 'decrypt' if disable else 'encrypt'
word = 'decrypt' if disable or load else 'encrypt'
print(f'Failed to {word} config: {e}')

0 comments on commit a462c50

Please sign in to comment.