Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 17.0.6 #1369

Merged
merged 6 commits into from
Jan 31, 2025
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
2 changes: 1 addition & 1 deletion keepercommander/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
# Contact: [email protected]
#

__version__ = '17.0.5'
__version__ = '17.0.6'
3 changes: 2 additions & 1 deletion keepercommander/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import csv
import datetime
import io
import itertools
import json
import logging
import os
Expand Down Expand Up @@ -675,7 +676,7 @@ def get_custom_field(record, field_name): # type: (vault.KeeperRecord, str)
return next((x.value for x in record.custom if field_name.lower() == x.name.lower()), None)

if isinstance(record, vault.TypedRecord):
return next((x.get_default_value(str) for x in record.custom
return next((x.get_default_value(str) for x in itertools.chain(record.fields, record.custom)
if (x.type or 'text') in RecordMixin.CUSTOM_FIELD_TYPES and field_name.lower() == (x.label or '').lower()), None)

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion keepercommander/commands/discover/job_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,4 @@ def execute(self, params, **kwargs):
print(f"To stop and remove the current job, use the command "
f"'{bcolors.OKGREEN}pam action discover remove -j <Job ID>'.")
else:
print_router_response(router_response, conversation_id)
print_router_response(router_response, "job_info", conversation_id, gateway_uid=gateway_context.gateway_uid)
31 changes: 26 additions & 5 deletions keepercommander/commands/discoveryrotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@
from .pam_debug.graph import PAMDebugGraphCommand
from .pam_debug.info import PAMDebugInfoCommand
from .pam_debug.gateway import PAMDebugGatewayCommand
from .pam_service.list import PAMActionServiceListCommand
from .pam_service.add import PAMActionServiceAddCommand
from .pam_service.remove import PAMActionServiceRemoveCommand


def register_commands(commands):
Expand Down Expand Up @@ -168,6 +171,19 @@ def __init__(self):
self.default_verb = 'list'


class PAMActionServiceCommand(GroupCommand):

def __init__(self):
super(PAMActionServiceCommand, self).__init__()
self.register_command('list', PAMActionServiceListCommand(),
'List all mappings', 'l')
self.register_command('add', PAMActionServiceAddCommand(),
'Add a user and machine to the mapping', 'a')
self.register_command('remove', PAMActionServiceRemoveCommand(),
'Remove a user and machine from the mapping', 'r')
self.default_verb = 'list'


class GatewayActionCommand(GroupCommand):

def __init__(self):
Expand All @@ -177,6 +193,8 @@ def __init__(self):
self.register_command('rotate', PAMGatewayActionRotateCommand(), 'Rotate command', 'r')
self.register_command('job-info', PAMGatewayActionJobCommand(), 'View Job details', 'ji')
self.register_command('job-cancel', PAMGatewayActionJobCommand(), 'View Job details', 'jc')
self.register_command('service', PAMActionServiceCommand(),
'Manage services and scheduled tasks user mappings.', 's')
self.register_command('debug', PAMDebugCommand(), 'PAM debug information')

# self.register_command('job-list', DRCmdListJobs(), 'List Running jobs')
Expand Down Expand Up @@ -255,6 +273,8 @@ class PAMCreateRecordRotationCommand(Command):
'5:56PM UTC enter following cron -sc "56 17 * * *"')
schedule_group.add_argument('--on-demand', '-od', required=False, dest='on_demand',
action='store_true', help='Schedule On Demand')
schedule_group.add_argument('--schedule-config', '-sf', required=False, dest='schedule_config',
action='store_true', help='Schedule from Configuration')
parser.add_argument('--complexity', '-x', required=False, dest='pwd_complexity', action='store',
help='Password complexity: length, upper, lower, digits, symbols. Ex. 32,5,5,5,5')
parser.add_argument('--admin-user', '-a', required=False, dest='admin', action='store',
Expand Down Expand Up @@ -359,6 +379,7 @@ def add_folders(sub_folder): # type: (BaseFolderNode) -> None
schedule_json_data = kwargs.get('schedule_json_data')
schedule_cron_data = kwargs.get('schedule_cron_data') # See this page for more details: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html#examples
schedule_on_demand = kwargs.get('on_demand') is True
schedule_config = kwargs.get('schedule_config') is True
schedule_data = None # type: Optional[List]
if isinstance(schedule_json_data, list):
schedule_data = [json.loads(x) for x in schedule_json_data]
Expand Down Expand Up @@ -482,7 +503,7 @@ def config_iam_aad_user(_dag, target_record, target_iam_aad_config_uid):
# 2. Schedule
record_schedule_data = schedule_data
if record_schedule_data is None:
if current_record_rotation:
if current_record_rotation and not schedule_config:
try:
current_schedule = current_record_rotation.get('schedule')
if current_schedule:
Expand Down Expand Up @@ -1975,7 +1996,7 @@ def execute(self, params, **kwargs):
message_type=pam_pb2.CMT_GENERAL,
is_streaming=False
)
print_router_response(router_response, conversation_id)
print_router_response(router_response, 'job_info', conversation_id)


class PAMGatewayActionJobCommand(Command):
Expand Down Expand Up @@ -2006,7 +2027,7 @@ def execute(self, params, **kwargs):
destination_gateway_uid_str=gateway_uid
)

