Public Access
Settings → Security gains a SPELUNK section: an API address and a key, saved as a new keychain item, a Spelunk key (SyncEntityType 14, AadResourceType 17, server table spelunk_key via the AddSpelunkKeyItem migration). It is ciphertext only on the server, lives in the personal vault, syncs like every secret, and holds the key together with the one address it may be sent to. The key box empties once saved; the list shows name, address and how many hosts use each key, never the key. Saving a key for an address that already has one replaces it, and deleting goes through the shared confirmation card with the hosts that would stop connecting. A host's route names a saved key where it named a credential, in the same apiKeyCredentialId field, so no host schema changes and an older build reads such a host as one whose key credential has gone. At connect a saved key is sent only to its own address; a host naming it with another Spelunk address is refused in words. Keys kept as a credential's password still work. The editor offers saved keys first and picks the one saved for an address as it is typed. A link from a Spelunk with a saved key now connects straight away, including the first one: saving the key is the user's say-so that the first-link editor used to ask for. The address and key merge as one pair, so two concurrent edits can never pair one device's key with another's address, and the key never reaches the conflict log.
112 lines
4.5 KiB
C#
112 lines
4.5 KiB
C#
using DodoSSH.Domain;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace DodoSSH.Infrastructure;
|
|
|
|
/// <summary>
|
|
/// The application database context.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Everything lives in the <c>dodo</c> schema with snake_case names. Timestamps are
|
|
/// <c>timestamptz</c> and always UTC, so there is no tzdata dependency and no conflict with
|
|
/// <c>InvariantGlobalization</c>.
|
|
/// </para>
|
|
/// <para>
|
|
/// Two concurrency mechanisms coexist deliberately. <c>Version</c> on item rows is the
|
|
/// client-visible, monotonic value used for <c>expectedVersion</c> conflict detection.
|
|
/// <c>xmin</c> is the server-side optimistic guard and is never exposed, because it is not stable
|
|
/// across <c>VACUUM FREEZE</c> and must never become a client cursor.
|
|
/// </para>
|
|
/// </remarks>
|
|
public class DodoDbContext(DbContextOptions<DodoDbContext> options) : DbContext(options)
|
|
{
|
|
/// <summary>The database schema every table lives in.</summary>
|
|
public const string SchemaName = "dodo";
|
|
|
|
/// <summary>User accounts.</summary>
|
|
public DbSet<UserAccount> Users => Set<UserAccount>();
|
|
|
|
/// <summary>Identity key generations.</summary>
|
|
public DbSet<UserKey> UserKeys => Set<UserKey>();
|
|
|
|
/// <summary>Wraps of users' secret bundles.</summary>
|
|
public DbSet<UserKeyWrap> UserKeyWraps => Set<UserKeyWrap>();
|
|
|
|
/// <summary>Enrolled devices.</summary>
|
|
public DbSet<Device> Devices => Set<Device>();
|
|
|
|
/// <summary>The append-only key transparency log.</summary>
|
|
public DbSet<KeyLogEntry> KeyLog => Set<KeyLogEntry>();
|
|
|
|
/// <summary>Teams.</summary>
|
|
public DbSet<Team> Teams => Set<Team>();
|
|
|
|
/// <summary>Team memberships.</summary>
|
|
public DbSet<TeamMembership> TeamMemberships => Set<TeamMembership>();
|
|
|
|
/// <summary>Vaults.</summary>
|
|
public DbSet<Vault> Vaults => Set<Vault>();
|
|
|
|
/// <summary>Wrapped vault keys.</summary>
|
|
public DbSet<VaultKeyGrant> VaultKeyGrants => Set<VaultKeyGrant>();
|
|
|
|
/// <summary>SSH hosts.</summary>
|
|
public DbSet<SshHost> Hosts => Set<SshHost>();
|
|
|
|
/// <summary>SSH key pairs, held as ciphertext.</summary>
|
|
public DbSet<VaultSshKey> SshKeys => Set<VaultSshKey>();
|
|
|
|
/// <summary>Usernames and passwords, held as ciphertext.</summary>
|
|
public DbSet<VaultCredential> Credentials => Set<VaultCredential>();
|
|
|
|
/// <summary>Trusted SSH host keys, held as ciphertext.</summary>
|
|
public DbSet<VaultKnownHostKey> KnownHostKeys => Set<VaultKnownHostKey>();
|
|
|
|
/// <summary>Host groups, held as ciphertext.</summary>
|
|
public DbSet<VaultHostGroup> HostGroups => Set<VaultHostGroup>();
|
|
|
|
/// <summary>Tags hosts can wear, held as ciphertext.</summary>
|
|
public DbSet<VaultTag> Tags => Set<VaultTag>();
|
|
|
|
/// <summary>Saved commands, held as ciphertext.</summary>
|
|
public DbSet<VaultSnippet> Snippets => Set<VaultSnippet>();
|
|
|
|
/// <summary>Connections that were made, held as ciphertext.</summary>
|
|
public DbSet<VaultConnectionLogEntry> ConnectionLog => Set<VaultConnectionLogEntry>();
|
|
|
|
/// <summary>Changes made to keychain items, held as ciphertext.</summary>
|
|
public DbSet<VaultActivityLogEntry> ActivityLog => Set<VaultActivityLogEntry>();
|
|
|
|
/// <summary>S3-compatible buckets and their credentials, held as ciphertext.</summary>
|
|
public DbSet<VaultObjectStore> ObjectStores => Set<VaultObjectStore>();
|
|
|
|
/// <summary>Spelunk API keys and the addresses they may be sent to, held as ciphertext.</summary>
|
|
public DbSet<VaultSpelunkKey> SpelunkKeys => Set<VaultSpelunkKey>();
|
|
|
|
/// <summary>The per-vault change log that delta sync reads.</summary>
|
|
public DbSet<VaultChange> VaultChanges => Set<VaultChange>();
|
|
|
|
/// <summary>Applied-operation receipts, for exactly-once retries.</summary>
|
|
public DbSet<SyncOperationReceipt> SyncOperationReceipts => Set<SyncOperationReceipt>();
|
|
|
|
/// <summary>Redeemed relay tickets: the replay guard ADR 0004 shares between nodes.</summary>
|
|
public DbSet<RelayTicketUse> RelayTicketUses => Set<RelayTicketUse>();
|
|
|
|
/// <summary>The relay's audit record, one row per session.</summary>
|
|
public DbSet<RelaySession> RelaySessions => Set<RelaySession>();
|
|
|
|
/// <inheritdoc />
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(modelBuilder);
|
|
|
|
modelBuilder.HasDefaultSchema(SchemaName);
|
|
modelBuilder.HasPostgresExtension("citext");
|
|
|
|
modelBuilder.ApplyConfigurationsFromAssembly(typeof(DodoDbContext).Assembly);
|
|
|
|
base.OnModelCreating(modelBuilder);
|
|
}
|
|
}
|