Verificando autenticación…

Saltar al contenido principal

Template Email ACL

Propósito y Alcance

Este proyecto actúa como un ACL (Access Control Layer) de Correos. Su principal objetivo es recibir solicitudes de envío de correo electrónico a través de mensajes en Google Cloud Pub/Sub y procesarlos para enviarlos utilizando Amazon Simple Email Service (SES).

Problema que resuelve: Proporciona una interfaz centralizada y desacoplada para el envío de correos electrónicos, permitiendo que otros servicios deleguen esta funcionalidad sin necesidad de integrar directamente el SDK de Amazon SES ni gestionar credenciales de AWS de forma distribuida.

Alcance General:

  • Recepción de mensajes en formato JSON desde una suscripción de Google Cloud Pub/Sub.
  • Parseo del mensaje para extraer destinatarios, asunto, cuerpo del correo (HTML soportado) y adjuntos.
  • Construcción dinámica del remitente del correo basado en metadatos de la aplicación solicitante y el entorno.
  • Conversión y manejo de archivos adjuntos (codificados en Base64).
  • Integración con Amazon SES para el envío efectivo de los correos.
  • Manejo de configuración específica por entorno (local, dev, intg, prod).

Fuera del Alcance:

  • No expone una API REST pública para el envío de correos (la interacción es vía Pub/Sub).
  • No gestiona plantillas de correo complejas (el cuerpo del correo se recibe directamente).
  • No implementa lógica de reintentos avanzada más allá de lo que Pub/Sub pueda ofrecer.

Arquitectura

El proyecto sigue una arquitectura hexagonal ( puertos y adaptadores), buscando un buen desacoplamiento entre la lógica de negocio (dominio) y los componentes de infraestructura (integraciones con servicios externos).

Flujo Principal:

  1. Un mensaje es publicado en un topic de Google Cloud Pub/Sub por un servicio cliente.
  2. El PubSubReceiver (adaptador de entrada) en este proyecto está suscrito a dicho topic (a través de una suscripción específica) y recibe el mensaje.
  3. El mensaje (payload JSON) es procesado por el PubSubService (lógica de aplicación/dominio).
  4. El PubSubService transforma el payload en un objeto EmailDetails, determina el remitente (from email), y prepara los detalles para el envío.
  5. Si hay adjuntos, se decodifican de Base64 y se preparan.
  6. El EmailAmazonService y EmailAmazonRawService (adaptadores de salida) utilizan el cliente de Amazon SES para construir y enviar el correo electrónico.
  7. Las credenciales para AWS SES (AWS_ACCESS_KEY, AWS_SECRET_KEY) se gestionan de forma segura, típicamente a través de secretos en GCP Secret Manager inyectados como variables de entorno en el despliegue de GKE.

Estructura del Proyecto (simplificada):

com.latam.ltmds.email.acl
├── application
│ └── request/ # DTOs como EmailDetails, Attachment
├── domain
│ ├── service/ # Lógica de negocio principal
│ │ ├── email/ # Servicios relacionados con Email (interfaz con SES)
│ │ └── pubsub/ # Servicios relacionados con Pub/Sub (procesamiento de mensajes)
│ └── exception/ # Excepciones customizadas
└── infrastructure
├── config/ # Configuración de Spring, Beans
├── pubsub/ # Adaptador para recibir mensajes de Pub/Sub
└── ses/ # Adaptador para enviar correos vía Amazon SES

Tecnologías y Dependencias

  • Lenguaje: Java 21
  • Framework: Spring Boot 3.4.5
  • Build Tool: Gradle 8.12.1
  • Mensajería Asíncrona:
    • Google Cloud Pub/Sub (Receptor): com.google.cloud:spring-cloud-gcp-starter-pubsub
    • Amazon Simple Email Service (SES) (Emisor): software.amazon.awssdk:ses
  • Servidor Embebido: Tomcat (default en Spring Boot Starter Web)
  • Bibliotecas Clave:
    • spring-boot-starter-web: Para funcionalidades web básicas y Actuator.
    • spring-boot-starter-integration: Para la integración con Pub/Sub.
    • spring-boot-starter-validation: Para validaciones.
    • jakarta.mail: API para la construcción de correos.
    • org.projectlombok:lombok: Para reducir código boilerplate.
    • com.latam:latam-logging-library: Para logging estandarizado.
    • com.google.code.gson:gson: Para manipulación de JSON (si es necesario adicionalmente a Jackson).
    • io.netty: Dependencia transitiva, gestionada para resolver vulnerabilidades.
  • Pruebas:
    • JUnit 5
    • Mockito
  • Observabilidad:
    • Spring Boot Actuator
    • Integración con Google Cloud Logging & Trace (si está habilitado)
  • Licencias: Consultar LibrariesLicenses.txt para un detalle completo. El proyecto en sí es Proprietary and confidential según LICENSE.

