def _CheckAccess(self, username, subject_id, approval_type): """Checks access to a given subject by a given user.""" precondition.AssertType(subject_id, Text) cache_key = (username, subject_id, approval_type) try: self.acl_cache.Get(cache_key) APPROVAL_SEARCHES.Increment(fields=["-", "cache"]) return True except KeyError: APPROVAL_SEARCHES.Increment(fields=["-", "reldb"]) approvals = data_store.REL_DB.ReadApprovalRequests( username, approval_type, subject_id=subject_id, include_expired=False) errors = [] for approval in approvals: try: approval_checks.CheckApprovalRequest(approval) self.acl_cache.Put(cache_key, True) return except access_control.UnauthorizedAccess as e: errors.append(e) subject = approval_checks.BuildLegacySubject(subject_id, approval_type) if not errors: raise access_control.UnauthorizedAccess("No approval found.", subject=subject) else: raise access_control.UnauthorizedAccess(" ".join( str(e) for e in errors), subject=subject)
def CreateFlow(self, args, context=None): if not args.client_id: raise ValueError("client_id must be provided") if args.flow.name in self.allowed_file_finder_flow_names: self._CheckFileFinderArgs(args.flow.args) override_flow_name = self.effective_file_finder_flow_name override_flow_args = self._FixFileFinderArgs(args.flow.args) throttler = self._GetFileFinderThrottler() elif args.flow.name in self.allowed_artifact_collector_flow_names: self._CheckArtifactCollectorFlowArgs(args.flow.args) override_flow_name = self.effective_artifact_collector_flow_name override_flow_args = None throttler = self._GetArtifactCollectorFlowThrottler() else: raise access_control.UnauthorizedAccess( "Creating arbitrary flows (%s) is not allowed." % args.flow.name) try: throttler.EnforceLimits(args.client_id.ToString(), context.username, args.flow.name, args.flow.args) except throttle.DuplicateFlowError as e: # If a similar flow did run recently, just return it. return ApiRobotReturnDuplicateFlowHandler(flow_id=e.flow_id) except throttle.DailyFlowRequestLimitExceededError as e: # Raise UnauthorizedAccess so that the user gets an HTTP 403. raise access_control.UnauthorizedAccess(str(e)) return ApiRobotCreateFlowHandler(override_flow_name=override_flow_name, override_flow_args=override_flow_args)
def _CheckAccess(self, username, subject_id, approval_type): """Checks access to a given subject by a given user.""" utils.AssertType(subject_id, unicode) cache_key = (username, subject_id, approval_type) try: self.acl_cache.Get(cache_key) stats.STATS.IncrementCounter("approval_searches", fields=["-", "cache"]) return True except KeyError: stats.STATS.IncrementCounter("approval_searches", fields=["-", "reldb"]) approvals = data_store.REL_DB.ReadApprovalRequests( username, approval_type, subject_id=subject_id, include_expired=False) errors = [] for approval in approvals: try: approval_checks.CheckApprovalRequest(approval) self.acl_cache.Put(cache_key, True) return except access_control.UnauthorizedAccess as e: errors.append(e) subject = approval_checks.BuildLegacySubject(subject_id, approval_type) if not errors: raise access_control.UnauthorizedAccess( "No approval found.", subject=subject) else: raise access_control.UnauthorizedAccess( " ".join(utils.SmartStr(e) for e in errors), subject=subject)
def ValidateAccessAndSubjects(requested_access, subjects): """Does basic requested access validation. Args: requested_access: String consisting or 'r', 'w' and 'q' characters. subjects: A list of subjects that are about to be accessed with a given requested_access. Used for logging purposes only. Returns: True if requested_access is valid. Raises: access_control.UnauthorizedAccess: if requested_access is not valid. ValueError: if subjects list is empty. """ if not requested_access: raise access_control.UnauthorizedAccess( "Must specify requested access type for %s" % subjects) for s in requested_access: if s not in "rwq": raise ValueError("Invalid access requested for %s: %s" % (subjects, requested_access)) if "q" in requested_access and "r" not in requested_access: raise access_control.UnauthorizedAccess( "Invalid access request: query permissions require read permissions " "for %s" % subjects, requested_access=requested_access) return True
def CreateFlow(self, args, token=None): if not args.client_id: raise ValueError("client_id must be provided") override_flow_args = None throttler = None if args.flow.name == self.file_finder_flow_name: self._CheckFileFinderArgs(args.flow.args) override_flow_args = self._FixFileFinderArgs(args.flow.args) throttler = self._GetFileFinderThrottler() elif args.flow.name == self.artifact_collector_flow_name: self._CheckArtifactCollectorFlowArgs(args.flow.args) throttler = self._GetArtifactCollectorFlowThrottler() else: raise access_control.UnauthorizedAccess( "Creating arbitrary flows (%s) is not allowed." % args.flow.name) try: throttler.EnforceLimits(args.client_id.ToClientURN(), token.username, args.flow.name, args.flow.args, token=token) except throttle.ErrorFlowDuplicate as e: # If a similar flow did run recently, just return it. return ApiRobotReturnDuplicateFlowHandler(flow_urn=e.flow_urn) except throttle.ErrorDailyFlowRequestLimitExceeded as e: # Raise UnauthorizedAccess so that the user gets an HTTP 403. raise access_control.UnauthorizedAccess(str(e)) return ApiRobotCreateFlowHandler(robot_id=self.params.robot_id, override_flow_args=override_flow_args)
def _CheckFlowRobotId(self, client_id, flow_id, token=None): # We don't use robot ids in REL_DB, but simply check that flow's creator is # equal to the user making the request. # TODO(user): get rid of robot id logic as soon as AFF4 is gone. if data_store.RelationalDBEnabled(): flow_obj = data_store.REL_DB.ReadFlowObject( str(client_id), str(flow_id)) if flow_obj.creator != token.username: raise access_control.UnauthorizedAccess( "Flow %s (client %s) has to be created " "by the user making the request." % (flow_id, client_id)) return flow_obj.flow_class_name else: flow_urn = flow_id.ResolveClientFlowURN(client_id) fd = aff4.FACTORY.Open(flow_urn, aff4_type=flow.GRRFlow, token=token) needed_label_name = LABEL_NAME_PREFIX + self.params.robot_id if needed_label_name not in fd.GetLabelsNames(): raise access_control.UnauthorizedAccess( "Flow %s (client %s) does not have a proper robot id label set." % (flow_id, client_id)) return fd.Name()
def _CheckArtifactCollectorFlowArgs(self, flow_args): if not self.params.artifact_collector_flow.enabled: raise access_control.UnauthorizedAccess( "ArtifactCollectorFlow flow is not allowed by the configuration") for name in flow_args.artifact_list: if name not in self.params.artifact_collector_flow.allow_artifacts: raise access_control.UnauthorizedAccess( "Artifact %s is not whitelisted." % name)
def Grant(self): """Create the Approval object and notify the Approval Granter.""" approvals_root_urn = aff4.ROOT_URN.Add("ACL").Add( self.subject_urn.Path()).Add(self.delegate) children_urns = list(aff4.FACTORY.ListChildren(approvals_root_urn)) if not children_urns: raise access_control.UnauthorizedAccess( "No approval found for user %s" % utils.SmartStr(self.token.username), subject=self.subject_urn) approvals = aff4.FACTORY.MultiOpen(children_urns, mode="r", aff4_type=Approval, token=self.token) found_approval_urn = None for approval in approvals: approval_reason = approval.Get(approval.Schema.REASON) if (utils.SmartUnicode(approval_reason) == utils.SmartUnicode( self.reason) and (not found_approval_urn or approval_reason.age > found_approval_urn.age)): found_approval_urn = approval.urn found_approval_urn.age = approval_reason.age if not found_approval_urn: raise access_control.UnauthorizedAccess( "No approval with reason '%s' found for user %s" % (utils.SmartStr( self.reason), utils.SmartStr(self.token.username)), subject=self.subject_urn) # This object must already exist. try: approval_request = aff4.FACTORY.Open(found_approval_urn, mode="rw", aff4_type=self.approval_type, token=self.token) except IOError: raise access_control.UnauthorizedAccess( "Approval object does not exist.", requested_access="rw") with approval_request: # We are now an approver for this request. approval_request.AddAttribute( approval_request.Schema.APPROVER(self.token.username)) return found_approval_urn
def CheckMethodIsAccessChecked(self, method, access_type, args=None, context=None): context = context or self.context # Check that legacy access control manager is called and that the method # is then delegated. method(args, context=context) self.assertTrue(getattr(self.access_checker_mock, access_type).called) getattr(self.delegate_mock, method.__name__).assert_called_with( args, context=context) self.delegate_mock.reset_mock() self.access_checker_mock.reset_mock() try: # Check that when exception is raised by legacy manager, the delegate # method is not called. getattr(self.access_checker_mock, access_type).side_effect = access_control.UnauthorizedAccess("") with self.assertRaises(access_control.UnauthorizedAccess): method(args, context=context) self.assertTrue(getattr(self.access_checker_mock, access_type).called) self.assertFalse(getattr(self.delegate_mock, method.__name__).called) finally: getattr(self.access_checker_mock, access_type).side_effect = None self.delegate_mock.reset_mock() self.access_checker_mock.reset_mock()
def testCopyACLErrorIsCorrectlyDisplayed(self): args = rdf_file_finder.FileFinderArgs(paths=["a/b/*"]) flow.StartAFF4Flow(flow_name=flows_file_finder.FileFinder.__name__, args=args, client_id=self.client_id, token=self.token) # Navigate to client and select newly created flow. self.Open("/#/clients/C.0000000000000001/flows") self.Click("css=td:contains('FileFinder')") # Stub out the API handler to guarantee failure. with mock.patch.object( api_call_router_with_approval_checks. ApiCallRouterWithApprovalChecks, "CreateFlow") as create_flow_mock: # The error has to be an ACL error, since ACL errors are not handled # by the global errors handler and are not automatically displayed. create_flow_mock.side_effect = [ access_control.UnauthorizedAccess("oh no!") ] # Open wizard and launch the copy flow. self.Click("css=button[name=copy_flow]") self.Click("css=button:contains('Launch')") self.WaitUntil(self.IsElementPresent, "css=.modal-dialog .text-danger:contains('oh no!')") # Check that closing the dialog doesn't change flow selection. self.Click("css=button[name=Close]") self.WaitUntil(self.IsElementPresent, "css=grr-client-flows-view tr.row-selected")
def CheckIfCanStartClientFlow(self, username, flow_name): """Checks whether a given user can start a given flow.""" flow_cls = registry.FlowRegistry.FLOW_REGISTRY.get(flow_name) if flow_cls is None or not hasattr( flow_cls, "category") or not flow_cls.category: raise access_control.UnauthorizedAccess( "Flow %s can't be started via the API." % flow_name) if flow_cls in RESTRICTED_FLOWS: try: self.CheckIfHasAccessToRestrictedFlows(username) except access_control.UnauthorizedAccess as e: raise access_control.UnauthorizedAccess( "Not enough permissions to access restricted " f"flow {flow_name}") from e
def testGetGrrUserReturnsRestrictedTraitsForNonAdminUser(self): error = access_control.UnauthorizedAccess("some error") self.access_checker_mock.CheckIfUserIsAdmin.side_effect = error handler = self.router.GetGrrUser(None, context=self.context) self.assertNotEqual(handler.interface_traits, api_user.ApiGrrUserInterfaceTraits().EnableAll())
def CheckIfUserIsAdmin(self, username): """Checks whether the user is an admin.""" user_obj = data_store.REL_DB.ReadGRRUser(username) if user_obj.user_type != user_obj.UserType.USER_TYPE_ADMIN: raise access_control.UnauthorizedAccess( "User %s is not an admin." % username)
def Handle(self, args, token=None): if not args.username: raise ValueError("username can't be empty.") user_urn = aff4.ROOT_URN.Add("users").Add(args.username) events.Events.PublishEvent("Audit", rdf_events.AuditEvent(user=token.username, action="USER_ADD", urn=user_urn), token=token) if aff4.FACTORY.ExistsWithType(user_urn, aff4_type=users.GRRUser, token=token): raise access_control.UnauthorizedAccess( "Cannot add user %s: User already exists." % args.username) with aff4.FACTORY.Create(user_urn, aff4_type=users.GRRUser, mode="rw", token=token) as fd: if args.HasField("password"): fd.SetPassword(args.password) if args.user_type == args.UserType.USER_TYPE_ADMIN: fd.AddLabels(["admin"], owner="GRR") return api_user.ApiGrrUser().InitFromAff4Object(fd)
def GetFlow(self, args, context=None): if not self.params.get_flow.enabled: raise access_control.UnauthorizedAccess( "GetFlow is not allowed by the configuration.") self._CheckFlowRobotId(args.client_id, args.flow_id, context=context) return api_flow.ApiGetFlowHandler()
def ListFlowResults(self, args, token=None): if not self.params.list_flow_results.enabled: raise access_control.UnauthorizedAccess( "ListFlowResults is not allowed by the configuration.") self._CheckFlowRobotId(args.client_id, args.flow_id, token=token) return api_flow.ApiListFlowResultsHandler()
def ListFlowLogs(self, args, context=None): if not self.params.list_flow_logs.enabled: raise access_control.UnauthorizedAccess( "ListFlowLogs is not allowed by the configuration.") self._CheckFlowRobotId(args.client_id, args.flow_id, context=context) return api_flow.ApiListFlowLogsHandler()
def CheckIfCanStartClientFlow(self, username, flow_name): """Checks whether a given user can start a given flow.""" del username # Unused. flow_cls = registry.FlowRegistry.FLOW_REGISTRY.get(flow_name) if not flow_cls.category: raise access_control.UnauthorizedAccess( "Flow %s can't be started via the API." % flow_name)
def CheckIfCanStartClientFlow(self, username, flow_name): """Checks whether a given user can start a given flow.""" del username # Unused. flow_cls = flow.GRRFlow.GetPlugin(flow_name) if not flow_cls.category: raise access_control.UnauthorizedAccess( "Flow %s can't be started via the API." % flow_name)
def CheckApproversForLabel(self, token, client_id, requester, approvers, label): """Checks if requester and approvers have approval privileges for labels. Checks against list of approvers for each label defined in approvers.yaml to determine if the list of approvers is sufficient. Args: token: user token client_id: Client ID of the client requester: username string of person requesting approval. approvers: list of username strings that have approved this client. label: label strings to check approval privs for. Returns: True if access is allowed, raises otherwise. """ auth = self.reader.GetAuthorizationForSubject(label) if not auth: # This label isn't listed in approvers.yaml return True if auth.requester_must_be_authorized: if not self.CheckPermissions(requester, label): raise access_control.UnauthorizedAccess( "User %s not in %s or groups:%s for %s" % (requester, auth.users, auth.groups, label), subject=client_id, requested_access=token.requested_access) approved_count = 0 for approver in approvers: if self.CheckPermissions(approver, label) and approver != requester: approved_count += 1 if approved_count < auth.num_approvers_required: raise access_control.UnauthorizedAccess( "Found %s approvers for %s, needed %s" % (approved_count, label, auth.num_approvers_required), subject=client_id, requested_access=token.requested_access) return True
def InferUserAndSubjectFromUrn(self): """Infers user name and subject urn from self.urn.""" _, cron_str, cron_job_name, user, _ = self.urn.Split(5) if cron_str != "cron": raise access_control.UnauthorizedAccess( "Approval object has invalid urn %s." % self.urn, requested_access=self.token.requested_access) return (user, aff4.ROOT_URN.Add("cron").Add(cron_job_name))
def CheckUserForLabels(username, authorized_labels, token=None): """Verify that the username has all the authorized_labels set.""" authorized_labels = set(authorized_labels) try: user = aff4.FACTORY.Open( "aff4:/users/%s" % username, aff4_type=aff4_users.GRRUser, token=token) # Only return if all the authorized_labels are found in the user's # label list, otherwise raise UnauthorizedAccess. if (authorized_labels.intersection( user.GetLabelsNames()) == authorized_labels): return True else: raise access_control.UnauthorizedAccess( "User %s is missing labels (required: %s)." % (username, authorized_labels)) except IOError: raise access_control.UnauthorizedAccess("User %s not found." % username)
def _CheckFlowRobotId(self, client_id, flow_id, context=None): # We don't use robot ids in REL_DB, but simply check that flow's creator is # equal to the user making the request. # TODO(user): get rid of robot id logic as soon as AFF4 is gone. flow_obj = data_store.REL_DB.ReadFlowObject(str(client_id), str(flow_id)) if flow_obj.creator != context.username: raise access_control.UnauthorizedAccess( "Flow %s (client %s) has to be created " "by the user making the request." % (flow_id, client_id)) return flow_obj.flow_class_name
def _CheckFileFinderArgs(self, flow_args, context=None): ffparams = self.params.file_finder_flow if not ffparams.enabled: raise access_control.UnauthorizedAccess( "FileFinder flow is not allowed by the configuration.") if not ffparams.globs_allowed: for path in flow_args.paths: str_path = str(path) if "*" in str_path: raise access_control.UnauthorizedAccess( "Globs are not allowed by the configuration.") if not ffparams.interpolations_allowed: for path in flow_args.paths: str_path = str(path) if "%%" in str_path: raise access_control.UnauthorizedAccess( "Interpolations are not allowed by the configuration.")
def _CheckFlowRobotId(self, client_id, flow_id, token=None): flow_urn = flow_id.ResolveClientFlowURN(client_id, token=token) fd = aff4.FACTORY.Open(flow_urn, aff4_type=flow.GRRFlow, token=token) needed_label_name = LABEL_NAME_PREFIX + self.params.robot_id if needed_label_name not in fd.GetLabelsNames(): raise access_control.UnauthorizedAccess( "Flow %s (client %s) does not have a proper robot id label set." % (flow_id, client_id)) return fd
def _IsHomeDir(self, subject, token): """Checks user access permissions for paths under aff4:/users.""" h = CheckAccessHelper("IsHomeDir") h.Allow("aff4:/users/%s" % token.username) h.Allow("aff4:/users/%s/*" % token.username) try: return h.CheckAccess(subject, token) except access_control.UnauthorizedAccess: raise access_control.UnauthorizedAccess( "User can only access their " "home directory.", subject=subject)
def _CheckHasAdminApprovers(approval_request): grantors = set(g.grantor_username for g in approval_request.grants) for g in grantors: user_obj = data_store.REL_DB.ReadGRRUser(g) if user_obj.user_type == user_obj.UserType.USER_TYPE_ADMIN: return True raise access_control.UnauthorizedAccess( "Need at least 1 admin approver for access.", subject=BuildLegacySubject(approval_request.subject_id, approval_request.approval_type))
def CheckIfCanStartClientFlow(self, username, flow_name): """Checks whether a given user can start a given flow.""" flow_cls = flow.GRRFlow.GetPlugin(flow_name) if not flow_cls.category: raise access_control.UnauthorizedAccess( "Flow %s can't be started on a client by non-suid users." % flow_name) if flow_cls.AUTHORIZED_LABELS: self.CheckIfUserIsAdmin(username)
def _CheckHasEnoughGrants(approval_request): approvers_required = config.CONFIG["ACL.approvers_required"] approvers = set(g.grantor_username for g in approval_request.grants) missing = approvers_required - len(approvers) if missing > 0: msg = ("Need at least %d additional approver%s for access." % (missing, "s" if missing > 1 else "")) raise access_control.UnauthorizedAccess( msg, subject=BuildLegacySubject(approval_request.subject_id, approval_request.approval_type))
def CheckClientLabels(client_id, allow_labels=None, allow_labels_owners=None): """Checks a given client against labels/owners allowlists.""" allow_labels = allow_labels or [] allow_labels_owners = allow_labels_owners or [] labels = data_store.REL_DB.ReadClientLabels(str(client_id)) for label in labels: if (label.name in allow_labels and label.owner in allow_labels_owners): return raise access_control.UnauthorizedAccess( "Client %s doesn't have necessary labels." % client_id)