Пример #1
0
    def register(self, host=None):
        self.verify_configuration()

        # check if we are not already registered with the configured identity
        resp = requests.get(f"{self.explorer_url}/users?name={self.tname}")
        if resp.status_code == 404:
            user = None
        elif resp.status_code != 200:
            resp.raise_for_status()

        users = resp.json()
        if not users:
            user = None
        else:
            user = User.from_dict(users[0])

        tid = None
        if not user:
            if not host:
                try:
                    _, host = get_default_ip_config()
                except Exception:
                    host = "localhost"
            user = User()
            user.name = self.tname
            user.host = host
            user.email = self.email
            user.description = ""
            user.pubkey = self.nacl.get_verify_key_hex()

            resp = requests.post(f"{self.explorer_url}/users",
                                 json=user.to_dict())
            if resp.status_code == 409:  # conflict
                raise Input(
                    "A user with same name or email exists on TFGrid phonebook."
                )
            if resp.status_code != 201:
                resp.raise_for_status()

            tid = resp.json()["id"]
        else:
            if self.nacl.get_verify_key_hex() != user.pubkey:
                raise Input(
                    "The verify key on your local system does not correspond with verify key on the TFGrid explorer"
                )
            tid = user.id
        self._tid = tid
        if self.tname not in self.admins:
            self.admins.append(self.tname)
        self.save()
        return tid
Пример #2
0
    def get(self, farm_id: int = None, farm_name: str = None) -> Farm:
        """
        get the detail of a farm

        :param farm_id: ID of the farm to retrieve
        :type farm_id: int, optional
        :param farm_name: Name of the farm to retrieve, if you use name the client loops over all farm and match on the name.
                          Be carefull using this in performance critical code path
        :type farm_name: str, optional
        :raises NotFound: if no farm with the specified ID or name if found
        :raises Input: if farm_id and farm_name are None
        :return: Farm object
        :rtype: Farm
        """
        if farm_name:
            for farm in self.iter():
                if farm.name == farm_name:
                    return farm
            else:
                raise NotFound(f"Could not find farm with name {farm_name}")
        elif not farm_id:
            raise Input("farms.get requires at least farm_id or farm_name")

        resp = self._session.get(f"{self._url}/{farm_id}")
        return Farm.from_dict(resp.json())
Пример #3
0
    def sub_domain(self, gateway_id: str, domain: str, ips: List[str],
                   pool_id: int) -> GatewaySubdomain:
        """create a sub-domain workload object

        Args:
          gateway_id(str): the ID of the gateway where the create the sub-domain
          domain(str): sub-domain to create
          ips(List[str]): list of target IP pointed by the sub-domain
          pool_id(int): capacity pool ID

        Returns:
          Subdomain: Subdomain

        """
        for ip in ips:
            if not _is_valid_ip(ip):
                raise Input(f"{ip} is not valid IP address")
        domain = self.correct_domain(domain)
        sb = GatewaySubdomain()
        sb.info.node_id = gateway_id
        sb.domain = domain
        sb.ips = ips
        sb.info.workload_type = WorkloadType.Subdomain
        sb.info.pool_id = pool_id
        return sb
Пример #4
0
def _next_action(next_action):
    if next_action:
        if isinstance(next_action, str):
            next_action = getattr(NextAction, next_action.upper()).value
        if not isinstance(next_action, int):
            raise Input("next_action should be of type int")
    return next_action
Пример #5
0
    def delete_access(self, network: Network, node_id: str,
                      iprange: str) -> Network:
        """remove a peer that was added using add_access method
        use this is you want to revoke the access to the network from someone

        Args:
          network(Network): network object
          node_id(str): ID of the node to use as entrypoint to the network
          ip_range(str): IP range to allocate for this peer

        Returns:
          Network: Network

        """
        node_workloads = {}
        node_ranges = set()
        for net_workload in network.network_resources:
            node_workloads[net_workload.info.node_id] = net_workload
            node_ranges.add(net_workload.iprange)
        if iprange in node_ranges:
            raise Input("Can't delete zos node peer")

        access_workload = node_workloads.get(node_id)
        if not access_workload:
            raise Input(f"Node {node_id} is not part of network")
        # remove peer from access node
        new_peers = []
        for peer in access_workload.peers:
            if peer.iprange != iprange:
                new_peers.append(peer)
        access_workload.peers = new_peers
        # remove peer from allowed ips on all nodes
        access_node_range = node_workloads[node_id]
        routing_range = wg_routing_ip(iprange)
        for network_resource in node_workloads.values():
            for peer in network_resource.peers:
                if peer.iprange != access_node_range:
                    if iprange in peer.allowed_iprange:
                        peer.allowed_iprange.remove(iprange)
                    if routing_range in peer.allowed_iprange:
                        peer.allowed_iprange.remove(routing_range)
        return network
