def expose_local_services(runner: Runner, ssh: SSH, port_numbers: List[Tuple[int, int]]) -> None: """Create SSH tunnels from remote proxy pod to local host. :param runner: The runner :param ssh: A 'SSH` instance. :param port_numbers: List of pairs of (local port, remote port). """ output = sys.stderr.isatty() if not port_numbers and output: runner.show( "No traffic is being forwarded from the remote Deployment to your" " local machine. You can use the --expose option to specify which" " ports you want to forward.") remote_forward_arguments = [] for local_port, remote_port in port_numbers: if output: runner.show("Forwarding remote port {} to local port {}.".format( remote_port, local_port, )) remote_forward_arguments.extend([ "-R", "*:{}:127.0.0.1:{}".format(remote_port, local_port), ]) if remote_forward_arguments: runner.launch("SSH port forward (exposed ports)", ssh.bg_command(remote_forward_arguments)) if output: runner.show("")
def expose_local_services(processes: Subprocesses, ssh: SSH, port_numbers: List[Tuple[int, int]]) -> None: """Create SSH tunnels from remote proxy pod to local host. :param processes: A `Subprocesses` instance. :param ssh: A 'SSH` instance. :param port_numbers: List of pairs of (local port, remote port). """ output = sys.stderr.isatty() if not port_numbers and output: print( "No traffic is being forwarded from the remote Deployment to your" " local machine. You can use the --expose option to specify which" " ports you want to forward.", file=sys.stderr) remote_forward_arguments = [] for local_port, remote_port in port_numbers: if output: print("Forwarding remote port {} to local port {}.".format( remote_port, local_port, ), file=sys.stderr) remote_forward_arguments.extend([ "-R", "*:{}:127.0.0.1:{}".format(remote_port, local_port), ]) if remote_forward_arguments: processes.append(ssh.popen(remote_forward_arguments)) if output: print("", file=sys.stderr)
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: runner = Runner.open("-", "kubectl", False) ssh = SSH(runner, port, ip) expose_local_services(runner, ssh, expose_ports) # Wait for everything to exit: wait_for_exit(runner, main_process)
def connect( runner: Runner, remote_info: RemoteInfo, cmdline_args: argparse.Namespace ) -> Tuple[Subprocesses, int, SSH]: """ Start all the processes that handle remote proxying. Return (Subprocesses, local port of SOCKS proxying tunnel, SSH instance). """ span = runner.span() processes = Subprocesses() # Keep local copy of pod logs, for debugging purposes: processes.append( runner.popen( runner.kubectl( cmdline_args.context, remote_info.namespace, [ "logs", "-f", remote_info.pod_name, "--container", remote_info.container_name ] ), bufsize=0, ) ) ssh = SSH(runner, find_free_port()) # forward remote port to here, by tunneling via remote SSH server: processes.append( runner.popen( runner.kubectl( cmdline_args.context, remote_info.namespace, [ "port-forward", remote_info.pod_name, "{}:8022".format(ssh.port) ] ) ) ) if cmdline_args.method == "container": # kubectl port-forward currently only listens on loopback. So we # portforward from the docker0 interface on Linux, and the lo0 alias we # added on OS X, to loopback (until we can use kubectl port-forward # option to listen on docker0 - # https://github.com/kubernetes/kubernetes/pull/46517, or all our users # have latest version of Docker for Mac, which has nicer solution - # https://github.com/datawire/telepresence/issues/224). if sys.platform == "linux": # If ip addr is available use it if not fall back to ifconfig. if which("ip"): docker_interfaces = re.findall( r"(\d+\.\d+\.\d+\.\d+)", runner.get_output(["ip", "addr", "show", "dev", "docker0"]) ) elif which("ifconfig"): docker_interfaces = re.findall( r"(\d+\.\d+\.\d+\.\d+)", runner.get_output(["ifconfig", "docker0"]) ) else: raise SystemExit("'ip addr' nor 'ifconfig' available") if len(docker_interfaces) == 0: raise SystemExit("No interface for docker found") docker_interface = docker_interfaces[0] else: # The way to get routing from container to host is via an alias on # lo0 (https://docs.docker.com/docker-for-mac/networking/). We use # an IP range that is assigned for testing network devices and # therefore shouldn't conflict with real IPs or local private # networks (https://tools.ietf.org/html/rfc6890). runner.check_call([ "sudo", "ifconfig", "lo0", "alias", MAC_LOOPBACK_IP ]) atexit.register( runner.check_call, ["sudo", "ifconfig", "lo0", "-alias", MAC_LOOPBACK_IP] ) docker_interface = MAC_LOOPBACK_IP processes.append( runner.popen([ "socat", "TCP4-LISTEN:{},bind={},reuseaddr,fork".format( ssh.port, docker_interface, ), "TCP4:127.0.0.1:{}".format(ssh.port) ]) ) ssh.wait() # In Docker mode this happens inside the local Docker container: if cmdline_args.method != "container": expose_local_services( processes, ssh, cmdline_args.expose.local_to_remote(), ) # Start tunnels for the SOCKS proxy (local -> remote) # and the local server for the proxy to poll (remote -> local). socks_port = find_free_port() local_server_port = find_free_port() local_server = LocalServer(local_server_port, runner.output) processes.append(local_server, local_server.kill) forward_args = [ "-L127.0.0.1:{}:127.0.0.1:9050".format(socks_port), "-R9055:127.0.0.1:{}".format(local_server_port) ] processes.append(ssh.popen(forward_args)) span.end() return processes, socks_port, ssh