Configuración Local

Prerrequisitos

  • JDK 21.
  • Gradle (el wrapper gradlew está incluido en el proyecto).
  • Una IDE como IntelliJ IDEA o Eclipse.
  • Acceso a JFrog Artifactory de LATAM para dependencias corporativas.
  • Credenciales de AWS: AWS_ACCESS_KEY y AWS_SECRET_KEY con permisos para Amazon SES.
  • Credenciales de GCP: Un archivo de credenciales de cuenta de servicio (GOOGLE_APPLICATION_CREDENTIALS) con permisos para Google Cloud Pub/Sub (leer de suscripción) y, si se usa, Secret Manager.
  • Una suscripción de Pub/Sub creada y un topic al que enviar mensajes de prueba.

Pasos de Configuración

  1. Clonar el repositorio: Sigue los siguientes pasos

  2. Configurar credenciales de Artifactory: Crea un archivo gradle.properties en la raíz del proyecto (este archivo está en .gitignore) con tus credenciales:

    ARTIFACTORY_BASE_URL=https://artifactoryrepo1.appslatam.com/artifactory/
    ARTIFACTORY_GRADLE_CORP_REPO=corp-libs-gradle-release
    ARTIFACTORY_USERNAME=<tu_usuario_artifactory>
    ARTIFACTORY_PASSWORD=<tu_api_key_artifactory>
  3. Configurar variables de entorno para AWS y GCP localmente: Puedes configurar estas variables en tu sistema o en la configuración de ejecución de tu IDE.

    • AWS_ACCESS_KEY=<tu_aws_access_key>
    • AWS_SECRET_KEY=<tu_aws_secret_key>
    • GOOGLE_APPLICATION_CREDENTIALS=/ruta/a/tu/archivo-credenciales-gcp.json
    • PROJECT_ID=<tu_gcp_project_id>
    • SUBSCRIPTION-NAME=<tu_nombre_de_suscripcion_pubsub> (Este valor se usa en application.yaml, podría necesitar ajuste en application-local.yaml o perfiles específicos)
  4. Ajustar application-local.yaml (si es necesario): Verifica las configuraciones en src/main/resources/application-local.yaml y src/main/resources/application.yaml para la suscripción de Pub/Sub y otros parámetros específicos. Por ejemplo, en application.yaml:

    gcp:
    subscription:
    name: ${{ SUBSCRIPTION-NAME }} # Asegúrate que esta variable se resuelva o configura directamente para local
  5. Compilar el proyecto:

    ./gradlew clean build
  6. Ejecutar la aplicación:

    ./gradlew bootRun

    La aplicación se iniciará y comenzará a escuchar mensajes en la suscripción de Pub/Sub configurada.

Enviar un Mensaje de Prueba (vía GCP Console o gcloud CLI)

Publica un mensaje JSON al topic de Pub/Sub asociado con tu suscripción configurada. Formato del mensaje:

{
"toEmail": "destinatario1@example.com,destinatario2@example.com",
"subject": "Asunto del correo de prueba",
"body": "<h1>Hola!</h1><p>Este es el cuerpo <b>HTML</b> del correo de prueba.</p>",
"itelement": "MI_ITELEMENT",
"application": "MI_APLICACION",
"sender": "opcional-remitente-explicito@example.com",
"attachments": [
{
"byteArray": "BASE64_ENCODED_STRING_OF_FILE_BYTES_1",
"fileName": "archivo1.pdf"
},
{
"byteArray": "BASE64_ENCODED_STRING_OF_FILE_BYTES_2",
"fileName": "imagen.png"
}
]
}
  • toEmail: Lista de correos separados por coma.
  • body: Puede ser texto plano o HTML.
  • itelement, application: Usados para construir el remitente si sender no se provee.
  • sender (opcional): Si se provee, se usa este remitente directamente. Si no, se construye como <application>-<itelement>@mails-noprod.appslatam.com (o @mails.appslatam.com para prod).
  • attachments (opcional): Lista de objetos, cada uno con byteArray (contenido del archivo en Base64) y fileName.