Пример #6
0
    def add_master(
        self,
        node_id: str,
        network_name: str,
        cluster_secret: str,
        ip_address: str,
        size: int,
        ssh_keys: List[str],
        pool_id: int,
        public_ip_wid: int = 0,
        disable_default_ingress=True,
        datastore_endpoint="",
    ) -> K8s:
        """create a kubernetes marster workload object

        Args:
          node_id(str): node ID on which to deploy the k8s master
          network_name(str): name of the network to use
          cluster_secret(str): secret of the cluster. all the member of a same cluster must share the same secret
          ip_address(str): ip address of the k8s master
          size(int): size of the VM.
          ssh_keys(List[str]): list of public SSH key to authorize in the VM
          pool_id(int): capacity pool ID

        Returns:
          K8s: K8s

        Raises:
          Input: if size is not supported

        """
        if size not in range(1, 19):
            raise Input(f"VM size {size} is not supported")

        master = K8s()
        master.info.node_id = node_id
        master.info.workload_type = WorkloadType.Kubernetes
        master.info.pool_id = pool_id

        node = self._nodes.get(node_id)
        master.cluster_secret = encrypt_for_node(self._identity,
                                                 node.public_key_hex,
                                                 cluster_secret).decode()
        master.network_id = network_name
        master.ipaddress = ip_address
        master.size = size
        if not isinstance(ssh_keys, list):
            ssh_keys = [ssh_keys]
        master.ssh_keys = ssh_keys
        master.public_ip = public_ip_wid
        master.disable_default_ingress = disable_default_ingress
        master.datastore_endpoint = datastore_endpoint

        return master
Пример #7
0
    def add_node(self,
                 network: Network,
                 node_id: str,
                 ip_range: str,
                 pool_id: int,
                 wg_port: int = None):
        """add a node into the network

        Args:
          network(Network): network object
          node_id(str): ID of the node to add to the network
          ip_range(str): IP range (cidr) to assign to the node inside the network. The network mask must be a /24
          pool_id(int): the capacity pool ID
          wg_port(int, optional

        Raises:
          Input: if mask of ip_range is not /24
          Input: If specified access node not part of the network
          Input: If access node point doesn't have a public endpoint
          Input: If access node point doesn't have pubic endpoint of requested type): wireguar port of the wireguard endpoint. If None it will be picked automatically, defaults to None

        Returns:

        """
        node = self._nodes.get(node_id)

        if netaddr.IPNetwork(ip_range).prefixlen != 24:
            raise Input("ip_range should have a netmask of /24, not /%d",
                        ip_range.prefixlen)

        if wg_port is None:
            wg_port = _find_free_wg_port(node)

        _, wg_private_encrypted, wg_public = generate_zos_keys(
            node.public_key_hex)

        nr = NetworkResource()
        nr.info.pool_id = pool_id
        nr.info.workload_type = WorkloadType.Network_resource
        nr.network_iprange = network.iprange
        nr.iprange = ip_range
        nr.info.node_id = node_id
        nr.wireguard_listen_port = wg_port
        nr.wireguard_public_key = wg_public
        nr.wireguard_private_key_encrypted = wg_private_encrypted
        nr.name = network.name
        network.network_resources.append(nr)
        try:
            self._load_network(network)
            generate_peers(network)
        finally:
            self._cleanup_network(network)
Пример #8
0
    def create(self, ip_range: str, network_name: str = None) -> Network:
        """create a new network

        Args:
          ip_range(str): IP range (cidr) of the full network. The network mask must be /16
          network_name(str, optional): name of the network. If None, a random name will be generated, defaults to None

        Returns:
          Network: Network

        Raises:
          Input: if ip_range not a private range (RFC 1918)
          Input: if network mask of ip_range is not /16

        """
        network = netaddr.IPNetwork(ip_range)
        if not is_private(network):
            raise Input("ip_range must be a private network range (RFC 1918)")
        if network.prefixlen != 16:
            raise Input("network mask of ip range must be a /16")
        network = Network(network_name, ip_range)
        return network
