b2: Support file hiding instead of deleting them permanently
Automatically fall back to hiding files if not authorized to permanently delete files. This allows using restic with an append-only application key with B2. Thus, an attacker cannot directly delete backups with the API key used by restic. To use this feature create an application key without the deleteFiles capability. It is recommended to restrict the key to just one bucket. For example using the b2 command line tool: b2 create-key --bucket <bucketName> <keyName> listBuckets,readFiles,writeFiles,listFiles Suggested-by: Daniel Gröber <dxld@darkboxed.org>
This commit is contained in:
parent
24a2e5cab9
commit
1ccab95bc4
2 changed files with 43 additions and 4 deletions
19
changelog/unreleased/issue-2134
Normal file
19
changelog/unreleased/issue-2134
Normal file
|
@ -0,0 +1,19 @@
|
|||
Enhancement: Support B2 API keys restricted to hiding but not deleting files
|
||||
|
||||
When the B2 backend does not have the necessary permissions to permanently
|
||||
delete files, it now automatically falls back to hiding files. This allows
|
||||
using restic with an application key which is not allowed to delete files.
|
||||
This prevents an attacker to delete backups with the API key used by restic.
|
||||
|
||||
To use this feature create an application key without the deleteFiles
|
||||
capability. It is recommended to restrict the key to just one bucket.
|
||||
For example using the b2 command line tool:
|
||||
|
||||
b2 create-key --bucket <bucketName> <keyName> listBuckets,readFiles,writeFiles,listFiles
|
||||
|
||||
Alternatively, you can use the S3 backend to access B2, as described
|
||||
in the documentation. In this mode, files are also only hidden instead
|
||||
of being deleted permanently.
|
||||
|
||||
https://github.com/restic/restic/issues/2134
|
||||
https://github.com/restic/restic/pull/2398
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/kurin/blazer/b2"
|
||||
"github.com/kurin/blazer/base"
|
||||
)
|
||||
|
||||
// b2Backend is a backend which stores its data on Backblaze B2.
|
||||
|
@ -28,6 +29,8 @@ type b2Backend struct {
|
|||
listMaxItems int
|
||||
layout.Layout
|
||||
sem sema.Semaphore
|
||||
|
||||
canDelete bool
|
||||
}
|
||||
|
||||
// Billing happens in 1000 item granlarity, but we are more interested in reducing the number of network round trips
|
||||
|
@ -104,6 +107,7 @@ func Open(ctx context.Context, cfg Config, rt http.RoundTripper) (restic.Backend
|
|||
},
|
||||
listMaxItems: defaultListMaxItems,
|
||||
sem: sem,
|
||||
canDelete: true,
|
||||
}
|
||||
|
||||
return be, nil
|
||||
|
@ -314,11 +318,27 @@ func (be *b2Backend) Remove(ctx context.Context, h restic.Handle) error {
|
|||
// the retry backend will also repeat the remove method up to 10 times
|
||||
for i := 0; i < 3; i++ {
|
||||
obj := be.bucket.Object(be.Filename(h))
|
||||
err := obj.Delete(ctx)
|
||||
if err == nil {
|
||||
// keep deleting until we are sure that no leftover file versions exist
|
||||
continue
|
||||
|
||||
var err error
|
||||
if be.canDelete {
|
||||
err = obj.Delete(ctx)
|
||||
if err == nil {
|
||||
// keep deleting until we are sure that no leftover file versions exist
|
||||
continue
|
||||
}
|
||||
|
||||
code, _ := base.Code(err)
|
||||
if code == 401 { // unauthorized
|
||||
// fallback to hide if not allowed to delete files
|
||||
be.canDelete = false
|
||||
debug.Log("Removing %v failed, falling back to b2_hide_file.", h)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// hide adds a new file version hiding all older ones, thus retries are not necessary
|
||||
err = obj.Hide(ctx)
|
||||
}
|
||||
|
||||
// consider a file as removed if b2 informs us that it does not exist
|
||||
if b2.IsNotExist(err) {
|
||||
return nil
|
||||
|
|
Loading…
Reference in a new issue