# Copyright 2018 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Functions used to provision Fuchsia boot images.""" import common import logging import os import subprocess import tempfile import time import uuid _SSH_CONFIG_TEMPLATE = """ Host * CheckHostIP no StrictHostKeyChecking no ForwardAgent no ForwardX11 no User fuchsia IdentitiesOnly yes IdentityFile {identity} ServerAliveInterval 2 ServerAliveCountMax 5 ControlMaster auto ControlPersist 1m ControlPath /tmp/ssh-%r@%h:%p ConnectTimeout 5 """ # Specifies boot files intended for use by an emulator. TARGET_TYPE_QEMU = 'qemu' # Specifies boot files intended for use by anything (incl. physical devices). TARGET_TYPE_GENERIC = 'generic' # Defaults used by Fuchsia SDK _SSH_DIR = os.path.expanduser('~/.ssh') _SSH_CONFIG_DIR = os.path.expanduser('~/.fuchsia') def _GetPubKeyPath(): """Returns a path to the generated SSH public key.""" return os.path.join(_SSH_DIR, 'fuchsia_ed25519.pub') def ProvisionSSH(): """Generates a key pair and config file for SSH.""" fuchsia_authorized_keys_path = os.path.join(_SSH_DIR, 'fuchsia_authorized_keys') id_key_path = os.path.join(_SSH_DIR, 'fuchsia_ed25519') _GetPubKeyPath() logging.debug('Generating SSH credentials.') if not os.path.isfile(id_key_path): subprocess.check_output([ 'ssh-keygen', '-P', '', '-t', 'ed25519', '-f', id_key_path, '-C', 'generated by FEMU Start testing step' ]) if not os.path.isfile(fuchsia_authorized_keys_path): result = subprocess.check_output(['ssh-keygen', '-y', '-f', id_key_path]) with open(fuchsia_authorized_keys_path, 'w') as out: out.write(result.decode('utf-8')) if not os.path.exists(_SSH_CONFIG_DIR): os.mkdir(_SSH_CONFIG_DIR) elif not os.path.isdir(_SSH_CONFIG_DIR): raise Exception(_SSH_CONFIG_DIR + ' is not a directory.') ssh_config_path = os.path.join(_SSH_CONFIG_DIR, 'ssh_config') with open(ssh_config_path, "w") as ssh_config: ssh_config.write( _SSH_CONFIG_TEMPLATE.format(identity=id_key_path)) def GetTargetFile(filename, target_arch, target_type): """Computes a path to |filename| in the Fuchsia boot image directory specific to |target_type| and |target_arch|.""" assert target_type == TARGET_TYPE_QEMU or target_type == TARGET_TYPE_GENERIC return os.path.join(common.IMAGES_ROOT, target_arch, target_type, filename) def GetSSHConfigPath(): return os.path.join(_SSH_CONFIG_DIR, 'ssh_config') def GetBootImage(output_dir, target_arch, target_type): """"Gets a path to the Zircon boot image, with the SSH client public key added.""" ProvisionSSH() pubkey_path = _GetPubKeyPath() zbi_tool = common.GetHostToolPathFromPlatform('zbi') image_source_path = GetTargetFile('zircon-a.zbi', target_arch, target_type) image_dest_path = os.path.join(output_dir, 'gen', 'fuchsia-with-keys.zbi') cmd = [ zbi_tool, '-o', image_dest_path, image_source_path, '-e', 'data/ssh/authorized_keys=' + pubkey_path ] subprocess.check_call(cmd) return image_dest_path def GetKernelArgs(output_dir): return ['devmgr.epoch=%d' % time.time()] def AssertBootImagesExist(arch, platform): assert os.path.exists(GetTargetFile('zircon-a.zbi', arch, platform)), \ 'This checkout is missing the files necessary for\n' \ 'booting this configuration of Fuchsia.\n' \ 'To check out the files, add this entry to the "custom_vars"\n' \ 'section of your .gclient file:\n\n' \ ' "checkout_fuchsia_boot_images": "%s.%s"\n\n' % \ (platform, arch)