Skip to content

Commit 73ddca5

Browse files
DR-854, DR-866 local pamUser assignment and setting resourceUid to blank.
DR-854 Don't allow local pamUser record reuse. During processing discovery, if existing local pamUser record is assigned to the resource don't let the same local pamUser record be used for another resource. The list of pamUser records to be converted are passed to 'find user' method. When we filter out users, use it to filter out/notifiy it's taken when list users. DR-866 Don't assigned resourceUid is IAM User. If the parent of the user is a provider, make sure the resourceUid for set rotation is None.
1 parent 2aa5062 commit 73ddca5

File tree

4 files changed

+132
-61
lines changed

4 files changed

+132
-61
lines changed

keepercommander/commands/discover/result_process.py

Lines changed: 99 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
from ...proto import router_pb2, record_pb2
1313
from keepercommander.discovery_common.jobs import Jobs
1414
from keepercommander.discovery_common.process import Process, QuitException, NoDiscoveryDataException
15-
from keepercommander.discovery_common.types import (DiscoveryObject, UserAcl, PromptActionEnum, PromptResult,
16-
BulkRecordAdd, BulkRecordConvert, BulkProcessResults, BulkRecordSuccess,
17-
BulkRecordFail, DirectoryInfo, NormalizedRecord, RecordField)
15+
from keepercommander.discovery_common.types import (
16+
DiscoveryObject, UserAcl, PromptActionEnum, PromptResult, BulkRecordAdd, BulkRecordConvert, BulkProcessResults,
17+
BulkRecordSuccess, BulkRecordFail, DirectoryInfo, NormalizedRecord, RecordField)
1818
from pydantic import BaseModel
1919
from typing import Optional, List, Any, TYPE_CHECKING
2020

@@ -46,6 +46,7 @@ class AdminSearchResult(BaseModel):
4646
record: Any
4747
is_directory_user: bool
4848
is_pam_user: bool
49+
being_used: bool = False
4950

5051

5152
class PAMGatewayActionDiscoverResultProcessCommand(PAMGatewayActionDiscoverCommandBase):
@@ -582,7 +583,10 @@ def _prompt(self,
582583
raise QuitException()
583584
print()
584585

585-
def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None) -> Optional[TypedRecord]:
586+
def _find_user_record(self,
587+
params: KeeperParams,
588+
bulk_convert_records: List[BulkRecordConvert],
589+
context: Optional[Any] = None) -> (Optional[TypedRecord], bool):
586590

587591
gateway_context = context.get("gateway_context") # type: GatewayContext
588592
record_link = context.get("record_link") # type: RecordLink
@@ -599,11 +603,15 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
599603
for record in folder["records"]:
600604
shared_record_uids.append(record.get("record_uid"))
601605

606+
# Make a list of record we are already converting so we don't show them again.
607+
converting_list = [x.record_uid for x in bulk_convert_records]
608+
602609
logging.debug(f"shared folders record uid {shared_record_uids}")
603610

604611
while True:
605612
user_search = input("Enter an user to search for [ENTER/RETURN to quit]> ")
606613
if user_search == "":
614+
print(f"{bcolors.FAIL}No search terms, not performing search.{bcolors.ENDC}")
607615
return None
608616

609617
# Search for record with the search string.
@@ -613,39 +621,40 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
613621
search_str=user_search,
614622
record_version=3
615623
))
624+
# If not record are returned by the search just return None,
616625
if len(user_record) == 0:
617-
print(f"{bcolors.FAIL}Could not find any record.{bcolors.ENDC}")
626+
print(f"{bcolors.FAIL}Could not find any records that contain the search text.{bcolors.ENDC}")
627+
return None
618628

619629
# Find usable admin records.
620630
admin_search_results = [] # type: List[AdminSearchResult]
621631
for record in user_record:
622632

623633
user_record = vault.KeeperRecord.load(params, record.record_uid)
624634
if user_record.record_type == "pamUser":
635+
logging.debug(f"{record.record_uid} is a pamUser")
636+
637+
# If we are already converting this pamUser record, then don't show it.
638+
if record.record_uid in converting_list:
639+
logging.debug(f"pamUser {user_record.title}, {user_record.record_uid} is being converted; "
640+
"BAD for search")
641+
admin_search_results.append(
642+
AdminSearchResult(
643+
record=user_record,
644+
is_directory_user=False,
645+
is_pam_user=True,
646+
being_used=True
647+
)
648+
)
649+
continue
625650

