Commit ff893a7
authored
feat: support storing secrets/credentials in Postgres (#2665)
## Description
This supports #353.
This introduces a new secrets/credentials storage backend of sorts, such
that _NICo_ can now keep its credentials in _Postgres_ instead of
_Vault_, encrypted (with envelope encryption, leaning on
#939) so that a copy of
the database on its own gives up nothing. This entirely feature/flagged
and configurable, currently not enabled at all, but will let us:
- Use it _alongside_ Vault.
- Use it to _migrate away_ from Vault.
The encryption is layered. Every credential gets its own _data
encryption key_ (DEK), and that DEK is wrapped by a _key encryption key_
(KEK) that lives outside the database -- in NICo itself or whatever
`KmsProvider` we choose. The credential's path is mixed into the
encryption as well. The KEK is the only thing an operator has to
protect; everything else lives in Postgres (some reference
[here](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#envelope-encryption)
and [here](https://docs.cloud.google.com/kms/docs/envelope-encryption).
`PostgresCredentialManager` implements the same `CredentialManager`
traits the rest of the system already uses, so nothing downstream had to
change. It keeps the two reader behaviors callers learned from Vault:
the newest write for a path wins, and an empty-password entry reads as
"no credential" (several delete paths still record that tombstone). The
store is an append-only journal -- one row per write, newest-per-path on
read -- which the rotation work going on in
#367 can use for
history, rollback, rotation, etc.
Where the wrapping keys come from is pluggable through `KmsBackend`:
local key material (`integrated`), or Vault/OpenBao [Transit secrets
engine
API](https://developer.hashicorp.com/vault/api-docs/secret/transit), and
more than one provider at once. `[secrets.routing]` maps path prefixes
to the KEK that encrypts new writes under them. Startup cross-checks the
routing against the providers, so a misspelled, duplicated, or colliding
key fails at startup.
Moving an existing site off _Vault_ could be:
- **A one-time import at startup** -- deliberately all-or-nothing: any
list or read failure, or an empty listing, aborts the boot rather than
recording a half-finished import as done, and only one replica runs it
at a time behind a Postgres advisory lock. Once it completes, Vault is
out of the credential chain entirely -- with `[secrets]` set, the chain
is env -> file -> postgres and nothing falls back to Vault.
Prerequisites that live outside this process (services that still read
Vault directly, mixed-fleet rolling upgrades) are spelled out on
`SecretsConfig`.
- **A slow migration** -- we would introduce it as the primary
`CredentialWriter`, such that all new credentials get written to
_Postgres_, with _Vault_ still in the `ChainedCredentialReader` path,
allowing us to slowly migrate over.
**BUT, migration is NOT a part of this PR. This PR is for getting some
initial code in place, and then we will iterate on it, and then do
subsequent work for migration planning.**
Rotating a KEK is a config change plus one command: point the route at
the new key and run `carbide-admin-cli secrets re-wrap`. Only the
per-row data-key wrapping is redone; the encrypted values are never
touched. The re-wrap makes its KMS calls outside the write transaction,
runs one at a time, and reports how many rows still sit on a retired key
so you know when the old KEK is safe to remove.
Tests cover the manager round-trip, journal write-order under equal
timestamps, rotation rollback by deleting the newest entry, tombstone
reads, create conflicts, re-wrap idempotence and counting, and a
path-binding regression that confirms a transplanted row will not
decrypt -- plus an end-to-end Vault import against a real Vault dev
server.
Signed-off-by: Chet Nichols III <chetn@nvidia.com>
## Related issues
<!-- Refer to existing GitHub issues here -->
## Type of Change
<!-- Check one that best describes this PR -->
- [x] **Add** - New feature or capability
- [ ] **Change** - Changes in existing functionality
- [ ] **Fix** - Bug fixes
- [ ] **Remove** - Removed features or deprecated functionality
- [ ] **Internal** - Internal changes (refactoring, tests, docs, etc.)
## Breaking Changes
<!-- If checked, describe the breaking changes and migration steps -->
<!-- Breaking changes are not generally permitted, please discuss on a
GitHub discussion or with the development team if you believe you need
to break a backward compatibility guarantee -->
- [ ] **This PR contains breaking changes**
## Testing
<!-- How was this tested? Check all that apply -->
- [x] Unit tests added/updated
- [x] Integration tests added/updated
- [ ] Manual testing performed
- [ ] No testing required (docs, internal refactor, etc.)
## Additional Notes
<!-- Any additional context, deployment notes, or reviewer guidance -->
Signed-off-by: Chet Nichols III <chetn@nvidia.com>1 parent 5cfb65c commit ff893a7
43 files changed
Lines changed: 3449 additions & 158 deletions
File tree
- crates
- admin-cli
- src
- cfg
- secrets
- re_wrap
- api-core
- src
- auth
- cfg
- handlers
- secrets
- test_support
- api-db
- migrations
- src
- api-integration-tests
- tests
- api-model/src
- kms-provider
- src
- providers
- rpc/proto
- secrets/src
- uuid/src
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
| 84 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
29 | | - | |
30 | | - | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| |||
202 | 202 | | |
203 | 203 | | |
204 | 204 | | |
| 205 | + | |
| 206 | + | |
205 | 207 | | |
206 | 208 | | |
207 | 209 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
| 106 | + | |
106 | 107 | | |
107 | 108 | | |
108 | 109 | | |
| |||
274 | 275 | | |
275 | 276 | | |
276 | 277 | | |
| 278 | + | |
277 | 279 | | |
278 | 280 | | |
279 | 281 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
| |||
180 | 181 | | |
181 | 182 | | |
182 | 183 | | |
| 184 | + | |
183 | 185 | | |
| 186 | + | |
184 | 187 | | |
185 | 188 | | |
186 | 189 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
| 91 | + | |
91 | 92 | | |
92 | 93 | | |
93 | 94 | | |
| |||
1444 | 1445 | | |
1445 | 1446 | | |
1446 | 1447 | | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
1447 | 1455 | | |
1448 | 1456 | | |
1449 | 1457 | | |
| |||
0 commit comments