def connect(runner: Runner, remote_info: RemoteInfo, is_container_mode: bool, expose: PortMapping) -> Tuple[int, SSH]: """ Start all the processes that handle remote proxying. Return (local port of SOCKS proxying tunnel, SSH instance). """ span = runner.span() # Keep local copy of pod logs, for debugging purposes. Set is_critical to # False so logs failing doesn't bring down the Telepresence session. runner.launch( "kubectl logs", runner.kubectl("logs", "-f", remote_info.pod_name, "--container", remote_info.container_name, "--tail=10"), bufsize=0, is_critical=False, ) ssh = SSH(runner, find_free_port()) # forward remote port to here, by tunneling via remote SSH server: runner.launch( "kubectl port-forward", runner.kubectl("port-forward", remote_info.pod_name, "{}:8022".format(ssh.port))) if not ssh.wait(): raise RuntimeError("SSH to the cluster failed to start.") # Create ssh tunnels. In the case of the container method, just show the # associated messages; the tunnels will be created in the network # container, where those messages are not visible to the user. expose_local_services(runner, ssh, list(expose.local_to_remote()), show_only=is_container_mode) # 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() launch_local_server(runner, local_server_port) forward_args = [ "-L127.0.0.1:{}:127.0.0.1:9050".format(socks_port), "-R9055:127.0.0.1:{}".format(local_server_port) ] runner.launch("SSH port forward (socks and proxy poll)", ssh.bg_command(forward_args)) span.end() return socks_port, ssh
def connect(runner: Runner, remote_info: RemoteInfo, is_container_mode: bool, expose: PortMapping) -> Tuple[int, SSH]: """ Start all the processes that handle remote proxying. Return (local port of SOCKS proxying tunnel, SSH instance). """ span = runner.span() # Keep local copy of pod logs, for debugging purposes: runner.launch( "kubectl logs", runner.kubectl("logs", "-f", remote_info.pod_name, "--container", remote_info.container_name, "--tail=10"), bufsize=0, ) ssh = SSH(runner, find_free_port()) # forward remote port to here, by tunneling via remote SSH server: runner.launch( "kubectl port-forward", runner.kubectl("port-forward", remote_info.pod_name, "{}:8022".format(ssh.port))) ssh.wait() # In Docker mode this happens inside the local Docker container: if not is_container_mode: expose_local_services( runner, ssh, list(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() launch_local_server(runner, local_server_port) forward_args = [ "-L127.0.0.1:{}:127.0.0.1:9050".format(socks_port), "-R9055:127.0.0.1:{}".format(local_server_port) ] runner.launch("SSH port forward (socks and proxy poll)", ssh.bg_command(forward_args)) span.end() return socks_port, ssh
def result(self): if self._result is None: print("Launching {}".format(self)) local_port = find_free_port() self.loopback_url = self.LOOPBACK_URL_TEMPLATE.format(local_port, ) # This is a local web server that the Telepresence probe can try to # interact with to verify network routing to the host. p = Popen( # TODO Just cross our fingers and hope this port is available... [executable, "-m", "http.server", str(local_port)], cwd=str(DIRECTORY), ) self._cleanup.append(lambda: _cleanup_process(p)) also_proxy = [ self.ALSO_PROXY_HOSTNAME.argument, self.ALSO_PROXY_IP.argument, self.ALSO_PROXY_CIDR.argument, ] http_servers = [ self.HTTP_SERVER_SAME_PORT, self.HTTP_SERVER_DIFFERENT_PORT, self.HTTP_SERVER_LOW_PORT, ] self._result = run_telepresence_probe( self._request, self.method, self.operation, self.DESIRED_ENVIRONMENT, {self.CLIENT_ENV_VAR: "FOO"}, [self.loopback_url], self.QUESTIONABLE_COMMANDS, self.INTERESTING_PATHS, also_proxy, http_servers) self._cleanup.append(self.ensure_dead) self._cleanup.append(self.cleanup_resources) return self._result
def result(self): if self._result is None: print("Launching {}".format(self)) local_port = find_free_port() self.loopback_url = self.LOOPBACK_URL_TEMPLATE.format(local_port) # This is a local web server that the Telepresence probe can try to # interact with to verify network routing to the host. # TODO Just cross our fingers and hope this port is available... server_cmd = [executable, "-m", "http.server", str(local_port)] p = Popen(server_cmd, cwd=str(DIRECTORY)) self._cleanup.append(lambda: _cleanup_process(p)) # This is for testing the container method's connectivity to the # host using port forwarding. self.fwd_url = self.LOOPBACK_URL_TEMPLATE.format( LOCAL_WEB_CONTAINER_PORT) server_cmd = [executable, "-m", "http.server", str(LOCAL_WEB_PORT)] p2 = Popen(server_cmd, cwd=str(DIRECTORY)) self._cleanup.append(lambda: _cleanup_process(p2)) also_proxy = [ self.ALSO_PROXY_HOSTNAME.argument, self.ALSO_PROXY_IP.argument, self.ALSO_PROXY_CIDR.argument, ] http_servers = [ self.HTTP_SERVER_SAME_PORT, self.HTTP_SERVER_DIFFERENT_PORT, self.HTTP_SERVER_LOW_PORT, ] self.desired_exit_code = len(str(self)) self._result = "FAILED" self._result = run_telepresence_probe( self._request, self.method, self.operation, self.DESIRED_ENVIRONMENT, {self.CLIENT_ENV_VAR: "FOO"}, [self.loopback_url, self.fwd_url], self.QUESTIONABLE_COMMANDS, self.INTERESTING_PATHS, also_proxy, http_servers, self.desired_exit_code, ) self._cleanup.append(self.ensure_dead) self._cleanup.append(self.cleanup_resources) if self._result == "FAILED": pytest.skip("Probe has failed.") return self._result
def connect(runner: Runner, remote_info: RemoteInfo, is_container_mode: bool, expose: PortMapping) -> Tuple[int, SSH]: """ Start all the processes that handle remote proxying. Return (local port of SOCKS proxying tunnel, SSH instance). """ span = runner.span() # Keep local copy of pod logs, for debugging purposes: runner.launch( "kubectl logs", runner.kubectl("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: runner.launch( "kubectl port-forward", runner.kubectl("port-forward", remote_info.pod_name, "{}:8022".format(ssh.port))) if is_container_mode: # 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 runner.platform == "linux": # If ip addr is available use it if not fall back to ifconfig. missing = runner.depend(["ip", "ifconfig"]) if "ip" not in missing: docker_interfaces = re.findall( r"(\d+\.\d+\.\d+\.\d+)", runner.get_output(["ip", "addr", "show", "dev", "docker0"])) elif "ifconfig" not in missing: docker_interfaces = re.findall( r"(\d+\.\d+\.\d+\.\d+)", runner.get_output(["ifconfig", "docker0"])) else: raise runner.fail( """At least one of "ip addr" or "ifconfig" must be """ + "available to retrieve Docker interface info.") if len(docker_interfaces) == 0: raise runner.fail("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]) runner.add_cleanup( "Mac Loopback", runner.check_call, ["sudo", "ifconfig", "lo0", "-alias", MAC_LOOPBACK_IP]) docker_interface = MAC_LOOPBACK_IP runner.launch("socat for docker", [ "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 not is_container_mode: expose_local_services( runner, ssh, list(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() runner.track_background( launch_local_server(local_server_port, runner.output)) forward_args = [ "-L127.0.0.1:{}:127.0.0.1:9050".format(socks_port), "-R9055:127.0.0.1:{}".format(local_server_port) ] runner.launch("SSH port forward (socks and proxy poll)", ssh.bg_command(forward_args)) span.end() return socks_port, ssh
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
def run_docker_command( runner: Runner, remote_info: RemoteInfo, docker_run: List[str], expose: PortMapping, to_pod: List[int], from_pod: List[int], container_to_host: PortMapping, remote_env: Dict[str, str], ssh: SSH, mount_dir: Optional[str], use_docker_mount: Optional[bool], pod_info: Dict[str, str], ) -> "subprocess.Popen[bytes]": """ --docker-run support. Connect using sshuttle running in a Docker container, and then run user container. :param remote_env: Dictionary with environment on remote pod. :param mount_dir: Path to local directory where remote pod's filesystem is mounted. """ # Update environment: remote_env["TELEPRESENCE_METHOD"] = "container" # mostly just for tests :( # Extract --publish flags and add them to the sshuttle container, which is # responsible for defining the network entirely. docker_args, publish_args = parse_docker_args(docker_run) # Point a host port to the network container's sshd container_sshd_port = find_free_port() publish_args.append( "--publish=127.0.0.1:{}:38022/tcp".format(container_sshd_port) ) local_ssh = SSH(runner, container_sshd_port, "[email protected]") # Start the network (sshuttle) container: name = random_name() config = { "cidrs": ["0/0"], "expose_ports": list(expose.local_to_remote()), "to_pod": to_pod, "from_pod": from_pod, } dns_args = [] if "hostname" in pod_info: dns_args.append("--hostname={}".format(pod_info["hostname"].strip())) if "hosts" in pod_info: dns_args.extend(parse_hosts_aliases(pod_info["hosts"])) if "resolv" in pod_info: dns_args.extend(parse_resolv_conf(pod_info["resolv"])) # Image already has tini init so doesn't need --init option: span = runner.span() runner.launch( "Network container", runner.docker( "run", *publish_args, *dns_args, "--rm", "--privileged", "--name=" + name, TELEPRESENCE_LOCAL_IMAGE, "proxy", json.dumps(config) ), killer=make_docker_kill(runner, name), keep_session=runner.sudo_for_docker, ) # Set up ssh tunnel to allow the container to reach the cluster if not local_ssh.wait(): raise RuntimeError("SSH to the network container failed to start.") container_forward_args = ["-R", "38023:127.0.0.1:{}".format(ssh.port)] for container_port, host_port in container_to_host.local_to_remote(): if runner.chatty: runner.show( "Forwarding container port {} to host port {}.".format( container_port, host_port ) ) container_forward_args.extend([ "-R", "{}:127.0.0.1:{}".format(container_port, host_port) ]) runner.launch( "Local SSH port forward", local_ssh.bg_command(container_forward_args) ) # Wait for sshuttle to be running: sshuttle_ok = False for _ in runner.loop_until(120, 1): try: runner.check_call( runner.docker( "run", "--network=container:" + name, "--rm", TELEPRESENCE_LOCAL_IMAGE, "wait" ) ) except subprocess.CalledProcessError as e: if e.returncode == 100: # We're good! sshuttle_ok = True break elif e.returncode == 125: # Docker failure, probably due to original container not # starting yet... so try again: continue else: raise else: raise RuntimeError( "Waiting container exited prematurely. File a bug, please!" ) if not sshuttle_ok: # This used to loop forever. Now we time out after two minutes. raise RuntimeError( "Waiting for network container timed out. File a bug, please!" ) # Start the container specified by the user: container_name = random_name() docker_command = runner.docker( "run", "--name=" + container_name, "--network=container:" + name, env=True, ) # Prepare container environment for key in remote_env: docker_command.append("-e={}".format(key)) docker_env = os.environ.copy() docker_env.update(remote_env) if mount_dir: if use_docker_mount: mount_volume = "telepresence-" + runner.session_id else: mount_volume = mount_dir docker_command.append("--volume={}:{}".format(mount_volume, mount_dir)) # Don't add --init if the user is doing something with it init_args = [ arg for arg in docker_args if arg == "--init" or arg.startswith("--init=") ] # Older versions of Docker don't have --init: docker_run_help = runner.get_output(["docker", "run", "--help"]) if not init_args and "--init" in docker_run_help: docker_command += ["--init"] docker_command += docker_args span.end() runner.show("Setup complete. Launching your container.") process = subprocess.Popen(docker_command, env=docker_env) def terminate_if_alive() -> None: runner.write("Shutting down containers...\n") if process.poll() is None: runner.write("Killing local container...\n") make_docker_kill(runner, container_name)() runner.add_cleanup("Terminate local container", terminate_if_alive) return process
def command(runner, args): with runner.cleanup_handling(), crash_reporting(runner): # Process arguments name = args.name or runner.session_id local_port = args.port deployment = args.deployment patterns = [ dict(name=header, regex_match=pattern) for header, pattern in args.match ] # Inform the user runner.show("Setting up intercept session {}".format(name)) runner.show("Intercepting requests to {}".format(deployment)) runner.show("and redirecting them to localhost:{}".format(local_port)) runner.show("when the following headers match:") for obj in patterns: runner.show(" {name}: {regex_match}".format(**obj)) # Check the deployment exists and has the sidecar # FIXME: implement # Connect to the proxy runner.show("Connecting to the Telepresence Proxy") proxy_name = "telepresence-proxy" remote_info = get_remote_info(runner, proxy_name, "deployment") old_chatty, runner.chatty = runner.chatty, False _, ssh = connect(runner, remote_info, False, PortMapping()) runner.chatty = old_chatty # Forward local port to the proxy's API server api_server_port = find_free_port() forward_args = [ "-L127.0.0.1:{}:127.0.0.1:8081".format(api_server_port) ] runner.launch("SSH port forward (api server)", ssh.bg_command(forward_args)) url = "http://127.0.0.1:{}/intercept/{}".format( api_server_port, deployment) runner.write("Proxy URL is {}".format(url)) # Start the intercept, get the remote port on the proxy data = json.dumps(dict(name=name, patterns=patterns)) response = proxy_request(runner, url, data, "POST") try: remote_port = int(response) except ValueError: raise runner.fail("Unexpected response from the proxy") # Forward remote proxy port to the local port. This is how the # intercepted requests will get from the proxy to the user's code. forward_args = ["-R{}:127.0.0.1:{}".format(remote_port, local_port)] runner.launch("SSH port forward (proxy to user code)", ssh.bg_command(forward_args)) runner.add_cleanup("Delete intercept", proxy_request, runner, url, str(remote_port), "DELETE") runner.show("Intercept is running. Press Ctrl-C/Ctrl-Break to quit.") user_process = Popen(["cat"], stdout=DEVNULL) runner.wait_for_exit(user_process)