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 24 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,248 @@
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,
subscription_id, 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.subscription_id = subscription_id
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: str, paging_options: dict):
request_data = {"query": query, "options": paging_options}
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'))
query = args.get('query')
list_of_query_results = []
total_records = 0

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)
total_records = response.get('totalRecords')
list_of_query_results = response.get('data')
elif page_number:
params = {'$top': page_size}
response = client.query_resources(query=query, paging_options=params)
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}
else:
params = {}

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

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 = 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 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')
if not tenant or not auth_and_token_url:
return_error('Token and ID must be provided.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its better practice to take this logic outside of the main.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in dff3feb

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)
subscription_id = args.get('subscription_id') or params.get(
'cred_subscription_id', {}).get('password') or params.get('subscription_id')
proxy: bool = params.get('proxy', False)
self_deployed: bool = params.get('self_deployed', False)
if not 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')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its better practice to take this logic outside of the main.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in dff3feb

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
}

commands_with_args_and_params: Dict[Any, Any] = {
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
commands_with_args_and_params: Dict[Any, Any] = {
}

Removing as we usually do not pass the params but saves in the client what might be needed.


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

try:
# Initial setup
if not subscription_id:
return_error('A subscription ID must be provided.')
JasBeilin marked this conversation as resolved.
Show resolved Hide resolved
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,
subscription_id=subscription_id, 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))

elif command in commands_with_args_and_params:
return_results(commands_with_args_and_params[command](client, args, params))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elif command in commands_with_args_and_params:
return_results(commands_with_args_and_params[command](client, args, params))


