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/cenkalti/backoff/v4"
|
||||||
"github.com/kurin/blazer/b2"
|
"github.com/kurin/blazer/b2"
|
||||||
|
"github.com/kurin/blazer/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
// b2Backend is a backend which stores its data on Backblaze B2.
|
// b2Backend is a backend which stores its data on Backblaze B2.
|
||||||
|
@ -28,6 +29,8 @@ type b2Backend struct {
|
||||||
listMaxItems int
|
listMaxItems int
|
||||||
layout.Layout
|
layout.Layout
|
||||||
sem sema.Semaphore
|
sem sema.Semaphore
|
||||||
|
|
||||||
|
canDelete bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Billing happens in 1000 item granlarity, but we are more interested in reducing the number of network round trips
|
// 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,
|
listMaxItems: defaultListMaxItems,
|
||||||
sem: sem,
|
sem: sem,
|
||||||
|
canDelete: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
return be, nil
|
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
|
// the retry backend will also repeat the remove method up to 10 times
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
obj := be.bucket.Object(be.Filename(h))
|
obj := be.bucket.Object(be.Filename(h))
|
||||||
err := obj.Delete(ctx)
|
|
||||||
if err == nil {
|
var err error
|
||||||
// keep deleting until we are sure that no leftover file versions exist
|
if be.canDelete {
|
||||||
continue
|
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
|
// consider a file as removed if b2 informs us that it does not exist
|
||||||
if b2.IsNotExist(err) {
|
if b2.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in a new issue