Skip to content

Latest commit

 

History

History
320 lines (240 loc) · 13.9 KB

File metadata and controls

320 lines (240 loc) · 13.9 KB

Deployment

This guide covers deploying VouchGate and the mandatory post-deployment hardening steps. The infra/deploy.sh / infra/deploy.ps1 scripts handle the end-to-end infrastructure and code deployment; the Exchange Online step below must be performed manually and before VouchGate is used in production.


1. Deploy

# macOS / Linux / WSL
./infra/deploy.sh --project vouchgate-prod \
  --mail-sender guest-access@contoso.com \
  --fallback-group "Guest Access Approvers"
# Windows PowerShell
.\infra\deploy.ps1 -Project vouchgate-prod `
  -MailSender guest-access@contoso.com `
  -FallbackGroup "Guest Access Approvers"

The scripts create the Entra ID App Registration, deploy the Bicep infrastructure, assign Microsoft Graph application permissions to the Function App's system-assigned Managed Identity, deploy the backend and frontend, and register the redirect URIs.

Required parameters (first deployment)

On a first deployment (when the infrastructure is included) two parameters are mandatory. The script fails fast with a clear message if either is missing.

Parameter (bash / PowerShell) Purpose
--mail-sender / -MailSender Shared mailbox used as the sender for all notification mails.
--fallback-group / -FallbackGroup Fallback approver group, given as an object ID or a display name. Approves requests from users who have no manager. May be a plain security group (does not need to be mail-enabled): members are notified individually via BCC. Resolved and validated at deploy time via az ad group show; the deployment aborts if the group is not found.

Approver resolution order. For each request the backend resolves the approver as follows:

  1. The requester's manager (Microsoft Graph /users/{id}/manager).
  2. If the requester has no manager, the fallback approver group (APPROVER_FALLBACK_GROUP_ID); any member may approve.
  3. Once a request has been pending past ESCALATION_DAYS, fallback group members may also approve manager-routed requests (escalation).

Without the fallback group, requests from manager-less users cannot be routed and those users receive a clear "no approver could be determined" message in the portal. This is why --fallback-group is required on a first deployment.

Microsoft Graph permissions assigned to the Managed Identity

Permission Why VouchGate needs it
User.Invite.All Create the guest via POST /invitations
User.ReadWrite.All Resolve the requester's manager; patch the guest (extensionAttribute13, employeeType, sponsors)
Mail.Send Send notification mails as MAIL_SENDER
GroupMember.Read.All Verify fallback approver group membership (checkMemberGroups)

⏳ Graph role assignments can take 2 to 5 minutes to propagate. Early 403 responses usually resolve themselves after a short wait.

User consent

The portal's sign-in uses four delegated Microsoft Graph permissions: openid, profile, offline_access and User.Read. Without admin consent, every user sees a one-time Microsoft permissions prompt on their first sign-in asking them to approve these. The prompt is harmless (these are low-privilege, user-level scopes) but avoidable.

The deploy scripts declare these delegated permissions on the App Registration and grant tenant-wide admin consent automatically when the deploying account has a sufficient role (Privileged Role Administrator or Global Administrator), so no user sees the prompt. If consent cannot be granted (insufficient role), the script logs a warning with portal fallback steps: Entra ID > App registrations > your app > API permissions > Grant admin consent for <tenant>. Deployment still succeeds; users just get the one-time prompt until consent is granted.


2. 🔒 MANDATORY, scope Mail.Send to the sender mailbox

VouchGate sends all notification mails as the shared mailbox configured in the MAIL_SENDER application setting, using the Mail.Send application permission on the Managed Identity.

⚠️ Mail.Send as an application permission is tenant-wide by default. Without further restriction, the Managed Identity can send email as any mailbox in the tenant. This is a significant risk and must be constrained.

Scope the Managed Identity so it can only send as the MAIL_SENDER mailbox using an Exchange Online Application Access Policy. The policy grants (or here, restricts) app access to the members of a mail-enabled security group; place only the shared sender mailbox in that group.