except Exception as e:
return_error(e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
except Exception as e:
return_error(e)
else:
raise NotImplementedError(f'Command "{command}" is not implemented.')
except Exception as e:
return_error(f'Failed to execute {command} command. Error: {str(e)}')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in dff3feb



if __name__ in ['__main__', 'builtin', 'builtins']:
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
category: Cloud Services
sectionorder:
- Connect
- Collect
commonfields:
id: Azure Resource Graph
version: -1
configuration:
- display: Host URL (e.g., https://management.azure.com)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- display: Host URL (e.g., https://management.azure.com)
- display: Server URL (e.g., https://management.azure.com)

name: host
defaultvalue: https://management.azure.com
type: 0
required: true
section: Connect
- display: ID (Client ID)
name: auth_id
type: 4
required: false
section: Connect
hidden: true
Copy link
Contributor

@JasBeilin JasBeilin Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it hidden? If there is no need for that you can remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- display: ID (Client ID)
name: auth_id
type: 4
required: false
section: Connect
hidden: true

Removing as it is old param that could not be removed due to BC but no need for it here.

- display: Token (Tenant ID)
name: tenant_id
type: 4
required: false
section: Connect
hidden: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- display: Token (Tenant ID)
name: tenant_id
type: 4
required: false
section: Connect
hidden: true

Removing as it is old param that could not be removed due to BC but no need for it here.

- display: Key (Client Secret)
name: enc_key
type: 4
required: false
section: Connect
hidden: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- display: Key (Client Secret)
name: enc_key
type: 4
required: false
section: Connect
hidden: false

Removing as it is old param that could not be removed due to BC but no need for it here.

- display: Certificate Thumbprint
name: certificate_thumbprint
type: 4
required: false
section: Connect
hidden: false
additionalinfo: Used for certificate authentication. As appears in the "Certificates & secrets" page of the app.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- display: Certificate Thumbprint
name: certificate_thumbprint
type: 4
required: false
section: Connect
hidden: false
additionalinfo: Used for certificate authentication. As appears in the "Certificates & secrets" page of the app.

Removing as it is old param that could not be removed due to BC but no need for it here.

- display: ""
displaypassword: ID (received from the admin consent - see Detailed Instructions (?)
name: cred_auth_id
type: 9
required: false
hiddenusername: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- display: ""
displaypassword: ID (received from the admin consent - see Detailed Instructions (?)
name: cred_auth_id
type: 9
required: false
hiddenusername: true
- displaypassword: ID / Client ID
name: cred_auth_id
type: 9
required: false
hiddenusername: true
display: ""
section: connect

- display: ""
displaypassword: Token (received from the admin consent - see Detailed Instructions (?) section)
name: cred_token
type: 9
required: false
hiddenusername: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- display: ""
displaypassword: Token (received from the admin consent - see Detailed Instructions (?) section)
name: cred_token
type: 9
required: false
hiddenusername: true
- displaypassword: Token / Tenant ID
name: cred_token
type: 9
required: false
hiddenusername: true
display: ""
section: connect

- display: ""
displaypassword: Key (received from the admin consent - see Detailed Instructions (?)
name: cred_enc_key
type: 9
required: false
hiddenusername: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- display: ""
displaypassword: Key (received from the admin consent - see Detailed Instructions (?)
name: cred_enc_key
type: 9
required: false
hiddenusername: true
- displaypassword: Key / Client Secret
name: cred_enc_key
type: 9
required: false
hiddenusername: true
display: ""

- display: ""
displaypassword: Certificate Thumbprint
name: cred_certificate_thumbprint
type: 9
required: false
hiddenusername: true
additionalinfo: Used for certificate authentication. As appears in the "Certificates & secrets" page of the app.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
additionalinfo: Used for certificate authentication. As appears in the "Certificates & secrets" page of the app.
additionalinfo: Used for certificate authentication. As appears in the "Certificates & secrets" page of the app.
section: connect

- display: Private Key
name: private_key
type: 14
required: false
additionalinfo: Used for certificate authentication. The private key of the registered certificate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
additionalinfo: Used for certificate authentication. The private key of the registered certificate.
additionalinfo: Used for certificate authentication. The private key of the registered certificate.
section: connect

- display: Default Subscription ID
name: subscription_id
type: 4
required: false
hidden: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- display: Default Subscription ID
name: subscription_id
type: 4
required: false
hidden: true

- display: ""
displaypassword: Default Subscription ID
name: cred_subscription_id
type: 9
required: false
hiddenusername: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?

- display: Use system proxy settings
name: proxy
type: 8
required: false
- display: Trust any certificate (not secure)
name: unsecure
type: 8
required: false
- display: Use a self-deployed Azure Application
name: self_deployed
type: 8
required: false
additionalinfo: Select this checkbox if you are using a self-deployed Azure application.
description: 'Azure Resource Graph integration is designed to allow for executing Azure Resource Graph commands, like querying resource data.'
display: Azure Resource Graph
name: Azure Resource Graph
script:
commands:
- name: azure-rg-auth-reset
arguments: []
description: Run this command if for some reason you need to rerun the authentication process.
- name: azure-rg-list-operations
arguments:
- description: 'The maximum number of operations to return. NOTE: Do not use with "page" and "page_size".'
name: limit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: limit
name: limit
default: 50

- description: The maximum number of operations to return for page.
name: page_size
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: page_size
name: page_size
default: 50

- description: The page number to return.
name: page
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: page
name: page
default: 1

outputs:
- contextPath: AzureResourceGraph.Operations
description: A list of available Azure Resource Graph operations permissions and descriptions.
type: String
description: Gets all Azure Resource Graph operations permissions and descriptions.
- name: azure-rg-query
arguments:
JasBeilin marked this conversation as resolved.
Show resolved Hide resolved
- description: 'The maximum number of operations to return (Default is 50). NOTE: Do not use with "page" and "page_size".'
name: limit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: limit
name: limit
default: 50

- description: The maximum number of operations to return for page.
name: page_size
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: page_size
name: page_size
default: 50

- description: The page number to return.
name: page
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: page
name: page
default: 1

- name: query
required: true
description: 'The query for the Azure Resource Graph to execute. The query is based on Kusto Query Language (KQL). Reference: https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description: 'The query for the Azure Resource Graph to execute. The query is based on Kusto Query Language (KQL). Reference: https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/'
description: |-
The query for the Azure Resource Graph to execute.
For example: `resources | where (resourceGroup =~ ('demisto-sentinel2'))`.
The query is based on Kusto Query Language (KQL). Reference: https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/

outputs:
- contextPath: AzureResourceGraph.Query
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are specific fields returning its worth putting here so it will be available when running in playbooks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are numerous amounts also there is the ability for custom fields.

description: Data returned from query.
type: String
description: 'Executes a given Azure Resource Graph Query. (Ex: query="Resources | project name, type | limit 5 | order by name asc").'
dockerimage: demisto/crypto:1.0.0.84658
runonce: false
script: ''
subtype: python3
type: python
fromversion: 6.10.0
tests:
- No tests (auto formatted)
Loading
Loading