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
- Azure Accounts & Tenants
- Entra External ID (CIAM)
- Azure Resources
- Key Vault Secrets Reference
- App Service Configuration
- Cloudflare
- Third-Party Services
- ADO Pipelines & Build Agents
- Repositories
- Domains & Environments
- Runbooks
Azure Accounts & Tenants
| Account | Purpose | Login |
|---|---|---|
| Personal (old) | Legacy — traxs-dev.onmicrosoft.com — do not use for production | Bitwarden → "Azure Personal" |
| Traxs Group LLC | Production account — all live resources live here | Bitwarden → "Azure Traxs Group" |
Tenants in Traxs Group LLC account
| Tenant | Domain | Tenant ID | Purpose |
|---|---|---|---|
| Traxs Group LLC | traxsgroup.onmicrosoft.com | _fill_in_ | Primary Azure subscription tenant |
| roundtripapp | roundtripapp.onmicrosoft.com | 08f09e72-40f3-4443-8fec-3a77c4c0d1ee | Entra 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
tidextension 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
- Formula:
App Roles (defined on RoundTrip API registration)
| Role | ID |
|---|---|
| TenantAdmin | c626f6f7-8399-46aa-b10f-dab79d8befbb |
| Dispatcher | e92ce80e-3223-469c-bbde-19f64cc5283d |
| Technician | 5a01e722-b83d-401a-80d1-7b793e485142 |
| BillingStaff | b7a6b1fc-74b3-4770-b68d-ac1eeb74b21f |
| ReadOnly | 837257a4-cda0-406e-93c0-b816b19e81d7 |
Service Principal
- RoundTrip API Service Principal Object ID:
5be28949-5ad8-4409-8fdd-e2db74327c54- Used in
GraphUserService.AssignAppRoleAsyncfor role assignments
- Used in
Azure Resources
Resource Group: rg-roundtrip-production
| Resource | Type | Name | Notes |
|---|---|---|---|
| App Service | Web App | app-roundtrip-production | .NET 10, Linux. WebSockets enabled via CLI. |
| App Service Plan | Plan | _fill_in_ | |
| SQL Server | Azure SQL | _fill_in_ | |
| SQL Database | Azure SQL DB | RoundTripDb | |
| Key Vault | Key Vault | kv-roundtrip-production | Managed identity access from App Service |
| Blob Storage | Storage Account | _fill_in_ | Container: invoice-pdfs |
| Application Insights | Monitor | _fill_in_ |
Connection String
- App Service setting name:
ConnectionStrings__Default(notDefaultConnection) - 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 Name | Maps to Config Key | What it is |
|---|---|---|
ApplicationInsights--ConnectionString | ApplicationInsights:ConnectionString | App Insights instrumentation |
ConnectionStrings--BlobStorage | ConnectionStrings:BlobStorage | Azure Blob Storage connection string |
Entra--ClientId | AzureAd:ClientId | RoundTrip API app registration Client ID |
Entra--ClientSecret | AzureAd:ClientSecret | RoundTrip API app registration secret |
Entra--TenantId | AzureAd:TenantId | roundtripapp tenant ID |
GraphApi--ClientId | GraphApi:ClientId | b2c-extensions-app Client ID |
GraphApi--ClientSecret | GraphApi:ClientSecret | b2c-extensions-app client secret ⚠️ rotate every 24mo |
GraphApi--TenantId | GraphApi:TenantId | roundtripapp tenant ID |
GraphApi--ExtensionAttributePrefix | GraphApi:ExtensionAttributePrefix | extension_74ae24d66fa9411095712cfddae0db04 |
SendGrid--ApiKey | SendGrid:ApiKey | SendGrid API key for invoice emails |
Stripe--SecretKey | Stripe:SecretKey | Stripe secret key |
Stripe--PublishableKey | Stripe:PublishableKey | Stripe publishable key |
Stripe--WebhookSigningSecret | Stripe:WebhookSigningSecret | Stripe webhook endpoint secret |
Stripe--PriceId--Starter | Stripe:PriceId:Starter | Stripe price ID for Starter plan |
Stripe--PriceId--Standard | Stripe:PriceId:Standard | Stripe price ID for Standard plan |
Stripe--PriceId--Professional | Stripe:PriceId:Professional | Stripe price ID for Professional plan |
Twilio--AccountSid | Twilio:AccountSid | Twilio account SID |
Twilio--AuthToken | Twilio:AuthToken | Twilio auth token |
Twilio--FromNumber | Twilio:FromNumber | Twilio sending number |
VapidKeys--PublicKey | VapidKeys:PublicKey | Web Push VAPID public key |
VapidKeys--PrivateKey | VapidKeys:PrivateKey | Web Push VAPID private key |
VapidKeys--Subject | VapidKeys:Subject | Web 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 Name | Source | Notes |
|---|---|---|
ASPNETCORE_ENVIRONMENT | App Service | Production |
ASPNETCORE_URLS | App Service | |
App__BaseUrl | App Service | https://roundtrips.app |
PORT | App Service | |
ConnectionStrings__Default | App Service | Direct value — not Key Vault ref |
ApplicationInsights__ConnectionString | Key Vault | |
AzureAd__ClientId | Key Vault | → Entra--ClientId |
AzureAd__ClientSecret | Key Vault | → Entra--ClientSecret |
AzureAd__TenantId | Key Vault | → Entra--TenantId |
ConnectionStrings__BlobStorage | Key Vault | |
GraphApi__ClientId | Key Vault | → GraphApi--ClientId |
GraphApi__ClientSecret | Key Vault | → GraphApi--ClientSecret |
GraphApi__TenantId | Key Vault | → GraphApi--TenantId |
GraphApi__ExtensionAttributePrefix | Key Vault | → GraphApi--ExtensionAttributePrefix |
SendGrid__ApiKey | Key Vault | → SendGrid--ApiKey |
Stripe__SecretKey | Key Vault | |
Stripe__PublishableKey | Key Vault | |
Stripe__WebhookSigningSecret | Key Vault | |
Stripe__PriceId__Starter | Key Vault | |
Stripe__PriceId__Standard | Key Vault | |
Stripe__PriceId__Professional | Key Vault |
Important App Service Notes
- Connection string key must be
ConnectionStrings__Default(notDefaultConnection) - 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
| Item | Value |
|---|---|
| Login | Bitwarden → "Cloudflare" |
| Production project | roundtrip — production branch: main |
| Dev project | roundtrip-dev — production branch: development |
| Production domain | roundtrips.app |
| Dev domain | dev.roundtrips.app |
| CSP / security headers | Controlled by _headers file in web repo root |
⚠️ Critical Pipeline Rule
- Dev pipeline (
azure-pipelines-dev.yml) must use--branch developmentin Wrangler deploy - Production pipeline uses
--branch main - Using
--branch mainin 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://inconnect-src - Leaflet map tiles
Third-Party Services
| Service | Purpose | Login | Notes |
|---|---|---|---|
| SendGrid | Invoice emails | Bitwarden → "SendGrid" | API key in Key Vault SendGrid--ApiKey |
| Twilio | SMS notifications | Bitwarden → "Twilio" | Deferred pre-beta |
| Stripe | Billing / subscriptions | Bitwarden → "Stripe" | Webhook secret in Key Vault |
| Cloudflare | Frontend hosting + DNS | Bitwarden → "Cloudflare" | Pages + DNS |
| Azure DevOps | CI/CD pipelines | Bitwarden → "Azure DevOps" | Self-hosted agent laptop in pool traxs-self-hosted |
ADO Pipelines & Build Agents
| Item | Value |
|---|---|
| Organization | _fill_in_ |
| Agent pool | traxs-self-hosted |
| Agent name | laptop |
| Dev pipeline file | azure-pipelines-dev.yml |
| Prod pipeline file | azure-pipelines.yml (or similar) |
Repositories
| Repo | Purpose | Branch pattern |
|---|---|---|
Traxs-dev/RoundTripAPI | .NET 10 API | feature/TRA-NNN-description → development → main |
Traxs-dev/RoundTripWeb | React/Vite frontend | feature/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
| Environment | Frontend | API | Branch |
|---|---|---|---|
| Production | roundtrips.app | app-roundtrip-production.azurewebsites.net | main |
| Development | dev.roundtrips.app | _fill_in_ | development |
Runbooks
Deploy to production
- Merge
development→mainvia PR - ADO pipeline triggers automatically
- Wrangler deploys frontend to Cloudflare Pages
roundtripproject - App Service deploys API via OneDeploy
Add a new Key Vault secret
- Go to
kv-roundtrip-production→ Secrets → Generate/Import - Use
--as separator (e.g.MyService--ApiKey) - Add corresponding App Service environment variable:
MyService__ApiKey→@Microsoft.KeyVault(VaultName=kv-roundtrip-production;SecretName=MyService--ApiKey) - Save App Service settings
- Full stop → start App Service (not restart)
Rotate a client secret
- Go to the app registration → Certificates & secrets → New client secret
- Copy the value immediately (only shown once)
- Go to Key Vault → find the secret → + New version → paste new value
- Update Bitwarden entry
- Full stop/start App Service
- Delete the old secret version from the app registration
App Service won't start (SiteStartupCancelled)
- Check Log stream:
az webapp log tail --name app-roundtrip-production --resource-group rg-roundtrip-production - Check App Service → Environment variables — look for Key Vault refs with red/yellow status badges
- 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
- 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 __EFMigrationsHistoryin DataGrip - SQL Server sequences must be created on a separate ADO.NET connection outside any open EF Core transaction