Example #1
0
    def get(self, request):
        """Handles an incoming CatalogMachineRetrievalRequest."""
        user = auth.get_current_identity().to_bytes()
        logging.info(
            'Received CatalogMachineRetrievalRequest:\nUser: %s\n%s',
            user,
            request,
        )
        if acl.is_catalog_admin():
            if not request.backend:
                raise endpoints.BadRequestException(
                    'Backend unspecified by administrator')
        elif acl.is_backend_service():
            current_backend = acl.get_current_backend()
            if request.backend is None:
                request.backend = current_backend
            if request.backend != current_backend:
                raise endpoints.ForbiddenException('Mismatched backend')

        entry = models.CatalogMachineEntry.get(request.backend,
                                               request.hostname)
        if not entry:
            raise endpoints.NotFoundException('CatalogMachineEntry not found')

        response = rpc_messages.CatalogMachineRetrievalResponse(
            dimensions=entry.dimensions,
            policies=entry.policies,
            state=entry.state,
        )
        if entry.lease_expiration_ts:
            # datetime_to_timestamp returns microseconds, convert to seconds.
            response.lease_expiration_ts = utils.datetime_to_timestamp(
                entry.lease_expiration_ts) / 1000 / 1000
        return response
Example #2
0
  def check_backend(request):
    """Checks that the given catalog manipulation request specifies a backend.

    Returns:
      rpc_messages.CatalogManipulationRequestError.UNSPECIFIED_BACKEND if the
      backend is unspecified and can't be inferred, otherwise None.
    """
    if acl.is_catalog_admin():
      # Catalog administrators may update any CatalogEntry, but the backend must
      # be specified because hostname uniqueness is enforced per-backend.
      if not request.dimensions.backend:
        logging.warning('Backend unspecified by administrator')
        return rpc_messages.CatalogManipulationRequestError.UNSPECIFIED_BACKEND
    elif acl.is_backend_service():
      # Backends may only update their own machines.
      current_backend = acl.get_current_backend()
      if request.dimensions.backend is None:
        request.dimensions.backend = current_backend
      if request.dimensions.backend != current_backend:
        logging.warning('Mismatched backend')
        return rpc_messages.CatalogManipulationRequestError.MISMATCHED_BACKEND
Example #3
0
    def check_backend(request):
        """Checks that the given catalog manipulation request specifies a backend.

    Returns:
      rpc_messages.CatalogManipulationRequestError.UNSPECIFIED_BACKEND if the
      backend is unspecified and can't be inferred, otherwise None.
    """
        if acl.is_catalog_admin():
            # Catalog administrators may update any CatalogEntry, but the backend must
            # be specified because hostname uniqueness is enforced per-backend.
            if not request.dimensions.backend:
                logging.warning('Backend unspecified by administrator')
                return rpc_messages.CatalogManipulationRequestError.UNSPECIFIED_BACKEND
        elif acl.is_backend_service():
            # Backends may only update their own machines.
            current_backend = acl.get_current_backend()
            if request.dimensions.backend is None:
                request.dimensions.backend = current_backend
            if request.dimensions.backend != current_backend:
                logging.warning('Mismatched backend')
                return rpc_messages.CatalogManipulationRequestError.MISMATCHED_BACKEND
Example #4
0
    def poll(self, request):
        """Handles an incoming PollRequest."""
        user = auth.get_current_identity()
        if not request.backend:
            if acl.is_backend_service():
                # Backends may omit this field to mean backend == self.
                request.backend = acl.get_current_backend()
            else:
                # Anyone else omitting the backend field is an error.
                raise endpoints.BadRequestException('Backend unspecified')

        entry = models.CatalogMachineEntry.get(request.backend,
                                               request.hostname)
        if not entry:
            raise endpoints.NotFoundException('CatalogMachineEntry not found')

        # Determine authorization. User must be the known service account for the
        # machine, the backend service that owns the machine, or an administrator.
        machine_service_account = None
        if entry.policies:
            machine_service_account = entry.policies.machine_service_account
        if user.name != machine_service_account:
            if entry.dimensions.backend != acl.get_current_backend():
                if not acl.is_catalog_admin():
                    # It's found, but raise 404 so we don't reveal the existence of
                    # machines to unauthorized users.
                    logging.warning(
                        'Unauthorized poll\nUser: %s\nMachine: %s',
                        user.to_bytes(),
                        entry,
                    )
                    raise endpoints.NotFoundException(
                        'CatalogMachineEntry not found')

        # Authorized request, return the current instruction for the machine.
        if not entry.lease_id or not entry.instruction:
            return rpc_messages.PollResponse()

        # The cron job which processes expired leases may not have run yet. Check
        # the lease expiration time to make sure this lease is still active.
        if entry.lease_expiration_ts <= utils.utcnow():
            return rpc_messages.PollResponse()

        # The cron job which processes early lease releases may not have run yet.
        # Check the LeaseRequest to make sure this lease is still active.
        lease = models.LeaseRequest.get_by_id(entry.lease_id)
        if not lease or lease.released:
            return rpc_messages.PollResponse()

        # Only the machine itself polling for its own instructions causes
        # the state of the instruction to be updated. Only update the state
        # if it's PENDING.
        if entry.instruction.state == models.InstructionStates.PENDING:
            if user.name == machine_service_account:
                self._update_instruction_state(
                    entry.key, models.InstructionStates.RECEIVED)

        return rpc_messages.PollResponse(
            instruction=entry.instruction.instruction,
            state=entry.instruction.state,
        )