Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions keepercommander/commands/discover/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,11 @@ def from_gateway(params: KeeperParams, gateway: str, configuration_uid: str | No
if application is None:
logging.debug(f"cannot find application for gateway {gateway}, skipping.")

if (utils.base64_url_encode(found_gateway.controllerUid) == gateway or
found_gateway.controllerName.lower() == gateway.lower()):
# When --configuration-uid selected the config, trust that selection.
# Otherwise require the gateway name/UID to match.
if (configuration_uid is not None
or utils.base64_url_encode(found_gateway.controllerUid) == gateway
or found_gateway.controllerName.lower() == gateway.lower()):
return GatewayContext(
configuration=configuration_record,
facade=configuration_facade,
Expand Down
275 changes: 189 additions & 86 deletions keepercommander/commands/discover/job_status.py

Large diffs are not rendered by default.

55 changes: 53 additions & 2 deletions keepercommander/commands/discover/rule_list.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations
import argparse
import json
from . import PAMGatewayActionDiscoverCommandBase, GatewayContext, MultiConfigurationException, multi_conf_msg
from ...display import bcolors
from ..pam.router_helper import router_get_connected_gateways
Expand All @@ -20,10 +21,29 @@ class PAMGatewayActionDiscoverRuleListCommand(PAMGatewayActionDiscoverCommandBas

parser.add_argument('--search', '-s', required=False, dest='search', action='store',
help='Search for rules.')
parser.add_argument('--format', dest='format', action='store', choices=['table', 'json'],
default='table', help='Output format (table, json)')

def get_parser(self):
return PAMGatewayActionDiscoverRuleListCommand.parser

@staticmethod
def _rule_to_dict(rule: RuleItem):
action_value = None
if getattr(rule, 'action', None) is not None:
action_value = rule.action.value
return {
'rule_id': rule.rule_id,
'name': rule.name or '',
'action': action_value,
'priority': rule.priority,
'case_sensitive': bool(rule.case_sensitive),
'added': rule.added_ts_str if rule.added_ts else '',
'shared_folder_uid': getattr(rule, 'shared_folder_uid', None) or '',
'admin_uid': getattr(rule, 'admin_uid', None) or '',
'rule': Rules.make_action_rule_statement_str(rule.statement),
}

@staticmethod
def print_rule_table(rule_list: List[RuleItem]):

Expand Down Expand Up @@ -90,21 +110,43 @@ def execute(self, params, **kwargs):

gateway = kwargs.get("gateway")
configuration_uid = kwargs.get('configuration_uid')
format_type = kwargs.get('format') or 'table'
try:
gateway_context = GatewayContext.from_gateway(params=params,
gateway=gateway,
configuration_uid=configuration_uid)
if gateway_context is None:
print(f"{bcolors.FAIL}Could not find the gateway configuration for {gateway}.{bcolors.ENDC}")
message = f'Could not find the gateway configuration for {gateway}.'
if format_type == 'json':
print(json.dumps({'message': message}, indent=2))
else:
print(f"{bcolors.FAIL}{message}{bcolors.ENDC}")
return
except MultiConfigurationException as err:
multi_conf_msg(gateway, err)
if format_type == 'json':
configs = []
for item in (err.items or []):
record = item.get('configuration_record')
if record is not None:
configs.append({
'uid': getattr(record, 'record_uid', ''),
'title': getattr(record, 'title', ''),
})
print(json.dumps({
'message': f'Found multiple configuration records for gateway {gateway}.',
'configurations': configs,
}, indent=2))
else:
multi_conf_msg(gateway, err)
return

rules = Rules(record=gateway_context.configuration, params=params)
rule_list = rules.rule_list(rule_type=RuleTypeEnum.ACTION,
search=kwargs.get("search")) # type: List[RuleItem]
if len(rule_list) == 0:
if format_type == 'json':
print(json.dumps({'rules': []}, indent=2))
return
print("")
text = f"{bcolors.FAIL}There are no rules. " \
f"Use 'pam action discover rule add -g {gateway_context.gateway_uid} "
Expand All @@ -114,4 +156,13 @@ def execute(self, params, **kwargs):
print(text)
return

if format_type == 'json':
print(json.dumps({
'gateway': gateway_context.gateway_name,
'gateway_uid': gateway_context.gateway_uid,
'configuration_uid': gateway_context.configuration_uid,
'rules': [self._rule_to_dict(rule) for rule in rule_list],
}, indent=2))
return

self.print_rule_table(rule_list=rule_list)
140 changes: 103 additions & 37 deletions keepercommander/commands/discoveryrotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1670,13 +1670,16 @@ class PAMListRecordRotationCommand(Command):
parser = argparse.ArgumentParser(prog='pam rotation list')
parser.add_argument('--verbose', '-v', required=False, default=False, dest='is_verbose', action='store_true',
help='Verbose output')
parser.add_argument('--format', dest='format', action='store', choices=['table', 'json'], default='table',
help='Output format (table, json)')

def get_parser(self):
return PAMListRecordRotationCommand.parser

def execute(self, params, **kwargs):

is_verbose = kwargs.get('is_verbose')
format_type = kwargs.get('format') or 'table'

rq = pam_pb2.PAMGenericUidsRequest()
schedules_proto = router_get_rotation_schedules(params, rq)
Expand All @@ -1695,19 +1698,27 @@ def execute(self, params, **kwargs):
all_pam_config_records = pam_configurations_get_all(params)
table = []

headers = []
headers.append('Record UID')
headers.append('Record Title')
headers.append('Record Type')
headers.append('Schedule')
if format_type == 'json':
headers = ['record_uid', 'record_title', 'record_type', 'schedule', 'gateway']
if is_verbose:
headers.append('gateway_uid')
headers.append('pam_configuration')
if is_verbose:
headers.append('pam_configuration_uid')
else:
headers = []
headers.append('Record UID')
headers.append('Record Title')
headers.append('Record Type')
headers.append('Schedule')

headers.append('Gateway')
if is_verbose:
headers.append('Gateway UID')
headers.append('Gateway')
if is_verbose:
headers.append('Gateway UID')

headers.append('PAM Configuration (Type)')
if is_verbose:
headers.append('PAM Configuration UID')
headers.append('PAM Configuration (Type)')
if is_verbose:
headers.append('PAM Configuration UID')

for s in schedules:
row = []
Expand All @@ -1724,22 +1735,27 @@ def execute(self, params, **kwargs):
is_controller_online = any(
(poc for poc in enterprise_controllers_connected_uids_bytes if poc == controller_uid))

row_color = ''
if record_exists_in_vault(params, record_uid):
row_color = bcolors.HIGHINTENSITYWHITE
record_accessible = True
record_title, record_type = get_vault_record_title_type(params, record_uid)
else:
row_color = bcolors.WHITE
record_accessible = False
record_title = '[record inaccessible]'
record_type = '[record inaccessible]'

if record_type != "pamUser":
# only pamUser records are supported for rotation
continue

row.append(f'{row_color}{record_uid}')
row.append(record_title or '[untitled]')
row.append(record_type or '[unknown]')
if format_type == 'json':
row.append(record_uid)
row.append(record_title or '[untitled]')
row.append(record_type or '[unknown]')
else:
row_color = bcolors.HIGHINTENSITYWHITE if record_accessible else bcolors.WHITE
row.append(f'{row_color}{record_uid}')
row.append(record_title or '[untitled]')
row.append(record_type or '[unknown]')

if s.noSchedule is True:
# Per Sergey A:
Expand All @@ -1756,9 +1772,15 @@ def execute(self, params, **kwargs):
else:
schedule_str = s.scheduleData
else:
schedule_str = f'{bcolors.FAIL}[empty]'
schedule_str = '[empty]'

row.append(f'{schedule_str}')
if format_type == 'json':
row.append(schedule_str)
else:
if schedule_str == '[empty]':
row.append(f'{bcolors.FAIL}[empty]')
else:
row.append(f'{schedule_str}')

# Controller Info
connected_controller = None
Expand All @@ -1767,29 +1789,45 @@ def execute(self, params, **kwargs):
list(enterprise_controllers_connected_resp.controllers)}
connected_controller = router_controllers.get(controller_details.controllerUid)

if connected_controller:
controller_stat_color = bcolors.OKGREEN
if format_type == 'json':
if controller_details:
row.append(controller_details.controllerName)
else:
row.append('[Does not exist]')
if is_verbose:
row.append(utils.base64_url_encode(controller_uid))
else:
controller_stat_color = bcolors.WHITE
if connected_controller:
controller_stat_color = bcolors.OKGREEN
else:
controller_stat_color = bcolors.WHITE

controller_color = bcolors.WHITE
if is_controller_online:
controller_color = bcolors.OKGREEN
controller_color = bcolors.WHITE
if is_controller_online:
controller_color = bcolors.OKGREEN

if controller_details:
row.append(f'{controller_stat_color}{controller_details.controllerName}{bcolors.ENDC}')
else:
row.append(f'{controller_stat_color}[Does not exist]{bcolors.ENDC}')
if controller_details:
row.append(f'{controller_stat_color}{controller_details.controllerName}{bcolors.ENDC}')
else:
row.append(f'{controller_stat_color}[Does not exist]{bcolors.ENDC}')

if is_verbose:
row.append(f'{controller_color}{utils.base64_url_encode(controller_uid)}{bcolors.ENDC}')
if is_verbose:
row.append(f'{controller_color}{utils.base64_url_encode(controller_uid)}{bcolors.ENDC}')

if not pam_configuration:
if not is_verbose:
row.append(f"{bcolors.FAIL}[No config found]{bcolors.ENDC}")
if format_type == 'json':
if not is_verbose:
row.append('[No config found]')
else:
row.append(
f'[No config found. Looks like configuration {configuration_uid_str} was removed '
f'but rotation schedule was not modified]')
else:
row.append(
f"{bcolors.FAIL}[No config found. Looks like configuration {configuration_uid_str} was removed but rotation schedule was not modified{bcolors.ENDC}")
if not is_verbose:
row.append(f"{bcolors.FAIL}[No config found]{bcolors.ENDC}")
else:
row.append(
f"{bcolors.FAIL}[No config found. Looks like configuration {configuration_uid_str} was removed but rotation schedule was not modified{bcolors.ENDC}")

else:
pam_config_name, pam_config_type = get_vault_record_title_type(params, configuration_uid_str)
Expand All @@ -1813,13 +1851,19 @@ def execute(self, params, **kwargs):
row.append(f"{pam_config_name or '[untitled]'} ({pam_config_type or '[unknown]'})")

if is_verbose:
row.append(f'{utils.base64_url_encode(configuration_uid)}{bcolors.ENDC}')
if format_type == 'json':
row.append(utils.base64_url_encode(configuration_uid))
else:
row.append(f'{utils.base64_url_encode(configuration_uid)}{bcolors.ENDC}')

table.append(row)

table.sort(key=lambda x: (x[1] or ''))

dump_report_data(table, headers, fmt='table', filename="", row_number=False, column_width=None)
report = dump_report_data(table, headers, fmt=format_type, filename="",
row_number=False, column_width=None)
if format_type == 'json':
return report

print(f"\n{bcolors.OKBLUE}----------------------------------------------------------{bcolors.ENDC}")
print(f"{bcolors.OKBLUE}Example to rotate record to which this user has access to:{bcolors.ENDC}")
Expand Down Expand Up @@ -4204,13 +4248,18 @@ class PAMGatewayActionServerInfoCommand(Command):
parser = argparse.ArgumentParser(prog='dr-info-command')
parser.add_argument('--gateway', '-g', required=False, dest='gateway_uid', action='store', help='Gateway UID')
parser.add_argument('--verbose', '-v', required=False, dest='verbose', action='store_true', help='Verbose Output')
parser.add_argument('--format', dest='format', action='store', choices=['text', 'json'],
default='text', help='Output format (text, json)')

def get_parser(self):
return PAMGatewayActionServerInfoCommand.parser

def execute(self, params, **kwargs):
from .pam.router_helper import get_response_payload

destination_gateway_uid_str = kwargs.get('gateway_uid')
is_verbose = kwargs.get('verbose')
format_type = kwargs.get('format') or 'text'
router_response = router_send_action_to_gateway(
params=params,
gateway_action=GatewayActionGatewayInfo(is_scheduled=False),
Expand All @@ -4219,6 +4268,23 @@ def execute(self, params, **kwargs):
destination_gateway_uid_str=destination_gateway_uid_str
)

if format_type == 'json':
if not router_response:
print(json.dumps({"gateway_info": None, "message": "No response from gateway."}, indent=2))
return
payload = get_response_payload(router_response)
if not (payload.get('is_ok') or payload.get('isOk')):
print(json.dumps({"ok": False, "response": payload}, indent=2))
return
result = {
"ok": True,
"gateway_info": payload.get('data'),
}
if payload.get('warnings'):
result['warnings'] = payload.get('warnings')
print(json.dumps(result, indent=2))
return

print_router_response(router_response, 'gateway_info', is_verbose=is_verbose,
gateway_uid=destination_gateway_uid_str)

Expand Down
Loading