Skip to main content

Infrastructure Reference

Internal use only. Do not commit secrets to this file. Passwords and client secrets live in Bitwarden. This doc contains non-secret reference information only. Last updated: 2026-07-01


Table of Contents

  1. Azure Accounts & Tenants
  2. Entra External ID (CIAM)
  3. Azure Resources
  4. Key Vault Secrets Reference
  5. App Service Configuration
  6. Cloudflare
  7. Third-Party Services
  8. ADO Pipelines & Build Agents
  9. Repositories
  10. Domains & Environments
  11. Runbooks

Azure Accounts & Tenants

AccountPurposeLogin
Personal (old)Legacy — traxs-dev.onmicrosoft.comdo not use for productionBitwarden → "Azure Personal"
Traxs Group LLCProduction account — all live resources live hereBitwarden → "Azure Traxs Group"

Tenants in Traxs Group LLC account

TenantDomainTenant IDPurpose
Traxs Group LLCtraxsgroup.onmicrosoft.com_fill_in_Primary Azure subscription tenant
roundtripapproundtripapp.onmicrosoft.com08f09e72-40f3-4443-8fec-3a77c4c0d1eeEntra External ID CIAM for RoundTrip users

Entra External ID (CIAM)

Tenant: roundtripapp (roundtripapp.onmicrosoft.com)

Tenant ID: 08f09e72-40f3-4443-8fec-3a77c4c0d1ee

App Registrations

RoundTrip API

  • Purpose: Backend API authentication (validates JWT tokens from CIAM)
  • Client ID: _fill_in_
  • Permissions: User.Read (Delegated) — standard sign-in only
  • Client Secret: Bitwarden → "Entra RoundTrip API Client Secret"
  • Key Vault secrets: Entra--ClientId, Entra--ClientSecret, Entra--TenantId

b2c-extensions-app ⚠️ Do not modify without care

  • Purpose: Graph API operations — invite users, create users, assign app roles, set tid extension attribute
  • Client ID: 74ae24d6-6fa9-4110-9571-2cfddae0db04
  • Permissions (Application): User.Invite.All, User.ReadWrite.All, Directory.ReadWrite.All, AppRoleAssignment.ReadWrite.All
  • Client Secret: Bitwarden → "Entra b2c-extensions-app Client Secret" (rotate every 24 months)
  • Key Vault secrets: GraphApi--ClientId, GraphApi--ClientSecret, GraphApi--TenantId
  • Extension attribute prefix: extension_74ae24d66fa9411095712cfddae0db04
    • Formula: extension_ + Client ID with hyphens removed
    • Key Vault secret: GraphApi--ExtensionAttributePrefix

App Roles (defined on RoundTrip API registration)

RoleID
TenantAdminc626f6f7-8399-46aa-b10f-dab79d8befbb
Dispatchere92ce80e-3223-469c-bbde-19f64cc5283d
Technician5a01e722-b83d-401a-80d1-7b793e485142
BillingStaffb7a6b1fc-74b3-4770-b68d-ac1eeb74b21f
ReadOnly837257a4-cda0-406e-93c0-b816b19e81d7

Service Principal

  • RoundTrip API Service Principal Object ID: 5be28949-5ad8-4409-8fdd-e2db74327c54
    • Used in GraphUserService.AssignAppRoleAsync for role assignments

Azure Resources

Resource Group: rg-roundtrip-production

ResourceTypeNameNotes
App ServiceWeb Appapp-roundtrip-production.NET 10, Linux. WebSockets enabled via CLI.
App Service PlanPlan_fill_in_
SQL ServerAzure SQL_fill_in_
SQL DatabaseAzure SQL DBRoundTripDb
Key VaultKey Vaultkv-roundtrip-productionManaged identity access from App Service
Blob StorageStorage Account_fill_in_Container: invoice-pdfs
Application InsightsMonitor_fill_in_

Connection String

  • App Service setting name: ConnectionStrings__Default (not DefaultConnection)
  • Value stored directly in App Service (not Key Vault reference)

Key Vault Secrets Reference

Vault: kv-roundtrip-production

Secrets use double-dash (--) as the hierarchy separator, which maps to : in .NET configuration.

