AI Red Teaming con Promptfoo en Azure

Despliegue en Container Apps (Parte 1)

En este post desplegamos Promptfoo sobre Azure Container Apps desde cero junto con la la gestión segura de credenciales con Key Vault y managed identity, y la configuración de persistencia con Azure File Share para que los resultados de las campañas no se pierdan entre sesiones.


Arquitectura

Antes de empezar con los comandos, vamos a ver la arquitectura de lo que vamos a construir:

Por qué Azure Container Apps: Nos permite desplegar contenedores sin gestionar infraestructura. Con el plan Consumption y min-replicas 1 el container siempre está disponible. La integración nativa con Key Vault a través de managed identity simplifica la gestión de credenciales sin exponer secrets en variables de entorno.

Por qué Azure File Share: Promptfoo almacena su base de datos SQLite en /root/.promptfoo. Sin persistencia externa, todo el historial de campañas desaparece cuando el container se reinicia. El File Share montado en esa ruta garantiza que los datos sobreviven a reinicios y redespliegues.

Por qué Key Vault + Managed Identity: las API keys del modelo target nunca aparecen en código, en la CLI ni en ficheros de configuración. La Container App tiene una system-assigned managed identity con rol Key Vault Secrets User y los secrets se inyectan en tiempo de ejecución.

Por que se llama pyrit: Por limitaciones en mi suscripción de estudiantee no puedo tener más de un ACA en la misma región , y actualmente es la que me permite realizar más pruebas por lo que para este caso que es algo temporal vale , las buenas practicas es cada uno con su resource group correspondiente.

Requisitos

  • Azure CLI instalado y autenticado (az login)
  • Suscripción Azure activa
  • Deployment de Azure OpenAI con GPT-4o disponible

Variables que usaremos a lo largo del proceso defínelas al inicio:

bash

RG_ENV="rg-lab-pyrit"
LOCATION="swedencentral"
ENV_NAME="cae-copyrit-lab"  
APP_NAME="promptfoo"
KV_NAME="kv-promptfoo-lab"
STORAGE_NAME="stprompfoolab"
SHARE_NAME="promptfoo-data"

Como digo , el entorno de cae-copyrit-lab es el que usamos en la serie de post anteriores del despliegue de PyRIT versión UI , aquí lo aprovecharemos , si no lo tenéis creado podéis ir al post y ver como lo hicimos, este paso del entorno ya nos lo ahorramos.

Paso 1: Registrar el namespace de Key Vault

Azure for Students no registra todos los namespaces por defecto. Key Vault hay que registrarlo antes de poder crearlo:

bash

az provider register --namespace Microsoft.KeyVault

# Verificar — esperar hasta "Registered"
az provider show --namespace Microsoft.KeyVault --query registrationState -o tsv

Paso 2: Desplegar la Container App

La imagen oficial de Promptfoo está en GitHub Container Registry y no requiere build:

bash

az containerapp create \
  --name $APP_NAME \
  --resource-group $RG_ENV \
  --environment $ENV_NAME \
  --image ghcr.io/promptfoo/promptfoo:latest \
  --target-port 3000 \
  --ingress external \
  --min-replicas 1 \
  --max-replicas 1 \
  --cpu 0.5 --memory 1.0Gi \
  --env-vars "PROMPTFOO_DISABLE_TELEMETRY=true"

Si tiene éxito obtendrás la URL pública:

Container app created. Access your app at https://promptfoo.XXXXX.swedencentral.azurecontainerapps.io/

Errores que me he encontrado con la suscripción de Azure for Students

Límite de 1 Container App Environment por suscripción. Si ya tienes un lab desplegado (PyRIT u otro), no puedes crear un environment nuevo. El error es:

(MaxNumberOfGlobalEnvironmentsInSubExceeded) The subscription cannot have 
more than 1 Container App Environments.

Solución: reutilizar el environment existente apuntando ENV_NAME al que ya tienes. Las Container Apps de distintos proyectos conviven sin problema en el mismo environment.

Región bloqueada. La mayoría de regiones europeas están restringidas en Students. El error al crear el Log Analytics workspace asociado al environment es el síntoma:

(RequestDisallowedByAzure) Resource was disallowed by Azure.

swedencentral es la región que funciona para este lab es la que os recomiendo usar para todos los recursos.

Paso 3: Crear el Key Vault y asignar permisos

bash

az keyvault create \
  --name $KV_NAME \
  --resource-group $RG_ENV \
  --location $LOCATION

El Key Vault usa RBAC y el usuario de CLI no tiene permisos de escritura por defecto. Hay que asignárselos:

bash

MY_OID=$(az ad user show --id [email protected] --query id -o tsv)

az role assignment create \
  --role "Key Vault Secrets Officer" \
  --assignee-object-id $MY_OID \
  --assignee-principal-type User \
  --scope "/subscriptions/TU_SUBSCRIPTION_ID/resourceGroups/$RG_ENV/providers/Microsoft.KeyVault/vaults/$KV_NAME"

