Skip to content

Commit 9bebe82

Browse files
Merge branch 'main' into security-bulletin-changelog-update
2 parents eafbe6a + 8fc7a33 commit 9bebe82

File tree

24 files changed

+440
-60
lines changed

24 files changed

+440
-60
lines changed

api/client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ const (
7070
// SSRF protection.
7171
RequestHeaderName = "X-Vault-Request"
7272

73+
SnapshotHeaderName = "X-Vault-Recover-Snapshot-Id"
74+
RecoverSourcePathHeaderName = "X-Vault-Recover-Source-Path"
75+
7376
TLSErrorString = "This error usually means that the server is running with TLS disabled\n" +
7477
"but the client is configured to use TLS. Please either enable TLS\n" +
7578
"on the server or run the client with -address set to an address\n" +

api/logical.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,9 @@ func (c *Logical) Recover(ctx context.Context, path string, snapshotID string) (
309309
func (c *Logical) RecoverFromPath(ctx context.Context, newPath string, snapshotID string, originalPath string) (*Secret, error) {
310310
r := c.c.NewRequest(http.MethodPut, "/v1/"+newPath)
311311
r.Params.Set("recover_snapshot_id", snapshotID)
312+
r.Headers.Set(SnapshotHeaderName, snapshotID)
312313
if originalPath != "" && originalPath != newPath {
313-
r.Params.Set("recover_source_path", url.QueryEscape(originalPath))
314+
r.Headers.Set(RecoverSourcePathHeaderName, originalPath)
314315
}
315316
return c.write(ctx, originalPath, r)
316317
}

api/sys_raft.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,10 +503,27 @@ func (c *Sys) RaftUnloadSnapshot(snapID string) (*Secret, error) {
503503
// RaftUnloadSnapshotWithContext unloads a snapshot from the raft cluster.
504504
// It accepts a snapshot ID to identify the snapshot to be unloaded.
505505
func (c *Sys) RaftUnloadSnapshotWithContext(ctx context.Context, snapID string) (*Secret, error) {
506+
return c.raftUnloadSnapshotWithContext(ctx, snapID, false)
507+
}
508+
509+
// RaftForceUnloadSnapshot wraps RaftForceUnloadSnapshotWithContext using context.Background.
510+
func (c *Sys) RaftForceUnloadSnapshot(snapID string) (*Secret, error) {
511+
return c.RaftForceUnloadSnapshotWithContext(context.Background(), snapID)
512+
}
513+
514+
// RaftForceUnloadSnapshotWithContext forcefully unloads the given snapshot
515+
func (c *Sys) RaftForceUnloadSnapshotWithContext(ctx context.Context, snapID string) (*Secret, error) {
516+
return c.raftUnloadSnapshotWithContext(ctx, snapID, true)
517+
}
518+
519+
func (c *Sys) raftUnloadSnapshotWithContext(ctx context.Context, snapID string, force bool) (*Secret, error) {
506520
ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
507521
defer cancelFunc()
508522

509523
r := c.c.NewRequest(http.MethodDelete, "/v1/sys/storage/raft/snapshot-load/"+snapID)
524+
if force {
525+
r.Params.Set("force", "true")
526+
}
510527

511528
resp, err := c.c.rawRequestWithContext(ctx, r)
512529
if err != nil {

builtin/logical/ssh/backend.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ func Backend(conf *logical.BackendConfig) (*backend, error) {
5656
caPrivateKeyStoragePath,
5757
keysStoragePrefix,
5858
},
59+
60+
AllowSnapshotRead: []string{
61+
"config/ca",
62+
},
5963
},
6064

6165
Paths: []*framework.Path{

builtin/logical/ssh/path_config_ca.go

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ func pathConfigCA(b *backend) *framework.Path {
107107
OperationSuffix: "ca-configuration",
108108
},
109109
},
110+
logical.RecoverOperation: &framework.PathOperation{
111+
Callback: b.pathConfigCARecover,
112+
},
110113
},
111114

112115
HelpSynopsis: `Set the SSH private key used for signing certificates.`,
@@ -120,7 +123,9 @@ Read operations will return the public key, if already stored/generated.`,
120123
}
121124

122125
func (b *backend) pathConfigCARead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
123-
publicKey, err := getCAPublicKey(ctx, req.Storage)
126+
// prevent migration from deprecated paths on snapshot read as writes to a loaded snapshot storage are forbidden
127+
allowMigration := !req.IsSnapshotReadOrList()
128+
publicKey, err := getCAPublicKey(ctx, req.Storage, allowMigration)
124129
if err != nil {
125130
return nil, fmt.Errorf("failed to read CA public key: %w", err)
126131
}
@@ -142,16 +147,22 @@ func (b *backend) pathConfigCADelete(ctx context.Context, req *logical.Request,
142147
if err := req.Storage.Delete(ctx, caPrivateKeyStoragePath); err != nil {
143148
return nil, err
144149
}
150+
if err := req.Storage.Delete(ctx, caPrivateKeyStoragePathDeprecated); err != nil {
151+
return nil, err
152+
}
145153
if err := req.Storage.Delete(ctx, caPublicKeyStoragePath); err != nil {
146154
return nil, err
147155
}
156+
if err := req.Storage.Delete(ctx, caPublicKeyStoragePathDeprecated); err != nil {
157+
return nil, err
158+
}
148159
if err := req.Storage.Delete(ctx, caManagedKeyStoragePath); err != nil {
149160
return nil, err
150161
}
151162
return nil, nil
152163
}
153164

154-
func readStoredKey(ctx context.Context, storage logical.Storage, keyType string) (*keyStorageEntry, error) {
165+
func readStoredKeyEntry(ctx context.Context, storage logical.Storage, keyType string, allowMigration bool) (*logical.StorageEntry, error) {
155166
var path, deprecatedPath string
156167
switch keyType {
157168
case caPrivateKey:
@@ -176,25 +187,39 @@ func readStoredKey(ctx context.Context, storage logical.Storage, keyType string)
176187
if err != nil {
177188
return nil, err
178189
}
190+
179191
if entry != nil {
192+
// modify entry variable, both for possible migration and also to comply with the expected JSON entry for the caller
180193
entry, err = logical.StorageEntryJSON(path, keyStorageEntry{
181194
Key: string(entry.Value),
182195
})
183196
if err != nil {
184197
return nil, err
185198
}
186-
if err := storage.Put(ctx, entry); err != nil {
187-
return nil, err
188-
}
189-
if err = storage.Delete(ctx, deprecatedPath); err != nil {
190-
return nil, err
199+
// migrations are disable on recover, as we can't write to the loaded snapshot storage
200+
if allowMigration {
201+
if err := storage.Put(ctx, entry); err != nil {
202+
return nil, err
203+
}
204+
if err = storage.Delete(ctx, deprecatedPath); err != nil {
205+
return nil, err
206+
}
191207
}
192208
}
193209
}
210+
return entry, nil
211+
}
212+
213+
// readStoredKey reads a key from storage, returning nil if not found.
214+
// ignore-nil-nil-function-check
215+
func readStoredKey(ctx context.Context, storage logical.Storage, keyType string, allowMigration bool) (*keyStorageEntry, error) {
216+
entry, err := readStoredKeyEntry(ctx, storage, keyType, allowMigration)
217+
if err != nil {
218+
return nil, err
219+
}
194220
if entry == nil {
195221
return nil, nil
196222
}
197-
198223
var keyEntry keyStorageEntry
199224
if err := entry.DecodeJSON(&keyEntry); err != nil {
200225
return nil, err
@@ -450,10 +475,10 @@ func (b *backend) createManagedKey(ctx context.Context, s logical.Storage, manag
450475
return nil
451476
}
452477

453-
func getCAPublicKey(ctx context.Context, storage logical.Storage) (string, error) {
478+
func getCAPublicKey(ctx context.Context, storage logical.Storage, allowMigration bool) (string, error) {
454479
var publicKey string
455480

456-
storedKeyEntry, err := readStoredKey(ctx, storage, caPublicKey)
481+
storedKeyEntry, err := readStoredKey(ctx, storage, caPublicKey, allowMigration)
457482
if err != nil {
458483
return "", err
459484
}
@@ -495,12 +520,13 @@ func readManagedKey(ctx context.Context, storage logical.Storage) (*managedKeySt
495520
}
496521

497522
func caKeysConfigured(ctx context.Context, s logical.Storage) (bool, error) {
498-
publicKeyEntry, err := readStoredKey(ctx, s, caPublicKey)
523+
const allowMigration = false // no need to allow migration when just checking for existence, we can do that later
524+
publicKeyEntry, err := readStoredKey(ctx, s, caPublicKey, allowMigration)
499525
if err != nil {
500526
return false, fmt.Errorf("failed to read CA public key: %w", err)
501527
}
502528

503-
privateKeyEntry, err := readStoredKey(ctx, s, caPrivateKey)
529+
privateKeyEntry, err := readStoredKey(ctx, s, caPrivateKey, allowMigration)
504530
if err != nil {
505531
return false, fmt.Errorf("failed to read CA private key: %w", err)
506532
}
@@ -520,3 +546,64 @@ func caKeysConfigured(ctx context.Context, s logical.Storage) (bool, error) {
520546

521547
return false, nil
522548
}
549+
550+
// pathConfigCARecover recovers the CA from the target snapshot back to the live storage.
551+
// ignore-nil-nil-function-check
552+
func (b *backend) pathConfigCARecover(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
553+
// check live storage for existing keys. Disallow recovery if CA is already configured for consistency with create operation
554+
found, err := caKeysConfigured(ctx, req.Storage)
555+
if err != nil {
556+
return nil, err
557+
}
558+
if found {
559+
return logical.ErrorResponse("keys are already configured; delete them before recovering the CA"), nil
560+
}
561+
562+
// fetch directly from the snapshot storage instead of following the usual restore procedure of getting the values
563+
// from the req.Data, since those came from a previous CARead operation on the loaded snapshot, which only contains
564+
// the public key.
565+
snapshotStorage, err := logical.NewSnapshotStorageView(req)
566+
if err != nil {
567+
return nil, err
568+
}
569+
const allowMigration = false // prevent migration from deprecated paths as we can't allow writes on the snapshot storage
570+
publicKeyEntry, err := readStoredKeyEntry(ctx, snapshotStorage, caPublicKey, allowMigration)
571+
if err != nil {
572+
return nil, fmt.Errorf("failed to read CA public key for restore: %w", err)
573+
}
574+
privateKeyEntry, err := readStoredKeyEntry(ctx, snapshotStorage, caPrivateKey, allowMigration)
575+
if err != nil {
576+
return nil, fmt.Errorf("failed to read CA private key for restore: %w", err)
577+
}
578+
managedKey, err := readManagedKey(ctx, snapshotStorage)
579+
if err != nil {
580+
return nil, fmt.Errorf("failed to read CA managed key for restore: %w", err)
581+
}
582+
583+
if publicKeyEntry == nil && privateKeyEntry == nil && managedKey == nil {
584+
return logical.ErrorResponse("no CA keys found in snapshot storage to restore"), nil
585+
}
586+
587+
// it's possible that we've read the keys from a deprecated path in the snapshot, but it should be automatically
588+
// upgraded to the new path anyway, so we don't care about restoring it back to the deprecated path
589+
if publicKeyEntry != nil {
590+
err = req.Storage.Put(ctx, publicKeyEntry)
591+
if err != nil {
592+
return nil, fmt.Errorf("failed to restore public key entry in storage: %w", err)
593+
}
594+
}
595+
if privateKeyEntry != nil {
596+
err = req.Storage.Put(ctx, privateKeyEntry)
597+
if err != nil {
598+
return nil, fmt.Errorf("failed to restore private key entry in storage: %w", err)
599+
}
600+
}
601+
if managedKey != nil {
602+
err = b.createManagedKey(ctx, req.Storage, managedKey.KeyName.String(), managedKey.KeyId.String())
603+
if err != nil {
604+
return nil, fmt.Errorf("failed to restore managed key entry in storage: %w", err)
605+
}
606+
}
607+
608+
return nil, nil
609+
}

0 commit comments

Comments
 (0)