Despliegue

El proyecto está configurado para ser desplegado en Google Kubernetes Engine (GKE) utilizando el pipeline corporativo de LATAM.

  • Pipeline Jenkins: El archivo Jenkinsfile define el pipeline a utilizar:
    pipelineLatam("gke-krane-java21")
  • Configuración de Kubernetes: Se encuentra en la carpeta deploy/gke/. El archivo principal es deployment.yaml.erb, que es una plantilla procesada durante el despliegue.
  • Variables de Entorno por Ambiente: Los archivos en deploy/env/ (ej. dev.yaml, intg.yaml, prod.yaml) se utilizan para especificar variables de entorno dinámicas para cada ambiente.
  • Dockerfile: Ubicado en la raíz del proyecto, define la imagen Docker a construir. Utiliza una imagen base de LATAM JRE 21. FROM <%= redhat_ubi_8_jre_21 %> (o similar latam-jre21).
  • Variables de Entorno Clave en Despliegue:
    • ENVIRONMENT: (dev, intg, prod)
    • AWS_ACCESS_KEY: Clave de acceso AWS (obtenida de Secret Manager).
    • AWS_SECRET_KEY: Clave secreta AWS (obtenida de Secret Manager).
    • PROJECT_ID: ID del proyecto GCP.
    • SUBSCRIPTION-NAME: Nombre completo de la suscripción Pub/Sub (ej. projects/mi-proyecto/subscriptions/mi-subscripcion).
    • Otras configuraciones del configMap en deployment.yaml.erb.

Endpoints de la API

Este servicio no expone una API REST tradicional para sus funcionalidades principales, ya que opera consumiendo mensajes de Google Cloud Pub/Sub.

Interacción Principal vía Pub/Sub:

  • Mecanismo: El servicio escucha una suscripción de Pub/Sub.
  • Formato de Mensaje: JSON (ver ejemplo en sección "Configuración Local").

Endpoints de Actuator (Manejo y Salud): Estos endpoints son expuestos por Spring Boot Actuator y se configuran en deployment.yaml.erb y application.yaml. El contextPath es /api/email/ses.

MétodoEndpointDescripción
GET/api/email/ses/actuator/healthEstado general de salud de la aplicación.
GET/api/email/ses/actuator/health/readinessSonda de Readiness para Kubernetes.
GET/api/email/ses/actuator/health/livenessSonda de Liveness para Kubernetes.
GET/api/email/ses/actuator/infoInformación de la aplicación (build, git).
GET/api/email/ses/actuator/loggersVisualizar y modificar niveles de log.
GET/api/email/ses/actuator/metricsMétricas de la aplicación (JVM, HTTP, etc.).

No hay swagger.yaml explícito para endpoints de negocio, pero si springdoc-openapi está activo, /v3/api-docs podría estar disponible para los endpoints de Actuator.

Componentes Específicos

Google Cloud Pub/Sub

  • Propósito: Recibir las solicitudes de envío de correo de forma asíncrona.
  • Configuración: En src/main/resources/application.yaml:
    spring:
    cloud:
    gcp:
    project-id: ${PROJECT_ID} # Variable de entorno
    # ...
    gcp:
    subscription:
    name: ${{ SUBSCRIPTION-NAME }} # Variable de entorno, ej: projects/mi-proyecto/subscriptions/mi-suscripcion-email
    La autenticación se maneja típicamente mediante la variable de entorno GOOGLE_APPLICATION_CREDENTIALS (localmente) o la cuenta de servicio asignada al pod de Kubernetes (en GKE).
  • Implementación Principal:
    • com.latam.ltmds.email.acl.infrastructure.pubsub.PubSubReceiver: Contiene el @ServiceActivator que escucha los mensajes del PubSubInboundChannelAdapter.
    • com.latam.ltmds.email.acl.domain.service.pubsub.PubSubService: Procesa la lógica del mensaje recibido, transforma el payload y llama al servicio de email.
  • Deshabilitar (si fuera necesario):
    1. Eliminar la dependencia com.google.cloud:spring-cloud-gcp-starter-pubsub de gradle/dependencies.gradle.
    2. Remover o comentar las clases relacionadas con Pub/Sub (PubSubReceiver, PubSubService) y su configuración.
    3. Quitar las configuraciones gcp.subscription.name y gcp.project-id de application.yaml.

