SITEMAP
This article is Part 2 of the topic presenting how to connect Azure Logic App, Azure Blob Storage, Azure Key Vault, and Aviatrix Controller. together.
The topic is divided into the following parts:
- Part 1: presents the task, algorithm, and the components used ( https://cloud-cod.com/index.php/2024/03/14/azure-logic-app-talking-to-storage-account-and-aviatrix-controller/ )
- Part 2: this article presents the real implementation of the Logic App
Azure Logic App - Managed Identity
The Azure Logic App that we are going to create is going to retrieve some Secrets (more information in the Azure Key Vault section below) from the Key Vault. It is required for the Logic App to be successfully authenticated when asking for Secrets. The System Assigned Managed Identity must be enabled.
Azure Key Vault
There are 3 Secrets stored in Key Vault:
- Aviatrix Controller domain name
- Aviatrix Controller username
- Aviatrix Controller password
Those secrets are going to be retrieved by the Logic App. The Logic App will use them to make API calls to the Aviatrix Controller.
Please notice that there is a Managed Identity turned on for the Logic App (more on that above in the Azure Logic App section). What is more, the Managed Identity (Azure Logic App) has a Key Vault Secrets User Role assigned.
Azure Blob Storage
I have created a Storage Account avtxstorageroute. Additionally, there is a Container avtxstorageroute created inside it.
Furthermore, there is a Storage Blob Data Contributor Role assigned to Azure Logic App Managed Identity. The Logic App must be able to Create, Read, and Delete Blobs.
Azure Logic App
Once all the related services and components are ready (Azure Key Vault, Azure Blob Storage) we can proceed with creating the Logic App. The complete application looks like below. In the next sections, I will focus on all the parts one by one.
The complete application looks like below. In the next sections, I will focus on all the parts one by one.
Part 1 - Scheduling application
The application will be triggered once per day.
Part 2 - Retrieving Secrets from Key Vault
As mentioned earlier, there are 3 Secrets stored in the Key Vault. Those Secrets are needed by the API call that we are going to execute. The part below retrieves the Secrets.
Please notice that I have marked the controllerpassword and the controlleruser as sensitive. The reason is that I do not want them to be shown as plain text, of course. Go to Settings -> and enable Secure Outputs:
Part 3 - Variables initialization
The following variables will be used:
- listofgateways
- the list of Aviatrix Transit Gateways
- the value of this variable will be taken from Blob Storage gw_list.txt
- once received from Blob Storage the content will be parsed to extract the names of Gateways
The “Select an output from previous steps” is:
body('Parse_JSON')
You could paste the content of gw_list.txt in “Use sample payload to generate schema” (Parse JSON block). And this is what the content of gw_list.txt looks like:
- approvedcidrlist
- stores the list of approved CIDRs (for Connection Mode gateways)
- gwapprovedcidrlist
- stores the list of approved CIDRs (for Gateway Mode gateways)
- oldapprovednumber
- storese the number of approved CIDRs (for Connection Mode gateways)
- oldgwapprovednumber
- stores the number of approved CIDRs (for Gateway Mode gateways)
Part 4 - Get CID API call
Next step, we can execute the API call to get CID from the Aviatrix Controller. The Body of the Response must be parsed to extract the CID.
The URI of the API call:
https://@{body('Get_secret_controllerdomain')?['value']}/v1/api?action=login&username=@{body('Get_secret_controlleruser')?['value']}&password=@{body('Get_secret_controllerpassword')?['value']}
The schema:
{
"properties": {
"CID": {
"type": "string"
},
"results": {
"type": "string"
},
"return": {
"type": "boolean"
},
"warning": {
"type": "string"
}
},
"type": "object"
}
Part 5 - Checking approved CIDRs
Now it is high time to take a closer look at the part that is checking whether the current number of approved CIDRs is the same as the previous one. The expanded section looks like below:
Part 5.1 - Get Route Approval Info
The FOR-EACH loop will execute the API call for every Gateway listed in the variable listofgateways. The purpose is to get Route Approval Information.
The “Select an output from previous steps” is:
variables('listofgateways')
The “Current item” is:
items('For_each_gateway')
The URI:
https://@{body('Get_secret_controllerdomain')?['value']}/v1/api?action=show_transit_learned_cidrs_approval_info&CID=@{body('Parse_JSON_HTTP_Get_CID_Response')?['CID']}&gateway_name=@{items('For_each_gateway')}
The Schema:
{
"properties": {
"results": {
"properties": {
"activemesh_2_0": {
"type": "boolean"
},
"approved_learned_cidrs": {
"type": "array"
},
"connection_learned_cidrs_approval_info": {
"items": {
"properties": {
"conn_approved_learned_cidrs": {
"items": {
"type": "string"
},
"type": "array"
},
"conn_learned_cidrs_approval": {
"type": "string"
},
"conn_name": {
"type": "string"
},
"conn_pending_learned_cidrs": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"conn_approved_learned_cidrs",
"conn_learned_cidrs_approval",
"conn_name",
"conn_pending_learned_cidrs"
],
"type": "object"
},
"type": "array"
},
"learned_cidrs_approval": {
"type": "string"
},
"learned_cidrs_approval_mode": {
"type": "string"
},
"pending_learned_cidrs": {
"type": "array"
},
"transit_gw": {
"type": "string"
}
},
"type": "object"
},
"return": {
"type": "boolean"
},
"warning": {
"type": "string"
}
},
"type": "object"
}
You could use the following payload to generate the Schema:
{
"return": true,
"results": {
"activemesh_2_0": true,
"approved_learned_cidrs": [],
"connection_learned_cidrs_approval_info": [
{
"conn_approved_learned_cidrs": [
"1.1.1.1/32",
"2.2.2.2/32"
],
"conn_learned_cidrs_approval": "yes",
"conn_name": "t2s",
"conn_pending_learned_cidrs": []
},
{
"conn_approved_learned_cidrs": [
"1.1.1.1/32",
"2.2.2.2/32"
],
"conn_learned_cidrs_approval": "yes",
"conn_name": "fakeconn",
"conn_pending_learned_cidrs": []
}
],
"learned_cidrs_approval": "no",
"learned_cidrs_approval_mode": "connection",
"pending_learned_cidrs": [],
"transit_gw": "az-10-130-tgw"
},
"warning": "API token is introduced in 7.0 and will be enforced in future release. For more detail, refer to https://docs.aviatrix.com/documentation/latest/release-notes/field-notices/field-notices.html#field-notice-41 for more information"
}
Part 5.2 - Store Route Approval Info in Blob
The response received after executing the API call (Route Approval Info) will be stored in Blob Storage. Before saving it in Blob we have to use the Compose action to be able to manipulate/transform the data. The Blob will be named in the following way: <transit-gw-name>-timestamp.json .
The Compose Inputs is:
body('Parse_JSON_HTTP_Learned_CIDR_List')
The Blob Name:
concat(items('For_each_gateway'),'-',formatDateTime(utcNow(), 'yyyy-MM-ddTHH-mm-ssZ'),'.json')
The Blob Content:
outputs('Compose')
Part 5.3 - Check Condition (Connection Mode or Gateway Mode?)
The condition verifies (for every Gateway) whether the Route Approval Mode (learned_cidrs_approval_mode) is Connection (TRUE) or Gateway (FALSE).
body('Parse_JSON_HTTP_Learned_CIDR_List')?['results']?['learned_cidrs_approval_mode']
There are two different approaches for checking the number of Approved CIDRs:
- in the case of Gateway using Connection Mode, the number of Approved CIDRs must be checked for every VPN Connection (for-each loop is required here)
- in the case of Gateway using Gateway Mode, it is enough to check the number of CIDRs just once
Part 5.4 - Connection Mode
For every VPN connection (connection_learned_cidrs_approval_info) the following is done:
- variable approvedcidrlist is set to the value conn_approved_learned_cidrs taken from the API call response
- the number of approved CIDRs is counted. This is our “current” (new) amount of CIDRs
The Value:
items('For_each_connection')?['conn_approved_learned_cidrs']
The Input for the Compose Expression:
length(variables('approvedcidrslist'))
- to compare the “current” (new) number of Approved CIDRs to the old/previous value we have to first retrieve the “old/previous” number from Blob (names of Blob we are checking are: <transit-gw-name>-<connection-name>.txt
The transit_gw:
body('Parse_JSON_HTTP_Learned_CIDR_List')?['results']?['transit_gw']
The conn_name:
items('For_each_connection')?['conn_name']
- once we have the “old/previous” number of Approved CIDRs we assign it to a variable called oldapprovednumber
The Value of File Content:
body('Get_blob_content_(V2)_previousapprovednumber')
- the Condition checks whether the new number of Approved CIDRs (taken from Compose_count_number_of_conn_approved_cidrs action) equals the “previous” number of approved CIDRs (oldapprovednumber)
The condition:
outputs('Compose_count_number_of_conn_approved_cidrs')
equals to Expression:
int(variables('oldapprovednumber'))
There are two possible results of the comparison:
- TRUE, means that the new number of Approved CIDRs is the same as the old one. In such a case we do not have to do anything because it means that the number of Approved CIDRs has not changed
- FALSE, means that the number of Approved CIDRs changed. If so, we will update the number stored in Blob (using Delete Blob) and Create Blob actions). The name of the Blob we are going to update (Delete/Create) is <transit-gw-name>-<conn-name>.txt .
Optionally (not shown here), we could also send an e-mail or MS Adaptive Card using an appropriate Action. The following article presents how to use Adaptive Cards: https://cloud-cod.com/index.php/2024/02/26/azure-app-registration-clien-secret-expiration-notification/ .
The transit_gw:
body('Parse_JSON_HTTP_Learned_CIDR_List')?['results']?['transit_gw']
The conn_name:
items('For_each_connection')?['conn_name']
The outputs:
outputs('Compose_count_number_of_conn_approved_cidrs')
Part 5.5 - Gateway Mode
Similar Actions are executed in case the Gateway is using Gateway Mode and not Connection Mode (Connection Mode is described in part 5.4). The only difference is that for the Gateway Mode, the for-each loop is not required because the application will be executed just once.
I also have name the variables in a quite different way but the purpose of them is the same:
- gwapprovedlist (instead of approvedcidrlist for Connection Mode)
- oldgwapprovednumber (instead of oldapprovednumber for Connection Mode)
Details:
- The Set variable gwapprovedlist
body('Parse_JSON_HTTP_Learned_CIDR_List')?['results']?['approved_learned_cidrs']
- The Compose Expression part
length(variables('gwapprovedlist'))
- The transit_gw:
body('Parse_JSON_HTTP_Learned_CIDR_List')?['results']?['transit_gw']
- The Set variable oldgwapprovednumber:
body('Get_blob_content_(V2)_previousgwapprovednumber')
- The Condition checks the number of approved CIDRs (“new” vs “old”) similarly to the way we set up the Connection Mode gateways.
- transit-gw dynamic content is:
body('Prase_JSON_HTTP_Learned_CDIR_List')?['results']?['transit_gw']
- Outputs dynamic content is:
outputs('Compose_count_number_of_gw_approved_cidrs')
Optionally (not shown here), we could also send an e-mail or MS Adaptive Card using an appropriate Action. The following article presents how to use Adaptive Cards: https://cloud-cod.com/index.php/2024/02/26/azure-app-registration-clien-secret-expiration-notification/ .
Conclusions
The Logic App presented below is just an example of how to easily set up an integration between different Azure services (e.g. Key Vault, Blob Storage) and external HTTP Endpoints (e.g. Aviatrix Controller).
The complete Logic App will the for-each loops and conditions: