Desplegar una Lambda de AWS con AWS Secrets manager usando Serverless Framework
Necesidad de tener una buena plantilla
Seguro, en algún momento, te han solicitado la creación de la IaC sobre la que se ejecutará tu función Lambda y como buen desarrollador, has investigado y has terminado por utilizar serverless framework para tu despliegue, haz hecho
serverless deploy
Y tu función ahora está en AWS. Haz ejecutado este mismo comando para las cuentas de DEV, QA y PROD y ahora has triunfado, sin embargo, tener una buena plantilla es necesario. En mi opinión una buena plantilla cumple con las siguientes características:
- Evita el uso de variables hardcode.
- Evita el uso de variables pasadas como parámetro.
- Las variables de entorno de la función lambda se encuentran. resguardadas y centralizadas.
- Haces uso de un solo bucket para todos tus despliegues.
Estas prácticas te ayudarán a tener una plantilla de despliegue mejor y más organizada. Los primeros 3 puntos se pueden solucionar al usar params de serverless framework, sin embargo, como discutiré más adelante, esto no es óptimo. Una mejor opción es combinar este método con el servicio de secrets manager, a contiuación explico el porqué.
Problemas comunes a los que me he enfrentado sin utilizar un secrets manager
Una plantilla con las características que mencioné, especificamente, utilizando secrets manager, permitirá un despliegue subsecuente de las funciones lambda sin ningún problema, ya que si todas las variables que utilizaste se encuentran centralizadas y no tienen que ser ingresadas manualmente por el equipo Devops, se minimiza el error de dedo. Por ejemplo, este es un comando de despliegue que puede llegar a presentarse en la vida real utilizando únicamente los parámetros de serverless framework:
serverless deploy — param=”ROLE=my_role” — param=”POSTGREB_USER=my_db_user” — param=”POSTGREB_PWD=my_db_pwd” — param=”POSTGREB_SERVER=my_db_server” — param=”POSTGREB_DATABASE=my_db_database” — param=”POSTGREB_POOL_MAX=my_db_pool_max” — param=”POSTGREB_POOL_MIN=my_db_pool_min” — param=”DB_CONNECT_TIMEOUT=my_db_connect_timeout” — param=”DB_REQUEST_TIMEOUT=my_db_request_timeout” — param=”OTRA_VARIABLE_DE_ENTORNO=my_otra_variable_de_entorno” — param=”SECURITY_GROUP=my_security_group” — param=”SUBNET_ID=my_subnet_id”
Un comando así es insostenible y propenso a errores, a demás, no es especialmente elegante. Imagina al equipo DEVOPS buscando y tecleando cada uno de los param’s… Creo que se puede hacer mejor. ¿Qué tal se ve este otro comando de despliegue?
sls deploy — stage dev — param=”deployBucket=comercial-ventas-bucket”
Evidentemente, es más corto y el uso de los parámetros está limitado a configuraciones de despleigue y no de las variables de entorno de nuestra función. En este coso, las variables que definimos anteriormente ahora se encuentran en un secret de AWS Secrets manager.
Ejemplo de configuración
A continuación presento un código YAML para la configuración de una función lambda hipotética para ser ejecutada mediante eventos de AWS Eventbridge todos los días a las 12:10 am, cuya función será recuperar datos de una base de datos en PostgreSQL.
service: cron-job-actualiza-status
provider:
name: aws
runtime: nodejs18.x
stage: ${opt:stage, 'dev'}
region: us-east-1
role: ${self:custom.secrets.ROLE}
environment:
NODE_PATH: /opt
TZ: America/Mexico_City
POSTGREB_USER: ${self:custom.secrets.POSTGREB_USER}
POSTGREB_PWD: ${self:custom.secrets.POSTGREB_PWD}
POSTGREB_SERVER: ${self:custom.secrets.POSTGREB_SERVER}
POSTGREB_DATABASE: ${self:custom.secrets.POSTGREB_DATABASE}
POSTGREB_POOL_MAX: ${self:custom.secrets.POSTGREB_POOL_MAX}
POSTGREB_POOL_MIN: ${self:custom.secrets.POSTGREB_POOL_MIN}
POSTGREB_CONNECT_TIMEOUT: ${self:custom.secrets.POSTGREB_CONNECT_TIMEOUT}
POSTGREB_REQUEST_TIMEOUT: ${self:custom.secrets.POSTGREB_REQUEST_TIMEOUT}
OTRA_VARIABLE_DE_ENTORNO: ${self:custom.secrets.OTRA_VARIABLE_DE_ENTORNO}
vpc:
securityGroupIds:
- ${self:custom.secrets.SECURITY_GROUP}
subnetIds:
- ${self:custom.secrets.SUBNET_ID}
deploymentPrefix: serverless
deploymentBucket:
name: ${param:deployBucket}
maxPreviousDeploymentArtifacts: 10
blockPublicAccess: true
skipPolicySetup: true
versioning: true
functions:
cronFunction:
handler: dist/handler.handler
layers:
- !Ref CronJobActualizarEstatusLambdaLayer
timeout: ${param:timeout, 900}
maximumRetryAttempts: ${param:maximumRetryAttempts, 0}
events:
- schedule:
rate:
- cron(10 12 * * ? *)
enabled: true
custom:
secrets: ${ssm:/aws/reference/secretsmanager/${param:sman,'cron-job-actualiza-ventas'}}
package:
exclude:
- .serverless/**
- .env
- .dccache
- .gitignore
- config_layers.js
- Macro.png
- README.md
- Dockerfile
- node_modules/**
- node_modules.zip
- package.json
- package-lock.json
- README.md
- serverless.yml
- src/**
- tsconfig.build.json
- tsconfig.json
layers:
cronJobActualizarEstatus:
package:
artifact: node_modules.zip
name: cronJobActualizarEstatus
compatibleRuntimes:
- nodejs18.x
Evidentemente son muchas partes en este código, pero las dos más importantes son la configuración de variables de entorno de la función Lambda.
environment:
NODE_PATH: /opt
TZ: America/Mexico_City
POSTGREB_USER: ${self:custom.secrets.POSTGREB_USER}
POSTGREB_PWD: ${self:custom.secrets.POSTGREB_PWD}
POSTGREB_SERVER: ${self:custom.secrets.POSTGREB_SERVER}
POSTGREB_DATABASE: ${self:custom.secrets.POSTGREB_DATABASE}
POSTGREB_POOL_MAX: ${self:custom.secrets.POSTGREB_POOL_MAX}
POSTGREB_POOL_MIN: ${self:custom.secrets.POSTGREB_POOL_MIN}
POSTGREB_CONNECT_TIMEOUT: ${self:custom.secrets.POSTGREB_CONNECT_TIMEOUT}
POSTGREB_REQUEST_TIMEOUT: ${self:custom.secrets.POSTGREB_REQUEST_TIMEOUT}
OTRA_VARIABLE_DE_ENTORNO: ${self:custom.secrets.OTRA_VARIABLE_DE_ENTORNO}ya
Y la invocación del secret:
custom:
secrets: ${ssm:/aws/reference/secretsmanager/${param:sman,'cron-job-actualiza-ventas'}}
Juntas estas partes, nos permiten centralizar nuestras variables de entorno, sin exponerlas y sin re-escribirlas cada vez que necesitamos desplegar la función.
La sección environment de nuestro serverless.yml especifica las variables de entorno que tendrán nuestras funciones lambda. Algunas pueden ir fijas, como NODE_PATH, donde se le indica al runtime de Node donde se encuentran los módulos que necesitará para su ejecución (util cuando manejamos Lambda Layers) y la otra es la variable TZ, que indica la zona horaria. Ambas variables no son confidenciales, pero ¿Qué hay de POSTGREB_USER y de POSTGREB_PWD? Estas variables son confidenciales! Sería una mala práctica tenerlas expuestas en el archivo de despliegue y que el equipo Devops lo tenga guardado como texto plano dentro de un archivo *.txt en sus computadoras tampoco es buena idea.
Si observamos la nomenclatura indicada en las variables de entorno, notaremos que el valor asignado es ${self:custom.secrets.POSTGREB_USER}. El valor self hace referencia al mismo documento serverless.yml, custom.secrets hace referencia a la sección custom.secrets, es decir:
custom:
secrets: ${ssm:/aws/reference/secretsmanager/${param:sman,'cron-job-actualiza-ventas'}}
Y por ultimo, POSTGREB_USER, hace referencia a la variable en custom.secrets.POSTGREB_USER. Lo que está pasando es que la invocación del secret manager, recupera un diccionario, por lo tanto, desde la sección de environment de nuestra Lambda, podemos acceder a variables específicas como si se tratara de un objeto en Js.
Cómo funciona la parte del secret manager
Empecemos por explicar esta nomenclatura:
ssm:/aws/reference/secretsmanager/
Notarás que ssm encaja con la abreviatura de Secrets Manager, sin embargo, esta abreviatura hace referencia a AWS Systems Managment, un servicio de administración y operación centralizado de recursos de AWS. De acuerdo a la documentación de AWS, este servicio sirve como un canalizador de los secretos almacenados en Secrets Manager a través de la funcionalidad de Params Store (útil para guardar configuraciones que pueden ser solicitadas desde otros servicios).
El hecho de que SSM solo sea un canalizador, significa que cuando es invocado, debe saber si hacemos referencia a un valor en el Param Store o un Secret de AWS Secrets Manager, por lo tanto, de aquí viene esta nomenclatura:
:/aws/reference/secretsmanager/
De acuerdo con la documentación de AWS, esta es la forma de hacer referencia a un secreto de Secrets Manager.
${ssm:/aws/reference/secretsmanager/${param:sman,’cron-job-actualiza-ventas’}}
Ahora solo queda pendiente el ${param:sman,’cron-job-actualiza-ventas’}}. Esta parte solo está indicando que el nombre del secreto se puede pasar como parámetro a través de la clave sman, es decir:
sls deploy — stage dev — param=”sman=otro-secreto-cron-job-actualiza-ventas”
O que puede tomar por defecto el secreto llamado ’cron-job-actualiza-ventas’.
¿Por qué? Bueno, yo lo configuré así debido a que el equipo Devops de la empresa donde trabajo utiliza su propia nomenclatura para nombrar los secretos de AWS Secrets Manager, y no querían estar usando un por defecto. Al permitirles pasar a serverless framework el nombre del secreto como parámetro, existe una mayor flexibilidad y rastreabilidad para el equipo Devops.
Otras partes importantes de la configuración
Existen otras partes importantes de la configuración, como el deployment bucket, las lambda layers, roles a utilizar, la VPC y usar parámetros de serverless para realizar algunas configuraciones que pudieran cambiar en un futuro. Tocaré estos temas en otros blogs.
Costos asociados
El uso de AWS Lambda, AWS Secrets Manager y el Serverless Framework conlleva ciertos costos que debes tener en cuenta.
- AWS Secrets Manager: Este servicio tiene un costo asociado por cada secreto almacenado y por cada 10,000 llamadas de API para recuperar un secreto.
Conclusión
El diseño de una arquitectura eficiente en AWS utilizando Lambda y Secrets Manager con el Serverless Framework ofrece numerosas ventajas, como la escalabilidad, la seguridad y la facilidad de despliegue. La implementación de buenas prácticas, como centralizar las variables de entorno en Secrets Manager y optimizar las llamadas a los servicios, no solo mejora la seguridad y la eficiencia, sino que también ayuda a reducir los errores operativos. Esta combinación es poderosa para el despliegue de aplicaciones serverless, permitiendo a los equipos centrarse en el desarrollo y la innovación, mientras se mantiene una gestión eficiente de los secretos y configuraciones.