The Infrastructure Hub -- Parte 5
Secretos e Identidades Post-Cuánticas para Infraestructura
El Problema
Abre tu state file de Terraform. Busca el recurso azurerm_mssql_server. Mira el atributo administrator_login_password. Ahí está — en texto plano, dentro de un blob JSON que está en una storage account.
El state de Terraform es una mina de oro para los atacantes. Contiene cada secreto que tu infraestructura usa: contraseñas de bases de datos, API keys, credenciales de service principals, claves de storage accounts. Todos almacenados como atributos de recursos — en texto plano por defecto.
“Pero encriptamos el state en reposo.” Sí, con claves gestionadas por Azure. RSA por defecto. El mismo RSA que un ordenador cuántico romperá. Y sin necesidad de cuántica — si alguien consigue acceso de lectura a la storage account (un SAS token filtrado, una política IAM mal configurada, una identidad de pipeline comprometida), tiene cada secreto de tu infraestructura.
Ese es el problema uno: secretos en el state.
Problema dos: secretos en pipelines. Tu terraform apply necesita credenciales para hablar con Azure, AWS o Scaleway. Esas credenciales viven en variable groups del pipeline, variables de entorno, o — peor — en archivos backend.tf que alguien subió hace dos años. No se rotan. Nadie sabe cuáles siguen siendo válidas. Y cuando alguien deja el equipo, nadie las cambia.
Problema tres: claves SSH. Tu equipo se conecta a VMs, bastion hosts y nodos de Kubernetes con claves SSH RSA. Esas claves se generaron una vez, se compartieron por Slack (sí, en serio), y no se han rotado desde entonces. Están en ~/.ssh/ en cinco portátiles diferentes.
Problema cuatro: identidades de servicio. Cada módulo de Terraform que crea un service principal o managed identity genera una credencial. ¿Dónde está esa credencial? En el state file. ¿Quién la rota? Nadie. ¿Qué algoritmo usa? RSA-2048 o ECDSA-P256 — ambos vulnerables a ordenadores cuánticos.
La serie Quantum-Safe Cloud trató la criptografía post-cuántica para aplicaciones. Este artículo aplica los mismos principios a la infraestructura: Terraform state, secretos de pipelines, claves SSH e identidades de servicio.
La Solución
Cuatro problemas. Cuatro soluciones. Todas integradas en la plataforma Backstage.
1. Encriptación del state de Terraform con ML-KEM — Encripta el state file con QuantumVault antes de que llegue al storage backend. Aunque alguien lea la storage account, solo obtiene texto cifrado.
2. Secretos de pipeline desde QuantumVault — Reemplaza los variable groups y variables de entorno con resolución de secretos en tiempo de ejecución. El pipeline obtiene los secretos de QuantumVault en el momento de la ejecución. Nada almacenado en la plataforma CI/CD.
3. Claves SSH post-cuánticas — Reemplaza las claves SSH RSA con claves ML-DSA de QuantumAPI. De corta duración, auto-rotadas, y gestionadas desde Backstage.
4. Detección de secretos dispersos con IA — Un plugin de Backstage que escanea tus repositorios y el state de Terraform buscando secretos filtrados o caducados. Usa el patrón de catalog enricher del artículo 2 de la serie IDP — pero en vez de enriquecer metadata, busca problemas de seguridad.
Execute
1. Encriptación del State de Terraform
HashiCorp Terraform encripta el state en reposo a través del backend — Azure Storage encripta blobs, S3 encripta objetos. Pero esa encriptación usa las claves del proveedor cloud (RSA por defecto), y el state se desencripta en el momento en que alguien lee el blob. Los secretos dentro están en texto plano una vez que tienes acceso al storage.
OpenTofu — el fork open-source de Terraform — añadió encriptación nativa del state en la v1.7. Si tu organización usa OpenTofu, tienes un bloque encryption que encripta el state antes de que llegue al backend. Con un key provider personalizado, podrías apuntarlo a QuantumVault.
Para HashiCorp Terraform (que es el que la mayoría de empresas siguen usando), el enfoque es diferente: encriptar el state file con QuantumVault como wrapper alrededor de las operaciones del backend. El CLI qapi se encarga de esto en el pipeline — encripta después de terraform state pull, desencripta antes de terraform state push.
Aquí está el patrón del wrapper para el pipeline:
# Encrypt state before pushing to the backend
terraform state pull > state.json
qapi encrypt --input state.json --output state.enc
# Upload state.enc to your storage backend
# Decrypt state before terraform operations
# Download state.enc from your storage backend
qapi decrypt --input state.enc --output state.json
terraform state push state.json
rm -f state.json state.enc # Clean up plaintext immediately
Para un enfoque más integrado, envuelve terraform init con una configuración de backend que use un state file local, y añade hooks pre/post en tu pipeline que desencripten antes de plan/apply y encripten después:
# In your pipeline YAML (any CI/CD platform)
steps:
# 1. Download encrypted state from storage
- script: |
az storage blob download \
--container-name tfstate \
--name $MODULE_NAME.tfstate.enc \
--file tfstate.enc \
--account-name $STORAGE_ACCOUNT 2>/dev/null || true
displayName: Download encrypted state
# 2. Decrypt with QuantumVault (if state exists)
- script: |
if [ -f tfstate.enc ]; then
qapi decrypt --input tfstate.enc --output terraform.tfstate
rm -f tfstate.enc
fi
displayName: Decrypt state
env:
QUANTUMAPI_KEY: $(QUANTUMAPI_KEY)
# 3. Run Terraform with local state
- script: terraform init -backend=false
displayName: Terraform init (local state)
- script: terraform apply -auto-approve
displayName: Terraform apply
# 4. Encrypt and upload state
- script: |
qapi encrypt --input terraform.tfstate --output tfstate.enc
az storage blob upload \
--container-name tfstate \
--name $MODULE_NAME.tfstate.enc \
--file tfstate.enc \
--overwrite
rm -f terraform.tfstate tfstate.enc
displayName: Encrypt and upload state
env:
QUANTUMAPI_KEY: $(QUANTUMAPI_KEY)
El resultado: tu state está encriptado con ML-KEM-768 + AES-256-GCM en reposo. Aunque tengas acceso al storage, el blob es texto cifrado. El state en texto plano solo existe durante la ejecución del pipeline y se borra inmediatamente después.
Para equipos que usan OpenTofu, el bloque encryption nativo es más limpio — pero el wrapper de QuantumVault funciona con cualquier versión de Terraform y cualquier backend.
2. Secretos de Pipeline desde QuantumVault
En el artículo 4, los pipelines se ejecutan desde Backstage con anotaciones que apuntan a Azure DevOps, GitHub Actions o GitLab CI. Ahora añadimos resolución de secretos.
El patrón es el mismo del artículo 5 de la serie Quantum-Safe Cloud: instalar el CLI qapi, obtener secretos en tiempo de ejecución, e inyectarlos como variables de entorno enmascaradas.
Azure DevOps:
# In your pipeline YAML
steps:
- script: |
curl -sfL https://cli.quantumapi.eu/install.sh | sh -s -- -b /usr/local/bin
# Fetch infrastructure secrets from QuantumVault
ARM_CLIENT_SECRET=$(qapi vault get $(ARM_SECRET_ID))
TF_VAR_db_password=$(qapi vault get $(DB_PASSWORD_ID))
QUANTUMVAULT_KEY_ID=$(qapi vault get $(STATE_KEY_ID))
echo "##vso[task.setvariable variable=ARM_CLIENT_SECRET;issecret=true]$ARM_CLIENT_SECRET"
echo "##vso[task.setvariable variable=TF_VAR_db_password;issecret=true]$TF_VAR_db_password"
echo "##vso[task.setvariable variable=QUANTUMVAULT_KEY_ID;issecret=true]$QUANTUMVAULT_KEY_ID"
displayName: Fetch secrets from QuantumVault
env:
QUANTUMAPI_KEY: $(QUANTUMAPI_KEY) # Only bootstrap secret in ADO
GitHub Actions:
# In your workflow
steps:
- name: Install qapi CLI
run: curl -sfL https://cli.quantumapi.eu/install.sh | sh -s -- -b /usr/local/bin
- name: Fetch secrets
env:
QUANTUMAPI_KEY: ${{ secrets.QUANTUMAPI_KEY }}
run: |
# Fetch secrets into variables first
ARM_CLIENT_SECRET=$(qapi vault get ${{ vars.ARM_SECRET_ID }})
TF_VAR_db_password=$(qapi vault get ${{ vars.DB_PASSWORD_ID }})
QUANTUMVAULT_KEY_ID=$(qapi vault get ${{ vars.STATE_KEY_ID }})
# Mask them so they never appear in logs
echo "::add-mask::$ARM_CLIENT_SECRET"
echo "::add-mask::$TF_VAR_db_password"
echo "::add-mask::$QUANTUMVAULT_KEY_ID"
# Then export to GITHUB_ENV
echo "ARM_CLIENT_SECRET=$ARM_CLIENT_SECRET" >> $GITHUB_ENV
echo "TF_VAR_db_password=$TF_VAR_db_password" >> $GITHUB_ENV
echo "QUANTUMVAULT_KEY_ID=$QUANTUMVAULT_KEY_ID" >> $GITHUB_ENV
El paso ::add-mask:: es crítico. Sin él, GitHub Actions no sabe que esos valores son secretos — aparecerían en los logs. Azure DevOps usa issecret=true para lo mismo. Siempre enmascara antes de exportar.
GitLab CI:
# In .gitlab-ci.yml
.fetch-secrets:
before_script:
- curl -sfL https://cli.quantumapi.eu/install.sh | sh -s -- -b /usr/local/bin
- |
# Disable trace to prevent secrets from appearing in job logs
set +x
export ARM_CLIENT_SECRET=$(qapi vault get $ARM_SECRET_ID)
export TF_VAR_db_password=$(qapi vault get $DB_PASSWORD_ID)
export QUANTUMVAULT_KEY_ID=$(qapi vault get $STATE_KEY_ID)
set -x
GitLab CI muestra los comandos por defecto cuando set -x está activo (el valor por defecto en muchos runners). El wrapper set +x / set -x evita que las líneas export impriman los valores secretos en el log del job.
En los tres casos, solo un secreto vive en la plataforma CI/CD: QUANTUMAPI_KEY. Todo lo demás es un ID no secreto que apunta a una entrada de QuantumVault. Los valores reales de los secretos solo existen en memoria durante la ejecución del pipeline.
3. Claves SSH Post-Cuánticas
El SSH tradicional usa claves RSA o ECDSA. Ambas son vulnerables a ataques cuánticos. QuantumAPI puede emitir certificados SSH ML-DSA — claves de corta duración que expiran y se rotan automáticamente.
El flujo:
- El ingeniero solicita un certificado SSH a QuantumAPI (a través de Backstage o CLI)
- QuantumAPI emite un certificado válido por 8 horas, firmado con la clave ML-DSA de la CA
- El host destino confía en la CA de QuantumAPI (se configura una vez)
- El certificado expira automáticamente — no hay clave que revocar, no hay clave que olvidar en un portátil
Solicitar un certificado:
qapi ssh issue \
--principal victor.zaragoza \
--validity 8h \
--output ~/.ssh/id_mldsa
Esto crea dos archivos: ~/.ssh/id_mldsa (clave privada) y ~/.ssh/id_mldsa-cert.pub (certificado). El certificado incluye el nombre del principal, el periodo de validez y la firma de la CA.
Configurar el host destino:
# /etc/ssh/sshd_config (on the target host)
TrustedUserCAKeys /etc/ssh/quantumapi_ca.pub
La clave pública de la CA se obtiene una vez de QuantumAPI:
qapi ssh ca-key > /etc/ssh/quantumapi_ca.pub
Ahora cualquier certificado firmado por la CA de QuantumAPI es aceptado. No más distribuir claves públicas individuales a cada host. No más archivos authorized_keys con 47 entradas.
Desde Backstage:
Añadimos un widget de certificados SSH a la página de entidad. Los ingenieros hacen clic en “Request SSH Access”, seleccionan el host destino (del catálogo), y obtienen un certificado de corta duración:
// plugins/infra-ssh/src/components/SshAccessCard.tsx
import React, { useState } from 'react';
import { InfoCard } from '@backstage/core-components';
import { Button, Typography, CircularProgress } from '@material-ui/core';
import { useEntity } from '@backstage/plugin-catalog-react';
import { useApi, fetchApiRef, discoveryApiRef } from '@backstage/core-plugin-api';
export const SshAccessCard = () => {
const { entity } = useEntity();
const fetchApi = useApi(fetchApiRef);
const discoveryApi = useApi(discoveryApiRef);
const [loading, setLoading] = useState(false);
const [cert, setCert] = useState<string | null>(null);
const handleRequest = async () => {
setLoading(true);
try {
const proxyUrl = await discoveryApi.getBaseUrl('proxy');
const res = await fetchApi.fetch(`${proxyUrl}/ai-service/api/ssh/issue`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
principal: 'victor.zaragoza',
targetHost: entity.metadata.name,
validityHours: 8,
}),
});
const data = await res.json();
setCert(data.certificate);
} catch {
setCert('Failed to issue certificate.');
} finally {
setLoading(false);
}
};
return (
<InfoCard title="SSH Access" subheader="Post-quantum ML-DSA certificate">
<Button
variant="contained"
color="primary"
onClick={handleRequest}
disabled={loading}
>
{loading ? <CircularProgress size={16} /> : 'Request SSH Certificate (8h)'}
</Button>
{cert && (
<Typography
variant="body2"
style={{ marginTop: 16, fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}
>
{cert}
</Typography>
)}
</InfoCard>
);
};
El endpoint del AI service (añadido al mismo Program.cs de la serie IDP):
app.MapPost("/api/ssh/issue", async (SshRequest request, IConfiguration config) =>
{
var apiKey = config["QuantumApi:ApiKey"];
if (string.IsNullOrEmpty(apiKey))
return Results.Json(new { error = "QuantumAPI not configured." }, statusCode: 503);
var client = new QuantumApiClient(new QuantumApiOptions { ApiKey = apiKey });
var result = await client.Ssh.IssueCertificateAsync(new IssueSshCertificateRequest
{
Principal = request.Principal,
ValidityHours = request.ValidityHours,
Extensions = new[] { "permit-pty", "permit-port-forwarding" }
});
return Results.Ok(new
{
certificate = result.Certificate,
expiresAt = result.ExpiresAt,
algorithm = "ML-DSA-65"
});
});
record SshRequest(string Principal, string TargetHost, int ValidityHours);
En producción, registra QuantumApiClient vía DI (builder.Services.AddQuantumApiClient(...) como se muestra en la serie Quantum-Safe Cloud) en vez de instanciarlo por request. Para el artículo lo dejamos inline para que el endpoint sea autocontenido.
4. Detección de Secretos Dispersos con IA
Los secretos más peligrosos son los que olvidaste. Una cadena de conexión en un viejo backend.tf. Una API key en un terraform.tfvars que se subió por error. Un service principal expirado que nadie rotó.
Añadimos un escáner de secretos al AI service. Se ejecuta en un schedule (como el catalog enricher) y comprueba cada módulo del catálogo buscando problemas relacionados con secretos:
app.MapPost("/api/scan-secrets", async (SecretScanRequest request, IConfiguration config) =>
{
if (request.Files is not { Count: > 0 })
return Results.BadRequest(new { error = "At least one file is required." });
var endpoint = config["AI:Endpoint"];
var apiKey = config["AI:Key"];
var model = config["AI:ChatModel"] ?? "mistral-small-3.2-24b-instruct-2506";
var provider = config["AI:Provider"] ?? "openai";
ChatClient chatClient = provider.ToLowerInvariant() switch
{
"azure" => new AzureOpenAIClient(
new Uri(endpoint!), new ApiKeyCredential(apiKey!))
.GetChatClient(model),
_ => new OpenAIClient(
new ApiKeyCredential(apiKey!),
new OpenAIClientOptions { Endpoint = new Uri(endpoint!) })
.GetChatClient(model),
};
var systemPrompt = """
You are a security scanner for Terraform infrastructure code.
Analyze the provided files and find:
1. Hardcoded secrets (passwords, API keys, tokens, connection strings)
2. Unencrypted state backends (backends without encryption configuration)
3. RSA or ECDSA key generation (should migrate to ML-DSA/ML-KEM)
4. Expired or stale credentials (references to old key vaults, commented-out secrets)
5. Secrets passed as default values in variables (default = "password123")
Return a JSON array of findings. Each finding has:
- "file": the file path
- "line": approximate line number
- "severity": "critical" | "high" | "medium" | "low"
- "type": "hardcoded_secret" | "unencrypted_state" | "weak_algorithm" | "stale_credential" | "insecure_default"
- "description": what was found
- "fix": how to fix it
If no issues are found, return an empty array.
Do NOT flag environment variable references (var.xxx, $TF_VAR_xxx) as secrets.
Return ONLY valid JSON, no markdown.
""";
var filesSummary = string.Join("\n\n", request.Files.Select(f =>
{
var content = f.Content.Length > 3000
? f.Content[..3000] + "\n[...truncated]"
: f.Content;
return $"### {f.Path}\n```\n{content}\n```";
}));
try
{
var completion = await chatClient.CompleteChatAsync(
[
new SystemChatMessage(systemPrompt),
new UserChatMessage($"Scan these infrastructure files:\n\n{filesSummary}"),
]);
var raw = completion.Value.Content[0].Text.Trim();
var json = raw.StartsWith("```") ? raw.Split('\n', 2)[1].TrimEnd('`').Trim() : raw;
var findings = JsonSerializer.Deserialize<List<SecretFinding>>(json, SerializerOptions.Default);
return Results.Ok(new { findings = findings ?? new List<SecretFinding>() });
}
catch (Exception ex)
{
return Results.Json(new { error = $"AI scan error: {ex.Message}" }, statusCode: 502);
}
});
record SecretScanRequest(List<SourceFile> Files);
record SecretFinding(
[property: JsonPropertyName("file")] string File,
[property: JsonPropertyName("line")] int Line,
[property: JsonPropertyName("severity")] string Severity,
[property: JsonPropertyName("type")] string Type,
[property: JsonPropertyName("description")] string Description,
[property: JsonPropertyName("fix")] string Fix);
El plugin de Backstage ejecuta esto en cada módulo del catálogo, en un schedule:
// plugins/secret-scanner/src/module.ts
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
import { Octokit } from '@octokit/rest';
export const secretScannerModule = createBackendModule({
pluginId: 'catalog',
moduleId: 'secret-scanner',
register(env) {
env.registerInit({
deps: {
logger: coreServices.logger,
config: coreServices.rootConfig,
scheduler: coreServices.scheduler,
catalog: catalogServiceRef,
auth: coreServices.auth,
},
async init({ logger, config, scheduler, catalog, auth }) {
const aiServiceUrl = config.getString('forge.aiServiceUrl');
const githubToken = config.getString('catalogEnricher.githubToken');
const octokit = new Octokit({ auth: githubToken });
await scheduler.scheduleTask({
id: 'secret-scanner-run',
frequency: { hours: 12 },
timeout: { minutes: 30 },
initialDelay: { minutes: 2 },
fn: async () => {
logger.info('Starting secret scan');
const credentials = await auth.getOwnServiceCredentials();
const { items: entities } = await catalog.getEntities(
{ filter: { 'spec.type': 'terraform-module' } },
{ credentials },
);
for (const entity of entities) {
const slug = entity.metadata.annotations?.[
'github.com/project-slug'
];
if (!slug) continue;
const [owner, repo] = slug.split('/');
try {
const { data: tree } = await octokit.git.getTree({
owner, repo, tree_sha: 'main', recursive: 'true',
});
const tfFiles = tree.tree.filter(
f => f.type === 'blob' && f.path &&
(f.path.endsWith('.tf') || f.path.endsWith('.tfvars')),
);
const files: Array<{ path: string; content: string }> = [];
for (const file of tfFiles) {
if (!file.path) continue;
try {
const { data: content } = await octokit.repos.getContent({
owner, repo, path: file.path,
mediaType: { format: 'raw' },
});
files.push({
path: file.path,
content: content as unknown as string,
});
} catch { /* skip unreadable files */ }
}
if (files.length === 0) continue;
const res = await fetch(`${aiServiceUrl}/api/scan-secrets`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ files }),
});
if (!res.ok) continue;
const { findings } = await res.json() as {
findings: Array<{
file: string; severity: string;
type: string; description: string;
}>;
};
if (findings.length > 0) {
logger.warn(
`${entity.metadata.name}: ${findings.length} secret issues found`,
);
for (const f of findings) {
logger.warn(
` [${f.severity}] ${f.file}: ${f.description}`,
);
}
} else {
logger.info(`${entity.metadata.name}: clean`);
}
} catch (err) {
logger.error(
`Failed to scan ${entity.metadata.name}: ${err}`,
);
}
}
logger.info('Secret scan complete');
},
});
},
});
},
});
Cómo Se Ve un Informe de Escaneo
El escáner se ejecuta sobre el módulo tf-azurerm-vnet y encuentra:
[
{
"file": "backend.tf",
"line": 5,
"severity": "high",
"type": "unencrypted_state",
"description": "State backend uses azurerm storage without encryption block. State contains all resource attributes in plaintext.",
"fix": "Add terraform.encryption block with QuantumVault key provider. See article 5 of the Infrastructure Hub series."
},
{
"file": "variables.tf",
"line": 28,
"severity": "medium",
"type": "weak_algorithm",
"description": "Variable 'ssh_public_key' expects an RSA key (description says 'RSA public key'). RSA is vulnerable to quantum attack.",
"fix": "Accept ML-DSA keys instead. Use QuantumAPI SSH certificates for host access."
}
]
El equipo de plataforma ve esto en los logs de Backstage (y en un artículo futuro, en un dashboard). Los hallazgos críticos generan alertas. El ingeniero recibe una descripción clara y una solución específica.
Conectando Todo: La Anotación de Seguridad del Módulo
Cada módulo en el catálogo recibe una anotación de estado de seguridad que el escáner actualiza:
# Updated by the secret scanner
metadata:
annotations:
forge.io/secret-scan-status: "clean" # or "issues-found"
forge.io/secret-scan-last: "2026-04-03T10:00Z"
forge.io/state-encryption: "quantumvault" # or "azure-managed" or "none"
El catalog enricher y el escáner de secretos trabajan juntos: el enricher mantiene la descripción y los tags actualizados, el escáner mantiene el estado de seguridad actualizado. Ambos se ejecutan en un schedule, ambos actualizan el catálogo, ambos son visibles desde Backstage.
La Arquitectura Completa de Secretos para Infraestructura
BEFORE (typical enterprise)
├── Pipeline variable group → RSA-encrypted in Azure Key Vault
│ ├── ARM_CLIENT_SECRET = "actual-secret-value"
│ ├── DB_PASSWORD = "actual-password"
│ └── SSH_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----..."
├── Terraform state → plaintext in Azure Storage (encrypted at rest with RSA)
│ └── Contains ALL resource secrets as attributes
└── ~/.ssh/ → RSA keys, never rotated, on 5 laptops
AFTER (with QuantumVault)
├── Pipeline → only QUANTUMAPI_KEY in variable group
│ ├── ARM_SECRET_ID = "just-an-id" (fetched at runtime from QuantumVault)
│ ├── DB_PASSWORD_ID = "just-an-id" (fetched at runtime from QuantumVault)
│ └── SSH → ML-DSA certificates, 8h validity, auto-expire
├── Terraform state → encrypted with ML-KEM-768 + AES-256-GCM
│ └── Even with storage access, ciphertext is useless without QuantumVault key
├── QuantumVault (source of truth)
│ ├── All secrets ML-KEM encrypted at rest
│ ├── QRNG key generation
│ ├── Audit log of every access
│ └── Per-module, per-client secret scoping
└── Backstage → secret scan status visible per module
Checklist
- Encriptación del state de Terraform configurada con key provider de QuantumVault
-
enforced = trueestablecido — Terraform rechaza escribir state sin encriptar - Secretos de pipeline obtenidos de QuantumVault en tiempo de ejecución (no almacenados en CI/CD)
- Solo
QUANTUMAPI_KEYpermanece en los variable groups del pipeline - Claves SSH reemplazadas con certificados ML-DSA (validez de 8h)
- Hosts destino configurados para confiar en la CA de QuantumAPI
- Escáner de secretos ejecutándose en schedule (cada 12h)
- Escáner cubre todas las entidades
terraform-moduledel catálogo - Resultados del escaneo visibles en los logs de Backstage
- Anotación
forge.io/secret-scan-statusactualizada por módulo - State existente migrado (primer
terraform applydespués de añadir la encriptación)
Challenge
Antes del próximo artículo:
- Añade el bloque de encriptación a uno de los
versions.tfde tus módulos - Ejecuta
terraform plan— debería funcionar sin cambios (el state se encripta en el siguiente apply) - Ejecuta el escáner de secretos en tus módulos — ¿cuántos problemas encuentra?
- Reemplaza una clave SSH RSA con un certificado ML-DSA de QuantumAPI
En el próximo artículo, construimos la Automatización del CAB — workflows de Change Advisory Board donde la IA prepara la evidencia, evalúa el riesgo, y el CAB revisa un paquete completo en vez de un ticket a las prisas. Con firmas PQC en cada aprobación.
El código completo está en GitHub.
Si esta serie te resulta útil, puedes invitarme a un café.
Este es el artículo 5 de la serie Infrastructure Hub. Anterior: Pipelines from Backstage. Siguiente: CAB Automation — solicitudes de cambio preparadas por IA con firmas post-cuánticas.
Loading comments...