skip to Main Content

I have two Azure storage accounts – staging-account.blob.core.windows.net and production-public-account.blob.core.windows.net.

I would like to copy a blob mycontainer/mydir/myblob.bin from staging-account to production-public-account.

I am running this on a host that is serving as an Azure DevOps build agent – it has access to both the production-public-account (via public internet) and the staging-account (via Private Endpoint / VNet).

Is it possible to copy from the private account to the public account?

I’ve used azcopy with SAS tokens and it is failing with CannotVerifyCopySource. The same error does not occur when I go from staging-account to a different private-production-account over a private VNet, and if I temporarily enable public access on the source account it works fine as well. We would really like to ensure that our staging account is not accessible to the public internet for this.

Function Sync-Directory {                                                                                                                                                                                                                                                                                                                                   
  param (                                                                                                                                                                                                                                                                                                                                                     
    [string] $Container,                                                                                                                                                                                                                                                                                                                                    
    [string] $Directory,                                                                                                                                                                                                                                                                                                                                    
    [string] $TargetAccount,                                                                                                                                                                                                                                                                                                                                
    [string] $Exclude,                                                                                                                                                                                                                                                                                                                                      
    [string] $Include,                                                                                                                                                                                                                                                                                                                                      
    [string] $Command                                                                                                                                                                                                                                                                                                                                   
  )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
  $SrcAccount = $Env:SRC_ACCOUNT_NAME                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
  if ($Include) {                                                                                                                                                                                                                                                                                                                                             
    Write-Host ("Copying {0}/{1}**/{2} from {3} to {4}" -f $Container,$Directory,$Include,$SrcAccount,$TargetAccount)                                                                                                                                                                                                                                   
  } elseif ($Exclude) {                                                                                                                                                                                                                                                                                                                                       
    Write-Host ("Copying {0}/{1} (excluding **/{2}) from {3} to {4}" -f $Container,$Directory,$Exclude,$SrcAccount,$TargetAccount)                                                                                                                                                                                                                      
  } else {                                                                                                                                                                                                                                                                                                                                                    
    Write-Host ("Copying {0}/{1} from {2} to {3}" -f $Container,$Directory,$SrcAccount,$TargetAccount)                                                                                                                                                                                                                                                  
  }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
  $Expiry = $(Get-Date).AddMinutes(90)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
  Set-AzContext -Subscription $Env:SRC_ACCOUNT_SUBSCRIPTION | Out-Null                                                                                                                                                                                                                                                                                    
  $SrcContext = New-AzStorageContext -StorageAccountName $SrcAccount -UseConnectedAccount                                                                                                                                                                                                                                                                 
  $SrcToken   = Remove-LeadingQuestionMark((New-AzStorageContainerSASToken -Context $SrcContext -Name $Container - 
  ExpiryTime $Expiry -Permission "rl"))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
  Set-AzContext -Subscription $Env:OUR_BLOB_STORAGE_SUBSCRIPTION_ID | Out-Null                                                                                                                                                                                                                                                                          
  $DstContext = New-AzStorageContext -StorageAccountName $TargetAccount -UseConnectedAccount                                                                                                                                                                                                                                                              
  $DstToken   = Remove-LeadingQuestionMark((New-AzStorageContainerSASToken -Context $DstContext -Name $Container - 
  ExpiryTime $Expiry -Permission "rcwdl"))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
  $SrcHost = "{0}.blob.core.windows.net" -f $SrcAccount                                                                                                                                                                                                                                                                                                   
  $TargetHost = "{0}.blob.core.windows.net" -f $TargetAccount                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
  $RawSrcURL = "https://{0}/{1}/{2}" -f $SrcHost,$Container,$Directory                                                                                                                                                                                                                                                                                    
  $RawDstURL = "https://{0}/{1}/{2}" -f $TargetHost,$Container,$Directory                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
  $SrcURL = "{0}?{1}" -f $RawSrcURL,$SrcToken                                                                                                                                                                                                                                                                                                             
  $DstURL = "{0}?{1}" -f $RawDstURL,$DstToken                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
  if ($Command -eq $null) {                                                                                                                                                                                                                                                                                                                                   
    $Command = "sync"                                                                                                                                                                                                                                                                                                                                   
  }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
  # azcopy doesn't have great regex support when directories to be synced are at the root level in the container                                                                                                                                                                                                                                          
  if ($Exclude) {                                                                                                                                                                                                                                                                                                                                             
    $Results = & sh -c "/tmp/azcopy '$Command' --recursive=true --exclude-pattern '$Exclude;$GlobalExcludePath' --delete-destination=true --s2s-preserve-access-tier=false '$SrcURL' '$DstURL'"                                                                                                                                                         
  } elseif ($Include) {                                                                                                                                                                                                                                                                                                                                       
    $Results = & sh -c "/tmp/azcopy '$Command' --recursive=true --exclude-pattern '$GlobalExcludePath' --include-pattern '$Include' --s2s-preserve-access-tier=false --delete-destination=true '$SrcURL' '$DstURL'"                                                                                                                                     
  } else {                                                                                                                                                                                                                                                                                                                                                    
    $Results = & sh -c "/tmp/azcopy '$Command' --recursive=true --exclude-pattern '$GlobalExcludePath' --delete-destination=true --s2s-preserve-access-tier=false '$SrcURL' '$DstURL'"
...

2023/11/15 16:19:46 AzcopyVersion  10.21.2
2023/11/15 16:19:46 OS-Environment  linux
2023/11/15 16:19:46 OS-Architecture  amd64
2023/11/15 16:19:46 Log times are in UTC. Local time is 15 Nov 2023 16:19:46
2023/11/15 16:19:46 ISO 8601 START TIME: to copy files that changed before or after this job started, use the parameter --include-before=2023-11-15T16:19:41Z or --include-after=2023-11-15T16:19:41Z
2023/11/15 16:19:47 WARN: Failed to create destination container mycontainer. The transfer will continue if the container exists
2023/11/15 16:19:47 Any empty folders will not be processed, because source and/or destination doesn't have full folder support
2023/11/15 16:19:47 Job-Command cp --s2s-preserve-access-tier=false https://[private-staging-host].blob.core.windows.net/mycontainer/Blobdir/2.37.0/binary-blob-123456.bin?se=2023-11-15t17%3A49%3A43z&sig=-REDACTED-&ske=2023-11-15t17%3A49%3A43z&skoid=01b10450-230c-4aae-92c0-7c792ab955d5&sks=b&skt=2023-11-15t16%3A19%3A46z&sktid=07e4ccf9-8832-40b3-9731-2709e0742154&skv=2023-01-03&sp=rl&sr=b&sv=2023-01-03 https://[public-production-host].blob.core.windows.net/mycontainer/Blobdir/2.37.0/binary-blob-123456.bin?se=2023-11-15t17%3A49%3A43z&sig=-REDACTED-&ske=2023-11-15t17%3A49%3A43z&skoid=01b10450-230c-4aae-92c0-7c792ab955d5&sks=b&skt=2023-11-15t16%3A19%3A45z&sktid=07e4ccf9-8832-40b3-9731-2709e0742154&skv=2023-01-03&sp=rcwdl&sr=c&sv=2023-01-03
2023/11/15 16:19:47 Number of CPUs: 3
2023/11/15 16:19:47 Max file buffer RAM 1.500 GB
2023/11/15 16:19:47 Max concurrent network operations: 32 (Based on number of CPUs. Set AZCOPY_CONCURRENCY_VALUE environment variable to override)
2023/11/15 16:19:47 Check CPU usage when dynamically tuning concurrency: true (Based on hard-coded default. Set AZCOPY_TUNE_TO_CPU environment variable to true or false override)
2023/11/15 16:19:47 Max concurrent transfer initiation routines: 64 (Based on hard-coded default. Set AZCOPY_CONCURRENT_FILES environment variable to override)
2023/11/15 16:19:47 Max enumeration routines: 16 (Based on hard-coded default. Set AZCOPY_CONCURRENT_SCAN environment variable to override)
2023/11/15 16:19:47 Parallelize getting file properties (file.Stat): false (Based on AZCOPY_PARALLEL_STAT_FILES environment variable)
2023/11/15 16:19:47 Max open files when downloading: 1048152 (auto-computed)
2023/11/15 16:19:47 Final job part has been created
2023/11/15 16:19:47 JobID=993eaa9f-5646-1f49-6fe4-c720f7f75ac1, credential type: Anonymous
2023/11/15 16:19:47 Final job part has been scheduled
2023/11/15 16:19:47 INFO: [P#0-T#0] Starting transfer: Source "https://[private-staging-host].blob.core.windows.net/mycontainer/Blobdir/2.37.0/binary-blob-123456.bin?se=2023-11-15T17%3A49%3A43Z&sig=-REDACTED-&ske=2023-11-15T17%3A49%3A43Z&skoid=01b10450-230c-4aae-92c0-7c792ab955d5&sks=b&skt=2023-11-15T16%3A19%3A46Z&sktid=07e4ccf9-8832-40b3-9731-2709e0742154&skv=2023-01-03&sp=rl&sr=b&sv=2023-01-03" Destination "https://[public-production-host].blob.core.windows.net/mycontainer/Blobdir/2.37.0/binary-blob-123456.bin?se=2023-11-15T17%3A49%3A43Z&sig=-REDACTED-&ske=2023-11-15T17%3A49%3A43Z&skoid=01b10450-230c-4aae-92c0-7c792ab955d5&sks=b&skt=2023-11-15T16%3A19%3A45Z&sktid=07e4ccf9-8832-40b3-9731-2709e0742154&skv=2023-01-03&sp=rcwdl&sr=c&sv=2023-01-03". Specified chunk size 8388608
2023/11/15 16:19:47 ==> REQUEST/RESPONSE (Try=1/324.295011ms, OpTime=415.886833ms) -- RESPONSE STATUS CODE ERROR
   PUT https://[public-production-host].blob.core.windows.net/mycontainer/Blobdir/2.37.0/binary-blob-123456.bin?blockid=MDAwMDCfqj6ZRlZJH2%2FkxyD391rBMDAwMDAwMDAwMDAwMDI0&comp=block&se=2023-11-15T17%3A49%3A43Z&sig=-REDACTED-&ske=2023-11-15T17%3A49%3A43Z&skoid=01b10450-230c-4aae-92c0-7c792ab955d5&sks=b&skt=2023-11-15T16%3A19%3A45Z&sktid=07e4ccf9-8832-40b3-9731-2709e0742154&skv=2023-01-03&sp=rcwdl&sr=c&sv=2023-01-03
   Accept: application/xml
   Content-Length: 0
   User-Agent: AzCopy/10.21.2 azsdk-go-azblob/v1.1.0 (go1.19.12; linux)
   X-Ms-Client-Request-Id: 44431710-0710-4b9b-5800-0544edea6fe0
   x-ms-copy-source:
   x-ms-source-range:
   x-ms-version:
   --------------------------------------------------------------------------------
   RESPONSE Status: 403 This request is not authorized to perform this operation.
   Content-Length: 248
   Content-Type: application/xml
   Date: Wed, 15 Nov 2023 16:19:47 GMT
   Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
   X-Ms-Client-Request-Id: 44431710-0710-4b9b-5800-0544edea6fe0
   X-Ms-Error-Code: CannotVerifyCopySource
   X-Ms-Request-Id: 8d5e2152-201e-0004-43df-173200000000
   X-Ms-Version: 2020-10-02
Response Details: <Code>CannotVerifyCopySource</Code><Message>This request is not authorized to perform this operation. </Message>

2023/11/15 16:19:47 ERR: [P#0-T#0] COPYFAILED: https://[private-staging-host].blob.core.windows.net/mycontainer/Blobdir/2.37.0/binary-blob-123456.bin?se=2023-11-15T17%3A49%3A43Z&sig=-REDACTED-&ske=2023-11-15T17%3A49%3A43Z&skoid=01b10450-230c-4aae-92c0-7c792ab955d5&sks=b&skt=2023-11-15T16%3A19%3A46Z&sktid=07e4ccf9-8832-40b3-9731-2709e0742154&skv=2023-01-03&sp=rl&sr=b&sv=2023-01-03 : 403 : 403 This request is not authorized to perform this operation.. When Staging block from URL. X-Ms-Request-Id: 8d5e2152-201e-0004-43df-173200000000

   Dst: https://[public-production-host].blob.core.windows.net/mycontainer/Blobdir/2.37.0/binary-blob-123456.bin?se=2023-11-15T17%3A49%3A43Z&sig=-REDACTED-&ske=2023-11-15T17%3A49%3A43Z&skoid=01b10450-230c-4aae-92c0-7c792ab955d5&sks=b&skt=2023-11-15T16%3A19%3A45Z&sktid=07e4ccf9-8832-40b3-9731-2709e0742154&skv=2023-01-03&sp=rcwdl&sr=c&sv=2023-01-03
2023/11/15 16:19:47 JobID=993eaa9f-5646-1f49-6fe4-c720f7f75ac1 canceled
2023/11/15 16:19:47 all parts of entire Job 993eaa9f-5646-1f49-6fe4-c720f7f75ac1 successfully completed, cancelled or paused
2023/11/15 16:19:47 all parts of entire Job 993eaa9f-5646-1f49-6fe4-c720f7f75ac1 successfully cancelled
2023/11/15 16:19:47 is part of Job which 1 total number of parts done
2023/11/15 16:19:49 PERF: primary performance constraint is Unknown. States: W:  0, F:  0, S:  0, E:  1, T:  1, GRs: 32
2023/11/15 16:19:49 0.0 %, 0 Done, 0 Failed, 1 Pending, 0 Skipped, 1 Total, 2-sec Throughput (Mb/s): 1072.8358
2023/11/15 16:19:49

Diagnostic stats:
IOPS: 13
End-to-end ms per request: 418
Network Errors: 0.00%
Server Busy: 0.00%


Job 993eaa9f-5646-1f49-6fe4-c720f7f75ac1 summary
Elapsed Time (Minutes): 0.0334
Number of File Transfers: 1
Number of Folder Property Transfers: 0
Number of Symlink Transfers: 0
Total Number of Transfers: 1
Number of File Transfers Completed: 0
Number of Folder Transfers Completed: 0
Number of File Transfers Failed: 0
Number of Folder Transfers Failed: 0
Number of File Transfers Skipped: 0
Number of Folder Transfers Skipped: 0
TotalBytesTransferred: 0
Final Job Status: Cancelled

2

Answers


  1. Chosen as BEST ANSWER

    After much poking around at this, I finally figured out that azcopy requires a private endpoint for both the private and public accounts (even though the public account will not restrict traffic).

    My understanding (based on reading this somewhat-confusing recent blog from Microsoft) is that different routing / header magic is used on the Azure backend depending on whether the initial request to an account is done publicly or via a VNet address.


  2. AFAIK using azcopy to transfer files from an Azure storage private account (with a private endpoint/VNet) to a public endpoint it’s not possible.

    Alternatively, to copy Azure Storage Blob from a Private Storage Account to a Public Storage account you can make use of below steps:

    Created storage account with private endpoint. when access to the public endpoint is disabled, the storage account can still be accessed through its private endpoints using selected virtual networks and IP addresses.

    In the private storage account, enable Enabled from selected virtual networks and IP addresses and in the firewall, check Add your client IP address, add it, and save like below:

    enter image description here

    Select Allow Azure services on the trusted services list to access this storage account to allow trusted first party Microsoft services.

    Now, when I ran the azcopy script it transferred successfully like below:

    azcopy copy "https://<source-storage-account-name>.blob.core.windows.net/mycontainer/mydir/imr.bin<SAS-token>" "https://<destination-storage-account-name>.blob.core.windows.net/mycontainer/mydir/<sas-token>" --recursive=true
    

    Example:

    azcopy copy "https://storageaccprivate.blob.core.windows.net/container2/dic2/Nov.bin.html?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlaxxxxxxxx" "https://storageaccpublic.blob.core.windows.net/mycontainer/mydir/?sv=2022-11-02&ss=bfqt&srt=scoxxxxxxxx" --recursive=true
    

    Output:

    INFO: Scanning...
    INFO: azcopy.exe 10.19.0: A newer version 10.21.2 is available to download
    
    INFO: Any empty folders will not be processed, because source and/or destination doesn't have full folder support
    Job b4db0252-a7xxxxxxx has started
    Log file is located at: C:Usersimrakhan.azcopyb4db0252-axxxxxxc.log
    
    100.0 %, 0 Done, 0 Failed, 1 Pending, 0 Skipped, 1 Total, 2-sec Throughput (Mb/s): 0.0034
    100.0 %, 1 Done, 0 Failed, 0 Pending, 0 Skipped, 1 Total,
    100.0 %, 0 Done, 0 Failed, 1 Pending, 0 Skipped, 1 Total, 2-sec Throughput (Mb/s): 0.0034
    100.0 %, 1 Done, 0 Failed, 0 Pending, 0 Skipped, 1 Total,                                
    
    
    Job b4db0252-a757-2d42-xxxxxx summary
    Elapsed Time (Minutes): 0.0668
    Number of File Transfers: 1
    Number of Folder Property Transfers: 0
    Number of Symlink Transfers: 0
    Total Number of Transfers: 1
    Number of File Transfers Completed: 1
    Number of Folder Transfers Completed: 0
    Number of File Transfers Failed: 0
    Number of Folder Transfers Failed: 0
    Number of File Transfers Skipped: 0
    Number of Folder Transfers Skipped: 0
    TotalBytesTransferred: 863
    Final Job Status: Completed
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search