Beispiel #1
0
    def testErrorDetection(self):
        """Tests the end to end encrypted communicators."""
        # Install the client - now we can verify its signed messages
        self._MakeClientRecord()

        # Make something to send
        message_list = rdf_flows.MessageList()
        for i in range(0, 10):
            message_list.job.Append(session_id=str(i))

        result = rdf_flows.ClientCommunication()
        self.client_communicator.EncodeMessages(message_list, result)

        # TODO: We use `bytes` from the `future` package here to have
        # Python 3 iteration behaviour. This call should be a noop in Python 3 and
        # should be safe to remove on support for Python 2 is dropped.
        cipher_text = bytes(result.SerializeToBytes())

        # Depending on this modification several things may happen:
        # 1) The padding may not match which will cause a decryption exception.
        # 2) The protobuf may fail to decode causing a decoding exception.
        # 3) The modification may affect the signature resulting in UNAUTHENTICATED
        #    messages.
        # 4) The modification may have no effect on the data at all.
        for x in range(0, len(cipher_text), 50):
            # Futz with the cipher text (Make sure it's really changed)
            mod = chr((cipher_text[x] % 250) + 1).encode("latin-1")
            mod_cipher_text = cipher_text[:x] + mod + cipher_text[x + 1:]

            # TODO: Now we revert back to native `bytes` object because
            # proto deserialization assumes native indexing behaviour.
            if compatibility.PY2:
                mod_cipher_text = mod_cipher_text.__native__()

            try:
                decoded, client_id, _ = self.server_communicator.DecryptMessage(
                    mod_cipher_text)

                for i, message in enumerate(decoded):
                    # If the message is actually authenticated it must not be changed!
                    if message.auth_state == message.AuthorizationState.AUTHENTICATED:
                        self.assertEqual(message.source, client_id)

                        # These fields are set by the decoder and are not present in the
                        # original message - so we clear them before comparison.
                        message.auth_state = None
                        message.source = None
                        self.assertEqual(message, message_list.job[i])
                    else:
                        logging.debug("Message %s: Authstate: %s", i,
                                      message.auth_state)

            except communicator.DecodingError as e:
                logging.debug("Detected alteration at %s: %s", x, e)
Beispiel #2
0
    def testReceiveMessageList_Relational(self):
        if not data_store.RelationalDBFlowsEnabled():
            self.skipTest("Rel-db-only test.")

        fs_server = fs_frontend_tool.GRRFSServer()
        client_id = "C.1234567890123456"
        flow_id = "12345678"
        data_store.REL_DB.WriteClientMetadata(client_id,
                                              fleetspeak_enabled=True)

        rdf_flow = rdf_flow_objects.Flow(
            client_id=client_id,
            flow_id=flow_id,
            create_time=rdfvalue.RDFDatetime.Now())
        data_store.REL_DB.WriteFlowObject(rdf_flow)

        flow_request = rdf_flow_objects.FlowRequest(client_id=client_id,
                                                    flow_id=flow_id,
                                                    request_id=1)

        data_store.REL_DB.WriteFlowRequests([flow_request])
        session_id = "%s/%s" % (client_id, flow_id)
        fs_client_id = fleetspeak_utils.GRRIDToFleetspeakID(client_id)
        grr_messages = []
        for i in range(1, 10):
            grr_message = rdf_flows.GrrMessage(request_id=1,
                                               response_id=i + 1,
                                               session_id=session_id,
                                               payload=rdfvalue.RDFInteger(i))
            grr_messages.append(grr_message)
        packed_messages = rdf_flows.PackedMessageList()
        communicator.Communicator.EncodeMessageList(
            rdf_flows.MessageList(job=grr_messages), packed_messages)
        fs_message = fs_common_pb2.Message(message_type="MessageList",
                                           source=fs_common_pb2.Address(
                                               client_id=fs_client_id,
                                               service_name=FS_SERVICE_NAME))
        fs_message.data.Pack(packed_messages.AsPrimitiveProto())

        with test_lib.FakeTime(
                rdfvalue.RDFDatetime.FromSecondsSinceEpoch(123)):
            fs_server.Process(fs_message, None)

        # Ensure the last-ping timestamp gets updated.
        client_data = data_store.REL_DB.MultiReadClientMetadata([client_id])
        self.assertEqual(client_data[client_id].ping,
                         rdfvalue.RDFDatetime.FromSecondsSinceEpoch(123))

        flow_data = data_store.REL_DB.ReadAllFlowRequestsAndResponses(
            client_id, flow_id)
        self.assertLen(flow_data, 1)
        stored_flow_request, flow_responses = flow_data[0]
        self.assertEqual(stored_flow_request, flow_request)
        self.assertLen(flow_responses, 9)
