Example #1
0
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)
Example #2
0
def connect_sshuttle(
    runner: Runner, remote_info: RemoteInfo, args: argparse.Namespace,
    subprocesses: Subprocesses, env: Dict[str, str], ssh: SSH
):
    """Connect to Kubernetes using sshuttle."""
    # Make sure we have sudo credentials by doing a small sudo in advance
    # of sshuttle using it:
    Popen(["sudo", "true"]).wait()
    sshuttle_method = "auto"
    if sys.platform.startswith("linux"):
        # sshuttle tproxy mode seems to have issues:
        sshuttle_method = "nat"
    subprocesses.append(
        runner.popen([
            "sshuttle-telepresence",
            "-v",
            "--dns",
            "--method",
            sshuttle_method,
            "-e",
            (
                "ssh -oStrictHostKeyChecking=no " +
                "-oUserKnownHostsFile=/dev/null -F /dev/null"
            ),
            # DNS proxy running on remote pod:
            "--to-ns",
            "127.0.0.1:9053",
            "-r",
            "telepresence@localhost:" + str(ssh.port),
        ] + get_proxy_cidrs(
            runner, args, remote_info, env["KUBERNETES_SERVICE_HOST"]
        ))
    )

    # sshuttle will take a while to startup. We can detect it being up when
    # DNS resolution of services starts working. We use a specific single
    # segment so any search/domain statements in resolv.conf are applied,
    # which then allows the DNS proxy to detect the suffix domain and
    # filter it out.
    def get_hellotelepresence(counter=iter(range(10000))):
        # On Macs, and perhaps elsewhere, there is OS-level caching of
        # NXDOMAIN, so bypass caching by sending new domain each time. Another,
        # less robust alternative, is to `killall -HUP mDNSResponder`.
        runner.get_output([
            "python3", "-c",
            "import socket; socket.gethostbyname('hellotelepresence{}')".
            format(next(counter))
        ])

    start = time()
    while time() - start < 20:
        try:
            get_hellotelepresence()
            sleep(1)  # just in case there's more to startup
            break
        except CalledProcessError:
            sleep(0.1)
    get_hellotelepresence()
Example #3
0
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)
Example #4
0
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)
Example #5
0
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
Example #6
0
def run_docker_command(
    runner: Runner,
    remote_info: RemoteInfo,
    args: argparse.Namespace,
    remote_env: Dict[str, str],
    subprocesses: Subprocesses,
    ssh: SSH,
    mount_dir: Optional[str],
) -> Popen:
    """
    --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.
    """
    # 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(args.docker_run)

    # 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:
    span = runner.span()
    subprocesses.append(
        runner.popen(
            docker_runify(publish_args + [
                "--rm", "--privileged", "--name=" +
                name, TELEPRESENCE_LOCAL_IMAGE, "proxy",
                json.dumps(config)
            ])), make_docker_kill(runner, 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
            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([
        "--name=" + container_name,
        "--network=container:" + name,
    ])

    # 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:
        docker_command.append("--volume={}:{}".format(mount_dir, 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:
    if not init_args and "--init" in runner.get_output(
        ["docker", "run", "--help"]):
        docker_command += ["--init"]
    docker_command += docker_args
    span.end()

    p = Popen(docker_command, env=docker_env)

    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)()

    atexit.register(terminate_if_alive)
    return p