Secret NameMaps to Config KeyWhat it is
ApplicationInsights--ConnectionStringApplicationInsights:ConnectionStringApp Insights instrumentation
ConnectionStrings--BlobStorageConnectionStrings:BlobStorageAzure Blob Storage connection string
Entra--ClientIdAzureAd:ClientIdRoundTrip API app registration Client ID
Entra--ClientSecretAzureAd:ClientSecretRoundTrip API app registration secret
Entra--TenantIdAzureAd:TenantIdroundtripapp tenant ID
GraphApi--ClientIdGraphApi:ClientIdb2c-extensions-app Client ID
GraphApi--ClientSecretGraphApi:ClientSecretb2c-extensions-app client secret ⚠️ rotate every 24mo
GraphApi--TenantIdGraphApi:TenantIdroundtripapp tenant ID
GraphApi--ExtensionAttributePrefixGraphApi:ExtensionAttributePrefixextension_74ae24d66fa9411095712cfddae0db04
SendGrid--ApiKeySendGrid:ApiKeySendGrid API key for invoice emails
Stripe--SecretKeyStripe:SecretKeyStripe secret key
Stripe--PublishableKeyStripe:PublishableKeyStripe publishable key
Stripe--WebhookSigningSecretStripe:WebhookSigningSecretStripe webhook endpoint secret
Stripe--PriceId--StarterStripe:PriceId:StarterStripe price ID for Starter plan
Stripe--PriceId--StandardStripe:PriceId:StandardStripe price ID for Standard plan
Stripe--PriceId--ProfessionalStripe:PriceId:ProfessionalStripe price ID for Professional plan
Twilio--AccountSidTwilio:AccountSidTwilio account SID
Twilio--AuthTokenTwilio:AuthTokenTwilio auth token
Twilio--FromNumberTwilio:FromNumberTwilio sending number
VapidKeys--PublicKeyVapidKeys:PublicKeyWeb Push VAPID public key
VapidKeys--PrivateKeyVapidKeys:PrivateKeyWeb Push VAPID private key
VapidKeys--SubjectVapidKeys:SubjectWeb Push VAPID subject (mailto:)

App Service Configuration

App: app-roundtrip-production

App Settings (Environment Variables)

All Key Vault references use format: @Microsoft.KeyVault(VaultName=kv-roundtrip-production;SecretName=SECRET-NAME)

Setting NameSourceNotes
ASPNETCORE_ENVIRONMENTApp ServiceProduction
ASPNETCORE_URLSApp Service
App__BaseUrlApp Servicehttps://roundtrips.app
PORTApp Service
ConnectionStrings__DefaultApp ServiceDirect value — not Key Vault ref
ApplicationInsights__ConnectionStringKey Vault
AzureAd__ClientIdKey VaultEntra--ClientId
AzureAd__ClientSecretKey VaultEntra--ClientSecret
AzureAd__TenantIdKey VaultEntra--TenantId
ConnectionStrings__BlobStorageKey Vault
GraphApi__ClientIdKey VaultGraphApi--ClientId
GraphApi__ClientSecretKey VaultGraphApi--ClientSecret
GraphApi__TenantIdKey VaultGraphApi--TenantId
GraphApi__ExtensionAttributePrefixKey VaultGraphApi--ExtensionAttributePrefix
SendGrid__ApiKeyKey VaultSendGrid--ApiKey
Stripe__SecretKeyKey Vault
Stripe__PublishableKeyKey Vault
Stripe__WebhookSigningSecretKey Vault
Stripe__PriceId__StarterKey Vault
Stripe__PriceId__StandardKey Vault
Stripe__PriceId__ProfessionalKey Vault

Important App Service Notes

  • Connection string key must be ConnectionStrings__Default (not DefaultConnection)
  • WebSockets must be enabled via CLI: az webapp config set --web-sockets-enabled true
  • Key Vault secret caching — requires full stop/start (not restart) to pick up new secret versions
  • Linux plan — Portal UI toggle for WebSockets absent on Linux; always use CLI

Cloudflare

ItemValue
LoginBitwarden → "Cloudflare"
Production projectroundtrip — production branch: main
Dev projectroundtrip-dev — production branch: development
Production domainroundtrips.app
Dev domaindev.roundtrips.app
CSP / security headersControlled by _headers file in web repo root

