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

[Marketplace Contribution] - EXPANDR-7038 - Azure Resource Graph #32121

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
aaeaccb
Add Pack ReadMe
BigEasyJ Dec 28, 2023
0bac941
Add integration
BigEasyJ Jan 8, 2024
003f9ce
Add integration description, image, and secrets ignore file
BigEasyJ Jan 9, 2024
ddb11e6
Add metadata file and pack ignore
BigEasyJ Dec 31, 2023
84939f5
Add test files and tests first
BigEasyJ Jan 5, 2024
2038200
Add Integration ReadMe
BigEasyJ Jan 11, 2024
28839bc
Update marketplaces
BigEasyJ Jan 11, 2024
c590de5
Update commands descriptions and output
BigEasyJ Jan 11, 2024
8709ee2
Update secrets ignore
BigEasyJ Jan 11, 2024
2002215
Resize image
BigEasyJ Jan 11, 2024
6bf7396
Update integration yml commands
BigEasyJ Jan 11, 2024
0714067
Update integration readme
BigEasyJ Jan 11, 2024
32adcbb
Resize image
BigEasyJ Jan 11, 2024
78afdd8
Address doc review and some design review comments
BigEasyJ Jan 23, 2024
8bcfd45
Update client credential flow section of ReadMe
BigEasyJ Jan 23, 2024
e28f806
Update list_operations_command to support a limit argument
BigEasyJ Jan 23, 2024
01f4c28
Update azure-rg-list-operations in ReadMe
BigEasyJ Jan 23, 2024
3dc0f7c
Merge branch 'contrib/PaloAltoNetworks_EXPANDR-7038' into EXPANDR-7038
BigEasyJ Feb 12, 2024
5a6169a
Merge branch 'contrib/PaloAltoNetworks_EXPANDR-7038' into EXPANDR-7038
BigEasyJ May 29, 2024
bca5b1c
Update azure-rg-list-operations to support paging
BigEasyJ Jun 6, 2024
12cda59
Update azure-rg-query to support paging
BigEasyJ Jun 6, 2024
b5cfd22
Update tests
BigEasyJ Jun 6, 2024
108d832
Remove Comments
BigEasyJ Jun 6, 2024
95202c7
Update integration configuration yml settings
BigEasyJ Jun 6, 2024
b8ee973
Add management_groups & subscriptions parameters for query command
BigEasyJ Jun 17, 2024
dff3feb
Add suggested changes from second review
BigEasyJ Jun 17, 2024
8f3ed14
Merge branch 'contrib/PaloAltoNetworks_EXPANDR-7038' into EXPANDR-7038
BigEasyJ Jun 23, 2024
85ec219
Update Readme and Description from code review
BigEasyJ Jul 3, 2024
615cec1
Update integration files with code review suggestions
BigEasyJ Jul 3, 2024
e615301
Update defaultValue key in YAML and docker version
BigEasyJ Jul 3, 2024
ce0cd98
Update section titles in YAML
BigEasyJ Jul 3, 2024
1597bea
Remove subscription_id from client and format
BigEasyJ Jul 3, 2024
bd496db
Remove DefaultValues
BigEasyJ Jul 3, 2024
1a867e0
Update ReadMe
BigEasyJ Jul 3, 2024
828715a
Formatting
BigEasyJ Jul 3, 2024
50dce6f
Remove subscription_id from client in test file
BigEasyJ Jul 3, 2024
3d109df
Update tests and fix mypy errors
BigEasyJ Jul 3, 2024
6efaf12
Merge branch 'contrib/PaloAltoNetworks_EXPANDR-7038' into EXPANDR-7038
BigEasyJ Jul 3, 2024
cebfc49
Update address mypy errors
BigEasyJ Jul 3, 2024
85addbc
Merge branch 'contrib/PaloAltoNetworks_EXPANDR-7038' into EXPANDR-7038
BigEasyJ Jul 8, 2024
c7f08cd
Merge branch 'contrib/PaloAltoNetworks_EXPANDR-7038' into EXPANDR-7038
BigEasyJ Jul 9, 2024
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
Empty file.
10 changes: 10 additions & 0 deletions Packs/AzureResourceGraph/.secrets-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
https://management.azure.com
https://xsoar.pan.dev*
https://portal.azure.com
https://login.microsoftonline.com'
AzureResourceGraphClient
Microsoft.ResourceGraph/*
"microsoft.network/*
azure-rg
Query
11.22.33.44
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import demistomock as demisto # noqa: F401
from CommonServerPython import * # noqa: F401
from MicrosoftApiModule import * # noqa: E402

'''GLOBAL VARS'''
API_VERSION = '2022-10-01'
APP_NAME = 'azure-resource-graph'
MAX_PAGE_SIZE = 50


class AzureResourceGraphClient:
"""
Azure Resource Graph Client enables authorized access to query for resource information.
"""

def __init__(self, tenant_id, auth_id, enc_key, app_name, base_url, verify, proxy, self_deployed, ok_codes, server,
certificate_thumbprint, private_key):

self.ms_client = MicrosoftClient(
tenant_id=tenant_id, auth_id=auth_id, enc_key=enc_key, app_name=app_name, base_url=base_url, verify=verify,
proxy=proxy, self_deployed=self_deployed, ok_codes=ok_codes, scope=Scopes.management_azure,
certificate_thumbprint=certificate_thumbprint, private_key=private_key,
command_prefix="azure-rg",
)

self.server = server
self.default_params = {"api-version": API_VERSION}

def list_operations(self):
return self.ms_client.http_request(
method='GET',
full_url=f"{self.server}/providers/Microsoft.ResourceGraph/operations",
params=self.default_params,
)

def query_resources(self, query, paging_options: dict[str, Any], subscriptions: list, management_groups: list):
request_data = {"query": query, "options": paging_options}

if subscriptions:
request_data["subscriptions"] = subscriptions

if management_groups:
request_data["managementGroups"] = management_groups

return self.ms_client.http_request(
method='POST',
full_url=f"{self.server}/providers/Microsoft.ResourceGraph/resources",
params=self.default_params,
json_data=request_data
)


def query_resources_command(client: AzureResourceGraphClient, args: dict[str, Any]) -> CommandResults:
limit = arg_to_number(args.get('limit'))
page_size = arg_to_number(args.get('page_size'))
page_number = arg_to_number(args.get('page'))
management_groups = argToList(args.get('management_groups', None))
subscriptions = argToList(args.get('subscriptions', None))

query = args.get('query')

list_of_query_results = []
total_records = 0

if page_number and not page_size:
raise DemistoException("Please enter a value for \"page_size\" when using \"page\".")
if page_size and not page_number:
raise DemistoException("Please enter a value for \"page\" when using \"page_size\".")

if page_number and page_size:
skip = (page_number - 1) * page_size + 1
params = {'$skip': skip, '$top': page_size}
response = client.query_resources(query=query,
paging_options=params,
management_groups=management_groups,
subscriptions=subscriptions)
total_records = response.get('totalRecords')
list_of_query_results = response.get('data')
elif page_number:
params = {'$top': page_size} # type: ignore
response = client.query_resources(query=query,
paging_options=params,
management_groups=management_groups,
subscriptions=subscriptions)
total_records = response.get('totalRecords')
list_of_query_results = response.get('data')
else:
query_results = []
skip_token = ""
counter = 0

while True:
if skip_token:
params = {'$skipToken': skip_token} # type: ignore
else:
params = {}

response = client.query_resources(query=query,
paging_options=params,
management_groups=management_groups,
subscriptions=subscriptions)

list_of_query_results = response.get('data')
query_results.extend(list_of_query_results)
counter += len(list_of_query_results)
if limit and counter >= limit:
break
if '$skipToken' in response and (not limit or counter < limit):
skip_token = response.get('$skipToken')
else:
break

total_records = response.get('totalRecords')
list_of_query_results = query_results

if limit:
list_of_query_results = list_of_query_results[:limit]

title = f"Results of query:\n```{query}```\n\n Total Number of Possible Records:{total_records} \n"
human_readable = tableToMarkdown(title, list_of_query_results, removeNull=True)

return CommandResults(
readable_output=human_readable,
outputs_prefix='AzureResourceGraph.Query',
outputs_key_field='Query',
outputs=list_of_query_results,
raw_response=response
)


def list_operations_command(client: AzureResourceGraphClient, args: dict[str, Any]) -> CommandResults:
limit = arg_to_number(args.get('limit'))
page_size = arg_to_number(args.get('page_size'))
page = arg_to_number(args.get('page'))

response = client.list_operations()
operations_list = response.get('value')
md_output_notes = ""

if page and not page_size:
raise DemistoException("Please enter a value for \"page_size\" when using \"page\".")
if page_size and not page:
raise DemistoException("Please enter a value for \"page\" when using \"page_size\".")
if page and page_size:
if limit:
md_output_notes = "\"limit\" was ignored for paging parameters."
demisto.debug("\"limit\" was ignored for paging parameters.")
operations_list = pagination(operations_list, page_size, page)

if page_size:
limit = page_size

operations = []
for operation in operations_list[:limit]:
operation_context = {
'Name': operation.get('name'),
'Display': operation.get('display')
}
operations.append(operation_context)

title = 'List of Azure Resource Graph Operations\n\n' + md_output_notes
human_readable = tableToMarkdown(title, operations, removeNull=True)

return CommandResults(
readable_output=human_readable,
outputs_prefix='AzureResourceGraph.Operations',
outputs_key_field='Operations',
outputs=operations,
raw_response=response
)


def test_module(client: AzureResourceGraphClient):
# Implicitly will test tenant, enc_token and subscription_id
try:
result = client.list_operations()
if result:
return 'ok'
except DemistoException as e:
return_error(f"Test connection failed with message {e}")


# Helper Methods

def pagination(response, page_size, page_number):
"""Method to generate a page (slice) of data.
Args:
response: The response from the API.
limit: Maximum number of objects to retrieve.
page: Page number
Returns:
Return a list of objects from the response according to the page and limit per page.
"""
if page_size > MAX_PAGE_SIZE:
page_size = MAX_PAGE_SIZE

starting_index = (page_number - 1) * page_size
ending_index = starting_index + page_size
return response[starting_index:ending_index]


JasBeilin marked this conversation as resolved.
Show resolved Hide resolved
def validate_connection_params(tenant: str = None,
auth_and_token_url: str = None,
is_self_deployed: bool = False,
enc_key: str = None,
certificate_thumbprint: str = None,
private_key: str = None) -> None:
if not tenant or not auth_and_token_url:
raise DemistoException('Token and ID must be provided.')

if not is_self_deployed and not enc_key:
raise DemistoException('Key must be provided. For further information see '
'https://xsoar.pan.dev/docs/reference/articles/microsoft-integrations---authentication')
elif not enc_key and not (certificate_thumbprint and private_key):
raise DemistoException('Key or Certificate Thumbprint and Private Key must be providedFor further information see '
'https://xsoar.pan.dev/docs/reference/articles/microsoft-integrations---authentication')


def main():
params: dict = demisto.params()
args = demisto.args()
server = params.get('host', 'https://management.azure.com').rstrip('/')
tenant = params.get('cred_token', {}).get('password') or params.get('tenant_id')
auth_and_token_url = params.get('cred_auth_id', {}).get('password') or params.get('auth_id')
enc_key = params.get('cred_enc_key', {}).get('password') or params.get('enc_key')
certificate_thumbprint = params.get('cred_certificate_thumbprint', {}).get(
'password') or params.get('certificate_thumbprint')
private_key = params.get('private_key')
verify = not params.get('unsecure', False)
proxy: bool = params.get('proxy', False)
self_deployed: bool = params.get('self_deployed', False)

validate_connection_params(tenant, auth_and_token_url, self_deployed, enc_key,
certificate_thumbprint, private_key)

ok_codes = (200, 201, 202, 204)
JasBeilin marked this conversation as resolved.
Show resolved Hide resolved

commands_without_args: Dict[Any, Any] = {
'test-module': test_module
}

commands_with_args: Dict[Any, Any] = {
'azure-rg-query': query_resources_command,
'azure-rg-list-operations': list_operations_command
}

'''EXECUTION'''
command = demisto.command()
LOG(f'Command being called is {command}')

try:
# Initial setup
base_url = f"{server}/providers/Microsoft.ResourceGraph"

client = AzureResourceGraphClient(
base_url=base_url, tenant_id=tenant, auth_id=auth_and_token_url, enc_key=enc_key, app_name=APP_NAME,
verify=verify, proxy=proxy, self_deployed=self_deployed, ok_codes=ok_codes, server=server,
certificate_thumbprint=certificate_thumbprint, private_key=private_key)
if command == 'azure-rg-auth-reset':
return_results(reset_auth())
elif command in commands_without_args:
return_results(commands_without_args[command](client))
elif command in commands_with_args:
return_results(commands_with_args[command](client, args))
else:
raise NotImplementedError(f'Command "{command}" is not implemented.')
except Exception as e:
return_error(f'Failed to execute {command} command. Error: {str(e)}')


if __name__ in ['__main__', 'builtin', 'builtins']:
main()
Loading
Loading