def test_secure_write_unix(): directory = tempfile.mkdtemp() fname = os.path.join(directory, 'check_perms') try: with secure_write(fname) as f: f.write('test 1') mode = os.stat(fname).st_mode assert 0o0600 == (stat.S_IMODE(mode) & 0o7677) # tolerate owner-execute bit with open(fname, 'r') as f: assert f.read() == 'test 1' # Try changing file permissions ahead of time os.chmod(fname, 0o755) with secure_write(fname) as f: f.write('test 2') mode = os.stat(fname).st_mode assert 0o0600 == (stat.S_IMODE(mode) & 0o7677) # tolerate owner-execute bit with open(fname, 'r') as f: assert f.read() == 'test 2' finally: shutil.rmtree(directory)
def test_secure_write_unix(): directory = tempfile.mkdtemp() fname = os.path.join(directory, 'check_perms') try: with secure_write(fname) as f: f.write('test 1') mode = os.stat(fname).st_mode assert '0600' == oct(stat.S_IMODE(mode)).replace('0o', '0') with open(fname, 'r') as f: assert f.read() == 'test 1' # Try changing file permissions ahead of time os.chmod(fname, 0o755) with secure_write(fname) as f: f.write('test 2') mode = os.stat(fname).st_mode assert '0600' == oct(stat.S_IMODE(mode)).replace('0o', '0') with open(fname, 'r') as f: assert f.read() == 'test 2' finally: shutil.rmtree(directory)
def record_connection_info(self, conn_info): log.info("Connection info: %s", conn_info) runtime_dir = jupyter_runtime_dir() ensure_dir_exists(runtime_dir) fname = os.path.join(runtime_dir, 'kernel-%s.json' % uuid4()) # Only ever write this file as user read/writeable # This would otherwise introduce a vulnerability as a file has secrets # which would let others execute arbitrarily code as you with secure_write(fname) as f: f.write(json.dumps(conn_info, indent=2)) log.info("To connect a client: --existing %s", os.path.basename(fname)) return fname
def test_secure_write_win32(): def fetch_win32_permissions(filename): '''Extracts file permissions on windows using icacls''' role_permissions = {} for index, line in enumerate( os.popen("icacls %s" % filename).read().splitlines()): if index == 0: line = line.split(filename)[-1].strip().lower() match = re.match(r'\s*([^:]+):\(([^\)]*)\)', line) if match: usergroup, permissions = match.groups() usergroup = usergroup.lower().split('\\')[-1] permissions = set(p.lower() for p in permissions.split(',')) role_permissions[usergroup] = permissions elif not line.strip(): break return role_permissions def check_user_only_permissions(fname): # Windows has it's own permissions ACL patterns import win32api username = win32api.GetUserName().lower() permissions = fetch_win32_permissions(fname) print(permissions) # for easier debugging assert username in permissions assert permissions[username] == set(['r', 'w']) assert 'administrators' in permissions assert permissions['administrators'] == set(['f']) assert 'everyone' not in permissions assert len(permissions) == 2 directory = tempfile.mkdtemp() fname = os.path.join(directory, 'check_perms') try: with secure_write(fname) as f: f.write('test 1') check_user_only_permissions(fname) with open(fname, 'r') as f: assert f.read() == 'test 1' finally: shutil.rmtree(directory)
def make_connection_file(self): """Generates a JSON config file, including the selection of random ports. """ runtime_dir = jupyter_runtime_dir() ensure_dir_exists(runtime_dir) fname = os.path.join(runtime_dir, 'kernel-%s.json' % new_key()) cfg = self.make_ports() cfg['ip'] = self.ip cfg['key'] = new_key() cfg['transport'] = self.transport cfg['signature_scheme'] = 'hmac-sha256' # Only ever write this file as user read/writeable # This would otherwise introduce a vulnerability as a file has secrets # which would let others execute arbitrarily code as you with secure_write(fname) as f: f.write(json.dumps(cfg, indent=2)) set_sticky_bit(fname) return fname, cfg
def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0, control_port=0, ip='', key=b'', transport='tcp', signature_scheme='hmac-sha256', kernel_name=''): """Generates a JSON config file, including the selection of random ports. Parameters ---------- fname : unicode The path to the file to write shell_port : int, optional The port to use for ROUTER (shell) channel. iopub_port : int, optional The port to use for the SUB channel. stdin_port : int, optional The port to use for the ROUTER (raw input) channel. control_port : int, optional The port to use for the ROUTER (control) channel. hb_port : int, optional The port to use for the heartbeat REP channel. ip : str, optional The ip address the kernel will bind to. key : str, optional The Session key used for message authentication. signature_scheme : str, optional The scheme used for message authentication. This has the form 'digest-hash', where 'digest' is the scheme used for digests, and 'hash' is the name of the hash function used by the digest scheme. Currently, 'hmac' is the only supported digest scheme, and 'sha256' is the default hash function. kernel_name : str, optional The name of the kernel currently connected to. """ if not ip: ip = localhost() # default to temporary connector file if not fname: fd, fname = tempfile.mkstemp('.json') os.close(fd) # Find open ports as necessary. ports = [] ports_needed = int(shell_port <= 0) + \ int(iopub_port <= 0) + \ int(stdin_port <= 0) + \ int(control_port <= 0) + \ int(hb_port <= 0) if transport == 'tcp': for i in range(ports_needed): sock = socket.socket() # struct.pack('ii', (0,0)) is 8 null bytes sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8) sock.bind((ip, 0)) ports.append(sock) for i, sock in enumerate(ports): port = sock.getsockname()[1] sock.close() ports[i] = port else: N = 1 for i in range(ports_needed): while os.path.exists("%s-%s" % (ip, str(N))): N += 1 ports.append(N) N += 1 if shell_port <= 0: shell_port = ports.pop(0) if iopub_port <= 0: iopub_port = ports.pop(0) if stdin_port <= 0: stdin_port = ports.pop(0) if control_port <= 0: control_port = ports.pop(0) if hb_port <= 0: hb_port = ports.pop(0) cfg = dict( shell_port=shell_port, iopub_port=iopub_port, stdin_port=stdin_port, control_port=control_port, hb_port=hb_port, ) cfg['ip'] = ip cfg['key'] = bytes_to_str(key) cfg['transport'] = transport cfg['signature_scheme'] = signature_scheme cfg['kernel_name'] = kernel_name # Only ever write this file as user read/writeable # This would otherwise introduce a vulnerability as a file has secrets # which would let others execute arbitrarily code as you with secure_write(fname) as f: f.write(json.dumps(cfg, indent=2)) if hasattr(stat, 'S_ISVTX'): # set the sticky bit on the file and its parent directory # to avoid periodic cleanup paths = [fname] runtime_dir = os.path.dirname(fname) if runtime_dir: paths.append(runtime_dir) for path in paths: permissions = os.stat(path).st_mode new_permissions = permissions | stat.S_ISVTX if new_permissions != permissions: try: os.chmod(path, new_permissions) except OSError as e: if e.errno == errno.EPERM and path == runtime_dir: # suppress permission errors setting sticky bit on runtime_dir, # which we may not own. pass else: # failed to set sticky bit, probably not a big deal warnings.warn( "Failed to set sticky bit on %r: %s" "\nProbably not a big deal, but runtime files may be cleaned up periodically." % (path, e), RuntimeWarning, ) return fname, cfg
def write_connection_file( fname: Optional[str] = None, shell_port: Union[Integer, Int, int] = 0, iopub_port: Union[Integer, Int, int] = 0, stdin_port: Union[Integer, Int, int] = 0, hb_port: Union[Integer, Int, int] = 0, control_port: Union[Integer, Int, int] = 0, ip: str = "", key: bytes = b"", transport: str = "tcp", signature_scheme: str = "hmac-sha256", kernel_name: str = "", ) -> Tuple[str, KernelConnectionInfo]: """Generates a JSON config file, including the selection of random ports. Parameters ---------- fname : unicode The path to the file to write shell_port : int, optional The port to use for ROUTER (shell) channel. iopub_port : int, optional The port to use for the SUB channel. stdin_port : int, optional The port to use for the ROUTER (raw input) channel. control_port : int, optional The port to use for the ROUTER (control) channel. hb_port : int, optional The port to use for the heartbeat REP channel. ip : str, optional The ip address the kernel will bind to. key : str, optional The Session key used for message authentication. signature_scheme : str, optional The scheme used for message authentication. This has the form 'digest-hash', where 'digest' is the scheme used for digests, and 'hash' is the name of the hash function used by the digest scheme. Currently, 'hmac' is the only supported digest scheme, and 'sha256' is the default hash function. kernel_name : str, optional The name of the kernel currently connected to. """ if not ip: ip = localhost() # default to temporary connector file if not fname: fd, fname = tempfile.mkstemp(".json") os.close(fd) # Find open ports as necessary. ports: List[int] = [] sockets: List[socket.socket] = [] ports_needed = (int(shell_port <= 0) + int(iopub_port <= 0) + int(stdin_port <= 0) + int(control_port <= 0) + int(hb_port <= 0)) if transport == "tcp": for _ in range(ports_needed): sock = socket.socket() # struct.pack('ii', (0,0)) is 8 null bytes sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b"\0" * 8) sock.bind((ip, 0)) sockets.append(sock) for sock in sockets: port = sock.getsockname()[1] sock.close() ports.append(port) else: N = 1 for _ in range(ports_needed): while os.path.exists("%s-%s" % (ip, str(N))): N += 1 ports.append(N) N += 1 if shell_port <= 0: shell_port = ports.pop(0) if iopub_port <= 0: iopub_port = ports.pop(0) if stdin_port <= 0: stdin_port = ports.pop(0) if control_port <= 0: control_port = ports.pop(0) if hb_port <= 0: hb_port = ports.pop(0) cfg: KernelConnectionInfo = dict( shell_port=shell_port, iopub_port=iopub_port, stdin_port=stdin_port, control_port=control_port, hb_port=hb_port, ) cfg["ip"] = ip cfg["key"] = key.decode() cfg["transport"] = transport cfg["signature_scheme"] = signature_scheme cfg["kernel_name"] = kernel_name # Only ever write this file as user read/writeable # This would otherwise introduce a vulnerability as a file has secrets # which would let others execute arbitrarily code as you with secure_write(fname) as f: f.write(json.dumps(cfg, indent=2)) if hasattr(stat, "S_ISVTX"): # set the sticky bit on the parent directory of the file # to ensure only owner can remove it runtime_dir = os.path.dirname(fname) if runtime_dir: permissions = os.stat(runtime_dir).st_mode new_permissions = permissions | stat.S_ISVTX if new_permissions != permissions: try: os.chmod(runtime_dir, new_permissions) except OSError as e: if e.errno == errno.EPERM: # suppress permission errors setting sticky bit on runtime_dir, # which we may not own. pass return fname, cfg