print_router_response(router_response, original_conversation_id=conversation_id, response_type='job_info')
print_router_response(router_response, 'job_info', original_conversation_id=conversation_id, gateway_uid=gateway_uid)


class PAMGatewayActionRotateCommand(Command):
Expand Down Expand Up @@ -2134,7 +2155,7 @@ def execute(self, params, **kwargs):
encrypted_transmission_key=encrypted_transmission_key,
encrypted_session_token=encrypted_session_token)

print_router_response(router_response, 'job_info', conversation_id)
print_router_response(router_response, 'job_info', conversation_id, gateway_uid=gateway_uid)


class PAMGatewayActionServerInfoCommand(Command):
Expand All @@ -2156,7 +2177,7 @@ def execute(self, params, **kwargs):
destination_gateway_uid_str=destination_gateway_uid_str
)

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


class PAMGatewayActionDiscoverCommandBase(Command):
Expand Down
12 changes: 7 additions & 5 deletions keepercommander/commands/discoveryrotation_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ class PAMCreateRecordRotationCommand(Command):
schedule_group.add_argument('--schedulejson', '-sj', required=False, dest='schedule_json_data', action='append', help='Json of the scheduler. Example: -sj \'{"type": "WEEKLY", "utcTime": "15:44", "weekday": "SUNDAY", "intervalCount": 1}\'')
schedule_group.add_argument('--schedulecron', '-sc', required=False, dest='schedule_cron_data', action='append', help='Cron tab string of the scheduler. Example: to run job daily at 5:56PM UTC enter following cron -sc "56 17 * * *"')
schedule_group.add_argument('--on-demand', '-sm', required=False, dest='on_demand', action='store_true', help='Schedule On Demand')
schedule_group.add_argument('--schedule-config', '-sf', required=False, dest='schedule_config', action='store_true', help='Schedule from Configuration')
parser.add_argument('--complexity', '-x', required=False, dest='pwd_complexity', action='store', help='Password complexity: length, upper, lower, digits, symbols. Ex. 32,5,5,5,5')
state_group = parser.add_mutually_exclusive_group()
state_group.add_argument('--enable', dest='enable', action='store_true', help='Enable rotation')
Expand Down Expand Up @@ -245,6 +246,7 @@ def add_folders(sub_folder): # type: (BaseFolderNode) -> None
schedule_json_data = kwargs.get('schedule_json_data')
schedule_cron_data = kwargs.get('schedule_cron_data') # See this page for more details: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html#examples
schedule_on_demand = kwargs.get('on_demand') is True
schedule_config = kwargs.get('schedule_config') is True
schedule_data = None # type: Optional[List]
if isinstance(schedule_json_data, list):
schedule_data = [json.loads(x) for x in schedule_json_data]
Expand Down Expand Up @@ -320,7 +322,7 @@ def add_folders(sub_folder): # type: (BaseFolderNode) -> None
# 2. Schedule
record_schedule_data = schedule_data
if record_schedule_data is None:
if current_record_rotation:
if current_record_rotation and not schedule_config:
try:
current_schedule = current_record_rotation.get('schedule')
if current_schedule:
Expand Down Expand Up @@ -1497,7 +1499,7 @@ def execute(self, params, **kwargs):
message_type=pam_pb2.CMT_GENERAL,
is_streaming=False
)
print_router_response(router_response, conversation_id)
print_router_response(router_response, 'job_info', conversation_id)