Beispiel #3
0
    def UrlMock(self, num_messages=10, url=None, data=None, **kwargs):
        """A mock for url handler processing from the server's POV."""
        if "server.pem" in url:
            cert = str(config.CONFIG["Frontend.certificate"]).encode("ascii")
            return MakeResponse(200, cert)

        _ = kwargs
        try:
            comms_cls = rdf_flows.ClientCommunication
            self.client_communication = comms_cls.FromSerializedBytes(data)

            # Decrypt incoming messages
            self.messages, source, ts = self.server_communicator.DecodeMessages(
                self.client_communication)

            # Make sure the messages are correct
            self.assertEqual(source, self.client_cn)
            messages = sorted([
                m for m in self.messages if m.session_id == "aff4:/W:session"
            ],
                              key=lambda m: m.response_id)
            self.assertEqual([m.response_id for m in messages],
                             list(range(len(messages))))
            self.assertEqual([m.request_id for m in messages],
                             [1] * len(messages))

            # Now prepare a response
            response_comms = rdf_flows.ClientCommunication()
            message_list = rdf_flows.MessageList()
            for i in range(0, num_messages):
                message_list.job.Append(request_id=i, **self.server_response)

            # Preserve the timestamp as a nonce
            self.server_communicator.EncodeMessages(
                message_list,
                response_comms,
                destination=source,
                timestamp=ts,
                api_version=self.client_communication.api_version)

            return MakeResponse(200, response_comms.SerializeToBytes())
        except communicator.UnknownClientCertError:
            raise MakeHTTPException(406)
        except Exception as e:
            logging.info("Exception in mock urllib.request.Open: %s.", e)
            self.last_urlmock_error = e

            if flags.FLAGS.pdb_post_mortem:
                pdb.post_mortem()

            raise MakeHTTPException(500)
Beispiel #4
0
    def HandleMessageBundles(self, request_comms, response_comms):
        """Processes a queue of messages as passed from the client.

    We basically dispatch all the GrrMessages in the queue to the task scheduler
    for backend processing. We then retrieve from the TS the messages destined
    for this client.

    Args:
       request_comms: A ClientCommunication rdfvalue with messages sent by the
       client. source should be set to the client CN.

       response_comms: A ClientCommunication rdfvalue of jobs destined to this
       client.

    Returns:
       tuple of (source, message_count) where message_count is the number of
       messages received from the client with common name source.
    """
        messages, source, timestamp = self._communicator.DecodeMessages(
            request_comms)

        now = time.time()
        if messages:
            # Receive messages in line.
            self.ReceiveMessages(source, messages)

        # We send the client a maximum of self.max_queue_size messages
        required_count = max(0, self.max_queue_size - request_comms.queue_size)
        tasks = []

        message_list = rdf_flows.MessageList()
        # Only give the client messages if we are able to receive them in a
        # reasonable time.
        if time.time() - now < 10:
            tasks = self.DrainTaskSchedulerQueueForClient(
                source, required_count)
            message_list.job = tasks

        # Encode the message_list in the response_comms using the same API version
        # the client used.
        self._communicator.EncodeMessages(
            message_list,
            response_comms,
            destination=source,
            timestamp=timestamp,
            api_version=request_comms.api_version)

        return source, len(messages)
