azurerustazure-storage

Azure_identity in rust doesn't implement ClientSecretCredential class


I would like to use something along the lines below in rust (see below for working C# code). But I cannot find a way to authenticate in rust because of what the azure_identity crate in rust implements.

How can I authenticate in rust based on clientID, tenantID and clientSecret as shown here, please?

using System.Threading.Tasks;
using Azure.Core;
using Azure.Identity;
using Azure.Storage.Blobs;
using DotNetEnv;
using Sprache;
using static System.Runtime.InteropServices.JavaScript.JSType;

class Program
{
    static async Task Main(string[] args)
    {
        Env.Load();

        //// Retrieve Azure credentials from environment variables
        string? clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
        string? tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
        string? clientSecret = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
        string? accountName = Environment.GetEnvironmentVariable("AZURE_ACCOUNT_NAME");
        string? containerName = Environment.GetEnvironmentVariable("AZURE_CONTAINER_NAME");
        string? prefix = Environment.GetEnvironmentVariable("AZURE_PREFIX");

        if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(tenantId) ||
            string.IsNullOrEmpty(clientSecret) || string.IsNullOrEmpty(accountName) ||
            string.IsNullOrEmpty(containerName))
        {
            Console.WriteLine("Missing required Azure credentials in .env file.");
            return;
        }

        // Use ClientSecretCredential for authentication
        var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);

        // Create a BlobServiceClient object
        var blobServiceClient = new BlobServiceClient(
            new Uri($"https://{accountName}.blob.core.windows.net"),
            credential
        );

        // Get a reference to the container
        var containerClient = blobServiceClient.GetBlobContainerClient(containerName);

        // List blobs in the specified folder
        Console.WriteLine($"Listing blobs in folder {prefix ?? "root"}:");

        await foreach (var blobItem in containerClient.GetBlobsAsync(prefix: prefix))
        {
            // Get just the filename without the path
            string fileName = Path.GetFileName(blobItem.Name);
            Console.WriteLine($"File name: {fileName}");
        }
    }
}

EDIT As suggested by the comment I tried to make it work with version 0.21. Here is how far I got:


use azure_identity::ClientSecretCredential;
use azure_storage_blobs::prelude::*;
use std::env;
use dotenv::dotenv;
use anyhow::{Result, anyhow};
use futures::stream::StreamExt; // For stream processing

#[tokio::main]
async fn main() -> Result<()> {
    dotenv().ok();

    let client_id = env::var("AZURE_CLIENT_ID").map_err(|_| anyhow!("AZURE_CLIENT_ID not set"))?;
    let client_secret = env::var("AZURE_CLIENT_SECRET").map_err(|_| anyhow!("AZURE_CLIENT_SECRET not set"))?;
    let tenant_id = env::var("AZURE_TENANT_ID").map_err(|_| anyhow!("AZURE_TENANT_ID not set"))?;
    let account_name = env::var("AZURE_ACCOUNT_NAME").map_err(|_| anyhow!("AZURE_ACCOUNT_NAME not set"))?;
    let container_name = env::var("AZURE_CONTAINER_NAME").map_err(|_| anyhow!("AZURE_CONTAINER_NAME not set"))?;
    let azure_prefix = env::var("AZURE_PREFIX").map_err(|_| anyhow!("AZURE_PREFIX not set"))?;

    let credentials = std::sync::Arc::new(ClientSecretCredential::new(tenant_id, client_id, client_secret));

    let container_client = ClientBuilder::new(account_name, credentials)
        .container_client(container_name);

    // List blobs in the container
    let mut stream = container_client.list_blobs().into_stream();

    println!("Blobs in container '{}':", container_name);

    while let Some(result) = stream.next().await {
        match result {
            Ok(response) => {
                for blob in response.blobs.blobs() {
                    println!("  - {}", blob.name);
                }
            }
            Err(error) => {
                eprintln!("Error listing blobs: {}", error);
                return Err(error.into()); // Convert Azure Storage Error to Anyhow Error
            }
        }
    }

    Ok(())
}

with this cargo.toml:


[package]
name = "azure_test"
version = "0.1.0"
edition = "2024"