626651
# Does the record exist in the gateway shared folder?
627652
# We want to filter our other gateway's pamUser, or it will get overwhelming.
628653
if user_record.record_uid not in shared_record_uids:
629654
logging.debug(f"pamUser {record.title}, {user_record.record_uid} not in shared "
630-
"folder, skip")
655+
"folder, BAD for search")
631656
continue
632657

633-
# # If a pamUser, make sure the user is part of our configuration
634-
# record_rotation = params.record_rotation_cache.get(record.record_uid)
635-
# if record_rotation is not None:
636-
# configuration_uid = record_rotation.get("configuration_uid")
637-
# if configuration_uid is None or configuration_uid == "":
638-
# logging.debug(f"pamUser {record.title}, {record.record_uid} does not have a controller, "
639-
# "skip")
640-
# continue
641-
# if configuration_uid != gateway_context.configuration_uid:
642-
# logging.debug(f"pamUser {record.title}, {record.record_uid} controller is not this "
643-
# " controller, skip")
644-
# continue
645-
# else:
646-
# logging.debug(f"pamUser {record.title}, {record.record_uid} does not have a rotation
647-
# settings.")
648-
649658
# If the record does not exist in the record linking, it's orphaned; accept it
650659
# If it does exist, then check if it belonged to a directory.
651660
# Very unlikely a user that belongs to a database or another machine can be used.
@@ -659,28 +668,32 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
659668
is_directory_user = self._is_directory_user(parent_record.record_type)
660669
if is_directory_user is False:
661670
logging.debug(f"pamUser parent for {user_record.title}, "
662-
"{user_record.record_uid} is not a directory, skip")
671+
"{user_record.record_uid} is not a directory; BAD for search")
663672
continue
664673

674+
logging.debug(f"pamUser {user_record.title}, {user_record.record_uid} is a directory user; "
675+
"good for search")
676+
665677
else:
666-
logging.debug(f"pamUser {user_record.title}, {user_record.record_uid} does not have record "
667-
"linking vertex.")
678+
logging.debug(f"pamUser {user_record.title}, {user_record.record_uid} does not a parent; "
679+
"good for search")
668680
else:
669681
logging.debug(f"pamUser {user_record.title}, {user_record.record_uid} does not have record "
670-
"linking vertex.")
682+
"linking vertex; good for search")
671683

672684
admin_search_results.append(
673685
AdminSearchResult(
674686
record=user_record,
675687
is_directory_user=is_directory_user,
676-
is_pam_user=True
688+
is_pam_user=True,
689+
being_used=False
677690
)
678691
)
679692

680693
# Else this is a non-PAM record.
681694
# Make sure it has a login, password, private key
682695
else:
683-
logging.debug(f"{record.record_uid} is not a pamUser")
696+
logging.debug(f"{record.record_uid} is NOT a pamUser")
684697
login_field = next((x for x in record.fields if x.type == "login"), None)
685698
password_field = next((x for x in record.fields if x.type == "password"), None)
686699
private_key_field = next((x for x in record.fields if x.type == "keyPair"), None)
@@ -693,11 +706,16 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
693706
is_pam_user=False
694707
)
695708
)
709+
logging.debug(f"{record.title} is has credentials, good for search")
696710
else:
697-
logging.debug(f"{record.title} is missing full credentials, skip")
711+
logging.debug(f"{record.title} is missing full credentials, BAD for search")
698712

699-
user_index = 1
713+
# If all the users have been filtered out, then just return None
714+
if len(admin_search_results) == 0:
715+
print(f"{bcolors.FAIL}Could not find any available records.{bcolors.ENDC}")
716+
return None
700717

718+
user_index = 1
701719
admin_search_results = sorted(admin_search_results,
702720
key=lambda x: x.is_pam_user,
703721
reverse=True)
@@ -709,32 +727,50 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
709727
has_local_user = True
710728
is_local_user = True
711729