Beispiel #5
0
    def testErrorDetection(self):
        """Tests the end to end encrypted communicators."""
        # Install the client - now we can verify its signed messages
        self._MakeClientRecord()

        # Make something to send
        message_list = rdf_flows.MessageList()
        for i in range(0, 10):
            message_list.job.Append(session_id=str(i))

        result = rdf_flows.ClientCommunication()
        self.client_communicator.EncodeMessages(message_list, result)
        cipher_text = result.SerializeToString()

        # Depending on this modification several things may happen:
        # 1) The padding may not match which will cause a decryption exception.
        # 2) The protobuf may fail to decode causing a decoding exception.
        # 3) The modification may affect the signature resulting in UNAUTHENTICATED
        #    messages.
        # 4) The modification may have no effect on the data at all.
        for x in range(0, len(cipher_text), 50):
            # Futz with the cipher text (Make sure it's really changed)
            mod = chr((ord(cipher_text[x]) % 250) + 1).encode("latin-1")
            mod_cipher_text = cipher_text[:x] + mod + cipher_text[x + 1:]

            try:
                decoded, client_id, _ = self.server_communicator.DecryptMessage(
                    mod_cipher_text)

                for i, message in enumerate(decoded):
                    # If the message is actually authenticated it must not be changed!
                    if message.auth_state == message.AuthorizationState.AUTHENTICATED:
                        self.assertEqual(message.source, client_id)

                        # These fields are set by the decoder and are not present in the
                        # original message - so we clear them before comparison.
                        message.auth_state = None
                        message.source = None
                        self.assertEqual(message, message_list.job[i])
                    else:
                        logging.debug("Message %s: Authstate: %s", i,
                                      message.auth_state)

            except communicator.DecodingError as e:
                logging.debug("Detected alteration at %s: %s", x, e)
Beispiel #6
0
  def _SendMessages(self, grr_msgs, background=False):
    """Sends a block of messages through Fleetspeak."""
    message_list = rdf_flows.PackedMessageList()
    communicator.Communicator.EncodeMessageList(
        rdf_flows.MessageList(job=grr_msgs), message_list)
    fs_msg = fs_common_pb2.Message(
        message_type="MessageList",
        destination=fs_common_pb2.Address(service_name="GRR"),
        background=background)
    fs_msg.data.Pack(message_list.AsPrimitiveProto())

    try:
      sent_bytes = self._fs.Send(fs_msg)
    except (IOError, struct.error):
      logging.critical("Broken local Fleetspeak connection (write end).")
      raise

    stats.STATS.IncrementCounter("grr_client_sent_bytes", sent_bytes)
Beispiel #7
0
  def GetMessages(self, soft_size_limit=None):
    """Retrieves and removes the messages from the queue.

    Args:
      soft_size_limit: int If there is more data in the queue than
        soft_size_limit bytes, the returned list of messages will be
        approximately this large. If None (default), returns all messages
        currently on the queue.

    Returns:
      rdf_flows.MessageList A list of messages that were .Put on the queue
      earlier.
    """
    with self._lock:
      ret = rdf_flows.MessageList()
      ret_size = 0
      for message in self._Generate():
        ret.job.append(rdf_flows.GrrMessage.FromSerializedString(message))
        ret_size += len(message)
        if soft_size_limit is not None and ret_size > soft_size_limit:
          break

      return ret
