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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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