Пример #9
0
    def verify_configuration(self):
        """
        Verifies passed arguments to constructor

        Raises: NotFound incase tid is passed but does not exists on the explorer
        Raises: Input: when params are missing
        """
        if not self.words:
            raise Input("Words are mandotory for an indentity")
        if self._tid != -1:
            resp = requests.get(f"{self.explorer_url}/users/{self._tid}")
            resp.raise_for_status()
            user = User.from_dict(resp.json())
            if self.tname and self.tname != user.name:
                raise Input("Name of user does not match name in explorer")
            self.tname = user.name
            if self.nacl.get_verify_key_hex() != user.pubkey:
                raise Input(
                    "The verify key on your local system does not correspond with verify key on the TFGrid explorer"
                )
        else:
            for key in ["email", "tname"]:
                if not getattr(self, key):
                    raise Value(f"Threebot {key} not configured")
Пример #10
0
    def add_master(
        self,
        node_id: str,
        network_name: str,
        cluster_secret: str,
        ip_address: str,
        size: int,
        ssh_keys: List[str],
        pool_id: int,
    ) -> K8s:
        """create a kubernetes marster workload object

        Args:
          node_id(str): node ID on which to deploy the k8s master
          network_name(str): name of the network to use
          cluster_secret(str): secret of the cluster. all the member of a same cluster must share the same secret
          ip_address(str): ip address of the k8s master
          size(int): size of the VM.
          ssh_keys(List[str]): list of public SSH key to authorize in the VM
          pool_id(int): capacity pool ID

        Returns:
          K8s: K8s

        Raises:
          Input: if size is not supported

        """
        if size not in [1, 2]:
            raise Input("size can only be 1 or 2")

        master = K8s()
        master.info.node_id = node_id
        master.info.workload_type = WorkloadType.Kubernetes
        master.info.pool_id = pool_id

        node = self._nodes.get(node_id)
        master.cluster_secret = encrypt_for_node(node.public_key_hex,
                                                 cluster_secret).decode()
        master.network_id = network_name
        master.ipaddress = ip_address
        master.size = size
        if not isinstance(ssh_keys, list):
            ssh_keys = [ssh_keys]
        master.ssh_keys = ssh_keys

        return master
Пример #11
0
    def run_cmd(self):
        # get eab_kid and eab_hmac_key based on email or api_key_
        if not self.email and not self.api_key_:
            raise Input("email or api_key_ must be provided")

        # set them to get the full run-cmd with correct arguments
        if self.api_key_:
            resp = j.tools.http.post(self.KEY_CREDENTIALS_URL, params={"access_key": self.api_key_})
        else:
            resp = j.tools.http.post(self.EMAIL_CREDENTIALS_URL, data={"email": self.email})

        resp.raise_for_status()
        data = resp.json()

        self.eab_kid = data["eab_kid"]
        self.eab_hmac_key = data["eab_hmac_key"]

        return super().run_cmd
Пример #12
0
def is_access_node(node, version):
    """

    Args:
      node:
      version:

    Returns:

    """
    if version not in [4, 6]:
        raise Input("ip version can only be 4 or 6")

    if node.public_config and node.public_config.master:
        if version == 4:
            return is_public_ip(node.public_config.ipv4, 4)
        elif node.public_config.ipv6:
            return is_public_ip(node.public_config.ipv6, 6)
    return False
Пример #13
0
    def configure_free_to_use(self, node_id: str, free: bool) -> bool:
        """
        configure if the node capacity can be paid using FreeTFT

        :param node_id: the node ID of the node to configure
        :type node_id: str
        :param free: if True, enable FreeTFT, if false disable it
        :type free: bool
        :return: True when the configuration was done successfully
        :rtype: bool
        """
        if not isinstance(free, bool):
            raise Input("free must be a boolean")

        data = {"free_to_use": free}
        self._session.post(
            f"{self._url}/{node_id}/configure_free", json=data,
        )
        return True
Пример #14
0
    def create(
        self,
        node_id: str,
        network_name: str,
        name: str,
        ip_address: str,
        ssh_keys: List[str],
        pool_id: int,
        size: int,
        public_ip_wid: int = 0,
    ) -> VirtualMachine:
        """create a virtual machine

        Args:
          node_id(str): node ID on which to deploy the vm
          network_name(str): name of the network to use
          ip_address(str): ip address of the vm
          ssh_keys(List[str]): list of public SSH key to authorize in the VM
          pool_id(int): capacity pool ID
          name(str): the name of the vm image to deploy
          public_ip_wid(int): The workload public ip
        Returns:
          vm: VirtualMachine

        """
        if size not in range(1, 19):
            raise Input(f"VM size {size} is not supported")

        vm = VirtualMachine()
        vm.info.node_id = node_id
        vm.info.workload_type = WorkloadType.Virtual_Machine
        vm.info.pool_id = pool_id

        vm.network_id = network_name
        vm.ipaddress = ip_address
        if not isinstance(ssh_keys, list):
            ssh_keys = [ssh_keys]
        vm.ssh_keys = ssh_keys
        vm.public_ip = public_ip_wid
        vm.name = name
        vm.size = size

        return vm
