def proxy(config: dict): """Start sshuttle proxy to Kubernetes.""" port = config["port"] if "ip" in config: # Typically host is macOS: ip = config["ip"] else: # Typically host is Linux, use default route: ip = None route_output = str(check_output(["route", "-n"]), "ascii") for line in route_output.splitlines(): parts = line.split() if parts[0] == "default" or parts[0] == "0.0.0.0": ip = parts[1] break assert ip is not None, route_output cidrs = config["cidrs"] expose_ports = config["expose_ports"] # Start the sshuttle VPN-like thing: # XXX duplicates code in telepresence, remove duplication main_process = Popen([ "sshuttle-telepresence", "-v", "--dns", "--method", "nat", "-e", ("ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null " + "-F /dev/null"), "--to-ns", "127.0.0.1:9053", "-r", "telepresence@{}:{}".format(ip, port) ] + cidrs) # Start the SSH tunnels to expose local services: subps = Subprocesses() runner = Runner.open("-", "kubectl", False) ssh = SSH(runner, port, ip) expose_local_services(subps, ssh, expose_ports) # Wait for everything to exit: wait_for_exit(runner, main_process, subps)
def run_local_command( runner: Runner, remote_info: RemoteInfo, args: argparse.Namespace, env_overrides: Dict[str, str], subprocesses: Subprocesses, socks_port: int, ssh: SSH, mount_dir: Optional[str], ) -> None: """--run-shell/--run support, run command locally.""" env = os.environ.copy() env.update(env_overrides) # Don't use runner.popen() since we want to give program access to current # stdout and stderr if it wants it. env["PROMPT_COMMAND"] = ('PS1="@{}|$PS1";unset PROMPT_COMMAND'.format( args.context)) # Inject replacements for unsupported tools like ping: unsupported_tools_path = get_unsupported_tools(args.method != "inject-tcp") env["PATH"] = unsupported_tools_path + ":" + env["PATH"] if mount_dir: env["TELEPRESENCE_ROOT"] = mount_dir # Make sure we use "bash", no "/bin/bash", so we get the copied version on # OS X: if args.run is None: # We skip .bashrc since it might e.g. have kubectl running to get bash # autocomplete, and Go programs don't like DYLD on macOS at least (see # https://github.com/datawire/telepresence/issues/125). command = ["bash", "--norc"] else: command = args.run if args.method == "inject-tcp": setup_torsocks(runner, env, socks_port, unsupported_tools_path) p = Popen(["torsocks"] + command, env=env) elif args.method == "vpn-tcp": connect_sshuttle(runner, remote_info, args, subprocesses, env, ssh) p = Popen(command, env=env) def terminate_if_alive(): runner.write("Shutting down local process...\n") if p.poll() is None: runner.write("Killing local process...\n") kill_process(p) atexit.register(terminate_if_alive) wait_for_exit(runner, p, subprocesses)
def run_docker_command( runner: Runner, remote_info: RemoteInfo, args: argparse.Namespace, remote_env: Dict[str, str], subprocesses: Subprocesses, ssh: SSH, ) -> None: """ --docker-run support. Connect using sshuttle running in a Docker container, and then run user container. :param args: Command-line args to telepresence binary. :param remote_env: Dictionary with environment on remote pod. :param mount_dir: Path to local directory where remote pod's filesystem is mounted. """ # Mount remote filesystem. We allow all users if we're using Docker because # we don't know what uid the Docker container will use: mount_dir, mount_cleanup = mount_remote_volumes( runner, remote_info, ssh, True, ) # Update environment: remote_env["TELEPRESENCE_ROOT"] = mount_dir remote_env["TELEPRESENCE_METHOD"] = "container" # mostly just for tests :( # Start the sshuttle container: name = random_name() config = { "port": ssh.port, "cidrs": get_proxy_cidrs(runner, args, remote_info, remote_env["KUBERNETES_SERVICE_HOST"]), "expose_ports": list(args.expose.local_to_remote()), } if sys.platform == "darwin": config["ip"] = MAC_LOOPBACK_IP # Image already has tini init so doesn't need --init option: subprocesses.append( runner.popen( docker_runify([ "--rm", "--privileged", "--name=" + name, TELEPRESENCE_LOCAL_IMAGE, "proxy", json.dumps(config) ])), make_docker_kill(runner, name)) # Write out env file: with NamedTemporaryFile("w", delete=False) as envfile: for key, value in remote_env.items(): envfile.write("{}={}\n".format(key, value)) atexit.register(os.remove, envfile.name) # Wait for sshuttle to be running: while True: try: runner.check_call( docker_runify([ "--network=container:" + name, "--rm", TELEPRESENCE_LOCAL_IMAGE, "wait" ])) except CalledProcessError as e: if e.returncode == 100: # We're good! break return name, envfile.name elif e.returncode == 125: # Docker failure, probably due to original container not # starting yet... so sleep and try again: sleep(1) continue else: raise else: raise RuntimeError( "Waiting container exited prematurely. File a bug, please!") # Start the container specified by the user: container_name = random_name() docker_command = docker_runify([ "--volume={}:{}".format(mount_dir, mount_dir), "--name=" + container_name, "--network=container:" + name, "--env-file", envfile.name, ]) # Older versions of Docker don't have --init: if "--init" in runner.get_output(["docker", "run", "--help"]): docker_command += ["--init"] docker_command += args.docker_run p = Popen(docker_command) def terminate_if_alive(): runner.write("Shutting down containers...\n") if p.poll() is None: runner.write("Killing local container...\n") make_docker_kill(runner, container_name)() mount_cleanup() atexit.register(terminate_if_alive) wait_for_exit(runner, p, subprocesses)
def main(session): """ Top-level function for Telepresence """ ######################################## # Preliminaries: No changes to the machine or the cluster, no cleanup session.args = parse_args() # tab-completion stuff goes here session.output = Output(session.args.logfile) del session.args.logfile session.kube_info, session.runner = analyze_args(session) span = session.runner.span() atexit.register(span.end) # Set up signal handling # Make SIGTERM and SIGHUP do clean shutdown (in particular, we want atexit # functions to be called): def shutdown(signum, frame): raise SystemExit(0) signal.signal(signal.SIGTERM, shutdown) signal.signal(signal.SIGHUP, shutdown) # Usage tracking call_scout(session) # Set up exit handling # XXX exit handling via atexit try: ######################################## # Now it's okay to change things runner = session.runner args = session.args # Set up the proxy pod (operation -> pod name) remote_info = start_proxy(runner, args) # Connect to the proxy (pod name -> ssh object) subprocesses, socks_port, ssh = connect(runner, remote_info, args) # Capture remote environment information (ssh object -> env info) env = get_remote_env(runner, args, remote_info) # Used by mount_remote session.ssh = ssh session.remote_info = remote_info session.env = env # Handle filesystem stuff (pod name, ssh object) mount_dir = mount_remote(session) # Maybe write environment files write_env_files(session) # Set up outbound networking (pod name, ssh object) # Launch user command with the correct environment (...) if args.method == "container": user_process = run_docker_command( runner, remote_info, args, env, subprocesses, ssh, mount_dir, ) else: user_process = run_local_command(runner, remote_info, args, env, subprocesses, socks_port, ssh, mount_dir) # Clean up (call the cleanup methods for everything above) # XXX handled by wait_for_exit and atexit wait_for_exit(runner, user_process, subprocesses) finally: pass