Software Testing & QA Services

Seguridad en los patrones de diseño de software

Tags: Tecnologías
ciberseguridad

 

En un ciclo de desarrollo DevSecOps, la seguridad en el software no hace parte únicamente de la fase de pruebas y despliegue, sino que la seguridad se concibe como un elemento a revisar desde el comienzo del ciclo DevSecOps, lo que se conoce como shift left de seguridad.

 

Desde el punto de vista de seguridad existen cinco principios fundamentales que describen una adecuada implementación de seguridad en un ciclo DevSecOps.

 

  • Automatización: Integrar controles de seguridad en pipelines CI/CD para detección temprana.
  • Mínimo privilegio: Cada componente, usuario o servicio debe tener solo los permisos estrictamente necesarios.
  • Defensa en profundidad: Múltiples capas de seguridad buscan mantener un nivel aceptable de seguridad en caso de que una capa falle.
  • Fallo seguro (Fail Secure): Los errores de sistema deben llevar a estados donde no se exponga información sensible ni se “abra” el sistema a terceros no autorizados.
  • Observabilidad: Todos los eventos del ciclo DevSecOps deben ser registrados (logs) para detectar fallas de seguridad.

 

Adicionalmente, se presentan herramientas de seguridad a considerar en cada fase del ciclo DevSecOps y se profundiza en los patrones seguros de diseño de software que, a juicio de Rootstack, deberían utilizarse en todos los ciclos DevSecOps, sin importar el tipo de producto a entregar.

 

Planificación y diseño

 

En esta fase, un arquitecto de software debe considerar las siguientes herramientas para asegurar el software desde su concepción:

 

  • Modelamiento de amenazas: Uso del modelo PASTA para análisis estratégico, combinado con STRIDE para identificar amenazas técnicas que deben gestionarse antes de la implementación.
  • Análisis de requerimientos de seguridad: El estándar OWASP Application Security Verification Standard (ASVS) permite definir requerimientos de seguridad complementarios a los identificados con PASTA y STRIDE.

 

Desarrollo

 

A continuación, se listan los patrones de desarrollo seguro más importantes definidos durante la planificación y el diseño.

 

Patrón de diseñoTipoAmenaza STRIDERequerimiento OWASP ASVSDescripción
1Input Validation GuardEstructural (Proxy)Tampering, InjectionEncoding & Sanitization, Input ValidationValida todas las entradas antes de procesarlas para prevenir inyecciones de código.
2Authentication FacadeEstructural (Facade)Spoofing, RepudiationAuthentication, Session ManagementCentraliza la autenticación bajo una interfaz unificada.
3Authorization InterceptorComportamiento (Chain of Responsibility)Elevation of PrivilegeAccess ControlAgrega múltiples capas de autorización para evitar movimientos laterales.
4Token Validator ChainComportamiento (Chain of Responsibility)Spoofing, TamperingAuthentication, Data ProtectionCadena de validación, firma, expiración y revocación de JWT.
5Secure Session ManagerCreacional (Singleton)Spoofing, Information DisclosureSession ManagementGestiona sesiones utilizando tokens seguros.
6Rate Limiter GatewayEstructural (Facade/Gateway)Denial of ServiceAccess Control, Business LogicLimita intentos de inicio de sesión para prevenir ataques DoS y fuerza bruta.
7Parameterized Query BuilderCreacional (Builder)TamperingEncoding & Sanitization, Input ValidationConstruye consultas parametrizadas para prevenir inyecciones SQL.
8Secret Vault ProxyEstructural (Proxy)Information Disclosure, TamperingStored Cryptography, ConfigurationControla el acceso a secretos mediante un gestor de secretos.
9Audit Logger DecoratorEstructural (Decorator)RepudiationError Handling and LoggingAgrega logs de eventos de seguridad sin exponer detalles a usuarios finales.
10Encryption AdapterEstructural (Adapter)TamperingStored Cryptography, CommunicationUtiliza librerías criptográficas verificadas y estandarizadas.

 

Ejemplos de implementación en Python

 

Input Validation Guard

 

La librería pydantic permite definir validadores personalizados para entradas de usuario.

 

