Merge pull request #2398 from DanielG/b2-hide-file
b2: Fallback to b2_hide_file when delete returns unauthorized
This commit is contained in:
commit
1bfe98bdc0
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
|
||||
|
@ -297,11 +301,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)
|
||||
|
||||
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