Azure Identity para Rust Azure Rust Avanzado MIT
CultivaMetrics SaaS · microservicio metrics-engine · autenticación Entra ID para Key Vault, Blob Storage y Cognitive Services
Arquitectura de autenticación por entorno metrics-engine · 3 entornos · 3 credenciales
🖥 Local / Dev
DeveloperToolsCredential
az login → az login (Azure CLI)
☁ Producción (AKS)
ManagedIdentityCredential
System-assigned · sin secretos
⚙ CI/CD (GitHub Actions)
ClientSecretCredential
Service Principal · env vars
App Rust
metrics-engine
Tokio async
Token JWT
Crate
azure_identity
Entra ID (OAuth2)
Bearer auth
Key Vault
API Keys
cliente-secrets
Blob Storage
Datasets
parquet campañas
Cognitive Svc
ML Inference
modelos predicción
📦 Cargo.toml dependencias oficiales azure-sdk
Solo crates oficiales: publicadas por el usuario azure-sdk en crates.io. No usar azure_sdk_* (deprecated MindFlavor). No añadir azure_core directamente — lo re-exportan las crates de servicio.
TOML
[package]
name    = "metrics-engine"
version = "0.1.0"
edition = "2021"

[dependencies]
# ── Azure Identity (autenticación) ──────────────────────────────
azure_identity              = "0.21"   # DeveloperTools / ManagedIdentity / ClientSecret

# ── Servicios Azure (re-exportan azure_core) ─────────────────────
azure_security_keyvault_secrets = "0.21"
azure_storage_blobs         = "0.21"

# ── Runtime async ────────────────────────────────────────────────
tokio   = { version = "1", features = ["full"] }

# ── Config y errores ─────────────────────────────────────────────
anyhow  = "1"
dotenvy = "0.15"            # carga .env en local (nunca en producción)

# ── Serialización ────────────────────────────────────────────────
serde       = { version = "1", features = ["derive"] }
serde_json  = "1"
🔑 src/auth.rs — Factory de credenciales detecta entorno · devuelve Arc-wrapped credential
RUST
/// auth.rs — CultivaMetrics · metrics-engine
/// Factory de credenciales Azure según entorno de ejecución.
///
/// Estrategia:
///   APP_ENV=development → DeveloperToolsCredential (az login)
///   APP_ENV=production  → ManagedIdentityCredential (AKS system-assigned)
///   APP_ENV=ci          → ClientSecretCredential (Service Principal)

use anyhow::{Context, Result};
use azure_identity::{
    ClientSecretCredential,
    DeveloperToolsCredential,
    ManagedIdentityCredential,
};
use std::sync::Arc;

/// Tipo alias — agrupa todos los credenciales posibles tras un Arc
pub type AzureCredential = Arc<dyn azure_core::auth::TokenCredential>;

#[derive(Debug, Clone, PartialEq)]
pub enum AppEnv {
    Development,
    Production,
    Ci,
}

impl AppEnv {
    pub fn from_env() -> Self {
        match std::env::var("APP_ENV").as_deref() {
            Ok("production")  => AppEnv::Production,
            Ok("ci")          => AppEnv::Ci,
            _                => AppEnv::Development, // seguro por defecto
        }
    }
}

/// Construye la credencial adecuada para el entorno actual.
/// El caller recibe un Arc thread-safe: clonar es barato.
pub fn build_credential(env: &AppEnv) -> Result<AzureCredential> {
    let cred: AzureCredential = match env {

        AppEnv::Development => {
            // Prueba az login, luego azd auth login. Sin secretos en código.
            let c = DeveloperToolsCredential::new(None)
                .context("DeveloperToolsCredential: ejecuta `az login` primero")?;
            Arc::new(c)
        }

        AppEnv::Production => {
            // AKS system-assigned managed identity: sin secretos, sin rotación manual.
            let c = ManagedIdentityCredential::new(None)
                .context("ManagedIdentityCredential: verifica que el pod tenga la MI asignada")?;
            Arc::new(c)
        }

        AppEnv::Ci => {
            // Service Principal para GitHub Actions. Vars en GitHub Secrets.
            let tenant_id = std::env::var("AZURE_TENANT_ID")
                .context("falta AZURE_TENANT_ID")?;
            let client_id = std::env::var("AZURE_CLIENT_ID")
                .context("falta AZURE_CLIENT_ID")?;
            let client_secret = std::env::var("AZURE_CLIENT_SECRET")
                .context("falta AZURE_CLIENT_SECRET")?;
            let c = ClientSecretCredential::new(
                &tenant_id, &client_id, &client_secret, None
            ).context("ClientSecretCredential: verifica las vars de entorno")?;
            Arc::new(c)
        }
    };

    Ok(cred)
}
src/main.rs — Uso con 3 servicios Azure Key Vault · Blob Storage · Cognitive Services
RUST
/// main.rs — CultivaMetrics · metrics-engine
/// Muestra cómo reutilizar una sola credencial para múltiples clientes.
/// Los clientes son thread-safe: crea una vez, comparte en todo Tokio.

mod auth;