Prerequisites

  • Exchange Online PowerShell module: Install-Module ExchangeOnlineManagement -Scope CurrentUser

  • The Application (client) ID: for a system-assigned Managed Identity this is the Application ID of the Managed Identity's enterprise application (service principal), not the portal App Registration's client ID. Retrieve it with:

    # AppId of the Function App's system-assigned Managed Identity service principal
    az ad sp list --display-name "vouchgate-prod-func" `
      --query "[0].appId" -o tsv

    (Replace vouchgate-prod-func with your Function App name. The service principal is named after the Function App.)

Full example

# 1. Connect to Exchange Online (Global Admin or Exchange Admin)
Connect-ExchangeOnline -Organization contoso.onmicrosoft.com

# 2. Create a mail-enabled security group that contains ONLY the sender mailbox.
#    (Skip if it already exists.)
New-DistributionGroup `
    -Name "VouchGate Mail Senders" `
    -Alias "vouchgate-mail-senders" `
    -Type Security `
    -PrimarySmtpAddress "vouchgate-mail-senders@contoso.com" `
    -Members "guest-access@contoso.com"

# 3. Restrict the Managed Identity so it can send ONLY as members of that group.
#    -AppId is the Application ID of the Managed Identity's service principal.
New-ApplicationAccessPolicy `
    -AppId "<managed-identity-app-id>" `
    -PolicyScopeGroupId "vouchgate-mail-senders@contoso.com" `
    -AccessRight RestrictAccess `
    -Description "Restrict VouchGate Managed Identity to the guest-access shared mailbox only"

# 4. Verify: the sender mailbox must be GRANTED (AccessCheckResult = Granted)
Test-ApplicationAccessPolicy `
    -Identity "guest-access@contoso.com" `
    -AppId "<managed-identity-app-id>"

# 5. Verify: any OTHER mailbox must be DENIED (AccessCheckResult = Denied)
Test-ApplicationAccessPolicy `
    -Identity "ceo@contoso.com" `
    -AppId "<managed-identity-app-id>"

ℹ️ Application Access Policy changes can take up to ~30 minutes to take effect across Exchange Online. If sendMail returns 403 ErrorAccessDenied immediately after applying the policy, wait and retry.

With AccessRight RestrictAccess, the app can send only as mailboxes that are members of the referenced group. Step 4 must report Granted for the sender mailbox and step 5 must report Denied for any unrelated mailbox.


3. Restrict who can request access

By default every user in the tenant can sign in to the portal.

  1. Entra ID → Enterprise Applications → the VouchGate portal app.
  2. Properties → set Assignment required? to Yes.
  3. Users and groups → assign a dedicated security group (e.g. SG-VouchGate-Requesters).

4. For approvers

Approvers use the same portal URL as requesters. There is no separate admin area or admin login.

  • After signing in, an Approver Inbox tab appears automatically whenever there are pending requests assigned to the signed-in user, either as the resolved manager of the requester, or as a fallback approver group member once a request has escalated (pending past ESCALATION_DAYS). If no requests are assigned, the tab is not shown.
  • The link in the notification mail (/?request={id}) is a convenience deep link that opens and highlights that specific request. It is not the only way in: approvers can open the portal directly and use the Approver Inbox tab to see every request assigned to them.

Fallback approver group notifications

The fallback approver group can be a plain security group. It does not need to be mail-enabled. When a request is routed to (or escalated to) the fallback group, VouchGate resolves the group's transitive user members via Microsoft Graph (GroupMember.Read.All) and sends a single notification mail with every member's address in BCC (the MAIL_SENDER mailbox is the visible To recipient). This means members are notified individually without the group needing a mailbox, and without exposing the member list.

If no member has a usable mail address, the request is still created (approvers can act from the portal), a warning is logged, and a VouchGateApproverNotificationFailed event is emitted to Application Insights.

⚠️ If "Assignment required" is enabled (section 3), approvers must also be assigned to the app. Managers and fallback approver group members sign in to the same app as requesters, so if only requesters are assigned, managers cannot sign in to approve. Assign a group that covers both requesters and approvers, or add the approvers' group to the app assignment.

No admin dashboard (v1)

