azure: Unable to init repo when using container-level SAS (SAT)
Output of restic version
restic 0.14.0 (v0.14.0-234-g9354262b1-dirty) compiled with go1.19 on darwin/amd64
How did you run restic exactly?
export AZURE_ACCOUNT_NAME=storageaccountname
export AZURE_ACCOUNT_SAS='si=containername&spr=https&sv=2021-06-08&sr=c&sig=deadbeef'
export RESTIC_PASSWORD=password
restic -r azure:containername:/backup init
What backend/server/service did you use to store the repository?
Azure Blob Storage
Expected behavior
created restic repository f2b2e727bb at azure:containername:/backup
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
Actual behavior
Fatal: create repository at azure:containername:/backup failed: container.CreateIfNotExists: storage: service returned error: StatusCode=403, ErrorCode=AuthorizationFailure, ErrorMessage=This request is not authorized to perform this operation.
RequestId:e0f0387b-701e-0037-26e0-efa955000000
Time:2022-11-03T23:57:40.3114536Z, RequestInitiated=Thu, 03 Nov 2022 23:57:39 GMT, RequestId=e0f0387b-701e-0037-26e0-efa955000000, API Version=2021-06-08, QueryParameterName=, QueryParameterValue=
Steps to reproduce the behavior
In Azure Blob Storage, create a Storage Account, then in that SA create a Container, and in that Container create a Shared Access Token (which is basically the same thing as a Shared Access Signature that you can create on the Storage Account level), then run the commands above.
Do you have any idea what may have caused this?
This has nothing to do with the /backup path in the example - the symptoms are the same even with just azure:containername:/ as the repository.
The problem seems to be that when using a SAS/SAT on the container level, there is no permission to perform actions that have to do with creating the container - the SAS/SAT is only allowed to poke around inside the existing container, which makes sense.
The Azure library restic uses calls container.CreateIfNotExists which is where things fail, because it tries to create a container, which is denied.
Do you have an idea how to solve the issue?
In order to support SAS/SAT on a container level we must make the code not attempt to create a container (or similar), and instead just presume that it is there. The entire SAS/SAT can not have been created without the container already existing, in this case.
I guess we have two options to handle this:
- If creation of the backend fails (this is what fails here,
create(ctx, repo, gopts.extended)), ignore this and somehow instantiate a backend anyway, then carry on and try to initialize a repository at that backend (s.Init(ctx, version, gopts.password, chunkerPolynomial)). Not sure how feasible and clean this is. - Inspect the
AZURE_ACCOUNT_SASvalue and determine if it's an account- or container-level SAS/SAT, and depending on that do or skip the backend creation before initializing a repository on the backend. We'd still need to instantiate a backend in the code, so I'm not sure how to do that cleanly either. Perhaps it's simple.
Did restic help you today? Did it make you happy in any way?
Always 👍
EDIT: A workaround is to temporarily use an account-level SAS (or the regular account key, if one fancies that instead) for the restic init and after that use the container-level SAS/SAT as usual for the other restic commands (works fine since they won't try to create the container).
Just ignoring the permission error when creating the container, should probably just work. The S3 backend does something similar:
https://github.com/restic/restic/blob/8dd95b710e6ba2a267c27f7ad0087d68cad7bdfd/internal/backend/s3/s3.go#L142-L145
If the access token is missing privileges, then creating the config file will just fail.
I can confirm that ignoring the error does just work. I chucked a check in the Create method that is functionally the same as the S3 example above and the repository was successfully created.
162 } else if err != nil && bloberror.HasCode(err, bloberror.AuthorizationFailure) {
163 debug.Log(" Cannot call GetProperties due to AuthorizationFailure. Ignoring - see Issue #4004")
164 err = nil
165 }
If this is the approach you want to take with the project, I can do a pull request for the change; if not I don't mind because I'm happy to patch my version of restic as needed.
Ignoring access denied errors when creating the container should be fine. We're already doing something similar in the S3 backend (see https://github.com/restic/restic/blob/bcd5ac34bb2c178605826e0e6ead7ebbccf4fc78/internal/backend/s3/s3.go#L201).
So, yes we'd be interested in a PR :-) .
OK, I will/I have done a PR for this, after tidying it up to have tests and such.