Imagine you could use your favorite infrastructure provisioning tool (CloudFormation) to deploy resources to your favorite cloud provider (Azure). If that idea tickles your fancy, read on. This repository shows you how to realize this idea. We will create a CloudFormation custom resource backed by a Lambda function. This function will use an Azure service principal in order to set up Azure infrastructure via ARM-templates. The ARM-template is passed in as a parameter to the custom resource.
The CloudFormation custom resource will create a new resource group, or use an existing resource group if one exists with the name provided during deployment. When you delete the corresponding CloudFormation stack the Azure resource group will be deleted as well. You have been warned.
As this is a rather advanced example I will assume you know what you are getting yourself into. In short, to follow this example as-is you will need the AWS CLI, the SAM CLI, and Docker. The commands shown below assumes you use the default AWS profile.
To authenticate the API calls from AWS to Azure we need to create an app registration and assign an appropriate role to the resulting service principal. It is not possible to create this registration using regular ARM templates or Azure Bicep. Follow the steps below to set this up.
- Go to the Azure portal
- Go to Azure Active Directory
- Click on
App registrations
in the left-hand menu - Click on
+ New registration
- Give the registration a suitable name, e.g.
aws-deployment-app
- Click on
Register
- Copy and save the value for the
Application (client ID)
and theDirectory (tenant) ID
shown on the overview page - Click on
Certificates & secrets
in the left-hand menu - Click on
+ New client secret
- Enter a description for the secret and set a suitable expiration time, then click on
Add
- Copy and save the secret value
The three values you have stored from the above procedure will be used as input parameters when we set up the CloudFormation custom resource in AWS. Follow the steps below to assign an appropriate role to the service principal.
- Go to your subscription
- Click on
Access control (IAM)
in the left-hand menu - Click on
+ Add
then onAdd role assignment
- Select a role, e.g.
Contributor
and search for the name of the service principal and select it - Click
Save
The role you assign will determine what operations the custom resource in AWS will be able to do. To cover many different scenarios you should set the Contributor
role. If your template includes role-assignments you will need to give your service principal additional roles, or even set it as Owner
on your subscription.
Now we are ready to create the CloudFormation custom resource in AWS. We will use AWS SAM for this purpose.
# build the custom resource
sam build --use-container
# deploy the custom resource
sam deploy --guided
SAM will ask you to provide a number of inputs before it creates the custom resource. When prompted, provide the values for the client ID, the client Secret, and your tenant ID.
Now we have everything we need to deploy ARM-templates from within our CloudFormation templates. A sample hello-world resource is shown below. The custom resource ServiceToken
is available as an exported value in CloudFormation, we can reference it with !ImportValue AzureResourceManager
.
AWSTemplateFormatVersion: "2010-09-09"
Description: Hello-World Example
Parameters:
AzureSubscriptionID:
Type: String
AzureResourceGroup:
Type: String
AzureLocation:
Type: String
Resources:
AzureResourceManagerDeployment:
Type: Custom::AzureResourceManager
Properties:
ServiceToken: !ImportValue AzureResourceManager
SubscriptionID: !Ref AzureSubscriptionID
ResourceGroup: !Ref AzureResourceGroup
Location: !Ref AzureLocation
DeploymentName: "aws-deployment"
TemplateParameters:
yourName: "Mattias"
Template: |
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"yourName": {
"type": "string"
}
},
"variables": {
"hello": "Hello World! - Hi"
},
"resources": {},
"outputs": {
"helloWorld": {
"type": "string",
"value": "[format('{0} {1}', variables('hello'), parameters('yourName'))]"
}
}
}
Outputs:
HelloWorld:
Value: !GetAtt AzureResourceManagerDeployment.helloWorld
You must provide SubscriptionID
, ResourceGroup
, Location
and Template
when you create the resource. The DeploymentName
is optional. If your ARM-template requires that you specify input parameters you can provide these in TemplateParameters
. Any outputs from your deployment can be accessed with !GetAtt <resource logical name>.<ARM-template output name>
.
An advanced example is provided in template.yaml. This example creates an Azure Container Instance and an AWS HTTP API Gateway. The API Gateway will direct traffic to the container in Azure. Deploy this example with the following command.
aws cloudformation deploy \
--stack-name AzureExample \
--template-file ./cloudformation/template.yaml \
--parameter-overrides \
AzureSubscriptionID=<subscription ID> \
AzureResourceGroup=<resource group name> \
AzureLocation=<Azure location>
- As of October 2021 there is no support to transform a Bicep template to an ARM-template using the Azure SDK for Python. As far as I know this is only possible using the Bicep C# library. Thus the example provided here does not support Bicep templates. As a work-around you can transform your Bicep templates to ARM-templates using the Bicep CLI.