|  | # Copyright 2020 The Fuchsia Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | import attr | 
|  |  | 
|  | from recipe_engine import recipe_api | 
|  | from recipe_engine.config_types import Path | 
|  |  | 
|  | _SSH_CONFIG_TEMPLATE = """ | 
|  | Host * | 
|  | CheckHostIP no | 
|  | StrictHostKeyChecking no | 
|  | ForwardAgent no | 
|  | ForwardX11 no | 
|  | UserKnownHostsFile /dev/null | 
|  | User fuchsia | 
|  | IdentitiesOnly yes | 
|  | IdentityFile {identity} | 
|  | ServerAliveInterval 2 | 
|  | ServerAliveCountMax 5 | 
|  | ControlMaster auto | 
|  | ControlPersist 1m | 
|  | ControlPath /tmp/ssh-%r@%h:%p | 
|  | ConnectTimeout 5 | 
|  | """ | 
|  |  | 
|  |  | 
|  | @attr.s | 
|  | class SSHFilePaths(object): | 
|  | """Required files to setup SSH on FEMU.""" | 
|  |  | 
|  | # Recipe API, required | 
|  | _api = attr.ib(type=recipe_api.RecipeApi) | 
|  |  | 
|  | # Files for SSH | 
|  | host_private = attr.ib(type=Path, default=None) | 
|  | host_public = attr.ib(type=Path, default=None) | 
|  |  | 
|  | id_private = attr.ib(type=Path, default=None) | 
|  | id_public = attr.ib(type=Path, default=None) | 
|  |  | 
|  | def _exists(self, p): | 
|  | return p and self._api.path.exists(p) | 
|  |  | 
|  | def _exist(self): | 
|  | return all([ | 
|  | self._exists(self.host_private), | 
|  | self._exists(self.host_public), | 
|  | self._exists(self.id_private), | 
|  | self._exists(self.id_public), | 
|  | ]) | 
|  |  | 
|  | def _report_missing(self): | 
|  | result = [] | 
|  | if not self._exists(self.host_private): | 
|  | result.append(self.host_private) | 
|  | if not self._exists(self.host_public): | 
|  | result.append(self.host_public) | 
|  | if not self._exists(self.id_private): | 
|  | result.append(self.id_private) | 
|  | if not self._exists(self.id_public): | 
|  | result.append(self.id_public) | 
|  | return result | 
|  |  | 
|  |  | 
|  | class SSHApi(recipe_api.RecipeApi): | 
|  |  | 
|  | def __init__(self, *args, **kwargs): | 
|  | super(SSHApi, self).__init__(*args, **kwargs) | 
|  | self._ssh_paths = None | 
|  |  | 
|  | def _create_ssh_keys(self, timeout_secs=10 * 60): | 
|  | """Generate private, public key-pairs for Host side and Device side ssh keys.""" | 
|  | self.m.file.ensure_directory('init ssh cache', self.ssh_cache_root) | 
|  | if not self._ssh_paths: | 
|  | self._ssh_paths = SSHFilePaths( | 
|  | api=self.m, | 
|  | host_private=self.ssh_cache_root.join('ssh_host_key'), | 
|  | host_public=self.ssh_cache_root.join('ssh_host_key.pub'), | 
|  | id_private=self.ssh_cache_root.join('id_ed25519'), | 
|  | id_public=self.ssh_cache_root.join('id_ed25519.pub'), | 
|  | ) | 
|  |  | 
|  | if not self.m.file.listdir(name='check ssh cache content', | 
|  | source=self.ssh_cache_root, test_data=()): | 
|  | self.m.step( | 
|  | 'ssh-keygen host', | 
|  | [ | 
|  | 'ssh-keygen', | 
|  | '-t', | 
|  | 'ed25519', | 
|  | '-h', | 
|  | '-f', | 
|  | self._ssh_paths.host_private, | 
|  | '-P', | 
|  | '', | 
|  | '-N', | 
|  | '', | 
|  | ], | 
|  | infra_step=True, | 
|  | timeout=timeout_secs, | 
|  | ) | 
|  | self.m.step( | 
|  | 'ssh-keygen device', | 
|  | [ | 
|  | 'ssh-keygen', | 
|  | '-t', | 
|  | 'ed25519', | 
|  | '-f', | 
|  | self._ssh_paths.id_private, | 
|  | '-P', | 
|  | '', | 
|  | '-N', | 
|  | '', | 
|  | ], | 
|  | infra_step=True, | 
|  | timeout=timeout_secs, | 
|  | ) | 
|  | return self._ssh_paths | 
|  |  | 
|  | def generate_ssh_config(self, private_key_path, dest): | 
|  | """Generates and sets the private_key_path in ssh_config file.""" | 
|  | self.m.file.write_text( | 
|  | name='generate ssh_config at %s' % dest, | 
|  | dest=dest, | 
|  | text_data=_SSH_CONFIG_TEMPLATE.format(identity=private_key_path), | 
|  | ) | 
|  |  | 
|  | @property | 
|  | def ssh_paths(self): | 
|  | """Generate SSH keys. | 
|  |  | 
|  | Raises: | 
|  | StepFailure: When ssh key file paths do not exist. | 
|  | """ | 
|  | self._create_ssh_keys() | 
|  | if not self._ssh_paths._exist(): | 
|  | missing = self._ssh_paths._report_missing() | 
|  | ex = self.m.step.StepFailure( | 
|  | 'SSH paths do not exist. {missing_paths}'.format( | 
|  | missing_paths=missing | 
|  | ) | 
|  | ) | 
|  | ex.missing_paths = missing | 
|  | raise ex | 
|  | return self._ssh_paths | 
|  |  | 
|  | @property | 
|  | def ssh_cache_root(self): | 
|  | return self.m.buildbucket.builder_cache_path.join('ssh') |