azureblob: fix Entra ID server-side copy source authentication

When using Microsoft Entra ID credentials, Azure Blob server-side copy
uses a user delegation SAS URL for the private copy source.

The SAS start time was set to the current local time. Azure Storage
validates the copy source from the service side, and small clock
differences can make that SAS appear not yet valid. The service then
returns 403 CannotVerifyCopySource with AuthenticationFailed.

Start the copy-source SAS 15 minutes in the past, matching Microsoft SAS
guidance for clock skew.
This commit is contained in:
Edward Klesel
2026-08-06 17:52:42 +01:00
committed by Nick Craig-Wood
parent 5629f2668c
commit e4c7aca6bd
2 changed files with 8 additions and 2 deletions
+3 -2
View File
@@ -70,6 +70,7 @@ const (
defaultChunkSize = 4 * fs.Mebi
defaultAccessTier = blob.AccessTier("") // FIXME AccessTierNone
sasCopyValidity = time.Hour // how long SAS should last when doing server side copy
sasCopyStartSkew = 15 * time.Minute // how far back SAS should start to allow for clock skew
)
// setAcceptEncodingGzip is a per-call policy that sets Accept-Encoding: gzip
@@ -1827,7 +1828,7 @@ func (f *Fs) getUserDelegation(ctx context.Context) (*service.UserDelegationCred
}
// Validity window
start := time.Now().UTC()
start := time.Now().UTC().Add(-sasCopyStartSkew)
expiry := start.Add(2 * sasCopyValidity)
startStr := start.Format(time.RFC3339)
expiryStr := expiry.Format(time.RFC3339)
@@ -1876,7 +1877,7 @@ func (o *Object) getAuth(ctx context.Context, noAuth bool) (srcURL string, err e
// Build the SAS values
perms := sas.BlobPermissions{Read: true}
container, containerPath := o.split()
start := time.Now().UTC()
start := time.Now().UTC().Add(-sasCopyStartSkew)
expiry := start.Add(sasCopyValidity)
vals := sas.BlobSignatureValues{
StartTime: start,
@@ -54,6 +54,11 @@ func TestBlockIDCreator(t *testing.T) {
assert.ErrorContains(t, bic2.checkID(chunkNumber, got), "random bytes")
}
func TestCopySASTimingConstants(t *testing.T) {
require.Greater(t, sasCopyStartSkew, time.Duration(0))
require.Greater(t, sasCopyValidity, sasCopyStartSkew)
}
func (f *Fs) testFeatures(t *testing.T) {
// Check first feature flags are set on this remote
enabled := f.Features().SetTier