Amazon Simple Email Service (SES)

  • Propósito: Enviar efectivamente los correos electrónicos.

  • Configuración: En src/main/resources/application.yaml:

    aws:
    region:
    static: us-east-1 # Región de AWS SES
    auto: false
    stack:
    auto: false
    credentials:
    access-key: ${AWS_ACCESS_KEY:aws_key} # Variable de entorno
    secret-key: ${AWS_SECRET_KEY:aws_secret_key} # Variable de entorno

    mail:
    sender:
    non-prod: "@mails-noprod.appslatam.com" # Dominio para remitentes en no-producción
    prod: "@mails.appslatam.com" # Dominio para remitentes en producción

    Las credenciales AWS_ACCESS_KEY y AWS_SECRET_KEY deben ser configuradas como variables de entorno o secretos (en GKE, se obtienen de GCP Secret Manager).

  • Implementación Principal:

    • com.latam.ltmds.email.acl.infrastructure.ses.AmazonSesConfig: Configura el SesClient de AWS SDK.
    • com.latam.ltmds.email.acl.domain.service.email.EmailAmazonService: Servicio de fachada para el envío de correos.
    • com.latam.ltmds.email.acl.domain.service.email.EmailAmazonRawService: Lógica para construir el mensaje MIME (incluyendo cuerpo HTML y adjuntos) y enviarlo usando SendRawEmail de SES.
  • Deshabilitar (si fuera necesario):

    1. Eliminar la dependencia software.amazon.awssdk:ses de gradle/dependencies.gradle.
    2. Remover o comentar las clases relacionadas con SES (AmazonSesConfig, EmailAmazonService, EmailAmazonRawService).
    3. Quitar las configuraciones aws.* y mail.sender.* de application.yaml.

Pruebas

El proyecto utiliza JUnit 5 y Mockito para pruebas unitarias y de integración ligera.

  • Ejecutar Pruebas:

    ./gradlew test

    Esto ejecutará todas las pruebas en src/test/java.

  • Configuración de Pruebas: Se encuentra en gradle/test.gradle. Incluye la configuración de Jacoco para la generación de reportes de cobertura.

    jacoco {
    toolVersion = "0.8.12"
    reportsDirectory = layout.buildDirectory.dir('reports/jacoco/test/html/')
    }

    test {
    systemProperty 'spring.profiles.active', 'test' // Activa el perfil 'test' para las pruebas
    useJUnitPlatform()
    finalizedBy jacocoTestReport
    }

    jacocoTestReport {
    // ... configuración de reportes ...
    }
  • Reportes de Cobertura: Después de ejecutar ./gradlew test, los reportes HTML de Jacoco se encontrarán en build/reports/jacoco/test/html/index.html.

  • Archivos de Configuración para Pruebas: src/main/resources/application-test.yaml puede contener configuraciones específicas para el entorno de pruebas.

Consideraciones de Seguridad

  • Gestión de Credenciales:
    • Las credenciales de AWS (AWS_ACCESS_KEY, AWS_SECRET_KEY) NUNCA deben estar hardcodeadas en el código o en archivos de configuración versionados. Deben ser inyectadas como variables de entorno en tiempo de ejecución. En GKE, esto se gestiona a través de GCP Secret Manager.
    • Para GCP, la autenticación se realiza preferentemente mediante la cuenta de servicio asociada al pod de GKE. Localmente, se usa el archivo referenciado por GOOGLE_APPLICATION_CREDENTIALS.
  • Licencia: El proyecto tiene una licencia propietaria (LICENSE - "Proprietary and confidential"). Asegúrate de cumplir con sus términos.
  • Dependencias: Mantén las dependencias actualizadas para mitigar vulnerabilidades conocidas. Revisa regularmente los informes de SonarQube u otras herramientas de análisis de seguridad.
  • Validación de Entradas: Aunque el servicio consume de Pub/Sub (un sistema interno), es una buena práctica validar los datos del payload JSON recibido (ej. formato de emails, tamaño de adjuntos si es relevante) para evitar errores inesperados o abusos. Spring Validation (spring-boot-starter-validation) está incluido y puede ser utilizado en los DTOs.
  • Logging: Asegúrate de no loguear información sensible como las credenciales o el contenido completo de archivos adjuntos, a menos que sea estrictamente necesario para debugging y con el nivel de log adecuado.