mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Fix #1711: adapter: access tokens
Generate server access token in enable_attach(), propagate it to the adapter, and have the adapter authenticate to the server via "pydevdAuthorize". Generate client access token in adapter when not spawned by server, and propagate it to pydevd. Fix pydevd to correctly propagate access tokens for subprocesses that are not forked.
This commit is contained in:
parent
f51c96450c
commit
0d79c16f80
12 changed files with 111 additions and 58 deletions
|
|
@ -170,4 +170,4 @@ __file__ = os.path.abspath(__file__)
|
|||
|
||||
# Preload encodings that we're going to use to avoid import deadlocks on Python 2,
|
||||
# before importing anything from ptvsd.
|
||||
map(codecs.lookup, ["ascii", "utf8", "utf-8", "latin1", "latin-1", "idna"])
|
||||
map(codecs.lookup, ["ascii", "utf8", "utf-8", "latin1", "latin-1", "idna", "hex"])
|
||||
|
|
|
|||
|
|
@ -67,14 +67,16 @@ def _get_setup_updated_with_protocol(setup):
|
|||
|
||||
def _get_python_c_args(host, port, indC, args, setup):
|
||||
setup = _get_setup_updated_with_protocol(setup)
|
||||
return ("import sys; sys.path.append(r'%s'); import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL=%r;"
|
||||
"pydevd.settrace(host=%r, port=%s, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True); "
|
||||
return ("import sys; sys.path.append(r'%s'); import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL=%r; "
|
||||
"pydevd.settrace(host=%r, port=%s, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True, access_token=%r, ide_access_token=%r); "
|
||||
"from pydevd import SetupHolder; SetupHolder.setup = %s; %s"
|
||||
) % (
|
||||
pydev_src_dir,
|
||||
pydevd_constants.get_protocol(),
|
||||
host,
|
||||
port,
|
||||
setup.get('access-token'),
|
||||
setup.get('ide-access-token'),
|
||||
setup,
|
||||
args[indC + 1])
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
|||
|
||||
import argparse
|
||||
import atexit
|
||||
import codecs
|
||||
import json
|
||||
import locale
|
||||
import os
|
||||
|
|
@ -19,7 +20,7 @@ __file__ = os.path.abspath(__file__)
|
|||
|
||||
|
||||
def main(args):
|
||||
from ptvsd.common import log, options as common_options
|
||||
from ptvsd.common import compat, log, options as common_options
|
||||
from ptvsd.adapter import ide, servers, sessions, options as adapter_options
|
||||
|
||||
if args.log_stderr:
|
||||
|
|
@ -31,10 +32,17 @@ def main(args):
|
|||
log.to_file(prefix="ptvsd.adapter")
|
||||
log.describe_environment("ptvsd.adapter startup environment:")
|
||||
|
||||
if args.for_enable_attach and args.port is None:
|
||||
log.error("--for-enable-attach requires --port")
|
||||
if args.for_server and args.port is None:
|
||||
log.error("--for-server requires --port")
|
||||
sys.exit(64)
|
||||
|
||||
# adapter_options.ide_access_token = args.ide_access_token
|
||||
adapter_options.server_access_token = args.server_access_token
|
||||
if not args.for_server:
|
||||
adapter_options.adapter_access_token = compat.force_str(
|
||||
codecs.encode(os.urandom(32), "hex")
|
||||
)
|
||||
|
||||
server_host, server_port = servers.listen()
|
||||
ide_host, ide_port = ide.listen(port=args.port)
|
||||
endpoints_info = {
|
||||
|
|
@ -42,7 +50,7 @@ def main(args):
|
|||
"server": {"host": server_host, "port": server_port},
|
||||
}
|
||||
|
||||
if args.for_enable_attach:
|
||||
if args.for_server:
|
||||
log.info("Writing endpoints info to stdout:\n{0!r}", endpoints_info)
|
||||
print(json.dumps(endpoints_info))
|
||||
sys.stdout.flush()
|
||||
|
|
@ -99,10 +107,16 @@ def _parse_argv(argv):
|
|||
help="start the adapter in debugServer mode on the specified host",
|
||||
)
|
||||
|
||||
# parser.add_argument(
|
||||
# "--ide-access-token", type=str, help="access token expected by the IDE"
|
||||
# )
|
||||
|
||||
parser.add_argument(
|
||||
"--for-enable-attach", action="store_true", help=argparse.SUPPRESS
|
||||
"--server-access-token", type=str, help="access token expected by the server"
|
||||
)
|
||||
|
||||
parser.add_argument("--for-server", action="store_true", help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument(
|
||||
"--log-dir",
|
||||
type=str,
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ def spawn_debuggee(session, start_request, sudo, args, console, console_title):
|
|||
_, port = servers.Connection.listener.getsockname()
|
||||
arguments = dict(start_request.arguments)
|
||||
arguments["port"] = port
|
||||
arguments["clientAccessToken"] = adapter_options.adapter_access_token
|
||||
spawn_launcher()
|
||||
|
||||
if not session.wait_for(
|
||||
|
|
|
|||
|
|
@ -11,3 +11,12 @@ or configuartion files.
|
|||
|
||||
log_stderr = False
|
||||
"""Whether detailed logs are written to stderr."""
|
||||
|
||||
# ide_access_token = None
|
||||
# """Access token used to authenticate with the IDE."""
|
||||
|
||||
server_access_token = None
|
||||
"""Access token used to authenticate with the server."""
|
||||
|
||||
adapter_access_token = None
|
||||
"""Access token used by the server to authenticate with this adapter."""
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import time
|
|||
|
||||
import ptvsd
|
||||
from ptvsd.common import compat, fmt, json, log, messaging, sockets
|
||||
from ptvsd.adapter import components
|
||||
from ptvsd.adapter import components, options
|
||||
|
||||
_lock = threading.RLock()
|
||||
|
||||
|
|
@ -49,6 +49,7 @@ class Connection(sockets.ClientConnection):
|
|||
self.channel.start()
|
||||
|
||||
try:
|
||||
self.authenticate()
|
||||
info = self.channel.request("pydevdSystemInfo")
|
||||
process_info = info("process", json.object())
|
||||
self.pid = process_info("pid", int)
|
||||
|
|
@ -126,6 +127,16 @@ if 'ptvsd' not in sys.modules:
|
|||
def __str__(self):
|
||||
return "Server" + fmt("[?]" if self.pid is None else "[pid={0}]", self.pid)
|
||||
|
||||
def authenticate(self):
|
||||
if options.server_access_token is None and options.adapter_access_token is None:
|
||||
return
|
||||
auth = self.channel.request(
|
||||
"pydevdAuthorize", {"debugServerAccessToken": options.server_access_token}
|
||||
)
|
||||
if auth["clientAccessToken"] != options.adapter_access_token:
|
||||
self.channel.close()
|
||||
raise RuntimeError('Mismatched "clientAccessToken"; server not authorized.')
|
||||
|
||||
def request(self, request):
|
||||
raise request.isnt_valid(
|
||||
"Requests from the debug server to the IDE are not allowed."
|
||||
|
|
@ -230,6 +241,7 @@ class Server(components.Component):
|
|||
|
||||
def initialize(self, request):
|
||||
assert request.is_request("initialize")
|
||||
self.connection.authenticate()
|
||||
request = self.channel.propagate(request)
|
||||
request.wait_for_response()
|
||||
self.capabilities = self.Capabilities(self, request.response)
|
||||
|
|
@ -394,6 +406,8 @@ def inject(pid, ptvsd_args):
|
|||
"--port",
|
||||
str(port),
|
||||
]
|
||||
if options.adapter_access_token is not None:
|
||||
cmdline += ["--client-access-token", options.adapter_access_token]
|
||||
cmdline += ptvsd_args
|
||||
cmdline += ["--pid", str(pid)]
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ def launch_request(request):
|
|||
|
||||
if not request("noDebug", json.default(False)):
|
||||
port = request("port", int)
|
||||
ptvsd_args = request("ptvsdArgs", json.array(unicode))
|
||||
cmdline += [
|
||||
compat.filename(os.path.dirname(ptvsd.__file__)),
|
||||
"--client",
|
||||
|
|
@ -70,7 +69,12 @@ def launch_request(request):
|
|||
"127.0.0.1",
|
||||
"--port",
|
||||
str(port),
|
||||
] + ptvsd_args
|
||||
]
|
||||
client_access_token = request("clientAccessToken", unicode, optional=True)
|
||||
if client_access_token != ():
|
||||
cmdline += ["--client-access-token", compat.filename(client_access_token)]
|
||||
ptvsd_args = request("ptvsdArgs", json.array(unicode))
|
||||
cmdline += ptvsd_args
|
||||
|
||||
program = module = code = ()
|
||||
if "program" in request:
|
||||
|
|
@ -119,7 +123,7 @@ def launch_request(request):
|
|||
# If neither the property nor the option were specified explicitly, choose
|
||||
# the default depending on console type - "internalConsole" needs it to
|
||||
# provide any output at all, but it's unnecessary for the terminals.
|
||||
redirect_output = (request("console", unicode) == "internalConsole")
|
||||
redirect_output = request("console", unicode) == "internalConsole"
|
||||
if redirect_output:
|
||||
# sys.stdout buffering must be disabled - otherwise we won't see the output
|
||||
# at all until the buffer fills up.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import codecs
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
|
|
@ -12,7 +13,7 @@ import sys
|
|||
import threading
|
||||
|
||||
import ptvsd
|
||||
from ptvsd.common import log, options as common_opts
|
||||
from ptvsd.common import compat, log, options as common_opts
|
||||
from ptvsd.server import options as server_opts
|
||||
from _pydevd_bundle.pydevd_constants import get_global_debugger
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_file
|
||||
|
|
@ -25,10 +26,8 @@ _ADAPTER_PATH = os.path.join(os.path.dirname(ptvsd.__file__), "adapter")
|
|||
def wait_for_attach():
|
||||
log.info("wait_for_attach()")
|
||||
dbg = get_global_debugger()
|
||||
if not bool(dbg):
|
||||
msg = "wait_for_attach() called before enable_attach()."
|
||||
log.info(msg)
|
||||
raise AssertionError(msg)
|
||||
if dbg is None:
|
||||
raise RuntimeError("wait_for_attach() called before enable_attach().")
|
||||
|
||||
cancel_event = threading.Event()
|
||||
ptvsd.wait_for_attach.cancel = wait_for_attach.cancel = cancel_event.set
|
||||
|
|
@ -80,16 +79,20 @@ def enable_attach(dont_trace_start_patterns, dont_trace_end_patterns):
|
|||
if hasattr(enable_attach, "called"):
|
||||
raise RuntimeError("enable_attach() can only be called once per process.")
|
||||
|
||||
server_access_token = compat.force_str(codecs.encode(os.urandom(32), "hex"))
|
||||
|
||||
import subprocess
|
||||
|
||||
adapter_args = [
|
||||
sys.executable,
|
||||
_ADAPTER_PATH,
|
||||
"--for-server",
|
||||
"--host",
|
||||
server_opts.host,
|
||||
"--port",
|
||||
str(server_opts.port),
|
||||
"--for-enable-attach",
|
||||
"--server-access-token",
|
||||
server_access_token,
|
||||
]
|
||||
|
||||
if common_opts.log_dir is not None:
|
||||
|
|
@ -122,6 +125,8 @@ def enable_attach(dont_trace_start_patterns, dont_trace_end_patterns):
|
|||
block_until_connected=True,
|
||||
dont_trace_start_patterns=dont_trace_start_patterns,
|
||||
dont_trace_end_patterns=dont_trace_end_patterns,
|
||||
access_token=server_access_token,
|
||||
ide_access_token=server_opts.client_access_token,
|
||||
)
|
||||
|
||||
log.info("pydevd debug client connected to: {0}:{1}", host, port)
|
||||
|
|
@ -146,6 +151,7 @@ def attach(dont_trace_start_patterns, dont_trace_end_patterns):
|
|||
patch_multiprocessing=server_opts.multiprocess,
|
||||
dont_trace_start_patterns=dont_trace_start_patterns,
|
||||
dont_trace_end_patterns=dont_trace_end_patterns,
|
||||
ide_access_token=server_opts.client_access_token,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ __file__ = os.path.abspath(__file__)
|
|||
_ptvsd_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
|
||||
def attach(host, port, client, log_dir=None):
|
||||
def attach(host, port, client, log_dir=None, client_access_token=None):
|
||||
try:
|
||||
import sys
|
||||
|
||||
|
|
@ -68,6 +68,7 @@ def attach(host, port, client, log_dir=None):
|
|||
options.client = client
|
||||
options.host = host
|
||||
options.port = port
|
||||
options.client_access_token = client_access_token
|
||||
|
||||
if options.client:
|
||||
ptvsd.attach((options.host, options.port))
|
||||
|
|
|
|||
|
|
@ -108,8 +108,7 @@ switches = [
|
|||
("--log-stderr", None, set_log_stderr(), False),
|
||||
|
||||
# Switches that are used internally by the IDE or ptvsd itself.
|
||||
("--subprocess-of", "<pid>", set_arg("subprocess_of", pid), False),
|
||||
("--subprocess-notify", "<port>", set_arg("subprocess_notify", port), False),
|
||||
("--client-access-token", "<token>", set_arg("client_access_token"), False),
|
||||
|
||||
# Targets. The "" entry corresponds to positional command line arguments,
|
||||
# i.e. the ones not preceded by any switch name.
|
||||
|
|
@ -270,31 +269,24 @@ def attach_to_pid():
|
|||
log.info("Attaching to process with PID={0}", options.target)
|
||||
|
||||
pid = options.target
|
||||
host = options.host
|
||||
port = options.port
|
||||
client = options.client
|
||||
log_dir = common_opts.log_dir
|
||||
if log_dir is None:
|
||||
log_dir = ""
|
||||
|
||||
try:
|
||||
attach_pid_injected_dirname = os.path.join(
|
||||
os.path.dirname(ptvsd.__file__), "server"
|
||||
)
|
||||
assert os.path.exists(attach_pid_injected_dirname)
|
||||
attach_pid_injected_dirname = os.path.join(
|
||||
os.path.dirname(ptvsd.__file__), "server"
|
||||
)
|
||||
assert os.path.exists(attach_pid_injected_dirname)
|
||||
|
||||
log_dir = log_dir.replace("\\", "/")
|
||||
log_dir = (common_opts.log_dir or "").replace("\\", "/")
|
||||
encode = lambda s: list(bytearray(s.encode("utf-8")))
|
||||
setup = {
|
||||
"script": encode(attach_pid_injected_dirname),
|
||||
"host": encode(options.host),
|
||||
"port": options.port,
|
||||
"client": options.client,
|
||||
"log_dir": encode(log_dir),
|
||||
"client_access_token": encode(options.client_access_token),
|
||||
}
|
||||
|
||||
encode = lambda s: list(bytearray(s.encode("utf-8")))
|
||||
setup = {
|
||||
"script": encode(attach_pid_injected_dirname),
|
||||
"host": encode(host),
|
||||
"port": port,
|
||||
"client": client,
|
||||
"log_dir": encode(log_dir),
|
||||
}
|
||||
|
||||
python_code = """
|
||||
python_code = """
|
||||
import sys;
|
||||
import codecs;
|
||||
decode = lambda s: codecs.utf_8_decode(bytearray(s))[0];
|
||||
|
|
@ -304,32 +296,39 @@ import attach_pid_injected;
|
|||
sys.path.remove(script_path);
|
||||
host = decode({host});
|
||||
log_dir = decode({log_dir}) or None;
|
||||
attach_pid_injected.attach(port={port}, host=host, client={client}, log_dir=log_dir)
|
||||
client_access_token = decode({client_access_token}) or None;
|
||||
attach_pid_injected.attach(
|
||||
port={port},
|
||||
host=host,
|
||||
client={client},
|
||||
log_dir=log_dir,
|
||||
client_access_token=client_access_token,
|
||||
)
|
||||
"""
|
||||
python_code = python_code.replace("\r", "").replace("\n", "").format(**setup)
|
||||
log.info("Code to be injected: \n{0}", python_code.replace(";", ";\n"))
|
||||
python_code = python_code.replace("\r", "").replace("\n", "").format(**setup)
|
||||
log.info("Code to be injected: \n{0}", python_code.replace(";", ";\n"))
|
||||
|
||||
# pydevd restriction on characters in injected code.
|
||||
assert not (
|
||||
{'"', "'", "\r", "\n"} & set(python_code)
|
||||
), "Injected code should not contain any single quotes, double quots, or newlines."
|
||||
# pydevd restriction on characters in injected code.
|
||||
assert not (
|
||||
{'"', "'", "\r", "\n"} & set(python_code)
|
||||
), "Injected code should not contain any single quotes, double quotes, or newlines."
|
||||
|
||||
pydevd_attach_to_process_path = os.path.join(
|
||||
os.path.dirname(pydevd.__file__), "pydevd_attach_to_process"
|
||||
)
|
||||
pydevd_attach_to_process_path = os.path.join(
|
||||
os.path.dirname(pydevd.__file__), "pydevd_attach_to_process"
|
||||
)
|
||||
|
||||
assert os.path.exists(pydevd_attach_to_process_path)
|
||||
sys.path.append(pydevd_attach_to_process_path)
|
||||
assert os.path.exists(pydevd_attach_to_process_path)
|
||||
sys.path.append(pydevd_attach_to_process_path)
|
||||
|
||||
try:
|
||||
import add_code_to_python_process # noqa
|
||||
|
||||
show_debug_info_on_target_process = 0 # hard-coded (1 to debug)
|
||||
log.info("Injecting code into process with PID={0} ...", pid)
|
||||
add_code_to_python_process.run_python_code(
|
||||
pid,
|
||||
python_code,
|
||||
connect_debugger_tracing=True,
|
||||
show_debug_info=show_debug_info_on_target_process,
|
||||
show_debug_info=int(os.getenv("PTVSD_ATTACH_BY_PID_DEBUG_INFO", "0")),
|
||||
)
|
||||
except Exception:
|
||||
raise log.exception("Code injection into PID={0} failed:", pid)
|
||||
|
|
|
|||
|
|
@ -53,3 +53,6 @@ multiprocess = True
|
|||
"""Whether this ptvsd instance is running in multiprocess mode, detouring creation
|
||||
of new processes and enabling debugging for them.
|
||||
"""
|
||||
|
||||
client_access_token = None
|
||||
"""Access token to authenticate with the adapter."""
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ def dump():
|
|||
log.info("Dumping logs from {0!j}", options.log_dir)
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(options.log_dir):
|
||||
for name in filenames:
|
||||
for name in sorted(filenames):
|
||||
if not name.startswith("ptvsd") and not name.startswith("pydevd"):
|
||||
continue
|
||||
try:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue