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.com — do 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.com15efc329-0d2b-4568-920c-38d65d3f8f7cPrimary 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: c7f83f45-27cd-4bef-a6dc-f81d98077144
  • 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 PlanPlanplan-roundtrip-production (B1: 1)
SQL ServerAzure SQLrg-roundtrip-production
SQL DatabaseAzure SQL DBRoundTripDb
Key VaultKey Vaultkv-roundtrip-productionManaged identity access from App Service
Blob StorageStorage AccountstroundtripproductionContainer: invoices
Application InsightsMonitorappi-roundtrip-production

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 Vault→ Entra--ClientId
AzureAd__ClientSecretKey Vault→ Entra--ClientSecret
AzureAd__TenantIdKey Vault→ Entra--TenantId
ConnectionStrings__BlobStorageKey Vault
GraphApi__ClientIdKey Vault→ GraphApi--ClientId
GraphApi__ClientSecretKey Vault→ GraphApi--ClientSecret
GraphApi__TenantIdKey Vault→ GraphApi--TenantId
GraphApi__ExtensionAttributePrefixKey Vault→ GraphApi--ExtensionAttributePrefix
SendGrid__ApiKeyKey Vault→ SendGrid--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
Organizationtraxs
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-description → development → main
Traxs-dev/RoundTripWebReact/Vite frontendfeature/TRA-NNN-description → development → main

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 development → main 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