This project shows how to build an Azure Marketplace Portal and webhook using a serverless setup. The scripting used to deploy the application assumes a bash shell. The application front end follows a minimalist design with a goal of making it easier for a web developer to customize the UI to meet their company styling needs. The only external JavaScript libaries in use are:
- jQuery: Used to do simple manipulation of the web page and send XmlHttpRequests.
- Azure App Insights: Used to track behavior on the deployed portal page.
This sample shows how to setup a SaaS Azure Marketplace Portal page using a custom domain. To follow along, it is assumed that you have a certificate which minimally supports two names:
- yourdomain.com
- www.yourdomain.com
Wildcard certificates, such as *.yourdomain.com, definitely work. You will need the certificate in the form of a .PFX.
You will need some packages installed on your machine. The scripts and other pieces all assume a bash environment. Bash environments are available on Linux, Windows, and macOS.
The following packages must be installed in order to deploy your portal:
- Terraform: Deploys to Azure.
- Azure Command Line Interface: Used for interacting with Azure; some functionality is in the CLI but not in the portal.
- Azure Functions Core Tools: Allows for local development and debugging. Also used to upload to Azure.
- pyenv: For the Python back-end example, this is a handy way to install a Python environment which is compatible for use with Azure Functions Core Tools. If you have other mechanisms, great!
- Visual Studio Code: While you can certainly use your tool of choice, VS Code has handy integrations for all things Azure. In our case, it integrates well with Azure Functions Core Tools and makes debugging easy.
To login to Azure and select a default subscription, do the following from the command line:
- Run
az login
- From the output, set the active subscription with
az account set -s [subscription UUID]
The scripts will require that the Azure CDN is a selectable entity from your IAM configuration when you configure Key Vault a bit later. This is a one time operation on the subscription. To do this:
- Login using these steps.
- Run
az ad sp create --id 205478c0-bd83-4e1b-a9d6-db63a3e1e1c8
The Terraform scripts and other login scripts use a service principal login instead of an interactive login. This was done to allow for a CI/CD pipeline to be built around this sample.
Please note that variables.conf
should never be checked into source control. This file contains
secrets which, if exposed to a github crawl, would open your resources to misuse by others.
All the values stored in variables.conf may also be stored as environment variables in a build
environment. If you choose to do this, make sure to remove references to variables.conf from
/src/deployment/deploy.sh
.
To create the service prinicipal and configure the solution, follow these steps.
- Login using these steps.
- Create the service principal:
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/[SUBSCRIPTION_ID]"
. This generates output like:
{
"appId": "<app UUID>",
"displayName": "azure-cli-<YEAR-MONTH-DATE-HOUR-MINUTE-SECOND>",
"name": "http://azure-cli-<YEAR-MONTH-DATE-HOUR-MINUTE-SECOND>",
"password": "<password as a UUID>",
"tenant": "<tenant UUID>"
}
Save this output for use in step 5.
- Open the file
/src/deployment/variables.conf.template
. - Save the file
/src/deployment/variables.conf.template
as/src/deployment/variables.conf
. - Decide what region to deploy to. To get a list of regions, use
az account list-locations | jq '[.[] | {name: .name, displayName: .displayName}]'
. You will use thename
in the next step when filling inresource_group_location
. - Set the values in variables.conf.template by copying values from the output of the service principal creation as follows (Note: we will fill in the storage_access_key [in a moment](#Setup-Terraform-shared state-management-for-deployments)):
service_principal_client_id=[name]
service_principal_client_secret=[password]
azure_ad_tenant_id=[tenant]
azure_subscription_id=[SUBSCRIPTION_ID]
resource_group_location=[region name]
base_name=[base name to be used for created resources, eg. contosoinc]
domain_name=[yourdomain.com (eg. the name of the domain associated with your certificate)]
storage_access_key=[access key for the Terraform state storage account]
- Save
/src/deployment/variables.conf
.
Because this is designed to run in a CI/CD pipeline, we want the Terraform state information to be persisted to a shared location. For this, we want to setup a connection to Azure Storage.
- From the
/src/deployment/terraform_state
directory, runterraform init
. This initializes your Terraform environment in this directory. You only need to do this step once. - Run
/src/deployment/terraform_state/deploy.sh
. Output from this command will look something like this:
Outputs:
properties = [STORAGE KEY]
- Open
/src/deployment/variables.conf
. Remote state info doesn't "do" variables, so we'll be entering this in manually. - Set
storage_access_key
to[STORAGE KEY]
- Save
/src/deployment/variables.conf
.
- Open up the Azure portal and open your AAD tenant.
- Select Custom domain names as shown below.
- Select + Add custom domain.
- Add the domain. This should match the domain you use in your certificate.
- Go to your registrar and add the appropriate TXT record. Once done, click Verify.
You now have your custom domain registered.
Now, we will deploy the application to Azure.
- From the
/src/deployment
directory, runterraform init
. This initializes your Terraform environment in this directory. You only need to do this step once. When done, the state file will be stored based on the setup from Setup Terraform shared state management for deployments. - From the
/src/deployment
directory, rundeploy.sh
. This will provision the marketplace portal.
Once complete, there are some manual steps we need to perform to setup the security, SSL, and custom domains for the application.
The function application offers a feature called
EasyAuth.
This is a simplified endpoint that handles a lot of the AAD complexities. We will set this up next.
For this, you need to open the Functions Application named [base_name]func
. Follow these steps to setup
AAD authentication.
- Select Platform features.
- Under Networking, click on Authentication / Authorization.
- Set App Service Authentication to On.
- Set Action to take when request is not authenticated to Log in with Azure Active Directory.
- Click on Azure Active Directory.
- For Management mode, select Express. Note: we will return here in a bit. We are using the "easy" path to configure things correctly.
- Leave the defaults as is and click on OK.
- Add the following to Allowed External Redirect URLs:
- Localhost testing:
http://localhost:63342/azuremarketplace/src/portal/index.html
- Public website:
https://[your domain]/index.html
- Localhost testing:
-
Click on Save.
-
Navigate to Azure Active Directory within the portal.
-
Select App Registrations.
- Select your portal app,
[base_name]func
. We are going to update the following items, in this order:- Branding
- Expose an API
- Authentication
-
On Branding, set the various fields to appropriate values. This includes updating the Publisher Domain to
https://[your domain]
. -
On Expose an API, change the Application ID URI to
https://[your tenant].onmicrosoft.com/[base_name]func
. -
On Authentication, for Supported account types, do the following:
- Select Accounts in any organizational directory (Any Azure AD directory - Multitenant).
- Click on Save.
-
Open the Functions Application named
[base_name]func
. -
Select Platform features.
-
Under Networking, click on Authentication / Authorization.
-
Click on Azure Active Directory.
-
Set Management mode to Advanced.
-
Clear Issuer Url. This allows the application to use the common login, required for multi-tenant AAD authentication.
-
Click on OK, then Save.
Here, we are going to update the CDN endpoint mapping our storage account containing our HTML, CSS, and JavaScript. Once you are done with this piece, it can take 10 minutes for the files to propagate to the CDN.
- From the Azure portal, find the CDN named `[base_name]cdn.
- From the endpoint list, select the endpoint named
[base_name]funcep
. - Under Settings, select Origin.
- Set the Origin path to
/web
. - Click on Save.
- Select Custom domains.
- Click on + Custom domain.
- Set the Custom hostname to
[your domain]
. You can also use a different value, likeportal.[your domain]
orazuremarketplace.[your domain]
.- To complete this, you will also need to add an CNAME record in your DNS to
point to the endpoint hostname,
[base_name]funcep.azureedge.net
.
- To complete this, you will also need to add an CNAME record in your DNS to
point to the endpoint hostname,
- Click on Add when you are done. Once the custom hostname is ready, you will see something like this:
- Click on the Custom domain you just added.
- Set Certificate management type to CDN managed.
- Press Save.