Exemplo n.º 1
0
    def __init__(self,
                 destination_vip,
                 destination_serverkey,
                 destination_historian_identity=PLATFORM_HISTORIAN,
                 remote_identity=None,
                 **kwargs):
        """
        
        :param destination_vip: vip address of the destination volttron 
        instance
        :param destination_serverkey: public key of the destination server
        :param services_topic_list: subset of topics that are inherently 
        supported by base historian. Default is device, analysis, logger, 
        and record topics
        :param custom_topic_list: any additional topics this historian 
        should subscribe to.
        :param destination_historian_identity: vip identity of the 
        destination historian. default is 'platform.historian'
        :param destination_instance_name: instance name of destination server
        :param kwargs: additional arguments to be passed along to parent class
        """
        kwargs["process_loop_in_greenlet"] = True
        self.destination_instance_name = kwargs.pop(
            'destination_instance_name', None)
        self.destination_message_bus = kwargs.pop('destination_message_bus',
                                                  'zmq')
        super(DataMover, self).__init__(**kwargs)
        self.destination_vip = destination_vip
        self.destination_serverkey = destination_serverkey
        self.destination_historian_identity = destination_historian_identity
        self.remote_identity = remote_identity
        self._target_platform = None

        self.local_message_bus = utils.get_messagebus()
        self.rmq_to_rmq_comm = False
        config = {
            "destination_vip": self.destination_vip,
            "destination_serverkey": self.destination_serverkey,
            "destination_historian_identity":
            self.destination_historian_identity,
            "remote_identity": self.remote_identity
        }

        self.update_default_config(config)
        _log.debug("My idenity {}".format(self.core.identity))
        # will be available in both threads.
        self._last_timeout = 0
        if self.local_message_bus == 'rmq' and self.destination_message_bus == 'rmq':
            self.rmq_to_rmq_comm = True