VouchGate v1 has no admin dashboard. The full audit trail lives in the VouchGateAudit table in the deployment's storage account, with one record per request submission and per approval or denial (actor, approverType, guest email, comment, source IP and timestamp). View it through the Azure Portal (Storage account → Storage browser → Tables → VouchGateAudit) or Azure Storage Explorer.


5. Conditional Access

Create a Conditional Access policy targeting the same group:

  • 🔐 Require multi-factor authentication.
  • 🌍 Restrict by trusted location / country as appropriate.
  • 🚫 Block legacy authentication.

6. Lifecycle integration

VouchGate stamps the approval expiry on each guest as extensionAttribute13 and sets employeeType = "VouchGate" and sponsors = requester. Expiry cleanup is handled externally by EntraID Guest User LifeCycle Management, which reads extensionAttribute13.

VouchGate never sets extensionAttribute15 = "ExcludeFromLCM", so every guest it provisions remains subject to the external lifecycle cleanup.


7. Re-deploy & partial deploys

Re-run the same command to update an existing deployment:

# macOS / Linux / WSL
./infra/deploy.sh --project vouchgate-prod
# Windows PowerShell
.\infra\deploy.ps1 -Project vouchgate-prod

Skip stages for faster iterations:

# Code only (skip Bicep)
./infra/deploy.sh --project vouchgate-prod --skip-infra

# Infrastructure only (skip backend + frontend)
./infra/deploy.sh --project vouchgate-prod --skip-backend --skip-frontend
# Code only (skip Bicep)
.\infra\deploy.ps1 -Project vouchgate-prod -SkipInfra

# Infrastructure only
.\infra\deploy.ps1 -Project vouchgate-prod -SkipBackend -SkipFrontend

8. Custom domain & App Registration redirect URI

The deploy scripts automatically register the Static Web App URL as a SPA redirect URI on the App Registration. If you add a custom domain, you must register that URL as an additional redirect URI, otherwise sign-in fails with AADSTS50011.

Deploy with a custom domain:

# macOS / Linux
./infra/deploy.sh --project vouchgate-prod --location westeurope --domain vouch.company.com
# Windows PowerShell
.\infra\deploy.ps1 -Project vouchgate-prod -Location westeurope -CustomDomain vouch.company.com

Add the redirect URI manually (Portal or CLI):

APP_OBJ_ID=$(az ad app list --display-name "vouchgate-prod-vouchgate-portal" --query "[0].id" -o tsv)
az rest --method PATCH \
  --uri "https://graph.microsoft.com/v1.0/applications/${APP_OBJ_ID}" \
  --headers "Content-Type=application/json" \
  --body '{"spa":{"redirectUris":["https://vouch.company.com"]}}'

Or: Entra ID → App registrations → your app → Authentication → Redirect URIs and add https://vouch.company.com.


9. Troubleshooting

Symptom Likely cause / fix
403 from Graph right after deploy Managed Identity role assignments take 2 to 5 min to propagate. Wait and retry.
sendMail returns 403 ErrorAccessDenied Application Access Policy not yet effective (up to ~30 min), or the sender mailbox is not a member of the scoped group.
Sign-in fails with AADSTS50011 The portal URL (e.g. a custom domain) is not registered as a redirect URI. See section 8.
Sign-in fails with AADSTS50105 The user is not assigned to the app while Assignment required? is on. Assign them (or their group) in Enterprise Applications.
Submit fails with "no approver" The signed-in user has no manager in Entra ID and no fallback approver group is configured (or the configured group ID is wrong). Set APPROVER_FALLBACK_GROUP_ID by re-running the deploy with --fallback-group / -FallbackGroup, or set a manager on the user.
Approver never receives mail Check MAIL_SENDER is set and the Application Access Policy grants it; verify the approver (manager or group) has a mail address.
First request after idle is slow Cold start. Always On is now enabled by default on the Function App (siteConfig.alwaysOn = true, supported by the B1 plan), which keeps the app warm. If you see this, confirm Always On is on under Function App > Configuration > General settings.
Method Not Allowed in the redirect URI step Entra ID propagation delay on a fresh App Registration: the object id lookup returned empty, so the PATCH hit the applications collection URL instead of a specific app. The deploy scripts now retry the lookup and skip the PATCH if it stays empty. Re-running the deployment once the registration has propagated resolves it.