Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions Core/Resgrid.Config/DataProtectionConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
namespace Resgrid.Config
{
/// <summary>
/// Advanced Data Protection (ADP) platform configuration. Endpoint addresses, mounts and key
/// names are configuration; SECRETS ARE NOT — the broker's client certificate and key, the
/// OpenBao token, the YubiHSM PIN, and recovery shares must never appear here, in
/// appsettings*.json, resgrid.env, container images, or the repository. Values load like every
/// other Resgrid.Config class: "DataProtectionConfig.FieldName" JSON keys or
/// RESGRID:DataProtectionConfig:FieldName environment variables.
/// </summary>
public static class DataProtectionConfig
{
/// <summary>Base URL of the Protected Data Broker service (empty = no broker deployed).</summary>
public static string BrokerBaseUrl = "";

/// <summary>Audience the application tier expects on broker mTLS/workload credentials.</summary>
public static string BrokerAudience = "resgrid-protected-broker";

/// <summary>Broker request timeout in milliseconds; protected operations fail closed on expiry.</summary>
public static int BrokerTimeoutMs = 10000;

/// <summary>
/// Shared workload secret the application tier presents to the broker (X-Resgrid-Broker-Key).
/// Supplied through the environment/secret store only; an empty value on the broker refuses
/// every request (fail closed). This is defense-in-depth UNDER network isolation and mTLS —
/// never the only control.
/// </summary>
public static string BrokerApiKey = "";

/// <summary>Maximum field items one broker request may carry; larger requests are refused.</summary>
public static int BrokerMaxItemsPerRequest = 200;

/// <summary>True on the broker host to run the ADP migration coordinator sweep there (the only
/// host with a real KMS adapter). Workers.Console keeps its sweep for liveness/offboarding
/// flips but never runs nights — its engine reports unavailable.</summary>
public static bool BrokerRunsMigrations = true;

/// <summary>Broker-hosted migration sweep interval in seconds (matches worker command 27).</summary>
public static int BrokerMigrationSweepSeconds = 300;

/// <summary>Issuer (iss) on Protected Data Grants — the identity tier's logical name.</summary>
public static string GrantIssuer = "resgrid-identity";

/// <summary>Audience (aud) on Protected Data Grants, pinned by the broker and API validators.</summary>
public static string GrantAudience = "resgrid-protected-data";

/// <summary>
/// Filesystem path to the grant SIGNING certificate (PFX with an ECDSA P-256 private key).
/// Present ONLY on identity-tier hosts (the step-up endpoint); the broker gets the public
/// validation certificate instead. The path is configuration; the file is a mounted secret.
/// </summary>
public static string GrantSigningCertificatePath = "";

/// <summary>PFX password for the signing certificate, supplied through the environment only.</summary>
public static string GrantSigningCertificatePassword = "";

/// <summary>
/// Filesystem path to the grant VALIDATION certificate (public key only, CER/PEM/PFX). Set on
/// broker and API hosts. When empty, validation falls back to the signing certificate's
/// public part where that is configured (single-host development).
/// </summary>
public static string GrantValidationCertificatePath = "";

/// <summary>Bounded clock skew allowed when validating grant lifetimes, in seconds.</summary>
public static int GrantClockSkewSeconds = 30;

/// <summary>
/// Key-wrapping provider the broker uses: "OpenBaoTransit" (production default), or "LocalDev"
/// for synthetic/non-PHI testing only — production startup must reject LocalDev.
/// </summary>
public static string KeyWrappingProviderType = "OpenBaoTransit";

/// <summary>OpenBao base address, reachable ONLY from broker hosts (never Web/API/workers).</summary>
public static string OpenBaoAddress = "";

/// <summary>OpenBao Transit mount path.</summary>
public static string OpenBaoTransitMount = "transit";

/// <summary>Derived (per-department context) Transit KEK name.</summary>
public static string OpenBaoTransitKeyName = "resgrid-dept-kek";

/// <summary>
/// Filesystem path to the broker's mTLS client certificate (PFX/PKCS#12) used for the OpenBao
/// cert auth method. The path is configuration; the certificate FILE is a mounted secret and
/// must never land in appsettings*.json, resgrid.env, container images, or the repository.
/// </summary>
public static string OpenBaoClientCertificatePath = "";

/// <summary>PFX password, supplied through the environment/secret store only.</summary>
public static string OpenBaoClientCertificatePassword = "";

/// <summary>Optional named cert-auth role ("name" parameter on auth/cert/login); empty = any matching role.</summary>
public static string OpenBaoCertAuthRoleName = "";

/// <summary>OpenBao HTTP request timeout in milliseconds; unwrap/wrap fail closed on expiry.</summary>
public static int OpenBaoTimeoutMs = 10000;

/// <summary>Default Protected Data Grant lifetime in minutes when a department has no policy value.</summary>
public static int StepUpWindowDefaultMinutes = 15;

/// <summary>Department values above this trigger an administrator warning plus recorded reason.</summary>
public static int StepUpWarningThresholdMinutes = 60;

/// <summary>Operator ceiling on StepUpWindowMinutes; departments cannot exceed it.</summary>
public static int StepUpMaximumMinutes = 480;

/// <summary>
/// ADP migration worker: maximum departments whose night runs in one sweep
/// (BackOffice-adjustable). Executions are SEQUENTIAL within the sweep — this caps how many
/// departments a sweep picks up, it does not parallelize them.
/// </summary>
public static int MigrationNightlyConcurrency = 1;

/// <summary>
/// Operator kill switch: true stops the worker from opening NEW migration windows. It never
/// interrupts an in-flight batch and never touches durable state, active protection, or
/// queued departments (plan section 19.2).
/// </summary>
public static bool MigrationQueuePaused = false;

/// <summary>Rows per transactional migration batch (cursor advances once per batch).</summary>
public static int MigrationBatchSize = 500;

/// <summary>
/// Measured migration throughput in rows/second for the sizing estimate (plan section 18.2).
/// Re-measured per deployment by the synthetic benchmark against production-equivalent
/// hardware; the conservative default stands in until then.
/// </summary>
public static int MigrationBenchmarkRowsPerSecond = 200;

/// <summary>Fixed per-table overhead added to the estimate, in seconds.</summary>
public static int MigrationEstimatePerTableOverheadSeconds = 30;

/// <summary>Verification-pass allowance as a fraction of the migration time (0.25 = +25%).</summary>
public static double MigrationEstimateVerificationAllowance = 0.25;

/// <summary>P90 multiplier over the P50 estimate — the range shown instead of false precision.</summary>
public static double MigrationEstimateP90Multiplier = 2.0;

/// <summary>Default department-local overnight migration window start ("HH:mm").</summary>
public static string MigrationWindowDefaultStartLocal = "22:00";

/// <summary>Default department-local overnight migration window end ("HH:mm").</summary>
public static string MigrationWindowDefaultEndLocal = "06:00";

/// <summary>Worker heartbeat interval for the department operation lock, in seconds.</summary>
public static int LockHeartbeatIntervalSeconds = 60;

/// <summary>Safety-valve lifetime added to each heartbeat; a stale lock stops enforcing after this.</summary>
public static int LockExpirySeconds = 300;

/// <summary>BackOffice protected-support grant lifetime in minutes (absolute, non-renewable).</summary>
public static int BackofficeProtectedSupportWindowMinutes = 5;

/// <summary>Hard maximum for BackofficeProtectedSupportWindowMinutes.</summary>
public static int BackofficeProtectedSupportWindowMaximumMinutes = 15;
}
}
1 change: 1 addition & 0 deletions Core/Resgrid.Config/ExternalErrorConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static class ExternalErrorConfig
public static string ExternalErrorServiceUrlForInternalWorker = "";
public static string ExternalErrorServiceUrlForMcp = "";
public static string ExternalErrorServiceUrlForTts = "";
public static string ExternalErrorServiceUrlForBroker = "";
public static double SentryPerfSampleRate = 0.4;
public static double SentryProfilingSampleRate = 0;
#endregion Sentry Settings
Expand Down
14 changes: 14 additions & 0 deletions Core/Resgrid.Config/PaymentProviderConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public static class PaymentProviderConfig
public static string PaddleTestBillingWebhookSigningKey = "";
public static string PaddlePTT10UserAddonPackage = "";
public static string PaddlePTT10UserAddonPackageTest = "";