[dependencies]
azure_storage_blobs = "0.14"
azure_identity = "0.21"
azure_core = "0.21"
tokio = { version = "1", features = ["full"] }
dotenv = "0.15"
anyhow = "1.0"
futures = "0.3"
reqwest = { version = "0.11", features = ["json", "stream"] }


But it seems these credentials aren't compatible with the storage blob

trait `From<Arc<ClientSecretCredential>>` is not implemented for `azure_storage::authorization::StorageCredentials`

how can I make this work pls?


Solution

  • Azure_identity in rust doesn't implement ClientSecretCredential class.

    According to this Document,

    The azure_identity package in Rust does not implement ClientSecretCredential,However, you can obtain an OAuth2 token manually using an HTTP request and use it for authentication with Azure Storage.

    Code:

    use azure_storage::StorageCredentials;
    use azure_storage_blobs::prelude::*;
    use reqwest::Client;
    use serde::{Deserialize, Serialize};
    use tokio;
    use futures::stream::StreamExt;
    
    #[derive(Serialize)]
    struct TokenRequest<'a> {
        grant_type: &'a str,
        client_id: &'a str,
        client_secret: &'a str,
        scope: &'a str,
    }
    
    #[derive(Deserialize)]
    struct TokenResponse {
        access_token: String,
    }
    
    async fn get_oauth_token(tenant_id: &str, client_id: &str, client_secret: &str) -> Result<String, Box<dyn std::error::Error>> {
        let token_url = format!("https://login.microsoftonline.com/{}/oauth2/v2.0/token", tenant_id);
    
        let params = TokenRequest {
            grant_type: "client_credentials",
            client_id,
            client_secret,
            scope: "https://storage.azure.com/.default",
        };
    
        let client = Client::new();
        let res = client.post(&token_url)
            .form(&params)
            .send()
            .await?
            .json::<TokenResponse>()
            .await?;
    
        Ok(res.access_token)
    }
    
    #[tokio::main]
    async fn main() -> azure_core::Result<()> {
        let tenant_id = "xxxx";
        let client_id = "xxxx";
        let client_secret = "xxx";
        let storage_account = "xxx";
        let container_name = "xxt";
    
        // Get OAuth2 Token
        let token = get_oauth_token(tenant_id, client_id, client_secret).await.unwrap();
    
        let credentials = StorageCredentials::bearer_token(token);
        let blob_service = BlobServiceClient::new(storage_account, credentials);
    
        let container_client = blob_service.container_client(container_name);
    
        let mut stream = container_client.list_blobs().into_stream();
    
        while let Some(response) = stream.next().await {
            match response {
                Ok(response) => {
                    for blob in response.blobs.blobs() {
                        println!("Blob name: {}", blob.name);
                    }
                }
                Err(err) => {
                    eprintln!("Error listing blobs: {:?}", err);
                }
            }
        }
    
        Ok(())
    }
    

    The above rust code fetches an OAuth2 token using the client credentials flow, then uses it to authenticate with Azure Blob Storage and list all blobs in a specified container.

    Note: Make sure the Service principal has Storage Blob Data Contributor to access the storage account.

    Output:

    Blob name: data.gz
    Blob name: demo.pdf
    Blob name: foo/bar/industry.csv
    Blob name: foo/bar/sample5000.xlsx
    Blob name: foo/demo/cert_1.key
    Blob name: foo/demo/cert_2.key
    Blob name: foo/test/Octkb 24.md
    Blob name: foo/test/Octkb2 24.md
    Blob name: large-file.csv
    Blob name: sample.mp4
    Blob name: sample.txt
    Blob name: sample5000.xlsx
    Blob name: samplehub1/venkatesan/0xxx4.avro
    Blob name: samplehub1/venkatesan/xxxx.avro
    Blob name: scenery.jpg
    Blob name: test.xlsx
    Blob name: test/demo/cert_0.cer
    Blob name: test/demo/cert_1.cer
    Blob name: testblob.txt
    

    enter image description here

    Update:

    cargo.toml:

    [dependencies]
    tokio = { version = "1", features = ["full"] }
    azure_core = "0.20"
    azure_identity = "0.20"
    azure_storage = "0.20"
    azure_storage_blobs = "0.20"
    reqwest = { version = "0.11", features = ["json"] }
    serde = { version = "1", features = ["derive"] }
    serde_json = "1"
    futures = "0.3"