-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1367 from Keeper-Security/DR-892_service_task_use…
…r_management DR-892 Add commands to control the user service/task mapping
- Loading branch information
Showing
5 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
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') | ||
parser.add_argument('--user-uid', '-u', required=True, dest='user_uid', action='store', | ||
help='The UID of the User') | ||
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.")) | ||
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.")) | ||
|
||
# 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) | ||
|
||
text_type = "service" | ||
if rel_type == "task": | ||
text_type = "scheduled task" | ||
|
||
user_service.save() | ||
|
||
print( | ||
self._gr( | ||
f"{user_record.title} is connected to {machine_record.title}. " | ||
f"The user, and password, is used on this machine for a {text_type}." | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
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.constants import PAM_MACHINE | ||
from keepercommander.keeper_dag import EdgeType | ||
from typing import Optional, TYPE_CHECKING | ||
|
||
if TYPE_CHECKING: | ||
from ...vault import TypedRecord | ||
from ...params import KeeperParams | ||
|
||
|
||
class PAMActionServiceListCommand(PAMGatewayActionDiscoverCommandBase): | ||
parser = argparse.ArgumentParser(prog='pam-action-service-list') | ||
|
||
# The record to base everything on. | ||
parser.add_argument('--gateway', '-g', required=True, dest='gateway', action='store', | ||
help='Gateway name or UID') | ||
|
||
def get_parser(self): | ||
return PAMActionServiceListCommand.parser | ||
|
||
def execute(self, params: KeeperParams, **kwargs): | ||
|
||
gateway = kwargs.get("gateway") | ||
|
||
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) | ||
|
||
service_map = {} | ||
for resource_vertex in user_service.dag.get_root.has_vertices(edge_type=EdgeType.LINK): | ||
resource_record = vault.KeeperRecord.load(params, resource_vertex.uid) # type: Optional[TypedRecord] | ||
if resource_record is None or resource_record.record_type != PAM_MACHINE: | ||
continue | ||
user_vertices = user_service.get_user_vertices(resource_vertex.uid) | ||
if len(user_vertices) > 0: | ||
for user_vertex in user_vertices: | ||
user_record = vault.KeeperRecord.load(params, user_vertex.uid) # type: Optional[TypedRecord] | ||
if user_record is None: | ||
continue | ||
acl = user_service.get_acl(resource_record.record_uid, user_record.record_uid) | ||
if acl is None or (acl.is_service is False and acl.is_task is False): | ||
continue | ||
if user_record.record_uid not in service_map: | ||
service_map[user_record.record_uid] = { | ||
"title": user_record.title, | ||
"machines": [] | ||
} | ||
text = f"{resource_record.title} ({resource_record.record_uid}) :" | ||
if acl.is_service is True: | ||
text += f" {bcolors.OKGREEN}Services{bcolors.ENDC}" | ||
if acl.is_task is True: | ||
text += f" {bcolors.OKBLUE}Scheduled Tasks{bcolors.ENDC}" | ||
service_map[user_record.record_uid]["machines"].append(text) | ||
|
||
print("") | ||
print(self._h("User Mapping")) | ||
for user_uid in service_map: | ||
user = service_map[user_uid] | ||
print(f" {self._b(user['title'])} ({user_uid})") | ||
for machine in user["machines"]: | ||
print(f" * {machine}") | ||
print("") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
from __future__ import annotations | ||
import argparse | ||
from ..discover import PAMGatewayActionDiscoverCommandBase, GatewayContext | ||
from ...display import bcolors | ||
from ... import vault | ||
from keepercommander.discovery_common.constants import PAM_USER, PAM_MACHINE | ||
from keepercommander.discovery_common.user_service import UserService | ||
from typing import Optional, TYPE_CHECKING | ||
|
||
if TYPE_CHECKING: | ||
from ...vault import TypedRecord | ||
from ...params import KeeperParams | ||
|
||
|
||
class PAMActionServiceRemoveCommand(PAMGatewayActionDiscoverCommandBase): | ||
parser = argparse.ArgumentParser(prog='pam-action-service-remove') | ||
|
||
# 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') | ||
parser.add_argument('--user-uid', '-u', required=True, dest='user_uid', action='store', | ||
help='The UID of the User') | ||
parser.add_argument('--type', '-t', required=True, choices=['service', 'task'], dest='type', | ||
action='store', help='Relationship to remove [service, task]') | ||
|
||
def get_parser(self): | ||
return PAMActionServiceRemoveCommand.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) | ||
|
||
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 | ||
|
||
if machine_record.record_type != PAM_MACHINE: | ||
print(self._f("The machine record is not a PAM Machine.")) | ||
return | ||
|
||
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 | ||
|
||
if user_record.record_type != PAM_USER: | ||
print(self._f("The user record is not a PAM User.")) | ||
return | ||
|
||
machine_vertex = user_service.get_record_link(machine_record.record_uid) | ||
if machine_vertex is None: | ||
print(self._f(f"The machine {machine_record.title} does not exist in the mapping.")) | ||
|
||
user_vertex = user_service.get_record_link(user_record.record_uid) | ||
if user_vertex is None: | ||
print(self._f(f"The user {user_record.title} does not exist in the mapping.")) | ||
|
||
acl = user_service.get_acl(machine_vertex.uid, user_vertex.uid) | ||
if acl is None: | ||
print(f"{bcolors.WARNING}The user {user_record.title} did not control any services or " | ||
f"scheduled tasks on {machine_record.title}{bcolors.ENDC}") | ||
return | ||
|
||
if rel_type == "service": | ||
acl.is_service = False | ||
else: | ||
acl.is_task = False | ||
|
||
if user_service.dag.get_root.has(machine_vertex) is False: | ||
user_service.belongs_to(gateway_context.configuration_uid, machine_vertex.uid) | ||
|
||
user_service.belongs_to(machine_vertex.uid, user_vertex.uid, acl=acl) | ||
|
||
text_type = "services" | ||
if rel_type == "task": | ||
text_type = "scheduled tasks" | ||
|
||
user_service.save() | ||
|
||
print( | ||
self._gr( | ||
f"{user_record.title} was removed from {machine_record.title} for {text_type}." | ||
) | ||
) |