⚠️ Critical Pipeline Rule

  • Dev pipeline (azure-pipelines-dev.yml) must use --branch development in Wrangler deploy
  • Production pipeline uses --branch main
  • Using --branch main in the dev pipeline overwrites production regardless of project name (root cause of TRA-195)

CSP entries required in _headers

  • Google Fonts: style-src + connect-src
  • CIAM: https://*.ciamlogin.com
  • Cloudflare beacon: script-src
  • SignalR WebSockets: wss:// in connect-src
  • Leaflet map tiles

Third-Party Services

ServicePurposeLoginNotes
SendGridInvoice emailsBitwarden → "SendGrid"API key in Key Vault SendGrid--ApiKey
TwilioSMS notificationsBitwarden → "Twilio"Deferred pre-beta
StripeBilling / subscriptionsBitwarden → "Stripe"Webhook secret in Key Vault
CloudflareFrontend hosting + DNSBitwarden → "Cloudflare"Pages + DNS
Azure DevOpsCI/CD pipelinesBitwarden → "Azure DevOps"Self-hosted agent laptop in pool traxs-self-hosted

ADO Pipelines & Build Agents

ItemValue
Organization_fill_in_
Agent pooltraxs-self-hosted
Agent namelaptop
Dev pipeline fileazure-pipelines-dev.yml
Prod pipeline fileazure-pipelines.yml (or similar)

Repositories

RepoPurposeBranch pattern
Traxs-dev/RoundTripAPI.NET 10 APIfeature/TRA-NNN-descriptiondevelopmentmain
Traxs-dev/RoundTripWebReact/Vite frontendfeature/TRA-NNN-descriptiondevelopmentmain

Commit conventions

  • Format: feat(TRA-NNN): description
  • Prefixes: feat:, fix:, chore:, refactor:, test:
  • One commit per Linear ticket
  • PRs chained sequentially — never merge multiple branches simultaneously

Domains & Environments

EnvironmentFrontendAPIBranch
Productionroundtrips.appapp-roundtrip-production.azurewebsites.netmain
Developmentdev.roundtrips.app_fill_in_development

Runbooks

Deploy to production

  1. Merge developmentmain via PR
  2. ADO pipeline triggers automatically
  3. Wrangler deploys frontend to Cloudflare Pages roundtrip project
  4. App Service deploys API via OneDeploy

Add a new Key Vault secret

  1. Go to kv-roundtrip-production → Secrets → Generate/Import
  2. Use -- as separator (e.g. MyService--ApiKey)
  3. Add corresponding App Service environment variable: MyService__ApiKey@Microsoft.KeyVault(VaultName=kv-roundtrip-production;SecretName=MyService--ApiKey)
  4. Save App Service settings
  5. Full stop → start App Service (not restart)

Rotate a client secret

  1. Go to the app registration → Certificates & secrets → New client secret
  2. Copy the value immediately (only shown once)
  3. Go to Key Vault → find the secret → + New version → paste new value
  4. Update Bitwarden entry
  5. Full stop/start App Service
  6. Delete the old secret version from the app registration

App Service won't start (SiteStartupCancelled)

  1. Check Log stream: az webapp log tail --name app-roundtrip-production --resource-group rg-roundtrip-production
  2. Check App Service → Environment variables — look for Key Vault refs with red/yellow status badges
  3. Common causes:
    • Missing App Service environment variable for a Key Vault secret
    • Key Vault secret reference typo
    • Managed identity lost Key Vault access
    • Config key required at startup throwing InvalidOperationException
  4. Fix the issue, save settings, full stop/start

Check Hangfire failed jobs

  • Hangfire dashboard path: _fill_in_ (not at /hangfire — check startup config)
  • If jobs exhausted retries → go to Failed tab → Requeue
  • Common failure: missing config key causes job class constructor to throw

EF Core migrations (RoundTrip)

  • Never run at startup
  • Create with: dotnet ef migrations add MigrationName
  • Apply manually via INSERT INTO __EFMigrationsHistory in DataGrip
  • SQL Server sequences must be created on a separate ADO.NET connection outside any open EF Core transaction