use anyhow::Result;
use auth::{AppEnv, build_credential};
use azure_security_keyvault_secrets::SecretClient;
use azure_storage_blobs::prelude::*;

/// Configuración del tenant de CultivaMetrics (no son secretos)
const KEY_VAULT_URL: &str  = "https://cultiva-metrics-kv.vault.azure.net/";
const STORAGE_ACCOUNT: &str = "cultivametricsdata";
const BLOB_CONTAINER: &str  = "campañas-parquet";

#[tokio::main]
async fn main() -> Result<()> {
    // Carga .env solo en desarrollo (dotenvy es no-op si no existe el archivo)
    let _ = dotenvy::dotenv();

    let env        = AppEnv::from_env();
    let credential = build_credential(&env)?;

    println!("Entorno: {:?} → credencial configurada", env);

    // ── 1. Key Vault: leer API key del cliente ────────────────────────────
    // Clonar credential es O(1) — es un Arc, sin copia del token subyacente
    let kv_client = SecretClient::new(
        KEY_VAULT_URL,
        credential.clone(),  // ← clon del Arc, thread-safe
        None,
    )?;

    let secret = kv_client
        .get_secret("cliente-acme-api-key", None)
        .await?
        .into_model()?;

    println!("API key recuperada: {} chars", secret.value.len());

    // ── 2. Blob Storage: leer dataset de campañas ─────────────────────────
    let blob_client = ClientBuilder::new(
        STORAGE_ACCOUNT,
        StorageCredentials::token_credential(credential.clone()),
    )
    .blob_client(BLOB_CONTAINER, "q2-2026-campaign-data.parquet");

    let data = blob_client.get_content().await?;
    println!("Dataset descargado: {} bytes", data.len());

    // ── 3. Cognitive Services: inferencia ML (llamada REST con Bearer token)
    let token = credential
        .get_token(&["https://cognitiveservices.azure.com/.default"])
        .await?;

    println!(
        "Token Cognitive Services obtenido (expira: {:?})",
        token.expires_on
    );

    // Aquí iría la llamada HTTP a /score con Bearer token.value
    // Los clientes son reusables: créalos una vez fuera de esta función.

    Ok(())
}
Variables de entorno
SHELL
# .env (solo local, en .gitignore)
# Dev: solo APP_ENV, az login provee el token
APP_ENV=development

# CI/CD: GitHub Secrets → Actions env
APP_ENV=ci
AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CLIENT_ID=yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
AZURE_CLIENT_SECRET=${{ secrets.AZURE_CLIENT_SECRET }}

# Producción AKS: solo APP_ENV
APP_ENV=production
# → ManagedIdentityCredential no necesita vars adicionales
Comandos de desarrollo
SHELL
# 1. Login local (una vez por sesión)
az login
# o: azd auth login

# 2. Añadir dependencias
cargo add azure_identity tokio

# 3. Compilar
cargo build

# 4. Ejecutar en local
APP_ENV=development cargo run

# 5. Verificar RBAC del Service Principal
az role assignment list --assignee <client-id> -o table

# 6. Roles mínimos necesarios:
#   Key Vault Secrets User  → leer secretos
#   Storage Blob Data Reader → leer blobs
📋 Referencia de credenciales azure_identity crate oficial azure-sdk · v0.21
Tipo de Credencial Caso de uso Entorno
DeveloperToolsCredential Desarrollo local — prueba az login, luego azd auth login Local
ManagedIdentityCredential VMs, App Service, Functions, AKS — sin secretos Azure
WorkloadIdentityCredential Kubernetes workload identity federation K8s
ClientSecretCredential Service principal con secreto — CI/CD pipelines CI/CD
ClientCertificateCredential Service principal con certificado — más seguro que secreto CI/Prod
AzureCliCredential Autenticación directa con Azure CLI Local
AzureDeveloperCliCredential Autenticación directa con azd CLI Local
AzurePipelinesCredential Azure Pipelines service connection CI/CD
ClientAssertionCredential Federated identity — assertions custom (OIDC) Ambos
🔒 Buenas prácticas de seguridad checklist CultivaMetrics · metrics-engine
  • Sin DefaultAzureCredential — usa el tipo correcto por entorno El SDK Rust no incluye DefaultAzureCredential. Elige explícitamente DeveloperToolsCredential (dev) o ManagedIdentityCredential (prod).
  • Cero credenciales hardcodeadas Todos los secretos vienen de variables de entorno o de managed identity. El código no contiene ningún valor sensible.
  • Clona credential.clone() al construir múltiples clientes Las credenciales son Arc-wrapped e internamente thread-safe. Clonar es O(1), sin copias del token JWT.
  • RBAC mínimo necesario (Principle of Least Privilege) "Key Vault Secrets User" para leer secretos, "Storage Blob Data Reader" para blobs. Nunca "Owner" ni "Contributor".
  • Clientes Tokio creados una vez, reutilizados SecretClient, ClientBuilder, etc. son thread-safe. Crear una instancia por servicio al arrancar el proceso, no en cada request.
  • Solo crates oficiales (azure-sdk user en crates.io) No usar azure_sdk_* (MindFlavor/deprecated). Verificar versión en crates.io antes de añadir dependencias.