Exemplo n.º 2
0
    def connect_remote_platform(
            self,
            address,
            serverkey=None,
            agent_class=None
    ):
        """
        Agent attempts to connect to a remote platform to exchange data.

        address must start with http, https, tcp, ampq, or ampqs or a
        ValueError will be
        raised

        If this function is successful it will return an instance of the
        `agent_class`
        parameter if not then this function will return None.

        If the address parameter begins with http or https
        TODO: use the known host functionality here
        the agent will attempt to use Discovery to find the values
        associated with it.

        Discovery should return either an rmq-address or a vip-address or
        both.  In
        that situation the connection will be made using zmq.  In the event
        that
        fails then rmq will be tried.  If both fail then None is returned
        from this
        function.

        """
        from volttron.platform.vip.agent.utils import build_agent
        from volttron.platform.vip.agent import Agent

        if agent_class is None:
            agent_class = Agent

        parsed_address = urlparse(address)
        _log.debug("Begining auth.connect_remote_platform: {}".format(address))

        value = None
        if parsed_address.scheme == "tcp":
            # ZMQ connection
            hosts = KnownHostsStore()
            temp_serverkey = hosts.serverkey(address)
            if not temp_serverkey:
                _log.info(
                    "Destination serverkey not found in known hosts file, "
                    "using config"
                )
                destination_serverkey = serverkey
            elif not serverkey:
                destination_serverkey = temp_serverkey
            else:
                if temp_serverkey != serverkey:
                    raise ValueError(
                        "server_key passed and known hosts serverkey do not "
                        ""
                        "match!"
                    )
                destination_serverkey = serverkey

            publickey, secretkey = (
                self._core().publickey,
                self._core().secretkey,
            )
            _log.debug(
                "Connecting using: %s", get_fq_identity(self._core().identity)
            )

            value = build_agent(
                agent_class=agent_class,
                identity=get_fq_identity(self._core().identity),
                serverkey=destination_serverkey,
                publickey=publickey,
                secretkey=secretkey,
                message_bus="zmq",
                address=address,
            )
        elif parsed_address.scheme in ("https", "http"):
            from volttron.platform.web import DiscoveryInfo
            from volttron.platform.web import DiscoveryError

            try:
                # TODO: Use known host instead of looking up for discovery
                #  info if possible.

                # We need to discover which type of bus is at the other end.
                info = DiscoveryInfo.request_discovery_info(address)
                remote_identity = "{}.{}.{}".format(
                    info.instance_name,
                    get_platform_instance_name(),
                    self._core().identity,
                )
                # if the current message bus is zmq then we need
                # to connect a zmq on the remote, whether that be the
                # rmq router or proxy.  Also note that we are using the
                # fully qualified
                # version of the identity because there will be conflicts if
                # volttron central has more than one platform.agent connecting
                if get_messagebus() == "zmq":
                    if not info.vip_address or not info.serverkey:
                        err = (
                            "Discovery from {} did not return serverkey "
                            "and/or vip_address".format(address)
                        )
                        raise ValueError(err)

                    _log.debug(
                        "Connecting using: %s",
                        get_fq_identity(self._core().identity),
                    )

                    # use fully qualified identity
                    value = build_agent(
                        identity=get_fq_identity(self._core().identity),
                        address=info.vip_address,
                        serverkey=info.serverkey,
                        secretkey=self._core().secretkey,
                        publickey=self._core().publickey,
                        agent_class=agent_class,
                    )

                else:  # we are on rmq messagebus

                    # This is if both remote and local are rmq message buses.
                    if info.messagebus_type == "rmq":
                        _log.debug("Both remote and local are rmq messagebus.")
                        fqid_local = get_fq_identity(self._core().identity)

                        # Check if we already have the cert, if so use it
                        # instead of requesting cert again
                        remote_certs_dir = self.get_remote_certs_dir()
                        remote_cert_name = "{}.{}".format(
                            info.instance_name, fqid_local
                        )
                        certfile = os.path.join(
                            remote_certs_dir, remote_cert_name + ".crt"
                        )
                        if os.path.exists(certfile):
                            response = certfile
                        else:
                            response = self.request_cert(
                                address, fqid_local, info
                            )

                        if response is None:
                            _log.error("there was no response from the server")
                            value = None
                        elif isinstance(response, tuple):
                            if response[0] == "PENDING":
                                _log.info(
                                    "Waiting for administrator to accept a "
                                    "CSR request."
                                )
                            value = None
                        # elif isinstance(response, dict):
                        #     response
                        elif os.path.exists(response):
                            # info = DiscoveryInfo.request_discovery_info(
                            # address)
                            # From the remote platforms perspective the
                            # remote user name is
                            #   remoteinstance.localinstance.identity,
                            #   this is what we must
                            #   pass to the build_remote_connection_params
                            #   for a successful

                            remote_rmq_user = get_fq_identity(
                                fqid_local, info.instance_name
                            )
                            _log.debug(
                                "REMOTE RMQ USER IS: %s", remote_rmq_user
                            )
                            remote_rmq_address = self._core().rmq_mgmt.build_remote_connection_param(
                                remote_rmq_user,
                                info.rmq_address,
                                ssl_auth=True,
                                cert_dir=self.get_remote_certs_dir(),
                            )

                            value = build_agent(
                                identity=fqid_local,
                                address=remote_rmq_address,
                                instance_name=info.instance_name,
                                publickey=self._core().publickey,
                                secretkey=self._core().secretkey,
                                message_bus="rmq",
                                enable_store=False,
                                agent_class=agent_class,
                            )
                        else:
                            raise ValueError(
                                "Unknown path through discovery process!"
                            )

                    else:
                        # TODO: cache the connection so we don't always have
                        #  to ping the server to connect.

                        # This branch happens when the message bus is not
                        # the same note
                        # this writes to the agent-data directory of this
                        # agent if the agent
                        # is installed.
                        if get_messagebus() == "rmq":
                            if not os.path.exists("keystore.json"):
                                with open("keystore.json", "w") as file_pointer:
                                    file_pointer.write(
                                        jsonapi.dumps(
                                            KeyStore.generate_keypair_dict()
                                        )
                                    )

                            with open("keystore.json") as file_pointer:
                                keypair = jsonapi.loads(file_pointer.read())

                        value = build_agent(
                            agent_class=agent_class,
                            identity=remote_identity,
                            serverkey=info.serverkey,
                            publickey=keypair.get("publickey"),
                            secretkey=keypair.get("secretekey"),
                            message_bus="zmq",
                            address=info.vip_address,
                        )
            except DiscoveryError:
                _log.error(
                    "Couldn't connect to %s or incorrect response returned "
                    "response was %s",
                    address,
                    value,
                )

        else:
            raise ValueError(
                "Invalid configuration found the address: {} has an invalid "
                "scheme".format(address)
            )

        return value