sleep 30

Paso 4: Guardar los secrets

bash

az keyvault secret set \
  --vault-name $KV_NAME \
  --name "azure-openai-key" \
  --value "TU_AZURE_OPENAI_KEY"

az keyvault secret set \
  --vault-name $KV_NAME \
  --name "azure-openai-endpoint" \
  --value "URL del endpoint"

Paso 5: Managed Identity y permisos de lectura

bash

az containerapp identity assign \
  --name $APP_NAME \
  --resource-group $RG_ENV \
  --system-assigned

IDENTITY=$(az containerapp show \
  --name $APP_NAME \
  --resource-group $RG_ENV \
  --query identity.principalId -o tsv)

az role assignment create \
  --role "Key Vault Secrets User" \
  --assignee-object-id $IDENTITY \
  --assignee-principal-type ServicePrincipal \
  --scope "/subscriptions/SUBSCRIPTION_ID/resourceGroups/$RG_ENV/providers/Microsoft.KeyVault/vaults/$KV_NAME"

Paso 6: Vincular secrets a la Container App

bash

az containerapp secret set \
  --name $APP_NAME \
  --resource-group $RG_ENV \
  --secrets \
    "azure-openai-key=keyvaultref:https://$KV_NAME.vault.azure.net/secrets/azure-openai-key,identityref:system" \
    "azure-openai-endpoint=keyvaultref:https://$KV_NAME.vault.azure.net/secrets/azure-openai-endpoint,identityref:system"

az containerapp update \
  --name $APP_NAME \
  --resource-group $RG_ENV \
  --set-env-vars \
    "AZURE_OPENAI_API_KEY=secretref:azure-openai-key" \
    "AZURE_OPENAI_ENDPOINT=secretref:azure-openai-endpoint"

Paso 7: Persistencia con Azure File Share

bash

# Crear el Storage Account y el File Share
az storage account create \
  --name $STORAGE_NAME \
  --resource-group $RG_ENV \
  --location $LOCATION \
  --sku Standard_LRS

az storage share create \
  --name $SHARE_NAME \
  --account-name $STORAGE_NAME

# Vincular el storage al environment
STORAGE_KEY=$(az storage account keys list \
  --account-name $STORAGE_NAME \
  --resource-group $RG_ENV \
  --query "[0].value" -o tsv)

az containerapp env storage set \
  --name $ENV_NAME \
  --resource-group $RG_ENV \
  --storage-name promptfoo-storage \
  --azure-file-account-name $STORAGE_NAME \
  --azure-file-account-key $STORAGE_KEY \
  --azure-file-share-name $SHARE_NAME \
  --access-mode ReadWrite

El mount del volumen en la Container App requiere editar el YAML directamente la CLI no expone esta opción con flags:

bash

az containerapp show \
  --name $APP_NAME \
  --resource-group $RG_ENV \
  --output yaml > /tmp/promptfoo-app.yaml

python3 << 'PYEOF'
import yaml

with open('/tmp/promptfoo-app.yaml', 'r') as f:
    app = yaml.safe_load(f)

template = app['properties']['template']

template['volumes'] = [{
    'name': 'promptfoo-data',
    'storageName': 'promptfoo-storage',
    'storageType': 'AzureFile'
}]

for container in template['containers']:
    if container['name'] == 'promptfoo':
        container['volumeMounts'] = [{
            'volumeName': 'promptfoo-data',
            'mountPath': '/root/.promptfoo'
        }]

with open('/tmp/promptfoo-mount.yaml', 'w') as f:
    yaml.dump(app, f, default_flow_style=False, allow_unicode=True)
print("OK")
PYEOF

az containerapp update \
  --name $APP_NAME \
  --resource-group $RG_ENV \
  --yaml /tmp/promptfoo-mount.yaml

Verificación

bash

az containerapp revision list \
  --name $APP_NAME \
  --resource-group $RG_ENV \
  --query "[].{Name:name, State:properties.runningState, Health:properties.healthState}" \
  -o table

Debe mostrar RunningAtMaxScale y Healthy. Abrimos la URL en el navegador y ya vemos la pantalla de Promptfoo.

Nota sobre autenticación

El lab tal como está desplegado es accesible públicamente sin autenticación. La URL larga y no indexable ofrece protección mínima por oscuridad, suficiente para un lab personal.

La solución correcta para un entorno corporativo es Entra ID SSO, que Azure Container Apps soporta de forma nativa. Con una suscripción M365 E5 estándar se configura con un solo comando. Azure for Students no permite crear app registrations en Entra ID, que es el requisito previo, así que queda como mejora.

En el siguiente post configuramos la primera campaña de red teaming desde la Web UI y analizamos los resultados reales obtenidos contra GPT-4o.

Entradas relacionadas

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *