-
Notifications
You must be signed in to change notification settings - Fork 1
DevSecOps Practices
DevSecOps integra la seguridad como un ciudadano de primera clase en todo el ciclo de vida del desarrollo, no como un paso final antes del release. En Linker1, la seguridad está embebida en el código, en el pipeline, en la infraestructura y en los procesos del equipo.
Todos los valores sensibles están almacenados como GitHub Secrets (encriptados, nunca visibles en logs):
| Secret | Nivel | Propósito |
|---|---|---|
LD_SDK_KEY |
Repositorio | LaunchDarkly SDK key |
DEPLOYMENT_PRIVATE_KEY |
Repositorio | SSH private key para deploy vía Bastion |
DEPLOYMENT_PUBLIC_KEY |
Repositorio | SSH public key para sesión Bastion |
OCI_CLI_USER |
Repositorio | Autenticación OCI API |
OCI_CLI_TENANCY |
Organización | Tenant de OCI |
OCI_CLI_FINGERPRINT |
Repositorio | Fingerprint de API key OCI |
OCI_CLI_KEY_CONTENT |
Repositorio | Private key de OCI |
OTEL_EXPORTER_OTLP_HEADERS |
Repositorio | Token de autenticación Grafana Cloud |
MYSQL_PWD |
Repositorio | Contraseña MySQL |
| Variable | Nivel | Propósito |
|---|---|---|
OCI_CLI_REGION |
Organización | Región OCI (sa-bogota-1) |
OCI_BASTION_OCID |
Organización | ID del servicio Bastion |
OCI_INSTANCE_OCID |
Repositorio | ID de la VM de producción |
OTEL_EXPORTER_OTLP_ENDPOINT |
Repositorio | URL del gateway OTLP |
MYSQL_HOST / MYSQL_DATABASE / MYSQL_USER
|
Repositorio | Config MySQL |
LOG_LEVEL |
Repositorio | Nivel de logging |
Los secrets críticos de infraestructura (SSH keys, passwords de base de datos) se almacenan adicionalmente en OCI Vault (https://cloud.oracle.com/security/secrets?region=sa-bogota-1), proporcionando:
- Encriptación en reposo con claves gestionadas por OCI
- Acceso auditado
- Rotación sin cambiar la configuración del pipeline
Los secrets en el pipeline de deploy se transportan en base64 para evitar problemas de inyección de shell:
- name: Base64-encode secrets for safe transport
run: |
b64() { printf '%s' "$1" | base64 -w0; }
echo "ld_sdk_key=$(b64 "${{ secrets.LD_SDK_KEY }}")" >> "$GITHUB_OUTPUT"Y se decodifican + escapan para systemd solo en el script remoto:
LD_SDK_KEY=$(printf '%s' "${{ steps.b64.outputs.ld_sdk_key }}" | base64 -d)
LD_SDK_KEY=$(systemd_escape_value "$LD_SDK_KEY")| Regla | Estado |
|---|---|
| Requiere Pull Request | ✅ |
| Aprobaciones mínimas | 1 |
| Dismiss stale reviews | ✅ |
| Status checks requeridos | Build, Tests, Package, Summary, Smoke Test |
| Branch up to date | ✅ (strict: true) |
| Conversaciones resueltas | ✅ |
| Force push | ❌ Bloqueado |
| Eliminar branch | ❌ Bloqueado |
| Admin bypass | ❌ (enforce_admins: true) |
| Regla | Estado |
|---|---|
| Requiere Pull Request | ✅ |
| Aprobaciones mínimas | 1 |
| Dismiss stale reviews | ✅ |
| Status checks requeridos | Build, Tests, Package, Summary, Smoke Test |
| Branch up to date | ✅ (strict: true) |
| Force push | ❌ Bloqueado |
| Admin bypass | ❌ (enforce_admins: true) |
Lección aprendida: inicialmente incluimos checks de pipeline.yml como required en DEV. Ese workflow solo corre en push a main, no en PRs. El resultado: todos los PRs quedaron bloqueados. Se corrigió inmediatamente.
feature/* ──── PR ────▶ DEV ──── PR ────▶ main
│ │
▼ ▼
1 approval 1 approval
CI verde CI verde
Review comments Review comments
resueltos resueltos
El repositorio incluye un template de PR que guía al autor a documentar:
- Descripción de los cambios
- Tipo de cambio (bug fix, feature, breaking change)
- Checklist de calidad
* @AnderssonProgramming @Anderfg13 @daniel-pm19 @esteban0903 @Juan-Jose-D
Todo el equipo es CODEOWNER de todo el repositorio. Esto garantiza que:
- Cada PR tiene reviewers asignados automáticamente
- Nadie puede aprobar su propio código (GitHub lo previene)
- El conocimiento del codebase se distribuye entre todos los miembros
| Check | Workflow | Qué valida |
|---|---|---|
| Build | ci.yml |
El código compila |
| Tests | ci.yml |
Tests unitarios pasan + cobertura 100% |
| Package | ci.yml |
El fat JAR se genera correctamente |
| Summary | ci.yml |
El artefacto final es válido |
| Smoke Test | ci.yml |
La app inicia y los endpoints responden |
JaCoCo está configurado con un umbral de 100% de cobertura de líneas (excluyendo Main.class):
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>1.00</minimum>
</limit>
</limits>
</rule>Si la cobertura baja de 100%, el build falla y el PR no se puede mergear.
El job rollback en pipeline.yml se ejecuta automáticamente si el deploy o la validación fallan:
- Identifica el tag SemVer anterior al actual
- Descarga el JAR de ese release desde GitHub Releases
- Lo despliega vía Bastion
- Verifica que el servicio responde 200
ssh linker
cd linker1
bash scripts/rollback.sh v1.0.0El script:
- Verifica el tag existe
- Detiene el servicio
- Cambia al código del tag anterior
- Reconstruye con
deploy.sh - Verifica el servicio está activo y responde
Para features controladas por LaunchDarkly, el rollback es instantáneo: un toggle en el dashboard deshabilita la feature sin necesidad de re-deploy, rebuild, ni ninguna intervención en la infraestructura.
Un OCI Bastion es un servicio gestionado que permite acceso SSH a recursos en subnets privadas sin necesidad de IP pública, jump hosts, ni VPNs.
La VM de producción tiene assign_public_ip = false. No existe ninguna IP pública asociada a ella. El Bastion es el único mecanismo de acceso.
GitHub Actions OCI Bastion VM
│ │ │
├── Crea sesión SSH ────────────▶│ │
│ │ │
├── Envía JAR + script ────────▶│──── SSH tunnel ─────────▶│
│ │ │── ejecuta script
│ │ │── copia JAR
│ │ │── restart systemd
│ │◀─── resultado ────────────│
│◀── Resultado ─────────────────│ │
│ │ │
├── Destruye sesión ────────────▶│ │
- Sesiones efímeras: cada sesión tiene un TTL de 1800 segundos
- Autenticación dual: API key de OCI + par de claves SSH del deploy
- Sin estado: no hay un servidor permanente que pueda comprometerse
- Auditable: cada sesión queda registrada en OCI Audit
El repositorio incluye archivos de comunidad estándar de GitHub:
| Archivo | Propósito |
|---|---|
.github/SECURITY.md |
Política de reporte de vulnerabilidades |
.github/CONTRIBUTING.md |
Guía de contribución |
.github/CODE_OF_CONDUCT.md |
Código de conducta |
.github/SUPPORT.md |
Canales de soporte |
.github/PULL_REQUEST_TEMPLATE.md |
Template estandarizado para PRs |
.github/ISSUE_TEMPLATE/ |
Templates para bugs y feature requests |
.github/CODEOWNERS |
Asignación automática de reviewers |
┌──────────────────────────────────────────────────────────────────┐
│ CAPA DE SEGURIDAD │
├──────────────┬───────────────────────────────────────────────────┤
│ Código │ Branch protection, CODEOWNERS, PR obligatorio │
│ │ Code review, CI obligatorio │
├──────────────┼───────────────────────────────────────────────────┤
│ Secrets │ GitHub Secrets (encriptados), OCI Vault │
│ │ Base64 transport, systemd escaping │
├──────────────┼───────────────────────────────────────────────────┤
│ Pipeline │ Tests + cobertura 100%, smoke tests, │
│ │ rollback automático │
├──────────────┼───────────────────────────────────────────────────┤
│ Infraestructura│ Sin IP pública, Bastion, sesiones efímeras │
│ │ IaC (Terraform), cloud-init reproducible │
├──────────────┼───────────────────────────────────────────────────┤
│ Runtime │ systemd isolation (User=ubuntu), │
│ │ Nginx reverse proxy, health checks │
├──────────────┼───────────────────────────────────────────────────┤
│ Observabilidad│ Logs centralizados, métricas, traces, │
│ │ synthetic monitoring, alerting │
└──────────────┴───────────────────────────────────────────────────┘