forked from demisto/content
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AwsEC2SyncAccounts script (demisto#31680)
* init * finished unit-tests * docs complete * update release notes * docs * name change * CR changes * Bump pack from version AWS-EC2 to 1.3.0. * Demo changes * bug fixes * small changes * refactor * fix unit-tests * added use-case * Update docker * build wars: round 1 * Update Packs/AWS-EC2/ReleaseNotes/1_3_0.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Bump pack from version AWS-EC2 to 1.4.0. * more tests * more tests * ignore secrets --------- Co-authored-by: Content Bot <bot@demisto.com> Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com>
- Loading branch information
1 parent
fc4bc1f
commit 8cc4e0b
Showing
7 changed files
with
354 additions
and
2 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
::7940 | ||
ec2::2222 | ||
ec2::2222 | ||
user@xsoar.com |
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,6 @@ | ||
|
||
#### Scripts | ||
|
||
##### New: AwsEC2SyncAccounts | ||
|
||
New: Update an AWS - EC2 instance with a list of accounts in an AWS organization, which will allow EC2 commands to run in all of them. (Available from Cortex XSOAR 6.10.0). |
118 changes: 118 additions & 0 deletions
118
Packs/AWS-EC2/Scripts/AwsEC2SyncAccounts/AwsEC2SyncAccounts.py
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,118 @@ | ||
import demistomock as demisto | ||
from CommonServerPython import * | ||
|
||
|
||
ACCOUNT_LIST_COMMAND = 'aws-org-account-list' | ||
EC2_ACCOUNTS_PARAM = 'accounts_to_access' | ||
|
||
|
||
def internal_request(method: str, uri: str, body: dict = {}) -> dict: | ||
"""A wrapper for demisto.internalHttpRequest. | ||
Args: | ||
method (str): HTTP method such as: GET or POST | ||
uri (str): Server uri to request. For example: "/contentpacks/marketplace/HelloWorld". | ||
body (dict, optional): Optional body for a POST request. Defaults to {}. | ||
Returns: | ||
dict: The body of request response. | ||
""" | ||
return demisto.executeCommand( | ||
f'core-api-{method.lower()}', | ||
{'uri': uri, 'body': json.dumps(body)} | ||
)[0]['Contents']['response'] # type: ignore | ||
|
||
|
||
def get_account_ids(ec2_instance_name: str | None) -> tuple[list[str], str]: | ||
'''Get the AWS organization accounts using the `aws-org-account-list` command. | ||
Returns: | ||
list[str]: A list of AWS account IDs. | ||
''' | ||
try: | ||
command_args = {'using': ec2_instance_name} if ec2_instance_name else {} | ||
account_list_result: list[dict] = demisto.executeCommand(ACCOUNT_LIST_COMMAND, command_args) # type: ignore | ||
accounts = dict_safe_get( | ||
account_list_result, (0, 'EntryContext', 'AWS.Organizations.Account(val.Id && val.Id == obj.Id)'), [] | ||
) | ||
return [account['Id'] for account in accounts], str(dict_safe_get(account_list_result, (0, 'HumanReadable'), '')) | ||
except ValueError as e: | ||
raise DemistoException(f'The command {ACCOUNT_LIST_COMMAND!r} must be operational to run this script.\nServer error: {e}') | ||
except StopIteration: | ||
raise DemistoException(f'AWS - Organizations instance {ec2_instance_name!r} was not found.') | ||
except (KeyError, TypeError): | ||
account_list_result = locals().get('account_list_result') # type: ignore # catch unbound variable error | ||
raise DemistoException(f'Unexpected output from {ACCOUNT_LIST_COMMAND!r}:\n{account_list_result}') | ||
except Exception as e: | ||
raise DemistoException(f'Unexpected error while fetching accounts:\n{e}') | ||
|
||
|
||
def get_instance(ec2_instance_name: str) -> dict: | ||
'''Get the object of the instance with the name provided. | ||
Args: | ||
instance_name (str): The name of the instance to get. | ||
Returns: | ||
dict: The instance object. | ||
''' | ||
integrations = internal_request('post', '/settings/integration/search') | ||
return next(inst for inst in integrations['instances'] if inst['name'] == ec2_instance_name) | ||
|
||
|
||
def set_instance(instance: dict, accounts: str) -> dict: | ||
'''Set an instance configuration with the accounts. | ||
Args: | ||
instance (dict): The instance object to configure. | ||
accounts (str): The accounts to add to the body. | ||
Returns: | ||
dict: The server response from the configuration call. | ||
''' | ||
accounts_param: dict = next(param for param in instance['data'] if param['name'] == EC2_ACCOUNTS_PARAM) | ||
accounts_param.update({ | ||
'hasvalue': True, | ||
'value': accounts | ||
}) | ||
return internal_request('put', '/settings/integration', instance) | ||
|
||
|
||
def update_ec2_instance(account_ids: list[str], ec2_instance_name: str) -> str: | ||
'''Update an AWS - EC2 instance with AWS Organization accounts. | ||
Args: | ||
account_ids (list[str]): The accounts to configure the instance with. | ||
instance_name (str): The name of the instance to configure. | ||
Returns: | ||
str: A message regarding the outcome of the script run. | ||
''' | ||
accounts_as_str = ','.join(account_ids) | ||
try: | ||
instance = get_instance(ec2_instance_name) | ||
if instance['configvalues'][EC2_ACCOUNTS_PARAM] == accounts_as_str: | ||
return f'Account list in ***{ec2_instance_name}*** is up to date.' | ||
response = set_instance(instance, accounts_as_str) | ||
if response['configvalues'][EC2_ACCOUNTS_PARAM] != accounts_as_str: | ||
demisto.debug(f'{response=}') | ||
raise DemistoException(f'Attempt to update {ec2_instance_name!r} with accounts {accounts_as_str} has failed.') | ||
return f'Successfully updated ***{ec2_instance_name}*** with accounts:' | ||
except StopIteration: | ||
raise DemistoException(f'AWS - EC2 instance {ec2_instance_name!r} was not found or is not an AWS - EC2 instance.') | ||
except Exception as e: | ||
raise DemistoException(f'Unexpected error while configuring AWS - EC2 instance with accounts {accounts_as_str!r}:\n{e}') | ||
|
||
|
||
def main(): | ||
try: | ||
args: dict = demisto.args() | ||
account_ids, readable_output = get_account_ids(args.get('org_instance_name')) | ||
result = update_ec2_instance(account_ids, args['ec2_instance_name']) | ||
return_results(CommandResults(readable_output=f'## {result} \n--- \n{readable_output}')) | ||
except Exception as e: | ||
return_error(f'Error in AwsEC2SyncAccounts: {e}') | ||
|
||
|
||
if __name__ in ('__main__', 'builtins', '__builtin__'): | ||
main() |
22 changes: 22 additions & 0 deletions
22
Packs/AWS-EC2/Scripts/AwsEC2SyncAccounts/AwsEC2SyncAccounts.yml
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,22 @@ | ||
args: | ||
- description: The name of the AWS - EC2 instance integration to update. | ||
name: ec2_instance_name | ||
required: true | ||
- description: The name of the AWS - Organizations instance to collect account from. If not provided, the primary instance will be used. | ||
name: org_instance_name | ||
comment: Update an AWS - EC2 instance with a list of accounts in an AWS organization, which will allow EC2 commands to run in all of them. | ||
commonfields: | ||
id: AwsEC2SyncAccounts | ||
version: -1 | ||
enabled: true | ||
name: AwsEC2SyncAccounts | ||
outputs: [] | ||
script: '-' | ||
tags: | ||
- Amazon Web Services | ||
timeout: '0' | ||
type: python | ||
subtype: python3 | ||
dockerimage: demisto/python3:3.10.13.83255 | ||
runas: DBotWeakRole | ||
fromversion: 6.10.0 |
158 changes: 158 additions & 0 deletions
158
Packs/AWS-EC2/Scripts/AwsEC2SyncAccounts/AwsEC2SyncAccounts_test.py
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,158 @@ | ||
import demistomock as demisto | ||
from CommonServerPython import * | ||
from unittest.mock import MagicMock | ||
import pytest | ||
|
||
|
||
def test_internal_request(mocker): | ||
from AwsEC2SyncAccounts import internal_request | ||
|
||
mock_execute_command = mocker.patch.object( | ||
demisto, "executeCommand", return_value=[{'Contents': {'response': 'result'}}] | ||
) | ||
|
||
result = internal_request('POST', '/path/', {'body': 'data'}) | ||
|
||
assert result == 'result' | ||
mock_execute_command.assert_called_with( | ||
'core-api-post', {'uri': '/path/', 'body': '{"body": "data"}'} | ||
) | ||
|
||
|
||
def test_get_account_ids(mocker): | ||
from AwsEC2SyncAccounts import get_account_ids | ||
|
||
mock_execute_command = mocker.patch.object(demisto, "executeCommand") | ||
mock_execute_command.return_value = [ | ||
{ | ||
"EntryContext": { | ||
"AWS.Organizations.Account(val.Id && val.Id == obj.Id)": [ | ||
{"Id": "1234"}, | ||
{"Id": "5678"}, | ||
] | ||
}, | ||
"HumanReadable": "human_readable" | ||
} | ||
] | ||
|
||
account_ids = get_account_ids('instance_name') | ||
|
||
assert account_ids == (["1234", "5678"], "human_readable") | ||
mock_execute_command.assert_called_with("aws-org-account-list", {'using': 'instance_name'}) | ||
|
||
|
||
def test_set_instance(mocker): | ||
import AwsEC2SyncAccounts | ||
|
||
internal_request: MagicMock = mocker.patch.object( | ||
AwsEC2SyncAccounts, "internal_request" | ||
) | ||
AwsEC2SyncAccounts.set_instance( | ||
{ | ||
"data": [ | ||
{ | ||
"name": "accounts_to_access", | ||
"hasvalue": False, | ||
"value": "", | ||
}, | ||
{ | ||
"name": "sessionDuration" | ||
}, | ||
], | ||
}, | ||
'accounts' | ||
) | ||
internal_request.assert_called_with( | ||
'put', '/settings/integration', { | ||
"data": [ | ||
{ | ||
"name": "accounts_to_access", | ||
"hasvalue": True, | ||
"value": "accounts", | ||
}, | ||
{ | ||
"name": "sessionDuration" | ||
}, | ||
], | ||
} | ||
) | ||
|
||
|
||
def test_update_ec2_instance(mocker): | ||
import AwsEC2SyncAccounts | ||
|
||
internal_request: MagicMock = mocker.patch.object( | ||
AwsEC2SyncAccounts, | ||
"internal_request", | ||
side_effect=lambda *args: { | ||
("post", "/settings/integration/search"): { | ||
"instances": [ | ||
{ | ||
"id": "2fa1071e-af66-4668-8f79-8c57a3e4851d", | ||
"name": "AWS - EC2", | ||
"configvalues": { | ||
"accounts_to_access": "", | ||
"sessionDuration": None, | ||
}, | ||
"configtypes": {"accounts_to_access": 0, "sessionDuration": 0}, | ||
"data": [ | ||
{ | ||
"name": "accounts_to_access", | ||
"hasvalue": False, | ||
"value": "", | ||
}, | ||
{ | ||
"name": "sessionDuration" | ||
}, | ||
], | ||
}, | ||
{ | ||
"name": "wrong name", | ||
}, | ||
] | ||
}, | ||
("put", "/settings/integration"): { | ||
"configvalues": {"accounts_to_access": "1234,5678"} | ||
}, | ||
}.get(args[:2]), | ||
) | ||
|
||
result = AwsEC2SyncAccounts.update_ec2_instance(["1234", "5678"], "AWS - EC2") | ||
|
||
assert internal_request.mock_calls[0].args == ('post', '/settings/integration/search') | ||
assert internal_request.mock_calls[1].args == ( | ||
'put', | ||
'/settings/integration', | ||
{ | ||
"id": "2fa1071e-af66-4668-8f79-8c57a3e4851d", | ||
"name": "AWS - EC2", | ||
"configvalues": { | ||
"accounts_to_access": "", | ||
"sessionDuration": None, | ||
}, | ||
"configtypes": {"accounts_to_access": 0, "sessionDuration": 0}, | ||
"data": [ | ||
{ | ||
"name": "accounts_to_access", | ||
"hasvalue": True, | ||
"value": "1234,5678", | ||
}, | ||
{ | ||
"name": "sessionDuration" | ||
}, | ||
], | ||
} | ||
) | ||
assert result == "Successfully updated ***AWS - EC2*** with accounts:" | ||
|
||
|
||
def test_errors(mocker): | ||
import AwsEC2SyncAccounts as sync | ||
|
||
with pytest.raises(DemistoException, match='Unexpected error while configuring AWS - EC2 instance with accounts'): | ||
sync.get_instance = lambda _: 1 / 0 | ||
sync.update_ec2_instance([], '') | ||
|
||
with pytest.raises(DemistoException, match="Unexpected output from 'aws-org-account-list':\nNone"): | ||
sync.demisto.executeCommand = lambda *_: {}['key'] | ||
sync.get_account_ids('') |
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,47 @@ | ||
Update an AWS - EC2 instance with a list of accounts in an AWS organization, which will allow EC2 commands to run in all of them. | ||
This script can be run on a schedule to keep an AWS - EC2 instance in sync with the created, deleted or removed accounts of the organization. | ||
|
||
### Prerequisites | ||
- An ***AWS - EC2*** instance. | ||
- An ***AWS - Organizations*** instance with a working `aws-org-account-list` command. | ||
- A ***Core REST API*** instance. | ||
|
||
## Script Data | ||
|
||
--- | ||
|
||
| **Name** | **Description** | | ||
| --- | --- | | ||
| Script Type | python3 | | ||
| Tags | Amazon Web Services | | ||
| Cortex XSOAR Version | 6.10.0 | | ||
|
||
## Inputs | ||
|
||
--- | ||
|
||
| **Argument Name** | **Description** | | ||
| --- | --- | | ||
| ec2_instance_name | The name of the AWS - EC2 instance integration to update. | | ||
| org_instance_name | The name of the AWS - Organizations instance to collect account from. If not provided, the primary instance will be used. | | ||
|
||
## Outputs | ||
|
||
--- | ||
There are no outputs for this script. | ||
|
||
## Script Examples | ||
|
||
### Example command | ||
|
||
```!AwsEC2SyncAccounts ec2_instance_name="AWS_EC2_Instance" org_instance_name="AWS_Organizations_Instance"``` | ||
|
||
### Human Readable Output | ||
|
||
> ## Successfully updated ***AWS_EC2_Instance*** with accounts: | ||
> --- | ||
>### AWS Organization Accounts | ||
>|Id|Arn|Name|Email|JoinedMethod|JoinedTimestamp|Status| | ||
>|---|---|---|---|---|---|---| | ||
>| 111222333444 | arn:aws:organizations::111222333444:account/o-abcde12345/111222333444 | Name | user@xsoar.com | CREATED | 2023-09-04 09:17:14.299000+00:00 | ACTIVE | | ||
>| 111222333444 | arn:aws:organizations::111222333444:account/o-abcde12345/111222333444 | ferrum-techs | user@xsoar.com | INVITED | 2022-07-25 09:11:23.528000+00:00 | SUSPENDED | |
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