Exemple #1
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)
Exemple #2
0
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)
Exemple #3
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)
Exemple #4
0
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