# HashiCorp Vault

Optional Socigy.OpenSource.DB.HashiCorp package: field encryption keyed from Vault and rotating PostgreSQL credentials from Vault's Database secrets engine, wired with one DI call each.

The optional **`Socigy.OpenSource.DB.HashiCorp`** package integrates Socigy with [HashiCorp Vault](https://www.vaultproject.io/) for two things:

1. **Field encryption.** It supplies the `IFieldEncryptor` for [`[Encrypted]` columns](/database/0.3.2/defining-models/encrypted-columns), keyed from Vault.
2. **Rotating DB credentials.** It supplies an `IDbCredentialsProvider` that leases short-lived PostgreSQL credentials from Vault's Database secrets engine, which the generated connection factory consumes automatically.

```bash
dotnet add package Socigy.OpenSource.DB.HashiCorp
```

> **NOTE** This package depends on the main `Socigy.OpenSource.DB` package and `VaultSharp`. The two Vault features are independent: register either or both.

## Field encryption

The data-encryption key is read from a **Vault KV-v2 secret at startup** and used for fast, local AES-256-CBC + HMAC encryption. Per-field crypto stays synchronous with no Vault round-trip per row. Store a Base64-encoded 32-byte key in Vault:

```bash
vault kv put secret/socigy/db-encryption-key key="$(openssl rand -base64 32)"
```

Then register it:

```csharp
builder.Services.AddSocigyVaultEncryption(o =>
{
    o.Address = "https://vault.example.com:8200";
    o.Token = builder.Configuration["Vault:Token"];     // or AppRoleId + AppRoleSecretId
    o.KvMountPoint = "secret";
    o.KeySecretPath = "socigy/db-encryption-key";
    o.KeyField = "key";
});
```

At host start the package reads the key and installs the ambient `SocigyFieldEncryption` encryptor, so every `[Encrypted]` column just works.

> **NOTE** Key material is fetched into process memory for local crypto. Rotating the key means updating the KV secret and re-encrypting existing rows. (A Transit data-key envelope mode that keeps old rows readable across rotations is a planned enhancement.)

## Rotating database credentials

Configure Vault's Database secrets engine with a role per database, then map each Socigy database name to its Vault role:

```csharp
builder.Services.AddSocigyVaultCredentials(o =>
{
    o.Address = "https://vault.example.com:8200";
    o.AppRoleId = builder.Configuration["Vault:RoleId"];
    o.AppRoleSecretId = builder.Configuration["Vault:SecretId"];
    o.DatabaseMountPoint = "database";
    o.BaseConnectionString = "Host=db.internal;Port=5432;Pooling=true";   // no user/pass
    o.DatabaseRoles["AuthDb"] = "auth-db-role";
    o.DatabaseRoles["UserDb"] = "user-db-role";
    o.RefreshInterval = TimeSpan.FromMinutes(30);   // fallback only; renewal normally tracks the lease TTL
});

builder.AddAuthDb();   // the generated factory picks up IDbCredentialsProvider automatically
builder.AddUserDb();
```

How it fits together:

- At startup the package leases credentials for each configured database and composes a connection string (`BaseConnectionString` + the leased `Username`/`Password`), cached in memory. The string is built with `DbConnectionStringBuilder`, so leased passwords containing `;`, `=`, quotes or spaces are escaped correctly.
- The generated connection factory calls the provider **synchronously** for the current connection string each time it opens a connection. See [Connections & DI](/database/0.3.2/core-concepts/connections-and-di).
- A background timer renews credentials at **~2/3 of the actual lease TTL** (`LeaseDurationSeconds` returned by Vault), so they are refreshed before they expire even when the role issues short leases. `RefreshInterval` is only a fallback used when the lease TTL is unknown. When credentials rotate, new connections use the new string and Npgsql's old pool drains naturally.

## Authentication

Both `Add…` calls accept either a **token** (`Token`) or **AppRole** (`AppRoleId` + `AppRoleSecretId`). AppRole is recommended for production workloads.

The Vault **auth token is kept alive automatically** by a background service: it renews the token (renew-self) before it expires, and when renewal can no longer extend it (max TTL) and AppRole credentials are configured, it re-logs-in for a fresh token. A **static, non-renewable token cannot be kept alive** — for long-running services use AppRole, a periodic token, or a renewable token. The library logs a clear error if it detects a token it cannot keep alive.

> **WARNING** Use an `https://` Vault `Address` in production. If the address is plaintext `http://` to a non-loopback host, the library logs a warning — tokens, keys, and leased credentials would otherwise travel unencrypted. Plain `http://` to loopback is fine for local development.

## Diagnostics

All background actions are observable so admins can track what the library does:

- **OpenTelemetry spans** under the existing `Socigy.OpenSource.DB` ActivitySource: `vault.encryption.key.fetch`, `vault.credentials.lease` (with `db.name`, `vault.database.role`, and lease-duration tags), and `vault.token.renew` / `vault.token.relogin` for background auth-token upkeep. Subscribe with `AddSource("Socigy.OpenSource.DB")` (see [Diagnostics & OpenTelemetry](/database/0.3.2/observability/diagnostics)).
- **`ILogger` messages** under categories `Socigy.OpenSource.DB.Vault.Encryption` and `Socigy.OpenSource.DB.Vault.Credentials`: key load, each credential lease (database, role, user, lease seconds), renewal ticks, and failures. The connection factory also logs when it refreshes rotating credentials, and `SocigyFieldEncryption.Configure` logs when an encryptor is installed.