class PAMGatewayActionJobCommand(Command):
Expand Down Expand Up @@ -1529,7 +1531,7 @@ def execute(self, params, **kwargs):
destination_gateway_uid_str=gateway_uid
)

print_router_response(router_response, original_conversation_id=conversation_id, response_type='job_info')
print_router_response(router_response, 'job_info', original_conversation_id=conversation_id, gateway_uid=gateway_uid)


class PAMGatewayActionRotateCommand(Command):
Expand Down Expand Up @@ -1629,7 +1631,7 @@ def execute(self, params, **kwargs):
gateway_destination=facade.controller_uid),
message_type=pam_pb2.CMT_ROTATE, is_streaming=False)

print_router_response(router_response, conversation_id)
print_router_response(router_response, 'job_info', conversation_id, gateway_uid=facade.controller_uid)


class PAMGatewayActionServerInfoCommand(Command):
Expand All @@ -1651,7 +1653,7 @@ def execute(self, params, **kwargs):
destination_gateway_uid_str=destination_gateway_uid_str
)

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


class PAMGatewayRemoveCommand(Command):
Expand Down
5 changes: 3 additions & 2 deletions keepercommander/commands/pam/router_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ def get_dag_leafs(params, encrypted_session_token, encrypted_transmission_key, r
return None


def print_router_response(router_response, response_type, original_conversation_id=None, is_verbose=False):
def print_router_response(router_response, response_type, original_conversation_id=None, is_verbose=False, gateway_uid=''):
if not router_response:
return

Expand Down Expand Up @@ -463,8 +463,9 @@ def print_router_response(router_response, response_type, original_conversation_
if router_response_response_payload_dict.get('isScheduled') or router_response_response_payload_dict.get('is_scheduled'):
conversation_id = router_response_response_payload_dict.get('conversation_id')

gwinfo = f" --gateway={gateway_uid}" if gateway_uid else ""
print(f"Scheduled action id: {bcolors.OKBLUE}{conversation_id}{bcolors.ENDC}")
print(f"The action has been scheduled, use command '{bcolors.OKGREEN}pam action job-info {conversation_id}{bcolors.ENDC}' to get status of the scheduled action")
print(f"The action has been scheduled, use command '{bcolors.OKGREEN}pam action job-info {conversation_id}{gwinfo}{bcolors.ENDC}' to get status of the scheduled action")
return

elif response_type == 'job_info':
Expand Down
17 changes: 17 additions & 0 deletions keepercommander/commands/pam_service/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from __future__ import annotations
from ...utils import value_to_boolean
import os
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from ...params import KeeperParams
from keepercommander.keeper_dag.connection import ConnectionBase


def get_connection(params: KeeperParams) -> ConnectionBase:
if value_to_boolean(os.environ.get("USE_LOCAL_DAG", False)) is False:
from keepercommander.keeper_dag.connection.commander import Connection as CommanderConnection
return CommanderConnection(params=params)
else:
from keepercommander.keeper_dag.connection.local import Connection as LocalConnection
return LocalConnection()
166 changes: 166 additions & 0 deletions keepercommander/commands/pam_service/add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
from __future__ import annotations
import argparse
from ..discover import PAMGatewayActionDiscoverCommandBase, GatewayContext
from ...display import bcolors
from ... import vault
from keepercommander.discovery_common.user_service import UserService
from keepercommander.discovery_common.record_link import RecordLink
from keepercommander.discovery_common.constants import PAM_USER, PAM_MACHINE
from keepercommander.discovery_common.types import ServiceAcl
from keepercommander.keeper_dag.types import RefType, EdgeType
from typing import Optional, TYPE_CHECKING

if TYPE_CHECKING:
from ...vault import TypedRecord
from ...params import KeeperParams


class PAMActionServiceAddCommand(PAMGatewayActionDiscoverCommandBase):
parser = argparse.ArgumentParser(prog='pam-action-service-add')

# The record to base everything on.
parser.add_argument('--gateway', '-g', required=True, dest='gateway', action='store',
help='Gateway name or UID')

parser.add_argument('--machine-uid', '-m', required=True, dest='machine_uid', action='store',
help='The UID of the Windows Machine record')
parser.add_argument('--user-uid', '-u', required=True, dest='user_uid', action='store',
help='The UID of the User record')
parser.add_argument('--type', '-t', required=True, choices=['service', 'task'], dest='type',
action='store', help='Relationship to add [service, task]')

def get_parser(self):
return PAMActionServiceAddCommand.parser

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

gateway = kwargs.get("gateway")
machine_uid = kwargs.get("machine_uid")
user_uid = kwargs.get("user_uid")
rel_type = kwargs.get("type")

print("")

gateway_context = GatewayContext.from_gateway(params, gateway)
if gateway_context is None:
print(f"{bcolors.FAIL}Could not find the gateway configuration for {gateway}.")
return

if gateway_context is None:
print(f" {self._f('Cannot get gateway information. Gateway may not be up.')}")
return

user_service = UserService(record=gateway_context.configuration, params=params, fail_on_corrupt=False)
record_link = RecordLink(record=gateway_context.configuration, params=params, fail_on_corrupt=False)

###############

# Check to see if the record exists.
machine_record = vault.KeeperRecord.load(params, machine_uid) # type: Optional[TypedRecord]
if machine_record is None:
print(self._f("The machine record does not exists."))
return

# Make sure the record is a PAM Machine.
if machine_record.record_type != PAM_MACHINE:
print(self._f("The machine record is not a PAM Machine."))
return

# Make sure this machine is linked to the configuration record.
machine_rl = record_link.get_record_link(machine_record.record_uid)
if machine_rl.get_edge(record_link.dag.get_root, edge_type=EdgeType.LINK) is None:
print(self._f("The machine record does not belong to this gateway."))
return

###############

# Check to see if the record exists.
user_record = vault.KeeperRecord.load(params, user_uid) # type: Optional[TypedRecord]
if user_record is None:
print(self._f("The user record does not exists."))
return

# Make sure this user is a PAM User.
if user_record.record_type != PAM_USER:
print(self._f("The user record is not a PAM User."))
return

record_rotation = params.record_rotation_cache.get(user_record.record_uid)
if record_rotation is not None:
controller_uid = record_rotation.get("configuration_uid")
if controller_uid is None or controller_uid != gateway_context.configuration_uid:
print(self._f("The user record does not belong to this gateway. Cannot use this user."))
return
else:
print(self._f("The user record does not have any rotation settings."))
return

########

# Make sure we are setting up a Windows machine.
# Linux and Mac do not use passwords in services and cron jobs; no need to link.
os_field = next((x for x in machine_record.fields if x.label == "operatingSystem"), None)
if os_field is None:
print(self._f("Cannot find the operating system field in this record."))
return
os_type = None
if len(os_field.value) > 0:
os_type = os_field.value[0]
if os_type is None:
print(self._f("The operating system field of the machine record is blank."))
return
if os_type != "windows":
print(self._f("The operating system is not Windows. "
"PAM can only rotate the services and scheduled task password on Windows."))
return

# Get the machine service vertex.
# If it doesn't exist, create one.
machine_vertex = user_service.get_record_link(machine_record.record_uid)
if machine_vertex is None:
machine_vertex = user_service.dag.add_vertex(
uid=machine_record.record_uid,
name=machine_record.title,
vertex_type=RefType.PAM_MACHINE)

# Get the user service vertex.
# If it doesn't exist, create one.
user_vertex = user_service.get_record_link(user_record.record_uid)
if user_vertex is None:
user_vertex = user_service.dag.add_vertex(
uid=user_record.record_uid,
name=user_record.title,
vertex_type=RefType.PAM_USER)

# Get the existing service ACL and set the proper attribute.
acl = user_service.get_acl(machine_vertex.uid, user_vertex.uid)
if acl is None:
acl = ServiceAcl()
if rel_type == "service":
acl.is_service = True
else:
acl.is_task = True

# Make sure the machine has a LINK connection to the configuration.
if user_service.dag.get_root.has(machine_vertex) is False:
user_service.belongs_to(gateway_context.configuration_uid, machine_vertex.uid)

# Add our new ACL edge between the machine and the yser.
user_service.belongs_to(machine_vertex.uid, user_vertex.uid, acl=acl)

user_service.save()

if rel_type == "service":
print(
self._gr(
f"Success: Services running on this machine, with this user, will be updated and restarted after "
"password rotation."
)
)
else:
print(
self._gr(
f"Success: Scheduled tasks running on this machine, with this user, will be updated after "
"password rotation."
)
)
Loading
Loading