Beispiel #8
0
  def RunOnce(self):
    """Makes a single request to the GRR server.

    Returns:
      A Status() object indicating how the last POST went.
    """
    # Attempt to fetch and load server certificate.
    if not self._FetchServerCertificate():
      self.timer.Wait()
      return HTTPObject(code=500)

    # Here we only drain messages if we were able to connect to the server in
    # the last poll request. Otherwise we just wait until the connection comes
    # back so we don't expire our messages too fast.
    if self.http_manager.consecutive_connection_errors == 0:
      # Grab some messages to send
      message_list = self.client_worker.Drain(
          max_size=config.CONFIG["Client.max_post_size"])
    else:
      message_list = rdf_flows.MessageList()

    # If any outbound messages require fast poll we switch to fast poll mode.
    for message in message_list.job:
      if message.require_fastpoll:
        self.timer.FastPoll()
        break

    # Make new encrypted ClientCommunication rdfvalue.
    payload = rdf_flows.ClientCommunication()

    # If our memory footprint is too large, we advertise that our input queue
    # is full. This will prevent the server from sending us any messages, and
    # hopefully allow us to work down our memory usage, by processing any
    # outstanding messages.
    if self.client_worker.MemoryExceeded():
      logging.info("Memory exceeded, will not retrieve jobs.")
      payload.queue_size = 1000000
    else:
      # Let the server know how many messages are currently queued in
      # the input queue.
      payload.queue_size = self.client_worker.InQueueSize()

    nonce = self.communicator.EncodeMessages(message_list, payload)
    payload_data = payload.SerializeToString()
    response = self.MakeRequest(payload_data)

    # Unable to decode response or response not valid.
    if response.code != 200 or response.messages is None:
      # We don't print response here since it should be encrypted and will
      # cause ascii conversion errors.
      logging.info("%s: Could not connect to server at %s, status %s",
                   self.communicator.common_name,
                   self.http_manager.active_base_url, response.code)

      # Force the server pem to be reparsed on the next connection.
      self.server_certificate = None

      # Reschedule the tasks back on the queue so they get retried next time.
      messages = list(message_list.job)
      for message in messages:
        message.require_fastpoll = False
        message.ttl -= 1
        if message.ttl > 0:
          self.client_worker.QueueResponse(message)
        else:
          logging.info("Dropped message due to retransmissions.")

      return response

    # Check the decoded nonce was as expected.
    if response.nonce != nonce:
      logging.info("Nonce not matched.")
      response.code = 500
      return response

    if response.source != self.communicator.server_name:
      logging.info("Received a message not from the server "
                   "%s, expected %s.", response.source,
                   self.communicator.server_name)
      response.code = 500
      return response

    # Check to see if any inbound messages want us to fastpoll. This means we
    # drop to fastpoll immediately on a new request rather than waiting for the
    # next beacon to report results.
    for message in response.messages:
      if message.require_fastpoll:
        self.timer.FastPoll()
        break

    # Process all messages. Messages can be processed by clients in
    # any order since clients do not have state.
    self.client_worker.QueueMessages(response.messages)

    cn = self.communicator.common_name
    logging.info(
        "%s: Sending %s(%s), Received %s messages in %s sec. "
        "Sleeping for %s sec.", cn, len(message_list), len(payload_data),
        len(response.messages), response.duration, self.timer.sleep_time)

    return response
Beispiel #9
0
    def testReceiveMessageListFleetspeak(self):
        service_name = "GRR"
        fake_service_client = _FakeGRPCServiceClient(service_name)

        fleetspeak_connector.Reset()
        fleetspeak_connector.Init(service_client=fake_service_client)

        fsd = fs_frontend_tool.GRRFSServer()

        grr_client_nr = 0xab
        grr_client_id_urn = self.SetupClient(grr_client_nr)

        flow_obj = self.FlowSetup(flow_test_lib.FlowOrderTest.__name__,
                                  grr_client_id_urn)

        num_msgs = 9

        session_id = flow_obj.session_id
        messages = [
            rdf_flows.GrrMessage(request_id=1,
                                 response_id=i,
                                 session_id=session_id,
                                 payload=rdfvalue.RDFInteger(i))
            for i in range(1, num_msgs + 1)
        ]

        fs_client_id = "\x10\x00\x00\x00\x00\x00\x00\xab"
        # fs_client_id should be equivalent to grr_client_id_urn
        self.assertEqual(
            fs_client_id,
            fleetspeak_utils.GRRIDToFleetspeakID(grr_client_id_urn.Basename()))

        message_list = rdf_flows.PackedMessageList()
        communicator.Communicator.EncodeMessageList(
            rdf_flows.MessageList(job=messages), message_list)

        fs_message = fs_common_pb2.Message(message_type="MessageList",
                                           source=fs_common_pb2.Address(
                                               client_id=fs_client_id,
                                               service_name=service_name))
        fs_message.data.Pack(message_list.AsPrimitiveProto())
        fsd.Process(fs_message, None)

        # Make sure the task is still on the client queue
        manager = queue_manager.QueueManager(token=self.token)
        tasks_on_client_queue = manager.Query(grr_client_id_urn, 100)
        self.assertEqual(len(tasks_on_client_queue), 1)

        want_messages = [message.Copy() for message in messages]
        for want_message in want_messages:
            # This is filled in by the frontend as soon as it gets the message.
            want_message.auth_state = (
                rdf_flows.GrrMessage.AuthorizationState.AUTHENTICATED)
            want_message.source = grr_client_id_urn

        stored_messages = data_store.DB.ReadResponsesForRequestId(
            session_id, 1)

        self.assertEqual(len(stored_messages), len(want_messages))

        stored_messages.sort(key=lambda m: m.response_id)
        # Check that messages were stored correctly
        for stored_message, want_message in itertools.izip(
                stored_messages, want_messages):
            stored_message.timestamp = None
            self.assertRDFValuesEqual(stored_message, want_message)