Extending Microsoft Copilot for Security Capabilities with Azure Function Apps by info.odysseyx@gmail.com August 15, 2024 written by info.odysseyx@gmail.com August 15, 2024 0 comment 13 views 13 Azure Function Apps Function Apps) offer a convenient way to execute functions in a server-less environment. They allow users to write functions in C#, Java, JavaScript, PowerShell, Python and Typescript which can then be called using several trigger options. One of the most common triggers is the HTTP trigger allowing functions to be called like a REST API. Microsoft Copilot for Security (Copilot) is a large language model (LLM) based generative Artificial Intelligence (AI) system for cybersecurity use cases. Copilot is not a monolithic system but is an ecosystem running on a platform that allows data requests to be fulfilled from multiple sources using a unique plugin mechanism. The plugin mechanism gives Copilot the capability to pull data for any external data source that supports a REST like API, thus allowing Copilot to call Function Apps that use the HTTP trigger. By using Function Apps as Copilot for Security plugins, you can have Copilot indirectly execute custom code. In the code, custom use cases which are not provided by current set of Microsoft and non-Microsoft plugins or Logic Apps, can be implemented. Some examples where Function Apps can be used are: Calling external APIs which use non-standard authentication: If an external API does not use oAuth2 or any of the supported authentication types in Copilot, or require a message hash to generated with each API request, the only way to make the API request is by executing custom code from a Function App. Data Massaging: Several API calls return lot of data and data massaging is required to remove irrelevant fields and condense the data being sent to Copilot. In this case, Function Apps can act as the proxy that makes the API call, receives the response data, removes irrelevant fields and then passes the cleaned up data to Copilot. Getting Data from in-house tools: Several organizations have custom security and operations tools which do not have a REST API, making a Copilot plugin not possible. In these cases, Function Apps can act as the proxy that takes the required input from Copilot, run the custom tools and send the output back to Copilot. This article assumes proficiency with the Python programming language and familiarity with Function Apps and the Visual Studio Code (VS Code) development environment. Integrating Azure Function Apps with VS Code The Azure Functions core tools allow development of Functions App locally while the Azure Function extension for VS Code lets you easily deploy Function Apps from VS Code itself. A good description of the setup required to build and deploy Python based Functions apps in VS Code is given here. We highly recommend to setup your VS Code for Azure Function App development, before proceeding further. Once setup of core tools and Azure functions has been completed successfully, authenticate to your Azure account after which an Azure icon is visible in VS Code as highlighted in the circle. Clicking on the icon populates the list of Azure resources in the navigation pane. Sample Use Case Prioritizing remediation for the latest CVEs is an overwhelming task and it is common for security teams to prioritize remediation based on the exploitability of a CVE. It is important to know if a CVE is being actively exploited in the wild and if that particular CVE impacts your environment. Copilot calling Function Apps as a plug-in works well for this use case. The Cybersecurity & Infrastructure Security Agency (CISA) maintains a catalog called the Known Exploited Vulnerabilities (KEV) which contains a list of vulnerabilities that have been exploited in the wild. Since the threat landscape is constantly evolving the KEV catalog is constantly being updated and several organizations use the KEV catalog as one of the sources of Common Vulnerabilities and Exposures (CVEs) that should be scanned in their environment. The KEV catalog is available in CSV and JSON formats which can be downloaded from here. A sample cutout of the CSV file is shown below, notice the several fields available for each CVE: Using Function Apps we will add capability in Copilot to query the CISA KEV catalog and extract the following information: Total number of CVEs present. Number of CVEs present for a Product. Number of CVEs present for a Publisher. List all CVEs for a Product. The above is only a small subset of information that can be extracted from the KEV catalog and you can build upon the code to extract any additional information. Note that Copilot also has in-built Public Web and GPT skills that can extract some information from the KEV catalog, but extracting more specific information requires help of code which a Function App provides. Python Code When building a function app, it is good practice to test the code separately and we recommend encapsulating all the functions you need in a Python Class, which makes porting to a Function App much easier. The following class encapsulates all the functions we need to fulfill the 4 data points we need for our use case, inline comments provide more details on each function. Filename: cisakey.py import requests import ast import json class cisakev: ''' Input: N/A Output: N/A Details: Constructor, intended to be private function ''' def __init__(self): # Hard code URL to the CISA KEV self._url = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json" self._jsonDict = "" # Dictionary to store data from the CISA KEV catalog self.__download__() # Download The file in the constructor ''' Input: N/A Output: N/A Details: Downloads the latest CISA KEV and assign to an internal JSON dictionary Called from constructor, intended as private function ''' def __download__(self): try: response = requests.get(self._url) response.raise_for_status() # Will raise an HTTPError if the HTTP request returned an unsuccessful status code jsonStr = response.content.decode("utf-8") # safe evaluation jsonStr = jsonStr.replace('\\/','') self._jsonDict = ast.literal_eval(jsonStr) # Convert to dictionary from JSON string except requests.RequestException as e: raise SystemExit(f"Failed to make the request. Error: {e}") # Will result in HTTP 500 ''' Input: CVE ID Output: True or False depending if the CVE is present Details: Does an exact match on the CVE after converting input to upper case ''' def isCVEPresent(self, cve): try: cve = cve.upper() for vuln in self._jsonDict["vulnerabilities"]: if(vuln["cveID"] == cve): # Do an exact match return self.getJSONOutput(True) return self.getJSONOutput(False) except Exception as e: raise SystemExit(f"Failed to make the request. Error: {e}") # Will result in HTTP 500 ''' Input: Vendor Name Output: Count of the CVEs tagged with the Vendor Details: Uses a 'contains in' match, where the given vendor name string is contained inside the 'vendorProject' field ''' def countCVEForVendor(self, vendor): try: vendor = vendor.upper() count = 0 for vuln in self._jsonDict["vulnerabilities"]: if(vendor in vuln["vendorProject"].upper()): # contains in match count += 1 return self.getJSONOutput(count) except Exception as e: raise SystemExit(f"Failed to make the request. Error: {e}") # Will result in HTTP 500 ''' Input: Product Name Output: Count of the CVEs tagged with the Product Details: Uses a 'contains in' match, where the given product string is contained inside the 'product' field ''' def countCVEForProduct(self, product): try: product = product.upper() count = 0 for vuln in self._jsonDict["vulnerabilities"]: # contains in match if(product in vuln["product"].upper()): count += 1 return self.getJSONOutput(count) except Exception as e: raise SystemExit(f"Failed to make the request. Error: {e}") # Will result in HTTP 500 ''' Input: Product Name Output: list of the CVEs tagged with the Product, returned as List(Array) Details: Uses a 'contains in' match, where the given product string is contained inside the 'product' field ''' def listCVEForProduct(self, product): try: product = product.upper() cves = [] for vuln in self._jsonDict["vulnerabilities"]: if(product in vuln["product"].upper()): cves.append(vuln["cveID"]) return self.getJSONOutput(cves) except Exception as e: raise SystemExit(f"Failed to make the request. Error: {e}") # Will result in HTTP 500 ''' Input: Numeric or Array Output: Converts the Input to a Json assigned to the variable 'value'. Example: If input is 1, Output will be {"value":1} Details: N/A ''' def getJSONOutput(self, value): sJson = '{"value":' sJson += json.dumps(value) + "}" # Any exception will bubble up return sJson The constructor of this Class (__init__()) calls __download__() which downloads the JSON formatted KEV catalog and stores it in an internal dictionary variable (_jsonDict) which allows search and data extraction convenient. We use the JSON format since it is easier to handle JSON in Python code, but using the CSV is also ok. _jsonDict is referenced by the individual functions that take the required input parameters and return the data after searching _jsonDict. Note that the final output is passed via getJSONOutput(), so that a formatted JSON response is returned. While this is not required, it adds consistency as the output format is like what one should expect from a REST API. The following code is given in a separate file which is used to call the functions in the Class and simulates the Function App. This is good way to insure that correct data is being returned and for more complex functions, using a Pythonic Unit test framework like unittest is better. Filename: consumer.py from cisakev import cisakev def main(): ck = cisakev() print("ck.isCVEPresent('CVE-2021-27102')",ck.isCVEPresent("CVE-2021-27102")) # True print("ck.countCVEForVendor('Linux')",ck.countCVEForVendor("Linux")) # 14 print("ck.countCVEForProduct('Outlook')",ck.countCVEForProduct("Outlook")) # 1 print("ck.listCVEForProduct('Android Kernel')",ck.listCVEForProduct("Android Kernel")) # "CVE-2019-2215", "CVE-2020-0041" if __name__ == '__main__': main() The output of running consumer.py in VS Code is given below: With all functions in the cisakev Class working we are ready to move and build the Function App. Testing Azure Function Apps locally We need to create a Function App project in VSCode. This can be done by bringing in the Command Palette (F1) and selecting ‘Azure Functions: Create New Project….’. or ‘Azure Functions: Create Function App in Azure …’. The latter will also create the necessary Azure App resource in the Azure subscription while with the former option the Azure App has to be created via the Azure console, which is the option we have selected for this article. After choosing ‘Azure Functions: Create New Project….’, select the language as Python, model as ‘Model V2’ and among the many trigger options select the HTTP trigger (allowing our code to run on a Copilot HTTP GET request): Name the function ‘http_trigger_cisakev’: Select ‘Anonymous’ for level control. Anonymous access allows the request to be received from any address, this is a good option for testing. Before we host the function in Azure, we will change it to ‘Function’ via the code, requiring all requests to contain the API Key: Once these steps are complete an Azure Function basic project structure is created, as shown below: function_app.py is where the main code and entry functions resides, and a basic templated code is auto-generated for us: Note that you may have to install the ‘azure.functions’ module (using: pip install azure.functions) before running the code. Next, copy the cisakev.py file previously built in the standalone project to this project. The project structure should look like this: In our API call, we will add two query parameters ‘op’ and ‘data’. The value of ‘op’ will determine which of our 4 functions is called, and the ‘data’ is the parameter to that function. Hence a sample URL to run the Azure function will be: https://?op=’'&data="" We will use Python’s match-case syntax to branch over the op value and call the respective function. The updated code for function_app.py is given, note that we instantiate the cisakev class in the same way we did in the consumer.py code: Filename: function_app.py import azure.functions as func import logging from cisakev import cisakev app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS) # URL /?op=''&data="" @app.route(route="get_cisa_kev") def http_trigger_cisakev(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') op = req.params.get('op') data = req.params.get('data') # Instantiate the cisakev object ck = cisakev() response = "" match op: # Call the function based on the op and pass in data case "cvePresent": # code for pattern 1 response = ck.isCVEPresent(data) case "countCVEForVendor": # code for pattern 2 response = ck.countCVEForVendor(data) case "countCVEForProduct": # code for pattern N response = ck.countCVEForProduct(data) case "listCVEForProduct": # default code block response = ck.listCVEForProduct(data) case _: response = "Input data is missing" return func.HttpResponse(response,status_code=200) With Azure core tools you can run the function app locally (press F5) and for HTTP triggers a local port is made accessible. Note that if code needs storage the ‘Azurite’ emulator needs to be running as explained here. Once the Azure function is loaded and ready, the local URL will made available in the VS Code Terminal pane: Using a REST API client (Boomerang, Postman etc.) we can make calls to the local Azure App and check if all required options are working correctly. The following call checks if a CVE is present in the latest KEV catalog with the JSON response shown in the right. The following call gets the list of CVEs for a specific product: With the Function app working well locally, it’s time to deploy to Azure. Deploying the Azure Function App to Azure We will need a Function App resource that will host and execute the code. The resource can be created from the Azure console or from VS Code using the ‘Azure Functions: Create Function App in Azure …’ command as detailed here. For our example, we created the Function App ‘FA-CISA-KEY’ in Azure and can see it in our resources (resource name: FA-CISA-APP): Once the resource is available, use command template (F1) in VS Code and select ‘Azure Functions: Deploy to Function App…’. However, before proceeding further let’s discuss an important file that contains list of modules our code needs. requirements.txt In our cisakev class we are using the requests module which is not part of standard Python distribution. While we can use pip to install any PyPI package locally we cannot execute pip in Function Apps, so all non-standard modules that are required must be defined in the requirements.txt file, which exists in the main folder of our projects. Make sure not to remove any existing modules present in the requirements.txt file, and after requests is added the contents of this file are: Filename: requirements.txt # DO NOT include azure-functions-worker in this file # The Python Worker is managed by Azure Functions platform # Manually managing azure-functions-worker may cause unexpected issues azure-functions requests With the requirements.txt updated, run ‘Azure Functions: Deploy to Function App…’ command which guides through the steps. Since the Function App is already hosted, you will get to select the function app resource in Azure where the newly developed Function App project must be deployed. The deployment of the function app will erase any previous deployments, VS Code shows a warning as to that respect: After verifying you are updating the right Function App, select ‘Deploy’ to deploy the Function App to Azure. App/API Keys In the code we tested locally, the authentication level was set to Anonymous which allows anyone to call the Function app, which is a really bad idea in a production environment. Hence, we will add some access control using App keys which are Function App’s equivalent to API keys. Function App access can be set to Anonymous, Admin or Function level, while access keys can be Function, Host, Master and System. More details on the access and key types is given here. For our App we will set Function level access and use the default key. The access level is set up in the code. In the function_app.py files change the existing Anonymous access from: app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS) to: app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION) Deploy the app again with this change so that the Function App running in Azure will only allow access if the key is present. The key is accessed in the Azure console via the App Keys link as shown below, and we will use the default key: Testing the Function App It’s time to test our Function app via a REST client as we did before. The URL of the hosted Function App is visible in the Overview pane in Azure console: With this URL let’s head to Boomerang and make a call to see if a CVE is present. We only change the URL and make the call, but we get a HTTP 401 (Access Denied). Can you guess why? Since we have added access control in the code we need to specify the access key in the headers. The header string for Function App access key is ‘x-functions-key’ and the value is copy/pasted from the Azure Console as shown below: With the correct value for ‘x-functions-key’ specified, this call is successful. Let’s make another call to fetch the list of CVEs and confirm it returns the correct data. Now it’s time to have Copilot make calls to our Function app via an API plugin. Copilot API Plugin Copilot’s API plugins needs two JSON or YAML files, and a beginner’s walkthrough for building API plugins is available here. The main plugin YAML file is given below: Filename: API_Plugin_AzureFunction_CISA_KEV.yaml Descriptor: Name: Fetch details from the CISA Known Exploited Vulnerabilities (KEV) list DisplayName: Fetch details from the CISA Known Exploited Vulnerabilities (KEV) list Description: The skills in this plugin will get different information on CVEs present from the latest CISA KEV list DescriptionForModel: The skills in this plugin will get information like CVE count by product or publisher as well as list of CVEs. This information is sourced from the latest CISA Fetch details from the CISA Known Exploited Vulnerabilities (KEV) list. SupportedAuthTypes: - ApiKey Authorization: Type: APIKey Key: x-functions-key Location: Header AuthScheme: '' SkillGroups: - Format: API Settings: OpenApiSpecUrl: http:///API_Plugin_AzureFunction_CISA_KEV_OAPI.yaml Note the ‘Key’ value given in the YAML side, this is inserted into the header by Copilot and should match exactly as the Function App expects it. The OpenAPI specification file for our plugin is given below: Filename: API_Plugin_AzureFunction_CISA_KEV_OAPI.yaml openapi: 3.0.0 info: title: Fetch details from the CISA Known Exploited Vulnerabilities (KEV) list description: The skills in this plugin will get different information on CVEs present from the latest CISA KEV list version: "v1" # This should point to the Azure Function URL servers: - url: https://fa-cisa-kev.azurewebsites.net/api/ paths: /get_cisa_kev?op=cvePresent: get: operationId: cvePresent description: Determines if a given CVE is present in the latest CISA KEV list parameters: - in: query name: data schema: type: string required: true description: The CVE whose presence has to be determined. responses: "200": description: OK content: application/json: /get_cisa_kev?op=countCVEForProduct: get: operationId: countCVEForProduct description: Counts the number of CVEs based on the name of a product, in the latest CISA KEV list parameters: - in: query name: data schema: type: string required: true description: The product name for whom the CVE count has to be determined responses: "200": description: OK content: application/json: /get_cisa_kev?op=listCVEForProduct: get: operationId: listCVEForProduct description: Lists all the CVEs based on the name of a product, in the latest CISA KEV list. parameters: - in: query name: data schema: type: string required: true description: The product name for whom the CVE list has to be determined responses: "200": description: OK content: application/json: /get_cisa_kev?op=countCVEForVendor: get: operationId: countCVEForVendor description: Counts the number of CVEs based on the name of a vendor, in the latest CISA KEV list parameters: - in: query name: data schema: type: string required: true description: The vendor name for whom the CVE count has to be determined responses: "200": description: OK content: application/json: Each of the supported operations is in its own URL (matching to an individual skill) that is given as a fixed query parameter, but the data is taken as an Input. Copilot will insert the data from the user’s prompt, and Copilot’s orchestrator will select the skill (and the plugin) based on the Description and DescriptionForModel defined in both the YAML files. After uploading the OpenAPI specification file to an Internet accessible URL, we are ready to upload the plugin to Copilot. In the Copilot console, select the Plugin sources in command bar: Select Add Plugin: Select the main YAML file for the plugin and press ‘Add’: You will receive a confirmation when the Plugin has been added: After the plugin is added, you will be asked to enter an API key. Copy/Paste the API/App key from the Azure console as discussed earlier and press ‘Save’. With the key entered ensure the custom plugins and this specific plugin are both enabled: We are now ready to test the plugin. Let us first invoke one of the skills directly to confirm Copilot can communicate with our Function App. For directly invoking the skill, click on the ‘Prompts’: And select “See all System capabilities”: Scroll down till the “Fetch Details From…” plugin name is displayed, and within it all four skills will be visible. Select ‘cvePresent’, and enter the same CVE we had been using in our previous tests, click the Submit icon: Copilot will select the CISA KEV plugin and the skill, make a call to the Azure Function App and format the response JSON to natural language: This confirms Copilot can talk to the Function app and we are ready to issue a prompt that leverages the CISA KEV plugin. Let’s start with checking the presence of another CVE: Prompt: “Can you tell me if CVE-2020-12812 is present in the CISA KEV catalog?” Prompt: “Show me the number of CVEs present in the CISA KEV catalog where the vendor is Linux”: Prompt: “Show me the number of CVEs present in the CISA KEV catalog where the product is Outlook”: Prompt: “Show me all the CVEs present in the CISA KEV catalog where the product is Android Kernel”: After getting the CVE list we can query Defender or other sources to check if those CVEs exist in the environment. This highlights how Copilot can pull data from multiple tools and have custom plugins like the one we built, enrich the reasoning from those tools. Prompt: “From Defender, how many of the above CVEs are present in my environment?” Copilot confirms via data from Defender Vulnerability Management that none of the specific CVEs we queried from CISA KEV catalog exist in our environment. In our Function App, for each request the CISA KEV catalog is downloaded. While this ensures we are looking at the latest CISA KEV catalog, if requests from Copilot are coming in frequently the same CISA KEV catalog file is downloaded multiple times. Even though serverless computing like Function App are meant to be stateless, there are ways the downloaded file can be persisted in storage or in memory (via Durable Functions) improving the efficiency of the Function App. However, this is outside the purview of this article and for more information please look in the previous links. In this article, we have shown how to build a Function App and have Copilot use it as an API plugin. This gives Copilot the indirect capability to execute custom code and use it’s output to reason on, opening a wide range of possibilities and use cases. Would be good to start by explaining why this cannot be achieved any other way i.e. in what circumstances is using Function Apps the best or the only way to achieve a certain use cases. Source link Share 0 FacebookTwitterPinterestEmail info.odysseyx@gmail.com previous post Run PowerShell Scripts directly in Logic Apps with inline action next post New Sales Travel Consultant Job Openings in Surat at Intend Travel – Apply Now You may also like 7 Disturbing Tech Trends of 2024 December 19, 2024 AI on phones fails to impress Apple, Samsung users: Survey December 18, 2024 Standout technology products of 2024 December 16, 2024 Is Intel Equivalent to Tech Industry 2024 NY Giant? December 12, 2024 Google’s Willow chip marks breakthrough in quantum computing December 11, 2024 Job seekers are targeted in mobile phishing campaigns December 10, 2024 Leave a Comment Cancel Reply Save my name, email, and website in this browser for the next time I comment.