712-
print(f"{bcolors.HEADER}[{user_index}] {bcolors.ENDC}"
713-
f"{_b('* ') if is_local_user is True else ''}"
714-
f"{admin_search_result.record.title} "
715-
f'{"(Directory User)" if admin_search_result.is_directory_user is True else ""}')
730+
hc = bcolors.HEADER
731+
b = bcolors.BOLD
732+
tc = ""
733+
index_str = user_index
734+
if admin_search_result.being_used is True:
735+
hc = bcolors.WARNING
736+
b = bcolors.WARNING
737+
tc = bcolors.WARNING
738+
index_str = "-" * len(str(index_str))
739+
740+
print(f"{hc}[{index_str}] {bcolors.ENDC}"
741+
f"{b + '* ' + bcolors.ENDC if is_local_user is True else ''}"
742+
f"{tc}{admin_search_result.record.title}{bcolors.ENDC} "
743+
f'{"(Directory User) " if admin_search_result.is_directory_user is True else ""}'
744+
f'{tc + "(Already taken)" + bcolors.ENDC if admin_search_result.being_used is True else ""}')
716745
user_index += 1
717746

718747
if has_local_user is True:
719748
print(f"{bcolors.BOLD}* Not a PAM User record. "
720749
f"A PAM User would be generated from this record.{bcolors.ENDC}")
721750

