nextcloud / nextcloud/server

S3 external storage rejects bucket-restricted Backblaze B2 keys due to HeadBucket check

Open
#60,791 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

0. Needs triage 32-feedback bug feature: external storage feature: object storage pending documentation
Dominant language
PHP
Stars
36.9k
Forks
5.2k
Avg merge
2d 3h
Merged PRs (30d)
713

Description

⚠️ This issue respects the following points: ⚠️
Bug description

Summary

apps/files_external/lib/Lib/Storage/AmazonS3.php::test() calls headBucket() to verify the configured external S3 storage is reachable. The Availability storage wrapper invokes test() and, when it throws, records available=false in the storage availability cache. Subsequent file operations short-circuit to HTTP 503 until RECHECK_TTL_SEC (10 min) and the next test() again succeeds.

Backblaze B2 (which Nextcloud supports as an S3-compatible target via the AmazonS3 external storage backend) returns HTTP 403 Forbidden for HeadBucket on any application key that has a bucket restriction (i.e., any key created with bucketIds), regardless of the key's capabilities. Therefore an external storage mount backed by a B2 bucket-restricted key is permanently marked unavailable, even though the key can perform all relevant file operations (Get / Put / List / Delete) on that bucket.

Affected version

Nextcloud Server 32.0.9 (confirmed). The same code path exists in older versions and main.

Steps to reproduce

  1. Create a Backblaze B2 application key restricted to a single bucket, e.g.:
    b2 key create --bucket <bucket-name> mykey listFiles readFiles writeFiles deleteFiles readBuckets writeBuckets listAllBucketNames
    
  2. In Nextcloud, configure an external storage mount with the "Amazon S3" backend:
    • Bucket: <bucket-name>
    • Hostname: s3.<region>.backblazeb2.com
    • Region: B2 region (e.g., us-west-004)
    • Use SSL / use path-style: enabled
    • Authentication: Access key with the application key id and key from step 1
  3. Open the Files app or run occ files_external:verify <mount-id>.

Expected behavior

The mount is usable. test() succeeds whenever the underlying credentials can list / read / write objects in the configured bucket, even if the S3-compatible service does not grant HeadBucket on that credential.

Current behavior

occ files_external:verify <mount-id>:

- status: error
- code: 1
- message: Aws\S3\Exception\S3Exception: Error executing "HeadBucket" on "https://s3.us-west-004.backblazeb2.com/<bucket>":
   (client): 403 Forbidden (Request-ID: ...)

nextcloud.log:

files_external · OCP\Files\StorageNotAvailableException
Creation of bucket "<bucket>" failed. Error executing "CreateBucket" on "https://s3.us-west-004.backblazeb2.com/<bucket>":
AWS HTTP error: AccessDenied (client): not entitled

(The CreateBucket attempt is S3ConnectionTrait::getConnection()'s autocreate branch, separately fixable via verify_bucket_exists=false. After fixing that, the Availability wrapper still fails on test() -> HeadBucket and permanently marks the storage unavailable.)

Root cause

apps/files_external/lib/Lib/Storage/AmazonS3.php::test():

public function test(): bool {
    $this->getConnection()->headBucket([
        'Bucket' => $this->bucket,
    ]);
    return true;
}

lib/private/Files/Storage/Wrapper/Availability.php calls test() and records unavailability on exception. RECHECK_TTL_SEC = 600.

On Backblaze B2, the S3-compatible API returns 403 Forbidden for HeadBucket on any application key with a bucket restriction. Verified on a real B2 account with five key shapes (master, MBAK 4-bucket, SBAK 1-bucket full caps, SBAK 1-bucket without listAllBucketNames, SBAK 1-bucket with only readBuckets); HeadBucket returned 403 in all cases. The same keys returned 200 for ListObjectsV2 (MaxKeys=1), GetBucketLocation, PutObject, GetObject, DeleteObject on the same bucket.

Proposed fix

Replace HeadBucket in test() with a check that does not require the HeadBucket privilege but that any working external-storage credential already has. ListObjectsV2(MaxKeys=1) works across AWS S3, MinIO, Wasabi, and Backblaze B2:

public function test(): bool {
    $this->getConnection()->listObjectsV2([
        'Bucket' => $this->bucket,
        'MaxKeys' => 1,
    ]);
    return true;
}

GetBucketLocation is also a viable equivalent on B2; ListObjectsV2 is more universally accepted across S3-compatible services and matches how the existing getFolderContents() and unlink() paths in the same file already exercise the bucket.

Workaround in user environment

Until this is fixed upstream, B2-backed external storage mounts are unusable in Nextcloud. The B2 bucket can still be used as the primary object store (config.php objectstore section) because lib/private/Files/ObjectStore/S3ConnectionTrait uses a separate verify_bucket_exists flag for the bucket-existence check and does not invoke headBucket from a Storage::test()-equivalent path.

Additional references

  • apps/files_external/lib/Lib/Storage/AmazonS3.php (line ~594, current test())
  • lib/private/Files/Storage/Wrapper/Availability.php (RECHECK_TTL_SEC)
  • lib/private/Files/ObjectStore/S3ConnectionTrait.php (parallel pattern for object store, uses verify_bucket_exists rather than calling HeadBucket unconditionally)
  • Backblaze B2 application key documentation (bucket restriction limits HeadBucket on S3-compatible API)
Steps to reproduce
  1. use external strage. backblaze. B2
  2. setup external strage
Expected behavior

can upload files.

Nextcloud Server version

32

Operating system

None

PHP engine version

None

Web server

None

Database engine version

None

Is this bug present after an update or on a fresh install?

None

Are you using the Nextcloud Server Encryption module?

None

What user-backends are you using?
  • Default user-backend (database)
  • LDAP/ Active Directory
  • SSO - SAML
  • Other
Configuration report

List of activated Apps

Nextcloud Signing status

Nextcloud Logs

Additional info

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in apps/files_external/lib/Lib/Storage/AmazonS3.php at test(), then read how lib/private/Files/Storage/Wrapper/Availability.php handles failures. Compare the bucket check with lib/private/Files/ObjectStore/S3ConnectionTrait.php and verify the external-storage entry point with a bucket-restricted Backblaze B2 key. Done means the mount verifies and supports file operations without requiring HeadBucket.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, php
Domain
backend, cloud
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.