// Advanced Data Protection yearly addon (Paddle product pro_01m11vjn9cjmgmwzgv2kt8wndk).
// The Stripe side lives on the PlanAddons row (M0126); Paddle price ids follow the PTT
// precedent and live here.
public static string PaddleAdpAddon = "pri_01m11vm50c17z0rxcgy4fppf80";
public static string PaddleAdpAddonTest = "";
public static string PaddleProductionEnvironment = "production";
public static string PaddleTestEnvironment = "sandbox";
public static string PaddleProductionClientToken = "";
Expand Down Expand Up @@ -139,6 +145,14 @@ public static string GetPaddlePTT10UserAddonPackageId()
return PaddlePTT10UserAddonPackage;
}

public static string GetPaddleAdpAddonPriceId()
{
if (IsTestMode)
return PaddleAdpAddonTest;
else
return PaddleAdpAddon;
}

public static string GetPaddleEnvironment()
{
if (IsTestMode)
Expand Down
20 changes: 20 additions & 0 deletions Core/Resgrid.Localization/Areas/User/Security/Security.ar.resx
Original file line number Diff line number Diff line change
Expand Up @@ -331,4 +331,24 @@
<data name="PermDispatchAppLoginNote" xml:space="preserve"><value>Controls who can sign in to the Dispatch app. Dispatch shows private command, unit and responder communications for every incident, so restrict this if your members are not all dispatchers.</value></data>
<data name="PermCommandAppLoginLabel" xml:space="preserve"><value>Command App Login</value></data>
<data name="PermCommandAppLoginNote" xml:space="preserve"><value>Controls who can act as a commander: sign in to the IC app, establish incident command on a call, and view command boards. Narrowing this beyond Everyone also lets the people you pick help work any command board (assign and move resources, run timers and accountability) without holding an ICS position on it &#8212; useful for giving dispatchers a hand in the Dispatch app. While set to Everyone, board actions stay limited to the incident commander and assigned ICS roles.</value></data>
<data name="PermAdpSectionHeader" xml:space="preserve"><value>Advanced Data Protection</value></data>
<data name="PermAdpSectionNote" xml:space="preserve"><value>These permissions control who may work with encrypted (protected) data when the Advanced Data Protection addon is active. Every reveal or edit additionally requires a recent two-factor verification; these settings choose who may even attempt it. Unlike most Resgrid permissions, unset values default to the restrictive selection shown.</value></data>
<data name="PermAdpManageLabel" xml:space="preserve"><value>Manage Data Protection Settings</value></data>
<data name="PermAdpManageNote" xml:space="preserve"><value>Who can change Advanced Data Protection settings such as the verification window and notification content options. Purchasing, enrollment and cancellation always remain restricted to the department managing member.</value></data>
<data name="PermAdpViewCallLabel" xml:space="preserve"><value>View Protected Call Data</value></data>
<data name="PermAdpViewCallNote" xml:space="preserve"><value>Who can reveal protected call fields (nature, address, contact info, notes) after two-factor verification. Defaults to Everyone because responding personnel must be able to read a dispatch.</value></data>
<data name="PermAdpEditCallLabel" xml:space="preserve"><value>Edit Protected Call Data</value></data>
<data name="PermAdpEditCallNote" xml:space="preserve"><value>Who can edit protected call fields after two-factor verification. Defaults to Everyone to match the normal call workflow.</value></data>
<data name="PermAdpViewPersonnelLabel" xml:space="preserve"><value>View Protected Personnel Data</value></data>
<data name="PermAdpViewPersonnelNote" xml:space="preserve"><value>Who can reveal protected personnel information (employee IDs, emergency contacts) after two-factor verification. Defaults to Department Admins.</value></data>
<data name="PermAdpViewContactLabel" xml:space="preserve"><value>View Protected Contact Data</value></data>
<data name="PermAdpViewContactNote" xml:space="preserve"><value>Who can reveal protected contact information (names, phone numbers, government IDs, locations) after two-factor verification. Defaults to Department Admins.</value></data>
<data name="PermAdpViewOperationalLabel" xml:space="preserve"><value>View Protected Operational Data</value></data>
<data name="PermAdpViewOperationalNote" xml:space="preserve"><value>Who can reveal protected operational content (logs, form submissions, incident command notes and attachments) after two-factor verification. Defaults to Department and Group Admins.</value></data>
<data name="PermAdpExportLabel" xml:space="preserve"><value>Export Protected Data</value></data>
<data name="PermAdpExportNote" xml:space="preserve"><value>Who can export data containing protected fields. Exports leave the protection of Resgrid, so every export is separately audited. Defaults to Department Admins; Everyone is deliberately not offered.</value></data>
<data name="PermAdpEgressLabel" xml:space="preserve"><value>Configure Protected Data Delivery</value></data>
<data name="PermAdpEgressNote" xml:space="preserve"><value>Who can change how protected content leaves Resgrid over push, SMS, email and voice. Defaults to Department Admins; Everyone is deliberately not offered.</value></data>
<data name="PermAdpBreakGlassLabel" xml:space="preserve"><value>Emergency Break-Glass Access</value></data>
<data name="PermAdpBreakGlassNote" xml:space="preserve"><value>Who may use the audited emergency access path for protected data. It only works if break-glass is enabled in the department protection policy, requires a recorded reason, and notifies the department. Defaults to Department Admins; Everyone is deliberately not offered.</value></data>
</root>
20 changes: 20 additions & 0 deletions Core/Resgrid.Localization/Areas/User/Security/Security.de.resx
Original file line number Diff line number Diff line change
Expand Up @@ -933,4 +933,24 @@
<data name="PermDispatchAppLoginNote" xml:space="preserve"><value>Controls who can sign in to the Dispatch app. Dispatch shows private command, unit and responder communications for every incident, so restrict this if your members are not all dispatchers.</value></data>
<data name="PermCommandAppLoginLabel" xml:space="preserve"><value>Command App Login</value></data>
<data name="PermCommandAppLoginNote" xml:space="preserve"><value>Controls who can act as a commander: sign in to the IC app, establish incident command on a call, and view command boards. Narrowing this beyond Everyone also lets the people you pick help work any command board (assign and move resources, run timers and accountability) without holding an ICS position on it &#8212; useful for giving dispatchers a hand in the Dispatch app. While set to Everyone, board actions stay limited to the incident commander and assigned ICS roles.</value></data>
<data name="PermAdpSectionHeader" xml:space="preserve"><value>Advanced Data Protection</value></data>
<data name="PermAdpSectionNote" xml:space="preserve"><value>These permissions control who may work with encrypted (protected) data when the Advanced Data Protection addon is active. Every reveal or edit additionally requires a recent two-factor verification; these settings choose who may even attempt it. Unlike most Resgrid permissions, unset values default to the restrictive selection shown.</value></data>
<data name="PermAdpManageLabel" xml:space="preserve"><value>Manage Data Protection Settings</value></data>
<data name="PermAdpManageNote" xml:space="preserve"><value>Who can change Advanced Data Protection settings such as the verification window and notification content options. Purchasing, enrollment and cancellation always remain restricted to the department managing member.</value></data>
<data name="PermAdpViewCallLabel" xml:space="preserve"><value>View Protected Call Data</value></data>
<data name="PermAdpViewCallNote" xml:space="preserve"><value>Who can reveal protected call fields (nature, address, contact info, notes) after two-factor verification. Defaults to Everyone because responding personnel must be able to read a dispatch.</value></data>
<data name="PermAdpEditCallLabel" xml:space="preserve"><value>Edit Protected Call Data</value></data>
<data name="PermAdpEditCallNote" xml:space="preserve"><value>Who can edit protected call fields after two-factor verification. Defaults to Everyone to match the normal call workflow.</value></data>
<data name="PermAdpViewPersonnelLabel" xml:space="preserve"><value>View Protected Personnel Data</value></data>
<data name="PermAdpViewPersonnelNote" xml:space="preserve"><value>Who can reveal protected personnel information (employee IDs, emergency contacts) after two-factor verification. Defaults to Department Admins.</value></data>
<data name="PermAdpViewContactLabel" xml:space="preserve"><value>View Protected Contact Data</value></data>
<data name="PermAdpViewContactNote" xml:space="preserve"><value>Who can reveal protected contact information (names, phone numbers, government IDs, locations) after two-factor verification. Defaults to Department Admins.</value></data>
<data name="PermAdpViewOperationalLabel" xml:space="preserve"><value>View Protected Operational Data</value></data>
<data name="PermAdpViewOperationalNote" xml:space="preserve"><value>Who can reveal protected operational content (logs, form submissions, incident command notes and attachments) after two-factor verification. Defaults to Department and Group Admins.</value></data>
<data name="PermAdpExportLabel" xml:space="preserve"><value>Export Protected Data</value></data>
<data name="PermAdpExportNote" xml:space="preserve"><value>Who can export data containing protected fields. Exports leave the protection of Resgrid, so every export is separately audited. Defaults to Department Admins; Everyone is deliberately not offered.</value></data>
<data name="PermAdpEgressLabel" xml:space="preserve"><value>Configure Protected Data Delivery</value></data>
<data name="PermAdpEgressNote" xml:space="preserve"><value>Who can change how protected content leaves Resgrid over push, SMS, email and voice. Defaults to Department Admins; Everyone is deliberately not offered.</value></data>
<data name="PermAdpBreakGlassLabel" xml:space="preserve"><value>Emergency Break-Glass Access</value></data>
<data name="PermAdpBreakGlassNote" xml:space="preserve"><value>Who may use the audited emergency access path for protected data. It only works if break-glass is enabled in the department protection policy, requires a recorded reason, and notifies the department. Defaults to Department Admins; Everyone is deliberately not offered.</value></data>
</root>
Loading
Loading