from pydantic import BaseModel, EmailStr, Field, validator
class UserInput(BaseModel):
    email: EmailStr
    username: str = Field(min_length=3, max_length=20,
                          regex=r'^[a-zA-Z][a-zA-Z0-9_]*$')
    password: str = Field(min_length=8, max_length=32)
    
    @validator('username')
    def validate_username(cls, v):
        forbidden = [
            'admin', 'root', 'system', 'administrator',
            'superuser', 'moderator', 'null', 'undefined', 'test'
        ]
        if v.lower() in forbidden:
            raise ValueError('El nombre de usuario no es permitido')
        return v
    
    @validator('password')
    def validate_password(cls, v, values):
        if not any(c.isupper() for c in v):
            raise ValueError('Debe contener una mayúscula')
        if not any(c.islower() for c in v):
            raise ValueError('Debe contener una minúscula')
        if not any(c.isdigit() for c in v):
            raise ValueError('Debe contener un número')
        if not any(c in '!@#$%^&*()_+-=[]{}|;:,.<>?' for c in v):
            raise ValueError('Debe contener un carácter especial')
        if ' ' in v:
            raise ValueError('No puede contener espacios')
        weak = ['password', 'password123', '12345678', 'qwerty123', 'admin123']
        if v.lower() in weak:
            raise ValueError('La contraseña no es segura')
        if 'username' in values and values['username'].lower() in v.lower():
            raise ValueError('Usuario y contraseña no pueden coincidir')
        return v

 

Authentication Facade

 

from passlib.context import CryptContext
import jwt
from datetime import datetime, timedelta
class AuthenticationFacade:
    def __init__(self, secret_key: str):
        self.pwd_context = CryptContext(
            schemes=["bcrypt"], deprecated="auto"
        )
        self.secret_key = secret_key
    
    def hash_password(self, password: str) -> str:
        return self.pwd_context.hash(password)
    
    def verify_password(self, plain: str, hashed: str) -> bool:
        return self.pwd_context.verify(plain, hashed)
    
    def create_token(self, user_id: str, expires_minutes: int = 30) -> str:
        now = datetime.utcnow()
        payload = {
            'user_id': user_id,
            'exp': now + timedelta(minutes=expires_minutes),
            'iat': now
        }
        return jwt.encode(payload, self.secret_key, algorithm="HS256")
    def verify_token(self, token: str) -> dict:
        return jwt.decode(
            token, self.secret_key, algorithms=["HS256"]
        )

 

Authorization Interceptor

 

from abc import ABC, abstractmethod
class AuthorizationHandler(ABC):
    def __init__(self):
        self._next = None
    
    def set_next(self, handler):
        self._next = handler
        return handler
    
    @abstractmethod
    def handle(self, user: dict, resource: str, action: str) -> bool:
        if self._next:
            return self._next.handle(user, resource, action)
        return False
class RoleHandler(AuthorizationHandler):
    def handle(self, user, resource, action):
        if user.get('role') == 'admin':
            return True
        return super().handle(user, resource, action)
class PermissionHandler(AuthorizationHandler):
    def handle(self, user, resource, action):
        permissions = user.get('permissions', [])
        if f"{resource}:{action}" in permissions:
            return True
        return super().handle(user, resource, action)
class OwnershipHandler(AuthorizationHandler):
    def handle(self, user, resource, action):
        if user.get('id') == resource.split('/')[-1]:
            return True
        return super().handle(user, resource, action)

 

CI (Integración continua)

 

Antes de realizar commits hacia un pipeline, es fundamental ejecutar análisis de seguridad de código de aplicación, infraestructura como código (IaC), dependencias y secretos en texto plano (análisis estático).

 

Algunas herramientas comunes son:

 

  • Snyk
  • SonarQube

 

CD (Despliegue continuo)

 

En esta fase se realizan pruebas dinámicas de seguridad, escaneo de vulnerabilidades y pentesting sobre sistemas ya desplegados.

 

  • Shodan
  • Google Dorks
  • WHOIS
  • theHarvester
  • Maltego
  • Nmap
  • Nessus / OpenVAS
  • OWASP ZAP
  • Nikto
  • Burp Suite
  • Metasploit
  • PowerShell / CMD / Terminal
  • John the Ripper
  • Ettercap

 

Operación

 

En operación, la observabilidad es clave para detectar comportamientos indebidos, imprudentes o maliciosos de usuarios finales.

 

Algunas herramientas y prácticas recomendadas son:

 

  • SIEM para correlación de eventos de seguridad
  • Monitoreo de integridad de archivos
  • Análisis de comportamiento de usuarios (UBA)
  • Detección de anomalías con machine learning
  • Canary tokens
  • Procedimientos de gestión de incidentes de seguridad

 

Te recomendamos en video