Azure Logic App talking to Storage Account, Azure Key Vault, and Aviatrix Controller – Part 2

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:

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.

logicappmanagedidentity

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:

Leave a Reply

Your email address will not be published. Required fields are marked *