Exemplo n.º 3
0
    def request_cert(
            self,
            csr_server,
            fully_qualified_local_identity,
            discovery_info
    ):
        """
        Get a signed csr from the csr_server endpoint

        This method will create a csr request that is going to be sent to the
        signing server.

        :param csr_server: the http(s) location of the server to connect to.
        :return:
        """
        if get_messagebus() != "rmq":
            raise ValueError(
                "Only can create csr for rabbitmq based platform in ssl mode."
            )

        # from volttron.platform.web import DiscoveryInfo
        config = RMQConfig()

        if not config.is_ssl:
            raise ValueError(
                "Only can create csr for rabbitmq based platform in ssl mode."
            )

        # info = discovery_info
        # if info is None:
        #     info = DiscoveryInfo.request_discovery_info(csr_server)

        certs = Certs()
        csr_request = certs.create_csr(
            fully_qualified_local_identity, discovery_info.instance_name
        )
        # The csr request requires the fully qualified identity that is
        # going to be connected to the external instance.
        #
        # The remote instance id is the instance name of the remote platform
        # concatenated with the identity of the local fully quallified
        # identity.
        remote_cert_name = "{}.{}".format(
            discovery_info.instance_name, fully_qualified_local_identity
        )
        remote_ca_name = discovery_info.instance_name + "_ca"

        # if certs.cert_exists(remote_cert_name, True):
        #     return certs.cert(remote_cert_name, True)

        json_request = dict(
            csr=csr_request.decode("utf-8"),
            identity=remote_cert_name,
            # get_platform_instance_name()+"."+self._core().identity,
            hostname=config.hostname,
        )
        request = grequests.post(
            csr_server + "/csr/request_new",
            json=jsonapi.dumps(json_request),
            verify=False,
        )
        response = grequests.map([request])

        if response and isinstance(response, list):
            response[0].raise_for_status()
        response = response[0]
        # response = requests.post(csr_server + "/csr/request_new",
        #                          json=jsonapi.dumps(json_request),
        #                          verify=False)

        _log.debug("The response: %s", response)

        j = response.json()
        status = j.get("status")
        cert = j.get("cert")
        message = j.get("message", "")
        remote_certs_dir = self.get_remote_certs_dir()
        if status == "SUCCESSFUL" or status == "APPROVED":
            certs.save_agent_remote_info(
                remote_certs_dir,
                fully_qualified_local_identity,
                remote_cert_name,
                cert.encode("utf-8"),
                remote_ca_name,
                discovery_info.rmq_ca_cert.encode("utf-8"),
            )
            os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(
                remote_certs_dir, "requests_ca_bundle"
            )
            _log.debug(
                "Set os.environ requests ca bundle to %s",
                os.environ["REQUESTS_CA_BUNDLE"],
            )
        elif status == "PENDING":
            _log.debug("Pending CSR request for {}".format(remote_cert_name))
        elif status == "DENIED":
            _log.error("Denied from remote machine.  Shutting down agent.")
            status = Status.build(
                BAD_STATUS,
                context="Administrator denied remote "
                "connection.  "
                "Shutting down",
            )
            self._owner.vip.health.set_status(status.status, status.context)
            self._owner.vip.health.send_alert(
                self._core().identity + "_DENIED", status
            )
            self._core().stop()
            return None
        elif status == "ERROR":
            err = "Error retrieving certificate from {}\n".format(
                config.hostname
            )
            err += "{}".format(message)
            raise ValueError(err)
        else:  # No resposne
            return None

        certfile = os.path.join(remote_certs_dir, remote_cert_name + ".crt")
        if os.path.exists(certfile):
            return certfile
        else:
            return status, message
Exemplo n.º 4
0
    def request_cert(self, csr_server, fully_qualified_local_identity,
                     discovery_info):
        """ Get a signed csr from the csr_server endpoint

        This method will create a csr request that is going to be sent to the
        signing server.

        :param csr_server: the http(s) location of the server to connect to.
        :return:
        """

        if get_messagebus() != 'rmq':
            raise ValueError(
                "Only can create csr for rabbitmq based platform in ssl mode.")

        # from volttron.platform.web import DiscoveryInfo
        config = RMQConfig()

        if not config.is_ssl:
            raise ValueError(
                "Only can create csr for rabbitmq based platform in ssl mode.")

        # info = discovery_info
        # if info is None:
        #     info = DiscoveryInfo.request_discovery_info(csr_server)

        certs = Certs()
        csr_request = certs.create_csr(fully_qualified_local_identity,
                                       discovery_info.instance_name)
        # The csr request requires the fully qualified identity that is
        # going to be connected to the external instance.
        #
        # The remote instance id is the instance name of the remote platform
        # concatenated with the identity of the local fully quallified identity.
        remote_cert_name = "{}.{}".format(discovery_info.instance_name,
                                          fully_qualified_local_identity)
        remote_ca_name = discovery_info.instance_name + "_ca"

        # if certs.cert_exists(remote_cert_name, True):
        #     return certs.cert(remote_cert_name, True)

        json_request = dict(
            csr=csr_request,
            identity=
            remote_cert_name,  # get_platform_instance_name()+"."+self._core().identity,
            hostname=config.hostname)
        response = requests.post(csr_server + "/csr/request_new",
                                 json=json.dumps(json_request),
                                 verify=False)

        _log.debug("The response: {}".format(response))
        # from pprint import pprint
        # pprint(response.json())
        j = response.json()
        status = j.get('status')
        cert = j.get('cert')
        message = j.get('message', '')

        if status == 'SUCCESSFUL' or status == 'APPROVED':
            certs.save_remote_info(fully_qualified_local_identity,
                                   remote_cert_name, cert, remote_ca_name,
                                   discovery_info.rmq_ca_cert)

        elif status == 'PENDING':
            _log.debug("Pending CSR request for {}".format(remote_cert_name))
        elif status == 'DENIED':
            _log.error("Denied from remote machine.  Shutting down agent.")
            status = Status.build(
                BAD_STATUS,
                context="Administrator denied remote connection.  Shutting down"
            )
            self._owner.vip.health.set_status(status.status, status.context)
            self._owner.vip.health.send_alert(
                self._core().identity + "_DENIED", status)
            self._core().stop()
            return None
        elif status == 'ERROR':
            err = "Error retrieving certificate from {}\n".format(
                config.hostname)
            err += "{}".format(message)
            raise ValueError(err)
        else:  # No resposne
            return None

        certfile = certs.cert_file(remote_cert_name, remote=True)

        if certs.cert_exists(remote_cert_name, remote=True):
            return certfile
        else:
            return status, message