Skip to main content

Getting started with Terraform

Prerequisites

  1. (Suggested) Chocholaty installed https://community.chocolatey.org/courses/installation/installing?method=install-from-powershell-v3
  2. Terraform.exe available locally and in path (choco install terraform -s https://chocolatey.org/api/v2/).
  3. Azure command line tool (choco install azure-cli -s https://chocolatey.org/api/v2/)
  4. A pre-existing Azure Resource Group to which the resources will get deployed
  5. A user account having contributor role in the resource group
  6. Access to DevOps and Respositories.
  7. Have the Terraform repo cloned to your machines

Setup Steps

[!NOTE] Have your Azure AD (not B2C) account ready. Also, your AAD tenant GUID and subscription GUIDs.

Run Terraform to create the resources

  1. Login to the B2C tenant with az login --tenant {tenantGuid}
  2. Set the subscription on which the resource group is created az account set --subscription {subscriptionId}
  3. Open AuthEngine-IaC.tfvars.json and update all the variable values
  4. Run terraform init
  5. Run terraform plan -out AuthEngine-IaC.tfplan -var-file="AuthEngine-IaC.tfvars.json"
  6. Verify that the changes are what you would expect.
  7. Run terraform apply AuthEngine-IaC.tfplan

Run Terraform output

To configure the variable group, we can use Terraform to output the values of the configuration that we need.

To do this, run terraform output -json > tfoutput.json Then, open the file. Note that there will be secrets displayed in plaintext here so treat with care.

Azure AD App Registration

Complete the steps given below to set up Microsoft Azure AD app for authentication.

  1. Log on to the corresponding Azure portal directory with a user having administrative rights.
  2. Go to Azure Active Directory > App Registration.
  3. Register a new application under new registration.
    1. Give an appropriate name to the application.
    2. Under Supported account types, select Accounts in this organizational directory only ( tenantname - Single tenant).
  4. Open the newly created application and go to the Authentication tab.
  5. Under platform configurations, add a redirect URI as given below
    1. Select 'Add a platform'
    2. Select 'Single-page application' from the modal.
    3. Set Redirect URI in the format {siteurl}/auth.
    4. Get the API application's base url from the terraform output and replace the {siteurl}. Eg: If the siteurl is https://mytestapplicationurl.com, the Redirect URI should be set as https://mytestapplicationurl.com/auth
  6. Make sure Access tokens and ID tokens are selected under Implicit grant and hybrid flows.
  7. Find Advanced settings and set 'Allow public client flows' to Yes.
  8. Go to the Certificates & secrets tab, and add a new client secret. Record this secret value.
  9. Go to Token configuration and select Add groups claim. In the modal that opens up, select all checkboxes and save.

Add groups claim

  1. Go to Expose an API tab and add a scope by following the steps below

    1. Select Add a scope and click Save and continue from modal.
    2. On the next page, set the scope name as 'access_as_user'.
    3. Set Admins and users for Who can consent?
    4. Provide Admin consent display name and Admin consent description.
    5. Ensure state is Enabled and save it.

    Add scopes

  2. Select Add a client application, provide the application's id as the Client ID and select the access_as_user scope that was previously created and save it.

  3. Go to the API Permission tab and configure it by following the steps given below.

    1. Add Group.Read.All, GroupMember.Read.All, User.Read.All Microsoft Graph application permissions.
    2. Add the access_as_user permission created from Add a permission > My APIs > Select the current application.
    3. Grant admin consent for these permissions.

    Add permissions

Configure the B2C Tenant

The B2C tenant whose users the Authorization Engine will be managing. This can be an existing B2C Tenant or a new one. The tenant must be configured as follows:

  1. Create an Azure AD B2C Tenant - skip if you already have a B2C Tenant.
  2. Register the Authorization Engine as an application within your B2C Tenant.

Registering RAMPART in your B2C Tenant

The Authorization Engine uses application access tokens to connect to your tenant through Microsoft Graph. To acquire said tokens, an application must be registered for it with the necessary permissions.

  1. Click this link to open the Azure Portal and load up Active Directory. Alternatively, open the Azure Portal and search for "Azure Active Directory". Make sure you're in your B2C Tenant.

  2. On the left blade, select App registrations and then New Registration.

    Adding the registration

  3. In the Register an Application menu:

    • Provide the name as "WhoIAM Authorization Engine" (or whatever helps you identify this application)
    • For supported account types, select Accounts in this organizational directory only
    • Leave the Redirect URI empty.
    • In permissions, leave Grant admin consent to openid and offline_access permissions checked.\

    Registering the app - data

    • Verify and hit register.
  4. Your app registration will be created. Now add the required permissions.

    • Click the API Permissions blade.
    • Click on Add a permission.
    • In the request API permissions dialog box, select Microsoft Graph. Opening the permissions add menu
    • Select Application Permissions
    • Search for and add the permissions Application.ReadWrite.All and User.ReadWrite.All Adding Permissions
  5. Now grant admin consent for your application Granting Admin Consent for your application

  6. Once consent has been granted successfully, go to the Certificates & Secrets blade and add a Client secret. Provide a meaningful description and expiry. Adding a secret

  7. Note the generated client secret and the client ID of the application. You will need them to configure the Management API. Copying Client ID and secret

Create tenant secrets

  1. Navigate to "Policy keys"
  2. Create a new key and select "Manual"
  3. For the name, use AccessRequestApiKey
  4. For the value, use ApplicationAccessFunctionKeyValue
  5. Click "Save"
  6. Repeat the steps to map...
  • (B2C KeyName) InvitationRedemptionApiKey --> (Terraform) InvitationRedemptionFunctionKeyValue
  • (B2C KeyName) PolicyExecutionApiKey --> (Terraform) AuthorizationPolicyExecutionFunctionKeyValue

Self-Signed Certificate for Invitations

TODO: this could be skippable, but B2C policy keys are required if all the policies should be uploaded

In the same B2C key management blade, choose "Upload"

  • Run the certificate generation script in the Terraform repository in the "scripts" folder as administrator
  • This will create a self-signed certificate for use with the invitations feature of RAMPART
  • Enter a password for the script. Then, navigate to the /assets folder and verify that a RAMPARTCert.pfx file was created
  • Upload this cert and enter the password in B2C.
  • Use InvitationCert as the name

[!NOTE] You should now have the following keys: B2C_1A_AccessRequestApiKey, B2C_1A_InvitationCert, B2C_1A_InvitationRedemptionApiKey, B2C_1A_PolicyExecutionApiKey. They'll be needed in the next section.

Release pipeline setup

Typically, RAMPART will exist in its' own pipeline, separate from B2C policy and UI deployments. However, since it relies on values from those deployments, we can create the following variable groups:

  1. B2C Policies
  2. RAMPART Core
  3. RAMPART Invitations
  4. RAMPART Password Reset

B2C Policy Variables

NameValue
AccessRequestApiBaseUrlAccessRequestApiBaseUrl from Terraform
AccessRequestApiKeyNameB2C_1A_AccessRequestApiKey
APIMetadataUrlRAMPART-APIMetadataUrl from Terraform
AppInsightsDeveloperModetrue for dev/test, false for production
AppInsightsInstrumentationKeyInstrumentation key of the App Insights resource to send B2C logs to
AzureIEFAppIdIdentityExperienceFramework application ID
AzureProxyIEFAppIdProxyIdentityExperienceFramework app id
DeploymentModeDeploymentMode="Development" or DeploymentMode="Production" depending on the environment
ExtensionsAppIdapp ID of the extensions app
ExtensionsAppObjectIdobject ID of the extensions app
IdTokenHintIssuerA randomly generated GUID/UUID to use as the issuer for the invitation policy
InvitationCertificateB2C_1A_InvitationCert
InvitationRedemptionApiBaseUrlInvitationRedemptionApiBaseUrl from Terraform
InvitationRedemptionApiKeyNameB2C_1A_InvitationRedemptionApiKey
PolicyDeployAppIdApplication ID of the policy deployment app registration Required to push policy updates.
PolicyDeployAppSecretSecret of the policy deployment app registration. Required to push policy updates.
PolicyExecutionApiBaseUrlPolicyExecutionApiBaseUrl from Terraform
PolicyExecutionApiKeyNameB2C_1A_PolicyExecutionApiKey
ScriptExecutionElement<ScriptExecution>Allow</ScriptExecution>
StorageAccountNameStorageAccountName from Terraform
StorageBaseUrlhttps://$(StorageAccountName).blob.core.windows.net/$(UIContainerName)
TenantGuidTenant ID of the B2C tenant. Find under any app registration
TenantShortFirst part of the tenant name before .onmicrosoft.com (e.g. myorg in myorg.onmicrosoft.com)
UIBaseUrl$(StorageBaseUrl)/ui/
UIContainerNameBlob storage container for the ui (e.g. b2cui)

RAMPART Core

NameValue
AzureAD-ClientIdClientID of the AAD app registration
AzureAD-ClientSecretClient secret of the AAD app registration
AzureAD-TenantIdTenant ID of the AAD tenant
ApplicationInsights-ConnectionStringAppInsights-ConnectionString from Terraform
AzureADB2C-AppIdB2C app id from above
AzureADB2C-ClientSecretB2C app registration client secret
AzureADB2C-TenantDomain{tenantShort}.b2clogin.com
AzureADB2C-TenantFriendlyNameShort description of your B2C tenant
AzureAdB2C-TenantIdTenant ID of your B2C tenant
CosmosDb-AccountCosmosDBEndpoint from Terraform
CosmosDb-DatabaseNameCosmosDBName from Terraform
CosmosDb-DBPrefixCosmosDBPrefix from Terraform
CosmosDb-KeyCosmosDBPrimaryKey from Terraform

Release pipeline setup

Release pipeline overview

Release pipeline overview

Deploy UI/API to Azure

Use the following mapping for the App Service configuration settings

-AzureAD:TenantId "$(AzureAD-TenantId)" -AzureAD:ClientId "$(AzureAD-ClientId)" -AzureAD:ClientSecret "$(AzureAD-ClientSecret)" -AzureADB2C:TenantId "$(AzureADB2C-TenantId)" -AzureADB2C:TenantDomain "$(AzureADB2C-TenantDomain)" -AzureADB2C:TenantFriendlyName "$(AzureADB2C-TenantFriendlyName)" -AzureADB2C:AppId "$(AzureADB2C-AppId)" -AzureADB2C:ClientSecret "$(AzureADB2C-ClientSecret)" -AzureADB2C:B2CExtensionAppId "$(ExtensionsAppId)" -AzureADB2C:IdentityExperienceFrameworkAppId "$(AzureIEFAppId)" -AzureADB2C:ProxyIdentityExperienceFrameworkAppId "$(AzureProxyIEFAppId)" -AzureADB2C:PasswordReset:ApplicationId "$(AzureADB2C-PasswordReset-ApplicationId)" -AzureADB2C:PasswordReset:PolicyReturnUrl "$(AzureADB2C-PasswordReset-PolicyReturnUrl)" -AzureADB2C:PasswordReset:PolicyId "$(AzureADB2C-PasswordReset-PolicyId)" -AzureADB2C:PasswordReset:EmailFrom "$(AzureADB2C-PasswordReset-EmailFrom)" -AzureADB2C:PasswordReset:EmailTemplateId "$(AzureADB2C-PasswordReset-EmailTemplateId)" -AzureADB2C:Invitation:PolicyId "$(AzureADB2C-Invitation-PolicyId)" -AzureADB2C:Invitation:PolicyReturnUrl "$(AzureADB2C-Invitation-PolicyReturnUrl)" -AzureADB2C:Invitation:EmailFrom "$(AzureADB2C-Invitation-EmailFrom)" -AzureADB2C:Invitation:EmailTemplateId "$(AzureADB2C-Invitation-EmailTemplateId)" -AzureADB2C:Invitation:ExpirationDuration "$(AzureADB2C-Invitation-ExpirationDuration)" -AzureADB2C:Invitation:InvitationBaseUrl "$(AzureADB2C-Invitation-InvitationBaseUrl)" -AzureADB2C:Invitation:DefaultApplicationId "$(AzureADB2C-Invitation-DefaultApplicationId)" -AzureADB2C:MFAReset:Enabled "$(AzureADB2C-MFAReset-Enabled)" -CosmosDb:Account "$(CosmosDb-Account)" -CosmosDb:Key "$(CosmosDb-Key)" -CosmosDb:DatabaseName "$(CosmosDb-DatabaseName)" -CosmosDb:DBPrefix "$(CosmosDb-DBPrefix)" -SendGrid:ApiKey "$(SendGrid-ApiKey)" -AzureADB2C:CertificateThumbprint "$(AzureADB2C-CertificateThumbprint)" -WEBSITE_LOAD_CERTIFICATES "$(AzureADB2C-CertificateThumbprint)" -AzureADB2C:TokenIssuer "$(IdTokenHintIssuer)"

Deploy Function Apps

Function Apps

Use the function apps task in DevOps. Link the artifacts according to the Package or folder section in each app.

Invitation Redemption Package or folder: $(System.DefaultWorkingDirectory)/_GateKeeperBuild/InvitationRedemption/*.zip App Name: select from dropdown Application and Configuration Settings --> App Settings:

-APPINSIGHTS_INSTRUMENTATIONKEY "$(AppInsightsInstrumentationKey)" -APPLICATIONINSIGHTS_CONNECTION_STRING "$(ApplicationInsights-ConnectionString)" -Cosmos:Account "$(CosmosDb-Account)" -Cosmos:DbPrefix "$(CosmosDb-DBPrefix)" -Cosmos:Key "$(CosmosDb-Key)" -FUNCTIONS_EXTENSION_VERSION ~4 -WEBSITE_ENABLE_SYNC_UPDATE_SITE true -Cosmos:DatabaseName "$(CosmosDb-DatabaseName)" -AzureADB2C:TenantDomain "$(AzureADB2C-TenantDomain)" -AzureADB2C:TenantId "$(AzureADB2C-TenantId)" -AzureADB2C:Invitation:PolicyId "$(AzureADB2C-Invitation-PolicyId)" -AzureADB2C:Invitation:DefaultApplicationId "$(AzureADB2C-Invitation-DefaultApplicationId)" -AzureADB2C:Invitation:PolicyReturnUrl "$(AzureADB2C-Invitation-PolicyReturnUrl)" -AzureADB2C:TokenIssuer "$(IdTokenHintIssuer)"

Authz Execution Package or folder: $(System.DefaultWorkingDirectory)/_GateKeeperBuild/AuthorizationPolicyExecution/*.zip App Name: select from dropdown Application and Configuration Settings --> App Settings:

-APPINSIGHTS_INSTRUMENTATIONKEY $(AppInsightsInstrumentationKey) -APPLICATIONINSIGHTS_CONNECTION_STRING $(ApplicationInsights-ConnectionString) -Cosmos:Account $(CosmosDb-Account) -Cosmos:DbPrefix $(CosmosDb-DBPrefix) -Cosmos:Key $(CosmosDb-Key) -FUNCTIONS_EXTENSION_VERSION ~4 -WEBSITE_ENABLE_SYNC_UPDATE_SITE true -WEBSITE_RUN_FROM_PACKAGE 1 -Cosmos:DatabaseName $(CosmosDb-DatabaseName)

AppAccessRequest Package or folder: $(System.DefaultWorkingDirectory)/_GateKeeperBuild/AuthorizationPolicyExecution/*.zip App Name: select from dropdown Application and Configuration Settings --> App Settings:

-APPINSIGHTS_INSTRUMENTATIONKEY $(AppInsightsInstrumentationKey) -APPLICATIONINSIGHTS_CONNECTION_STRING $(ApplicationInsights-ConnectionString) -Cosmos:Account $(CosmosDb-Account) -Cosmos:DbPrefix $(CosmosDb-DBPrefix) -Cosmos:Key $(CosmosDb-Key) -FUNCTIONS_EXTENSION_VERSION ~4 -WEBSITE_ENABLE_SYNC_UPDATE_SITE true -WEBSITE_RUN_FROM_PACKAGE 1 -Cosmos:DatabaseName $(CosmosDb-DatabaseName)

Run the release

Congratulations, the initial/core configuration is done! Run the release pipeline and WhoIAM RAMPART should be viewable.

Configure Invitations

Set up Sendgrid

The application requires SendGrid templates for Invitation and Reset emails.

SendGrid Prerequisites

  1. SendGrid account.
  2. Follow this [documentation] to create a SendGrid API. Ensure required permissions for Email Send are configured for the key.
  3. SendGrid documentation for reference.
  4. The template sample provided below is just for reference. Modify it according to the requirements.

SendGrid template for Invitations

  • SendGrid dynamic template documentation for reference here.

  • Create a dynamic template for Invitation Email in SendGrid. Record the template id.

  • Template input.

      {
    "email": "user email",
    "policyurl" : "Full b2c policy url for invitaiton",
    "tenantname": "Tenant name", //
    "message": "Message to be displayed to the user."
    }

Sample Template

<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en">
<head id="Head1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WaTech account invitation email</title>
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
<!-- Template B O365 -->
<style>
table td {border-collapse:collapse;margin:0;padding:0;}
</style>
</head>
<body dir="ltr" lang="en">
<table width="100%" cellpadding="0" cellspacing="0" border="0" dir="ltr" lang="en">
<tr>
<td valign="top" width="50%"></td>
<td valign="top">
<!-- Email Header -->
<table width="640" cellpadding="0" cellspacing="0" border="0" dir="ltr" lang="en" style="border-left:1px solid #e3e3e3;border-right: 1px solid #e3e3e3;">
<tr style="background-color: #0072C6;">
<td width="1" style="background:#0072C6; border-top:1px solid #e3e3e3;"></td>
<td width="24" style="border-top:1px solid #e3e3e3;border-bottom:1px solid #e3e3e3;">&nbsp;</td>
<td width="310" valign="middle" style="border-top:1px solid #e3e3e3; border-bottom:1px solid #e3e3e3;padding:12px 0;">
<h1 style="line-height:20pt;font-family:Segoe UI Light; font-size:18pt; color:#ffffff; font-weight:normal;">
<span id="HeaderPlaceholder_UserVerificationEmailHeader"><font color="#FFFFFF">You've been invited to access applications with in {{tenantname}}</font></span>
</h1>
</td>
<td width="24" style="border-top: 1px solid #e3e3e3;border-bottom: 1px solid #e3e3e3;">&nbsp;</td>
</tr>
</table>
<!-- Email Content -->
<table width="640" cellpadding="0" cellspacing="0" border="0" dir="ltr" lang="en">
<tr>
<td width="1" style="background:#e3e3e3;"></td>
<td width="24">&nbsp;</td>
<td id="PageBody" width="640" valign="top" colspan="2" style="border-bottom:1px solid #e3e3e3;padding:10px 0 20px;border-bottom-style:hidden;">
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="630" style="font-size:10pt; line-height:13pt; color:#000;">
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="" dir="ltr" lang="en">
<tr>
<td>
<div style="font-family:'Segoe UI', Tahoma, sans-serif; font-size:14px; color:#333;">
<span id="BodyPlaceholder_UserVerificationEmailBodySentence1">Click the link below to create an account with {{email}}</span>
</div>
<br>
<div style="font-family:'Segoe UI', Tahoma, sans-serif; font-size:14px; color:#333;">
<span id="BodyPlaceholder_UserVerificationEmailBodySentence2">{{message}}</span>
</div>
<br>
<div style="font-family:'Segoe UI', Tahoma, sans-serif; font-size:14px; color:#333; font-weight: bold">
<a href={{policyurl}} target="_blank"> Accept Invitation </a>
</div>
<br>
<br>
<div style="font-family:'Segoe UI', Tahoma, sans-serif; font-size:14px; color:#333;">
Sincerely,
</div>
<div style="font-family:'Segoe UI', Tahoma, sans-serif; font-size:14px; font-style:italic; color:#333;">
{{tenantname}}
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="1">&nbsp;</td>
<td width="1"></td>
<td width="1">&nbsp;</td>
<td width="1" valign="top"></td>
<td width="29">&nbsp;</td>
<td width="1" style="background:#e3e3e3;"></td>
</tr>
<tr>
<td width="1" style="background:#e3e3e3; border-bottom:1px solid #e3e3e3;"></td>
<td width="24" style="border-bottom:1px solid #e3e3e3;">&nbsp;</td>
<td id="PageFooterContainer" width="585" valign="top" colspan="6" style="border-bottom:1px solid #e3e3e3;padding:0px;">
</td>
<td width="29" style="border-bottom:1px solid #e3e3e3;">&nbsp;</td>
<td width="1" style="background:#e3e3e3; border-bottom:1px solid #e3e3e3;"></td>
</tr>
</table>
</td>
<td valign="top" width="50%"></td>
</tr>
</table>
<img src="https://mucp.api.account.microsoft.com/m/v2/v?d=AIAACWEPFYXYIUTJIJVV4ST7XLBHVI5MLLYBKJAVXHBDTBHUM5VBSVVPTTVRWDFIXJ5JQTHYOH5TUYIPO4ZAFRFK52UAMIS3UNIPPI7ZJNDZPRXD5VEJBN4H6RO3SPTBS6AJEEAJOUYL4APQX5RJUJOWGPKUABY&amp;i=AIAACL23GD2PFRFEY5YVM2XQLM5YYWMHFDZOCDXUI2B4LM7ETZQO473CVF22PT6WPGR5IIE6TCS6VGEKO5OZIONJWCDMRKWQQVNP5VBYAINF3S7STKYOVDJ4JF2XEW4QQVNHMAPQNHFV3KMR3V3BA4I36B6BO7L4VQUHQOI64EOWPLMG5RB3SIMEDEHPILXTF73ZYD3JT6MYOLAZJG7PJJCAXCZCQOEFVH5VCW2KBQOKRYISWQLRWAT7IINZ3EFGQI2CY2EMK3FQOXM7UI3R7CZ6D73IKDI" width="1" height="1">
</body>
</html>

Sample template output

Invitation Email Template

SendGrid template for Password Reset Email

Create a dynamic template for password reset emails in SendGrid. Record the template id.

Template input

{
"email": "testuser@mailinator.com",
"policyurl" : "reset password policy url here",
"tenantname": "Test Tenant Name"
}
  • Use a template similar to the one provided for the invitation email. Update the variable placeholders and text accordingly.
  • Sample template output

Invitation Email Template

Azure DevOps Variable Group

NameValue
AzureADB2C-CertificateThumbprintAppservicePlan-CertificateThumbprint from Terraform
AzureADB2C-Invitation-DefaultApplicationIdThe B2C application ID of the default app to invite users to
AzureADB2C-Invitation-EmailFromSource email from Sendgrid
AzureADB2C-Invitation-EmailTemplateIdTemplate ID from Sendgrid
AzureADB2C-Invitation-ExpirationDurationInterval until invitation expiration in ISO8601 (e.g. P10D = 10 days, P1Y4M7D = 1 year, 4 months, 7 days)
AzureADB2C-Invitation-InvitationBaseUrle.g. https://{invitation-base-url}/api/InvitationRedirect?token=
AzureADB2C-Invitation-PolicyIdB2C policy name e.g. B2C_1A_REDEEMINVITATION
AzureADB2C-Invitation-PolicyReturnUrlIn the case of invitation flow failure, redirect the user to this url (e.g. https://jwt.ms)
SendGrid-ApiKeyYour SendGrid API key. See TODO for required scopes.

Configure Password Reset

NameValue
AzureADB2C-MFAReset-Enabledtrue or false - whether to enable the MFA reset feature
AzureADB2C-PasswordReset-ApplicationIdApplication ID to point the password reset policy at.
AzureADB2C-PasswordReset-EmailFromSource/from email configured in SendGrid
AzureADB2C-PasswordReset-EmailTemplateIdTemplate ID from Sendgrid
AzureADB2C-PasswordReset-PolicyIde.g. B2C_1A_PASSWORDRESET
AzureADB2C-PasswordReset-PolicyReturnUrlURL of the application to redirect the user to after password reset

Finishing up

Link the final two variable groups in the pipeline and run it.