@@ -32,6 +32,7 @@ import (
3232 "github.com/aws/aws-sdk-go-v2/aws"
3333
3434 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
35+ "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
3536 "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
3637 blob2 "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
3738 "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
@@ -41,10 +42,11 @@ import (
4142
4243type wasb struct {
4344 DefaultObjectStorage
44- container * container.Client
45- azblobCli * azblob.Client
46- sc string
47- cName string
45+ container * container.Client
46+ azblobCli * azblob.Client
47+ sc string
48+ cName string
49+ useTokenAuth bool // true when using managed identity/token-based auth, false for shared key/connection string
4850}
4951
5052func (b * wasb ) String () string {
@@ -117,11 +119,25 @@ func (b *wasb) Copy(ctx context.Context, dst, src string) error {
117119 if b .sc != "" {
118120 options .Tier = str2Tier (b .sc )
119121 }
120- srcSASUrl , err := srcCli .GetSASURL (sas.BlobPermissions {Read : true }, time .Now ().Add (10 * time .Second ), nil )
121- if err != nil {
122- return err
122+
123+ var srcURL string
124+ var err error
125+
126+ if b .useTokenAuth {
127+ // Token-based authentication: use direct blob URL
128+ // Azure will authenticate using the OAuth token from the credential chain
129+ srcURL = srcCli .URL ()
130+ logger .Debugf ("Using token-based authentication for Copy operation (direct URL without SAS)" )
131+ } else {
132+ // Shared key authentication: generate SAS token for source blob
133+ srcURL , err = srcCli .GetSASURL (sas.BlobPermissions {Read : true }, time .Now ().Add (10 * time .Second ), nil )
134+ if err != nil {
135+ return err
136+ }
137+ logger .Debugf ("Using shared key authentication for Copy operation (SAS URL)" )
123138 }
124- _ , err = dstCli .CopyFromURL (ctx , srcSASUrl , options )
139+
140+ _ , err = dstCli .CopyFromURL (ctx , srcURL , options )
125141 return err
126142}
127143
@@ -180,6 +196,23 @@ func (b *wasb) SetStorageClass(sc string) error {
180196 return nil
181197}
182198
199+ // createAzureCredential creates a credential for Azure authentication.
200+ // Uses DefaultAzureCredential which attempts authentication via:
201+ // - Environment variables (service principal)
202+ // - Workload Identity (Kubernetes)
203+ // - Managed Identity (system-assigned and user-assigned)
204+ // - Azure CLI
205+ // - Azure Developer CLI
206+ func createAzureCredential () (azcore.TokenCredential , error ) {
207+ logger .Debugf ("Creating DefaultAzureCredential for token-based authentication" )
208+ cred , err := azidentity .NewDefaultAzureCredential (nil )
209+ if err != nil {
210+ logger .Debugf ("Failed to create DefaultAzureCredential: %v" , err )
211+ return nil , err
212+ }
213+ return cred , nil
214+ }
215+
183216func autoWasbEndpoint (containerName , accountName , scheme string , credential * azblob.SharedKeyCredential ) (string , error ) {
184217 baseURLs := []string {"blob.core.windows.net" , "blob.core.chinacloudapi.cn" }
185218 endpoint := ""
@@ -206,6 +239,32 @@ func autoWasbEndpoint(containerName, accountName, scheme string, credential *azb
206239 return endpoint , nil
207240}
208241
242+ func autoWasbEndpointWithToken (containerName , accountName , scheme string , credential azcore.TokenCredential ) (string , error ) {
243+ baseURLs := []string {"blob.core.windows.net" , "blob.core.chinacloudapi.cn" }
244+ endpoint := ""
245+ for _ , baseURL := range baseURLs {
246+ if _ , err := net .LookupIP (fmt .Sprintf ("%s.%s" , accountName , baseURL )); err != nil {
247+ logger .Debugf ("Attempt to resolve domain name %s failed: %s" , baseURL , err )
248+ continue
249+ }
250+ client , err := azblob .NewClient (fmt .Sprintf ("%s://%s.%s" , scheme , accountName , baseURL ), credential , nil )
251+ if err != nil {
252+ return "" , err
253+ }
254+ if _ , err = client .ServiceClient ().GetProperties (ctx , nil ); err != nil {
255+ logger .Debugf ("Try to get service properties at %s failed: %s" , baseURL , err )
256+ continue
257+ }
258+ endpoint = baseURL
259+ break
260+ }
261+
262+ if endpoint == "" {
263+ return "" , fmt .Errorf ("fail to get endpoint for container %s" , containerName )
264+ }
265+ return endpoint , nil
266+ }
267+
209268func newWasb (endpoint , accountName , accountKey , token string ) (ObjectStorage , error ) {
210269 if ! strings .Contains (endpoint , "://" ) {
211270 endpoint = fmt .Sprintf ("https://%s" , endpoint )
@@ -216,19 +275,52 @@ func newWasb(endpoint, accountName, accountKey, token string) (ObjectStorage, er
216275 }
217276 hostParts := strings .SplitN (uri .Host , "." , 2 )
218277 containerName := hostParts [0 ]
219- // Connection string support: DefaultEndpointsProtocol=[http|https];AccountName=***;AccountKey=***;EndpointSuffix=[core.windows.net|core.chinacloudapi.cn]
278+
279+ // Priority 1: Connection string support
280+ // DefaultEndpointsProtocol=[http|https];AccountName=***;AccountKey=***;EndpointSuffix=[core.windows.net|core.chinacloudapi.cn]
220281 if connString := os .Getenv ("AZURE_STORAGE_CONNECTION_STRING" ); connString != "" {
282+ logger .Debugf ("Using Azure connection string authentication" )
221283 var client * azblob.Client
222284 if client , err = azblob .NewClientFromConnectionString (connString , nil ); err != nil {
223285 return nil , err
224286 }
225- return & wasb {container : client .ServiceClient ().NewContainerClient (containerName ), azblobCli : client , cName : containerName }, nil
287+ return & wasb {container : client .ServiceClient ().NewContainerClient (containerName ), azblobCli : client , cName : containerName , useTokenAuth : false }, nil
226288 }
227289
290+ // Priority 2: Try managed identity / token-based authentication if no account key provided
291+ if accountKey == "" {
292+ logger .Debugf ("No account key provided, attempting token-based authentication (managed identity, Azure CLI, etc.)" )
293+ tokenCred , err := createAzureCredential ()
294+ if err != nil {
295+ return nil , fmt .Errorf ("Failed to create Azure credential (managed identity/Azure CLI): %v" , err )
296+ }
297+
298+ var domain string
299+ if len (hostParts ) > 1 {
300+ domain = hostParts [1 ]
301+ if ! strings .HasPrefix (hostParts [1 ], "blob" ) {
302+ domain = fmt .Sprintf ("blob.%s" , hostParts [1 ])
303+ }
304+ } else if domain , err = autoWasbEndpointWithToken (containerName , accountName , uri .Scheme , tokenCred ); err != nil {
305+ return nil , fmt .Errorf ("Unable to get endpoint of container %s: %s" , containerName , err )
306+ }
307+
308+ serviceURL := fmt .Sprintf ("%s://%s.%s" , uri .Scheme , accountName , domain )
309+ client , err := azblob .NewClient (serviceURL , tokenCred , nil )
310+ if err != nil {
311+ return nil , fmt .Errorf ("Failed to create Azure blob client with token credential: %v" , err )
312+ }
313+ logger .Debugf ("Successfully authenticated using token-based credential" )
314+ return & wasb {container : client .ServiceClient ().NewContainerClient (containerName ), azblobCli : client , cName : containerName , useTokenAuth : true }, nil
315+ }
316+
317+ // Priority 3: Shared key authentication (existing behavior)
318+ logger .Debugf ("Using Azure shared key authentication" )
228319 credential , err := azblob .NewSharedKeyCredential (accountName , accountKey )
229320 if err != nil {
230321 return nil , err
231322 }
323+
232324 var domain string
233325 if len (hostParts ) > 1 {
234326 domain = hostParts [1 ]
@@ -243,7 +335,7 @@ func newWasb(endpoint, accountName, accountKey, token string) (ObjectStorage, er
243335 if err != nil {
244336 return nil , err
245337 }
246- return & wasb {container : client .ServiceClient ().NewContainerClient (containerName ), azblobCli : client , cName : containerName }, nil
338+ return & wasb {container : client .ServiceClient ().NewContainerClient (containerName ), azblobCli : client , cName : containerName , useTokenAuth : false }, nil
247339}
248340
249341func init () {
0 commit comments