722-
select = input("Enter line number of user record to use, enter/return to refind the search, "
751+
select = input("Enter line number of user record to use, enter/return to refine the search, "
723752
f"or {_b('Q')} to quit search. > ").lower()
724753
if select == "":
725754
continue
726755
elif select[0] == "q":
727756
return None
728757
else:
729758
try:
730-
return admin_search_results[int(select) - 1].record # type: TypedRecord
759+
selected = admin_search_results[int(select) - 1]
760+
if selected.being_used is True:
761+
print(f"{bcolors.FAIL}Cannot select a record that has already been taken. "
762+
f"Another record is using this local user as its administrator.{bcolors.ENDC}")
763+
return None
764+
admin_record = selected.record # type: TypedRecord
765+
return admin_record, selected.is_directory_user
731766
except IndexError:
732767
print(f"{bcolors.FAIL}Entered row index does not exists.{bcolors.ENDC}")
733768
continue
734769

735770
@staticmethod
736-
def _handle_admin_record_from_record(record: TypedRecord, content: DiscoveryObject, context: Optional[Any] = None) \
737-
-> Optional[PromptResult]:
771+
def _handle_admin_record_from_record(record: TypedRecord,
772+
content: DiscoveryObject,
773+
context: Optional[Any] = None) -> Optional[PromptResult]:
738774

739775
params = context.get("param") # type: KeeperParams
740776
gateway_context = context.get("gateway_context") # type: GatewayContext
@@ -817,8 +853,13 @@ def _handle_admin_record_from_record(record: TypedRecord, content: DiscoveryObje
817853
"The password on that record will not be rotated."
818854
)
819855

820-
def _prompt_admin(self, parent_vertex: DAGVertex, content: DiscoveryObject, acl: UserAcl,
821-
indent: int = 0, context: Optional[Any] = None) -> PromptResult:
856+
def _prompt_admin(self,
857+
parent_vertex: DAGVertex,
858+
content: DiscoveryObject,
859+
acl: UserAcl,
860+
bulk_convert_records: List[BulkRecordConvert],
861+
indent: int = 0,
862+
context: Optional[Any] = None) -> PromptResult:
822863

823864
if content is None:
824865
raise Exception("The admin content was not passed in to prompt the user.")
@@ -863,7 +904,9 @@ def _prompt_admin(self, parent_vertex: DAGVertex, content: DiscoveryObject, acl:
863904
return prompt_result
864905
elif action[0] == 'f':
865906
print("")
866-
record = self._find_user_record(params, context=context)
907+
record, is_directory_user = self._find_user_record(params,
908+
context=context,
909+
bulk_convert_records=bulk_convert_records)
867910
if record is not None:
868911
admin_prompt_result = self._handle_admin_record_from_record(
869912
record=record,
@@ -872,6 +915,7 @@ def _prompt_admin(self, parent_vertex: DAGVertex, content: DiscoveryObject, acl:
872915
)
873916
if admin_prompt_result is not None:
874917
if admin_prompt_result.action == PromptActionEnum.ADD:
918+
admin_prompt_result.is_directory_user = is_directory_user
875919
print(f"{bcolors.OKGREEN}Adding admin record to save queue.{bcolors.ENDC}")
876920
return admin_prompt_result
877921
elif action[0] == 's':
@@ -907,7 +951,7 @@ def _prompt_confirm_add(bulk_add_records: List[BulkRecordAdd]):
907951
msg = (f"{bcolors.BOLD}There is 1 record queued to be added to your vault. "
908952
f"Do you wish to add it? [Y/N]> {bcolors.ENDC}")
909953
else:
910-
msg = (f"{bcolors.BOLD}There are {count} records queue to be added to your vault. "
954+
msg = (f"{bcolors.BOLD}There are {count} records queued to be added to your vault. "
911955
f"Do you wish to add them? [Y/N]> {bcolors.ENDC}")
912956
while True:
913957
yn = input(msg).lower()
@@ -1038,8 +1082,15 @@ def _create_records(cls, bulk_add_records: List[BulkRecordAdd], context: Optiona
10381082
# STEP 3 - Add rotation settings.
10391083
# Use the list we passed in, find the results, and add if the additions were successful.
10401084

1085+
# Keep track of each record we create a rotation for to avoid version problems, if there was a dup.
1086+
created_cache = []
1087+
10411088
# For the records passed in to be created.
10421089
for bulk_record in bulk_add_records:
1090+
if bulk_record.record_uid in created_cache:
1091+
logging.debug(f"found a duplicate of record uid: {bulk_record.record_uid}")
1092+
continue
1093+
10431094
# Grab the type Keeper record instance, and title from that record.
10441095
pb_add_record = bulk_record.record
10451096
title = bulk_record.title
@@ -1088,7 +1139,7 @@ def _create_records(cls, bulk_add_records: List[BulkRecordAdd], context: Optiona
10881139

10891140
# Only set the resource if the record type is a PAM User.
10901141
# Machines, databases, and directories have a login/password in the record that indicates who the admin is.
1091-
if bulk_record.record_type == "pamUser":
1142+
if bulk_record.record_type == "pamUser" and bulk_record.parent_record_uid is not None:
10921143
rq.resourceUid = url_safe_str_to_bytes(bulk_record.parent_record_uid)
10931144

10941145
# Right now, the schedule and password complexity are not set. This would be part of a rule engine.
@@ -1098,6 +1149,8 @@ def _create_records(cls, bulk_add_records: List[BulkRecordAdd], context: Optiona
10981149

10991150
router_set_record_rotation_information(params, rq)
11001151

1152+
created_cache.append(bulk_record.record_uid)
1153+
11011154
build_process_results.success.append(
11021155
BulkRecordSuccess(
11031156
title=title,
@@ -1123,6 +1176,8 @@ def _convert_records(cls, bulk_convert_records: List[BulkRecordConvert], context
11231176

11241177
rq = router_pb2.RouterRecordRotationRequest()
11251178
rq.recordUid = url_safe_str_to_bytes(bulk_convert_record.record_uid)
1179+
1180+
# We can't set the version to 0 if it's greater than 0, look up prior version.
11261181
record_rotation_revision = params.record_rotation_cache.get(bulk_convert_record.record_uid)
11271182
rq.revision = record_rotation_revision.get('revision') if record_rotation_revision else 0
11281183

@@ -1131,7 +1186,7 @@ def _convert_records(cls, bulk_convert_records: List[BulkRecordConvert], context
11311186

11321187
# Only set the resource if the record type is a PAM User.
11331188
# Machines, databases, and directories have a login/password in the record that indicates who the admin is.
1134-
if record.record_type == "pamUser":
1189+
if record.record_type == "pamUser" and bulk_convert_record.parent_record_uid is not None:
11351190
rq.resourceUid = url_safe_str_to_bytes(bulk_convert_record.parent_record_uid)
11361191
else:
11371192
rq.resourceUid = None
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.0.26'
1+
__version__ = '1.0.27'

0 commit comments

Comments
 (0)