Пример #15
0
    def attach_existing(self, container: Container,
                        volume_id: Union[str, Volume], mount_point: str):
        """attach an existing volume to a container.
           The volume must already exist on the node

        Args:
          container(Volume): container object returned from container.create_container function
          volume_id(Union[str,volume]): the volume to attached to the container or its full ID
          mount_point(str): path where to mount the volume in the container
        """
        if isinstance(volume_id, Volume):
            if not volume_id.id:
                raise Input(
                    "volume needs to be deployed before it can be attached to a container"
                )
            volume_id = f"{volume_id.id}-1"

        vol = ContainerMount()
        vol.volume_id = volume_id
        vol.mountpoint = mount_point
        container.volumes.append(vol)
Пример #16
0
def check_url_reachable(url: str, timeout=5, verify=True):
    """Check that given url is reachable

    Args:
        url (str): url to test
        timeout (int, optional): timeout of test. Defaults to 5.
        verify (bool, optional): boolean indication to verify the servers TLS certificate or not.

    Raises:
        Input: raises if not correct url

    Returns:
        bool: True if the test succeeds, False otherwise
    """
    try:
        code = jumpscale.tools.http.get(url, timeout=timeout,
                                        verify=verify).status_code
        return code == 200
    except jumpscale.tools.http.exceptions.MissingSchema:
        raise Input("Please specify correct url with correct scheme")
    except jumpscale.tools.http.exceptions.ConnectionError:
        return False
Пример #17
0
def filter_public_ip(node, version):
    """

    Args:
      node:
      version:

    Returns:

    """
    if version not in [4, 6]:
        raise Input("ip version can only be 4 or 6")

    if node.public_config and node.public_config.master:
        if version == 4:
            return is_public_ip(node.public_config.ipv4, 4)
        elif node.public_config.ipv6:
            return is_public_ip(node.public_config.ipv6, 6)
    else:
        for iface in node.ifaces:
            for addr in iface.addrs:
                if is_public_ip(addr, version):
                    return True
    return False
Пример #18
0
    def add_access(self,
                   network: Network,
                   node_id: str,
                   ip_range: str,
                   wg_public_key: str = None,
                   ipv4: bool = False) -> str:
        """add an external access to the network. use this function if you want to allow
        a peer to your network that is not a 0-OS node like User laptop, external server,...

        Args:
          network(Network): network object
          node_id(str): ID of the node to use as entrypoint to the network
          ip_range(str): IP range to allocate for this peer
          wg_public_key(str, optional): wireguard public key of the peer. If not specify a new key will be generated, defaults to None
          ipv4(bool, optional): If True, use an IPv4 address as endpoint to connect to the network otherwise use IPv6, defaults to False

        Returns:
          str: the wireguard configuration to be used by the peer to connect to the network

        """
        if netaddr.IPNetwork(ip_range).prefixlen != 24:
            raise Input("ip_range should have a netmask of /24, not /%d",
                        ip_range.prefixlen)

        try:
            self._load_network(network)

            access_point_nr = None
            for nr in network.network_resources:
                if node_id == nr.info.node_id:
                    access_point_nr = nr

            if access_point_nr is None:
                raise Input(
                    "can not add access through a node which is not in the network"
                )

            if len(access_point_nr.public_endpoints) == 0:
                raise Input("access node must have at least 1 public endpoint")

            endpoint = ""
            wg_port = access_point_nr.wireguard_listen_port
            for ep in access_point_nr.public_endpoints:
                if ipv4 and ep.version == 4:
                    endpoint = f"{str(ep.ip)}:{wg_port}"
                    break
                if not ipv4 and ep.version == 6:
                    ip = str(ep.ip)
                    endpoint = f"[{ip}]:{wg_port}"
                    break

            if not endpoint:
                raise Input(
                    "access node has no public endpoint of the requested type")

            wg_private_key = ""
            if not wg_public_key:
                wg_private = public.PrivateKey.generate()
                wg_public = wg_private.public_key
                wg_private_key = wg_private.encode(Base64Encoder).decode()
                wg_public_key = wg_public.encode(Base64Encoder).decode()

            network.access_points.append(
                AccessPoint(node_id=node_id,
                            subnet=ip_range,
                            wg_public_key=wg_public_key,
                            ip4=ipv4))

            generate_peers(network)

        finally:
            self._cleanup_network(network)

        return generate_wg_quick(wg_private_key, ip_range,
                                 access_point_nr.wireguard_public_key,
                                 network.iprange, endpoint)