1298 lines
47 KiB
Python
1298 lines
47 KiB
Python
#!/usr/bin/python
|
|
# Copyright: (c) 2017, Ansible Project
|
|
# GNU General Public License v3.0+
|
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
from __future__ import (absolute_import, division, print_function)
|
|
|
|
|
|
__metaclass__ = type
|
|
|
|
ANSIBLE_METADATA = {
|
|
"metadata_version": "1.1",
|
|
"status": ["preview"],
|
|
"supported_by": "community",
|
|
}
|
|
|
|
DOCUMENTATION = """
|
|
---
|
|
module: cyberark_account
|
|
short_description: Module for CyberArk Account object creation, deletion, and
|
|
modification using PAS Web Services SDK.
|
|
author:
|
|
- CyberArk BizDev (@cyberark-bizdev)
|
|
- Edward Nunez (@enunez-cyberark)
|
|
- James Stutes (@jimmyjamcabd)
|
|
version_added: 2.4
|
|
description:
|
|
- Creates a URI for adding, deleting, modifying a privileged credential
|
|
within the Cyberark Vault. The request uses the Privileged Account
|
|
Security Web Services SDK.
|
|
|
|
|
|
options:
|
|
state:
|
|
description:
|
|
- Assert the desired state of the account C(present) to creat or
|
|
update and account object. Set to C(absent) for deletion of an
|
|
account object.
|
|
required: true
|
|
default: present
|
|
choices: [present, absent]
|
|
type: str
|
|
logging_level:
|
|
description:
|
|
- Parameter used to define the level of troubleshooting output to
|
|
the C(logging_file) value.
|
|
required: true
|
|
choices: [NOTSET, DEBUG, INFO]
|
|
type: str
|
|
logging_file:
|
|
description:
|
|
- Setting the log file name and location for troubleshooting logs.
|
|
required: false
|
|
default: /tmp/ansible_cyberark.log
|
|
type: str
|
|
api_base_url:
|
|
description:
|
|
- A string containing the base URL of the server hosting CyberArk's
|
|
Privileged Account Security Web Services SDK.
|
|
- Example U(https://<IIS_Server_Ip>/PasswordVault/api/)
|
|
required: true
|
|
type: str
|
|
validate_certs:
|
|
description:
|
|
- If C(false), SSL certificate chain will not be validated. This
|
|
should only set to C(true) if you have a root CA certificate
|
|
installed on each node.
|
|
required: false
|
|
default: true
|
|
type: bool
|
|
cyberark_session:
|
|
description:
|
|
- Dictionary set by a CyberArk authentication containing the
|
|
different values to perform actions on a logged-on CyberArk
|
|
session, please see M(cyberark_authentication) module for an
|
|
example of cyberark_session.
|
|
required: true
|
|
type: dict
|
|
identified_by:
|
|
description:
|
|
- When an API call is made to Get Accounts, often times the default
|
|
parameters passed will identify more than one account. This
|
|
parameter is used to confidently identify a single account when
|
|
the default query can return multiple results.
|
|
required: false
|
|
default: username,address,platform_id
|
|
type: str
|
|
safe:
|
|
description:
|
|
- The safe in the Vault where the privileged account is to be
|
|
located.
|
|
required: true
|
|
type: str
|
|
platform_id:
|
|
description:
|
|
- The PolicyID of the Platform that is to be managing the account
|
|
required: false
|
|
type: str
|
|
address:
|
|
description:
|
|
- The address of the endpoint where the privileged account is
|
|
located.
|
|
required: false
|
|
type: str
|
|
name:
|
|
description:
|
|
- The ObjectID of the account
|
|
required: false
|
|
type: str
|
|
secret_type:
|
|
description:
|
|
- The value that identifies what type of account it will be.
|
|
required: false
|
|
default: password
|
|
choices: [password, key]
|
|
type: str
|
|
secret:
|
|
description:
|
|
- The initial password for the creation of the account
|
|
required: false
|
|
type: str
|
|
new_secret:
|
|
description:
|
|
- The new secret/password to be stored in CyberArk Vault.
|
|
type: str
|
|
username:
|
|
description:
|
|
- The username associated with the account.
|
|
required: false
|
|
type: str
|
|
secret_management:
|
|
description:
|
|
- Set of parameters associated with the management of the
|
|
credential.
|
|
required: false
|
|
type: dict
|
|
suboptions:
|
|
automatic_management_enabled:
|
|
description:
|
|
- Parameter that indicates whether the CPM will manage
|
|
the password or not.
|
|
default: False
|
|
type: bool
|
|
manual_management_reason:
|
|
description:
|
|
- String value indicating why the CPM will NOT manage
|
|
the password.
|
|
type: str
|
|
management_action:
|
|
description:
|
|
- CPM action flag to be placed on the account object
|
|
for credential rotation.
|
|
choices: [change, change_immediately, reconcile]
|
|
type: str
|
|
new_secret:
|
|
description:
|
|
- The actual password value that will be assigned for
|
|
the CPM action to be taken.
|
|
type: str
|
|
perform_management_action:
|
|
description:
|
|
- C(always) will perform the management action in
|
|
every action.
|
|
- C(on_create) will only perform the management action
|
|
right after the account is created.
|
|
choices: [always, on_create]
|
|
default: always
|
|
type: str
|
|
remote_machines_access:
|
|
description:
|
|
- Set of parameters for defining PSM endpoint access targets.
|
|
required: false
|
|
type: dict
|
|
suboptions:
|
|
remote_machines:
|
|
description:
|
|
- List of targets allowed for this account.
|
|
type: str
|
|
access_restricted_to_remote_machines:
|
|
description:
|
|
- Whether or not to restrict access only to specified
|
|
remote machines.
|
|
type: bool
|
|
platform_account_properties:
|
|
description:
|
|
- Object containing key-value pairs to associate with the account,
|
|
as defined by the account platform. These properties are
|
|
validated against the mandatory and optional properties of the
|
|
specified platform's definition. Optional properties that do not
|
|
exist on the account will not be returned here. Internal
|
|
properties are not returned.
|
|
required: false
|
|
type: dict
|
|
suboptions:
|
|
KEY:
|
|
description:
|
|
- Freeform key value associated to the mandatory or
|
|
optional property assigned to the specified
|
|
Platform's definition.
|
|
aliases: [Port, ExtrPass1Name, database]
|
|
type: str
|
|
"""
|
|
|
|
EXAMPLES = """
|
|
collections:
|
|
- cyberark.pas
|
|
|
|
tasks:
|
|
|
|
- name: Logon to CyberArk Vault using PAS Web Services SDK
|
|
cyberark_authentication:
|
|
api_base_url: "http://components.cyberark.local"
|
|
validate_certs: no
|
|
username: "bizdev"
|
|
password: "Cyberark1"
|
|
|
|
- name: Creating an Account using the PAS WebServices SDK
|
|
cyberark_account:
|
|
logging_level: DEBUG
|
|
identified_by: "address,username"
|
|
safe: "Test"
|
|
address: "cyberark.local"
|
|
username: "administrator-x"
|
|
platform_id: WinServerLocal
|
|
secret: "@N&Ibl3!"
|
|
platform_account_properties:
|
|
LogonDomain: "cyberark"
|
|
OwnerName: "ansible_user"
|
|
secret_management:
|
|
automatic_management_enabled: true
|
|
state: present
|
|
cyberark_session: "{{ cyberark_session }}"
|
|
register: cyberarkaction
|
|
|
|
- name:
|
|
- Rotate credential via reconcile and providing the password to
|
|
bechanged to.
|
|
cyberark_account:
|
|
identified_by: "address,username"
|
|
safe: "Domain_Admins"
|
|
address: "prod.cyberark.local"
|
|
username: "admin"
|
|
platform_id: WinDomain
|
|
platform_account_properties:
|
|
LogonDomain: "PROD"
|
|
secret_management:
|
|
new_secret: "Ama123ah12@#!Xaamdjbdkl@#112"
|
|
management_action: "reconcile"
|
|
automatic_management_enabled: true
|
|
state: present
|
|
cyberark_session: "{{ cyberark_session }}"
|
|
register: reconcileaccount
|
|
|
|
- name: Logoff from CyberArk Vault
|
|
cyberark_authentication:
|
|
state: absent
|
|
cyberark_session: "{{ cyberark_session }}"
|
|
|
|
"""
|
|
RETURN = """
|
|
changed:
|
|
description:
|
|
- Identify if the playbook run resulted in a change to the account in
|
|
any way.
|
|
returned: always
|
|
type: bool
|
|
failed:
|
|
description: Whether playbook run resulted in a failure of any kind.
|
|
returned: always
|
|
type: bool
|
|
status_code:
|
|
description: Result HTTP Status code.
|
|
returned: success
|
|
type: int
|
|
sample: "200, 201, -1, 204"
|
|
result:
|
|
description: A json dump of the resulting action.
|
|
returned: success
|
|
type: complex
|
|
contains:
|
|
address:
|
|
description:
|
|
- The adress of the endpoint where the privileged account is
|
|
located.
|
|
returned: successful addition and modification
|
|
type: str
|
|
sample: dev.local
|
|
createdTime:
|
|
description:
|
|
- Timeframe calculation of the timestamp of account creation.
|
|
returned: successful addition and modification
|
|
type: int
|
|
sample: "1567824520"
|
|
id:
|
|
description: Internal ObjectID for the account object identified
|
|
returned: successful addition and modification
|
|
type: int
|
|
sample: "25_21"
|
|
name:
|
|
description: The external ObjectID of the account
|
|
returned: successful addition and modification
|
|
type: str
|
|
sample:
|
|
- Operating System-WinServerLocal-cyberark.local-administrator
|
|
platformAccountProperties:
|
|
description:
|
|
- Object containing key-value pairs to associate with the
|
|
account, as defined by the account platform.
|
|
returned: successful addition and modification
|
|
type: complex
|
|
contains:
|
|
KEY VALUE:
|
|
description:
|
|
- Object containing key-value pairs to associate with the
|
|
account, as defined by the account platform.
|
|
returned: successful addition and modification
|
|
type: str
|
|
sample:
|
|
- "LogonDomain": "cyberark"
|
|
- "Port": "22"
|
|
platformId:
|
|
description:
|
|
- The PolicyID of the Platform that is to be managing the
|
|
account.
|
|
returned: successful addition and modification
|
|
type: str
|
|
sample: WinServerLocal
|
|
safeName:
|
|
description:
|
|
- The safe in the Vault where the privileged account is to
|
|
be located.
|
|
returned: successful addition and modification
|
|
type: str
|
|
sample: Domain_Admins
|
|
secretManagement:
|
|
description:
|
|
- Set of parameters associated with the management of
|
|
the credential.
|
|
returned: successful addition and modification
|
|
type: complex
|
|
sample:
|
|
automaticManagementEnabled:
|
|
description:
|
|
- Parameter that indicates whether the CPM will manage
|
|
the password or not.
|
|
returned: successful addition and modification
|
|
type: bool
|
|
lastModifiedTime:
|
|
description:
|
|
- Timeframe calculation of the timestamp of account
|
|
modification.
|
|
returned: successful addition and modification
|
|
type: int
|
|
sample: "1567824520"
|
|
manualManagementReason:
|
|
description:
|
|
returned: if C(automaticManagementEnabled) is set to false
|
|
type: str
|
|
sample: This is a static account
|
|
secretType:
|
|
description:
|
|
- The value that identifies what type of account it will be
|
|
returned: successful addition and modification
|
|
type: list
|
|
sample:
|
|
- key
|
|
- password
|
|
userName:
|
|
description: The username associated with the account
|
|
returned: successful addition and modification
|
|
type: str
|
|
sample: administrator
|
|
"""
|
|
|
|
|
|
from ansible.module_utils._text import to_text
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.urls import open_url
|
|
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
|
from ansible.module_utils.six.moves.urllib.parse import quote
|
|
from ansible.module_utils.six.moves.http_client import HTTPException
|
|
import json
|
|
import logging
|
|
|
|
_empty = object()
|
|
|
|
ansible_specific_parameters = [
|
|
"state",
|
|
"api_base_url",
|
|
"validate_certs",
|
|
"cyberark_session",
|
|
"identified_by",
|
|
"logging_level",
|
|
"logging_file",
|
|
"new_secret",
|
|
"secret_management.management_action",
|
|
"secret_management.new_secret",
|
|
"management_action",
|
|
"secret_management.perform_management_action",
|
|
]
|
|
|
|
cyberark_fixed_properties = [
|
|
"createdTime",
|
|
"id",
|
|
"name",
|
|
"lastModifiedTime",
|
|
"safeName",
|
|
"secretType",
|
|
"secret",
|
|
]
|
|
|
|
removal_value = "NO_VALUE"
|
|
|
|
cyberark_reference_fieldnames = {
|
|
"username": "userName",
|
|
"safe": "safeName",
|
|
"platform_id": "platformId",
|
|
"secret_type": "secretType",
|
|
"platform_account_properties": "platformAccountProperties",
|
|
"secret_management": "secretManagement",
|
|
"manual_management_reason": "manualManagementReason",
|
|
"automatic_management_enabled": "automaticManagementEnabled",
|
|
"remote_machines_access": "remoteMachinesAccess",
|
|
"access_restricted_to_remote_machines": "accessRestrictedToRemoteMachines",
|
|
"remote_machines": "remoteMachines",
|
|
}
|
|
|
|
ansible_reference_fieldnames = {
|
|
"userName": "username",
|
|
"safeName": "safe",
|
|
"platformId": "platform_id",
|
|
"secretType": "secret_type",
|
|
"platformAccountProperties": "platform_account_properties",
|
|
"secretManagement": "secret_management",
|
|
"manualManagementReason": "manual_management_reason",
|
|
"automaticManagementEnabled": "automatic_management_enabled",
|
|
"remoteMachinesAccess": "remote_machines_access",
|
|
"accessRestrictedToRemoteMachines": "access_testricted_to_remoteMachines",
|
|
"remoteMachines": "remote_machines",
|
|
}
|
|
|
|
|
|
def equal_value(existing, parameter):
|
|
if isinstance(existing, str):
|
|
return existing == str(parameter)
|
|
elif isinstance(parameter, str):
|
|
return str(existing) == parameter
|
|
else:
|
|
return existing == parameter
|
|
|
|
|
|
def update_account(module, existing_account):
|
|
|
|
logging.debug("Updating Account")
|
|
|
|
cyberark_session = module.params["cyberark_session"]
|
|
api_base_url = cyberark_session["api_base_url"]
|
|
validate_certs = cyberark_session["validate_certs"]
|
|
|
|
# Prepare result, end_point, and headers
|
|
result = {"result": existing_account}
|
|
changed = False
|
|
last_status_code = -1
|
|
|
|
HTTPMethod = "PATCH"
|
|
end_point = "/PasswordVault/api/Accounts/%s" % existing_account["id"]
|
|
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"Authorization": cyberark_session["token"],
|
|
}
|
|
|
|
payload = {"Operations": []}
|
|
|
|
# Determining whether to add or update properties
|
|
for parameter_name in list(module.params.keys()):
|
|
if (
|
|
parameter_name not in ansible_specific_parameters
|
|
and module.params[parameter_name] is not None
|
|
):
|
|
module_parm_value = module.params[parameter_name]
|
|
cyberark_property_name = referenced_value(
|
|
parameter_name, cyberark_reference_fieldnames, default=parameter_name
|
|
)
|
|
existing_account_value = referenced_value(
|
|
cyberark_property_name,
|
|
existing_account,
|
|
keys=list(existing_account.keys()),
|
|
)
|
|
if cyberark_property_name not in cyberark_fixed_properties:
|
|
if module_parm_value is not None and isinstance(
|
|
module_parm_value, dict
|
|
):
|
|
# Internal child values
|
|
replacing = {}
|
|
adding = {}
|
|
removing = {}
|
|
for child_parm_name in list(module_parm_value.keys()):
|
|
nested_parm_name = "%s.%s" % (parameter_name, child_parm_name)
|
|
if nested_parm_name not in ansible_specific_parameters:
|
|
child_module_parm_value = module_parm_value[child_parm_name]
|
|
child_cyberark_property_name = referenced_value(
|
|
child_parm_name,
|
|
cyberark_reference_fieldnames,
|
|
default=child_parm_name,
|
|
)
|
|
child_existing_account_value = referenced_value(
|
|
child_cyberark_property_name,
|
|
existing_account_value,
|
|
list(existing_account_value.keys())
|
|
if existing_account_value is not None
|
|
else {},
|
|
)
|
|
path_value = "/%s/%s" % (
|
|
cyberark_property_name,
|
|
child_cyberark_property_name,
|
|
)
|
|
if child_existing_account_value is not None:
|
|
logging.debug(
|
|
(
|
|
"child_module_parm_value: %s "
|
|
"child_existing_account_value=%s path=%s"
|
|
),
|
|
child_module_parm_value,
|
|
child_existing_account_value,
|
|
path_value
|
|
)
|
|
if child_module_parm_value == removal_value:
|
|
removing.update(
|
|
{
|
|
child_cyberark_property_name: child_existing_account_value
|
|
}
|
|
)
|
|
elif (
|
|
child_module_parm_value is not None
|
|
and not equal_value(
|
|
child_existing_account_value,
|
|
child_module_parm_value,
|
|
)
|
|
):
|
|
# Updating a property
|
|
replacing.update(
|
|
{
|
|
child_cyberark_property_name: child_module_parm_value
|
|
}
|
|
)
|
|
elif (
|
|
child_module_parm_value is not None
|
|
and child_module_parm_value != removal_value
|
|
):
|
|
# Adding a property value
|
|
adding.update(
|
|
{
|
|
child_cyberark_property_name: child_module_parm_value
|
|
}
|
|
)
|
|
logging.debug(
|
|
"parameter_name=%s value=%s existing=%s",
|
|
path_value,
|
|
child_module_parm_value,
|
|
child_existing_account_value
|
|
)
|
|
# Processing child operations
|
|
if len(list(adding.keys())) > 0:
|
|
payload["Operations"].append(
|
|
{
|
|
"op": "add",
|
|
"path": "/%s" % cyberark_property_name,
|
|
"value": adding,
|
|
}
|
|
)
|
|
if len(list(replacing.keys())) > 0:
|
|
payload["Operations"].append(
|
|
{
|
|
"op": "replace",
|
|
"path": "/%s" % cyberark_property_name,
|
|
"value": replacing,
|
|
}
|
|
)
|
|
if len(removing) > 0:
|
|
payload["Operations"].append(
|
|
{
|
|
"op": "remove",
|
|
"path": "/%s" % cyberark_property_name,
|
|
"value": removing,
|
|
}
|
|
)
|
|
else:
|
|
if existing_account_value is not None:
|
|
if module_parm_value == removal_value:
|
|
payload["Operations"].append(
|
|
{"op": "remove", "path": "/%s" % cyberark_property_name}
|
|
)
|
|
elif not equal_value(existing_account_value, module_parm_value):
|
|
# Updating a property
|
|
payload["Operations"].append(
|
|
{
|
|
"op": "replace",
|
|
"value": module_parm_value,
|
|
"path": "/%s" % cyberark_property_name,
|
|
}
|
|
)
|
|
elif module_parm_value != removal_value:
|
|
# Adding a property value
|
|
payload["Operations"].append(
|
|
{
|
|
"op": "add",
|
|
"value": module_parm_value,
|
|
"path": "/%s" % cyberark_property_name,
|
|
}
|
|
)
|
|
logging.debug(
|
|
"parameter_name=%s value=%s existing=%s",
|
|
parameter_name, module_parm_value, existing_account_value
|
|
)
|
|
|
|
if len(payload["Operations"]) != 0:
|
|
if module.check_mode:
|
|
logging.debug("Proceeding with Update Account (CHECK_MODE)")
|
|
logging.debug("Operations => %s", json.dumps(payload))
|
|
result = {"result": existing_account}
|
|
changed = True
|
|
last_status_code = -1
|
|
else:
|
|
logging.debug("Proceeding with Update Account")
|
|
|
|
logging.debug(
|
|
"Processing invidual operations (%d) => %s",
|
|
len(payload["Operations"]),
|
|
json.dumps(payload),
|
|
)
|
|
for operation in payload["Operations"]:
|
|
individual_payload = [operation]
|
|
try:
|
|
logging.debug(" ==> %s", json.dumps([operation]))
|
|
response = open_url(
|
|
api_base_url + end_point,
|
|
method=HTTPMethod,
|
|
headers=headers,
|
|
data=json.dumps(individual_payload),
|
|
validate_certs=validate_certs,
|
|
)
|
|
|
|
result = {"result": json.loads(response.read())}
|
|
changed = True
|
|
last_status_code = response.getcode()
|
|
|
|
# return (True, result, response.getcode())
|
|
|
|
except (HTTPError, HTTPException) as http_exception:
|
|
|
|
if isinstance(http_exception, HTTPError):
|
|
res = json.load(http_exception)
|
|
else:
|
|
res = to_text(http_exception)
|
|
|
|
module.fail_json(
|
|
msg=(
|
|
"Error while performing update_account."
|
|
"Please validate parameters provided."
|
|
"\n*** end_point=%s%s\n ==> %s"
|
|
% (api_base_url, end_point, res)
|
|
),
|
|
payload=individual_payload,
|
|
headers=headers,
|
|
status_code=http_exception.code,
|
|
)
|
|
|
|
except Exception as unknown_exception:
|
|
|
|
module.fail_json(
|
|
msg=(
|
|
"Unknown error while performing update_account."
|
|
"\n*** end_point=%s%s\n%s"
|
|
% (api_base_url, end_point, to_text(unknown_exception))
|
|
),
|
|
payload=individual_payload,
|
|
headers=headers,
|
|
status_code=-1,
|
|
)
|
|
|
|
return (changed, result, last_status_code)
|
|
|
|
|
|
def add_account(module):
|
|
|
|
logging.debug("Adding Account")
|
|
|
|
cyberark_session = module.params["cyberark_session"]
|
|
api_base_url = cyberark_session["api_base_url"]
|
|
validate_certs = cyberark_session["validate_certs"]
|
|
|
|
# Prepare result, end_point, and headers
|
|
result = {}
|
|
HTTPMethod = "POST"
|
|
end_point = "/PasswordVault/api/Accounts"
|
|
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"Authorization": cyberark_session["token"],
|
|
}
|
|
|
|
payload = {"safeName": module.params["safe"]}
|
|
|
|
for parameter_name in list(module.params.keys()):
|
|
if (
|
|
parameter_name not in ansible_specific_parameters
|
|
and module.params[parameter_name] is not None
|
|
):
|
|
cyberark_property_name = referenced_value(
|
|
parameter_name, cyberark_reference_fieldnames, default=parameter_name
|
|
)
|
|
if isinstance(module.params[parameter_name], dict):
|
|
payload[cyberark_property_name] = {}
|
|
for dict_key in list(module.params[parameter_name].keys()):
|
|
cyberark_child_property_name = referenced_value(
|
|
dict_key, cyberark_reference_fieldnames, default=dict_key
|
|
)
|
|
logging.debug(
|
|
(
|
|
"parameter_name =%s.%s cyberark_property_name=%s "
|
|
"cyberark_child_property_name=%s"
|
|
),
|
|
parameter_name,
|
|
dict_key,
|
|
cyberark_property_name,
|
|
cyberark_child_property_name,
|
|
)
|
|
if (
|
|
parameter_name + "." + dict_key
|
|
not in ansible_specific_parameters
|
|
and module.params[parameter_name][dict_key] is not None
|
|
):
|
|
payload[cyberark_property_name][
|
|
cyberark_child_property_name
|
|
] = deep_get(
|
|
module.params[parameter_name], dict_key, _empty, False
|
|
)
|
|
else:
|
|
if parameter_name not in cyberark_reference_fieldnames:
|
|
module_parm_value = deep_get(
|
|
module.params, parameter_name, _empty, False
|
|
)
|
|
if (
|
|
module_parm_value is not None
|
|
and module_parm_value != removal_value
|
|
):
|
|
payload[
|
|
parameter_name
|
|
] = module_parm_value # module.params[parameter_name]
|
|
else:
|
|
module_parm_value = deep_get(
|
|
module.params, parameter_name, _empty, True
|
|
)
|
|
if (
|
|
module_parm_value is not None
|
|
and module_parm_value != removal_value
|
|
):
|
|
payload[
|
|
cyberark_reference_fieldnames[parameter_name]
|
|
] = module_parm_value # module.params[parameter_name]
|
|
logging.debug("parameter_name =%s", parameter_name)
|
|
|
|
logging.debug("Add Account Payload => %s", json.dumps(payload))
|
|
|
|
try:
|
|
|
|
if module.check_mode:
|
|
logging.debug("Proceeding with Add Account (CHECK_MODE)")
|
|
return (True, {"result": None}, -1)
|
|
else:
|
|
logging.debug("Proceeding with Add Account")
|
|
response = open_url(
|
|
api_base_url + end_point,
|
|
method=HTTPMethod,
|
|
headers=headers,
|
|
data=json.dumps(payload),
|
|
validate_certs=validate_certs,
|
|
)
|
|
|
|
result = {"result": json.loads(response.read())}
|
|
|
|
return (True, result, response.getcode())
|
|
|
|
except (HTTPError, HTTPException) as http_exception:
|
|
|
|
if isinstance(http_exception, HTTPError):
|
|
res = json.load(http_exception)
|
|
else:
|
|
res = to_text(http_exception)
|
|
|
|
module.fail_json(
|
|
msg=(
|
|
"Error while performing add_account."
|
|
"Please validate parameters provided."
|
|
"\n*** end_point=%s%s\n ==> %s" % (api_base_url, end_point, res)
|
|
),
|
|
payload=payload,
|
|
headers=headers,
|
|
status_code=http_exception.code,
|
|
)
|
|
|
|
except Exception as unknown_exception:
|
|
|
|
module.fail_json(
|
|
msg=(
|
|
"Unknown error while performing add_account."
|
|
"\n*** end_point=%s%s\n%s"
|
|
% (api_base_url, end_point, to_text(unknown_exception))
|
|
),
|
|
payload=payload,
|
|
headers=headers,
|
|
status_code=-1,
|
|
)
|
|
|
|
|
|
def delete_account(module, existing_account):
|
|
|
|
if module.check_mode:
|
|
logging.debug("Deleting Account (CHECK_MODE)")
|
|
return (True, {"result": None}, -1)
|
|
else:
|
|
logging.debug("Deleting Account")
|
|
|
|
cyberark_session = module.params["cyberark_session"]
|
|
api_base_url = cyberark_session["api_base_url"]
|
|
validate_certs = cyberark_session["validate_certs"]
|
|
|
|
# Prepare result, end_point, and headers
|
|
result = {}
|
|
HTTPMethod = "DELETE"
|
|
end_point = "/PasswordVault/api/Accounts/%s" % existing_account["id"]
|
|
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"Authorization": cyberark_session["token"],
|
|
}
|
|
|
|
try:
|
|
|
|
response = open_url(
|
|
api_base_url + end_point,
|
|
method=HTTPMethod,
|
|
headers=headers,
|
|
validate_certs=validate_certs,
|
|
)
|
|
|
|
result = {"result": None}
|
|
|
|
return (True, result, response.getcode())
|
|
|
|
except (HTTPError, HTTPException) as http_exception:
|
|
|
|
if isinstance(http_exception, HTTPError):
|
|
res = json.load(http_exception)
|
|
else:
|
|
res = to_text(http_exception)
|
|
|
|
module.fail_json(
|
|
msg=(
|
|
"Error while performing delete_account."
|
|
"Please validate parameters provided."
|
|
"\n*** end_point=%s%s\n ==> %s" % (api_base_url, end_point, res)
|
|
),
|
|
headers=headers,
|
|
status_code=http_exception.code,
|
|
)
|
|
|
|
except Exception as unknown_exception:
|
|
|
|
module.fail_json(
|
|
msg=(
|
|
"Unknown error while performing delete_account."
|
|
"\n*** end_point=%s%s\n%s"
|
|
% (api_base_url, end_point, to_text(unknown_exception))
|
|
),
|
|
headers=headers,
|
|
status_code=-1,
|
|
)
|
|
|
|
|
|
def reset_account_if_needed(module, existing_account):
|
|
|
|
cyberark_session = module.params["cyberark_session"]
|
|
api_base_url = cyberark_session["api_base_url"]
|
|
validate_certs = cyberark_session["validate_certs"]
|
|
|
|
# Credential changes
|
|
management_action = deep_get(
|
|
module.params, "secret_management.management_action", "NOT_FOUND", False
|
|
)
|
|
cpm_new_secret = deep_get(
|
|
module.params, "secret_management.new_secret", "NOT_FOUND", False
|
|
)
|
|
logging.debug(
|
|
"management_action: %s cpm_new_secret: %s", management_action, cpm_new_secret
|
|
)
|
|
|
|
# Prepare result, end_point, and headers
|
|
result = {}
|
|
end_point = None
|
|
payload = {}
|
|
existing_account_id = None
|
|
if existing_account is not None:
|
|
existing_account_id = existing_account["id"]
|
|
elif module.check_mode:
|
|
existing_account_id = 9999
|
|
|
|
if (
|
|
management_action == "change"
|
|
and cpm_new_secret is not None
|
|
and cpm_new_secret != "NOT_FOUND"
|
|
):
|
|
logging.debug("CPM change secret for next CPM cycle")
|
|
end_point = (
|
|
"/PasswordVault/API/Accounts/%s/SetNextPassword"
|
|
) % existing_account_id
|
|
payload["ChangeImmediately"] = False
|
|
payload["NewCredentials"] = cpm_new_secret
|
|
elif management_action == "change_immediately" and (
|
|
cpm_new_secret == "NOT_FOUND" or cpm_new_secret is None
|
|
):
|
|
logging.debug("CPM change_immediately with random secret")
|
|
end_point = ("/PasswordVault/API/Accounts/%s/Change") % existing_account_id
|
|
payload["ChangeEntireGroup"] = True
|
|
elif management_action == "change_immediately" and (
|
|
cpm_new_secret is not None and cpm_new_secret != "NOT_FOUND"
|
|
):
|
|
logging.debug("CPM change immediately secret for next CPM cycle")
|
|
end_point = (
|
|
"/PasswordVault/API/Accounts/%s/SetNextPassword"
|
|
) % existing_account_id
|
|
payload["ChangeImmediately"] = True
|
|
payload["NewCredentials"] = cpm_new_secret
|
|
elif management_action == "reconcile":
|
|
logging.debug("CPM reconcile secret")
|
|
end_point = ("/PasswordVault/API/Accounts/%s/Reconcile") % existing_account_id
|
|
elif (
|
|
"new_secret" in list(module.params.keys())
|
|
and module.params["new_secret"] is not None
|
|
):
|
|
logging.debug("Change Credential in Vault")
|
|
end_point = (
|
|
"/PasswordVault/API/Accounts/%s/Password/Update"
|
|
) % existing_account_id
|
|
payload["ChangeEntireGroup"] = True
|
|
payload["NewCredentials"] = module.params["new_secret"]
|
|
|
|
if end_point is not None:
|
|
|
|
if module.check_mode:
|
|
logging.debug("Proceeding with Credential Rotation (CHECK_MODE)")
|
|
return (True, result, -1)
|
|
else:
|
|
logging.debug("Proceeding with Credential Rotation")
|
|
|
|
result = {"result": None}
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"Authorization": cyberark_session["token"],
|
|
}
|
|
HTTPMethod = "POST"
|
|
try:
|
|
|
|
response = open_url(
|
|
api_base_url + end_point,
|
|
method=HTTPMethod,
|
|
headers=headers,
|
|
data=json.dumps(payload),
|
|
validate_certs=validate_certs,
|
|
)
|
|
|
|
return (True, result, response.getcode())
|
|
|
|
except (HTTPError, HTTPException) as http_exception:
|
|
|
|
if isinstance(http_exception, HTTPError):
|
|
res = json.load(http_exception)
|
|
else:
|
|
res = to_text(http_exception)
|
|
|
|
module.fail_json(
|
|
msg=(
|
|
"Error while performing reset_account."
|
|
"Please validate parameters provided."
|
|
"\n*** end_point=%s%s\n ==> %s"
|
|
)
|
|
% (api_base_url, end_point, res),
|
|
headers=headers,
|
|
payload=payload,
|
|
status_code=http_exception.code,
|
|
)
|
|
|
|
except Exception as unknown_exception:
|
|
|
|
module.fail_json(
|
|
msg=(
|
|
"Unknown error while performing delete_account."
|
|
"\n*** end_point=%s%s\n%s"
|
|
% (api_base_url, end_point, to_text(unknown_exception))
|
|
),
|
|
headers=headers,
|
|
payload=payload,
|
|
status_code=-1,
|
|
)
|
|
|
|
else:
|
|
return (False, result, -1)
|
|
|
|
|
|
def referenced_value(field, dct, keys=None, default=None):
|
|
return dct[field] if field in (keys if keys is not None else dct) else default
|
|
|
|
|
|
def deep_get(dct, dotted_path, default=_empty, use_reference_table=True):
|
|
result_dct = {}
|
|
for key in dotted_path.split("."):
|
|
try:
|
|
key_field = key
|
|
if use_reference_table:
|
|
key_field = referenced_value(
|
|
key, cyberark_reference_fieldnames, default=key
|
|
)
|
|
|
|
if len(list(result_dct.keys())) == 0: # No result_dct set yet
|
|
result_dct = dct
|
|
|
|
logging.debug(
|
|
"keys=%s key_field=>%s key=>%s",
|
|
",".join(list(result_dct.keys())),
|
|
key_field,
|
|
key,
|
|
)
|
|
result_dct = (
|
|
result_dct[key_field]
|
|
if key_field in list(result_dct.keys())
|
|
else result_dct[key]
|
|
)
|
|
if result_dct is None:
|
|
return default
|
|
|
|
except KeyError as e:
|
|
logging.debug("KeyError %s", to_text(e))
|
|
if default is _empty:
|
|
raise
|
|
return default
|
|
return result_dct
|
|
|
|
|
|
def get_account(module):
|
|
|
|
logging.debug("Finding Account")
|
|
|
|
identified_by_fields = module.params["identified_by"].split(",")
|
|
logging.debug("Identified_by: %s", json.dumps(identified_by_fields))
|
|
safe_filter = (
|
|
quote("safeName eq ") + quote(module.params["safe"])
|
|
if "safe" in module.params and module.params["safe"] is not None
|
|
else None
|
|
)
|
|
search_string = None
|
|
for field in identified_by_fields:
|
|
if field not in ansible_specific_parameters:
|
|
search_string = "%s%s" % (
|
|
search_string + " " if search_string is not None else "",
|
|
deep_get(module.params, field, "NOT FOUND", False),
|
|
)
|
|
|
|
logging.debug("Search_String => %s", search_string)
|
|
logging.debug("Safe Filter => %s", safe_filter)
|
|
|
|
cyberark_session = module.params["cyberark_session"]
|
|
api_base_url = cyberark_session["api_base_url"]
|
|
validate_certs = cyberark_session["validate_certs"]
|
|
|
|
end_point = None
|
|
if search_string is not None and safe_filter is not None:
|
|
end_point = "/PasswordVault/api/accounts?filter=%s&search=%s" % (
|
|
safe_filter,
|
|
quote(search_string.lstrip()),
|
|
)
|
|
elif search_string is not None:
|
|
end_point = ("/PasswordVault/api/accounts?search=%s") % (search_string.lstrip())
|
|
else:
|
|
end_point = "/PasswordVault/api/accounts?filter=%s" % (safe_filter)
|
|
|
|
logging.debug("End Point => %s", end_point)
|
|
|
|
headers = {"Content-Type": "application/json"}
|
|
headers["Authorization"] = cyberark_session["token"]
|
|
|
|
try:
|
|
|
|
logging.debug("Executing: " + api_base_url + end_point)
|
|
response = open_url(
|
|
api_base_url + end_point,
|
|
method="GET",
|
|
headers=headers,
|
|
validate_certs=validate_certs,
|
|
)
|
|
|
|
result_string = response.read()
|
|
accounts_data = json.loads(result_string)
|
|
|
|
logging.debug("RESULT => %s", json.dumps(accounts_data))
|
|
|
|
if accounts_data["count"] == 0:
|
|
return (False, None, response.getcode())
|
|
else:
|
|
how_many = 0
|
|
first_record_found = None
|
|
for account_record in accounts_data["value"]:
|
|
logging.debug("Acct Record => %s", json.dumps(account_record))
|
|
found = False
|
|
for field in identified_by_fields:
|
|
record_field_value = deep_get(account_record, field, "NOT FOUND")
|
|
logging.debug(
|
|
(
|
|
"Comparing field %s | record_field_name=%s "
|
|
"record_field_value=%s module.params_value=%s"
|
|
),
|
|
field,
|
|
field,
|
|
record_field_value,
|
|
deep_get(module.params, field, "NOT FOUND"),
|
|
)
|
|
if record_field_value != "NOT FOUND" and (
|
|
record_field_value
|
|
== deep_get(module.params, field, "NOT FOUND", False)
|
|
):
|
|
found = True
|
|
else:
|
|
found = False
|
|
break
|
|
if found:
|
|
how_many = how_many + 1
|
|
if first_record_found is None:
|
|
first_record_found = account_record
|
|
|
|
logging.debug(
|
|
"How Many: %d First Record Found => %s",
|
|
how_many,
|
|
json.dumps(first_record_found),
|
|
)
|
|
if how_many > 1: # too many records found
|
|
module.fail_json(
|
|
msg=(
|
|
"Error while performing get_account. "
|
|
"Too many rows (%d) found matching your criteria!"
|
|
)
|
|
% how_many
|
|
)
|
|
else:
|
|
return (how_many == 1, first_record_found, response.getcode())
|
|
|
|
except (HTTPError, HTTPException) as http_exception:
|
|
|
|
if http_exception.code == 404:
|
|
return (False, None, http_exception.code)
|
|
else:
|
|
module.fail_json(
|
|
msg=(
|
|
"Error while performing get_account."
|
|
"Please validate parameters provided."
|
|
"\n*** end_point=%s%s\n ==> %s"
|
|
% (api_base_url, end_point, to_text(http_exception))
|
|
),
|
|
headers=headers,
|
|
status_code=http_exception.code,
|
|
)
|
|
|
|
except Exception as unknown_exception:
|
|
|
|
module.fail_json(
|
|
msg=(
|
|
"Unknown error while performing get_account."
|
|
"\n*** end_point=%s%s\n%s"
|
|
% (api_base_url, end_point, to_text(unknown_exception))
|
|
),
|
|
headers=headers,
|
|
status_code=-1,
|
|
)
|
|
|
|
|
|
def main():
|
|
|
|
fields = {
|
|
"state": {
|
|
"type": "str",
|
|
"choices": ["present", "absent"],
|
|
"default": "present",
|
|
},
|
|
"logging_level": {"type": "str", "choices": ["NOTSET", "DEBUG", "INFO"]},
|
|
"logging_file": {"type": "str", "default": "/tmp/ansible_cyberark.log"},
|
|
"api_base_url": {"type": "str"},
|
|
"validate_certs": {"type": "bool", "default": "true"},
|
|
"cyberark_session": {"required": True, "type": "dict", "no_log": True},
|
|
"identified_by": {
|
|
"required": False,
|
|
"type": "str",
|
|
"default": "username,address,platform_id",
|
|
},
|
|
"safe": {"required": True, "type": "str"},
|
|
"platform_id": {"required": False, "type": "str"},
|
|
"address": {"required": False, "type": "str"},
|
|
"name": {"required": False, "type": "str"},
|
|
"secret_type": {
|
|
"required": False,
|
|
"type": "str",
|
|
"choices": ["password", "key"],
|
|
"default": "password",
|
|
},
|
|
"secret": {"required": False, "type": "str", "no_log": True},
|
|
"new_secret": {"required": False, "type": "str", "no_log": True},
|
|
"username": {"required": False, "type": "str"},
|
|
"secret_management": {
|
|
"required": False,
|
|
"type": "dict",
|
|
"options": {
|
|
"automatic_management_enabled": {"type": "bool"},
|
|
"manual_management_reason": {"type": "str"},
|
|
"management_action": {
|
|
"type": "str",
|
|
"choices": ["change", "change_immediately", "reconcile"],
|
|
},
|
|
"new_secret": {"type": "str", "no_log": True},
|
|
"perform_management_action": {
|
|
"type": "str",
|
|
"choices": ["on_create", "always"],
|
|
"default": "always",
|
|
},
|
|
},
|
|
},
|
|
"remote_machines_access": {
|
|
"required": False,
|
|
"type": "dict",
|
|
"options": {
|
|
"remote_machines": {"type": "str"},
|
|
"access_restricted_to_remote_machines": {"type": "bool"},
|
|
},
|
|
},
|
|
"platform_account_properties": {"required": False, "type": "dict"},
|
|
}
|
|
|
|
module = AnsibleModule(argument_spec=fields, supports_check_mode=True)
|
|
|
|
if module.params["logging_level"] is not None:
|
|
logging.basicConfig(
|
|
filename=module.params["logging_file"], level=module.params["logging_level"]
|
|
)
|
|
|
|
logging.info("Starting Module")
|
|
|
|
state = module.params["state"]
|
|
|
|
(found, account_record, status_code) = get_account(module)
|
|
logging.debug(
|
|
"Account was %s, status_code=%s", "FOUND" if found else "NOT FOUND", status_code
|
|
)
|
|
|
|
changed = False
|
|
result = {"result": account_record}
|
|
|
|
if state == "present":
|
|
|
|
if found: # Account already exists
|
|
(changed, result, status_code) = update_account(module, account_record)
|
|
else: # Account does not exist
|
|
(changed, result, status_code) = add_account(module)
|
|
|
|
perform_management_action = "always"
|
|
if "secret_management" in list(module.params.keys()):
|
|
secret_management = module.params["secret_management"]
|
|
if secret_management is not None and "perform_management_action" in list(
|
|
secret_management.keys()
|
|
):
|
|
perform_management_action = secret_management[
|
|
"perform_management_action"
|
|
]
|
|
|
|
logging.debug("Result=>%s", json.dumps(result))
|
|
if perform_management_action == "always" or (
|
|
perform_management_action == "on_create" and not found
|
|
):
|
|
(account_reset, no_result, no_status_code) = reset_account_if_needed(
|
|
module, result["result"]
|
|
)
|
|
if account_reset:
|
|
changed = True
|
|
|
|
elif found and state == "absent":
|
|
(changed, result, status_code) = delete_account(module, account_record)
|
|
|
|
module.exit_json(changed=changed, result=result, status_code=status_code)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|